summaryrefslogtreecommitdiffstats
path: root/src/pybind/mgr/dashboard/plugins/feature_toggles.py
blob: 7edbfa8f6adafb7f8e0b5d36072b7ae1c00faf12 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# -*- coding: utf-8 -*-
from __future__ import absolute_import

from enum import Enum
import cherrypy
from mgr_module import CLICommand, Option

from . import PLUGIN_MANAGER as PM
from . import interfaces as I
from .ttl_cache import ttl_cache

from ..controllers.rbd import Rbd, RbdSnapshot, RbdTrash
from ..controllers.rbd_mirroring import (
    RbdMirroringSummary, RbdMirroringPoolMode, RbdMirroringPoolPeer)
from ..controllers.iscsi import Iscsi, IscsiTarget
from ..controllers.cephfs import CephFS
from ..controllers.rgw import Rgw, RgwDaemon, RgwBucket, RgwUser


class Features(Enum):
    RBD = 'rbd'
    MIRRORING = 'mirroring'
    ISCSI = 'iscsi'
    CEPHFS = 'cephfs'
    RGW = 'rgw'


PREDISABLED_FEATURES = set()


Feature2Controller = {
    Features.RBD: [Rbd, RbdSnapshot, RbdTrash],
    Features.MIRRORING: [
        RbdMirroringSummary, RbdMirroringPoolMode, RbdMirroringPoolPeer],
    Features.ISCSI: [Iscsi, IscsiTarget],
    Features.CEPHFS: [CephFS],
    Features.RGW: [Rgw, RgwDaemon, RgwBucket, RgwUser],
}


class Actions(Enum):
    ENABLE = 'enable'
    DISABLE = 'disable'
    STATUS = 'status'


@PM.add_plugin
class FeatureToggles(I.CanMgr, I.CanLog, I.Setupable, I.HasOptions,
                     I.HasCommands, I.FilterRequest.BeforeHandler,
                     I.HasControllers):
    OPTION_FMT = 'FEATURE_TOGGLE_{}'
    CACHE_MAX_SIZE = 128  # Optimum performance with 2^N sizes
    CACHE_TTL = 10  # seconds

    @PM.add_hook
    def setup(self):
        self.Controller2Feature = {
            controller: feature
            for feature, controllers in Feature2Controller.items()
            for controller in controllers}

    @PM.add_hook
    def get_options(self):
        return [Option(
            name=self.OPTION_FMT.format(feature.value),
            default=(feature not in PREDISABLED_FEATURES),
            type='bool',) for feature in Features]

    @PM.add_hook
    def register_commands(self):
        @CLICommand(
            "dashboard feature",
            "name=action,type=CephChoices,strings={} ".format(
                "|".join(a.value for a in Actions))
            + "name=features,type=CephChoices,strings={},req=false,n=N".format(
                "|".join(f.value for f in Features)),
            "Enable or disable features in Ceph-Mgr Dashboard")
        def cmd(mgr, action, features=None):
            ret = 0
            msg = []
            if action in [Actions.ENABLE.value, Actions.DISABLE.value]:
                if features is None:
                    ret = 1
                    msg = ["At least one feature must be specified"]
                else:
                    for feature in features:
                        mgr.set_module_option(
                            self.OPTION_FMT.format(feature),
                            action == Actions.ENABLE.value)
                        msg += ["Feature '{}': {}".format(
                            feature,
                            'enabled' if action == Actions.ENABLE.value else
                            'disabled')]
            else:
                for feature in features or [f.value for f in Features]:
                    enabled = mgr.get_module_option(self.OPTION_FMT.format(feature))
                    msg += ["Feature '{}': '{}'".format(
                        feature,
                        'enabled' if enabled else 'disabled')]
            return ret, '\n'.join(msg), ''
        return {'handle_command': cmd}

    def _get_feature_from_request(self, request):
        try:
            return self.Controller2Feature[
                request.handler.callable.__self__]
        except (AttributeError, KeyError):
            return None

    @ttl_cache(ttl=CACHE_TTL, maxsize=CACHE_MAX_SIZE)
    def _is_feature_enabled(self, feature):
        return self.mgr.get_module_option(self.OPTION_FMT.format(feature.value))

    @PM.add_hook
    def filter_request_before_handler(self, request):
        feature = self._get_feature_from_request(request)
        if feature is None:
            return

        if not self._is_feature_enabled(feature):
            raise cherrypy.HTTPError(
                404, "Feature='{}' disabled by option '{}'".format(
                    feature.value,
                    self.OPTION_FMT.format(feature.value),
                    )
                )

    @PM.add_hook
    def get_controllers(self):
        from ..controllers import ApiController, RESTController

        @ApiController('/feature_toggles')
        class FeatureTogglesEndpoint(RESTController):

            def list(_):
                return {
                    feature.value: self._is_feature_enabled(feature)
                    for feature in Features
                }
        return [FeatureTogglesEndpoint]