def parse_filter_line(self, line): """ Map some operators to theyr complementary form to reduce the cases to manage. :param str operator: The operator to analyze :return: A tuple with operator, alias """ # Rows should have patterns # Filter: state = 3 # Stats: avg execution_time = ... # Stats: avg execution_time as exec_time parts = len(line.split()) if parts >= 4: _, attribute, operator, reference = self.split_option(line, 3) elif parts >= 3: _, attribute, operator = self.split_option(line, 2) reference = '' else: raise LiveStatusQueryError(450, 'invalid filter: %s' % line) # Parses a row with patterns like: # Filter: state = 3 # Or # Stats: scheduled_downtime_depth = 0 if operator in [ '=', '>', '>=', '<', '<=', '=~', '~', '~~', '!=', '!>', '!>=', '!<', '!<=', '!=~', '!~', '!~~' ]: # Some operators can simply be negated if operator in ['!>', '!>=', '!<', '!<=']: operator = { '!>': '<=', '!>=': '<', '!<': '>=', '!<=': '>' }[operator] return attribute, operator, reference # Parses a row with patterns like: # Stats: avg execution_time # Stats: avg execution_time = ... # Stats: avg execution_time as exec_time elif attribute in ['sum', 'min', 'max', 'avg', 'std']: operator, attribute = attribute, operator if reference.startswith('as '): _, alias = reference.split(' ', 2) return attribute, operator, None else: raise LiveStatusQueryError(450, 'invalid filter: %s' % line)
def format_item(self, item, columns): """ Format an item returing the requested columns only :param dict item: The item to format :param list columns: The requested columns :rtype: list :return: The object's columns """ row = [] for column in columns: try: mapping = self.query.mapping[self.query.table][column] attr = mapping.get('filters', {}).get('attr', column) if "function" in mapping: value = mapping["function"](item) elif "datatype" in mapping: datatype = mapping["datatype"] default = datatype() value = item.get(attr, default) value = datatype(value) else: value = item.get(attr, "") row.append(value) except Exception as e: logger.debug(traceback.format_exc()) raise LiveStatusQueryError( 500, "failed to map value %s/%s: %s" % (self.query.table, column, e)) return row
def ne_filter(item): try: return getattr(item, attribute)(self) != reference except Exception: if hasattr(item, attribute): return getattr(item.__class__, attribute).im_func.default != reference else: raise LiveStatusQueryError(450, attribute.replace('lsm_', ''))
def ge_contains_filter(item): try: if getattr(item, attribute).im_func.datatype == list: return reference in getattr(item, attribute)(self) else: return getattr(item, attribute)(self) >= reference except Exception: if hasattr(item, attribute): return getattr(item.__class__, attribute).im_func.default >= reference else: raise LiveStatusQueryError(450, attribute.replace('lsm_', ''))
def add_filter(self, stack, operator, attribute, reference): """ Builds a mongo filter from the comparison operation :param list stack: The stack to append filter to :param str operator: The comparison operator :param str attribute: The attribute name to compare :param str reference: The reference value to compare to """ if attribute not in self.datamgr.mapping[self.table]: raise LiveStatusQueryError( 450, "no column %s in table %s" % (attribute, self.table)) # Map livestatus attribute `name` to the object's name fct = self.operator_mapping.get(operator) # Matches operators if fct: if operator in ("sum", "min", "max", "avg", "count"): fct(stack, self.table, attribute, self.columns) else: fct(stack, self.table, attribute, reference) else: raise LiveStatusQueryError(450, 'invalid filter: %s' % line)
def match_nocase_filter(item): try: p = re.compile(str(reference), re.I) return p.search(getattr(item, attribute)(self)) except Exception: raise LiveStatusQueryError(450, attribute.replace('lsm_', ''))
def parse_input(self, data): """Parse the lines of a livestatus request. This function looks for keywords in input lines and sets the attributes of the request object """ for line in data.splitlines(): line = line.strip() # Tools like NagVis send KEYWORK:option, and we prefer to have # a space following the: if ':' in line and not ' ' in line: line = line.replace(':', ': ') keyword = line.split(' ')[0].rstrip(':') if keyword == 'GET': # Get the name of the base table _, self.table = self.split_command(line) if self.table not in table_class_map.keys(): raise LiveStatusQueryError(404, self.table) elif keyword == 'Columns': # Get the names of the desired columns _, self.columns = self.split_option_with_columns(line) self.response.columnheaders = 'off' elif keyword == 'ResponseHeader': _, responseheader = self.split_option(line) self.response.responseheader = responseheader elif keyword == 'OutputFormat': _, outputformat = self.split_option(line) self.response.outputformat = outputformat elif keyword == 'KeepAlive': _, keepalive = self.split_option(line) self.response.keepalive = keepalive elif keyword == 'ColumnHeaders': _, columnheaders = self.split_option(line) self.response.columnheaders = columnheaders elif keyword == 'Limit': _, self.limit = self.split_option(line) elif keyword == 'AuthUser': if self.table in [ 'hosts', 'hostgroups', 'services', 'servicegroups', 'hostsbygroup', 'servicesbygroup', 'servicesbyhostgroup' ]: _, self.authuser = self.split_option(line) # else self.authuser stays None and will be ignored elif keyword == 'Filter': try: _, attribute, operator, reference = self.split_option( line, 3) except ValueError as err: try: _, attribute, operator, reference = self.split_option( line, 2) + [''] except ValueError as err: raise LiveStatusQueryError(452, 'invalid Filter header') if operator in [ '=', '>', '>=', '<', '<=', '=~', '~', '~~', '!=', '!>', '!>=', '!<', '!<=', '!=~', '!~', '!~~' ]: # Cut off the table name attribute = self.strip_table_from_column(attribute) # Some operators can simply be negated if operator in ['!>', '!>=', '!<', '!<=']: operator = { '!>': '<=', '!>=': '<', '!<': '>=', '!<=': '>' }[operator] # Put a function on top of the filter_stack which implements # the desired operation self.filtercolumns.append(attribute) self.prefiltercolumns.append(attribute) self.filter_stack.put_stack( self.make_filter(operator, attribute, reference)) if self.table == 'log': self.db.add_filter(operator, attribute, reference) else: logger.warning("[Livestatus Query] Illegal operation: %s" % str(operator)) pass # illegal operation elif keyword == 'And': _, andnum = self.split_option(line) # Take the last andnum functions from the stack # Construct a new function which makes a logical and # Put the function back onto the stack self.filter_stack.and_elements(andnum) if self.table == 'log': self.db.add_filter_and(andnum) elif keyword == 'Or': _, ornum = self.split_option(line) # Take the last ornum functions from the stack # Construct a new function which makes a logical or # Put the function back onto the stack self.filter_stack.or_elements(ornum) if self.table == 'log': self.db.add_filter_or(ornum) elif keyword == 'Negate': self.filter_stack.not_elements() if self.table == 'log': self.db.add_filter_not() elif keyword == 'StatsGroupBy': _, stats_group_by = self.split_option_with_columns(line) self.filtercolumns.extend(stats_group_by) self.stats_group_by.extend(stats_group_by) # Deprecated. If your query contains at least one Stats:-header # then Columns: has the meaning of the old StatsGroupBy: header elif keyword == 'Stats': self.stats_query = True try: _, attribute, operator, reference = self.split_option( line, 3) if attribute in ['sum', 'min', 'max', 'avg', 'std' ] and reference.startswith('as '): attribute, operator = operator, attribute _, alias = reference.split(' ') self.aliases.append(alias) elif attribute in ['sum', 'min', 'max', 'avg', 'std' ] and reference == '=': # Workaround for thruk-cmds like: Stats: sum latency = attribute, operator = operator, attribute reference = '' except Exception: _, attribute, operator = self.split_option(line, 3) if attribute in ['sum', 'min', 'max', 'avg', 'std']: attribute, operator = operator, attribute reference = '' attribute = self.strip_table_from_column(attribute) if operator in [ '=', '>', '>=', '<', '<=', '=~', '~', '~~', '!=', '!>', '!>=', '!<', '!<=', '!=~', '!~', '!~~' ]: if operator in ['!>', '!>=', '!<', '!<=']: operator = { '!>': '<=', '!>=': '<', '!<': '>=', '!<=': '>' }[operator] self.filtercolumns.append(attribute) self.stats_columns.append(attribute) self.stats_filter_stack.put_stack( self.make_filter(operator, attribute, reference)) self.stats_postprocess_stack.put_stack( self.make_filter('count', attribute, None)) elif operator in ['sum', 'min', 'max', 'avg', 'std']: self.stats_columns.append(attribute) self.stats_filter_stack.put_stack( self.make_filter('dummy', attribute, None)) self.stats_postprocess_stack.put_stack( self.make_filter(operator, attribute, None)) else: logger.warning("[Livestatus Query] Illegal operation: %s" % str(operator)) pass # illegal operation elif keyword == 'StatsAnd': _, andnum = self.split_option(line) self.stats_filter_stack.and_elements(andnum) elif keyword == 'StatsOr': _, ornum = self.split_option(line) self.stats_filter_stack.or_elements(ornum) elif keyword == 'Separators': separators = map(lambda sep: chr(int(sep)), line.split(' ', 5)[1:]) self.response.separators = Separators(*separators) elif keyword == 'Localtime': _, self.client_localtime = self.split_option(line) elif keyword == 'COMMAND': _, self.extcmd = line.split(' ', 1) else: # This line is not valid or not implemented logger.error( "[Livestatus Query] Received a line of input which i can't handle: '%s'" % line) pass self.metainfo = LiveStatusQueryMetainfo(data)
def parse_input(self, data): """ Parse the lines of a livestatus request. This function looks for keywords in input lines and sets the attributes of the request object """ for line in data.splitlines(): line = line.strip() # Tools like NagVis send KEYWORK:option, and we prefer to have # a space following the: if ':' in line and not ' ' in line: line = line.replace(':', ': ') keyword = line.split(' ')[0].rstrip(':') if keyword == 'GET': # Get the name of the base table _, self.table = self.split_command(line) if self.table not in self.mapping.keys(): raise LiveStatusQueryError(404, self.table) elif keyword == 'Columns': # Get the names of the desired columns _, self.columns = self.split_option_with_columns(line) self.response.columnheaders = 'off' elif keyword == 'ResponseHeader': _, responseheader = self.split_option(line) self.response.responseheader = responseheader elif keyword == 'OutputFormat': _, outputformat = self.split_option(line) self.response.outputformat = outputformat elif keyword == 'KeepAlive': _, keepalive = self.split_option(line) self.response.keepalive = keepalive elif keyword == 'ColumnHeaders': _, columnheaders = self.split_option(line) self.response.columnheaders = columnheaders elif keyword == 'Limit': _, self.limit = self.split_option(line) elif keyword == 'AuthUser': _, authuser = self.split_option(line) if self.table in [ 'hosts', 'services', 'hostgroups', 'servicegroups', 'hostsbygroup', 'servicesbygroup', 'servicesbyhostgroup', 'problems' ]: self.datamgr.add_filter_user(self.filters_stack, "contacts", authuser) elif self.table in ['contactgroups']: self.datamgr.add_filter_user(self.filters_stack, "members", authuser) elif keyword == 'Filter': try: attribute, operator, reference = self.parse_filter_line( line) self.add_filter(self.filters_stack, operator, attribute, reference) except Exception as e: logger.warning("[Livestatus Query] Illegal operation: %s" % e) raise elif keyword == 'And': _, andnum = self.split_option(line) # Take the last andnum functions from the stack # Construct a new function which makes a logical and # Put the function back onto the stack self.datamgr.stack_filter_and(self.filters_stack, self.table, andnum) elif keyword == 'Or': _, ornum = self.split_option(line) # Take the last ornum functions from the stack # Construct a new function which makes a logical or # Put the function back onto the stack self.datamgr.stack_filter_or(self.filters_stack, self.table, ornum) elif keyword == 'Negate': _, notnum = self.split_option(line) self.datamgr.stack_filter_negate(self.filters_stack, notnum) elif keyword == 'Stats': self.stats_query = True try: attribute, operator, reference = self.parse_filter_line( line) self.add_filter(self.aggregations_stack, operator, attribute, reference) except Exception as e: logger.warning("[Livestatus Query] Illegal operation: %s" % e) raise continue elif keyword == 'StatsAnd': _, andnum = self.split_option(line) self.datamgr.stack_filter_and(self.aggregations_stack, self.table, andnum) elif keyword == 'StatsOr': _, ornum = self.split_option(line) self.datamgr.stack_filter_or(self.aggregations_stack, self.table, ornum) elif keyword == 'StatsNegate': _, notnum = self.split_option(line) self.datamgr.stack_filter_negate(self.aggregations_stack, notnum) elif keyword == 'Separators': separators = map(lambda sep: chr(int(sep)), line.split(' ', 5)[1:]) self.response.separators = Separators(*separators) elif keyword == 'Localtime': _, self.client_localtime = self.split_option(line) else: # This line is not valid or not implemented logger.error( "[Livestatus Query] Received a line of input which i can't handle: '%s'" % line)