]>
git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/selftest/module.py
2 from mgr_module
import MgrModule
, CommandResult
11 class Module(MgrModule
):
13 This module is for testing the ceph-mgr python interface from within
14 a running ceph-mgr daemon.
16 It implements a sychronous self-test command for calling the functions
17 in the MgrModule interface one by one, and a background "workload"
18 command for causing the module to perform some thrashing-type
19 activities in its serve() thread.
22 # These workloads are things that can be requested to run inside the
24 WORKLOAD_COMMAND_SPAM
= "command_spam"
25 WORKLOAD_THROW_EXCEPTION
= "throw_exception"
28 WORKLOADS
= (WORKLOAD_COMMAND_SPAM
, WORKLOAD_THROW_EXCEPTION
)
30 # The test code in qa/ relies on these options existing -- they
31 # are of course not really used for anything in the module
35 {'name': 'testnewline'},
37 {'name': 'roption2', 'type': 'str', 'default': 'xyz'},
38 {'name': 'rwoption1'},
39 {'name': 'rwoption2', 'type': 'int'},
40 {'name': 'rwoption3', 'type': 'float'},
41 {'name': 'rwoption4', 'type': 'str'},
42 {'name': 'rwoption5', 'type': 'bool'},
43 {'name': 'rwoption6', 'type': 'bool', 'default': True}
48 "cmd": "mgr self-test run",
49 "desc": "Run mgr python interface tests",
53 "cmd": "mgr self-test background start name=workload,type=CephString",
54 "desc": "Activate a background workload (one of {0})".format(
55 ", ".join(WORKLOADS
)),
59 "cmd": "mgr self-test background stop",
60 "desc": "Stop background workload if any is running",
64 "cmd": "mgr self-test config get name=key,type=CephString",
65 "desc": "Peek at a configuration value",
69 "cmd": "mgr self-test config get_localized name=key,type=CephString",
70 "desc": "Peek at a configuration value (localized variant)",
74 "cmd": "mgr self-test remote",
75 "desc": "Test inter-module calls",
79 "cmd": "mgr self-test module name=module,type=CephString",
80 "desc": "Run another module's self_test() method",
84 "cmd": "mgr self-test health set name=checks,type=CephString",
85 "desc": "Set a health check from a JSON-formatted description.",
89 "cmd": "mgr self-test health clear name=checks,type=CephString,n=N,req=False",
90 "desc": "Clear health checks by name. If no names provided, clear all.",
94 "cmd": "mgr self-test insights_set_now_offset name=hours,type=CephString",
95 "desc": "Set the now time for the insights module.",
99 "cmd": "mgr self-test cluster-log name=channel,type=CephString "
100 "name=priority,type=CephString "
101 "name=message,type=CephString",
102 "desc": "Create an audit log record.",
106 "cmd": "mgr self-test python-version",
107 "desc": "Query the version of the embedded Python runtime",
112 def __init__(self
, *args
, **kwargs
):
113 super(Module
, self
).__init
__(*args
, **kwargs
)
114 self
._event
= threading
.Event()
115 self
._workload
= None
118 def handle_command(self
, inbuf
, command
):
119 if command
['prefix'] == 'mgr self-test python-version':
120 major
= sys
.version_info
.major
121 minor
= sys
.version_info
.minor
122 micro
= sys
.version_info
.micro
123 return 0, f
'{major}.{minor}.{micro}', ''
125 elif command
['prefix'] == 'mgr self-test run':
127 return 0, '', 'Self-test succeeded'
129 elif command
['prefix'] == 'mgr self-test background start':
130 if command
['workload'] not in self
.WORKLOADS
:
131 return (-errno
.EINVAL
, '',
132 "Workload not found '{0}'".format(command
['workload']))
133 self
._workload
= command
['workload']
135 return 0, '', 'Running `{0}` in background'.format(self
._workload
)
137 elif command
['prefix'] == 'mgr self-test background stop':
139 was_running
= self
._workload
140 self
._workload
= None
142 return 0, '', 'Stopping background workload `{0}`'.format(
145 return 0, '', 'No background workload was running'
146 elif command
['prefix'] == 'mgr self-test config get':
147 return 0, str(self
.get_module_option(command
['key'])), ''
148 elif command
['prefix'] == 'mgr self-test config get_localized':
149 return 0, str(self
.get_localized_module_option(command
['key'])), ''
150 elif command
['prefix'] == 'mgr self-test remote':
151 self
._test
_remote
_calls
()
152 return 0, '', 'Successfully called'
153 elif command
['prefix'] == 'mgr self-test module':
155 r
= self
.remote(command
['module'], "self_test")
156 except RuntimeError as e
:
157 return -1, '', "Test failed: {0}".format(e
)
159 return 0, str(r
), "Self-test OK"
160 elif command
['prefix'] == 'mgr self-test health set':
161 return self
._health
_set
(inbuf
, command
)
162 elif command
['prefix'] == 'mgr self-test health clear':
163 return self
._health
_clear
(inbuf
, command
)
164 elif command
['prefix'] == 'mgr self-test insights_set_now_offset':
165 return self
._insights
_set
_now
_offset
(inbuf
, command
)
166 elif command
['prefix'] == 'mgr self-test cluster-log':
168 'info': self
.CLUSTER_LOG_PRIO_INFO
,
169 'security': self
.CLUSTER_LOG_PRIO_SEC
,
170 'warning': self
.CLUSTER_LOG_PRIO_WARN
,
171 'error': self
.CLUSTER_LOG_PRIO_ERROR
173 self
.cluster_log(command
['channel'],
174 priority_map
[command
['priority']],
176 return 0, '', 'Successfully called'
178 return (-errno
.EINVAL
, '',
179 "Command not found '{0}'".format(command
['prefix']))
181 def _health_set(self
, inbuf
, command
):
183 checks
= json
.loads(command
["checks"])
184 except Exception as e
:
185 return -1, "", "Failed to decode JSON input: {}".format(e
)
188 for check
, info
in six
.iteritems(checks
):
189 self
._health
[check
] = {
190 "severity": str(info
["severity"]),
191 "summary": str(info
["summary"]),
193 "detail": [str(m
) for m
in info
["detail"]]
195 except Exception as e
:
196 return -1, "", "Invalid health check format: {}".format(e
)
198 self
.set_health_checks(self
._health
)
201 def _health_clear(self
, inbuf
, command
):
202 if "checks" in command
:
203 for check
in command
["checks"]:
204 if check
in self
._health
:
205 del self
._health
[check
]
207 self
._health
= dict()
209 self
.set_health_checks(self
._health
)
212 def _insights_set_now_offset(self
, inbuf
, command
):
214 hours
= int(command
["hours"])
215 except Exception as e
:
216 return -1, "", "Timestamp must be numeric: {}".format(e
)
218 self
.remote("insights", "testing_set_now_time_offset", hours
)
221 def _self_test(self
):
222 self
.log
.info("Running self-test procedure...")
224 self
._self
_test
_osdmap
()
225 self
._self
_test
_getters
()
226 self
._self
_test
_config
()
227 self
._self
_test
_store
()
228 self
._self
_test
_misc
()
229 self
._self
_test
_perf
_counters
()
231 def _self_test_getters(self
):
236 # In this function, we will assume that the system is in a steady
237 # state, i.e. if a server/service appears in one call, it will
238 # not have gone by the time we call another function referring to it
242 "osdmap_crush_map_text",
262 assert self
.get(obj
) is not None
264 assert self
.get("__OBJ_DNE__") is None
266 servers
= self
.list_servers()
267 for server
in servers
:
268 self
.get_server(server
['hostname'])
270 osdmap
= self
.get('osd_map')
271 for o
in osdmap
['osds']:
273 self
.get_metadata("osd", str(osd_id
))
275 self
.get_daemon_status("osd", "0")
278 def _self_test_config(self
):
279 # This is not a strong test (can't tell if values really
280 # persisted), it's just for the python interface bit.
282 self
.set_module_option("testkey", "testvalue")
283 assert self
.get_module_option("testkey") == "testvalue"
285 self
.set_localized_module_option("testkey", "foo")
286 assert self
.get_localized_module_option("testkey") == "foo"
288 # Must return the default value defined in MODULE_OPTIONS.
289 value
= self
.get_localized_module_option("rwoption6")
290 assert isinstance(value
, bool)
294 assert self
.get_module_option("roption1") is None
295 assert self
.get_module_option("roption1", "foobar") == "foobar"
296 assert self
.get_module_option("roption2") == "xyz"
297 assert self
.get_module_option("roption2", "foobar") == "xyz"
299 # Option type is not defined => return as string.
300 self
.set_module_option("rwoption1", 8080)
301 value
= self
.get_module_option("rwoption1")
302 assert isinstance(value
, str)
303 assert value
== "8080"
305 # Option type is defined => return as integer.
306 self
.set_module_option("rwoption2", 10)
307 value
= self
.get_module_option("rwoption2")
308 assert isinstance(value
, int)
311 # Option type is defined => return as float.
312 self
.set_module_option("rwoption3", 1.5)
313 value
= self
.get_module_option("rwoption3")
314 assert isinstance(value
, float)
317 # Option type is defined => return as string.
318 self
.set_module_option("rwoption4", "foo")
319 value
= self
.get_module_option("rwoption4")
320 assert isinstance(value
, str)
321 assert value
== "foo"
323 # Option type is defined => return as bool.
324 self
.set_module_option("rwoption5", False)
325 value
= self
.get_module_option("rwoption5")
326 assert isinstance(value
, bool)
327 assert value
is False
329 # Specified module does not exist => return None.
330 assert self
.get_module_option_ex("foo", "bar") is None
332 # Specified key does not exist => return None.
333 assert self
.get_module_option_ex("dashboard", "bar") is None
335 self
.set_module_option_ex("telemetry", "contact", "test@test.com")
336 assert self
.get_module_option_ex("telemetry", "contact") == "test@test.com"
338 # No option default value, so use the specified one.
339 assert self
.get_module_option_ex("dashboard", "password") is None
340 assert self
.get_module_option_ex("dashboard", "password", "foobar") == "foobar"
342 # Option type is not defined => return as string.
343 self
.set_module_option_ex("selftest", "rwoption1", 1234)
344 value
= self
.get_module_option_ex("selftest", "rwoption1")
345 assert isinstance(value
, str)
346 assert value
== "1234"
348 # Option type is defined => return as integer.
349 self
.set_module_option_ex("telemetry", "interval", 60)
350 value
= self
.get_module_option_ex("telemetry", "interval")
351 assert isinstance(value
, int)
354 # Option type is defined => return as bool.
355 self
.set_module_option_ex("telemetry", "leaderboard", True)
356 value
= self
.get_module_option_ex("telemetry", "leaderboard")
357 assert isinstance(value
, bool)
360 def _self_test_store(self
):
361 existing_keys
= set(self
.get_store_prefix("test").keys())
362 self
.set_store("testkey", "testvalue")
363 assert self
.get_store("testkey") == "testvalue"
365 assert sorted(self
.get_store_prefix("test").keys()) == sorted(
366 list({"testkey"} | existing_keys
))
369 def _self_test_perf_counters(self
):
370 self
.get_perf_schema("osd", "0")
371 self
.get_counter("osd", "0", "osd.op")
373 #get_all_perf_coutners
375 def _self_test_misc(self
):
376 self
.set_uri("http://this.is.a.test.com")
377 self
.set_health_checks({})
379 def _self_test_osdmap(self
):
380 osdmap
= self
.get_osdmap()
382 osdmap
.get_crush_version()
385 inc
= osdmap
.new_incremental()
386 osdmap
.apply_incremental(inc
)
390 crush
= osdmap
.get_crush()
392 crush
.get_item_name(-1)
393 crush
.get_item_weight(-1)
395 crush
.get_take_weight_osd_map(-1)
397 #osdmap.get_pools_by_take()
398 #osdmap.calc_pg_upmaps()
399 #osdmap.map_pools_pgs_up()
401 #inc.set_osd_reweights
402 #inc.set_crush_compat_weight_set_weights
404 self
.log
.info("Finished self-test procedure.")
406 def _test_remote_calls(self
):
407 # Test making valid call
408 self
.remote("influx", "handle_command", "", {"prefix": "influx self-test"})
410 # Test calling module that exists but isn't enabled
411 # (arbitrarily pick a non-always-on module to use)
412 disabled_module
= "telegraf"
413 mgr_map
= self
.get("mgr_map")
414 assert disabled_module
not in mgr_map
['modules']
416 # (This works until the Z release in about 2027)
417 latest_release
= sorted(mgr_map
['always_on_modules'].keys())[-1]
418 assert disabled_module
not in mgr_map
['always_on_modules'][latest_release
]
421 self
.remote(disabled_module
, "handle_command", {"prefix": "influx self-test"})
425 raise RuntimeError("ImportError not raised for disabled module")
427 # Test calling module that doesn't exist
429 self
.remote("idontexist", "handle_command", {"prefix": "influx self-test"})
433 raise RuntimeError("ImportError not raised for nonexistent module")
435 # Test calling method that doesn't exist
437 self
.remote("influx", "idontexist", {"prefix": "influx self-test"})
441 raise RuntimeError("KeyError not raised")
443 def remote_from_orchestrator_cli_self_test(self
, what
):
445 if what
== 'OrchestratorError':
446 c
= orchestrator
.TrivialReadCompletion(result
=None)
447 c
.fail(orchestrator
.OrchestratorError('hello, world'))
449 elif what
== "ZeroDivisionError":
450 c
= orchestrator
.TrivialReadCompletion(result
=None)
451 c
.fail(ZeroDivisionError('hello, world'))
453 assert False, repr(what
)
456 self
._workload
= self
.SHUTDOWN
459 def _command_spam(self
):
460 self
.log
.info("Starting command_spam workload...")
461 while not self
._event
.is_set():
462 osdmap
= self
.get_osdmap()
464 count
= len(dump
['osds'])
465 i
= int(random
.random() * count
)
468 result
= CommandResult('')
469 self
.send_command(result
, 'mon', '', json
.dumps({
470 'prefix': 'osd reweight',
475 crush
= osdmap
.get_crush().dump()
476 r
, outb
, outs
= result
.wait()
479 self
.log
.info("Ended command_spam workload...")
483 if self
._workload
== self
.WORKLOAD_COMMAND_SPAM
:
485 elif self
._workload
== self
.SHUTDOWN
:
486 self
.log
.info("Shutting down...")
488 elif self
._workload
== self
.WORKLOAD_THROW_EXCEPTION
:
489 raise RuntimeError("Synthetic exception in serve")
491 self
.log
.info("Waiting for workload request...")