]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/mgr_module.py
import new upstream nautilus stable release 14.2.8
[ceph.git] / ceph / src / pybind / mgr / mgr_module.py
CommitLineData
3efd9988 1import ceph_module # noqa
3efd9988 2
494da23a
TL
3try:
4 from typing import Set, Tuple, Iterator, Any
5except ImportError:
6 # just for type checking
7 pass
7c673cae 8import logging
11fdf7f2 9import json
1adf2230 10import six
7c673cae 11import threading
11fdf7f2
TL
12from collections import defaultdict, namedtuple
13import rados
81eedcae 14import re
11fdf7f2
TL
15import time
16
17PG_STATES = [
18 "active",
19 "clean",
20 "down",
21 "recovery_unfound",
22 "backfill_unfound",
23 "scrubbing",
24 "degraded",
25 "inconsistent",
26 "peering",
27 "repair",
28 "recovering",
29 "forced_recovery",
30 "backfill_wait",
31 "incomplete",
32 "stale",
33 "remapped",
34 "deep",
35 "backfilling",
36 "forced_backfill",
37 "backfill_toofull",
38 "recovery_wait",
39 "recovery_toofull",
40 "undersized",
41 "activating",
42 "peered",
43 "snaptrim",
44 "snaptrim_wait",
45 "snaptrim_error",
46 "creating",
47 "unknown"]
3efd9988
FG
48
49
50class CPlusPlusHandler(logging.Handler):
51 def __init__(self, module_inst):
52 super(CPlusPlusHandler, self).__init__()
53 self._module = module_inst
54
55 def emit(self, record):
56 if record.levelno <= logging.DEBUG:
57 ceph_level = 20
58 elif record.levelno <= logging.INFO:
59 ceph_level = 4
60 elif record.levelno <= logging.WARNING:
61 ceph_level = 1
62 else:
63 ceph_level = 0
64
65 self._module._ceph_log(ceph_level, self.format(record))
66
67
11fdf7f2
TL
68def configure_logger(module_inst, module_name):
69 """
70 Create and configure the logger with the specified module.
3efd9988 71
11fdf7f2
TL
72 A handler will be added to the root logger which will redirect
73 the messages from all loggers (incl. 3rd party libraries) to the
74 Ceph log.
3efd9988 75
11fdf7f2
TL
76 :param module_inst: The module instance.
77 :type module_inst: instance
78 :param module_name: The module name.
79 :type module_name: str
80 :return: Return the logger with the specified name.
81 """
82 logger = logging.getLogger(module_name)
83 # Don't filter any logs at the python level, leave it to C++.
84 # FIXME: We should learn the log level from C++ land, and then
85 # avoid calling the C++ level log when we know a message
86 # is of an insufficient level to be ultimately output.
87 logger.setLevel(logging.DEBUG) # Don't use NOTSET
88
89 root_logger = logging.getLogger()
90 # Add handler to the root logger, thus this module and all
91 # 3rd party libraries will log their messages to the Ceph log.
92 root_logger.addHandler(CPlusPlusHandler(module_inst))
93 # Set the log level to ``ERROR`` to ensure that we only get
94 # those message from 3rd party libraries (only effective if
95 # they use the default log level ``NOTSET``).
96 # Check https://docs.python.org/3/library/logging.html#logging.Logger.setLevel
97 # for more information about how the effective log level is
98 # determined.
99 root_logger.setLevel(logging.ERROR)
3efd9988
FG
100
101 return logger
7c673cae
FG
102
103
11fdf7f2
TL
104def unconfigure_logger(module_name=None):
105 """
106 :param module_name: The module name. Defaults to ``None``.
107 :type module_name: str or None
108 """
109 logger = logging.getLogger(module_name)
110 rm_handlers = [
111 h for h in logger.handlers if isinstance(h, CPlusPlusHandler)]
3efd9988
FG
112 for h in rm_handlers:
113 logger.removeHandler(h)
114
11fdf7f2 115
7c673cae
FG
116class CommandResult(object):
117 """
118 Use with MgrModule.send_command
119 """
11fdf7f2
TL
120
121 def __init__(self, tag=None):
7c673cae
FG
122 self.ev = threading.Event()
123 self.outs = ""
124 self.outb = ""
125 self.r = 0
126
127 # This is just a convenience for notifications from
128 # C++ land, to avoid passing addresses around in messages.
11fdf7f2 129 self.tag = tag if tag else ""
7c673cae
FG
130
131 def complete(self, r, outb, outs):
132 self.r = r
133 self.outb = outb
134 self.outs = outs
135 self.ev.set()
136
137 def wait(self):
138 self.ev.wait()
139 return self.r, self.outb, self.outs
140
141
11fdf7f2
TL
142class HandleCommandResult(namedtuple('HandleCommandResult', ['retval', 'stdout', 'stderr'])):
143 def __new__(cls, retval=0, stdout="", stderr=""):
144 """
145 Tuple containing the result of `handle_command()`
146
147 Only write to stderr if there is an error, or in extraordinary circumstances
148
149 Avoid having `ceph foo bar` commands say "did foo bar" on success unless there
150 is critical information to include there.
151
152 Everything programmatically consumable should be put on stdout
153
154 :param retval: return code. E.g. 0 or -errno.EINVAL
155 :type retval: int
156 :param stdout: data of this result.
157 :type stdout: str
158 :param stderr: Typically used for error messages.
159 :type stderr: str
160 """
161 return super(HandleCommandResult, cls).__new__(cls, retval, stdout, stderr)
162
163
3efd9988
FG
164class OSDMap(ceph_module.BasePyOSDMap):
165 def get_epoch(self):
166 return self._get_epoch()
167
168 def get_crush_version(self):
169 return self._get_crush_version()
170
171 def dump(self):
172 return self._dump()
173
11fdf7f2
TL
174 def get_pools(self):
175 # FIXME: efficient implementation
176 d = self._dump()
177 return dict([(p['pool'], p) for p in d['pools']])
178
179 def get_pools_by_name(self):
180 # FIXME: efficient implementation
181 d = self._dump()
182 return dict([(p['pool_name'], p) for p in d['pools']])
183
3efd9988
FG
184 def new_incremental(self):
185 return self._new_incremental()
186
187 def apply_incremental(self, inc):
188 return self._apply_incremental(inc)
189
190 def get_crush(self):
191 return self._get_crush()
192
193 def get_pools_by_take(self, take):
194 return self._get_pools_by_take(take).get('pools', [])
195
196 def calc_pg_upmaps(self, inc,
11fdf7f2
TL
197 max_deviation=.01, max_iterations=10, pools=None):
198 if pools is None:
199 pools = []
3efd9988
FG
200 return self._calc_pg_upmaps(
201 inc,
202 max_deviation, max_iterations, pools)
203
204 def map_pool_pgs_up(self, poolid):
205 return self._map_pool_pgs_up(poolid)
206
11fdf7f2
TL
207 def pg_to_up_acting_osds(self, pool_id, ps):
208 return self._pg_to_up_acting_osds(pool_id, ps)
209
210 def pool_raw_used_rate(self, pool_id):
211 return self._pool_raw_used_rate(pool_id)
212
213 def get_ec_profile(self, name):
214 # FIXME: efficient implementation
215 d = self._dump()
216 return d['erasure_code_profiles'].get(name, None)
217
218
3efd9988
FG
219class OSDMapIncremental(ceph_module.BasePyOSDMapIncremental):
220 def get_epoch(self):
221 return self._get_epoch()
222
223 def dump(self):
224 return self._dump()
225
226 def set_osd_reweights(self, weightmap):
227 """
228 weightmap is a dict, int to float. e.g. { 0: .9, 1: 1.0, 3: .997 }
229 """
230 return self._set_osd_reweights(weightmap)
231
232 def set_crush_compat_weight_set_weights(self, weightmap):
233 """
234 weightmap is a dict, int to float. devices only. e.g.,
235 { 0: 3.4, 1: 3.3, 2: 3.334 }
236 """
237 return self._set_crush_compat_weight_set_weights(weightmap)
238
11fdf7f2 239
3efd9988 240class CRUSHMap(ceph_module.BasePyCRUSH):
b32b8144 241 ITEM_NONE = 0x7fffffff
11fdf7f2 242 DEFAULT_CHOOSE_ARGS = '-1'
b32b8144 243
3efd9988
FG
244 def dump(self):
245 return self._dump()
246
247 def get_item_weight(self, item):
248 return self._get_item_weight(item)
249
250 def get_item_name(self, item):
251 return self._get_item_name(item)
252
253 def find_takes(self):
254 return self._find_takes().get('takes', [])
255
256 def get_take_weight_osd_map(self, root):
257 uglymap = self._get_take_weight_osd_map(root)
11fdf7f2
TL
258 return {int(k): v for k, v in six.iteritems(uglymap.get('weights', {}))}
259
260 @staticmethod
261 def have_default_choose_args(dump):
262 return CRUSHMap.DEFAULT_CHOOSE_ARGS in dump.get('choose_args', {})
263
264 @staticmethod
265 def get_default_choose_args(dump):
266 return dump.get('choose_args').get(CRUSHMap.DEFAULT_CHOOSE_ARGS, [])
267
268 def get_rule(self, rule_name):
269 # TODO efficient implementation
270 for rule in self.dump()['rules']:
271 if rule_name == rule['rule_name']:
272 return rule
273
274 return None
275
276 def get_rule_by_id(self, rule_id):
277 for rule in self.dump()['rules']:
278 if rule['rule_id'] == rule_id:
279 return rule
280
281 return None
282
283 def get_rule_root(self, rule_name):
284 rule = self.get_rule(rule_name)
285 if rule is None:
286 return None
287
288 try:
289 first_take = [s for s in rule['steps'] if s['op'] == 'take'][0]
290 except IndexError:
291 self.log.warn("CRUSH rule '{0}' has no 'take' step".format(
292 rule_name))
293 return None
294 else:
295 return first_take['item']
296
297 def get_osds_under(self, root_id):
298 # TODO don't abuse dump like this
299 d = self.dump()
300 buckets = dict([(b['id'], b) for b in d['buckets']])
301
302 osd_list = []
303
304 def accumulate(b):
305 for item in b['items']:
306 if item['id'] >= 0:
307 osd_list.append(item['id'])
308 else:
309 try:
310 accumulate(buckets[item['id']])
311 except KeyError:
312 pass
313
314 accumulate(buckets[root_id])
315
316 return osd_list
317
318 def device_class_counts(self):
319 result = defaultdict(int)
320 # TODO don't abuse dump like this
321 d = self.dump()
322 for device in d['devices']:
323 cls = device.get('class', None)
324 result[cls] += 1
325
326 return dict(result)
327
328
329class CLICommand(object):
330 COMMANDS = {}
331
332 def __init__(self, prefix, args="", desc="", perm="rw"):
333 self.prefix = prefix
334 self.args = args
335 self.args_dict = {}
336 self.desc = desc
337 self.perm = perm
338 self.func = None
339 self._parse_args()
340
341 def _parse_args(self):
342 if not self.args:
343 return
344 args = self.args.split(" ")
345 for arg in args:
346 arg_desc = arg.strip().split(",")
347 arg_d = {}
348 for kv in arg_desc:
349 k, v = kv.split("=")
350 if k != "name":
351 arg_d[k] = v
352 else:
353 self.args_dict[v] = arg_d
354
355 def __call__(self, func):
356 self.func = func
357 self.COMMANDS[self.prefix] = self
358 return self.func
359
360 def call(self, mgr, cmd_dict, inbuf):
361 kwargs = {}
362 for a, d in self.args_dict.items():
363 if 'req' in d and d['req'] == "false" and a not in cmd_dict:
364 continue
365 kwargs[a.replace("-", "_")] = cmd_dict[a]
366 if inbuf:
367 kwargs['inbuf'] = inbuf
368 return self.func(mgr, **kwargs)
369
370 @classmethod
371 def dump_cmd_list(cls):
372 return [{
373 'cmd': '{} {}'.format(cmd.prefix, cmd.args),
374 'desc': cmd.desc,
375 'perm': cmd.perm
376 } for _, cmd in cls.COMMANDS.items()]
377
378
379def CLIReadCommand(prefix, args="", desc=""):
380 return CLICommand(prefix, args, desc, "r")
381
382
383def CLIWriteCommand(prefix, args="", desc=""):
384 return CLICommand(prefix, args, desc, "w")
385
386
387def _get_localized_key(prefix, key):
388 return '{}/{}'.format(prefix, key)
389
390
391class Option(dict):
392 """
393 Helper class to declare options for MODULE_OPTIONS list.
394
395 Caveat: it uses argument names matching Python keywords (type, min, max),
396 so any further processing should happen in a separate method.
397
398 TODO: type validation.
399 """
400
401 def __init__(
402 self, name,
403 default=None,
404 type='str',
405 desc=None, longdesc=None,
406 min=None, max=None,
407 enum_allowed=None,
408 see_also=None,
409 tags=None,
410 runtime=False,
411 ):
412 super(Option, self).__init__(
413 (k, v) for k, v in vars().items()
414 if k != 'self' and v is not None)
415
92f5a8d4
TL
416class Command(dict):
417 """
418 Helper class to declare options for COMMANDS list.
419
420 It also allows to specify prefix and args separately, as well as storing a
421 handler callable.
422
423 Usage:
424 >>> Command(prefix="example",
425 ... args="name=arg,type=CephInt",
426 ... perm='w',
427 ... desc="Blah")
428 {'poll': False, 'cmd': 'example name=arg,type=CephInt', 'perm': 'w', 'desc': 'Blah'}
429 """
430
431 def __init__(
432 self,
433 prefix,
434 args=None,
435 perm="rw",
436 desc=None,
437 poll=False,
438 handler=None
439 ):
440 super(Command, self).__init__(
441 cmd=prefix + (' ' + args if args else ''),
442 perm=perm,
443 desc=desc,
444 poll=poll)
445 self.prefix = prefix
446 self.args = args
447 self.handler = handler
448
449 def register(self, instance=False):
450 """
451 Register a CLICommand handler. It allows an instance to register bound
452 methods. In that case, the mgr instance is not passed, and it's expected
453 to be available in the class instance.
454 It also uses HandleCommandResult helper to return a wrapped a tuple of 3
455 items.
456 """
457 return CLICommand(
458 prefix=self.prefix,
459 args=self.args,
460 desc=self['desc'],
461 perm=self['perm']
462 )(
463 func=lambda mgr, *args, **kwargs: HandleCommandResult(*self.handler(
464 *((instance or mgr,) + args), **kwargs))
465 )
466
3efd9988
FG
467
468class MgrStandbyModule(ceph_module.BaseMgrStandbyModule):
469 """
470 Standby modules only implement a serve and shutdown method, they
471 are not permitted to implement commands and they do not receive
472 any notifications.
473
b32b8144 474 They only have access to the mgrmap (for accessing service URI info
3efd9988
FG
475 from their active peer), and to configuration settings (read only).
476 """
477
11fdf7f2
TL
478 MODULE_OPTIONS = []
479 MODULE_OPTION_DEFAULTS = {}
480
3efd9988
FG
481 def __init__(self, module_name, capsule):
482 super(MgrStandbyModule, self).__init__(capsule)
483 self.module_name = module_name
484 self._logger = configure_logger(self, module_name)
11fdf7f2
TL
485 # see also MgrModule.__init__()
486 for o in self.MODULE_OPTIONS:
487 if 'default' in o:
488 if 'type' in o:
489 self.MODULE_OPTION_DEFAULTS[o['name']] = o['default']
490 else:
491 self.MODULE_OPTION_DEFAULTS[o['name']] = str(o['default'])
3efd9988
FG
492
493 def __del__(self):
11fdf7f2 494 unconfigure_logger()
3efd9988
FG
495
496 @property
497 def log(self):
498 return self._logger
499
500 def serve(self):
501 """
502 The serve method is mandatory for standby modules.
503 :return:
504 """
505 raise NotImplementedError()
506
507 def get_mgr_id(self):
508 return self._ceph_get_mgr_id()
509
11fdf7f2 510 def get_module_option(self, key, default=None):
b32b8144
FG
511 """
512 Retrieve the value of a persistent configuration setting
513
514 :param str key:
515 :param default: the default value of the config if it is not found
516 :return: str
517 """
11fdf7f2 518 r = self._ceph_get_module_option(key)
b32b8144 519 if r is None:
11fdf7f2 520 return self.MODULE_OPTION_DEFAULTS.get(key, default)
b32b8144
FG
521 else:
522 return r
523
11fdf7f2
TL
524 def get_ceph_option(self, key):
525 return self._ceph_get_option(key)
526
527 def get_store(self, key):
528 """
529 Retrieve the value of a persistent KV store entry
530
531 :param key: String
532 :return: Byte string or None
533 """
534 return self._ceph_get_store(key)
3efd9988
FG
535
536 def get_active_uri(self):
537 return self._ceph_get_active_uri()
538
11fdf7f2
TL
539 def get_localized_module_option(self, key, default=None):
540 r = self._ceph_get_module_option(key, self.get_mgr_id())
3efd9988 541 if r is None:
11fdf7f2
TL
542 return self.MODULE_OPTION_DEFAULTS.get(key, default)
543 else:
544 return r
3efd9988 545
3efd9988
FG
546
547class MgrModule(ceph_module.BaseMgrModule):
7c673cae 548 COMMANDS = []
11fdf7f2
TL
549 MODULE_OPTIONS = []
550 MODULE_OPTION_DEFAULTS = {}
7c673cae 551
3efd9988
FG
552 # Priority definitions for perf counters
553 PRIO_CRITICAL = 10
554 PRIO_INTERESTING = 8
555 PRIO_USEFUL = 5
556 PRIO_UNINTERESTING = 2
557 PRIO_DEBUGONLY = 0
558
559 # counter value types
560 PERFCOUNTER_TIME = 1
561 PERFCOUNTER_U64 = 2
562
563 # counter types
564 PERFCOUNTER_LONGRUNAVG = 4
565 PERFCOUNTER_COUNTER = 8
566 PERFCOUNTER_HISTOGRAM = 0x10
28e407b8 567 PERFCOUNTER_TYPE_MASK = ~3
7c673cae 568
1adf2230
AA
569 # units supported
570 BYTES = 0
571 NONE = 1
11fdf7f2
TL
572
573 # Cluster log priorities
574 CLUSTER_LOG_PRIO_DEBUG = 0
575 CLUSTER_LOG_PRIO_INFO = 1
576 CLUSTER_LOG_PRIO_SEC = 2
577 CLUSTER_LOG_PRIO_WARN = 3
578 CLUSTER_LOG_PRIO_ERROR = 4
579
3efd9988
FG
580 def __init__(self, module_name, py_modules_ptr, this_ptr):
581 self.module_name = module_name
7c673cae 582
3efd9988
FG
583 # If we're taking over from a standby module, let's make sure
584 # its logger was unconfigured before we hook ours up
11fdf7f2 585 unconfigure_logger()
3efd9988 586 self._logger = configure_logger(self, module_name)
7c673cae 587
3efd9988 588 super(MgrModule, self).__init__(py_modules_ptr, this_ptr)
7c673cae 589
3efd9988 590 self._version = self._ceph_get_version()
7c673cae 591
3efd9988 592 self._perf_schema_cache = None
7c673cae 593
11fdf7f2
TL
594 # Keep a librados instance for those that need it.
595 self._rados = None
596
597 for o in self.MODULE_OPTIONS:
598 if 'default' in o:
599 if 'type' in o:
600 # we'll assume the declared type matches the
601 # supplied default value's type.
602 self.MODULE_OPTION_DEFAULTS[o['name']] = o['default']
603 else:
604 # module not declaring it's type, so normalize the
605 # default value to be a string for consistent behavior
606 # with default and user-supplied option values.
607 self.MODULE_OPTION_DEFAULTS[o['name']] = str(o['default'])
608
3efd9988 609 def __del__(self):
11fdf7f2 610 unconfigure_logger()
3efd9988 611
11fdf7f2
TL
612 @classmethod
613 def _register_commands(cls):
614 cls.COMMANDS.extend(CLICommand.dump_cmd_list())
7c673cae
FG
615
616 @property
617 def log(self):
618 return self._logger
619
11fdf7f2
TL
620 def cluster_log(self, channel, priority, message):
621 """
622 :param channel: The log channel. This can be 'cluster', 'audit', ...
623 :type channel: str
624 :param priority: The log message priority. This can be
625 CLUSTER_LOG_PRIO_DEBUG, CLUSTER_LOG_PRIO_INFO,
626 CLUSTER_LOG_PRIO_SEC, CLUSTER_LOG_PRIO_WARN or
627 CLUSTER_LOG_PRIO_ERROR.
628 :type priority: int
629 :param message: The message to log.
630 :type message: str
631 """
632 self._ceph_cluster_log(channel, priority, message)
633
7c673cae
FG
634 @property
635 def version(self):
636 return self._version
637
92f5a8d4
TL
638 @property
639 def release_name(self):
640 """
641 Get the release name of the Ceph version, e.g. 'nautilus' or 'octopus'.
642 :return: Returns the release name of the Ceph version in lower case.
643 :rtype: str
644 """
645 return self._ceph_get_release_name()
646
3efd9988
FG
647 def get_context(self):
648 """
649 :return: a Python capsule containing a C++ CephContext pointer
650 """
651 return self._ceph_get_context()
652
7c673cae
FG
653 def notify(self, notify_type, notify_id):
654 """
655 Called by the ceph-mgr service to notify the Python plugin
656 that new state is available.
11fdf7f2
TL
657
658 :param notify_type: string indicating what kind of notification,
659 such as osd_map, mon_map, fs_map, mon_status,
660 health, pg_summary, command, service_map
661 :param notify_id: string (may be empty) that optionally specifies
662 which entity is being notified about. With
663 "command" notifications this is set to the tag
664 ``from send_command``.
665 """
666 pass
667
668 def config_notify(self):
669 """
670 Called by the ceph-mgr service to notify the Python plugin
671 that the configuration may have changed. Modules will want to
672 refresh any configuration values stored in config variables.
7c673cae
FG
673 """
674 pass
675
676 def serve(self):
677 """
678 Called by the ceph-mgr service to start any server that
679 is provided by this Python plugin. The implementation
680 of this function should block until ``shutdown`` is called.
681
682 You *must* implement ``shutdown`` if you implement ``serve``
683 """
684 pass
685
686 def shutdown(self):
687 """
688 Called by the ceph-mgr service to request that this
689 module drop out of its serve() function. You do not
690 need to implement this if you do not implement serve()
691
692 :return: None
693 """
11fdf7f2
TL
694 if self._rados:
695 self._rados.shutdown()
7c673cae
FG
696
697 def get(self, data_name):
698 """
11fdf7f2
TL
699 Called by the plugin to fetch named cluster-wide objects from ceph-mgr.
700
701 :param str data_name: Valid things to fetch are osd_crush_map_text,
702 osd_map, osd_map_tree, osd_map_crush, config, mon_map, fs_map,
703 osd_metadata, pg_summary, io_rate, pg_dump, df, osd_stats,
704 health, mon_status, devices, device <devid>.
705
706 Note:
707 All these structures have their own JSON representations: experiment
708 or look at the C++ ``dump()`` methods to learn about them.
7c673cae 709 """
3efd9988 710 return self._ceph_get(data_name)
7c673cae 711
94b18763 712 def _stattype_to_str(self, stattype):
11fdf7f2 713
94b18763
FG
714 typeonly = stattype & self.PERFCOUNTER_TYPE_MASK
715 if typeonly == 0:
716 return 'gauge'
717 if typeonly == self.PERFCOUNTER_LONGRUNAVG:
718 # this lie matches the DaemonState decoding: only val, no counts
719 return 'counter'
720 if typeonly == self.PERFCOUNTER_COUNTER:
721 return 'counter'
722 if typeonly == self.PERFCOUNTER_HISTOGRAM:
723 return 'histogram'
11fdf7f2 724
94b18763
FG
725 return ''
726
81eedcae
TL
727 def _perfpath_to_path_labels(self, daemon, path):
728 label_names = ("ceph_daemon",)
729 labels = (daemon,)
730
731 if daemon.startswith('rbd-mirror.'):
732 match = re.match(
733 r'^rbd_mirror_([^/]+)/(?:(?:([^/]+)/)?)(.*)\.(replay(?:_bytes|_latency)?)$',
734 path
735 )
736 if match:
737 path = 'rbd_mirror_' + match.group(4)
738 pool = match.group(1)
739 namespace = match.group(2) or ''
740 image = match.group(3)
741 label_names += ('pool', 'namespace', 'image')
742 labels += (pool, namespace, image)
743
744 return path, label_names, labels,
745
28e407b8
AA
746 def _perfvalue_to_value(self, stattype, value):
747 if stattype & self.PERFCOUNTER_TIME:
748 # Convert from ns to seconds
749 return value / 1000000000.0
750 else:
751 return value
752
1adf2230
AA
753 def _unit_to_str(self, unit):
754 if unit == self.NONE:
755 return "/s"
756 elif unit == self.BYTES:
11fdf7f2
TL
757 return "B/s"
758
759 @staticmethod
760 def to_pretty_iec(n):
761 for bits, suffix in [(60, 'Ei'), (50, 'Pi'), (40, 'Ti'), (30, 'Gi'),
762 (20, 'Mi'), (10, 'Ki')]:
763 if n > 10 << bits:
764 return str(n >> bits) + ' ' + suffix
765 return str(n) + ' '
766
767 @staticmethod
768 def get_pretty_row(elems, width):
769 """
770 Takes an array of elements and returns a string with those elements
771 formatted as a table row. Useful for polling modules.
772
773 :param elems: the elements to be printed
774 :param width: the width of the terminal
775 """
776 n = len(elems)
777 column_width = int(width / n)
778
779 ret = '|'
780 for elem in elems:
781 ret += '{0:>{w}} |'.format(elem, w=column_width - 2)
782
783 return ret
784
785 def get_pretty_header(self, elems, width):
786 """
787 Like ``get_pretty_row`` but adds dashes, to be used as a table title.
788
789 :param elems: the elements to be printed
790 :param width: the width of the terminal
791 """
792 n = len(elems)
793 column_width = int(width / n)
794
795 # dash line
796 ret = '+'
797 for i in range(0, n):
798 ret += '-' * (column_width - 1) + '+'
799 ret += '\n'
800
801 # title
802 ret += self.get_pretty_row(elems, width)
803 ret += '\n'
804
805 # dash line
806 ret += '+'
807 for i in range(0, n):
808 ret += '-' * (column_width - 1) + '+'
809 ret += '\n'
810
811 return ret
812
7c673cae
FG
813 def get_server(self, hostname):
814 """
11fdf7f2
TL
815 Called by the plugin to fetch metadata about a particular hostname from
816 ceph-mgr.
817
818 This is information that ceph-mgr has gleaned from the daemon metadata
819 reported by daemons running on a particular server.
7c673cae 820
11fdf7f2 821 :param hostname: a hostname
7c673cae 822 """
3efd9988 823 return self._ceph_get_server(hostname)
7c673cae 824
c07f9fc5
FG
825 def get_perf_schema(self, svc_type, svc_name):
826 """
827 Called by the plugin to fetch perf counter schema info.
828 svc_name can be nullptr, as can svc_type, in which case
829 they are wildcards
830
11fdf7f2
TL
831 :param str svc_type:
832 :param str svc_name:
c07f9fc5
FG
833 :return: list of dicts describing the counters requested
834 """
3efd9988 835 return self._ceph_get_perf_schema(svc_type, svc_name)
c07f9fc5 836
7c673cae
FG
837 def get_counter(self, svc_type, svc_name, path):
838 """
11fdf7f2
TL
839 Called by the plugin to fetch the latest performance counter data for a
840 particular counter on a particular service.
7c673cae 841
11fdf7f2
TL
842 :param str svc_type:
843 :param str svc_name:
844 :param str path: a period-separated concatenation of the subsystem and the
845 counter name, for example "mds.inodes".
846 :return: A list of two-tuples of (timestamp, value) is returned. This may be
847 empty if no data is available.
7c673cae 848 """
3efd9988 849 return self._ceph_get_counter(svc_type, svc_name, path)
7c673cae 850
11fdf7f2
TL
851 def get_latest_counter(self, svc_type, svc_name, path):
852 """
853 Called by the plugin to fetch only the newest performance counter data
854 pointfor a particular counter on a particular service.
855
856 :param str svc_type:
857 :param str svc_name:
858 :param str path: a period-separated concatenation of the subsystem and the
859 counter name, for example "mds.inodes".
860 :return: A list of two-tuples of (timestamp, value) is returned. This may be
861 empty if no data is available.
862 """
863 return self._ceph_get_latest_counter(svc_type, svc_name, path)
864
7c673cae
FG
865 def list_servers(self):
866 """
11fdf7f2
TL
867 Like ``get_server``, but gives information about all servers (i.e. all
868 unique hostnames that have been mentioned in daemon metadata)
869
870 :return: a list of information about all servers
871 :rtype: list
7c673cae 872 """
3efd9988 873 return self._ceph_get_server(None)
7c673cae
FG
874
875 def get_metadata(self, svc_type, svc_id):
876 """
11fdf7f2 877 Fetch the daemon metadata for a particular service.
7c673cae 878
11fdf7f2
TL
879 ceph-mgr fetches metadata asynchronously, so are windows of time during
880 addition/removal of services where the metadata is not available to
881 modules. ``None`` is returned if no metadata is available.
882
883 :param str svc_type: service type (e.g., 'mds', 'osd', 'mon')
884 :param str svc_id: service id. convert OSD integer IDs to strings when
885 calling this
886 :rtype: dict, or None if no metadata found
7c673cae 887 """
3efd9988 888 return self._ceph_get_metadata(svc_type, svc_id)
7c673cae 889
224ce89b
WB
890 def get_daemon_status(self, svc_type, svc_id):
891 """
892 Fetch the latest status for a particular service daemon.
893
11fdf7f2
TL
894 This method may return ``None`` if no status information is
895 available, for example because the daemon hasn't fully started yet.
896
224ce89b
WB
897 :param svc_type: string (e.g., 'rgw')
898 :param svc_id: string
11fdf7f2 899 :return: dict, or None if the service is not found
224ce89b 900 """
3efd9988 901 return self._ceph_get_daemon_status(svc_type, svc_id)
224ce89b 902
11fdf7f2
TL
903 def mon_command(self, cmd_dict):
904 """
905 Helper for modules that do simple, synchronous mon command
906 execution.
907
908 See send_command for general case.
909
910 :return: status int, out std, err str
911 """
912
913 t1 = time.time()
914 result = CommandResult()
915 self.send_command(result, "mon", "", json.dumps(cmd_dict), "")
916 r = result.wait()
917 t2 = time.time()
918
919 self.log.debug("mon_command: '{0}' -> {1} in {2:.3f}s".format(
920 cmd_dict['prefix'], r[0], t2 - t1
921 ))
922
923 return r
924
7c673cae
FG
925 def send_command(self, *args, **kwargs):
926 """
927 Called by the plugin to send a command to the mon
928 cluster.
11fdf7f2
TL
929
930 :param CommandResult result: an instance of the ``CommandResult``
931 class, defined in the same module as MgrModule. This acts as a
932 completion and stores the output of the command. Use
933 ``CommandResult.wait()`` if you want to block on completion.
934 :param str svc_type:
935 :param str svc_id:
936 :param str command: a JSON-serialized command. This uses the same
937 format as the ceph command line, which is a dictionary of command
938 arguments, with the extra ``prefix`` key containing the command
939 name itself. Consult MonCommands.h for available commands and
940 their expected arguments.
941 :param str tag: used for nonblocking operation: when a command
942 completes, the ``notify()`` callback on the MgrModule instance is
943 triggered, with notify_type set to "command", and notify_id set to
944 the tag of the command.
7c673cae 945 """
3efd9988 946 self._ceph_send_command(*args, **kwargs)
7c673cae 947
c07f9fc5
FG
948 def set_health_checks(self, checks):
949 """
c07f9fc5
FG
950 Set the module's current map of health checks. Argument is a
951 dict of check names to info, in this form:
952
11fdf7f2
TL
953 ::
954
c07f9fc5
FG
955 {
956 'CHECK_FOO': {
957 'severity': 'warning', # or 'error'
958 'summary': 'summary string',
959 'detail': [ 'list', 'of', 'detail', 'strings' ],
960 },
961 'CHECK_BAR': {
962 'severity': 'error',
963 'summary': 'bars are bad',
964 'detail': [ 'too hard' ],
965 },
966 }
967
968 :param list: dict of health check dicts
969 """
3efd9988 970 self._ceph_set_health_checks(checks)
c07f9fc5 971
11fdf7f2
TL
972 def _handle_command(self, inbuf, cmd):
973 if cmd['prefix'] not in CLICommand.COMMANDS:
974 return self.handle_command(inbuf, cmd)
975 return CLICommand.COMMANDS[cmd['prefix']].call(self, cmd, inbuf)
976
977 def handle_command(self, inbuf, cmd):
7c673cae
FG
978 """
979 Called by ceph-mgr to request the plugin to handle one
980 of the commands that it declared in self.COMMANDS
981
982 Return a status code, an output buffer, and an
983 output string. The output buffer is for data results,
984 the output string is for informative text.
985
11fdf7f2
TL
986 :param inbuf: content of any "-i <file>" supplied to ceph cli
987 :type inbuf: str
988 :param cmd: from Ceph's cmdmap_t
989 :type cmd: dict
7c673cae 990
11fdf7f2 991 :return: HandleCommandResult or a 3-tuple of (int, str, str)
7c673cae
FG
992 """
993
994 # Should never get called if they didn't declare
995 # any ``COMMANDS``
996 raise NotImplementedError()
997
31f18b77
FG
998 def get_mgr_id(self):
999 """
11fdf7f2
TL
1000 Retrieve the name of the manager daemon where this plugin
1001 is currently being executed (i.e. the active manager).
31f18b77
FG
1002
1003 :return: str
1004 """
3efd9988 1005 return self._ceph_get_mgr_id()
31f18b77 1006
11fdf7f2
TL
1007 def get_ceph_option(self, key):
1008 return self._ceph_get_option(key)
7c673cae 1009
11fdf7f2
TL
1010 def _validate_module_option(self, key):
1011 """
1012 Helper: don't allow get/set config callers to
1013 access config options that they didn't declare
1014 in their schema.
7c673cae 1015 """
11fdf7f2
TL
1016 if key not in [o['name'] for o in self.MODULE_OPTIONS]:
1017 raise RuntimeError("Config option '{0}' is not in {1}.MODULE_OPTIONS".
1018 format(key, self.__class__.__name__))
1019
1020 def _get_module_option(self, key, default, localized_prefix=""):
1021 r = self._ceph_get_module_option(self.module_name, key,
1022 localized_prefix)
3efd9988 1023 if r is None:
11fdf7f2 1024 return self.MODULE_OPTION_DEFAULTS.get(key, default)
3efd9988
FG
1025 else:
1026 return r
7c673cae 1027
11fdf7f2
TL
1028 def get_module_option(self, key, default=None):
1029 """
1030 Retrieve the value of a persistent configuration setting
1031
1032 :param str key:
1033 :param str default:
1034 :return: str
1035 """
1036 self._validate_module_option(key)
1037 return self._get_module_option(key, default)
1038
1039 def get_module_option_ex(self, module, key, default=None):
1040 """
1041 Retrieve the value of a persistent configuration setting
1042 for the specified module.
1043
1044 :param str module: The name of the module, e.g. 'dashboard'
1045 or 'telemetry'.
1046 :param str key: The configuration key, e.g. 'server_addr'.
1047 :param str,None default: The default value to use when the
1048 returned value is ``None``. Defaults to ``None``.
1049 :return: str,int,bool,float,None
1050 """
1051 if module == self.module_name:
1052 self._validate_module_option(key)
1053 r = self._ceph_get_module_option(module, key)
1054 return default if r is None else r
1055
1056 def get_store_prefix(self, key_prefix):
31f18b77 1057 """
11fdf7f2
TL
1058 Retrieve a dict of KV store keys to values, where the keys
1059 have the given prefix
31f18b77 1060
11fdf7f2 1061 :param str key_prefix:
31f18b77
FG
1062 :return: str
1063 """
11fdf7f2 1064 return self._ceph_get_store_prefix(key_prefix)
31f18b77 1065
11fdf7f2
TL
1066 def _set_localized(self, key, val, setter):
1067 return setter(_get_localized_key(self.get_mgr_id(), key), val)
1068
1069 def get_localized_module_option(self, key, default=None):
224ce89b
WB
1070 """
1071 Retrieve localized configuration for this ceph-mgr instance
11fdf7f2
TL
1072 :param str key:
1073 :param str default:
224ce89b
WB
1074 :return: str
1075 """
11fdf7f2
TL
1076 self._validate_module_option(key)
1077 return self._get_module_option(key, default, self.get_mgr_id())
224ce89b 1078
11fdf7f2 1079 def _set_module_option(self, key, val):
eafe8130
TL
1080 return self._ceph_set_module_option(self.module_name, key,
1081 None if val is None else str(val))
224ce89b 1082
11fdf7f2 1083 def set_module_option(self, key, val):
7c673cae
FG
1084 """
1085 Set the value of a persistent configuration setting
1086
11fdf7f2
TL
1087 :param str key:
1088 :type val: str | None
7c673cae 1089 """
11fdf7f2
TL
1090 self._validate_module_option(key)
1091 return self._set_module_option(key, val)
7c673cae 1092
11fdf7f2
TL
1093 def set_module_option_ex(self, module, key, val):
1094 """
1095 Set the value of a persistent configuration setting
1096 for the specified module.
1097
1098 :param str module:
1099 :param str key:
1100 :param str val:
1101 """
1102 if module == self.module_name:
1103 self._validate_module_option(key)
1104 return self._ceph_set_module_option(module, key, str(val))
1105
1106 def set_localized_module_option(self, key, val):
224ce89b
WB
1107 """
1108 Set localized configuration for this ceph-mgr instance
11fdf7f2
TL
1109 :param str key:
1110 :param str val:
224ce89b
WB
1111 :return: str
1112 """
11fdf7f2
TL
1113 self._validate_module_option(key)
1114 return self._set_localized(key, val, self._set_module_option)
224ce89b 1115
11fdf7f2 1116 def set_store(self, key, val):
7c673cae 1117 """
11fdf7f2
TL
1118 Set a value in this module's persistent key value store.
1119 If val is None, remove key from store
7c673cae 1120
11fdf7f2
TL
1121 :param str key:
1122 :param str val:
7c673cae 1123 """
11fdf7f2 1124 self._ceph_set_store(key, val)
7c673cae 1125
11fdf7f2 1126 def get_store(self, key, default=None):
7c673cae 1127 """
11fdf7f2 1128 Get a value from this module's persistent key value store
7c673cae 1129 """
11fdf7f2
TL
1130 r = self._ceph_get_store(key)
1131 if r is None:
1132 return default
7c673cae 1133 else:
11fdf7f2
TL
1134 return r
1135
1136 def get_localized_store(self, key, default=None):
1137 r = self._ceph_get_store(_get_localized_key(self.get_mgr_id(), key))
1138 if r is None:
1139 r = self._ceph_get_store(key)
1140 if r is None:
1141 r = default
1142 return r
1143
1144 def set_localized_store(self, key, val):
1145 return self._set_localized(key, val, self.set_store)
224ce89b
WB
1146
1147 def self_test(self):
1148 """
1149 Run a self-test on the module. Override this function and implement
1150 a best as possible self-test for (automated) testing of the module
11fdf7f2
TL
1151
1152 Indicate any failures by raising an exception. This does not have
1153 to be pretty, it's mainly for picking up regressions during
1154 development, rather than use in the field.
1155
1156 :return: None, or an advisory string for developer interest, such
1157 as a json dump of some state.
224ce89b
WB
1158 """
1159 pass
3efd9988
FG
1160
1161 def get_osdmap(self):
1162 """
1163 Get a handle to an OSDMap. If epoch==0, get a handle for the latest
1164 OSDMap.
1165 :return: OSDMap
1166 """
1167 return self._ceph_get_osdmap()
1168
11fdf7f2
TL
1169 def get_latest(self, daemon_type, daemon_name, counter):
1170 data = self.get_latest_counter(
1171 daemon_type, daemon_name, counter)[counter]
1172 if data:
1173 return data[1]
1174 else:
1175 return 0
1176
1177 def get_latest_avg(self, daemon_type, daemon_name, counter):
1178 data = self.get_latest_counter(
1179 daemon_type, daemon_name, counter)[counter]
1180 if data:
1181 return data[1], data[2]
1182 else:
1183 return 0, 0
1184
1185 def get_all_perf_counters(self, prio_limit=PRIO_USEFUL,
1186 services=("mds", "mon", "osd",
81eedcae 1187 "rbd-mirror", "rgw", "tcmu-runner")):
3efd9988
FG
1188 """
1189 Return the perf counters currently known to this ceph-mgr
1190 instance, filtered by priority equal to or greater than `prio_limit`.
1191
11fdf7f2 1192 The result is a map of string to dict, associating services
3efd9988
FG
1193 (like "osd.123") with their counters. The counter
1194 dict for each service maps counter paths to a counter
1195 info structure, which is the information from
1196 the schema, plus an additional "value" member with the latest
1197 value.
1198 """
1199
1200 result = defaultdict(dict)
1201
3efd9988
FG
1202 for server in self.list_servers():
1203 for service in server['services']:
11fdf7f2 1204 if service['type'] not in services:
3efd9988
FG
1205 continue
1206
1207 schema = self.get_perf_schema(service['type'], service['id'])
1208 if not schema:
1209 self.log.warn("No perf counter schema for {0}.{1}".format(
1210 service['type'], service['id']
1211 ))
1212 continue
1213
1214 # Value is returned in a potentially-multi-service format,
1215 # get just the service we're asking about
11fdf7f2
TL
1216 svc_full_name = "{0}.{1}".format(
1217 service['type'], service['id'])
3efd9988
FG
1218 schema = schema[svc_full_name]
1219
1220 # Populate latest values
1221 for counter_path, counter_schema in schema.items():
1222 # self.log.debug("{0}: {1}".format(
1223 # counter_path, json.dumps(counter_schema)
1224 # ))
1225 if counter_schema['priority'] < prio_limit:
1226 continue
1227
28e407b8
AA
1228 counter_info = dict(counter_schema)
1229
1230 # Also populate count for the long running avgs
1231 if counter_schema['type'] & self.PERFCOUNTER_LONGRUNAVG:
11fdf7f2 1232 v, c = self.get_latest_avg(
28e407b8
AA
1233 service['type'],
1234 service['id'],
1235 counter_path
1236 )
1237 counter_info['value'], counter_info['count'] = v, c
1238 result[svc_full_name][counter_path] = counter_info
1239 else:
11fdf7f2 1240 counter_info['value'] = self.get_latest(
28e407b8
AA
1241 service['type'],
1242 service['id'],
1243 counter_path
1244 )
1245
3efd9988
FG
1246 result[svc_full_name][counter_path] = counter_info
1247
1248 self.log.debug("returning {0} counter".format(len(result)))
1249
1250 return result
1251
1252 def set_uri(self, uri):
1253 """
1254 If the module exposes a service, then call this to publish the
1255 address once it is available.
1256
1257 :return: a string
1258 """
1259 return self._ceph_set_uri(uri)
94b18763
FG
1260
1261 def have_mon_connection(self):
1262 """
1263 Check whether this ceph-mgr daemon has an open connection
1264 to a monitor. If it doesn't, then it's likely that the
1265 information we have about the cluster is out of date,
1266 and/or the monitor cluster is down.
1267 """
1268
28e407b8 1269 return self._ceph_have_mon_connection()
11fdf7f2
TL
1270
1271 def update_progress_event(self, evid, desc, progress):
1272 return self._ceph_update_progress_event(str(evid), str(desc), float(progress))
1273
1274 def complete_progress_event(self, evid):
1275 return self._ceph_complete_progress_event(str(evid))
1276
1277 def clear_all_progress_events(self):
1278 return self._ceph_clear_all_progress_events()
1279
1280 @property
1281 def rados(self):
1282 """
1283 A librados instance to be shared by any classes within
1284 this mgr module that want one.
1285 """
1286 if self._rados:
1287 return self._rados
1288
1289 ctx_capsule = self.get_context()
1290 self._rados = rados.Rados(context=ctx_capsule)
1291 self._rados.connect()
1292
1293 return self._rados
1294
1295 @staticmethod
1296 def can_run():
1297 """
1298 Implement this function to report whether the module's dependencies
1299 are met. For example, if the module needs to import a particular
1300 dependency to work, then use a try/except around the import at
1301 file scope, and then report here if the import failed.
1302
1303 This will be called in a blocking way from the C++ code, so do not
1304 do any I/O that could block in this function.
1305
1306 :return a 2-tuple consisting of a boolean and explanatory string
1307 """
1308
1309 return True, ""
1310
1311 def remote(self, module_name, method_name, *args, **kwargs):
1312 """
1313 Invoke a method on another module. All arguments, and the return
1314 value from the other module must be serializable.
1315
1316 Limitation: Do not import any modules within the called method.
1317 Otherwise you will get an error in Python 2::
1318
1319 RuntimeError('cannot unmarshal code objects in restricted execution mode',)
1320
1321
1322
1323 :param module_name: Name of other module. If module isn't loaded,
1324 an ImportError exception is raised.
1325 :param method_name: Method name. If it does not exist, a NameError
1326 exception is raised.
1327 :param args: Argument tuple
1328 :param kwargs: Keyword argument dict
1329 :raises RuntimeError: **Any** error raised within the method is converted to a RuntimeError
1330 :raises ImportError: No such module
1331 """
1332 return self._ceph_dispatch_remote(module_name, method_name,
1333 args, kwargs)
1334
1335 def add_osd_perf_query(self, query):
1336 """
1337 Register an OSD perf query. Argument is a
1338 dict of the query parameters, in this form:
1339
1340 ::
1341
1342 {
1343 'key_descriptor': [
1344 {'type': subkey_type, 'regex': regex_pattern},
1345 ...
1346 ],
1347 'performance_counter_descriptors': [
1348 list, of, descriptor, types
1349 ],
1350 'limit': {'order_by': performance_counter_type, 'max_count': n},
1351 }
1352
1353 Valid subkey types:
1354 'client_id', 'client_address', 'pool_id', 'namespace', 'osd_id',
1355 'pg_id', 'object_name', 'snap_id'
1356 Valid performance counter types:
1357 'ops', 'write_ops', 'read_ops', 'bytes', 'write_bytes', 'read_bytes',
1358 'latency', 'write_latency', 'read_latency'
1359
1360 :param object query: query
1361 :rtype: int (query id)
1362 """
1363 return self._ceph_add_osd_perf_query(query)
1364
1365 def remove_osd_perf_query(self, query_id):
1366 """
1367 Unregister an OSD perf query.
1368
1369 :param int query_id: query ID
1370 """
1371 return self._ceph_remove_osd_perf_query(query_id)
1372
1373 def get_osd_perf_counters(self, query_id):
1374 """
1375 Get stats collected for an OSD perf query.
1376
1377 :param int query_id: query ID
1378 """
1379 return self._ceph_get_osd_perf_counters(query_id)
494da23a 1380
92f5a8d4
TL
1381 def is_authorized(self, arguments):
1382 """
1383 Verifies that the current session caps permit executing the py service
1384 or current module with the provided arguments. This provides a generic
1385 way to allow modules to restrict by more fine-grained controls (e.g.
1386 pools).
1387
1388 :param arguments: dict of key/value arguments to test
1389 """
1390 return self._ceph_is_authorized(arguments)
1391
494da23a
TL
1392
1393class PersistentStoreDict(object):
1394 def __init__(self, mgr, prefix):
1395 # type: (MgrModule, str) -> None
1396 self.mgr = mgr
1397 self.prefix = prefix + '.'
1398
1399 def _mk_store_key(self, key):
1400 return self.prefix + key
1401
1402 def __missing__(self, key):
1403 # KeyError won't work for the `in` operator.
1404 # https://docs.python.org/3/reference/expressions.html#membership-test-details
1405 raise IndexError('PersistentStoreDict: "{}" not found'.format(key))
1406
1407 def clear(self):
1408 # Don't make any assumptions about the content of the values.
1409 for item in six.iteritems(self.mgr.get_store_prefix(self.prefix)):
1410 k, _ = item
1411 self.mgr.set_store(k, None)
1412
1413 def __getitem__(self, item):
1414 # type: (str) -> Any
1415 key = self._mk_store_key(item)
1416 try:
1417 val = self.mgr.get_store(key)
1418 if val is None:
1419 self.__missing__(key)
1420 return json.loads(val)
1421 except (KeyError, AttributeError, IndexError, ValueError, TypeError):
1422 logging.getLogger(__name__).exception('failed to deserialize')
1423 self.mgr.set_store(key, None)
1424 raise
1425
1426 def __setitem__(self, item, value):
1427 # type: (str, Any) -> None
1428 """
1429 value=None is not allowed, as it will remove the key.
1430 """
1431 key = self._mk_store_key(item)
1432 self.mgr.set_store(key, json.dumps(value) if value is not None else None)
1433
1434 def __delitem__(self, item):
1435 self[item] = None
1436
1437 def __len__(self):
1438 return len(self.keys())
1439
1440 def items(self):
1441 # type: () -> Iterator[Tuple[str, Any]]
1442 prefix_len = len(self.prefix)
1443 try:
1444 for item in six.iteritems(self.mgr.get_store_prefix(self.prefix)):
1445 k, v = item
1446 yield k[prefix_len:], json.loads(v)
1447 except (KeyError, AttributeError, IndexError, ValueError, TypeError):
1448 logging.getLogger(__name__).exception('failed to deserialize')
1449 self.clear()
1450
1451 def keys(self):
1452 # type: () -> Set[str]
1453 return {item[0] for item in self.items()}
1454
1455 def __iter__(self):
1456 return iter(self.keys())