Example #1
0
def set_acknowledgement_on_host_service(params):
    """Acknowledge for services on a host"""
    host_name = params['host_name']
    service_description = unquote(params['service_description'])
    body = params['body']

    service = Query([Services.description, Services.state],
                    And(Services.description.equals(service_description),
                        Services.host_name.equals(host_name))).first(sites.live())

    if service is None:
        return problem(
            status=404,
            title=f'Service {service_description!r} on host {host_name!r} does not exist.',
            detail='It is not currently monitored.',
        )

    if service.state == 0:
        return problem(status=400,
                       title=f"Service {service_description!r} does not have a problem.",
                       detail="The state is OK.")

    acknowledge_service_problem(
        sites.live(),
        host_name,
        service_description,
        sticky=body.get('sticky', False),
        notify=body.get('notify', False),
        persistent=body.get('persistent', False),
        user=_user_id(),
        comment=body.get('comment', 'Acknowledged'),
    )
    return http.Response(status=204)
Example #2
0
def bulk_delete_downtimes(params):
    """Bulk delete downtimes"""
    live = sites.live()
    entries = params['entries']
    not_found = []

    downtimes: Dict[int, int] = Query(
        [Downtimes.id, Downtimes.is_service],
        And(*[Downtimes.id.equals(downtime_id) for downtime_id in entries]),
    ).to_dict(live)

    for downtime_id in entries:
        if downtime_id not in downtimes:
            not_found.append(downtime_id)

    if not_found:
        raise ProblemException(404, http.client.responses[400],
                               f"Downtimes {', '.join(not_found)} not found")

    for downtime_id, is_service in downtimes.items():
        if is_service:
            downtime_commands.del_service_downtime(live, downtime_id)
        else:
            downtime_commands.del_host_downtime(live, downtime_id)
    return Response(status=204)
Example #3
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['hostname']
        if "services" not in body:
            host_expr = Downtimes.host_name.op("~", hostname)
            downtime_commands.delete_downtime_with_query(live, host_expr)
        else:
            services_expr = And(*[
                Downtimes.host_name == body['hostname'],
                Or(*[
                    Downtimes.service_description == svc_desc
                    for svc_desc in body['services']
                ])
            ])
            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)
Example #4
0
def bulk_set_acknowledgement_on_host_service(params):
    """Bulk Acknowledge specific services on specific host"""
    live = sites.live()
    body = params['body']
    host_name = body['host_name']
    entries = body.get('entries', [])

    query = Query([Services.description, Services.state],
                  And(
                      Services.host_name.equals(host_name),
                      Or(*[
                          Services.description.equals(service_description)
                          for service_description in entries
                      ])))

    services = query.to_dict(live)

    not_found = []
    for service_description in entries:
        if service_description not in services:
            not_found.append(service_description)

    if not_found:
        return problem(
            status=400,
            title=
            f"Services {', '.join(not_found)} not found on host {host_name}",
            detail='Currently not monitored')

    up_services = []
    for service_description in entries:
        if services[service_description] == 0:
            up_services.append(service_description)

    if up_services:
        return problem(
            status=400,
            title=f"Services {', '.join(up_services)} do not have a problem",
            detail="The states of these services are OK")

    for service_description in entries:
        acknowledge_service_problem(
            sites.live(),
            host_name,
            service_description,
            sticky=body.get('sticky', False),
            notify=body.get('notify', False),
            persistent=body.get('persistent', False),
            user=str(config.user.id),
            comment=body.get('comment', 'Acknowledged'),
        )
    return http.Response(status=204)
Example #5
0
def bulk_set_acknowledgement_on_hosts(params):
    """Bulk acknowledge for hosts"""
    live = sites.live()
    entries = params['entries']

    hosts: Dict[str, int] = {
        host_name: host_state
        for host_name, host_state in Query(  # pylint: disable=unnecessary-comprehension
            [Hosts.name, Hosts.state],
            And(*[Hosts.name.equals(host_name) for host_name in entries]),
        ).fetch_values(live)
    }

    not_found = []
    for host_name in entries:
        if host_name not in hosts:
            not_found.append(host_name)

    if not_found:
        return problem(status=400,
                       title=f"Hosts {', '.join(not_found)} not found",
                       detail='Current not monitored')

    up_hosts = []
    for host_name in entries:
        if hosts[host_name] == 0:
            up_hosts.append(host_name)

    if up_hosts:
        return problem(
            status=400,
            title=f"Hosts {', '.join(up_hosts)} do not have a problem",
            detail="The states of these hosts are UP")

    for host_name in entries:
        acknowledge_host_problem(
            sites.live(),
            host_name,
            sticky=params.get('sticky'),
            notify=params.get('notify'),
            persistent=params.get('persistent'),
            user=_user_id(),
            comment=params.get('comment', 'Acknowledged'),
        )
    return http.Response(status=204)
Example #6
0
def set_acknowledgement_for_service(params):
    """Acknowledge for a service globally"""
    service_description = unquote(params['service_description'])
    body = params['body']

    live = sites.live()

    services = Query(
        [Services.host_name, Services.description],
        And(
            Services.description.equals(service_description),
            Or(
                Services.state == 1,
                Services.state == 2,
            ),
        ),
    ).fetch_values(live)

    if not len(services):
        return problem(
            status=400,
            title=f'No services {service_description!r} with problems found.',
            detail='All services are OK.',
        )

    for _host_name, _service_description in services:
        acknowledge_service_problem(
            live,
            _host_name,
            _service_description,
            sticky=body.get('sticky', False),
            notify=body.get('notify', False),
            persistent=body.get('persistent', False),
            user=_user_id(),
            comment=body.get('comment', 'Acknowledged'),
        )

    return http.Response(status=204)
Example #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=404,
                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=404,
                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)
Example #8
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))
Example #9
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(),
        )