mirror of
https://github.com/ceph/ceph
synced 2024-12-29 15:03:33 +00:00
Merge pull request #60798 from rhcs-dashboard/edit-configuration-at-runtime
mgr/dashboard: Administration > Configuration > rgw_dns_name and rgw_dns_s3website_name not updatable via dashboard Reviewed-by: Afreen Misbah <afreen@ibm.com>
This commit is contained in:
commit
744eb5cdb3
@ -869,7 +869,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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -30,7 +30,6 @@ describe('Configuration page', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
configuration.clearTableSearchInput();
|
||||
configuration.getTableCount('found').as('configFound');
|
||||
});
|
||||
|
||||
after(() => {
|
||||
@ -50,6 +49,8 @@ describe('Configuration page', () => {
|
||||
});
|
||||
|
||||
it('should verify modified filter is applied properly', () => {
|
||||
configuration.clearFilter();
|
||||
configuration.getTableCount('found').as('configFound');
|
||||
configuration.filterTable('Modified', 'no');
|
||||
configuration.getTableCount('found').as('unmodifiedConfigs');
|
||||
|
||||
|
@ -12,7 +12,6 @@ export class ConfigurationPageHelper extends PageHelper {
|
||||
configClear(name: string) {
|
||||
this.navigateTo();
|
||||
const valList = ['global', 'mon', 'mgr', 'osd', 'mds', 'client']; // Editable values
|
||||
|
||||
this.getFirstTableCell(name).click();
|
||||
cy.contains('button', 'Edit').click();
|
||||
// Waits for the data to load
|
||||
@ -26,6 +25,8 @@ export class ConfigurationPageHelper extends PageHelper {
|
||||
|
||||
cy.wait(3 * 1000);
|
||||
|
||||
this.clearFilter();
|
||||
|
||||
// Enter config setting name into filter box
|
||||
this.searchTable(name, 100);
|
||||
|
||||
@ -49,6 +50,7 @@ export class ConfigurationPageHelper extends PageHelper {
|
||||
* Ex: [global, '2'] is the global value with an input of 2
|
||||
*/
|
||||
edit(name: string, ...values: [string, string][]) {
|
||||
this.clearFilter();
|
||||
this.getFirstTableCell(name).click();
|
||||
cy.contains('button', 'Edit').click();
|
||||
|
||||
@ -78,4 +80,12 @@ export class ConfigurationPageHelper extends PageHelper {
|
||||
cy.contains('[data-testid=config-details-table]', `${value[0]}\: ${value[1]}`);
|
||||
});
|
||||
}
|
||||
|
||||
clearFilter() {
|
||||
cy.get('div.filter-tags') // Find the div with class filter-tags
|
||||
.find('button.cds--btn.cds--btn--ghost') // Find the button with specific classes
|
||||
.contains('Clear filters') // Ensure the button contains the text "Clear filters"
|
||||
.should('be.visible') // Assert that the button is visible
|
||||
.click();
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
export class ConfigFormCreateRequestModel {
|
||||
name: string;
|
||||
value: Array<any> = [];
|
||||
force_update: boolean = false;
|
||||
}
|
||||
|
@ -150,7 +150,7 @@
|
||||
</div>
|
||||
<!-- Footer -->
|
||||
<div class="card-footer">
|
||||
<cd-form-button-panel (submitActionEvent)="submit()"
|
||||
<cd-form-button-panel (submitActionEvent)="forceUpdate ? openCriticalConfirmModal() : submit()"
|
||||
[form]="configForm"
|
||||
[submitText]="actionLabels.UPDATE"
|
||||
wrappingClass="text-right"></cd-form-button-panel>
|
||||
@ -158,3 +158,4 @@
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
@ -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 { ModalCdsService } from '~/app/shared/services/modal-cds.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: ModalCdsService
|
||||
) {
|
||||
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();
|
||||
|
||||
|
@ -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',
|
||||
@ -25,11 +27,27 @@ export class ConfigurationComponent extends ListWithDetails implements OnInit {
|
||||
columns: CdTableColumn[];
|
||||
selection = new CdTableSelection();
|
||||
filters: CdTableColumn[] = [
|
||||
{
|
||||
name: $localize`Modified`,
|
||||
prop: 'modified',
|
||||
filterOptions: [$localize`yes`, $localize`no`],
|
||||
filterInitValue: $localize`yes`,
|
||||
filterPredicate: (row, value) => {
|
||||
if (value === 'yes' && row.hasOwnProperty('value')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value === 'no' && !row.hasOwnProperty('value')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
},
|
||||
{
|
||||
name: $localize`Level`,
|
||||
prop: 'level',
|
||||
filterOptions: ['basic', 'advanced', 'dev'],
|
||||
filterInitValue: 'basic',
|
||||
filterPredicate: (row, value) => {
|
||||
enum Level {
|
||||
basic = 0,
|
||||
@ -60,22 +78,6 @@ export class ConfigurationComponent extends ListWithDetails implements OnInit {
|
||||
}
|
||||
return row.source.includes(value);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: $localize`Modified`,
|
||||
prop: 'modified',
|
||||
filterOptions: ['yes', 'no'],
|
||||
filterPredicate: (row, value) => {
|
||||
if (value === 'yes' && row.hasOwnProperty('value')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value === 'no' && !row.hasOwnProperty('value')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -9,4 +9,5 @@ export class ConfigFormModel {
|
||||
min: any;
|
||||
max: any;
|
||||
services: Array<string>;
|
||||
can_update_at_runtime: boolean;
|
||||
}
|
||||
|
@ -49,7 +49,7 @@
|
||||
[form]="deletionForm"
|
||||
[submitText]="(actionDescription | titlecase) + ' ' + itemDescription"
|
||||
[modalForm]="true"
|
||||
[submitBtnType]="actionDescription === 'delete' || 'remove' ? 'danger' : 'primary'"></cd-form-button-panel>
|
||||
[submitBtnType]="(actionDescription === 'delete' || actionDescription === 'remove') ? 'danger' : 'primary'"></cd-form-button-panel>
|
||||
|
||||
</cds-modal>
|
||||
|
||||
|
@ -3977,10 +3977,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
|
||||
@ -4007,6 +4024,7 @@ paths:
|
||||
trace.
|
||||
security:
|
||||
- jwt: []
|
||||
summary: Create/Update Cluster Configuration
|
||||
tags:
|
||||
- ClusterConfiguration
|
||||
put:
|
||||
|
Loading…
Reference in New Issue
Block a user