Linux altar53.supremepanel53.com 4.18.0-553.8.1.lve.el8.x86_64 #1 SMP Thu Jul 4 16:24:39 UTC 2024 x86_64
/ lib/ python2.7/ site-packages/ pynag/ Parsers/ |
|
# -*- coding: utf-8 -*- """Module for talking to MK-Livestatus sockets.""" from __future__ import absolute_import import six import socket import sys import time import pynag.Parsers.errors import pynag.Parsers.main import pynag.Utils.paths # TODO remove this and raise proper exceptions from pynag.Parsers.errors import ParserError class Error(ParserError): """Base class for errors in this module.""" class LivestatusNotConfiguredException(Error): """ This exception is raised if we tried to autodiscover path to livestatus and failed """ class LivestatusError(Error): """ Used when we get errors from Livestatus """ class InvalidResponseFromLivestatus(Error): """Used when an unparsable response comes out of livestatus""" def __init__(self, query, response, *args, **kwargs): self.query = query self.response = response super(InvalidResponseFromLivestatus, self).__init__(*args, **kwargs) def __str__(self): message = 'Could not parse response from livestatus.\nQuery:{query}\nResponse: {response}' return message.format(query=self.query, response=self.response) class LivestatusQuery(object): """Convenience class to help construct a livestatus query. When talking to Livestatus we use the LQL - the Livestatus Query Language. Each query contains: * A Command in the form of 'GET <table>' (e.g. 'GET services') * Arbritary number of 'Header Lines' in the form of 'Header: Argument' * An Empty line, i.e. \n Example Livestatus Queries: 'GET contacts\n' 'GET contacts\nColumns: name alias\n' Examples on using this class: >>> query = LivestatusQuery('GET contacts') >>> query.get_query() 'GET contacts\\n\\n' >>> query.set_outputformat('python') >>> query.get_query() 'GET contacts\\nOutputFormat: python\\n\\n' For more information on Livestatus see: https://mathias-kettner.de/checkmk_livestatus.html """ # The following constants describe names of specific # Livestatus headers: _RESPONSE_HEADER = 'ResponseHeader' _OUTPUT_FORMAT = 'OutputFormat' _COLUMNS = 'Columns' _COLUMN_HEADERS = 'ColumnHeaders' _LIMIT = 'Limit' _AUTH_USER = 'AuthUser' _STATS = 'Stats' _FILTER = 'Filter' _OR = 'Or' # How a header line is formatted in a query _FORMAT_OF_HEADER_LINE = '{keyword}: {arguments}' # How a typical filter statement looks like: __FILTER_STATEMENT = 'Filter: {attribute} = {value}' # This is a mapping from 'pynag style' attribute suffixes into appropriate # livestatus filter statement. __FILTER_TRANSMUTATION_SUFFIX = { '__contains': 'Filter: {attribute} ~~ {value}', '__has_field': 'Filter: {attribute} >= {value}', '__isnot': 'Filter: {attribute} != {value}', '__startswith': 'Filter: {attribute} ~ ^{value}', '__endswith': 'Filter: {attribute} ~ {value}$', '__regex': 'Filter: {attribute} ~ {value}', '__gt': 'Filter: {attribute} > {value}', '__lt': 'Filter: {attribute} < {value}', } def __init__(self, query, *args, **kwargs): """Create a new LivestatusQuery. Args: query: String. Initial query (like GET hosts). Technically any object (not only str) having a `splitlines` method (accepting no arguments) and returning an iterable will be handled as well. *args: String. Any args will appended to the query as additional headers. **kwargs: String. Any kwargs will be treated like additional filter to our query. Examples: >>> query = LivestatusQuery('GET services') >>> query.get_query() 'GET services\\n\\n' >>> query = LivestatusQuery('GET services', 'OutputFormat: json') >>> query.get_query() 'GET services\\nOutputFormat: json\\n\\n' >>> query = LivestatusQuery('GET services', 'Columns: service_description', host_name='localhost') >>> query.get_query() 'GET services\\nColumns: service_description\\nFilter: host_name = localhost\\n\\n' """ self._query = [] # We purposefully strip white space, extra line breaks will # be added to the query string when get_query() is called. for header_line in query.strip().splitlines(): self.add_header_line(header_line) for header_line in args: self.add_header_line(header_line) self.add_filters(**kwargs) def __eq__(self, other): return self._query == other._query def _join_arguments_with_or(self, *args): """join multiple Livestatus Filter statements with a logical OR. Args: *args: List of strings like: ['Filter: state = 0', 'Filter: state = 1'] Returns: List of strings. Same as args except 'Or: %s' % len(args) is added to the list. Examples: >>> query = LivestatusQuery('') >>> query._join_arguments_with_or('', '', '') ['', '', '', 'Or: 3'] >>> query._join_arguments_with_or('') [''] """ args = list(args) length_of_arguments = len(args) if length_of_arguments > 1: or_statement = 'Or: %s' % len(args) args.append(or_statement) return args def get_query(self): """Get a string representation of our query Example: >>> query = LivestatusQuery('GET services') >>> query.get_query() 'GET services\\n\\n' >>> query.add_header('Filter', 'host_name = foo') >>> query.get_query() 'GET services\\nFilter: host_name = foo\\n\\n' Returns: A string. String representation of our query that is compatibe with livestatus. """ return '\n'.join(self._query) + '\n\n' def get_header(self, keyword): """Get first header found with keyword in it. Examples: >>> query = LivestatusQuery('GET services') >>> query.get_header('OutputFormat') # Returns None >>> query.set_outputformat('python') >>> query.get_header('OutputFormat') 'python' """ signature = keyword + ':' for header in self._query: if header.startswith(signature): argument = header.split(':', 1)[1] argument = argument.strip() return argument def column_headers(self): """Check if ColumnHeaders are on or off. Example: >>> query = LivestatusQuery('GET services') >>> query.column_headers() # If not set, they are off False >>> query.set_columnheaders('on') >>> query.column_headers() True """ column_headers = self.get_header(self._COLUMN_HEADERS) if not column_headers or column_headers == 'off': return False if column_headers == 'on': return True raise LivestatusError("Not sure if ColumnHeaders are on or off, got '%s'" % column_headers) def output_format(self): """Return the currently configured OutputFormat if any is set. Examples: >>> query = LivestatusQuery('GET services') >>> query.output_format() # Returns None >>> query.set_outputformat('python') >>> query.output_format() 'python' """ return self.get_header(self._OUTPUT_FORMAT) def add_header_line(self, header_line): """Add a new header line to our livestatus query Example: >>> query = LivestatusQuery('GET services') >>> query.add_header_line('Filter: host_name = foo') >>> query.get_query() 'GET services\\nFilter: host_name = foo\\n\\n' """ self._query.append(header_line) def add_header(self, keyword, arguments): """Add a new header to our livestatus query. Example: >>> query = LivestatusQuery('GET services') >>> query.add_header('Filter', 'host_name = foo') >>> query.get_query() 'GET services\\nFilter: host_name = foo\\n\\n' """ header_line = self._FORMAT_OF_HEADER_LINE.format(keyword=keyword, arguments=arguments) self.add_header_line(header_line) def has_header(self, keyword): """ Returns True if specific header is in current query. Examples: >>> query = LivestatusQuery('GET services') >>> query.has_header('OutputFormat') False >>> query.add_header('OutputFormat', 'fixed16') >>> query.has_header('OutputFormat') True """ signature = keyword + ':' for row in self._query: if row.startswith(signature): return True return False def remove_header(self, keyword): """Remove a header from our query Examples: >>> query = LivestatusQuery('GET services') >>> query.has_header('OutputFormat') False >>> query.add_header('OutputFormat', 'fixed16') >>> query.has_header('OutputFormat') True >>> query.remove_header('OutputFormat') >>> query.has_header('OutputFormat') False """ signature = keyword + ':' self._query = [x for x in self._query if not x.startswith(signature)] def set_responseheader(self, response_header='fixed16'): """Set ResponseHeader to our query. Args: response_header: String. Response header that livestatus knows. Example: fixed16 Example: >>> query = LivestatusQuery('GET services') >>> query.set_responseheader() >>> query.get_query() 'GET services\\nResponseHeader: fixed16\\n\\n' """ # First remove whatever responseheader might have been set before self.remove_header(self._RESPONSE_HEADER) self.add_header(self._RESPONSE_HEADER, response_header) def set_outputformat(self, output_format): """Set OutFormat header in our query. Example: >>> query = LivestatusQuery('GET services') >>> query.set_outputformat('json') >>> query.get_query() 'GET services\\nOutputFormat: json\\n\\n' """ # Remove outputformat if it was already in out query self.remove_header(self._OUTPUT_FORMAT) self.add_header(self._OUTPUT_FORMAT, output_format) def set_columnheaders(self, status='on'): """Turn on or off ColumnHeaders Example: >>> query = LivestatusQuery('GET services') >>> query.set_columnheaders('on') >>> query.get_query() 'GET services\\nColumnHeaders: on\\n\\n' >>> query.set_columnheaders('off') >>> query.get_query() 'GET services\\nColumnHeaders: off\\n\\n' """ self.remove_header(self._COLUMN_HEADERS) self.add_header(self._COLUMN_HEADERS, status) def set_authuser(self, auth_user): """Set AuthUser in our query. Example: >>> query = LivestatusQuery('GET services') >>> query.set_authuser('nagiosadmin') >>> query.get_query() 'GET services\\nAuthUser: nagiosadmin\\n\\n' """ self.remove_header(self._AUTH_USER) self.add_header(self._AUTH_USER, auth_user) def has_responseheader(self): """ Check if there are any ResponseHeaders set. Example: >>> query = LivestatusQuery('GET services') >>> query.has_responseheader() False >>> query.set_responseheader('fixed16') >>> query.has_responseheader() True Returns: Boolean. True if query has any ResponseHeader, otherwise False. """ return self.has_header(self._RESPONSE_HEADER) def has_authuser(self): """ Check if AuthUser is set. Example: >>> query = LivestatusQuery('GET services') >>> query.has_authuser() False >>> query.set_authuser('nagiosadmin') >>> query.has_authuser() True Returns: Boolean. True if query has any AuthUser, otherwise False. """ return self.has_header(self._AUTH_USER) def has_outputformat(self): """ Check if OutputFormat is set. Example: >>> query = LivestatusQuery('GET services') >>> query.has_outputformat() False >>> query.set_outputformat('python') >>> query.has_outputformat() True Returns: Boolean. True if query has any OutputFormat set, otherwise False. """ return self.has_header(self._OUTPUT_FORMAT) def has_columnheaders(self): """ Check if there are any ColumnHeaders set. Example: >>> query = LivestatusQuery('GET services') >>> query.has_columnheaders() False >>> query.set_columnheaders('on') >>> query.has_columnheaders() True Returns: Boolean. True if query has any ColumnHeaders, otherwise False. """ return self.has_header(self._COLUMN_HEADERS) def has_stats(self): """ Returns True if Stats headers are present in our query. Example: >>> query = LivestatusQuery('GET services') >>> query.has_stats() False >>> query.add_header('Stats', 'state = 0') >>> query.has_stats() True Returns: Boolean. True if query has any stats, otherwise False. """ return self.has_header(self._STATS) def has_filters(self): """ Returns True if any filters are applied. Example: >>> query = LivestatusQuery('GET services') >>> query.has_filters() False >>> query.add_header('Filter', 'host_name = localhost') >>> query.has_filters() True Returns: Boolean. True if query has any filters, otherwise False. """ return self.has_header(self._FILTER) def __str__(self): """Wrapper around self.get_query(). Example: >>> query = LivestatusQuery('GET services', 'Columns: host_name') >>> str(query) 'GET services\\nColumns: host_name\\n\\n' """ return self.get_query() def splitlines(self, *args, **kwargs): """ Wrapper around str(self).splitlines(). This function is here for backwards compatibility because a lot of callers were previously passing in strings, but are now passing in LivestatusQuery. For this purpose we behave like a string. Example: >>> query = LivestatusQuery('GET services', 'Columns: host_name') >>> query.splitlines() ['GET services', 'Columns: host_name', ''] """ querystring = str(self) return querystring.splitlines(*args, **kwargs) def split(self, *args, **kwargs): """ Wrapper around str(self).split(). This function is here for backwards compatibility because a lot of callers were previously passing in strings, but are now passing in LivestatusQuery. For this purpose we behave like a string. Example: >>> query = LivestatusQuery('GET services', 'Columns: host_name') >>> query.split('\\n') ['GET services', 'Columns: host_name', '', ''] """ querystring = str(self) return querystring.split(*args, **kwargs) def strip(self, *args, **kwargs): """ Wrapper around str(self).strip(). This function is here for backwards compatibility because a lot of callers were previously passing in strings, but are now passing in LivestatusQuery. For this purpose we behave like a string. Example: >>> query = LivestatusQuery('GET services') >>> str(query) 'GET services\\n\\n' >>> query.strip() 'GET services' """ return str(self).strip(*args, **kwargs) def startswith(self, *args, **kwargs): """ Wrapper around str(self).startswith(). This function is here for backwards compatibility because a lot of callers were previously passing in strings, but are now passing in LivestatusQuery. For this purpose we behave like a string. Example: >>> query = LivestatusQuery('GET services') >>> str(query) 'GET services\\n\\n' >>> query.startswith('GET') True """ return str(self).startswith(*args, **kwargs) def convert_key_value_to_filter_statement(self, attribute, value): """Convert a key/value pair to a Livestatus compatible Filter: statement. Args: attribute: String. Name of a single livestatus attribute, for example 'host_name'. the attribute can have a 'pynag style' suffix which is a hint to what kind of filtering will be applied. See the examples section for an idea how this is applied. value: String. Single value to filter by, for example: 'localhost' Returns: String. A single livestatus compatible filter statement. See Examples section for an idea of what return statement looks like. Examples: >>> query = LivestatusQuery('') >>> query.convert_key_value_to_filter_statement('host_name', 'test') 'Filter: host_name = test' >>> query.convert_key_value_to_filter_statement('service_description__contains', 'serv') 'Filter: service_description ~~ serv' >>> query.convert_key_value_to_filter_statement('service_description__isnot', 'serv') 'Filter: service_description != serv' >>> query.convert_key_value_to_filter_statement('service_description__has_field', 'foo') 'Filter: service_description >= foo' >>> query.convert_key_value_to_filter_statement('service_description__startswith', 'foo') 'Filter: service_description ~ ^foo' >>> query.convert_key_value_to_filter_statement('service_description__endswith', 'foo') 'Filter: service_description ~ foo$' >>> query.convert_key_value_to_filter_statement('state__gt', '0') 'Filter: state > 0' >>> query.convert_key_value_to_filter_statement('state__lt', '1') 'Filter: state < 1' """ # Check if attribute ends with any of the suffixes in __FILTER_TRANSMUTATION_SUFFIX # For example if attribute ends with '__contains' we want the end result # to be 'Filter: attribute ~ value' (notice the ~ instead of =) for suffix, potential_filter_statement in self.__FILTER_TRANSMUTATION_SUFFIX.items(): if attribute.endswith(suffix): suffix_length = len(suffix) attribute = attribute[:-suffix_length] filter_statement = potential_filter_statement break else: filter_statement = self.__FILTER_STATEMENT return filter_statement.format(attribute=attribute, value=value) def create_filter_statement(self, attribute, values): """Create a Livestatus filter statement from a key/value pair. Args: attribute: String. Name of a livestatus attribute, example: 'host_name' values: List of strings. If more than one value is provided the resulting filter query will be be joined with a logical OR. Returns: List of strings. List of Livestatus Filter statements. Look at Examples section for an idea of what return result looks like. Examples: >>> query = LivestatusQuery('') >>> query.create_filter_statement('host', 'localhost') ['Filter: host = localhost'] >>> query.create_filter_statement('host', ['localhost', 'remote_host']) ['Filter: host = localhost', 'Filter: host = remote_host', 'Or: 2'] """ return_arguments = [] if not isinstance(values, list): values = [values] for value in values: filter_statement = self.convert_key_value_to_filter_statement(attribute, value) return_arguments.append(filter_statement) if len(return_arguments) > 1: return_arguments = self._join_arguments_with_or(*return_arguments) return return_arguments def add_filter(self, attribute, value): """Adds a single filter statement to current query. Args: attribute. String. Attribute to search for. value: String. Value to search for. >>> query = LivestatusQuery('GET services') >>> query.add_filter('host_name', 'localhost') >>> query.get_query() 'GET services\\nFilter: host_name = localhost\\n\\n' """ filter_statements = self.create_filter_statement(attribute, value) for statement in filter_statements: self.add_header_line(statement) def add_filters(self, **kwargs): """Add a new filter statement to current query. Args: **kwargs: Every key/value pair is a string. See examples for ideas. >>> query = LivestatusQuery('GET services') >>> query.add_filters(host_name='localhost') >>> query.get_query() 'GET services\\nFilter: host_name = localhost\\n\\n' >>> query.add_filters(description__contains='Ping') >>> query.get_query() 'GET services\\nFilter: host_name = localhost\\nFilter: description ~~ Ping\\n\\n' """ for key, value in kwargs.items(): self.add_filter(key, value) def set_columns(self, *columns): """Set a Columns header to our query with the specified Columns. Args: *columns: List of strings. Examples: ['host_name','description'] Examples: >>> query = LivestatusQuery('GET hosts') >>> query.set_columns('name', 'address') >>> query.get_query() 'GET hosts\\nColumns: name address\\n\\n' """ self.remove_header(self._COLUMNS) self.add_header(self._COLUMNS, ' '.join(columns)) def add_or_statement(self, number): """Adds an OR statement to the current set of header lines. Args: number: Integer. Tells how many arguments should be joined together. Examples: >>> query = LivestatusQuery('GET hosts') >>> query.add_filters(name='localhost') >>> query.add_filters(name='otherhost') >>> query.add_or_statement(2) >>> query.get_query() 'GET hosts\\nFilter: name = localhost\\nFilter: name = otherhost\\nOr: 2\\n\\n' """ self.add_header(self._OR, number) def set_limit(self, limit): """Set a Limit header to our query. Args: limit: Limit results to this number (integer) Examples: >>> query = LivestatusQuery('GET hosts') >>> query.set_limit(5) >>> query.get_query() 'GET hosts\\nLimit: 5\\n\\n' """ limit = int(limit) self.remove_limit() self.add_header(self._LIMIT, limit) def remove_limit(self): """Remove limit header from this query. Examples: >>> query = LivestatusQuery('GET hosts') >>> query.set_limit(5) >>> query.get_query() 'GET hosts\\nLimit: 5\\n\\n' >>> query.remove_limit() >>> query.get_query() 'GET hosts\\n\\n' """ self.remove_header(self._LIMIT) class Livestatus(object): """ Class for communicating with Livestatus. Example usage:: s = Livestatus() for hostgroup s.get_hostgroups(): print(hostgroup['name'], hostgroup['num_hosts']) For more information on Livestatus see: https://mathias-kettner.de/checkmk_livestatus.html """ # Its relatively common that livestatus queries fail because they are made # at the same time as nagios is being reloaded. For that reason we introduce # one retry on failed queries after waiting for _RETRY_INTERVAL seconds. _RETRY_INTERVAL = 0.5 def __init__(self, livestatus_socket_path=None, nagios_cfg_file=None, authuser=None): """ Initilize a new instance of Livestatus Args: livestatus_socket_path: Path to livestatus socket (if none specified, use one specified in nagios.cfg) nagios_cfg_file: Path to your nagios.cfg. If None then try to auto-detect authuser: If specified. Every data pulled is with the access rights of that contact. """ self.nagios_cfg_file = nagios_cfg_file self.error = None if not livestatus_socket_path: main_config = pynag.Parsers.main.MainConfig(nagios_cfg_file) # Look for a broker_module line in the main config and parse its arguments # One of the arguments is path to the file socket created for broker_module in main_config.get_list('broker_module'): if "livestatus.o" or "livestatus.so" in broker_module: for arg in broker_module.split()[1:]: if arg.startswith('/') or '=' not in arg: livestatus_socket_path = arg break else: # If we get here, then we could not locate a broker_module argument # that looked like a filename msg = "No Livestatus socket defined. Make sure livestatus broker module is loaded." raise ParserError(msg) self.livestatus_socket_path = livestatus_socket_path self.authuser = authuser def test(self, raise_error=True): """ Test if connection to livestatus socket is working Args: raise_error: If set to True, raise exception if test fails,otherwise return False Raises: ParserError if raise_error == True and connection fails Returns: True -- Connection is OK False -- there are problems and raise_error==False """ try: self.query("GET hosts") except Exception: t, e = sys.exc_info()[:2] self.error = e if raise_error: raise ParserError("got '%s' when testing livestatus socket. error was: '%s'" % (type(e), e)) else: return False return True def _get_socket(self): """ Returns a socket.socket() instance to communicate with livestatus Socket might be either unix filesocket or a tcp socket depending in the content of :py:attr:`livestatus_socket_path` Returns: Socket to livestatus instance (socket.socket) Raises: :py:class:`LivestatusNotConfiguredException` on failed connection. :py:class:`ParserError` If could not parse configured TCP address correctly. """ if not self.livestatus_socket_path: msg = ("We could not find path to MK livestatus socket file." "Make sure MK livestatus is installed and configured") raise LivestatusNotConfiguredException(msg) try: # If livestatus_socket_path contains a colon, then we assume that # it is tcp socket instead of a local filesocket if self.livestatus_socket_path.find(':') > 0: address, tcp_port = self.livestatus_socket_path.split(':', 1) if not tcp_port.isdigit(): msg = 'Could not parse host:port "%s". This "%s" does not look like a valid tcp port.' raise ParserError(msg % (self.livestatus_socket_path, tcp_port)) tcp_port = int(tcp_port) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((address, tcp_port)) else: s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) s.connect(self.livestatus_socket_path) return s except IOError: t, e = sys.exc_info()[:2] msg = "%s while connecting to '%s'. Make sure nagios is running and mk_livestatus loaded." raise ParserError(msg % (e, self.livestatus_socket_path)) def write(self, livestatus_query): """ Send a raw livestatus query to livestatus socket. Queries are corrected and convienient default data are added to the query before sending it to the socket. Returns: A string. The result that comes back from our livestatus socket. Raises: LivestatusError if there is a problem writing to socket. """ # Lets create a socket and see if we can write to it livestatus_socket = self._get_socket() if not six.PY2 and not isinstance(livestatus_query, six.binary_type): # socket.send() requires binary argument livestatus_query = livestatus_query.encode() try: livestatus_socket.send(livestatus_query) livestatus_socket.shutdown(socket.SHUT_WR) filesocket = livestatus_socket.makefile() result = filesocket.read() return result except IOError: msg = "Could not write to socket '%s'. Make sure you have the right permissions" raise LivestatusError(msg % self.livestatus_socket_path) finally: livestatus_socket.close() def raw_query(self, query, *args, **kwargs): """ Perform LQL queries on the livestatus socket. Args: query: String. Query to be passed to the livestatus socket *args: String. Will be appended to query **kwargs: String. Will be appended as 'Filter:' to query. For example name='foo' will be appended as 'Filter: name = foo' In most cases if you already have constructed a livestatus query, you should only need the query argument, args and kwargs can be used to assist in constructing the query. For example, the following calls all construct equalant queries: l = Livestatus() l.query('GET status\nColumns: requests\n') l.query('GET status'. 'Columns: requests') l.query('GET status', Columns:'requests') Returns: A string. The results that come out of our livestatus socket. Raises: LivestatusError: If there are problems with talking to socket. """ livestatus_query = LivestatusQuery(query, *args, **kwargs) response = self.write(str(livestatus_query)) return response def _parse_response_header(self, livestatus_response): if not livestatus_response: raise LivestatusError("Can't parse empty livestatus response") rows = livestatus_response.splitlines() header = rows.pop(0) data = '\n'.join(rows) return_code = header.split()[0] if not return_code.startswith('2'): error_message = header.strip() raise LivestatusError("Error '%s' from livestatus: %s" % (return_code, data)) return data def _process_query(self, livestatus_query): """ Applies pynag specific quirks and defaults to a Livestatus Query. The following will be added to our livestatus_query automatically: * If AuthUser is not specified, we add self.authuser. * If OutputFormat is not specified, we add python. * If ResponseHeader is not specified, we add fixed16. * If ColumnHeaders are not specified, we turn them on. * If Stats are specified, we turn ColumnHeaders off. Args: livestatus_query: LivestatusQuery. The query we will process. """ # Implicitly add ResponseHeader if none was specified if not livestatus_query.has_responseheader(): livestatus_query.set_responseheader('fixed16') # Implicitly add OutputFormat if none was specified if not livestatus_query.has_outputformat(): livestatus_query.set_outputformat('python') # Implicitly turn ColumnHeaders on if none we specified if not livestatus_query.has_columnheaders(): livestatus_query.set_columnheaders('on') # Implicitly add AuthUser if one was configured: if self.authuser and not livestatus_query.has_authuser(): livestatus_query.set_authuser(self.authuser) # This piece of code is here to workaround a bug in livestatus when # livestatus_query contains 'Stats' and ColumnHeaders are on. # * Old behavior: Livestatus turns columnheaders explicitly off. # * New behavior: Livestatus gives us headers, but corrupted data. # # Out of 2 evils we maintain consistency by choosing the older # behavior of always turning of columnheaders for Stats. if livestatus_query.has_stats(): livestatus_query.set_columnheaders('off') def _process_response(self, response_data): """Returns livestatus response in a structured format. Args: response_data: List of strings. Output from livestatus with PythonFormat On and without Response headers. Every string in the list represents a single line of livestatus output. Returns: * List of dicts. * Every element in the list represents one row from livestatus. * Every dict is a {'str':'str'} where the keys are column names and the values are column values. """ column_headers = response_data.pop(0) # Lets throw everything into a hashmap before we return result = [] for line in response_data: current_row = {} for i, value in enumerate(line): column_name = column_headers[i] current_row[column_name] = value result.append(current_row) return result def query(self, query, *args, **kwargs): """ Performs LQL queries on the livestatus socket. Note: The incoming query is mangled and various default headers are set on, for more details see _process_query(). Use raw_query() instead if you need more control over the input and output of Livestatus queries. Args: query: String. Query to be passed to the livestatus socket *args: String. Will be appended to query **kwargs: Will be appended as 'Filter:' to query. For example name='foo' will be appended as 'Filter: name = foo' Returns: List of dicts. Every item in the list is a row from livestatus and every row is a dictionary where the keys are column names and values are columns. Example return value: [{'host_name': 'localhost', 'service_description':'Ping'},] Raises: LivestatusError: If there is a problem talking to livestatus socket. """ # columns parameter exists for backwards compatibility only. # By default ColumnHeaders are 'on'. kwargs.pop('columns', None) livestatus_query = LivestatusQuery(query, *args, **kwargs) self._process_query(livestatus_query) # This is we actually send our query into livestatus. livestatus_response is the raw response # from livestatus socket (string): try: livestatus_response = self.write(livestatus_query.get_query()) except LivestatusError: time.sleep(self._RETRY_INTERVAL) livestatus_response = self.raw_query(livestatus_query) if not livestatus_response: raise InvalidResponseFromLivestatus(query=livestatus_query, response=livestatus_response) # Parse the response header from livestatus, will raise an exception if # livestatus returned an error: response_data = self._parse_response_header(livestatus_response) if livestatus_query.output_format() != 'python': return response_data # Return empty list if we got no results if not response_data: return [] try: response_data = eval(response_data) except Exception: raise InvalidResponseFromLivestatus(query=livestatus_query, response=response_data) # Usually when we query livestatus we get back a 'list of rows', # however Livestatus had a quirk in the past that if there were Stats # in the query Instead of returning rows, it would just return a list # of stats. For backwards Compatibility we cling on to the old bug, of # not returning 'rows' when asking for stats. if livestatus_query.has_stats() and len(response_data) == 1: return response_data[0] # Backwards compatibility. if ColumnHeaders=Off, we return livestatus # original format of lists of lists (instead of lists of dicts) if not livestatus_query.column_headers(): return response_data return self._process_response(response_data) def get(self, table, *args, **kwargs): """ Same as self.query('GET %s' % (table,)) Extra arguments will be appended to the query. Args: table: Table from which the data will be retrieved args, kwargs: These will be appendend to the end of the query to perform additional instructions. Example:: get('contacts', 'Columns: name alias') Returns: Answer from livestatus in python format. """ return self.query('GET %s' % (table,), *args, **kwargs) def get_host(self, host_name): """ Performs a GET query for a particular host This performs:: '''GET hosts Filter: host_name = %s''' % host_name Args: host_name: name of the host to obtain livestatus data from Returns: Answer from livestatus in python format. """ return self.query('GET hosts', 'Filter: host_name = %s' % host_name)[0] def get_service(self, host_name, service_description): """ Performs a GET query for a particular service This performs:: '''GET services Filter: host_name = %s Filter: service_description = %s''' % (host_name, service_description) Args: host_name: name of the host the target service is attached to. service_description: Description of the service to obtain livestatus data from. Returns: Answer from livestatus in python format. """ return self.query('GET services', 'Filter: host_name = %s' % host_name, 'Filter: description = %s' % service_description)[0] def get_hosts(self, *args, **kwargs): """ Performs a GET query for all hosts This performs:: '''GET hosts %s %s''' % (*args, **kwargs) Args: args, kwargs: These will be appendend to the end of the query to perform additional instructions. Returns: Answer from livestatus in python format. """ return self.query('GET hosts', *args, **kwargs) def get_services(self, *args, **kwargs): """ Performs a GET query for all services This performs:: '''GET services %s %s''' % (*args, **kwargs) Args: args, kwargs: These will be appendend to the end of the query to perform additional instructions. Returns: Answer from livestatus in python format. """ return self.query('GET services', *args, **kwargs) def get_hostgroups(self, *args, **kwargs): """ Performs a GET query for all hostgroups This performs:: '''GET hostgroups %s %s''' % (*args, **kwargs) Args: args, kwargs: These will be appendend to the end of the query to perform additional instructions. Returns: Answer from livestatus in python format. """ return self.query('GET hostgroups', *args, **kwargs) def get_servicegroups(self, *args, **kwargs): """ Performs a GET query for all servicegroups This performs:: '''GET servicegroups %s %s''' % (*args, **kwargs) Args: args, kwargs: These will be appendend to the end of the query to perform additional instructions. Returns: Answer from livestatus in python format. """ return self.query('GET servicegroups', *args, **kwargs) def get_contactgroups(self, *args, **kwargs): """ Performs a GET query for all contactgroups This performs:: '''GET contactgroups %s %s''' % (*args, **kwargs) Args: args, kwargs: These will be appendend to the end of the query to perform additional instructions. Returns: Answer from livestatus in python format. """ return self.query('GET contactgroups', *args, **kwargs) def get_contacts(self, *args, **kwargs): """ Performs a GET query for all contacts This performs:: '''GET contacts %s %s''' % (*args, **kwargs) Args: args, kwargs: These will be appendend to the end of the query to perform additional instructions. Returns: Answer from livestatus in python format. """ return self.query('GET contacts', *args, **kwargs) def get_contact(self, contact_name): """ Performs a GET query for a particular contact This performs:: '''GET contacts Filter: contact_name = %s''' % contact_name Args: contact_name: name of the contact to obtain livestatus data from Returns: Answer from livestatus in python format. """ return self.query('GET contacts', 'Filter: contact_name = %s' % contact_name)[0] def get_servicegroup(self, name): """ Performs a GET query for a particular servicegroup This performs:: '''GET servicegroups Filter: servicegroup_name = %s''' % servicegroup_name Args: servicegroup_name: name of the servicegroup to obtain livestatus data from Returns: Answer from livestatus in python format. """ return self.query('GET servicegroups', 'Filter: name = %s' % name)[0] def get_hostgroup(self, name): """ Performs a GET query for a particular hostgroup This performs:: '''GET hostgroups Filter: hostgroup_name = %s''' % hostgroup_name Args: hostgroup_name: name of the hostgroup to obtain livestatus data from Returns: Answer from livestatus in python format. """ return self.query('GET hostgroups', 'Filter: name = %s' % name)[0] def get_contactgroup(self, name): """ Performs a GET query for a particular contactgroup This performs:: '''GET contactgroups Filter: contactgroup_name = %s''' % contactgroup_name Args: contactgroup_name: name of the contactgroup to obtain livestatus data from Returns: Answer from livestatus in python format. """ return self.query('GET contactgroups', 'Filter: name = %s' % name)[0]