result = multisite_instance.get_zonegroup(zonegroup_name)
return result
+ @Endpoint('DELETE', path='storage-class')
+ @DeletePermission
+ def remove_storage_class(self, placement_id: str, storage_class: str):
+ multisite_instance = RgwMultisite()
+ result = multisite_instance.delete_placement_targets(placement_id, storage_class)
+ return result
+
@Endpoint()
@ReadPermission
def get_all_zonegroups_info(self):
(setExpandedRow)="setExpandedRow($event)"
(updateSelection)="updateSelection($event)"
>
+ <div class="table-actions">
+ <cd-table-actions class="btn-group"
+ [permission]="permission"
+ [selection]="selection"
+ [tableActions]="tableActions">
+ </cd-table-actions>
+ </div>
<cd-rgw-storage-class-details *cdTableDetail
[selection]="expandedRow">
</cd-rgw-storage-class-details>
import { RgwStorageClassListComponent } from './rgw-storage-class-list.component';
import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
+import { SharedModule } from '~/app/shared/shared.module';
+import { ToastrModule } from 'ngx-toastr';
+import { RouterTestingModule } from '@angular/router/testing';
describe('RgwStorageClassListComponent', () => {
let component: RgwStorageClassListComponent;
beforeEach(async () => {
await TestBed.configureTestingModule({
- imports: [HttpClientTestingModule],
+ imports: [HttpClientTestingModule, SharedModule, ToastrModule.forRoot(), RouterTestingModule],
+ providers: [NgbActiveModal],
declarations: [RgwStorageClassListComponent]
}).compileComponents();
Target,
ZoneGroupDetails
} from '../models/rgw-storage-class.model';
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { Icons } from '~/app/shared/enum/icons.enum';
+import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
+import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
+import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
+import { FinishedTask } from '~/app/shared/models/finished-task';
+import { RgwStorageClassService } from '~/app/shared/api/rgw-storage-class.service';
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { Permission } from '~/app/shared/models/permissions';
@Component({
selector: 'cd-rgw-storage-class-list',
export class RgwStorageClassListComponent extends ListWithDetails implements OnInit {
columns: CdTableColumn[];
selection = new CdTableSelection();
+ permission: Permission;
tableActions: CdTableAction[];
storageClassList: StorageClass[] = [];
- constructor(private rgwZonegroupService: RgwZonegroupService) {
+ constructor(
+ private rgwZonegroupService: RgwZonegroupService,
+ public actionLabels: ActionLabelsI18n,
+ private cdsModalService: ModalCdsService,
+ private taskWrapper: TaskWrapperService,
+ private authStorageService: AuthStorageService,
+ private rgwStorageClassService: RgwStorageClassService
+ ) {
super();
+ this.permission = this.authStorageService.getPermissions().rgw;
}
ngOnInit() {
flexGrow: 2
}
];
+ this.tableActions = [
+ {
+ name: this.actionLabels.REMOVE,
+ permission: 'delete',
+ icon: Icons.destroy,
+ click: () => this.removeStorageClassModal()
+ }
+ ];
}
loadStorageClass(): Promise<void> {
};
}
+ removeStorageClassModal() {
+ const storage_class = this.selection.first().storage_class;
+ const placement_target = this.selection.first().placement_target;
+ this.cdsModalService.show(CriticalConfirmationModalComponent, {
+ itemDescription: $localize`Tiering Storage Class`,
+ itemNames: [storage_class],
+ actionDescription: 'remove',
+ submitActionObservable: () =>
+ 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)
+ })
+ });
+ }
+
updateSelection(selection: CdTableSelection) {
this.selection = selection;
}
--- /dev/null
+import { TestBed } from '@angular/core/testing';
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
+
+import { RgwStorageClassService } from './rgw-storage-class.service';
+import { configureTestBed } from '~/testing/unit-test-helper';
+
+describe('RgwStorageClassService', () => {
+ let service: RgwStorageClassService;
+ let httpTesting: HttpTestingController;
+
+ configureTestBed({
+ providers: [RgwStorageClassService],
+ imports: [HttpClientTestingModule]
+ });
+ beforeEach(() => {
+ TestBed.configureTestingModule({});
+ service = TestBed.inject(RgwStorageClassService);
+ httpTesting = TestBed.inject(HttpTestingController);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+
+ it('should call remove', () => {
+ service.removeStorageClass('default-placement', 'Cloud8ibm').subscribe();
+ const req = httpTesting.expectOne(
+ 'api/rgw/zonegroup/storage-class/default-placement/Cloud8ibm'
+ );
+ expect(req.request.method).toBe('DELETE');
+ });
+});
--- /dev/null
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+@Injectable({
+ providedIn: 'root'
+})
+export class RgwStorageClassService {
+ private url = 'api/rgw/zonegroup';
+
+ constructor(private http: HttpClient) {}
+
+ removeStorageClass(placement_target: string, storage_class: string) {
+ return this.http.delete(`${this.url}/storage-class/${placement_target}/${storage_class}`, {
+ observe: 'response'
+ });
+ }
+}
}`;
}
),
+ // storage-class
+ 'rgw/zonegroup/storage-class': this.newTaskMessage(this.commonOperations.remove, (metadata) =>
+ this.rgwStorageClass(metadata)
+ ),
// iSCSI target tasks
'iscsi/target/create': this.newTaskMessage(this.commonOperations.create, (metadata) =>
this.iscsiTarget(metadata)
return $localize`service '${metadata.service_name}'`;
}
+ rgwStorageClass(metadata: any) {
+ return $localize`Tiering Storage Class '${metadata.storage_class}'`;
+ }
+
crudMessage(metadata: any) {
let message = metadata.__message;
_.forEach(metadata, (value, key) => {
- jwt: []
tags:
- RgwZonegroup
+ /api/rgw/zonegroup/storage-class/{placement_id}/{storage_class}:
+ delete:
+ parameters:
+ - in: path
+ name: placement_id
+ required: true
+ schema:
+ type: string
+ - in: path
+ name: storage_class
+ 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:
+ 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: []
+ tags:
+ - RgwZonegroup
/api/rgw/zonegroup/{zonegroup_name}:
delete:
parameters:
raise DashboardException(error, http_status_code=500, component='rgw')
return out
+ # If realm list is empty restart RGW daemons else update the period.
+ def handle_rgw_realm(self):
+ rgw_realm_list = self.list_realms()
+ if len(rgw_realm_list['realms']) < 1:
+ rgw_service_manager = RgwServiceManager()
+ rgw_service_manager.restart_rgw_daemons_and_set_credentials()
+ else:
+ self.update_period()
+
def add_placement_targets(self, zonegroup_name: str, placement_targets: List[Dict]):
rgw_add_placement_cmd = ['zonegroup', 'placement', 'add']
for placement_target in placement_targets:
raise DashboardException(error, http_status_code=500, component='rgw')
self.update_period()
+ def delete_placement_targets(self, placement_id: str, storage_class: str):
+ rgw_zonegroup_delete_cmd = ['zonegroup', 'placement', 'rm',
+ '--placement-id', placement_id,
+ '--storage-class', storage_class]
+
+ try:
+ exit_code, _, err = mgr.send_rgwadmin_command(rgw_zonegroup_delete_cmd)
+ if exit_code > 0:
+ raise DashboardException(
+ e=err,
+ msg=(f'Unable to delete placement {placement_id} '
+ f'with storage-class {storage_class}'),
+ http_status_code=500,
+ component='rgw'
+ )
+ except SubprocessError as error:
+ raise DashboardException(error, http_status_code=500, component='rgw')
+
+ self.handle_rgw_realm()
+
# pylint: disable=W0102
def edit_zonegroup(self, realm_name: str, zonegroup_name: str, new_zonegroup_name: str,
default: str = '', master: str = '', endpoints: str = '',