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)
Exemple #2
0
    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)