def group_is_monitored(group_type, group_name): # Danke mypy rv: bool if group_type == 'service': rv = bool( Query([Servicegroups.name], Servicegroups.name == group_name).first_value(sites.live())) elif group_type == 'host': rv = bool(Query([Hostgroups.name], Hostgroups.name == group_name).first_value(sites.live())) else: raise ValueError("Unknown group type.") return rv
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)
def show_downtime(params): """Show downtime""" live = sites.live() downtime_id = params["downtime_id"] q = Query( columns=[ Downtimes.id, Downtimes.host_name, Downtimes.service_description, Downtimes.is_service, Downtimes.author, Downtimes.start_time, Downtimes.end_time, Downtimes.recurring, Downtimes.comment, ], filter_expr=Downtimes.id.op("=", downtime_id), ) try: downtime = q.fetchone(live) except ValueError: return problem( status=404, title="The requested downtime was not found", detail=f"The downtime id {downtime_id} did not match any downtime", ) return _serve_downtime(downtime)
def schedule_hosts_downtimes_with_query( connection, query: QueryExpression, start_time: dt.datetime, end_time: dt.datetime, include_all_services=False, recur: RecurMode = 'fixed', duration: int = 0, user_id: str = '', comment: str = '', ): """Schedule a downtimes for hosts based upon a query""" q = Query([Hosts.name]).filter(query) hosts = [row['name'] for row in q.iterate(connection)] if not hosts: raise QueryException if not comment: comment = f"Downtime for hosts {', '.join(hosts)}" schedule_host_downtime( connection, host_name=hosts, start_time=start_time, end_time=end_time, include_all_services=include_all_services, recur=recur, duration=duration, user_id=user_id, comment=comment, )
def _query_site(connection, host_name: str) -> str: with detailed_connection(connection) as conn: site_id = Query([Hosts.name], Hosts.name.equals(host_name)).first_value(conn) if not isinstance(site_id, str): raise QueryException return site_id
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))
def list_hosts(param): """Show hosts of specific condition""" live = sites.live() sites_to_query = param["sites"] if sites_to_query: live.only_sites = sites_to_query q = Query(param["columns"]) query_expr = param.get("query") if query_expr: q = q.filter(query_expr) result = q.iterate(live) return constructors.serve_json( constructors.collection_object( domain_type="host", value=[ constructors.domain_object( domain_type="host", title=f"{entry['name']}", identifier=entry["name"], editable=False, deletable=False, extensions=entry, ) for entry in result ], ) )
def acknowledge_hostgroup_problem( connection, hostgroup_name: str, sticky: bool = False, notify: bool = False, persistent: bool = False, user: str = "", comment: str = "", ): """Acknowledge the problems of the current hosts of the hostgroup When acknowledging a problem, further notifications for the respective services are disabled, as long as a specific service doesn't change state. At state change, notifications are re-enabled. Args: connection: A livestatus connection object. hostgroup_name: The name of the host group. sticky: If set, only a state-change of the service to an OK state will discard the acknowledgement. Otherwise it will be discarded on any state-change. Defaults to False. notify: If set, notifications will be sent out to the configured contacts. Defaults to False. persistent: If set, the comment will persist a restart. Defaults to False. user: comment: If set, this comment will be stored alongside the acknowledgement. Raises: ValueError: when the Hostgroup in question doesn't exist. """ members: List[str] = Query( [tables.Hostgroups.members], tables.Hostgroups.name.equals(hostgroup_name) ).value(connection) acknowledgement = 2 if sticky else 1 # 1: normal, 2: sticky for host_name in members: send_command( connection, "ACKNOWLEDGE_HOST_PROBLEM", [ host_name, acknowledgement, int(notify), int(persistent), user, comment, ], )
def delete_downtime_with_query(connection, query): """Delete scheduled downtimes based upon a query""" q = Query([Downtimes.id, Downtimes.is_service]).filter(query) for downtime_id, is_service in [(row['id'], row['is_service']) for row in q.iterate(connection) ]: if is_service: del_service_downtime(connection, downtime_id) else: del_host_downtime(connection, downtime_id)
def delete_downtime(connection, downtime_id): """Delete a scheduled downtime based upon the downtime id""" is_service = Query( [Downtimes.is_service], Downtimes.id == downtime_id, ).value(connection) if is_service: del_service_downtime(connection, downtime_id) else: del_host_downtime(connection, downtime_id)
def delete_downtime(connection, downtime_id): """Delete a scheduled downtime based upon the downtime id""" with detailed_connection(connection) as conn: entry = Query( [Downtimes.is_service], Downtimes.id == downtime_id, ).fetchone(conn) if entry["is_service"]: del_service_downtime(connection, downtime_id, entry["site"]) else: del_host_downtime(connection, downtime_id, entry["site"])
def delete_downtime_with_query(connection, query): """Delete scheduled downtimes based upon a query""" q = Query([Downtimes.id, Downtimes.is_service]).filter(query) with detailed_connection(connection) as conn: downtimes = [(row["site"], row["id"], row["is_service"]) for row in q.iterate(conn)] for site_id, downtime_id, is_service in downtimes: if is_service: del_service_downtime(connection, downtime_id, site_id) else: del_host_downtime(connection, downtime_id, site_id)
def _list_services(param): live = sites.live() q = Query(param["columns"]) host_name = param.get("host_name") if host_name is not None: q = q.filter(Services.host_name == host_name) query_expr = param.get("query") if query_expr: q = q.filter(query_expr) result = q.iterate(live) return constructors.serve_json( constructors.collection_object( domain_type="service", value=[ constructors.domain_object( domain_type="service", title=f"{entry['description']} on {entry['host_name']}", identifier=f"{entry['host_name']}:{entry['description']}", editable=False, deletable=False, extensions=entry, self_link=constructors.link_rel( rel="cmk/show", href=constructors.object_action_href( "host", entry["host_name"], "show_service", query_params=[("service_description", entry["description"])], ), method="get", title=f"Show the service {entry['description']}", ), ) for entry in result ], ) )
def schedule_services_downtimes_with_query( connection, query: QueryExpression, start_time: dt.datetime, end_time: dt.datetime, recur: RecurMode = "fixed", duration: int = 0, user_id: str = "", comment: str = "", ): """Schedule downtimes for services based upon a query""" q = Query( [Services.description, Services.host_name], query, ) with detailed_connection(connection) as conn: result = [(row["site"], row["host_name"], row["description"]) for row in q.iterate(conn)] if not result: raise QueryException for site_id, host_name, service_description in result: if not comment: downtime_comment = f"Downtime for service {service_description}@{host_name}" else: downtime_comment = comment schedule_service_downtime( connection, site_id=site_id, host_name=host_name, service_description=service_description, start_time=start_time, end_time=end_time, recur=recur, duration=duration, user_id=user_id, comment=downtime_comment, )
def _list_services(param): live = sites.live() q = Query(param['columns']) host_name = param.get('host_name') if host_name is not None: q = q.filter(Services.host_name == host_name) query_expr = param.get('query') if query_expr: q = q.filter(query_expr) result = q.iterate(live) return constructors.serve_json( constructors.collection_object( domain_type='service', value=[ constructors.domain_object( domain_type='service', title=f"{entry['description']} on {entry['host_name']}", identifier=f"{entry['host_name']}:{entry['description']}", editable=False, deletable=False, extensions=entry, self_link=constructors.link_rel( rel='cmk/show', href=constructors.object_action_href( 'host', entry['host_name'], 'show_service', query_params=[('service_description', entry['description'])], ), method='get', title=f"Show the service {entry['description']}", ), ) for entry in result ], ))
def schedule_services_downtimes_with_query( connection, query: QueryExpression, start_time: dt.datetime, end_time: dt.datetime, recur: RecurMode = 'fixed', duration: int = 0, user_id: str = '', comment: str = '', ): """Schedule downtimes for services based upon a query""" q = Query( [Services.description, Services.host_name], query, ) result = [(row['host_name'], row['description']) for row in q.iterate(connection)] if not result: raise QueryException for host_name, service_description in result: if not comment: downtime_comment = f"Downtime for service {service_description}@{host_name}" else: downtime_comment = comment schedule_service_downtime( connection, host_name=host_name, service_description=service_description, start_time=start_time, end_time=end_time, recur=recur, duration=duration, user_id=user_id, comment=downtime_comment, )
def host_is_monitored(host_name: str) -> bool: return bool( Query([Hosts.name], Hosts.name == host_name).first_value(sites.live()))
def acknowledge_servicegroup_problem( connection, servicegroup_name: str, sticky: bool = False, notify: bool = False, persistent: bool = False, user: str = "", comment: str = "", ): """Acknowledge the problems of the current services of the servicegroup When acknowledging a problem, further notifications for the respective services are disabled, as long as a specific service doesn't change state. At state change, notifications are re-enabled. Args: connection: A livestatus connection object. servicegroup_name: The host-name for which this acknowledgement is for. sticky: If set, only a state-change of the service to an OK state will discard the acknowledgement. Otherwise it will be discarded on any state-change. Defaults to False. notify: If set, notifications will be sent out to the configured contacts. Defaults to False. persistent: If set, the comment will persist a restart. Defaults to False. user: comment: If set, this comment will be stored alongside the acknowledgement. Raises: ValueError: When the servicegroup could not be found. """ with detailed_connection(connection) as conn: group_entries = Query( [tables.Servicegroups.members], tables.Servicegroups.name.equals(servicegroup_name), ).fetchall(conn) acknowledgement = 2 if sticky else 1 # 1: normal, 2: sticky for entry in group_entries: site_id = entry["site"] for host_name, service_description in entry["members"]: send_command( connection, "ACKNOWLEDGE_SVC_PROBLEM", [ host_name, service_description, acknowledgement, int(notify), int(persistent), user, comment, ], site_id=site_id, )
def acknowledge_host_problem( connection, host_name, sticky: bool = False, notify: bool = False, persistent: bool = False, user: str = "", comment: str = "", ): """Acknowledge the current problem for the given host. When acknowledging a problem, notifications for the host are disabled, as long as the host doesn't change state. At state change, notifications are re-enabled. Args: connection: A livestatus connection object. host_name: The host-name for which this acknowledgement is for. sticky: If set, only a state-change of the host to an UP state will discard the acknowledgement. Otherwise it will be discarded on any state-change. Defaults to False. notify: If set, notifications will be sent out to the configured contacts. Defaults to False. persistent: If set, the comment will persist a restart. Defaults to False. user: comment: If set, this comment will be stored alongside the acknowledgement. Examples: >>> from cmk.gui.livestatus_utils.testing import simple_expect >>> cmd = "COMMAND [...] ACKNOWLEDGE_HOST_PROBLEM;example.com;1;0;0;;" >>> with simple_expect() as live: ... _ = live.expect_query("GET hosts\\nColumns: name\\nFilter: name = example.com") ... _ = live.expect_query(cmd, match_type="ellipsis") ... acknowledge_host_problem(live, 'example.com') """ acknowledgement = 2 if sticky else 1 # 1: normal, 2: sticky with detailed_connection(connection) as conn: site_id = Query([Hosts.name], Hosts.name.equals(host_name)).first_value(conn) return send_command( connection, "ACKNOWLEDGE_HOST_PROBLEM", [ host_name, acknowledgement, int(notify), int(persistent), user, comment, ], site_id=site_id, )
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)
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)
def set_acknowledgement_on_hosts(params): """Set acknowledgement on related hosts""" 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 == "host": name = body["host_name"] host_state = Query([Hosts.state], Hosts.name == name).value(live) if not host_state: raise ProblemException( status=422, title=f"Host {name!r} has no problem.", ) acknowledge_host_problem( live, name, sticky=sticky, notify=notify, persistent=persistent, user=user.ident, comment=comment, ) elif acknowledge_type == "hostgroup": host_group = body["hostgroup_name"] try: acknowledge_hostgroup_problem( live, host_group, sticky=sticky, notify=notify, persistent=persistent, user=user.ident, comment=comment, ) except ValueError: raise ProblemException( 400, title="Host group could not be found.", detail=f"Unknown host group: {host_group}", ) elif acknowledge_type == "host_by_query": query = body["query"] hosts = Query([Hosts.name], query).fetchall(live) if not hosts: raise ProblemException( status=422, title="The provided query returned no monitored hosts", ) for host in hosts: acknowledge_host_problem( live, host.name, 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)
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 create_service_related_downtime(params): """Create a service related scheduled downtime""" body = params["body"] live = sites.live() downtime_type: DowntimeType = body["downtime_type"] if downtime_type == "service": host_name = body["host_name"] with detailed_connection(live) as conn: site_id = Query(columns=[Hosts.name], filter_expr=Hosts.name.op("=", host_name)).value( conn ) downtime_commands.schedule_service_downtime( live, site_id, host_name=body["host_name"], service_description=body["service_descriptions"], start_time=body["start_time"], end_time=body["end_time"], recur=body["recur"], duration=body["duration"], user_id=user.ident, comment=body.get( "comment", f"Downtime for services {', '.join(body['service_descriptions'])!r}@{body['host_name']!r}", ), ) elif downtime_type == "servicegroup": downtime_commands.schedule_servicegroup_service_downtime( live, servicegroup_name=body["servicegroup_name"], start_time=body["start_time"], end_time=body["end_time"], recur=body["recur"], duration=body["duration"], user_id=user.ident, comment=body.get("comment", f"Downtime for servicegroup {body['servicegroup_name']!r}"), ) elif downtime_type == "service_by_query": try: downtime_commands.schedule_services_downtimes_with_query( live, query=body["query"], start_time=body["start_time"], end_time=body["end_time"], recur=body["recur"], duration=body["duration"], user_id=user.ident, comment=body.get("comment", ""), ) except QueryException: return problem( status=422, title="Query did not match any service", detail="The provided query returned an empty list so no downtime was set", ) else: return problem( status=400, title="Unhandled downtime-type.", detail=f"The downtime-type {downtime_type!r} is not supported.", ) return Response(status=204)
def schedule_hostgroup_host_downtime( connection, hostgroup_name: 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 = '', ): """Schedules downtime for all hosts in a given hostgroup. Args: connection: A LiveStatus connection object. hostgroup_name: The name of the hostgroup. A downtime will be scheduled for all hosts in this hostgroup. 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. Consequently, 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. connection: See Also: * https://assets.nagios.com/downloads/nagioscore/docs/externalcmds/cmdinfo.php?command_id=123 """ members: List[str] = Query([tables.Hostgroups.members], tables.Hostgroups.name.equals(hostgroup_name)).value(connection) schedule_host_downtime( connection, host_name=members, start_time=start_time, end_time=end_time, include_all_services=include_all_services, recur=recur, trigger_id=trigger_id, duration=duration, user_id=user_id, comment=comment, )
def schedule_servicegroup_service_downtime( connection, servicegroup_name: str, start_time: dt.datetime, end_time: dt.datetime, include_hosts: bool = False, recur: RecurMode = 'fixed', trigger_id: int = 0, duration: int = 0, user_id: str = '', comment: str = '', ): """Schedules downtime for all hosts, which have services in a given servicegroup. Args: connection: A LiveStatus connection object. servicegroup_name: The name of the service group. Any host having a service in this group will be A downtime will be scheduled for all hosts in this group. start_time: When the downtime shall begin. end_time: When the downtime shall end. include_hosts: When set to True, all hosts will also receive a scheduled downtime, not just their services which belong to this service group. 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. Consequently, 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. connection: """ members: List[List[str]] = Query( [tables.Servicegroups.members], tables.Servicegroups.name.equals(servicegroup_name), ).value(connection) for host_name, service_description in members: 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, ) if include_hosts: host_names = _deduplicate([_host_name for _host_name, _ in members]) schedule_host_downtime( connection, host_name=host_names, start_time=start_time, end_time=end_time, recur=recur, trigger_id=trigger_id, duration=duration, user_id=user_id, comment=comment, )