]>
Commit | Line | Data |
---|---|---|
11fdf7f2 TL |
1 | # -*- coding: utf-8 -*- |
2 | from __future__ import absolute_import | |
3 | ||
4 | from enum import Enum | |
5 | import cherrypy | |
6 | from mgr_module import CLICommand, Option | |
7 | ||
8 | from . import PLUGIN_MANAGER as PM | |
9 | from . import interfaces as I | |
10 | from .ttl_cache import ttl_cache | |
11 | ||
12 | from ..controllers.rbd import Rbd, RbdSnapshot, RbdTrash | |
13 | from ..controllers.rbd_mirroring import ( | |
14 | RbdMirroringSummary, RbdMirroringPoolMode, RbdMirroringPoolPeer) | |
15 | from ..controllers.iscsi import Iscsi, IscsiTarget | |
16 | from ..controllers.cephfs import CephFS | |
17 | from ..controllers.rgw import Rgw, RgwDaemon, RgwBucket, RgwUser | |
18 | ||
19 | ||
20 | class Features(Enum): | |
21 | RBD = 'rbd' | |
22 | MIRRORING = 'mirroring' | |
23 | ISCSI = 'iscsi' | |
24 | CEPHFS = 'cephfs' | |
25 | RGW = 'rgw' | |
26 | ||
27 | ||
28 | PREDISABLED_FEATURES = set() | |
29 | ||
30 | ||
31 | Feature2Controller = { | |
32 | Features.RBD: [Rbd, RbdSnapshot, RbdTrash], | |
33 | Features.MIRRORING: [ | |
34 | RbdMirroringSummary, RbdMirroringPoolMode, RbdMirroringPoolPeer], | |
35 | Features.ISCSI: [Iscsi, IscsiTarget], | |
36 | Features.CEPHFS: [CephFS], | |
37 | Features.RGW: [Rgw, RgwDaemon, RgwBucket, RgwUser], | |
38 | } | |
39 | ||
40 | ||
41 | class Actions(Enum): | |
42 | ENABLE = 'enable' | |
43 | DISABLE = 'disable' | |
44 | STATUS = 'status' | |
45 | ||
46 | ||
47 | @PM.add_plugin | |
48 | class FeatureToggles(I.CanMgr, I.CanLog, I.Setupable, I.HasOptions, | |
49 | I.HasCommands, I.FilterRequest.BeforeHandler, | |
50 | I.HasControllers): | |
51 | OPTION_FMT = 'FEATURE_TOGGLE_{}' | |
52 | CACHE_MAX_SIZE = 128 # Optimum performance with 2^N sizes | |
53 | CACHE_TTL = 10 # seconds | |
54 | ||
55 | @PM.add_hook | |
56 | def setup(self): | |
57 | self.Controller2Feature = { | |
58 | controller: feature | |
59 | for feature, controllers in Feature2Controller.items() | |
60 | for controller in controllers} | |
61 | ||
62 | @PM.add_hook | |
63 | def get_options(self): | |
64 | return [Option( | |
65 | name=self.OPTION_FMT.format(feature.value), | |
66 | default=(feature not in PREDISABLED_FEATURES), | |
67 | type='bool',) for feature in Features] | |
68 | ||
69 | @PM.add_hook | |
70 | def register_commands(self): | |
71 | @CLICommand( | |
72 | "dashboard feature", | |
73 | "name=action,type=CephChoices,strings={} ".format( | |
74 | "|".join(a.value for a in Actions)) | |
75 | + "name=features,type=CephChoices,strings={},req=false,n=N".format( | |
76 | "|".join(f.value for f in Features)), | |
77 | "Enable or disable features in Ceph-Mgr Dashboard") | |
78 | def cmd(mgr, action, features=None): | |
79 | ret = 0 | |
80 | msg = [] | |
81 | if action in [Actions.ENABLE.value, Actions.DISABLE.value]: | |
82 | if features is None: | |
83 | ret = 1 | |
84 | msg = ["At least one feature must be specified"] | |
85 | else: | |
86 | for feature in features: | |
87 | mgr.set_module_option( | |
88 | self.OPTION_FMT.format(feature), | |
89 | action == Actions.ENABLE.value) | |
90 | msg += ["Feature '{}': {}".format( | |
91 | feature, | |
92 | 'enabled' if action == Actions.ENABLE.value else | |
93 | 'disabled')] | |
94 | else: | |
95 | for feature in features or [f.value for f in Features]: | |
96 | enabled = mgr.get_module_option(self.OPTION_FMT.format(feature)) | |
97 | msg += ["Feature '{}': '{}'".format( | |
98 | feature, | |
99 | 'enabled' if enabled else 'disabled')] | |
100 | return ret, '\n'.join(msg), '' | |
101 | return {'handle_command': cmd} | |
102 | ||
103 | def _get_feature_from_request(self, request): | |
104 | try: | |
105 | return self.Controller2Feature[ | |
106 | request.handler.callable.__self__] | |
107 | except (AttributeError, KeyError): | |
108 | return None | |
109 | ||
110 | @ttl_cache(ttl=CACHE_TTL, maxsize=CACHE_MAX_SIZE) | |
111 | def _is_feature_enabled(self, feature): | |
112 | return self.mgr.get_module_option(self.OPTION_FMT.format(feature.value)) | |
113 | ||
114 | @PM.add_hook | |
115 | def filter_request_before_handler(self, request): | |
116 | feature = self._get_feature_from_request(request) | |
117 | if feature is None: | |
118 | return | |
119 | ||
120 | if not self._is_feature_enabled(feature): | |
121 | raise cherrypy.HTTPError( | |
122 | 404, "Feature='{}' disabled by option '{}'".format( | |
123 | feature.value, | |
124 | self.OPTION_FMT.format(feature.value), | |
125 | ) | |
126 | ) | |
127 | ||
128 | @PM.add_hook | |
129 | def get_controllers(self): | |
130 | from ..controllers import ApiController, RESTController | |
131 | ||
132 | @ApiController('/feature_toggles') | |
133 | class FeatureTogglesEndpoint(RESTController): | |
134 | ||
135 | def list(_): | |
136 | return { | |
137 | feature.value: self._is_feature_enabled(feature) | |
138 | for feature in Features | |
139 | } | |
140 | return [FeatureTogglesEndpoint] |