From a2ca1d6c845dc48b280b4166ec235410dde41db1 Mon Sep 17 00:00:00 2001 From: Naman Munet Date: Wed, 19 Feb 2025 20:44:26 +0530 Subject: [PATCH] mgr/dashboard: disable deleting bucket with objects Fixes: https://tracker.ceph.com/issues/70078 Signed-off-by: Naman Munet (cherry picked from commit 11677c29ee6ee60d9191edfdbfbe37b5308eb45e) Conflicts: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.ts --- src/pybind/mgr/dashboard/controllers/rgw.py | 17 ++++++++++++----- .../rgw-bucket-list.component.ts | 10 ++++++++++ .../app/shared/api/rgw-bucket.service.spec.ts | 14 ++------------ .../src/app/shared/api/rgw-bucket.service.ts | 3 +-- src/pybind/mgr/dashboard/openapi.yaml | 5 ----- 5 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/pybind/mgr/dashboard/controllers/rgw.py b/src/pybind/mgr/dashboard/controllers/rgw.py index f2c6acf9f46..46c192323f9 100644 --- a/src/pybind/mgr/dashboard/controllers/rgw.py +++ b/src/pybind/mgr/dashboard/controllers/rgw.py @@ -457,11 +457,18 @@ class RgwBucket(RgwRESTController): self._set_acl(bucket_name, canned_acl, uid, daemon_name) return self._append_bid(result) - def delete(self, bucket, purge_objects='true', daemon_name=None): - return self.proxy(daemon_name, 'DELETE', 'bucket', { - 'bucket': bucket, - 'purge-objects': purge_objects - }, json_response=False) + def delete(self, bucket, daemon_name=None): + try: + bucket_info = self.proxy(daemon_name, 'GET', 'bucket', {'bucket': bucket}) + num_objects = bucket_info.get('usage', {}).get('rgw.main', {}).get('num_objects', 0) + if num_objects > 0: + raise DashboardException(msg='Unable to delete bucket"{}" - Bucket is not empty. ' + 'Remove all objects before deletion.'.format(bucket)) + return self.proxy(daemon_name, 'DELETE', 'bucket', { + 'bucket': bucket + }, json_response=False) + except (DashboardException, RequestException) as e: # pragma: no cover + raise DashboardException(e, component='rgw') @RESTController.Collection(method='PUT', path='/setEncryptionConfig') @allow_empty_body diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.ts index 23f77001ecb..3ecb85316c0 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.ts @@ -117,12 +117,22 @@ export class RgwBucketListComponent extends ListWithDetails implements OnInit, O permission: 'delete', icon: Icons.destroy, click: () => this.deleteAction(), + disable: () => this.isDeleteDisabled(), name: this.actionLabels.DELETE }; this.tableActions = [addAction, editAction, deleteAction]; this.setTableRefreshTimeout(); } + isDeleteDisabled(): boolean | string { + if (!this.selection.first()) { + return true; + } + return this.selection.first()?.num_objects > 0 + ? $localize`Bucket is not empty. Remove all objects before deletion.` + : false; + } + getBucketList(context: CdTableFetchDataContext) { this.setTableRefreshTimeout(); this.subs.add( diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.spec.ts index eaed2c4abac..6481bb2041d 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.spec.ts @@ -97,19 +97,9 @@ describe('RgwBucketService', () => { expect(req.request.method).toBe('PUT'); }); - it('should call delete, with purgeObjects = true', () => { + it('should call delete', () => { service.delete('foo').subscribe(); - const req = httpTesting.expectOne( - `api/rgw/bucket/foo?${RgwHelper.DAEMON_QUERY_PARAM}&purge_objects=true` - ); - expect(req.request.method).toBe('DELETE'); - }); - - it('should call delete, with purgeObjects = false', () => { - service.delete('foo', false).subscribe(); - const req = httpTesting.expectOne( - `api/rgw/bucket/foo?${RgwHelper.DAEMON_QUERY_PARAM}&purge_objects=false` - ); + const req = httpTesting.expectOne(`api/rgw/bucket/foo?${RgwHelper.DAEMON_QUERY_PARAM}`); expect(req.request.method).toBe('DELETE'); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.ts index 8dbebff1f61..0d17c7abdd4 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.ts @@ -184,9 +184,8 @@ export class RgwBucketService extends ApiClient { }); } - delete(bucket: string, purgeObjects = true) { + delete(bucket: string) { return this.rgwDaemonService.request((params: HttpParams) => { - params = params.append('purge_objects', purgeObjects ? 'true' : 'false'); return this.http.delete(`${this.url}/${bucket}`, { params: params }); }); } diff --git a/src/pybind/mgr/dashboard/openapi.yaml b/src/pybind/mgr/dashboard/openapi.yaml index 560ed870e6f..14ad6c9e306 100644 --- a/src/pybind/mgr/dashboard/openapi.yaml +++ b/src/pybind/mgr/dashboard/openapi.yaml @@ -9759,11 +9759,6 @@ paths: required: true schema: type: string - - default: 'true' - in: query - name: purge_objects - schema: - type: string - allowEmptyValue: true in: query name: daemon_name