From a8a0b12495aff761e327645f2cce366a8d51967e Mon Sep 17 00:00:00 2001 From: Naman Munet Date: Fri, 22 Nov 2024 15:27:44 +0530 Subject: [PATCH] mgr/dashboard: Administration > Configuration > Some of the config options are not updatable at runtime Fixes: https://tracker.ceph.com/issues/68976 Fixes Includes: 1) by-passing 'can_update_at_runtime' flag for 'rgw' related configurations as the same can be updated at runtime via CLI. Also implemented a warning popup for user to make force edit to rgw related configurations. Signed-off-by: Naman Munet (cherry picked from commit 3181acc223dafd04e3fc56d418389ad50c5868e4) --- qa/tasks/mgr/dashboard/test_rbd.py | 12 ++++ .../controllers/cluster_configuration.py | 56 ++++++++++++++----- ...configuration-form-create-request.model.ts | 1 + .../configuration-form.component.html | 3 +- .../configuration-form.component.ts | 29 ++++++++-- .../configuration/configuration.component.ts | 8 ++- .../config-option/config-option.model.ts | 1 + src/pybind/mgr/dashboard/openapi.yaml | 20 ++++++- 8 files changed, 107 insertions(+), 23 deletions(-) diff --git a/qa/tasks/mgr/dashboard/test_rbd.py b/qa/tasks/mgr/dashboard/test_rbd.py index c2ffbd48e8a85..e5ccaaffedd4b 100644 --- a/qa/tasks/mgr/dashboard/test_rbd.py +++ b/qa/tasks/mgr/dashboard/test_rbd.py @@ -859,7 +859,19 @@ class RbdTest(DashboardTestCase): self.assertEqual(clone_format_version, 2) self.assertStatus(200) + # if empty list is sent, then the config will remain as it is value = [] + res = [{'section': "global", 'value': "2"}] + self._post('/api/cluster_conf', { + 'name': config_name, + 'value': value + }) + self.wait_until_equal( + lambda: _get_config_by_name(config_name), + res, + timeout=60) + + value = [{'section': "global", 'value': ""}] self._post('/api/cluster_conf', { 'name': config_name, 'value': value diff --git a/src/pybind/mgr/dashboard/controllers/cluster_configuration.py b/src/pybind/mgr/dashboard/controllers/cluster_configuration.py index da5be2cc81d6a..292f381d79f01 100644 --- a/src/pybind/mgr/dashboard/controllers/cluster_configuration.py +++ b/src/pybind/mgr/dashboard/controllers/cluster_configuration.py @@ -1,12 +1,14 @@ # -*- 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, RESTController +from . import APIDoc, APIRouter, EndpointDoc, Param, RESTController FILTER_SCHEMA = [{ "name": (str, 'Name of the config option'), @@ -80,22 +82,33 @@ class ClusterConfiguration(RESTController): return config_options - def create(self, name, value): + @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]) + self._updateable_at_runtime([name], force_update) - # Update config option - avail_sections = ['global', 'mon', 'mgr', 'osd', 'mds', 'client'] + for entry in value: + section = entry['section'] + entry_value = entry['value'] - for section in avail_sections: - for entry in value: - if entry['value'] is None: - break - - if entry['section'] == section: - CephService.send_command('mon', 'config set', who=section, name=name, - value=str(entry['value'])) - break + 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) @@ -116,11 +129,24 @@ class ClusterConfiguration(RESTController): raise cherrypy.HTTPError(404) - def _updateable_at_runtime(self, config_option_names): + 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) diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form-create-request.model.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form-create-request.model.ts index bca65a887c5d0..b6d23e35c9d02 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form-create-request.model.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form-create-request.model.ts @@ -1,4 +1,5 @@ export class ConfigFormCreateRequestModel { name: string; value: Array = []; + force_update: boolean = false; } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form.component.html index 741c18d52a610..a6775dcee1750 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form.component.html @@ -150,7 +150,7 @@ + diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form.component.ts index b6e9e700be464..f95479fc53e01 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form.component.ts @@ -13,6 +13,10 @@ import { CdForm } from '~/app/shared/forms/cd-form'; import { CdFormGroup } from '~/app/shared/forms/cd-form-group'; import { NotificationService } from '~/app/shared/services/notification.service'; import { ConfigFormCreateRequestModel } from './configuration-form-create-request.model'; +import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component'; +import { ModalService } from '~/app/shared/services/modal.service'; + +const RGW = 'rgw'; @Component({ selector: 'cd-configuration-form', @@ -29,13 +33,15 @@ export class ConfigurationFormComponent extends CdForm implements OnInit { maxValue: number; patternHelpText: string; availSections = ['global', 'mon', 'mgr', 'osd', 'mds', 'client']; + forceUpdate: boolean; constructor( public actionLabels: ActionLabelsI18n, private route: ActivatedRoute, private router: Router, private configService: ConfigurationService, - private notificationService: NotificationService + private notificationService: NotificationService, + private modalService: ModalService ) { super(); this.createForm(); @@ -95,7 +101,6 @@ export class ConfigurationFormComponent extends CdForm implements OnInit { setResponse(response: ConfigFormModel) { this.response = response; const validators = this.getValidators(response); - this.configForm.get('name').setValue(response.name); this.configForm.get('desc').setValue(response.desc); this.configForm.get('long_desc').setValue(response.long_desc); @@ -118,7 +123,7 @@ export class ConfigurationFormComponent extends CdForm implements OnInit { this.configForm.get('values').get(value.section).setValue(sectionValue); }); } - + this.forceUpdate = !this.response.can_update_at_runtime && response.name.includes(RGW); this.availSections.forEach((section) => { this.configForm.get('values').get(section).setValidators(validators); }); @@ -134,7 +139,7 @@ export class ConfigurationFormComponent extends CdForm implements OnInit { this.availSections.forEach((section) => { const sectionValue = this.configForm.getValue(section); - if (sectionValue !== null && sectionValue !== '') { + if (sectionValue !== null) { values.push({ section: section, value: sectionValue }); } }); @@ -143,12 +148,28 @@ export class ConfigurationFormComponent extends CdForm implements OnInit { const request = new ConfigFormCreateRequestModel(); request.name = this.configForm.getValue('name'); request.value = values; + if (this.forceUpdate) { + request.force_update = this.forceUpdate; + } return request; } return null; } + openCriticalConfirmModal() { + this.modalService.show(CriticalConfirmationModalComponent, { + buttonText: $localize`Force Edit`, + actionDescription: $localize`force edit`, + itemDescription: $localize`configuration`, + infoMessage: 'Updating this configuration might require restarting the client', + submitAction: () => { + this.modalService.dismissAll(); + this.submit(); + } + }); + } + submit() { const request = this.createRequest(); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration.component.ts index a57603d4c8aa5..d72b5922854e7 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration.component.ts @@ -12,6 +12,8 @@ import { CdTableSelection } from '~/app/shared/models/cd-table-selection'; import { Permission } from '~/app/shared/models/permissions'; import { AuthStorageService } from '~/app/shared/services/auth-storage.service'; +const RGW = 'rgw'; + @Component({ selector: 'cd-configuration', templateUrl: './configuration.component.html', @@ -143,7 +145,9 @@ export class ConfigurationComponent extends ListWithDetails implements OnInit { if (selection.selected.length !== 1) { return false; } - - return selection.selected[0].can_update_at_runtime; + if ((this.selection.selected[0].name as string).includes(RGW)) { + return true; + } + return this.selection.selected[0].can_update_at_runtime; } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/config-option/config-option.model.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/config-option/config-option.model.ts index d3ebc5f37c63d..0e1c0906f4ac1 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/config-option/config-option.model.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/config-option/config-option.model.ts @@ -9,4 +9,5 @@ export class ConfigFormModel { min: any; max: any; services: Array; + can_update_at_runtime: boolean; } diff --git a/src/pybind/mgr/dashboard/openapi.yaml b/src/pybind/mgr/dashboard/openapi.yaml index c368c878d7ec9..560ed870e6fe3 100644 --- a/src/pybind/mgr/dashboard/openapi.yaml +++ b/src/pybind/mgr/dashboard/openapi.yaml @@ -3885,10 +3885,27 @@ paths: application/json: schema: properties: + force_update: + description: Force update the config option + type: boolean name: + description: Config option name type: string value: - type: string + description: Section and Value of the config option + items: + properties: + section: + description: Section/Client where config needs to be updated + type: string + value: + description: Value of the config option + type: string + required: + - section + - value + type: object + type: array required: - name - value @@ -3915,6 +3932,7 @@ paths: trace. security: - jwt: [] + summary: Create/Update Cluster Configuration tags: - ClusterConfiguration put: -- 2.39.5