summaryrefslogtreecommitdiffstats
path: root/src/pybind/mgr/dashboard/controllers/cluster_configuration.py
blob: 292f381d79f0138a65e025b0d10ac61b3a5c5f88 (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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# -*- coding: utf-8 -*-

from typing import Optional

import cherrypy

from .. import mgr
from ..exceptions import DashboardException
from ..security import Scope
from ..services.ceph_service import CephService
from . import APIDoc, APIRouter, EndpointDoc, Param, RESTController

FILTER_SCHEMA = [{
    "name": (str, 'Name of the config option'),
    "type": (str, 'Config option type'),
    "level": (str, 'Config option level'),
    "desc": (str, 'Description of the configuration'),
    "long_desc": (str, 'Elaborated description'),
    "default": (str, 'Default value for the config option'),
    "daemon_default": (str, 'Daemon specific default value'),
    "tags": ([str], 'Tags associated with the cluster'),
    "services": ([str], 'Services associated with the config option'),
    "see_also": ([str], 'Related config options'),
    "enum_values": ([str], 'List of enums allowed'),
    "min": (str, 'Minimum value'),
    "max": (str, 'Maximum value'),
    "can_update_at_runtime": (bool, 'Check if can update at runtime'),
    "flags": ([str], 'List of flags associated')
}]


@APIRouter('/cluster_conf', Scope.CONFIG_OPT)
@APIDoc("Manage Cluster Configurations", "ClusterConfiguration")
class ClusterConfiguration(RESTController):

    def _append_config_option_values(self, options):
        """
        Appends values from the config database (if available) to the given options
        :param options: list of config options
        :return: list of config options extended by their current values
        """
        config_dump = CephService.send_command('mon', 'config dump')
        mgr_config = mgr.get('config')
        config_dump.append({'name': 'fsid', 'section': 'mgr', 'value': mgr_config['fsid']})

        for config_dump_entry in config_dump:
            for i, elem in enumerate(options):
                if config_dump_entry['name'] == elem['name']:
                    if 'value' not in elem:
                        options[i]['value'] = []
                        options[i]['source'] = 'mon'

                    options[i]['value'].append({'section': config_dump_entry['section'],
                                                'value': config_dump_entry['value']})
        return options

    def list(self):
        options = mgr.get('config_options')['options']
        return self._append_config_option_values(options)

    def get(self, name):
        return self._get_config_option(name)

    @RESTController.Collection('GET', query_params=['name'])
    @EndpointDoc("Get Cluster Configuration by name",
                 parameters={
                     'names': (str, 'Config option names'),
                 },
                 responses={200: FILTER_SCHEMA})
    def filter(self, names=None):
        config_options = []

        if names:
            for name in names.split(','):
                try:
                    config_options.append(self._get_config_option(name))
                except cherrypy.HTTPError:
                    pass

        if not config_options:
            raise cherrypy.HTTPError(404, 'Config options `{}` not found'.format(names))

        return config_options

    @EndpointDoc("Create/Update Cluster Configuration",
                 parameters={
                     'name': Param(str, 'Config option name'),
                     'value': (
                         [
                            {
                                'section': Param(
                                    str, 'Section/Client where config needs to be updated'
                                ),
                                'value': Param(str, 'Value of the config option')
                            }
                         ], 'Section and Value of the config option'
                     ),
                     'force_update': Param(bool, 'Force update the config option', False, None)
                 }
                 )
    def create(self, name, value, force_update: Optional[bool] = None):
        # Check if config option is updateable at runtime
        self._updateable_at_runtime([name], force_update)

        for entry in value:
            section = entry['section']
            entry_value = entry['value']

            if entry_value not in (None, ''):
                CephService.send_command('mon', 'config set', who=section, name=name,
                                         value=str(entry_value))
            else:
                CephService.send_command('mon', 'config rm', who=section, name=name)

    def delete(self, name, section):
        return CephService.send_command('mon', 'config rm', who=section, name=name)

    def bulk_set(self, options):
        self._updateable_at_runtime(options.keys())

        for name, value in options.items():
            CephService.send_command('mon', 'config set', who=value['section'],
                                     name=name, value=str(value['value']))

    def _get_config_option(self, name):
        for option in mgr.get('config_options')['options']:
            if option['name'] == name:
                return self._append_config_option_values([option])[0]

        raise cherrypy.HTTPError(404)

    def _updateable_at_runtime(self, config_option_names, force_update=False):
        not_updateable = []

        for name in config_option_names:
            config_option = self._get_config_option(name)

            # making rgw configuration to be editable by bypassing 'can_update_at_runtime'
            # as the same can be done via CLI.
            if force_update and 'rgw' in name and not config_option['can_update_at_runtime']:
                break

            if force_update and 'rgw' not in name and not config_option['can_update_at_runtime']:
                raise DashboardException(
                    msg=f'Only the configuration containing "rgw" can be edited at runtime with'
                        f' force_update flag, hence not able to update "{name}"',
                    code='config_option_not_updatable_at_runtime',
                    component='cluster_configuration'
                )
            if not config_option['can_update_at_runtime']:
                not_updateable.append(name)

        if not_updateable:
            raise DashboardException(
                msg='Config option {} is/are not updatable at runtime'.format(
                    ', '.join(not_updateable)),
                code='config_option_not_updatable_at_runtime',
                component='cluster_configuration')