]>
Commit | Line | Data |
---|---|---|
11fdf7f2 | 1 | # -*- coding: utf-8 -*- |
11fdf7f2 TL |
2 | |
3 | from enum import Enum | |
20effc67 | 4 | from typing import List, Optional, Set, no_type_check |
f67539c2 | 5 | |
11fdf7f2 TL |
6 | import cherrypy |
7 | from mgr_module import CLICommand, Option | |
8 | ||
f67539c2 TL |
9 | from ..controllers.cephfs import CephFS |
10 | from ..controllers.iscsi import Iscsi, IscsiTarget | |
2a845540 | 11 | from ..controllers.nfs import NFSGaneshaExports, NFSGaneshaUi |
f67539c2 TL |
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 | |
11fdf7f2 | 16 | from . import PLUGIN_MANAGER as PM |
9f95a23c | 17 | from . import interfaces as I # noqa: E741,N812 |
11fdf7f2 TL |
18 | from .ttl_cache import ttl_cache |
19 | ||
11fdf7f2 TL |
20 | |
21 | class 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 | 30 | PREDISABLED_FEATURES = set() # type: Set[str] |
11fdf7f2 | 31 | |
11fdf7f2 TL |
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], | |
2a845540 | 39 | Features.NFS: [NFSGaneshaUi, NFSGaneshaExports], |
11fdf7f2 TL |
40 | } |
41 | ||
42 | ||
43 | class 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 | 51 | class 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] |