Refactoring: Moved check parameters from unsorted.py to dedicated modules (CMK-1393)
[check_mk.git] / cmk / gui / sites.py
blob282f611d017e0f448f1d92f7317929aa4cdfd965
1 #!/usr/bin/python
2 # -*- encoding: utf-8; py-indent-offset: 4 -*-
3 # +------------------------------------------------------------------+
4 # | ____ _ _ __ __ _ __ |
5 # | / ___| |__ ___ ___| | __ | \/ | |/ / |
6 # | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
7 # | | |___| | | | __/ (__| < | | | | . \ |
8 # | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
9 # | |
10 # | Copyright Mathias Kettner 2014 mk@mathias-kettner.de |
11 # +------------------------------------------------------------------+
13 # This file is part of Check_MK.
14 # The official homepage is at http://mathias-kettner.de/check_mk.
16 # check_mk is free software; you can redistribute it and/or modify it
17 # under the terms of the GNU General Public License as published by
18 # the Free Software Foundation in version 2. check_mk is distributed
19 # in the hope that it will be useful, but WITHOUT ANY WARRANTY; with-
20 # out even the implied warranty of MERCHANTABILITY or FITNESS FOR A
21 # PARTICULAR PURPOSE. See the GNU General Public License for more de-
22 # tails. You should have received a copy of the GNU General Public
23 # License along with GNU Make; see the file COPYING. If not, write
24 # to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
25 # Boston, MA 02110-1301 USA.
27 from typing import List, Tuple # pylint: disable=unused-import
29 # suppress "Cannot find module" error from mypy
30 import livestatus # type: ignore
32 import cmk
34 import cmk.gui.config as config
35 from cmk.gui.globals import html
37 # .--API-----------------------------------------------------------------.
38 # | _ ____ ___ |
39 # | / \ | _ \_ _| |
40 # | / _ \ | |_) | | |
41 # | / ___ \| __/| | |
42 # | /_/ \_\_| |___| |
43 # | |
44 # +----------------------------------------------------------------------+
45 # | Functions und names for the public |
46 # '----------------------------------------------------------------------'
49 def live():
50 """Get Livestatus connection object matching the current site configuration
51 and user settings. On the first call the actual connection is being made."""
52 if _live is None:
53 _connect()
54 return _live
57 # Accessor for the status of a single site
58 def state(site_id, deflt=None):
59 """Get the status of a certain site. Returns a dictionary with various
60 entries. deflt is being returned in case the specified site doe not
61 exist or has no state."""
62 if _live is None:
63 _connect()
64 return _site_status.get(site_id, deflt)
67 def states():
68 """Returns dictionary of all known site states."""
69 if _live is None:
70 _connect()
71 return _site_status
74 def disconnect():
75 """Actively closes all Livestatus connections."""
76 global _live, _site_status
77 _live = None
78 _site_status = None
81 def sites_using_foreign_cores():
82 site_ids = []
83 for site_id, core in cores_by_site().items():
84 if core not in ["cmc", None]:
85 site_ids.append(site_id)
87 return sorted(site_ids)
90 def all_sites_use_foreign_cores():
91 return "cmc" not in cores_by_site().itervalues()
94 def cores_by_site():
95 cores = {}
96 for site_id, site_state in states().items():
97 # Offline sites don't provide core info. Assume CMC is this case
98 if site_state["state"] == "dead":
99 core = "cmc"
100 else:
101 core = site_state.get("core")
103 cores[site_id] = core
105 return cores
108 def all_groups(what):
109 # type: (str) -> List[Tuple[str, str]]
110 """Returns a list of host/service/contact groups (pairs of name/alias)
112 Groups are collected via livestatus from all sites. In case no alias is defined
113 the name is used as second element. The list is sorted by lower case alias in the first place."""
114 groups = live().query("GET %sgroups\nCache: reload\nColumns: name alias\n" % what)
115 return sorted([(name, alias or name) for name, alias in groups], key=lambda e: e[1].lower())
119 # .--Internal------------------------------------------------------------.
120 # | ___ _ _ |
121 # | |_ _|_ __ | |_ ___ _ __ _ __ __ _| | |
122 # | | || '_ \| __/ _ \ '__| '_ \ / _` | | |
123 # | | || | | | || __/ | | | | | (_| | | |
124 # | |___|_| |_|\__\___|_| |_| |_|\__,_|_| |
125 # | |
126 # +----------------------------------------------------------------------+
127 # | Internal functiions and variables |
128 # '----------------------------------------------------------------------'
130 # The global livestatus object. This is initialized automatically upon first access
131 # to the accessor function live()
132 _live = None
134 # _site_status keeps a dictionary for each site with the following keys:
135 # "state" --> "online", "disabled", "down", "unreach", "dead" or "waiting"
136 # "exception" --> An error exception in case of down, unreach, dead or waiting
137 # "status_host_state" --> host state of status host (0, 1, 2 or None)
138 # "livestatus_version" --> Version of sites livestatus if "online"
139 # "program_version" --> Version of Nagios if "online"
140 _site_status = None
143 # Build up a connection to livestatus to either a single site or multiple sites.
144 def _connect():
145 _init_site_status()
146 _connect_multiple_sites()
147 _set_livestatus_auth()
150 def _connect_multiple_sites():
151 global _live
152 enabled_sites, disabled_sites = _get_enabled_and_disabled_sites()
153 _set_initial_site_states(enabled_sites, disabled_sites)
155 if cmk.is_managed_edition():
156 import cmk.gui.cme.managed as managed
157 _live = managed.CMEMultiSiteConnection(enabled_sites, disabled_sites)
158 else:
159 _live = livestatus.MultiSiteConnection(enabled_sites, disabled_sites)
161 # Fetch status of sites by querying the version of Nagios and livestatus
162 # This may be cached by a proxy for up to the next configuration reload.
163 _live.set_prepend_site(True)
164 for response in _live.query(
165 "GET status\n"
166 "Cache: reload\n"
167 "Columns: livestatus_version program_version program_start num_hosts num_services"):
169 try:
170 site_id, v1, v2, ps, num_hosts, num_services = response
171 except ValueError:
172 e = livestatus.MKLivestatusQueryError("Invalid response to status query: %s" % response)
174 site_id = response[0]
175 _update_site_status(site_id, {
176 "exception": e,
177 "status_host_state": None,
178 "state": _status_host_state_name(None),
180 continue
182 _update_site_status(
183 site_id, {
184 "state": "online",
185 "livestatus_version": v1,
186 "program_version": v2,
187 "program_start": ps,
188 "num_hosts": num_hosts,
189 "num_services": num_services,
190 "core": v2.startswith("Check_MK") and "cmc" or "nagios",
192 _live.set_prepend_site(False)
194 # TODO(lm): Find a better way to make the Livestatus object trigger the update
195 # once self.deadsites is updated.
196 update_site_states_from_dead_sites()
199 def _get_enabled_and_disabled_sites():
200 enabled_sites, disabled_sites = {}, {}
202 for site_id, site in config.user.authorized_sites():
203 site = _site_config_for_livestatus(site_id, site)
205 if config.user.is_site_disabled(site_id):
206 disabled_sites[site_id] = site
207 else:
208 enabled_sites[site_id] = site
210 return enabled_sites, disabled_sites
213 def _site_config_for_livestatus(site_id, site):
214 site = site.copy()
216 if site["socket"][0] == "proxy":
217 site["cache"] = site["socket"][1].get("cache", True)
219 site["socket"] = encode_socket_for_livestatus(site_id, site["socket"])
221 return site
224 def encode_socket_for_livestatus(site_id, socket_spec):
225 family_spec, address_spec = socket_spec
227 if family_spec == "local":
228 return "unix:%s" % cmk.utils.paths.livestatus_unix_socket
230 if family_spec == "proxy":
231 return "unix:%sproxy/%s" % (cmk.utils.paths.livestatus_unix_socket, site_id)
233 if family_spec == "unix":
234 return "%s:%s" % (family_spec, address_spec["path"])
236 if family_spec in ["tcp", "tcp6"]:
237 return "%s:%s:%d" % (family_spec, address_spec["address"][0], address_spec["address"][1])
239 raise NotImplementedError()
242 def update_site_states_from_dead_sites():
243 # Get exceptions in case of dead sites
244 for site_id, deadinfo in live().dead_sites().items():
245 status_host_state = deadinfo.get("status_host_state")
246 _update_site_status(
247 site_id, {
248 "exception": deadinfo["exception"],
249 "status_host_state": status_host_state,
250 "state": _status_host_state_name(status_host_state),
254 def _status_host_state_name(shs):
255 if shs is None:
256 return "dead"
257 return {
258 1: "down",
259 2: "unreach",
260 3: "waiting",
261 }.get(shs, "unknown")
264 def _init_site_status():
265 global _site_status
266 _site_status = {}
269 def _set_initial_site_states(enabled_sites, disabled_sites):
270 for site_id, site in enabled_sites.items():
271 _set_site_status(site_id, {"state": "dead", "site": site})
273 for site_id, site in disabled_sites.items():
274 _set_site_status(site_id, {"state": "disabled", "site": site})
277 def _set_site_status(site_id, status):
278 _site_status[site_id] = status
281 def _update_site_status(site_id, status):
282 _site_status[site_id].update(status)
285 # If Multisite is retricted to data the user is a contact for, we need to set an
286 # AuthUser: header for livestatus.
287 def _set_livestatus_auth():
288 user_id = _livestatus_auth_user()
289 if user_id is not None:
290 _live.set_auth_user('read', user_id)
291 _live.set_auth_user('action', user_id)
293 # May the user see all objects in BI aggregations or only some?
294 if not config.user.may("bi.see_all"):
295 _live.set_auth_user('bi', user_id)
297 # May the user see all Event Console events or only some?
298 if not config.user.may("mkeventd.seeall"):
299 _live.set_auth_user('ec', user_id)
301 # Default auth domain is read. Please set to None to switch off authorization
302 _live.set_auth_domain('read')
305 # Returns either None when no auth user shal be set or the name of the user
306 # to be used as livestatus auth user
307 def _livestatus_auth_user():
308 if not config.user.may("general.see_all"):
309 return config.user.id
311 force_authuser = html.request.var("force_authuser")
312 if force_authuser == "1":
313 return config.user.id
314 elif force_authuser == "0":
315 return None
316 elif force_authuser:
317 return force_authuser # set a different user
319 # TODO: Remove this with 1.5.0/1.6.0
320 if html.output_format != 'html' \
321 and config.user.get_attribute("force_authuser_webservice"):
322 return config.user.id
324 if config.user.get_attribute("force_authuser"):
325 return config.user.id
327 return None