From e8048004324fc19e34c94b771afb382c60679485 Mon Sep 17 00:00:00 2001 From: avanthakkar Date: Mon, 6 Mar 2023 15:45:45 +0530 Subject: [PATCH] mgr/dashboard: edit rgw-multisite Fixes: https://tracker.ceph.com/issues/59171 Signed-off-by: Avan Thakkar --- src/pybind/mgr/dashboard/controllers/rgw.py | 10 ++++ .../rgw-multisite-details.component.html | 15 +++++ .../rgw-multisite-details.component.scss | 4 ++ .../rgw-multisite-details.component.ts | 2 +- .../rgw-multisite-realm-form.component.ts | 55 ++++++++++++++----- .../src/app/shared/api/rgw-realm.service.ts | 14 ++++- .../src/app/shared/api/rgw-zone.service.ts | 3 +- .../app/shared/api/rgw-zonegroup.service.ts | 3 +- src/pybind/mgr/dashboard/openapi.yaml | 46 ++++++++++++++++ .../mgr/dashboard/requirements-test.txt | 2 +- .../mgr/dashboard/services/rgw_client.py | 46 +++++++++++++++- 11 files changed, 178 insertions(+), 22 deletions(-) diff --git a/src/pybind/mgr/dashboard/controllers/rgw.py b/src/pybind/mgr/dashboard/controllers/rgw.py index 909bdecba5ad0..d58dc309ea923 100644 --- a/src/pybind/mgr/dashboard/controllers/rgw.py +++ b/src/pybind/mgr/dashboard/controllers/rgw.py @@ -702,6 +702,16 @@ class RgwRealm(RESTController): except NoRgwDaemonsException as e: raise DashboardException(e, http_status_code=404, component='rgw') + @allow_empty_body + # pylint: disable=W0613 + def set(self, realm_name, default, new_realm_name, daemon_name=None): + try: + instance = RgwClient.admin_instance() + result = instance.edit_realm(realm_name, default, new_realm_name) + return result + except NoRgwDaemonsException as e: + raise DashboardException(e, http_status_code=404, component='rgw') + @APIRouter('/rgw/zonegroup', Scope.RGW) class RgwZonegroup(RESTController): diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.html index 0e2e2accee8b1..09e45a87e1b9a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.html @@ -26,6 +26,10 @@ {{ node.data.name }} + + {{ node.data.type }} + default @@ -34,6 +38,17 @@ *ngIf="node.data.is_master"> master +
+ +
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.scss index bbedb62466695..f38f00565d2aa 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.scss +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.scss @@ -3,3 +3,7 @@ .tree-container { height: calc(100vh - vv.$tree-container-height); } + +.align-inline-btns { + margin-left: 15em; +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.ts index f11c00d56659a..25d994cc2cad1 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.ts @@ -103,7 +103,7 @@ export class RgwMultisiteDetailsComponent implements OnDestroy, OnInit { } openModal(entity: any, edit = false) { - const entityName = edit ? entity.data.name : entity; + const entityName = edit ? entity.data.type : entity; const action = edit ? 'edit' : 'create'; const initialState = { resource: entityName, diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-realm-form/rgw-multisite-realm-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-realm-form/rgw-multisite-realm-form.component.ts index 18c52a56caa9b..0d7d4e732324f 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-realm-form/rgw-multisite-realm-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-realm-form/rgw-multisite-realm-form.component.ts @@ -17,12 +17,14 @@ import { RgwRealm } from '../models/rgw-multisite'; export class RgwMultisiteRealmFormComponent implements OnInit { action: string; multisiteRealmForm: CdFormGroup; + info: any; editing = false; resource: string; multisiteInfo: object[] = []; realm: RgwRealm; realmList: RgwRealm[] = []; realmNames: string[]; + newRealmName: string; constructor( public activeModal: NgbActiveModal, @@ -42,7 +44,11 @@ export class RgwMultisiteRealmFormComponent implements OnInit { validators: [ Validators.required, CdValidators.custom('uniqueName', (realmName: string) => { - return this.realmNames && this.realmNames.indexOf(realmName) !== -1; + return ( + this.action === 'create' && + this.realmNames && + this.realmNames.indexOf(realmName) !== -1 + ); }) ] }), @@ -58,23 +64,44 @@ export class RgwMultisiteRealmFormComponent implements OnInit { this.realmNames = this.realmList.map((realm) => { return realm['name']; }); + if (this.action === 'edit') { + this.multisiteRealmForm.get('realmName').setValue(this.info.data.name); + this.multisiteRealmForm.get('default_realm').setValue(this.info.data.is_default); + } } submit() { const values = this.multisiteRealmForm.value; this.realm = new RgwRealm(); - this.realm.name = values['realmName']; - this.rgwRealmService.create(this.realm, values['default_realm']).subscribe( - () => { - this.notificationService.show( - NotificationType.success, - $localize`Realm: '${values['realmName']}' created successfully` - ); - this.activeModal.close(); - }, - () => { - this.multisiteRealmForm.setErrors({ cdSubmitButton: true }); - } - ); + if (this.action === 'create') { + this.realm.name = values['realmName']; + this.rgwRealmService.create(this.realm, values['default_realm']).subscribe( + () => { + this.notificationService.show( + NotificationType.success, + $localize`Realm: '${values['realmName']}' created successfully` + ); + this.activeModal.close(); + }, + () => { + this.multisiteRealmForm.setErrors({ cdSubmitButton: true }); + } + ); + } else if (this.action === 'edit') { + this.realm.name = this.info.data.name; + this.newRealmName = values['realmName']; + this.rgwRealmService.update(this.realm, values['default_realm'], this.newRealmName).subscribe( + () => { + this.notificationService.show( + NotificationType.success, + $localize`Realm: '${values['realmName']}' updated successfully` + ); + this.activeModal.close(); + }, + () => { + this.multisiteRealmForm.setErrors({ cdSubmitButton: true }); + } + ); + } } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-realm.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-realm.service.ts index db6733b293fe6..4c9c80c83f4ea 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-realm.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-realm.service.ts @@ -23,6 +23,17 @@ export class RgwRealmService { }); } + update(realm: RgwRealm, defaultRealm: boolean, newRealmName: string) { + return this.rgwDaemonService.request((params: HttpParams) => { + params = params.appendAll({ + realm_name: realm.name, + default: defaultRealm, + new_realm_name: newRealmName + }); + return this.http.put(`${this.url}/${realm.name}`, null, { params: params }); + }); + } + list(): Observable { return this.rgwDaemonService.request(() => { return this.http.get(`${this.url}`); @@ -46,10 +57,11 @@ export class RgwRealmService { let realmIds = []; nodes['id'] = realm.id; realmIds.push(realm.id); - nodes['name'] = realm.name + ' (realm)'; + nodes['name'] = realm.name; nodes['info'] = realm; nodes['is_default'] = realm.id === defaultRealmId ? true : false; nodes['icon'] = Icons.reweight; + nodes['type'] = 'realm'; return { nodes: nodes, realmIds: realmIds diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-zone.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-zone.service.ts index b6c1d360a7a2b..707154f247535 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-zone.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-zone.service.ts @@ -57,13 +57,14 @@ export class RgwZoneService { let zoneIds = []; nodes['id'] = zone.id; zoneIds.push(zone.id); - nodes['name'] = zone.name + ' (zone)'; + nodes['name'] = zone.name; nodes['info'] = zone; nodes['icon'] = Icons.deploy; nodes['parent'] = zonegroup ? zonegroup.name : ''; nodes['second_parent'] = realm ? realm.name : ''; nodes['is_default'] = zone.id === defaultZoneId ? true : false; nodes['is_master'] = zonegroup && zonegroup.master_zone === zone.id ? true : false; + nodes['type'] = 'zone'; return { nodes: nodes, zoneIds: zoneIds diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-zonegroup.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-zonegroup.service.ts index b8839fa93fda7..08242d1613b1e 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-zonegroup.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-zonegroup.service.ts @@ -47,12 +47,13 @@ export class RgwZonegroupService { getZonegroupTree(zonegroup: RgwZonegroup, defaultZonegroupId: string, realm?: RgwRealm) { let nodes = {}; nodes['id'] = zonegroup.id; - nodes['name'] = zonegroup.name + ' (zonegroup)'; + nodes['name'] = zonegroup.name; nodes['info'] = zonegroup; nodes['icon'] = Icons.cubes; nodes['is_master'] = zonegroup.is_master; nodes['parent'] = realm ? realm.name : ''; nodes['is_default'] = zonegroup.id === defaultZonegroupId ? true : false; + nodes['type'] = 'zonegroup'; return nodes; } } diff --git a/src/pybind/mgr/dashboard/openapi.yaml b/src/pybind/mgr/dashboard/openapi.yaml index 33031a94f1373..a48600ff0e205 100644 --- a/src/pybind/mgr/dashboard/openapi.yaml +++ b/src/pybind/mgr/dashboard/openapi.yaml @@ -8302,6 +8302,52 @@ paths: - jwt: [] tags: - RgwRealm + put: + parameters: + - in: path + name: realm_name + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + properties: + daemon_name: + type: string + default: + type: string + new_realm_name: + type: string + required: + - default + - new_realm_name + type: object + responses: + '200': + content: + application/vnd.ceph.api.v1.0+json: + type: object + description: Resource updated. + '202': + content: + application/vnd.ceph.api.v1.0+json: + type: object + description: Operation is still executing. Please check the task queue. + '400': + description: Operation exception. Please check the response body for details. + '401': + description: Unauthenticated access. Please login first. + '403': + description: Unauthorized access. Please check your permissions. + '500': + description: Unexpected error. Please check the response body for the stack + trace. + security: + - jwt: [] + tags: + - RgwRealm /api/rgw/site: get: parameters: diff --git a/src/pybind/mgr/dashboard/requirements-test.txt b/src/pybind/mgr/dashboard/requirements-test.txt index 4e925e8616f1a..d2566bab59f61 100644 --- a/src/pybind/mgr/dashboard/requirements-test.txt +++ b/src/pybind/mgr/dashboard/requirements-test.txt @@ -1,4 +1,4 @@ pytest-cov pytest-instafail pyfakefs==4.5.0 -jsonschema==4.16.0 +jsonschema diff --git a/src/pybind/mgr/dashboard/services/rgw_client.py b/src/pybind/mgr/dashboard/services/rgw_client.py index d4008faacb33c..5a022d0998e45 100644 --- a/src/pybind/mgr/dashboard/services/rgw_client.py +++ b/src/pybind/mgr/dashboard/services/rgw_client.py @@ -17,7 +17,7 @@ from ..awsauth import S3Auth from ..exceptions import DashboardException from ..rest_client import RequestException, RestClient from ..settings import Settings -from ..tools import dict_contains_path, dict_get, json_str_to_object +from ..tools import dict_contains_path, dict_get, json_str_to_object, str_to_bool try: from typing import Any, Dict, List, Optional, Tuple, Union @@ -660,6 +660,47 @@ class RgwClient(RestClient): except SubprocessError as error: raise DashboardException(error, http_status_code=500, component='rgw') + def edit_realm(self, realm_name: str, default: bool, new_realm_name: str): + rgw_realm_edit_cmd = [] + cmd_edit_realm_options = [] + if new_realm_name == realm_name: + if str_to_bool(default): + rgw_realm_edit_cmd = ['realm', 'default'] + cmd_edit_realm_options = ['--rgw-realm', realm_name] + rgw_realm_edit_cmd += cmd_edit_realm_options + try: + exit_code, _, err = mgr.send_rgwadmin_command(rgw_realm_edit_cmd) + if exit_code > 0: + raise DashboardException(e=err, msg='Unable to set {} as default realm'.format(realm_name), # noqa E501 #pylint: disable=line-too-long + http_status_code=500, component='rgw') + except SubprocessError as error: + raise DashboardException(error, http_status_code=500, component='rgw') + else: + raise DashboardException(msg='The realm already exists', + http_status_code=400, component='rgw') + else: + rgw_realm_edit_cmd = ['realm', 'rename'] + cmd_edit_realm_options = ['--rgw-realm', realm_name, '--realm-new-name', new_realm_name] + rgw_realm_edit_cmd += cmd_edit_realm_options + try: + exit_code, _, err = mgr.send_rgwadmin_command(rgw_realm_edit_cmd) + if exit_code > 0: + raise DashboardException(e=err, msg='Unable to edit realm', + http_status_code=500, component='rgw') + except ValueError: + pass + + if str_to_bool(default): + rgw_realm_edit_cmd = ['realm', 'default'] + cmd_edit_realm_options = ['--rgw-realm', new_realm_name] + try: + exit_code, _, _ = mgr.send_rgwadmin_command(rgw_realm_edit_cmd) + if exit_code > 0: + raise DashboardException(msg='Unable to set {} as default realm'.format(new_realm_name), # noqa E501 #pylint: disable=line-too-long + http_status_code=500, component='rgw') + except SubprocessError as error: + raise DashboardException(error, http_status_code=500, component='rgw') + def create_zonegroup(self, realm_name: str, zonegroup_name: str, default: bool, master: bool, endpoints: List[str]): rgw_zonegroup_create_cmd = ['zonegroup', 'create'] @@ -686,7 +727,6 @@ class RgwClient(RestClient): http_status_code=500, component='rgw') except SubprocessError as error: raise DashboardException(error, http_status_code=500, component='rgw') - self.update_period() return out def list_zonegroups(self): @@ -724,7 +764,7 @@ class RgwClient(RestClient): for rgw_zonegroup in rgw_zonegroup_list['zonegroups']: zonegroup_info = self.get_zonegroup(rgw_zonegroup) zonegroups_info.append(zonegroup_info) - all_zonegroups_info['zonegroups'] = zonegroups_info # type: ignore + all_zonegroups_info['zonegroups'] = zonegroups_info # type: ignore else: all_zonegroups_info['zonegroups'] = [] # type: ignore if 'default_info' in rgw_zonegroup_list and rgw_zonegroup_list['default_info'] != '': -- 2.39.5