]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/plugins/feature_toggles.py
c71d325476774f28f1d64db80208159d8aa229b8
[ceph.git] / ceph / src / pybind / mgr / dashboard / plugins / feature_toggles.py
1 # -*- coding: utf-8 -*-
2
3 from enum import Enum
4 from typing import List, Optional, Set, no_type_check
5
6 import cherrypy
7 from mgr_module import CLICommand, Option
8
9 from ..controllers.cephfs import CephFS
10 from ..controllers.iscsi import Iscsi, IscsiTarget
11 from ..controllers.nfs import NFSGanesha, NFSGaneshaExports
12 from ..controllers.rbd import Rbd, RbdSnapshot, RbdTrash
13 from ..controllers.rbd_mirroring import RbdMirroringPoolMode, \
14 RbdMirroringPoolPeer, RbdMirroringSummary
15 from ..controllers.rgw import Rgw, RgwBucket, RgwDaemon, RgwUser
16 from . import PLUGIN_MANAGER as PM
17 from . import interfaces as I # noqa: E741,N812
18 from .ttl_cache import ttl_cache
19
20
21 class Features(Enum):
22 RBD = 'rbd'
23 MIRRORING = 'mirroring'
24 ISCSI = 'iscsi'
25 CEPHFS = 'cephfs'
26 RGW = 'rgw'
27 NFS = 'nfs'
28
29
30 PREDISABLED_FEATURES = set() # type: Set[str]
31
32 Feature2Controller = {
33 Features.RBD: [Rbd, RbdSnapshot, RbdTrash],
34 Features.MIRRORING: [
35 RbdMirroringSummary, RbdMirroringPoolMode, RbdMirroringPoolPeer],
36 Features.ISCSI: [Iscsi, IscsiTarget],
37 Features.CEPHFS: [CephFS],
38 Features.RGW: [Rgw, RgwDaemon, RgwBucket, RgwUser],
39 Features.NFS: [NFSGanesha, NFSGaneshaExports],
40 }
41
42
43 class Actions(Enum):
44 ENABLE = 'enable'
45 DISABLE = 'disable'
46 STATUS = 'status'
47
48
49 # pylint: disable=too-many-ancestors
50 @PM.add_plugin
51 class FeatureToggles(I.CanMgr, I.Setupable, I.HasOptions,
52 I.HasCommands, I.FilterRequest.BeforeHandler,
53 I.HasControllers):
54 OPTION_FMT = 'FEATURE_TOGGLE_{.name}'
55 CACHE_MAX_SIZE = 128 # Optimum performance with 2^N sizes
56 CACHE_TTL = 10 # seconds
57
58 @PM.add_hook
59 def setup(self):
60 # pylint: disable=attribute-defined-outside-init
61 self.Controller2Feature = {
62 controller: feature
63 for feature, controllers in Feature2Controller.items()
64 for controller in controllers} # type: ignore
65
66 @PM.add_hook
67 def get_options(self):
68 return [Option(
69 name=self.OPTION_FMT.format(feature),
70 default=(feature not in PREDISABLED_FEATURES),
71 type='bool',) for feature in Features]
72
73 @PM.add_hook
74 def register_commands(self):
75 @CLICommand("dashboard feature")
76 def cmd(mgr,
77 action: Actions = Actions.STATUS,
78 features: Optional[List[Features]] = None):
79 '''
80 Enable or disable features in Ceph-Mgr Dashboard
81 '''
82 ret = 0
83 msg = []
84 if action in [Actions.ENABLE, Actions.DISABLE]:
85 if features is None:
86 ret = 1
87 msg = ["At least one feature must be specified"]
88 else:
89 for feature in features:
90 mgr.set_module_option(
91 self.OPTION_FMT.format(feature),
92 action == Actions.ENABLE)
93 msg += ["Feature '{.value}': {}".format(
94 feature,
95 'enabled' if action == Actions.ENABLE else
96 'disabled')]
97 else:
98 for feature in features or list(Features):
99 enabled = mgr.get_module_option(self.OPTION_FMT.format(feature))
100 msg += ["Feature '{.value}': {}".format(
101 feature,
102 'enabled' if enabled else 'disabled')]
103 return ret, '\n'.join(msg), ''
104 return {'handle_command': cmd}
105
106 @no_type_check # https://github.com/python/mypy/issues/7806
107 def _get_feature_from_request(self, request):
108 try:
109 return self.Controller2Feature[
110 request.handler.callable.__self__]
111 except (AttributeError, KeyError):
112 return None
113
114 @ttl_cache(ttl=CACHE_TTL, maxsize=CACHE_MAX_SIZE)
115 @no_type_check # https://github.com/python/mypy/issues/7806
116 def _is_feature_enabled(self, feature):
117 return self.mgr.get_module_option(self.OPTION_FMT.format(feature))
118
119 @PM.add_hook
120 def filter_request_before_handler(self, request):
121 feature = self._get_feature_from_request(request)
122 if feature is None:
123 return
124
125 if not self._is_feature_enabled(feature):
126 raise cherrypy.HTTPError(
127 404, "Feature='{}' disabled by option '{}'".format(
128 feature.value,
129 self.OPTION_FMT.format(feature),
130 )
131 )
132
133 @PM.add_hook
134 def get_controllers(self):
135 from ..controllers import APIDoc, APIRouter, EndpointDoc, RESTController
136
137 FEATURES_SCHEMA = {
138 "rbd": (bool, ''),
139 "mirroring": (bool, ''),
140 "iscsi": (bool, ''),
141 "cephfs": (bool, ''),
142 "rgw": (bool, ''),
143 "nfs": (bool, '')
144 }
145
146 @APIRouter('/feature_toggles')
147 @APIDoc("Manage Features API", "FeatureTogglesEndpoint")
148 class FeatureTogglesEndpoint(RESTController):
149 @EndpointDoc("Get List Of Features",
150 responses={200: FEATURES_SCHEMA})
151 def list(_): # pylint: disable=no-self-argument # noqa: N805
152 return {
153 # pylint: disable=protected-access
154 feature.value: self._is_feature_enabled(feature)
155 for feature in Features
156 }
157 return [FeatureTogglesEndpoint]