Fix typecheck idiom in cmk/gui/{plugins,wato}
[check_mk.git] / cmk / gui / wato / pages / host_diagnose.py
blob8df09fc2c58bca9b84d11be3ed0645dc6377da43
1 #!/usr/bin/env 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.
26 """Verify or find out a hosts agent related configuration"""
28 import json
30 import cmk.gui.pages
31 import cmk.gui.config as config
32 import cmk.gui.watolib as watolib
33 import cmk.gui.forms as forms
34 from cmk.gui.exceptions import MKAuthException, MKGeneralException, MKUserError
35 from cmk.gui.plugins.wato.utils.base_modes import WatoWebApiMode
36 from cmk.gui.i18n import _
37 from cmk.gui.globals import html
38 from cmk.gui.plugins.wato.utils.context_buttons import host_status_button
40 from cmk.gui.valuespec import (
41 TextAscii,
42 DropdownChoice,
43 Integer,
44 Float,
45 Dictionary,
46 Password,
47 HostAddress,
48 FixedValue,
51 from cmk.gui.plugins.wato import (
52 WatoMode,
53 mode_registry,
54 monitoring_macro_help,
58 @mode_registry.register
59 class ModeDiagHost(WatoMode):
60 @classmethod
61 def name(cls):
62 return "diag_host"
64 @classmethod
65 def permissions(cls):
66 return ["hosts", "diag_host"]
68 @classmethod
69 def diag_host_tests(cls):
70 return [
71 ('ping', _('Ping')),
72 ('agent', _('Agent')),
73 ('snmpv1', _('SNMPv1')),
74 ('snmpv2', _('SNMPv2c')),
75 ('snmpv2_nobulk', _('SNMPv2c (without Bulkwalk)')),
76 ('snmpv3', _('SNMPv3')),
77 ('traceroute', _('Traceroute')),
80 def _from_vars(self):
81 self._hostname = html.var("host")
82 if not self._hostname:
83 raise MKGeneralException(_('The hostname is missing.'))
85 self._host = watolib.Folder.current().host(self._hostname)
86 self._host.need_permission("read")
88 if self._host.is_cluster():
89 raise MKGeneralException(_('This page does not support cluster hosts.'))
91 def title(self):
92 return _('Diagnostic of host') + " " + self._hostname
94 def buttons(self):
95 html.context_button(_("Folder"), watolib.folder_preserving_link([("mode", "folder")]), "back")
96 host_status_button(self._hostname, "hoststatus")
97 html.context_button(_("Properties"), self._host.edit_url(), "edit")
98 if config.user.may('wato.rulesets'):
99 html.context_button(_("Parameters"), self._host.params_url(), "rulesets")
100 html.context_button(_("Services"), self._host.services_url(), "services")
102 def action(self):
103 if not html.check_transaction():
104 return
106 if html.var('_try'):
107 try:
108 self._validate_diag_html_vars()
109 except MKUserError, e:
110 html.add_user_error(e.varname, e)
111 return
113 if html.var('_save'):
114 # Save the ipaddress and/or community
115 vs_host = self._vs_host()
116 new = vs_host.from_html_vars('vs_host')
117 vs_host.validate_value(new, 'vs_host')
119 # If both snmp types have credentials set - snmpv3 takes precedence
120 return_message = []
121 if "ipaddress" in new:
122 return_message.append(_("IP address"))
123 if "snmp_v3_credentials" in new:
124 if "snmp_community" in new:
125 return_message.append(_("SNMPv3 credentials (SNMPv2 community was discarded)"))
126 else:
127 return_message.append(_("SNMPv3 credentials"))
128 new["snmp_community"] = new["snmp_v3_credentials"]
129 elif "snmp_community" in new:
130 return_message.append(_("SNMP credentials"))
131 return_message = _("Updated attributes: ") + ", ".join(return_message)
133 self._host.update_attributes(new)
134 html.del_all_vars()
135 html.set_var("host", self._hostname)
136 html.set_var("folder", watolib.Folder.current().path())
137 return "edit_host", return_message
139 def _validate_diag_html_vars(self):
140 vs_host = self._vs_host()
141 host_vars = vs_host.from_html_vars("vs_host")
142 vs_host.validate_value(host_vars, "vs_host")
144 vs_rules = self._vs_rules()
145 rule_vars = vs_rules.from_html_vars("vs_rules")
146 vs_rules.validate_value(rule_vars, "vs_rules")
148 def page(self):
149 html.open_div(class_="diag_host")
150 html.open_table()
151 html.open_tr()
152 html.open_td()
154 html.begin_form('diag_host', method="POST")
155 html.prevent_password_auto_completion()
157 forms.header(_('Host Properties'))
159 forms.section(legend=False)
161 # The diagnose page shows both snmp variants at the same time
162 # We need to analyse the preconfigured community and set either the
163 # snmp_community or the snmp_v3_credentials
164 vs_dict = {}
165 for key, value in self._host.attributes().items():
166 if key == "snmp_community" and isinstance(value, tuple):
167 vs_dict["snmp_v3_credentials"] = value
168 continue
169 vs_dict[key] = value
171 vs_host = self._vs_host()
172 vs_host.render_input("vs_host", vs_dict)
173 html.help(vs_host.help())
175 forms.end()
177 html.open_div(style="margin-bottom:10px")
178 html.button("_save", _("Save & Exit"))
179 html.close_div()
181 forms.header(_('Options'))
183 value = {}
184 forms.section(legend=False)
185 vs_rules = self._vs_rules()
186 vs_rules.render_input("vs_rules", value)
187 html.help(vs_rules.help())
188 forms.end()
190 html.button("_try", _("Test"))
192 html.hidden_fields()
193 html.end_form()
195 html.close_td()
196 html.open_td(style="padding-left:10px;")
198 self._show_diagnose_output()
200 def _show_diagnose_output(self):
201 if not html.var('_try'):
202 html.message(_('You can diagnose the connection to a specific host using this dialog. '
203 'You can either test whether your current configuration is still working '
204 'or investigate in which ways a host can be reached. Simply configure the '
205 'connection options you like to try on the right side of the screen and '
206 'press the "Test" button. The results will be displayed here.'))
207 return
209 if html.has_user_errors():
210 html.show_user_errors()
211 return
214 # TODO: Insert any vs_host valuespec validation
215 # These tests can be called with invalid valuespec settings...
216 # TODO: Replace hard coded icon paths with dynamic ones to old or new theme
217 for ident, title in ModeDiagHost.diag_host_tests():
218 html.h3(title)
219 html.open_table(class_=["data", "test"])
220 html.open_tr(class_=["data", "odd0"])
222 html.open_td(class_="icons")
223 html.open_div()
224 html.img("images/icon_reload.png", class_="icon", id_="%s_img" % ident)
225 html.open_a(href="")
226 html.img("images/icon_reload.png", class_=["icon", "retry"], id_="%s_retry" % ident, title=_('Retry this test'))
227 html.close_a()
228 html.close_div()
229 html.close_td()
231 html.open_td()
232 html.div('', class_="log", id="%s_log" % ident)
233 html.close_td()
235 html.close_tr()
236 html.close_table()
237 html.javascript('start_host_diag_test(%s, %s, %s)' %
238 (json.dumps(ident), json.dumps(self._hostname),
239 json.dumps(html.transaction_manager.fresh_transid())))
242 def _vs_host(self):
243 return Dictionary(
244 required_keys = ['hostname'],
245 elements = [
246 ('hostname', FixedValue(self._hostname,
247 title = _('Hostname'),
248 allow_empty = False
250 ('ipaddress', HostAddress(
251 title = _("IPv4 Address"),
252 allow_empty = False,
253 allow_ipv6_address = False,
255 ('snmp_community', Password(
256 title = _("SNMPv1/2 community"),
257 allow_empty = False
259 ('snmp_v3_credentials',
260 cmk.gui.plugins.wato.SNMPCredentials(default_value = None, only_v3 = True)
265 def _vs_rules(self):
266 if config.user.may('wato.add_or_modify_executables'):
267 ds_option = [
268 ('datasource_program', TextAscii(
269 title = _("Datasource Program (<a href=\"%s\">Rules</a>)") % \
270 watolib.folder_preserving_link([('mode', 'edit_ruleset'), ('varname', 'datasource_programs')]),
271 help = _("For agent based checks Check_MK allows you to specify an alternative "
272 "program that should be called by Check_MK instead of connecting the agent "
273 "via TCP. That program must output the agent's data on standard output in "
274 "the same format the agent would do. This is for example useful for monitoring "
275 "via SSH.") + monitoring_macro_help(),
278 else:
279 ds_option = []
281 return Dictionary(
282 optional_keys = False,
283 elements = [
284 ('agent_port', Integer(
285 allow_empty = False,
286 minvalue = 1,
287 maxvalue = 65535,
288 default_value = 6556,
289 title = _("Check_MK Agent Port (<a href=\"%s\">Rules</a>)") % \
290 watolib.folder_preserving_link([('mode', 'edit_ruleset'), ('varname', 'agent_ports')]),
291 help = _("This variable allows to specify the TCP port to "
292 "be used to connect to the agent on a per-host-basis.")
294 ('tcp_connect_timeout', Float(
295 allow_empty = False,
296 minvalue = 1.0,
297 default_value = 5.0,
298 unit = _("sec"),
299 display_format = "%.0f", # show values consistent to
300 size = 2, # SNMP-Timeout
301 title = _("TCP Connection Timeout (<a href=\"%s\">Rules</a>)") % \
302 watolib.folder_preserving_link([('mode', 'edit_ruleset'), ('varname', 'tcp_connect_timeouts')]),
303 help = _("This variable allows to specify a timeout for the "
304 "TCP connection to the Check_MK agent on a per-host-basis."
305 "If the agent does not respond within this time, it is considered to be unreachable.")
307 ('snmp_timeout', Integer(
308 allow_empty = False,
309 title = _("SNMP-Timeout (<a href=\"%s\">Rules</a>)") % \
310 watolib.folder_preserving_link([('mode', 'edit_ruleset'), ('varname', 'snmp_timing')]),
311 help = _("After a request is sent to the remote SNMP agent we will wait up to this "
312 "number of seconds until assuming the answer get lost and retrying."),
313 default_value = 1,
314 minvalue = 1,
315 maxvalue = 60,
316 unit = _("sec"),
318 ('snmp_retries', Integer(
319 allow_empty = False,
320 title = _("SNMP-Retries (<a href=\"%s\">Rules</a>)") % \
321 watolib.folder_preserving_link([('mode', 'edit_ruleset'), ('varname', 'snmp_timing')]),
322 default_value = 5,
323 minvalue = 0,
324 maxvalue = 50,
326 ] + ds_option,
330 class ModeAjaxDiagHost(WatoWebApiMode):
331 def page(self):
332 watolib.init_wato_datastructures(with_wato_lock=True)
334 if not config.user.may('wato.diag_host'):
335 raise MKAuthException(_('You are not permitted to perform this action.'))
337 if not html.check_transaction():
338 raise MKAuthException(_("Invalid transaction"))
340 request = self.webapi_request()
342 hostname = request.get("host")
343 if not hostname:
344 raise MKGeneralException(_('The hostname is missing.'))
346 host = watolib.Host.host(hostname)
348 if not host:
349 raise MKGeneralException(_('The given host does not exist.'))
350 if host.is_cluster():
351 raise MKGeneralException(_('This view does not support cluster hosts.'))
353 host.need_permission("read")
355 _test = request.get('_test')
356 if not _test:
357 raise MKGeneralException(_('The test is missing.'))
359 # Execute a specific test
360 if _test not in dict(ModeDiagHost.diag_host_tests()).keys():
361 raise MKGeneralException(_('Invalid test.'))
363 # TODO: Use ModeDiagHost._vs_rules() for processing/validation?
364 args = [""] * 13
365 for idx, what in enumerate([
366 'ipaddress',
367 'snmp_community',
368 'agent_port',
369 'snmp_timeout',
370 'snmp_retries',
371 'tcp_connect_timeout',
373 args[idx] = request.get(what, "")
375 if config.user.may('wato.add_or_modify_executables'):
376 args[6] = request.get("datasource_program", "")
378 if request.get("snmpv3_use"):
379 snmpv3_use = {
380 "0": "noAuthNoPriv",
381 "1": "authNoPriv",
382 "2": "authPriv",
383 }.get(request.get("snmpv3_use"))
384 args[7] = snmpv3_use
385 if snmpv3_use != "noAuthNoPriv":
386 snmpv3_auth_proto = {
387 DropdownChoice.option_id("md5"): "md5",
388 DropdownChoice.option_id("sha"): "sha"
389 }.get(request.get("snmpv3_auth_proto"))
390 args[8] = snmpv3_auth_proto
391 args[9] = request.get("snmpv3_security_name")
392 args[10] = request.get("snmpv3_security_password")
393 if snmpv3_use == "authPriv":
394 snmpv3_privacy_proto = {
395 DropdownChoice.option_id("DES"): "DES",
396 DropdownChoice.option_id("AES"): "AES"
397 }.get(request.get("snmpv3_privacy_proto"))
398 args[11] = snmpv3_privacy_proto
399 args[12] = request.get("snmpv3_privacy_password")
400 else:
401 args[9] = request.get("snmpv3_security_name")
403 result = watolib.check_mk_automation(host.site_id(), "diag-host", [hostname, _test] + args)
404 return {
405 "next_transid": html.transaction_manager.fresh_transid(),
406 "status_code": result[0],
407 "output": result[1],
411 cmk.gui.pages.register_page_handler("wato_ajax_diag_host", lambda: ModeAjaxDiagHost().handle_page())