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)
def schedule_host_downtime( connection, host_name: Union[List[str], str], start_time: dt.datetime, end_time: dt.datetime, include_all_services: bool = False, recur: RecurMode = 'fixed', trigger_id: int = 0, duration: int = 0, user_id: str = '', comment: str = '', ): """Schedule the downtime of a host. Notes: If `include_all_services` is set to True, the services table is only queried once, instead of len(host_name) times. If a lot of hosts are to be scheduled, this will save N queries. Issuing the command is still done sequentially. Args: connection: A livestatus connection object. host_name: The host-name for which this downtime is for. start_time: When the downtime shall begin. end_time: When the downtime shall end. include_all_services: If set, downtimes for all services associated with the given host will be scheduled. Defaults to False. recur: The recurring mode of the new downtime. Available modes are: * fixed * hour * day * week * second_week * fourth_week * weekday_start * weekday_end * day_of_month This only works when using the Enterprise Editions. Defaults to 'fixed'. trigger_id: The id of another downtime-entry. If given (other than 0) then this downtime will be triggered by the other downtime. duration: Duration in seconds. When set, the downtime does not begin automatically at a nominated time, but when a real problem status appears for the host. Consequencely, the start_time/end_time is only the time window in which the scheduled downtime can begin. user_id: comment: A comment which will be added to the downtime. See Also: * https://assets.nagios.com/downloads/nagioscore/docs/externalcmds/cmdinfo.php?command_id=118 * https://assets.nagios.com/downloads/nagioscore/docs/externalcmds/cmdinfo.php?command_id=122 Examples: >>> import pytz >>> _start_time = dt.datetime(1970, 1, 1, tzinfo=pytz.timezone("UTC")) >>> _end_time = dt.datetime(1970, 1, 2, tzinfo=pytz.timezone("UTC")) >>> from cmk.gui.livestatus_utils.testing import simple_expect >>> cmd = "COMMAND [...] SCHEDULE_HOST_DOWNTIME;example.com;0;86400;16;0;120;;Boom" >>> with simple_expect(cmd, match_type="ellipsis") as live: ... schedule_host_downtime(live, ... 'example.com', ... _start_time, ... _end_time, ... recur="day_of_month", ... duration=120, ... comment="Boom") """ if isinstance(host_name, str): host_names = [host_name] else: host_names = host_name for _host_name in host_names: _schedule_downtime( connection, "SCHEDULE_HOST_DOWNTIME", _host_name, None, start_time, end_time, recur, trigger_id, duration, user_id, comment, ) if include_all_services: services = Query( [tables.Services.host_name, tables.Services.description], Or(*[tables.Services.host_name.equals(_host_name) for _host_name in host_names])).fetch_values(connection) for _host_name, service_description in services: schedule_service_downtime( connection, host_name=_host_name, service_description=service_description, start_time=start_time, end_time=end_time, recur=recur, trigger_id=trigger_id, duration=duration, user_id=user_id, comment=comment, )
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}")