]>
git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/selftest/module.py
2 from mgr_module
import MgrModule
, CommandResult
10 class Module(MgrModule
):
12 This module is for testing the ceph-mgr python interface from within
13 a running ceph-mgr daemon.
15 It implements a sychronous self-test command for calling the functions
16 in the MgrModule interface one by one, and a background "workload"
17 command for causing the module to perform some thrashing-type
18 activities in its serve() thread.
21 # These workloads are things that can be requested to run inside the
23 WORKLOAD_COMMAND_SPAM
= "command_spam"
24 WORKLOAD_THROW_EXCEPTION
= "throw_exception"
27 WORKLOADS
= (WORKLOAD_COMMAND_SPAM
, WORKLOAD_THROW_EXCEPTION
)
29 # The test code in qa/ relies on these options existing -- they
30 # are of course not really used for anything in the module
34 {'name': 'testnewline'},
36 {'name': 'roption2', 'type': 'str', 'default': 'xyz'},
37 {'name': 'rwoption1'},
38 {'name': 'rwoption2', 'type': 'int'},
39 {'name': 'rwoption3', 'type': 'float'},
40 {'name': 'rwoption4', 'type': 'str'},
41 {'name': 'rwoption5', 'type': 'bool'},
42 {'name': 'rwoption6', 'type': 'bool', 'default': True}
47 "cmd": "mgr self-test run",
48 "desc": "Run mgr python interface tests",
52 "cmd": "mgr self-test background start name=workload,type=CephString",
53 "desc": "Activate a background workload (one of {0})".format(
54 ", ".join(WORKLOADS
)),
58 "cmd": "mgr self-test background stop",
59 "desc": "Stop background workload if any is running",
63 "cmd": "mgr self-test config get name=key,type=CephString",
64 "desc": "Peek at a configuration value",
68 "cmd": "mgr self-test config get_localized name=key,type=CephString",
69 "desc": "Peek at a configuration value (localized variant)",
73 "cmd": "mgr self-test remote",
74 "desc": "Test inter-module calls",
78 "cmd": "mgr self-test module name=module,type=CephString",
79 "desc": "Run another module's self_test() method",
83 "cmd": "mgr self-test health set name=checks,type=CephString",
84 "desc": "Set a health check from a JSON-formatted description.",
88 "cmd": "mgr self-test health clear name=checks,type=CephString,n=N,req=False",
89 "desc": "Clear health checks by name. If no names provided, clear all.",
93 "cmd": "mgr self-test insights_set_now_offset name=hours,type=CephString",
94 "desc": "Set the now time for the insights module.",
98 "cmd": "mgr self-test cluster-log name=channel,type=CephString "
99 "name=priority,type=CephString "
100 "name=message,type=CephString",
101 "desc": "Create an audit log record.",
106 def __init__(self
, *args
, **kwargs
):
107 super(Module
, self
).__init
__(*args
, **kwargs
)
108 self
._event
= threading
.Event()
109 self
._workload
= None
112 def handle_command(self
, inbuf
, command
):
113 if command
['prefix'] == 'mgr self-test run':
115 return 0, '', 'Self-test succeeded'
117 elif command
['prefix'] == 'mgr self-test background start':
118 if command
['workload'] not in self
.WORKLOADS
:
119 return (-errno
.EINVAL
, '',
120 "Workload not found '{0}'".format(command
['workload']))
121 self
._workload
= command
['workload']
123 return 0, '', 'Running `{0}` in background'.format(self
._workload
)
125 elif command
['prefix'] == 'mgr self-test background stop':
127 was_running
= self
._workload
128 self
._workload
= None
130 return 0, '', 'Stopping background workload `{0}`'.format(
133 return 0, '', 'No background workload was running'
134 elif command
['prefix'] == 'mgr self-test config get':
135 return 0, str(self
.get_module_option(command
['key'])), ''
136 elif command
['prefix'] == 'mgr self-test config get_localized':
137 return 0, str(self
.get_localized_module_option(command
['key'])), ''
138 elif command
['prefix'] == 'mgr self-test remote':
139 self
._test
_remote
_calls
()
140 return 0, '', 'Successfully called'
141 elif command
['prefix'] == 'mgr self-test module':
143 r
= self
.remote(command
['module'], "self_test")
144 except RuntimeError as e
:
145 return -1, '', "Test failed: {0}".format(e
)
147 return 0, str(r
), "Self-test OK"
148 elif command
['prefix'] == 'mgr self-test health set':
149 return self
._health
_set
(inbuf
, command
)
150 elif command
['prefix'] == 'mgr self-test health clear':
151 return self
._health
_clear
(inbuf
, command
)
152 elif command
['prefix'] == 'mgr self-test insights_set_now_offset':
153 return self
._insights
_set
_now
_offset
(inbuf
, command
)
154 elif command
['prefix'] == 'mgr self-test cluster-log':
156 'info': self
.CLUSTER_LOG_PRIO_INFO
,
157 'security': self
.CLUSTER_LOG_PRIO_SEC
,
158 'warning': self
.CLUSTER_LOG_PRIO_WARN
,
159 'error': self
.CLUSTER_LOG_PRIO_ERROR
161 self
.cluster_log(command
['channel'],
162 priority_map
[command
['priority']],
164 return 0, '', 'Successfully called'
166 return (-errno
.EINVAL
, '',
167 "Command not found '{0}'".format(command
['prefix']))
169 def _health_set(self
, inbuf
, command
):
171 checks
= json
.loads(command
["checks"])
172 except Exception as e
:
173 return -1, "", "Failed to decode JSON input: {}".format(e
)
176 for check
, info
in six
.iteritems(checks
):
177 self
._health
[check
] = {
178 "severity": str(info
["severity"]),
179 "summary": str(info
["summary"]),
181 "detail": [str(m
) for m
in info
["detail"]]
183 except Exception as e
:
184 return -1, "", "Invalid health check format: {}".format(e
)
186 self
.set_health_checks(self
._health
)
189 def _health_clear(self
, inbuf
, command
):
190 if "checks" in command
:
191 for check
in command
["checks"]:
192 if check
in self
._health
:
193 del self
._health
[check
]
195 self
._health
= dict()
197 self
.set_health_checks(self
._health
)
200 def _insights_set_now_offset(self
, inbuf
, command
):
202 hours
= int(command
["hours"])
203 except Exception as e
:
204 return -1, "", "Timestamp must be numeric: {}".format(e
)
206 self
.remote("insights", "testing_set_now_time_offset", hours
)
209 def _self_test(self
):
210 self
.log
.info("Running self-test procedure...")
212 self
._self
_test
_osdmap
()
213 self
._self
_test
_getters
()
214 self
._self
_test
_config
()
215 self
._self
_test
_store
()
216 self
._self
_test
_misc
()
217 self
._self
_test
_perf
_counters
()
219 def _self_test_getters(self
):
224 # In this function, we will assume that the system is in a steady
225 # state, i.e. if a server/service appears in one call, it will
226 # not have gone by the time we call another function referring to it
230 "osdmap_crush_map_text",
250 assert self
.get(obj
) is not None
252 assert self
.get("__OBJ_DNE__") is None
254 servers
= self
.list_servers()
255 for server
in servers
:
256 self
.get_server(server
['hostname'])
258 osdmap
= self
.get('osd_map')
259 for o
in osdmap
['osds']:
261 self
.get_metadata("osd", str(osd_id
))
263 self
.get_daemon_status("osd", "0")
266 def _self_test_config(self
):
267 # This is not a strong test (can't tell if values really
268 # persisted), it's just for the python interface bit.
270 self
.set_module_option("testkey", "testvalue")
271 assert self
.get_module_option("testkey") == "testvalue"
273 self
.set_localized_module_option("testkey", "foo")
274 assert self
.get_localized_module_option("testkey") == "foo"
276 # Must return the default value defined in MODULE_OPTIONS.
277 value
= self
.get_localized_module_option("rwoption6")
278 assert isinstance(value
, bool)
282 assert self
.get_module_option("roption1") is None
283 assert self
.get_module_option("roption1", "foobar") == "foobar"
284 assert self
.get_module_option("roption2") == "xyz"
285 assert self
.get_module_option("roption2", "foobar") == "xyz"
287 # Option type is not defined => return as string.
288 self
.set_module_option("rwoption1", 8080)
289 value
= self
.get_module_option("rwoption1")
290 assert isinstance(value
, str)
291 assert value
== "8080"
293 # Option type is defined => return as integer.
294 self
.set_module_option("rwoption2", 10)
295 value
= self
.get_module_option("rwoption2")
296 assert isinstance(value
, int)
299 # Option type is defined => return as float.
300 self
.set_module_option("rwoption3", 1.5)
301 value
= self
.get_module_option("rwoption3")
302 assert isinstance(value
, float)
305 # Option type is defined => return as string.
306 self
.set_module_option("rwoption4", "foo")
307 value
= self
.get_module_option("rwoption4")
308 assert isinstance(value
, str)
309 assert value
== "foo"
311 # Option type is defined => return as bool.
312 self
.set_module_option("rwoption5", False)
313 value
= self
.get_module_option("rwoption5")
314 assert isinstance(value
, bool)
315 assert value
is False
317 # Specified module does not exist => return None.
318 assert self
.get_module_option_ex("foo", "bar") is None
320 # Specified key does not exist => return None.
321 assert self
.get_module_option_ex("dashboard", "bar") is None
323 self
.set_module_option_ex("telemetry", "contact", "test@test.com")
324 assert self
.get_module_option_ex("telemetry", "contact") == "test@test.com"
326 # No option default value, so use the specified one.
327 assert self
.get_module_option_ex("dashboard", "password") is None
328 assert self
.get_module_option_ex("dashboard", "password", "foobar") == "foobar"
330 # Option type is not defined => return as string.
331 self
.set_module_option_ex("selftest", "rwoption1", 1234)
332 value
= self
.get_module_option_ex("selftest", "rwoption1")
333 assert isinstance(value
, str)
334 assert value
== "1234"
336 # Option type is defined => return as integer.
337 self
.set_module_option_ex("telemetry", "interval", 60)
338 value
= self
.get_module_option_ex("telemetry", "interval")
339 assert isinstance(value
, int)
342 # Option type is defined => return as bool.
343 self
.set_module_option_ex("telemetry", "leaderboard", True)
344 value
= self
.get_module_option_ex("telemetry", "leaderboard")
345 assert isinstance(value
, bool)
348 def _self_test_store(self
):
349 existing_keys
= set(self
.get_store_prefix("test").keys())
350 self
.set_store("testkey", "testvalue")
351 assert self
.get_store("testkey") == "testvalue"
353 assert sorted(self
.get_store_prefix("test").keys()) == sorted(
354 list({"testkey"} | existing_keys
))
357 def _self_test_perf_counters(self
):
358 self
.get_perf_schema("osd", "0")
359 self
.get_counter("osd", "0", "osd.op")
361 #get_all_perf_coutners
363 def _self_test_misc(self
):
364 self
.set_uri("http://this.is.a.test.com")
365 self
.set_health_checks({})
367 def _self_test_osdmap(self
):
368 osdmap
= self
.get_osdmap()
370 osdmap
.get_crush_version()
373 inc
= osdmap
.new_incremental()
374 osdmap
.apply_incremental(inc
)
378 crush
= osdmap
.get_crush()
380 crush
.get_item_name(-1)
381 crush
.get_item_weight(-1)
383 crush
.get_take_weight_osd_map(-1)
385 #osdmap.get_pools_by_take()
386 #osdmap.calc_pg_upmaps()
387 #osdmap.map_pools_pgs_up()
389 #inc.set_osd_reweights
390 #inc.set_crush_compat_weight_set_weights
392 self
.log
.info("Finished self-test procedure.")
394 def _test_remote_calls(self
):
395 # Test making valid call
396 self
.remote("influx", "handle_command", "", {"prefix": "influx self-test"})
398 # Test calling module that exists but isn't enabled
399 # (arbitrarily pick a non-always-on module to use)
400 disabled_module
= "telegraf"
401 mgr_map
= self
.get("mgr_map")
402 assert disabled_module
not in mgr_map
['modules']
404 # (This works until the Z release in about 2027)
405 latest_release
= sorted(mgr_map
['always_on_modules'].keys())[-1]
406 assert disabled_module
not in mgr_map
['always_on_modules'][latest_release
]
409 self
.remote(disabled_module
, "handle_command", {"prefix": "influx self-test"})
413 raise RuntimeError("ImportError not raised for disabled module")
415 # Test calling module that doesn't exist
417 self
.remote("idontexist", "handle_command", {"prefix": "influx self-test"})
421 raise RuntimeError("ImportError not raised for nonexistent module")
423 # Test calling method that doesn't exist
425 self
.remote("influx", "idontexist", {"prefix": "influx self-test"})
429 raise RuntimeError("KeyError not raised")
431 def remote_from_orchestrator_cli_self_test(self
, what
):
433 if what
== 'OrchestratorError':
434 c
= orchestrator
.TrivialReadCompletion(result
=None)
435 c
.fail(orchestrator
.OrchestratorError('hello, world'))
437 elif what
== "ZeroDivisionError":
438 c
= orchestrator
.TrivialReadCompletion(result
=None)
439 c
.fail(ZeroDivisionError('hello, world'))
441 assert False, repr(what
)
444 self
._workload
= self
.SHUTDOWN
447 def _command_spam(self
):
448 self
.log
.info("Starting command_spam workload...")
449 while not self
._event
.is_set():
450 osdmap
= self
.get_osdmap()
452 count
= len(dump
['osds'])
453 i
= int(random
.random() * count
)
456 result
= CommandResult('')
457 self
.send_command(result
, 'mon', '', json
.dumps({
458 'prefix': 'osd reweight',
463 crush
= osdmap
.get_crush().dump()
464 r
, outb
, outs
= result
.wait()
467 self
.log
.info("Ended command_spam workload...")
471 if self
._workload
== self
.WORKLOAD_COMMAND_SPAM
:
473 elif self
._workload
== self
.SHUTDOWN
:
474 self
.log
.info("Shutting down...")
476 elif self
._workload
== self
.WORKLOAD_THROW_EXCEPTION
:
477 raise RuntimeError("Synthetic exception in serve")
479 self
.log
.info("Waiting for workload request...")