def get_only_sites_from_context(context: VisualContext) -> Optional[List[SiteId]]: """Gather possible existing "only sites" information from context We need to deal with all possible site filters (sites, site and siteopt). VisualContext is structured like this: {"site": {"site": "sitename"}} {"siteopt": {"site": "sitename"}} {"sites": {"sites": "sitename|second"}} The difference is no fault or "old" data structure. We can have both kind of structures. These are the data structure the visuals work with. "site" and "sites" are conflicting filters. The new optional filter "sites" for many sites filter is only used if the view is configured to only this filter. """ if "sites" in context and "site" not in context: only_sites = context["sites"]["sites"] only_sites_list = [SiteId(site) for site in only_sites.strip().split("|") if site] return only_sites_list if only_sites_list else None for var in ["site", "siteopt"]: if site_name := context.get(var, {}).get("site"): return [SiteId(site_name)]
def test_rewrite_host_explicit_site(): _write_hosts_mk("""# Created by WATO # encoding: utf-8 all_hosts += ['ag'] host_tags.update({'ag': {'site': 'stable', 'address_family': 'ip-v4-only', 'ip-v4': 'ip-v4', 'agent': 'cmk-agent', 'tcp': 'tcp', 'piggyback': 'auto-piggyback', 'snmp_ds': 'no-snmp', 'criticality': 'prod', 'networking': 'lan'}}) host_labels.update({}) # Explicit IPv4 addresses ipaddresses.update({'ag': '127.0.0.1'}) # Host attributes (needed for WATO) host_attributes.update( {'ag': {'ipaddress': '127.0.0.1', 'site': 'stable', 'meta_data': {'created_at': 1627486290.0, 'created_by': 'cmkadmin', 'updated_at': 1627993165.0079741}}}) """) assert Folder.root_folder().host("ag").attribute("site") == "stable" update_hosts_and_folders(SiteId("stable"), SiteId("dingdong")) assert Folder.root_folder().host("ag").attribute("site") == "dingdong" # also verify that the attributes (host_tags) not read by WATO have been updated hosts_config = Folder.root_folder()._load_hosts_file() assert hosts_config is not None assert hosts_config["host_tags"]["ag"]["site"] == "dingdong"
def test_update_basic_site_config(): _write_site_config({ "heute": { "alias": "Die central Site", "disable_wato": True, "disabled": False, "insecure": False, "multisiteurl": "", "persist": False, "replicate_ec": False, "replication": None, "timeout": 10, "user_login": True, "url_prefix": "/heute/", "proxy": None, "socket": ("local", None), }, }) update_site_config(SiteId("heute"), SiteId("haha")) site_mgmt = SiteManagementFactory().factory() all_sites = site_mgmt.load_sites() # Site entry has been renamed assert "heute" not in all_sites assert "haha" in all_sites # url_prefix is updated assert all_sites["haha"]["url_prefix"] == "/haha/"
def test_rewrite_tags_no_explicit_site_set(monkeypatch): _write_folder_attributes({ "title": "Main", "attributes": { "meta_data": { "created_at": 1627991988.6232662, "updated_at": 1627991994.7575116, "created_by": None, } }, "num_hosts": 0, "lock": False, "lock_subfolders": False, "__id": "9f5a85386b7c4ad68738d66a49a4bfa9", }) _write_hosts_mk("""# Created by WATO # encoding: utf-8 all_hosts += ['ag'] host_tags.update({'ag': {'site': 'NO_SITE', 'address_family': 'ip-v4-only', 'ip-v4': 'ip-v4', 'agent': 'cmk-agent', 'tcp': 'tcp', 'piggyback': 'auto-piggyback', 'snmp_ds': 'no-snmp', 'criticality': 'prod', 'networking': 'lan'}}) host_labels.update({}) # Explicit IPv4 addresses ipaddresses.update({'ag': '127.0.0.1'}) # Host attributes (needed for WATO) host_attributes.update( {'ag': {'ipaddress': '127.0.0.1', 'meta_data': {'created_at': 1627486290.0, 'created_by': 'cmkadmin', 'updated_at': 1627993165.0079741}}}) """) assert Folder.root_folder().attribute("site") is None assert Folder.root_folder().load_host("ag").attribute("site") is None assert Folder.root_folder().load_host("ag").site_id() == "NO_SITE" # Simulate changed omd_site that we would have in application code in the moment the rename # action is executed. monkeypatch.setattr(cmk.gui.watolib.hosts_and_folders, "omd_site", lambda: "dingdong") monkeypatch.setattr(HostAttributeSite, "default_value", lambda self: "dingdong") update_hosts_and_folders(SiteId("NO_SITE"), SiteId("dingdong")) Folder.invalidate_caches() assert Folder.root_folder().attribute("site") is None assert Folder.root_folder().load_host("ag").attribute("site") is None assert Folder.root_folder().load_host("ag").site_id() == "dingdong" assert Folder.root_folder().load_host( "ag").tag_groups()["site"] == "dingdong" # also verify that the attributes (host_tags) not read by WATO have been updated hosts_config = Folder.root_folder()._load_hosts_file() assert hosts_config is not None assert hosts_config["host_tags"]["ag"]["site"] == "dingdong"
def test_run_executes_plugins(capsys, test_registry, mocker): handler_mock = mocker.MagicMock() test_registry.register( RenameAction(name="test", title="Test Title", sort_index=0, handler=handler_mock) ) assert main.main(["-v", "old"]) == 0 output = capsys.readouterr() assert output.err == "" assert "1/1 Test Title..." in output.out assert output.out.endswith("Done\n") assert handler_mock.called_once_with(SiteId("old"), SiteId("NO_SITE"))
def test_disable_activate_changes_writer(mocker: MockerFixture) -> None: add_to_site_mock = mocker.patch.object(ActivateChangesWriter, "_add_change_to_site") add_change("ding", "dong", sites=[SiteId("a")]) add_to_site_mock.assert_called_once() add_to_site_mock.reset_mock() with ActivateChangesWriter.disable(): add_change("ding", "dong", sites=[SiteId("a")]) add_to_site_mock.assert_not_called() add_to_site_mock.reset_mock() add_change("ding", "dong", sites=[SiteId("a")]) add_to_site_mock.assert_called_once()
def generate_response_data(cls, properties, context, settings): if config.is_single_local_site(): site_id: Optional[SiteId] = config.omd_site() else: site_filter = context.get("site", {}).get("site") site_id = SiteId(site_filter) if site_filter else None render_mode = "hosts" if site_id else "sites" if render_mode == "hosts": assert site_id is not None elements = cls._collect_hosts_data(site_id) default_title = _("Host overview") elif render_mode == "sites": elements = cls._collect_sites_data() default_title = _("Site overview") else: raise NotImplementedError() return { # TODO: This should all be done inside the dashlet class once it is instantiated by the # ajax call "title": render_title_with_macros_string( context, settings["single_infos"], settings.get("title", default_title), default_title, ), "render_mode": render_mode, "plot_definitions": [], "data": [e.serialize() for e in elements], }
def macro_mapping_from_context( context: VisualContext, single_infos: SingleInfos, title: str, default_title: str, **additional_macros: str, ) -> MacroMapping: macro_mapping = {"$DEFAULT_TITLE$": default_title} macro_mapping.update({ macro: context[key][key] for macro, key in ( ("$HOST_NAME$", "host"), ("$SERVICE_DESCRIPTION$", "service"), ) if key in context and key in context[key] and key in single_infos }) if "$HOST_ALIAS$" in title and "$HOST_NAME$" in macro_mapping: macro_mapping["$HOST_ALIAS$"] = get_alias_of_host( SiteId(additional_macros.get("$SITE$", "")), macro_mapping["$HOST_NAME$"], ) macro_mapping.update(additional_macros) return macro_mapping
def _query_site(connection, host_name: str) -> SiteId: 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 SiteId(site_id)
def test_get_request( self, monkeypatch: pytest.MonkeyPatch, ) -> None: request = Request({}) request.set_var("site_id", "NO_SITE") request.set_var("to_delete", "['x/y/z.txt', 'abc.ending', '/ä/☃/☕']") request.set_var("config_generation", "123") request.files = werkzeug_datastructures.ImmutableMultiDict({ "sync_archive": werkzeug_datastructures.FileStorage( stream=io.BytesIO(b"some data"), filename="sync_archive", name="sync_archive", ) }) monkeypatch.setattr( activate_changes, "_request", request, ) assert (activate_changes.AutomationReceiveConfigSync().get_request() == activate_changes.ReceiveConfigSyncRequest( site_id=SiteId("NO_SITE"), sync_archive=b"some data", to_delete=["x/y/z.txt", "abc.ending", "/ä/☃/☕"], config_generation=123, ))
def main(args: List[str]) -> int: arguments = parse_arguments(args) setup_logging(arguments) if arguments.debug: cmk.utils.debug.enable() logger.debug("parsed arguments: %s", arguments) new_site_id = SiteId(cmk.utils.site.omd_site()) if arguments.old_site_id == new_site_id: logger.info( "OLD_SITE_ID is equal to current OMD_SITE - Nothing to do.") return 0 load_plugins() try: has_errors = run(arguments, arguments.old_site_id, new_site_id) except Exception: if arguments.debug: raise logger.exception( 'ERROR: Please repair this and run "cmk-post-rename-site -v" ' "BEFORE starting the site again.") return 1 return 1 if has_errors else 0
def omd_site() -> SiteId: try: return SiteId(os.environ["OMD_SITE"]) except KeyError: raise MKGeneralException( _("OMD_SITE environment variable not set. You can " "only execute this in an OMD site."))
def generate_response_data(cls, properties, context, settings): site_id = context.get("site", {}).get("site") time_range, range_title = Timerange().compute_range( properties["time_range"]) default_title = _("Top alerters - %s") % range_title elements = cls._collect_data( only_sites=[SiteId(site_id)] if site_id else None, since=time_range[0], limit=properties.get("limit_objects"), ) return { "render_mode": "alert_statistics", # TODO: This should all be done inside the dashlet class once it is instantiated by the # ajax call "title": render_title_with_macros_string( context, settings["single_infos"], settings.get("title", default_title), default_title, ), "title_url": settings.get("title_url"), "plot_definitions": [], "data": [e.serialize() for e in elements], "upper_bound": max([100] + [e.num_problems + 1 for e in elements]), }
def generate_response_data(cls, properties, context, settings): if config.is_single_local_site(): site_id: Optional[SiteId] = config.omd_site() else: site_filter = context.get("site", {}).get("site") site_id = SiteId(site_filter) if site_filter else None render_mode = "hosts" if site_id else "sites" if render_mode == "hosts": assert site_id is not None elements = cls._collect_hosts_data(site_id) elif render_mode == "sites": elements = cls._collect_sites_data() else: raise NotImplementedError() return { # TODO: Get the correct dashlet title. This needs to use the general dashlet title # calculation. We somehow have to get the title from # cmk.gui.dashboard._render_dashlet_title. "title": _("Site overview"), "render_mode": render_mode, "plot_definitions": [], "data": [e.serialize() for e in elements], }
def _get_crash_report_row(self, crash_id: str, site_id: str) -> Optional[Dict[str, str]]: rows = CrashReportsRowTable().get_crash_report_rows( only_sites=[SiteId(site_id)], filter_headers="Filter: id = %s" % livestatus.lqencode(crash_id)) if not rows: return None return rows[0]
def test_key_mgmt_create_key(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: monkeypatch.setattr(time, "time", lambda: 123) key = key_mgmt.generate_key("älias", "passphra$e", UserId("dingdöng"), SiteId("test-site")) assert isinstance(key, key_mgmt.Key) assert key.alias == "älias" assert key.date == 123 assert key.owner == "dingdöng" assert key.certificate.startswith("-----BEGIN CERTIFICATE---") assert key.private_key.startswith("-----BEGIN ENCRYPTED PRIVATE KEY---")
def get_request(self) -> PushSnapshotRequest: site_id = SiteId(request.get_ascii_input_mandatory("siteid")) cmk.gui.watolib.activate_changes.verify_remote_site_config(site_id) snapshot = request.uploaded_file("snapshot") if not snapshot: raise MKGeneralException( _("Invalid call: The snapshot is missing.")) return PushSnapshotRequest(site_id=site_id, tar_content=snapshot[2])
def _get_alert_stats(cls, only_sites: OnlySites, since: int) -> Dict[Tuple[SiteId, HostName, str], AlertStats]: try: sites.live().set_only_sites(only_sites) sites.live().set_prepend_site(True) rows: LivestatusResponse = sites.live().query(cls._alert_stats_query(since)) finally: sites.live().set_prepend_site(False) sites.live().set_only_sites(None) return {(SiteId(row[0]), HostName(row[1]), row[2]): AlertStats(*row[3:]) for row in rows}
def _get_hostnames_from_filters( self, context: VisualContext, filters: List[Filter] ) -> Set[HostName]: filter_headers = "".join(get_livestatus_filter_headers(context, filters)) query = "GET hosts\nColumns: name" if filter_headers: query += "\n%s" % filter_headers site = request.var("site") with sites.only_sites(None if site is None else SiteId(site)): return {HostName(x) for x in sites.live().query_column_unique(query)}
def page(self): check_csrf_token() user.need_permission("wato.activate") api_request = self.webapi_request() # ? type of activate_until is unclear activate_until = api_request.get("activate_until") if not activate_until: raise MKUserError("activate_until", _('Missing parameter "%s".') % "activate_until") manager = activate_changes.ActivateChangesManager() manager.load() # ? type of api_request is unclear affected_sites_request = ensure_str( # pylint: disable= six-ensure-str-bin-call api_request.get("sites", "").strip()) if not affected_sites_request: affected_sites = manager.dirty_and_active_activation_sites() else: affected_sites = [ SiteId(s) for s in affected_sites_request.split(",") ] comment: Optional[str] = api_request.get("comment", "").strip() activate_foreign = api_request.get("activate_foreign", "0") == "1" valuespec = _vs_activation("", manager.has_foreign_changes()) if valuespec: valuespec.validate_value( { "comment": comment, "foreign": activate_foreign, }, "activate", ) if comment == "": comment = None activation_id = manager.start( sites=affected_sites, activate_until=ensure_str(activate_until), # pylint: disable= six-ensure-str-bin-call comment=comment, activate_foreign=activate_foreign, ) return { "activation_id": activation_id, }
def page(self) -> None: if not user.may("wato.diagnostics"): raise MKAuthException( _("Sorry, you lack the permission for downloading diagnostics dumps." )) site = SiteId(request.get_ascii_input_mandatory("site")) tarfile_name = request.get_ascii_input_mandatory("tarfile_name") file_content = self._get_diagnostics_dump_file(site, tarfile_name) response.set_content_type("application/x-tgz") response.headers[ "Content-Disposition"] = "Attachment; filename=%s" % tarfile_name response.set_data(file_content)
def _verify_cache_integrity(self) -> IntegrityCheckResponse: """Verify last program start value in redis with current value""" last_program_starts = self._redis_get_last_program_starts() if not last_program_starts: all_sites = self._get_site_ids() self._sites_to_update.update(all_sites) return IntegrityCheckResponse.UPDATE for site_id, last_program_start in last_program_starts.items(): # How can last_program_start be None if it is a Dict[str, str]? # Perhaps _redis_get_last_program_starts() should return something like an Optional[Mapping[SiteId, Optional[int]]. if last_program_start is None or ( int(last_program_start) != self._livestatus_get_last_program_start(SiteId(site_id))): self._sites_to_update.update([SiteId(site_id)]) if self._sites_to_update: return IntegrityCheckResponse.UPDATE return IntegrityCheckResponse.USE
def test_rewrite_folder_explicit_site(): _write_folder_attributes({ "title": "Main directory", "attributes": { "site": "stable", "meta_data": { "created_at": 1627991988.6232662, "updated_at": 1627991994.7575116, "created_by": None, }, }, "num_hosts": 0, "lock": False, "lock_subfolders": False, "__id": "9f5a85386b7c4ad68738d66a49a4bfa9", }) folder = Folder.root_folder() folder.load_instance() assert folder.attribute("site") == "stable" update_hosts_and_folders(SiteId("stable"), SiteId("dingdong")) assert folder.attribute("site") == "dingdong"
def _get_default_view_hostnames(self, max_nodes: int) -> Set[HostName]: """Returns all hosts without any parents""" query = "GET hosts\nColumns: name\nFilter: parents =" site = request.var("site") with sites.prepend_site(), sites.only_sites(None if site is None else SiteId(site)): hosts = [(x[0], x[1]) for x in sites.live().query(query)] # If no explicit site is set and the number of initially displayed hosts # exceeds the auto growth range, only the hosts of the master site are shown if len(hosts) > max_nodes: hostnames = {HostName(x[1]) for x in hosts if x[0] == omd_site()} else: hostnames = {HostName(x[1]) for x in hosts} return hostnames
def get_crash_report_rows(self, only_sites: Optional[List[SiteId]], filter_headers: str) -> List[Dict[str, str]]: # First fetch the information that is needed to query for the dynamic columns (crash_info, # ...) crash_infos = self._get_crash_report_info(only_sites, filter_headers) if not crash_infos: return [] rows = [] for crash_info in crash_infos: file_path = "/".join( [crash_info["crash_type"], crash_info["crash_id"]]) headers = ["crash_info"] columns = [ "file:crash_info:%s/crash.info" % livestatus.lqencode(file_path) ] if crash_info["crash_type"] == "check": headers += ["agent_output", "snmp_info"] columns += [ "file:agent_output:%s/agent_output" % livestatus.lqencode(file_path), "file:snmp_info:%s/snmp_info" % livestatus.lqencode(file_path), ] try: sites.live().set_prepend_site(False) sites.live().set_only_sites( [SiteId(ensure_str(crash_info["site"]))]) raw_row = sites.live().query_row( "GET crashreports\n" "Columns: %s\n" "Filter: id = %s" % (" ".join(columns), livestatus.lqencode(crash_info["crash_id"]))) finally: sites.live().set_only_sites(None) sites.live().set_prepend_site(False) crash_info.update(dict(zip(headers, raw_row))) rows.append(crash_info) return rows
def ajax_set_snapin_site(): response.set_content_type("application/json") ident = request.var("ident") if ident not in snapin_registry: raise MKUserError(None, _("Invalid ident")) site = request.var("site") site_choices = dict([(SiteId(""), _("All sites"))] + user_sites.get_configured_site_choices()) if site not in site_choices: raise MKUserError(None, _("Invalid site")) snapin_sites = user.load_file("sidebar_sites", {}, lock=True) snapin_sites[ident] = site user.save_file("sidebar_sites", snapin_sites)
def _get_site_data_files(self) -> List[Tuple[Path, SiteProgramStart]]: data_files = [] for path_object in self._path_site_structure_data.iterdir(): if path_object.is_dir(): continue name = path_object.name if not name.startswith(self._site_cache_prefix): continue try: _prefix, site_id, timestamp = name.split(".", 2) except ValueError: path_object.unlink(missing_ok=True) continue data_files.append((path_object, (SiteId(site_id), int(timestamp)))) return data_files
def get_request(self): site_id = SiteId(request.get_ascii_input_mandatory("site_id")) activate_changes.verify_remote_site_config(site_id) try: serialized_domain_requests = ast.literal_eval( request.get_ascii_input_mandatory("domains") ) if serialized_domain_requests and isinstance(serialized_domain_requests[0], str): serialized_domain_requests = [ asdict(DomainRequest(x)) for x in serialized_domain_requests ] except SyntaxError: raise watolib.MKAutomationException( _("Invalid request: %r") % request.get_ascii_input_mandatory("domains") ) return ActivateChangesRequest(site_id=site_id, domains=serialized_domain_requests)
def _create_tasks_from_hosts(hosts_to_discover: List[DiscoveryHost], bulk_size: BulkSize) -> List[DiscoveryTask]: """Create a list of tasks for the job Each task groups the hosts together that are in the same folder and site. This is mainly done to reduce the overhead of site communication and loading/saving of files """ current_site_and_folder = None tasks: List[DiscoveryTask] = [] for site_id, folder_path, host_name in sorted(hosts_to_discover): if (not tasks or (site_id, folder_path) != current_site_and_folder or len(tasks[-1].host_names) >= bulk_size): tasks.append( DiscoveryTask(SiteId(site_id), folder_path, [host_name])) else: tasks[-1].host_names.append(host_name) current_site_and_folder = site_id, folder_path return tasks
def get_alias_of_host(site_id: Optional[SiteId], host_name: str) -> SiteId: query = ("GET hosts\n" "Cache: reload\n" "Columns: alias\n" "Filter: name = %s" % lqencode(host_name)) with only_sites(site_id): try: return live().query_value(query) except Exception as e: logger.warning( "Could not determine alias of host %s on site %s: %s", host_name, site_id, e, ) if config.debug: raise return SiteId(host_name)