]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: [storage-class]: Deleting local storage class from UI does not remove... 67949/head
authorDnyaneshwari Talwekar <dtalwekar@li-4c4c4544-0038-3510-8056-b5c04f473234.ibm.com>
Tue, 17 Mar 2026 05:00:13 +0000 (10:30 +0530)
committerDnyaneshwari Talwekar <dtalwekar@li-4c4c4544-0038-3510-8056-b5c04f473234.ibm.com>
Mon, 23 Mar 2026 10:04:38 +0000 (15:34 +0530)
Fixes: https://tracker.ceph.com/issues/75541
Signed-off-by: Dnyaneshwari Talwekar <dtalwekar@redhat.com>
(cherry picked from commit 50c25d9514170d49cf92fd6a7736169bbd9124cc)

src/pybind/mgr/dashboard/controllers/rgw.py
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-list/rgw-storage-class-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-storage-class.service.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-storage-class.service.ts
src/pybind/mgr/dashboard/openapi.yaml
src/pybind/mgr/dashboard/services/rgw_client.py

index ec1ed24d540f6d725aae043b750125e3a7392863..7fef9480043caf97f6ecbce9ac507325303ba8a9 100755 (executable)
@@ -1432,9 +1432,9 @@ class RgwZonegroup(RESTController):
 
     @Endpoint('DELETE', path='storage-class')
     @DeletePermission
-    def remove_storage_class(self, placement_id: str, storage_class: str):
+    def remove_storage_class(self, placement_id: str, storage_class: str, zone_name: str = ''):
         multisite_instance = RgwMultisite()
-        result = multisite_instance.delete_placement_targets(placement_id, storage_class)
+        result = multisite_instance.delete_placement_targets(placement_id, storage_class, zone_name)
         return result
 
     @Endpoint('POST', path='storage-class')
index 023de5232e8d22627fc2ba9eb63121ac3553fc1e..92d9c5832a483c5bd2f6bf1e1061152cdbbe5df8 100644 (file)
@@ -5,6 +5,7 @@ import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
 
 import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
 import {
+  AllZonesResponse,
   StorageClass,
   TIER_TYPE,
   TIER_TYPE_DISPLAY,
@@ -14,6 +15,7 @@ import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
 import { FinishedTask } from '~/app/shared/models/finished-task';
 import { Icons } from '~/app/shared/enum/icons.enum';
 import { RgwZonegroupService } from '~/app/shared/api/rgw-zonegroup.service';
+import { RgwZoneService } from '~/app/shared/api/rgw-zone.service';
 import { DeleteConfirmationModalComponent } from '~/app/shared/components/delete-confirmation-modal/delete-confirmation-modal.component';
 import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
@@ -23,8 +25,8 @@ import { URLBuilderService } from '~/app/shared/services/url-builder.service';
 import { Permission } from '~/app/shared/models/permissions';
 import { BucketTieringUtils } from '../utils/rgw-bucket-tiering';
 import { Router } from '@angular/router';
-import { Observable, Subscriber } from 'rxjs';
 import { TableComponent } from '~/app/shared/datatable/table/table.component';
+import { finalize, switchMap } from 'rxjs/operators';
 
 const BASE_URL = 'rgw/storage-class';
 @Component({
@@ -44,6 +46,7 @@ export class RgwStorageClassListComponent extends ListWithDetails implements OnI
 
   constructor(
     private rgwZonegroupService: RgwZonegroupService,
+    private rgwZoneService: RgwZoneService,
     public actionLabels: ActionLabelsI18n,
     private cdsModalService: ModalCdsService,
     private taskWrapper: TaskWrapperService,
@@ -133,7 +136,10 @@ export class RgwStorageClassListComponent extends ListWithDetails implements OnI
           const tierObj = BucketTieringUtils.filterAndMapTierTargets(data);
           const tierConfig = tierObj.map((tier) => ({
             ...tier,
-            tier_type: this.mapTierTypeDisplay(tier.tier_type)
+            tier_type: this.mapTierTypeDisplay(tier.tier_type),
+            storageClass: tier.storage_class,
+            placementTarget: tier.placement_target,
+            tierType: this.mapTierTypeDisplay(tier.tier_type)
           }));
 
           this.transformTierData(tierConfig);
@@ -171,32 +177,39 @@ export class RgwStorageClassListComponent extends ListWithDetails implements OnI
   }
 
   removeStorageClassModal() {
-    const storage_class = this.selection.first().storage_class;
-    const placement_target = this.selection.first().placement_target;
+    const selectedItem = this.selection.first();
+    const { storageClass, placementTarget, tierType } = selectedItem;
+    const isLocalStorageClass =
+      tierType?.toLowerCase() === TIER_TYPE.LOCAL || tierType === TIER_TYPE_DISPLAY.LOCAL;
+
     this.cdsModalService.show(DeleteConfirmationModalComponent, {
       itemDescription: $localize`Storage class`,
-      itemNames: [storage_class],
+      itemNames: [storageClass],
       actionDescription: 'remove',
       submitActionObservable: () => {
-        return new Observable((observer: Subscriber<any>) => {
-          this.taskWrapper
-            .wrapTaskAroundCall({
-              task: new FinishedTask('rgw/zonegroup/storage-class', {
-                placement_target: placement_target,
-                storage_class: storage_class
-              }),
-              call: this.rgwStorageClassService.removeStorageClass(placement_target, storage_class)
-            })
-            .subscribe({
-              error: (error: any) => {
-                observer.error(error);
-              },
-              complete: () => {
-                observer.complete();
-                this.table.refreshBtn();
-              }
-            });
-        });
+        // For local storage classes, delete from both zone and zonegroup
+        const deleteObservable$ = isLocalStorageClass
+          ? this.rgwZoneService.getAllZonesInfo().pipe(
+              switchMap((data: AllZonesResponse) => {
+                const zoneInfo = BucketTieringUtils.getZoneInfoHelper(data.zones, selectedItem);
+                return this.rgwStorageClassService.removeStorageClass(
+                  placementTarget,
+                  storageClass,
+                  zoneInfo.zone_name || ''
+                );
+              })
+            )
+          : this.rgwStorageClassService.removeStorageClass(placementTarget, storageClass);
+
+        return this.taskWrapper
+          .wrapTaskAroundCall({
+            task: new FinishedTask('rgw/zonegroup/storage-class', {
+              placementTarget,
+              storageClass
+            }),
+            call: deleteObservable$
+          })
+          .pipe(finalize(() => this.table.refreshBtn()));
       }
     });
   }
index 06d7b7c2133c0d4981b37e9fceb7b3dc5449a410..49966b31cdee78733ab1181ff8290f32c58fca00 100644 (file)
@@ -30,6 +30,16 @@ describe('RgwStorageClassService', () => {
       'api/rgw/zonegroup/storage-class/default-placement/Cloud8ibm'
     );
     expect(req.request.method).toBe('DELETE');
+    expect(req.request.params.has('zone_name')).toBe(false);
+  });
+
+  it('should call remove with zone_name query param when provided', () => {
+    service.removeStorageClass('default-placement', 'Cloud8ibm', 'us-east-1').subscribe();
+    const req = httpTesting.expectOne(
+      'api/rgw/zonegroup/storage-class/default-placement/Cloud8ibm?zone_name=us-east-1'
+    );
+    expect(req.request.method).toBe('DELETE');
+    expect(req.request.params.get('zone_name')).toBe('us-east-1');
   });
 
   it('should call create', () => {
index cff162286f97a9f93b46608cf213c443410a816c..495daba3686000f60167819f3df12578c448c01c 100644 (file)
@@ -12,8 +12,13 @@ export class RgwStorageClassService {
 
   constructor(private http: HttpClient) {}
 
-  removeStorageClass(placement_target: string, storage_class: string) {
+  removeStorageClass(placement_target: string, storage_class: string, zone_name: string = '') {
+    let params: any = {};
+    if (zone_name) {
+      params.zone_name = zone_name;
+    }
     return this.http.delete(`${this.url}/${placement_target}/${storage_class}`, {
+      params,
       observe: 'response'
     });
   }
index dec615b3d053da5f04b8b4c1ef4aab5c8ca272a4..cf078a083e8f14b003f9135a305d2987a4c50012 100755 (executable)
@@ -16460,6 +16460,11 @@ paths:
         required: true
         schema:
           type: string
+      - default: ''
+        in: query
+        name: zone_name
+        schema:
+          type: string
       responses:
         '202':
           content:
index 5355a9ef18562530c58871c469a826413bf7faac..f7325c4e6d3152a60745c22df72b798fb042db32 100755 (executable)
@@ -2300,7 +2300,28 @@ class RgwMultisite:
                         if tier_type in CLOUD_S3_TIER_TYPES:
                             self.ensure_realm_and_sync_period()
 
-    def delete_placement_targets(self, placement_id: str, storage_class: str):
+    def delete_placement_targets(self, placement_id: str, storage_class: str,
+                                 zone_name: str = ''):
+        # Delete from zone first if zone_name is provided
+        if zone_name:
+            rgw_zone_delete_cmd = ['zone', 'placement', 'rm', '--rgw-zone', zone_name,
+                                   '--placement-id', placement_id,
+                                   '--storage-class', storage_class]
+
+            try:
+                exit_code, _, err = mgr.send_rgwadmin_command(rgw_zone_delete_cmd)
+                if exit_code > 0:
+                    raise DashboardException(
+                        e=err,
+                        msg=(f'Unable to delete placement {placement_id} '
+                             f'with storage-class {storage_class} from zone {zone_name}'),
+                        http_status_code=500,
+                        component='rgw'
+                    )
+            except SubprocessError as error:
+                raise DashboardException(error, http_status_code=500, component='rgw')
+
+        # Delete from zonegroup
         rgw_zonegroup_delete_cmd = ['zonegroup', 'placement', 'rm',
                                     '--placement-id', placement_id,
                                     '--storage-class', storage_class]