Пример #1
0
def delete_downtime(params):
    """Delete a scheduled downtime"""
    body = params['body']
    live = sites.live()
    delete_type = body['delete_type']
    if delete_type == "query":
        downtime_commands.delete_downtime_with_query(live, body['query'])
    elif delete_type == "by_id":
        downtime_commands.delete_downtime(live, body['downtime_id'])
    elif delete_type == "params":
        hostname = body['host_name']
        if "service_descriptions" not in body:
            host_expr = And(Downtimes.host_name.op("=", hostname),
                            Downtimes.is_service.op('=', 0))
            downtime_commands.delete_downtime_with_query(live, host_expr)
        else:
            services_expr = And(
                Downtimes.host_name.op('=', hostname),
                Or(*[
                    Downtimes.service_description == svc_desc
                    for svc_desc in body['service_descriptions']
                ]))
            downtime_commands.delete_downtime_with_query(live, services_expr)
    else:
        return problem(
            status=400,
            title="Unhandled delete_type.",
            detail=f"The downtime-type {delete_type!r} is not supported.")
    return Response(status=204)
Пример #2
0
def show_service(params):
    """Show the monitored service of a host"""
    service_description = params["service_description"]
    host_name = params["host_name"]
    live = sites.live()
    q = Query(
        [
            Services.description,
            Services.host_name,
            Services.state_type,
            Services.state,
            Services.last_check,
        ],
        filter_expr=And(Services.host_name.op("=", params["host_name"]),
                        Services.description.op("=", service_description)),
    )
    try:
        service = q.fetchone(live)
    except ValueError:
        return problem(
            status=404,
            title="The requested service was not found",
            detail=
            f"The service description {service_description} did not match any service",
        )
    return constructors.serve_json(
        constructors.domain_object(
            domain_type='service',
            identifier=f"{host_name}-{service_description}",
            title=f"Service {service_description}",
            extensions=service,
            links=[],
            editable=False,
            deletable=False))
Пример #3
0
def show_downtimes(param):
    """Show all scheduled downtimes"""
    live = sites.live()
    sites_to_query = param.get("sites")
    if sites_to_query:
        live.only_sites = sites_to_query

    q = Query(
        [
            Downtimes.id,
            Downtimes.host_name,
            Downtimes.service_description,
            Downtimes.is_service,
            Downtimes.author,
            Downtimes.start_time,
            Downtimes.end_time,
            Downtimes.recurring,
            Downtimes.comment,
        ]
    )

    query_expr = param.get("query")
    if query_expr is not None:
        q = q.filter(query_expr)

    host_name = param.get("host_name")
    if host_name is not None:
        q = q.filter(And(Downtimes.host_name.op("=", host_name), Downtimes.is_service.equals(0)))

    service_description = param.get("service_description")
    if service_description is not None:
        q = q.filter(Downtimes.service_description.contains(service_description))

    gen_downtimes = q.iterate(live)
    return _serve_downtimes(gen_downtimes)
Пример #4
0
    def from_string(
        cls,
        string_query: str,
    ) -> 'Query':
        """Constructs a Query instance from a string based LiveStatus-Query

        Args:
            string_query:
                A LiveStatus query as a string.

        Examples:

            >>> q = Query.from_string('GET services\\n'
            ...                       'Columns: service_service_description\\n'
            ...                       'Filter: service_service_description = \\n')

            >>> query_text = ('''GET services
            ... Columns: host_address host_check_command host_check_type host_custom_variable_names host_custom_variable_values host_downtimes_with_extra_info host_file name host_has_been_checked host_name host_scheduled_downtime_depth host_state service_accept_passive_checks service_acknowledged service_action_url_expanded service_active_checks_enabled service_cache_interval service_cached_at service_check_command service_check_type service_comments_with_extra_info service_custom_variable_names service_custom_variable_values service_custom_variables service_description service_downtimes service_downtimes_with_extra_info service_has_been_checked service_host_name service_icon_image service_in_check_period service_in_notification_period service_in_passive_check_period service_in_service_period service_is_flapping service_last_check service_last_state_change service_modified_attributes_list service_notes_url_expanded service_notifications_enabled service_perf_data service_plugin_output service_pnpgraph_present service_scheduled_downtime_depth service_service_description service_staleness service_state
            ... Filter: service_state = 0
            ... Filter: service_has_been_checked = 1
            ... And: 2
            ... Negate:
            ... Filter: service_has_been_checked = 1
            ... Filter: service_scheduled_downtime_depth = 0
            ... Filter: host_scheduled_downtime_depth = 0
            ... And: 2
            ... Filter: service_acknowledged = 0
            ... Filter: host_state = 1
            ... Filter: host_has_been_checked = 1
            ... And: 2
            ... Negate:
            ... Filter: host_state = 2
            ... Filter: host_has_been_checked = 1
            ... And: 2
            ... Negate:
            ... ''')
            >>> q = Query.from_string(query_text)

            We can faithfully recreate this query as a dict-representation.

            >>> q.dict_repr()
            {'op': 'and', 'expr': [\
{'op': 'not', 'expr': \
{'op': 'and', 'expr': [\
{'op': '=', 'left': 'services.state', 'right': '0'}, \
{'op': '=', 'left': 'services.has_been_checked', 'right': '1'}\
]}}, \
{'op': '=', 'left': 'services.has_been_checked', 'right': '1'}, \
{'op': 'and', 'expr': [\
{'op': '=', 'left': 'services.scheduled_downtime_depth', 'right': '0'}, \
{'op': '=', 'left': 'services.host_scheduled_downtime_depth', 'right': '0'}\
]}, \
{'op': '=', 'left': 'services.acknowledged', 'right': '0'}, \
{'op': 'not', 'expr': \
{'op': 'and', 'expr': [\
{'op': '=', 'left': 'services.host_state', 'right': '1'}, \
{'op': '=', 'left': 'services.host_has_been_checked', 'right': '1'}\
]}}, \
{'op': 'not', 'expr': \
{'op': 'and', 'expr': [\
{'op': '=', 'left': 'services.host_state', 'right': '2'}, \
{'op': '=', 'left': 'services.host_has_been_checked', 'right': '1'}\
]}}\
]}

            >>> q.columns
            [Column(services.host_address: string), Column(services.host_check_command: string), Column(services.host_check_type: int), Column(services.host_custom_variable_names: list), Column(services.host_custom_variable_values: list), Column(services.host_downtimes_with_extra_info: list), Column(services.host_has_been_checked: int), Column(services.host_name: string), Column(services.host_scheduled_downtime_depth: int), Column(services.host_state: int), Column(services.accept_passive_checks: int), Column(services.acknowledged: int), Column(services.action_url_expanded: string), Column(services.active_checks_enabled: int), Column(services.cache_interval: int), Column(services.cached_at: time), Column(services.check_command: string), Column(services.check_type: int), Column(services.comments_with_extra_info: list), Column(services.custom_variable_names: list), Column(services.custom_variable_values: list), Column(services.custom_variables: dict), Column(services.description: string), Column(services.downtimes: list), Column(services.downtimes_with_extra_info: list), Column(services.has_been_checked: int), Column(services.host_name: string), Column(services.icon_image: string), Column(services.in_check_period: int), Column(services.in_notification_period: int), Column(services.in_passive_check_period: int), Column(services.in_service_period: int), Column(services.is_flapping: int), Column(services.last_check: time), Column(services.last_state_change: time), Column(services.modified_attributes_list: list), Column(services.notes_url_expanded: string), Column(services.notifications_enabled: int), Column(services.perf_data: string), Column(services.plugin_output: string), Column(services.pnpgraph_present: int), Column(services.scheduled_downtime_depth: int), Column(services.description: string), Column(services.staleness: float), Column(services.state: int)]

            >>> q = Query.from_string('GET hosts\\n'
            ...                       'Columns: name service_description\\n'
            ...                       'Filter: service_description = ')
            Traceback (most recent call last):
            ...
            ValueError: Table 'hosts': Could not decode line 'Filter: service_description = '

            All unknown columns are ignored, as there are many places in Checkmk where queries
            specify wrong or unnecessary columns. Livestatus would normally ignore them.

            >>> _ = Query.from_string('GET hosts\\n'
            ...                       'Columns: service_service_description\\n'
            ...                       'Filter: service_description = ')
            Traceback (most recent call last):
            ...
            ValueError: Table 'hosts': Could not decode line 'Filter: service_description = '

            >>> _ = Query.from_string('GET foobazbar\\n'
            ...                       'Columns: name service_description\\n'
            ...                       'Filter: service_description = ')
            Traceback (most recent call last):
            ...
            ValueError: Table foobazbar was not defined in the tables module.


            >>> q = Query.from_string('GET hosts\\n'
            ...                       'Columns: name\\n'
            ...                       'Filter: name = heute\\n'
            ...                       'Filter: alias = heute\\n'
            ...                       'Or: 2')


            >>> q.table.__name__
            'Hosts'

            >>> q.columns
            [Column(hosts.name: string)]

            >>> q.filter_expr
            Or(Filter(name = heute), Filter(alias = heute))

            >>> print(q)
            GET hosts
            Columns: name
            Filter: name = heute
            Filter: alias = heute
            Or: 2

            So in essence this says that round trips work

                >>> assert str(q) == str(Query.from_string(str(q)))

        Returns:
            A Query instance.

        Raises:
            A ValueError if no Query() instance could be created.

        """
        lines = string_query.split("\n")
        for line in lines:
            if line.startswith('GET '):
                parts = line.split()
                if len(parts) < 2:
                    raise ValueError(f"No table found in line: {line!r}")

                table_name = parts[1]
                try:
                    table_class: Type[Table] = getattr(tables,
                                                       table_name.title())
                except AttributeError:
                    raise ValueError(
                        f"Table {table_name} was not defined in the tables module."
                    )
                break
        else:
            raise ValueError("No table found")

        for line in lines:
            if line.startswith('Columns: '):
                column_names = line.split(": ", 1)[1].lstrip().split()
                columns: List[Column] = []
                for col in column_names:
                    try:
                        columns.append(_get_column(table_class, col))
                    except AttributeError:
                        pass
                break
        else:
            raise ValueError("No columns found")

        filters: List[QueryExpression] = []
        for line in lines:
            if line.startswith('Filter: '):
                try:
                    filters.append(_parse_line(table_class, line))
                except AttributeError:
                    raise ValueError(
                        f"Table {table_name!r}: Could not decode line {line!r}"
                    )
            elif line.startswith('Or: ') or line.startswith("And: "):
                op, _count = line.split(": ")
                count = int(_count)
                # I'm sorry. :)
                # We take the last `count` filters and pass them into the BooleanExpression
                try:
                    expr = {
                        'or': Or,
                        'and': And
                    }[op.lower()](*filters[-count:])
                except ValueError:
                    raise ValueError(f"Could not parse {op} for {filters!r}")
                filters = filters[:-count]
                filters.append(expr)
            elif line.startswith('Negate:') or line.startswith('Not:'):
                filters[-1] = Not(filters[-1])

        if len(filters) > 1:
            filters = [And(*filters)]

        return cls(
            columns=columns,
            filter_expr=filters[0] if filters else NothingExpression(),
        )
Пример #5
0
    def filter(self, filter_expr: QueryExpression) -> 'Query':
        """Apply additional filters to an existing query.

        This will return a new `Query` instance. The original one is left untouched."""
        return Query(self.columns, And(self.filter_expr, filter_expr))
Пример #6
0
def set_acknowledgement_on_services(params):
    """Set acknowledgement on related services"""
    body = params["body"]
    live = sites.live()

    sticky = body["sticky"]
    notify = body["notify"]
    persistent = body["persistent"]
    comment = body["comment"]
    acknowledge_type = body["acknowledge_type"]

    if acknowledge_type == "service":
        description = unquote(body["service_description"])
        host_name = body["host_name"]
        service = Query(
            [Services.host_name, Services.description, Services.state],
            And(Services.host_name == host_name,
                Services.description == description),
        ).first(live)
        if not service:
            raise ProblemException(
                status=400,
                title=
                f"Service {description!r}@{host_name!r} could not be found.",
            )
        if not service.state:
            raise ProblemException(
                status=422,
                title=f"Service {description!r}@{host_name!r} has no problem.",
            )
        acknowledge_service_problem(
            live,
            service.host_name,
            service.description,
            sticky=sticky,
            notify=notify,
            persistent=persistent,
            user=user.ident,
            comment=comment,
        )
    elif acknowledge_type == "servicegroup":
        service_group = body["servicegroup_name"]
        try:
            acknowledge_servicegroup_problem(
                live,
                service_group,
                sticky=sticky,
                notify=notify,
                persistent=persistent,
                user=user.ident,
                comment=comment,
            )
        except ValueError:
            raise ProblemException(
                status=400,
                title="Service group could not be found.",
                detail=f"Unknown service group: {service_group}",
            )
    elif acknowledge_type == "service_by_query":
        services = Query(
            [Services.host_name, Services.description, Services.state],
            body["query"],
        ).fetchall(live)
        if not services:
            raise ProblemException(
                status=422,
                title="No services with problems found.",
                detail="All queried services are OK.",
            )

        for service in services:
            if not service.state:
                continue
            acknowledge_service_problem(
                live,
                service.host_name,
                service.description,
                sticky=sticky,
                notify=notify,
                persistent=persistent,
                user=user.ident,
                comment=comment,
            )
    else:
        raise ProblemException(
            status=400,
            title="Unhandled acknowledge-type.",
            detail=
            f"The acknowledge-type {acknowledge_type!r} is not supported.",
        )

    return http.Response(status=204)
Пример #7
0
def set_acknowledgement_on_services(params):
    """Set acknowledgement on related services"""
    body = params['body']
    live = sites.live()

    sticky = body['sticky']
    notify = body['notify']
    persistent = body['persistent']
    comment = body['comment']
    acknowledge_type = body['acknowledge_type']

    if acknowledge_type == 'service':
        description = unquote(body['service_description'])
        host_name = body['host_name']
        service = Query(
            [Services.host_name, Services.description, Services.state],
            And(Services.host_name == host_name,
                Services.description == description)).first(live)
        if not service:
            raise ProblemException(
                status=400,
                title=
                f'Service {description!r}@{host_name!r} could not be found.',
            )
        if not service.state:
            raise ProblemException(
                status=422,
                title=f'Service {description!r}@{host_name!r} has no problem.',
            )
        acknowledge_service_problem(
            live,
            service.host_name,
            service.description,
            sticky=sticky,
            notify=notify,
            persistent=persistent,
            user=config.user.ident,
            comment=comment,
        )
    elif acknowledge_type == 'servicegroup':
        service_group = body['servicegroup_name']
        try:
            acknowledge_servicegroup_problem(
                live,
                service_group,
                sticky=sticky,
                notify=notify,
                persistent=persistent,
                user=config.user.ident,
                comment=comment,
            )
        except ValueError:
            raise ProblemException(
                status=400,
                title="Servicegroup could not be found.",
                detail=f"Unknown servicegroup: {service_group}",
            )
    elif acknowledge_type == 'service_by_query':
        services = Query(
            [Services.host_name, Services.description, Services.state],
            body['query'],
        ).fetchall(live)
        if not services:
            raise ProblemException(
                status=422,
                title='No services with problems found.',
                detail='All queried services are OK.',
            )

        for service in services:
            if not service.state:
                continue
            acknowledge_service_problem(
                live,
                service.host_name,
                service.description,
                sticky=sticky,
                notify=notify,
                persistent=persistent,
                user=config.user.ident,
                comment=comment,
            )
    else:
        raise ProblemException(
            status=400,
            title="Unhandled acknowledge-type.",
            detail=
            f"The acknowledge-type {acknowledge_type!r} is not supported.",
        )

    return http.Response(status=204)
Пример #8
0
def tree_to_expr(filter_dict, table: Any = None) -> QueryExpression:
    """Turn a filter-dict into a QueryExpression.

    Examples:

        >>> tree_to_expr({'op': '=', 'left': 'hosts.name', 'right': 'example.com'})
        Filter(name = example.com)

        >>> tree_to_expr({'op': '!=', 'left': 'hosts.name', 'right': 'example.com'})
        Filter(name != example.com)

        >>> tree_to_expr({'op': '!=', 'left': 'name', 'right': 'example.com'}, 'hosts')
        Filter(name != example.com)

        >>> tree_to_expr({'op': 'and', \
                          'expr': [{'op': '=', 'left': 'hosts.name', 'right': 'example.com'}, \
                          {'op': '=', 'left': 'hosts.state', 'right': 0}]})
        And(Filter(name = example.com), Filter(state = 0))

        >>> tree_to_expr({'op': 'or', \
                          'expr': [{'op': '=', 'left': 'hosts.name', 'right': 'example.com'}, \
                          {'op': '=', 'left': 'hosts.name', 'right': 'heute'}]})
        Or(Filter(name = example.com), Filter(name = heute))

        >>> tree_to_expr({'op': 'not', \
                          'expr': {'op': '=', 'left': 'hosts.name', 'right': 'example.com'}})
        Not(Filter(name = example.com))

        >>> tree_to_expr({'op': 'not', \
                          'expr': {'op': 'not', \
                                   'expr': {'op': '=', \
                                            'left': 'hosts.name', \
                                            'right': 'example.com'}}})
        Not(Not(Filter(name = example.com)))

        >>> from cmk.utils.livestatus_helpers.tables import Hosts
        >>> tree_to_expr({'op': 'not', 'expr': Hosts.name == 'example.com'})
        Not(Filter(name = example.com))

        >>> tree_to_expr({'op': 'no_way', \
                          'expr': {'op': '=', 'left': 'hosts.name', 'right': 'example.com'}})
        Traceback (most recent call last):
        ...
        ValueError: Unknown operator: no_way

    Args:
        filter_dict:
            A filter-dict, which can either be persisted or passed over the wire.

        table:
            Optionally a table name. Only used when the columns are used in plain form
            (without table name prefixes).

    Returns:
        A valid LiveStatus query expression.

    Raises:
        ValueError: when unknown columns are queried

    """
    if not isinstance(filter_dict, dict):
        # FIXME
        #   Because of not having correct Python packages at the root-level, sometimes a
        #   locally defined class ends up having a relative dotted path, like for example
        #       <class 'expressions.BinaryExpression'>
        #   instead of
        #       <class 'cmk.utils.livestatus_helpers.expressions.BinaryExpression'>
        #   While these classes are actually the same, Python treats them distinct, so we can't
        #   just say `isinstance(filter_dict, BinaryExpression)` (or their super-type) here.
        return cast(QueryExpression, filter_dict)
    op = filter_dict["op"]
    if op in LIVESTATUS_OPERATORS:
        left = filter_dict["left"]
        if "." in left:
            _table, column = left.split(".")
            if table is not None and _table_name(table) != _table:
                raise ValueError(
                    f"This field can only query table {_table_name(table)!r}. ({left})"
                )
        else:
            if table is None:
                raise ValueError("Missing table parameter.")
            _table = _table_name(table)
            column = left
        return BinaryExpression(
            _lookup_column(_table, column),
            LiteralExpression(filter_dict["right"]),
            op,
        )

    if op == "and":
        return And(
            *[tree_to_expr(expr, table) for expr in filter_dict["expr"]])

    if op == "or":
        return Or(*[tree_to_expr(expr, table) for expr in filter_dict["expr"]])

    if op == "not":
        return Not(tree_to_expr(filter_dict["expr"], table))

    raise ValueError(f"Unknown operator: {op}")