From 9e512e074c2d79010f212978f49d3ebdba7588d1 Mon Sep 17 00:00:00 2001 From: Dnyaneshwari Date: Wed, 4 Dec 2024 11:21:11 +0530 Subject: [PATCH] mgr/dashboard: delete smb cluster Fixes: https://tracker.ceph.com/issues/69191 https://tracker.ceph.com/issues/69605 Signed-off-by: Dnyaneshwari Talwekar --- src/pybind/mgr/dashboard/controllers/smb.py | 18 ++++++++ .../smb-cluster-list.component.html | 10 +++++ .../smb-cluster-list.component.ts | 43 ++++++++++++++++++- .../src/app/shared/api/smb.service.spec.ts | 6 +++ .../src/app/shared/api/smb.service.ts | 6 +++ .../shared/services/task-message.service.ts | 8 ++++ src/pybind/mgr/dashboard/openapi.yaml | 37 ++++++++++++++++ src/pybind/mgr/dashboard/tests/test_smb.py | 21 +++++++++ 8 files changed, 147 insertions(+), 2 deletions(-) diff --git a/src/pybind/mgr/dashboard/controllers/smb.py b/src/pybind/mgr/dashboard/controllers/smb.py index 97eff8c3dfec4..c171864145609 100644 --- a/src/pybind/mgr/dashboard/controllers/smb.py +++ b/src/pybind/mgr/dashboard/controllers/smb.py @@ -137,6 +137,24 @@ class SMBCluster(RESTController): except RuntimeError as e: raise DashboardException(e, component='smb') + @DeletePermission + @EndpointDoc("Remove an smb cluster", + parameters={ + 'cluster_id': (str, 'Unique identifier for the cluster')}, + responses={204: None}) + def delete(self, cluster_id: str): + """ + Remove an smb cluster + + :param cluster_id: Cluster identifier + :return: None. + """ + resource = {} + resource['resource_type'] = self._resource + resource['cluster_id'] = cluster_id + resource['intent'] = Intent.REMOVED + return mgr.remote('smb', 'apply_resources', json.dumps(resource)).one().to_simplified() + @APIRouter('/smb/share', Scope.SMB) @APIDoc("SMB Share Management API", "SMB") diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/smb/smb-cluster-list/smb-cluster-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/smb/smb-cluster-list/smb-cluster-list.component.html index 1a73e58cdd28f..cfd7b65dbf47d 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/smb/smb-cluster-list/smb-cluster-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/smb/smb-cluster-list/smb-cluster-list.component.html @@ -10,6 +10,16 @@ [hasDetails]="false" (setExpandedRow)="setExpandedRow($event)" (fetchData)="loadSMBCluster($event)" + (updateSelection)="updateSelection($event)" > +
+ + +
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/smb/smb-cluster-list/smb-cluster-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/smb/smb-cluster-list/smb-cluster-list.component.ts index bf61643a0cc77..aa603730bbd1c 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/smb/smb-cluster-list/smb-cluster-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/smb/smb-cluster-list/smb-cluster-list.component.ts @@ -15,6 +15,13 @@ import { Permission } from '~/app/shared/models/permissions'; import { AuthStorageService } from '~/app/shared/services/auth-storage.service'; import { SmbService } from '~/app/shared/api/smb.service'; import { SMBCluster } from '../smb.model'; +import { Icons } from '~/app/shared/enum/icons.enum'; +import { CdTableSelection } from '~/app/shared/models/cd-table-selection'; +import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; +import { ModalCdsService } from '~/app/shared/services/modal-cds.service'; +import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component'; +import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service'; +import { FinishedTask } from '~/app/shared/models/finished-task'; @Component({ selector: 'cd-smb-cluster-list', @@ -28,17 +35,28 @@ export class SmbClusterListComponent extends ListWithDetails implements OnInit { permission: Permission; tableActions: CdTableAction[]; context: CdTableFetchDataContext; - smbClusters$: Observable; subject$ = new BehaviorSubject([]); + selection = new CdTableSelection(); + modalRef: NgbModalRef; constructor( private authStorageService: AuthStorageService, public actionLabels: ActionLabelsI18n, - private smbService: SmbService + private smbService: SmbService, + private modalService: ModalCdsService, + private taskWrapper: TaskWrapperService ) { super(); this.permission = this.authStorageService.getPermissions().smb; + this.tableActions = [ + { + permission: 'delete', + icon: Icons.destroy, + click: () => this.removeSMBClusterModal(), + name: this.actionLabels.REMOVE + } + ]; } ngOnInit() { @@ -70,4 +88,25 @@ export class SmbClusterListComponent extends ListWithDetails implements OnInit { loadSMBCluster() { this.subject$.next([]); } + + updateSelection(selection: CdTableSelection) { + this.selection = selection; + } + + removeSMBClusterModal() { + const cluster_id = this.selection.first().cluster_id; + + this.modalService.show(CriticalConfirmationModalComponent, { + itemDescription: $localize`Cluster`, + itemNames: [cluster_id], + actionDescription: $localize`remove`, + submitActionObservable: () => + this.taskWrapper.wrapTaskAroundCall({ + task: new FinishedTask('smb/cluster/remove', { + cluster_id: cluster_id + }), + call: this.smbService.removeCluster(cluster_id) + }) + }); + } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/smb.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/smb.service.spec.ts index 2dcdbdb6402a9..f20977330d141 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/smb.service.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/smb.service.spec.ts @@ -28,4 +28,10 @@ describe('SmbService', () => { const req = httpTesting.expectOne('api/smb/cluster'); expect(req.request.method).toBe('GET'); }); + + it('should call remove', () => { + service.removeCluster('cluster_1').subscribe(); + const req = httpTesting.expectOne('api/smb/cluster/cluster_1'); + expect(req.request.method).toBe('DELETE'); + }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/smb.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/smb.service.ts index 4f4ebcb423c4b..ba640d11451f7 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/smb.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/smb.service.ts @@ -15,4 +15,10 @@ export class SmbService { listClusters(): Observable { return this.http.get(`${this.baseURL}/cluster`); } + + removeCluster(clusterId: string) { + return this.http.delete(`${this.baseURL}/cluster/${clusterId}`, { + observe: 'response' + }); + } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-message.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-message.service.ts index cf7662eac65e9..e0711ead95d88 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-message.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-message.service.ts @@ -396,6 +396,10 @@ export class TaskMessageService { 'nfs/delete': this.newTaskMessage(this.commonOperations.delete, (metadata) => this.nfs(metadata) ), + // smb + 'smb/cluster/remove': this.newTaskMessage(this.commonOperations.remove, (metadata) => + this.smb(metadata) + ), // Grafana tasks 'grafana/dashboards/update': this.newTaskMessage( this.commonOperations.update, @@ -539,6 +543,10 @@ export class TaskMessageService { }'`; } + smb(metadata: { cluster_id: string }) { + return $localize`SMB Cluster '${metadata.cluster_id}'`; + } + service(metadata: any) { return $localize`service '${metadata.service_name}'`; } diff --git a/src/pybind/mgr/dashboard/openapi.yaml b/src/pybind/mgr/dashboard/openapi.yaml index de83629806429..49b34ea520122 100644 --- a/src/pybind/mgr/dashboard/openapi.yaml +++ b/src/pybind/mgr/dashboard/openapi.yaml @@ -14313,6 +14313,43 @@ paths: tags: - SMB /api/smb/cluster/{cluster_id}: + delete: + description: "\n Remove an smb cluster\n\n :param cluster_id:\ + \ Cluster identifier\n :return: None.\n " + parameters: + - description: Unique identifier for the cluster + in: path + name: cluster_id + required: true + schema: + type: string + responses: + '202': + content: + application/vnd.ceph.api.v1.0+json: + type: object + description: Operation is still executing. Please check the task queue. + '204': + content: + application/vnd.ceph.api.v1.0+json: + schema: + properties: {} + type: object + description: Resource deleted. + '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: [] + summary: Remove an smb cluster + tags: + - SMB get: description: "\n Get an smb cluster by cluster id\n " parameters: diff --git a/src/pybind/mgr/dashboard/tests/test_smb.py b/src/pybind/mgr/dashboard/tests/test_smb.py index 754df482add98..9a577709d066d 100644 --- a/src/pybind/mgr/dashboard/tests/test_smb.py +++ b/src/pybind/mgr/dashboard/tests/test_smb.py @@ -121,6 +121,27 @@ class SMBClusterTest(ControllerTestCase): self.assertStatus(201) self.assertInJsonBody(json.dumps(self._clusters['resources'][1])) + def test_remove(self): + _res = { + "resource": { + "resource_type": "ceph.smb.cluster", + "cluster_id": "smbRemoveCluster", + "intent": "removed" + }, + "state": "removed", + "success": "true" + } + _res_simplified = { + "resource_type": "ceph.smb.cluster", + "cluster_id": "smbRemoveCluster", + "intent": "removed" + } + mgr.remote = Mock(return_value=Mock(return_value=_res)) + mgr.remote.return_value.one.return_value.to_simplified = Mock(return_value=_res_simplified) + self._delete(f'{self._endpoint}/smbRemoveCluster') + self.assertStatus(204) + mgr.remote.assert_called_once_with('smb', 'apply_resources', json.dumps(_res_simplified)) + class SMBShareTest(ControllerTestCase): _endpoint = '/api/smb/share' -- 2.39.5