]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/plugins/feature_toggles.py
import ceph quincy 17.2.4
[ceph.git] / ceph / src / pybind / mgr / dashboard / plugins / feature_toggles.py
CommitLineData
11fdf7f2 1# -*- coding: utf-8 -*-
11fdf7f2
TL
2
3from enum import Enum
20effc67 4from typing import List, Optional, Set, no_type_check
f67539c2 5
11fdf7f2
TL
6import cherrypy
7from mgr_module import CLICommand, Option
8
f67539c2
TL
9from ..controllers.cephfs import CephFS
10from ..controllers.iscsi import Iscsi, IscsiTarget
2a845540 11from ..controllers.nfs import NFSGaneshaExports, NFSGaneshaUi
f67539c2
TL
12from ..controllers.rbd import Rbd, RbdSnapshot, RbdTrash
13from ..controllers.rbd_mirroring import RbdMirroringPoolMode, \
14 RbdMirroringPoolPeer, RbdMirroringSummary
15from ..controllers.rgw import Rgw, RgwBucket, RgwDaemon, RgwUser
11fdf7f2 16from . import PLUGIN_MANAGER as PM
9f95a23c 17from . import interfaces as I # noqa: E741,N812
11fdf7f2
TL
18from .ttl_cache import ttl_cache
19
11fdf7f2
TL
20
21class Features(Enum):
22 RBD = 'rbd'
23 MIRRORING = 'mirroring'
24 ISCSI = 'iscsi'
25 CEPHFS = 'cephfs'
26 RGW = 'rgw'
9f95a23c 27 NFS = 'nfs'
11fdf7f2
TL
28
29
9f95a23c 30PREDISABLED_FEATURES = set() # type: Set[str]
11fdf7f2 31
11fdf7f2
TL
32Feature2Controller = {
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],
2a845540 39 Features.NFS: [NFSGaneshaUi, NFSGaneshaExports],
11fdf7f2
TL
40}
41
42
43class Actions(Enum):
44 ENABLE = 'enable'
45 DISABLE = 'disable'
46 STATUS = 'status'
47
48
9f95a23c 49# pylint: disable=too-many-ancestors
11fdf7f2 50@PM.add_plugin
9f95a23c 51class FeatureToggles(I.CanMgr, I.Setupable, I.HasOptions,
11fdf7f2
TL
52 I.HasCommands, I.FilterRequest.BeforeHandler,
53 I.HasControllers):
f67539c2 54 OPTION_FMT = 'FEATURE_TOGGLE_{.name}'
11fdf7f2
TL
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):
9f95a23c 60 # pylint: disable=attribute-defined-outside-init
11fdf7f2
TL
61 self.Controller2Feature = {
62 controller: feature
63 for feature, controllers in Feature2Controller.items()
9f95a23c 64 for controller in controllers} # type: ignore
11fdf7f2
TL
65
66 @PM.add_hook
67 def get_options(self):
68 return [Option(
f67539c2 69 name=self.OPTION_FMT.format(feature),
11fdf7f2
TL
70 default=(feature not in PREDISABLED_FEATURES),
71 type='bool',) for feature in Features]
72
73 @PM.add_hook
74 def register_commands(self):
f67539c2
TL
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 '''
11fdf7f2
TL
82 ret = 0
83 msg = []
f67539c2 84 if action in [Actions.ENABLE, Actions.DISABLE]:
11fdf7f2
TL
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),
f67539c2
TL
92 action == Actions.ENABLE)
93 msg += ["Feature '{.value}': {}".format(
11fdf7f2 94 feature,
f67539c2 95 'enabled' if action == Actions.ENABLE else
11fdf7f2
TL
96 'disabled')]
97 else:
f67539c2 98 for feature in features or list(Features):
11fdf7f2 99 enabled = mgr.get_module_option(self.OPTION_FMT.format(feature))
f67539c2 100 msg += ["Feature '{.value}': {}".format(
11fdf7f2
TL
101 feature,
102 'enabled' if enabled else 'disabled')]
103 return ret, '\n'.join(msg), ''
104 return {'handle_command': cmd}
105
9f95a23c 106 @no_type_check # https://github.com/python/mypy/issues/7806
11fdf7f2
TL
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)
9f95a23c 115 @no_type_check # https://github.com/python/mypy/issues/7806
11fdf7f2 116 def _is_feature_enabled(self, feature):
f67539c2 117 return self.mgr.get_module_option(self.OPTION_FMT.format(feature))
11fdf7f2
TL
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,
f67539c2 129 self.OPTION_FMT.format(feature),
11fdf7f2 130 )
f67539c2 131 )
11fdf7f2
TL
132
133 @PM.add_hook
134 def get_controllers(self):
a4b75251 135 from ..controllers import APIDoc, APIRouter, EndpointDoc, RESTController
f67539c2
TL
136
137 FEATURES_SCHEMA = {
138 "rbd": (bool, ''),
139 "mirroring": (bool, ''),
140 "iscsi": (bool, ''),
141 "cephfs": (bool, ''),
142 "rgw": (bool, ''),
143 "nfs": (bool, '')
144 }
11fdf7f2 145
a4b75251
TL
146 @APIRouter('/feature_toggles')
147 @APIDoc("Manage Features API", "FeatureTogglesEndpoint")
11fdf7f2 148 class FeatureTogglesEndpoint(RESTController):
f67539c2
TL
149 @EndpointDoc("Get List Of Features",
150 responses={200: FEATURES_SCHEMA})
9f95a23c 151 def list(_): # pylint: disable=no-self-argument # noqa: N805
11fdf7f2 152 return {
9f95a23c 153 # pylint: disable=protected-access
11fdf7f2
TL
154 feature.value: self._is_feature_enabled(feature)
155 for feature in Features
156 }
157 return [FeatureTogglesEndpoint]