#! /usr/bin/python import os, json, socket, struct, logging, time, urllib2, platform, threading # Some day this will use "real" Zabbix auth and become more complicated. ZABBIX_API_SERVER = 'zabbix-api.example.com' ZABBIX_KEY = 'xyzzy-plugh-plover' class ZabbixActiveSender(object): '''This will send values to Zabbix via the same mechanism zabbix_sender uses. ''' def __init__(self, zabbixserver=None, prefix = None, no_send=False, clienthost=platform.node()): self.lock = threading.Lock() self.zabbixserver = zabbixserver self.clienthost = clienthost # This better be the FQDN logging.debug('ZAS.__init__: clienthost="%s"', self.clienthost) self.prefix = prefix self.no_send = no_send self.check_for_proxy() self.clear() logging.debug('ZAS.__init__: zabbixserver="%s"', str(self.zabbixserver)) if os.environ.has_key('http_proxy'): del os.environ['http_proxy'] logging.debug('ZAS.__init__: removed http_proxy from the environment') return def check_for_proxy(self): '''If the zabbixserver was specified, we trust that (e.g. for zabbix-stage.llnw.net). Otherwise, we go looking for the proxy that owns us. ''' if self.zabbixserver: return postdata = { "key": ZABBIX_KEY, "method": "proxymap.get", "params": { "hostnames": [ os.uname()[1] ], "output": "json" } } data = urllib2.urlopen('http://'+ZABBIX_API_SERVER+'/llnw/api_jsonrpc.php',json.dumps(postdata)) self.zabbixserver = json.load(data)['result'][0]['proxy'] return def __call__(self, key, value): '''Load up our dictionary with data preparatory to sending it on to Zabbix. If we're debugging, we'll immediately send each value, which makes it a *lot* easier to figure out where we've got problems.''' logging.debug('ZAS.__call__: %30s\t%s', key, value) if self.prefix: key = self.prefix+key self.lock.acquire() self.data.append({"key":key, "value":value, "host":self.clienthost, "clock":int(time.time())}) self.lock.release() if logging.root.level < logging.INFO: self.send() return def send(self): '''Ship the data to Zabbix. Call as often as you like, though of course it's more efficient to call it just once after you've accumulated all of the data you'd like to send. The magic in here with structs is required to format everything for Zabbix' wire protocol. If this breaks, it's time to dive into the Zabbix internals again. ''' self.lock.acquire() if not self.data: self.lock.release() return # Nothing to send! if not self.no_send: response = {"request":"agent data", "clock":int(time.time()), "data":self.data} string = json.dumps(response) logging.debug(string) string_to_send = 'ZBXD\x01%s%s' % (struct.pack('<q', len(string)), string) s = socket.create_connection((self.zabbixserver, 10051)) s.sendall(string_to_send) s.shutdown(socket.SHUT_WR) try: retstring = s.recv(5) # Header, don't care: 'ZBXD\01' datalen = struct.unpack('<q',s.recv(8))[0] # Length of data being returned retstring = s.recv(datalen) # Actual return string logging.debug(retstring) except Exception as e: logging.debug('ZAS.send: Failed to get a proper response: %s (%s)', retstring, e) self.clear() self.lock.release() return def clear(self): self.data = []