From 93fba05e46c9f74a61b182eba2e499a8d0559093 Mon Sep 17 00:00:00 2001 From: Dnyaneshwari Date: Tue, 11 Feb 2025 11:59:38 +0530 Subject: [PATCH] mg/dashboard: Edit Storage Class Fixes: https://tracker.ceph.com/issues/70016 Signed-off-by: Dnyaneshwari Talwekar --- src/pybind/mgr/dashboard/controllers/rgw.py | 15 + .../rgw/models/rgw-storage-class.model.ts | 6 +- .../rgw-storage-class-form.component.html | 609 +++++++++--------- .../rgw-storage-class-form.component.spec.ts | 2 +- .../rgw-storage-class-form.component.ts | 120 +++- .../rgw-storage-class-list.component.html | 5 + .../rgw-storage-class-list.component.ts | 22 +- .../frontend/src/app/ceph/rgw/rgw.module.ts | 5 + .../shared/api/rgw-storage-class.service.ts | 11 +- src/pybind/mgr/dashboard/openapi.yaml | 66 ++ .../mgr/dashboard/services/rgw_client.py | 102 ++- 11 files changed, 600 insertions(+), 363 deletions(-) diff --git a/src/pybind/mgr/dashboard/controllers/rgw.py b/src/pybind/mgr/dashboard/controllers/rgw.py index ef8903afb08..e315d38732f 100755 --- a/src/pybind/mgr/dashboard/controllers/rgw.py +++ b/src/pybind/mgr/dashboard/controllers/rgw.py @@ -1182,6 +1182,13 @@ class RgwZonegroup(RESTController): result = multisite_instance.get_zonegroup(zonegroup_name) return result + @Endpoint('GET') + @ReadPermission + def get_placement_target_by_placement_id(self, placement_id): + multisite_instance = RgwMultisite() + result = multisite_instance.get_placement_by_placement_id(placement_id) + return result + @Endpoint('DELETE', path='storage-class') @DeletePermission def remove_storage_class(self, placement_id: str, storage_class: str): @@ -1197,6 +1204,14 @@ class RgwZonegroup(RESTController): result = multisite_instance.add_placement_targets(zone_group, placement_targets) return result + @Endpoint('PUT', path='storage-class') + @CreatePermission + # pylint: disable=W0102 + def editStorageClass(self, zone_group, placement_targets: List[Dict[str, str]] = []): + multisite_instance = RgwMultisite() + result = multisite_instance.modify_placement_targets(zone_group, placement_targets) + return result + @Endpoint() @ReadPermission def get_all_zonegroups_info(self): diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-storage-class.model.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-storage-class.model.ts index 0e671db7499..85b0bf75d0a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-storage-class.model.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-storage-class.model.ts @@ -9,6 +9,7 @@ export interface StorageClass { endpoint: string; region: string; placement_target: string; + zonegroup_name?: string; } export interface StorageClassDetails { @@ -71,6 +72,7 @@ export interface S3Details { multipart_min_part_size: number; multipart_sync_threshold: number; host_style: boolean; + retain_head_object?: boolean; } export interface RequestModel { @@ -81,7 +83,6 @@ export interface RequestModel { export interface PlacementTarget { tags: string[]; placement_id: string; - storage_class: string; tier_type: typeof CLOUD_TIER; tier_config: { endpoint: string; @@ -93,6 +94,9 @@ export interface PlacementTarget { multipart_sync_threshold: number; multipart_min_part_size: number; }; + storage_class?: string; + name?: string; + tier_targets?: TierTarget[]; } export const CLOUD_TIER = 'cloud-s3'; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-form/rgw-storage-class-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-form/rgw-storage-class-form.component.html index 6d38088d627..35710457f44 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-form/rgw-storage-class-form.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-form/rgw-storage-class-form.component.html @@ -1,356 +1,351 @@
-
-
- {{ action | titlecase }} {{ resource | upperFirst }} -
- - - All fields are required, except where marked optional. - - -
-
- - - - - - This field is required. - + + +
+ {{ action | titlecase }} {{ resource | upperFirst }}
-
- - - - - - - This field is required. + + All fields are required, except where marked optional. + + +
+
+ + - + + + + This field is required. + +
+
+ + + + + + + This field is required. + +
-
- -
- Storage Class Name - - - - This field is required. - -
-
-
- + +
Target Region + [invalidText]="storageError" + >Storage Class Name - + This field is required.
-
- - Target Endpoint - +
+ + - - - Target Region + + + + This field is required. + +
+
+ + This field is required. - + [invalid]=" + storageClassForm.showError('endpoint', formDir, 'required') + " + [invalidText]="endpointError" + [helperText]="targetEndpointText" + >Target Endpoint + + + + This field is required. + +
-
- -
-
- Target Access Key - +
+
+ - - - - This field is required. - + >Target Access Key + + + + + This field is required. + +
+
+ + +
+
+ Target Secret Key + + + + + This field is required. + +
-
- -
-
- +
+ Target Secret Key + [invalidText]="targetError" + [helperText]="targetPathText" + >Target Path - - - + + This field is required.
-
- -
- Target Path - - - - This field is required. - -
- -
- - - -
-
- Multipart Sync Threshold - - +
+ + + +
+
+ Multipart Sync Threshold + + +
+
+ Multipart Minimum Part Size + + +
-
- Multipart Minimum Part Size - - +
+ Retain Head Object + {{ retainHeadObjectText }} +
-
-
- Retain Head Object - {{ retainHeadObjectText }} - -
-
-
- -
Advanced
-
-
- - RGW service would be restarted after creating the storage class. - - - + + + +
Advanced
+
+
+ + RGW service would be restarted after creating the storage class. + + + +
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-form/rgw-storage-class-form.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-form/rgw-storage-class-form.component.spec.ts index 2f1c43ca6c1..c6582e46af2 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-form/rgw-storage-class-form.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-form/rgw-storage-class-form.component.spec.ts @@ -56,7 +56,7 @@ describe('RgwStorageClassFormComponent', () => { }); it('on zonegroup changes', () => { - component.zoneGroupDeatils = { + component.zoneGroupDetails = { default_zonegroup: 'zonegroup1', name: 'zonegrp1', zonegroups: [ diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-form/rgw-storage-class-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-form/rgw-storage-class-form.component.ts index ed4677035f3..d380f08bcc0 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-form/rgw-storage-class-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-form/rgw-storage-class-form.component.ts @@ -1,18 +1,21 @@ import { Component, OnInit } from '@angular/core'; import { FormControl, Validators } from '@angular/forms'; -import { ActionLabelsI18n } from '~/app/shared/constants/app.constants'; +import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants'; import { CdForm } from '~/app/shared/forms/cd-form'; import { CdFormBuilder } from '~/app/shared/forms/cd-form-builder'; import { CdFormGroup } from '~/app/shared/forms/cd-form-group'; import _ from 'lodash'; -import { Router } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { RgwStorageClassService } from '~/app/shared/api/rgw-storage-class.service'; import { RgwZonegroupService } from '~/app/shared/api/rgw-zonegroup.service'; import { CLOUD_TIER, DEFAULT_PLACEMENT, + PlacementTarget, RequestModel, + StorageClass, Target, + TierTarget, ZoneGroup, ZoneGroupDetails } from '../models/rgw-storage-class.model'; @@ -28,6 +31,7 @@ export class RgwStorageClassFormComponent extends CdForm implements OnInit { storageClassForm: CdFormGroup; action: string; resource: string; + editing: boolean; targetPathText: string; targetEndpointText: string; targetRegionText: string; @@ -39,10 +43,12 @@ export class RgwStorageClassFormComponent extends CdForm implements OnInit { multipartSyncThreholdText: string; selectedZoneGroup: string; defaultZonegroup: ZoneGroup; - zoneGroupDeatils: ZoneGroupDetails; + zoneGroupDetails: ZoneGroupDetails; targetSecretKeyText: string; targetAccessKeyText: string; retainHeadObjectText: string; + storageClassInfo: StorageClass; + tierTargetInfo: TierTarget; constructor( public actionLabels: ActionLabelsI18n, @@ -50,10 +56,13 @@ export class RgwStorageClassFormComponent extends CdForm implements OnInit { private notificationService: NotificationService, private rgwStorageService: RgwStorageClassService, private rgwZoneGroupService: RgwZonegroupService, - private router: Router + private router: Router, + private route: ActivatedRoute ) { super(); this.resource = $localize`Tiering Storage Class`; + this.editing = this.router.url.startsWith(`/rgw/tiering/${URLVerbs.EDIT}`); + this.action = this.editing ? this.actionLabels.EDIT : this.actionLabels.CREATE; } ngOnInit() { @@ -72,9 +81,45 @@ export class RgwStorageClassFormComponent extends CdForm implements OnInit { "To view or copy your secret key, go to your cloud service's user management or credentials section, find your user profile, and locate the access key. You can view and copy the key by following the instructions provided."; this.retainHeadObjectText = 'Retain object metadata after transition to the cloud (default: deleted).'; - this.action = this.actionLabels.CREATE; this.createForm(); + this.loadingReady(); this.loadZoneGroup(); + if (this.editing) { + this.route.params.subscribe((params: StorageClass) => { + this.storageClassInfo = params; + }); + this.rgwStorageService + .getPlacement_target(this.storageClassInfo.placement_target) + .subscribe((placementTargetInfo: PlacementTarget) => { + this.tierTargetInfo = this.getTierTargetByStorageClass( + placementTargetInfo, + this.storageClassInfo.storage_class + ); + let response = this.tierTargetInfo.val.s3; + this.storageClassForm.get('zonegroup').disable(); + this.storageClassForm.get('placement_target').disable(); + this.storageClassForm.get('storage_class').disable(); + this.storageClassForm.get('zonegroup').setValue(this.storageClassInfo.zonegroup_name); + this.storageClassForm.get('region').setValue(response.region); + this.storageClassForm + .get('placement_target') + .setValue(this.storageClassInfo.placement_target); + this.storageClassForm.get('endpoint').setValue(response.endpoint); + this.storageClassForm.get('storage_class').setValue(this.storageClassInfo.storage_class); + this.storageClassForm.get('access_key').setValue(response.access_key); + this.storageClassForm.get('secret_key').setValue(response.access_key); + this.storageClassForm.get('target_path').setValue(response.target_path); + this.storageClassForm + .get('retain_head_object') + .setValue(response.retain_head_object || false); + this.storageClassForm + .get('multipart_sync_threshold') + .setValue(response.multipart_sync_threshold || ''); + this.storageClassForm + .get('multipart_min_part_size') + .setValue(response.multipart_min_part_size || ''); + }); + } } createForm() { @@ -109,7 +154,7 @@ export class RgwStorageClassFormComponent extends CdForm implements OnInit { return new Promise((resolve, reject) => { this.rgwZoneGroupService.getAllZonegroupsInfo().subscribe( (data: ZoneGroupDetails) => { - this.zoneGroupDeatils = data; + this.zoneGroupDetails = data; this.zonegroupNames = []; this.placementTargets = []; if (data.zonegroups && data.zonegroups.length > 0) { @@ -135,7 +180,7 @@ export class RgwStorageClassFormComponent extends CdForm implements OnInit { onZonegroupChange() { const zoneGroupControl = this.storageClassForm.get('zonegroup').value; - const selectedZoneGroup = this.zoneGroupDeatils.zonegroups.find( + const selectedZoneGroup = this.zoneGroupDetails.zonegroups.find( (zonegroup) => zonegroup.name === zoneGroupControl ); const defaultPlacementTarget = selectedZoneGroup.placement_targets.find( @@ -147,8 +192,12 @@ export class RgwStorageClassFormComponent extends CdForm implements OnInit { ); this.placementTargets = placementTargetNames; } - if (defaultPlacementTarget) { + if (defaultPlacementTarget && !this.editing) { this.storageClassForm.get('placement_target').setValue(defaultPlacementTarget.name); + } else { + this.storageClassForm + .get('placement_target') + .setValue(this.storageClassInfo.placement_target); } } @@ -156,33 +205,58 @@ export class RgwStorageClassFormComponent extends CdForm implements OnInit { const component = this; const requestModel = this.buildRequest(); const storageclassName = this.storageClassForm.get('storage_class').value; - this.rgwStorageService.createStorageClass(requestModel).subscribe( - () => { - this.notificationService.show( - NotificationType.success, - $localize`Created Storage Class '${storageclassName}'` - ); - this.goToListView(); - }, - () => { - component.storageClassForm.setErrors({ cdSubmitButton: true }); - } - ); + if (this.editing) { + this.rgwStorageService.editStorageClass(requestModel).subscribe( + () => { + this.notificationService.show( + NotificationType.success, + $localize`Updated Storage Class '${storageclassName}'` + ); + this.goToListView(); + }, + () => { + component.storageClassForm.setErrors({ cdSubmitButton: true }); + } + ); + } else { + this.rgwStorageService.createStorageClass(requestModel).subscribe( + () => { + this.notificationService.show( + NotificationType.success, + $localize`Created Storage Class '${storageclassName}'` + ); + this.goToListView(); + }, + () => { + component.storageClassForm.setErrors({ cdSubmitButton: true }); + } + ); + } } goToListView() { this.router.navigate([`rgw/tiering`]); } + getTierTargetByStorageClass(placementTargetInfo: PlacementTarget, storageClass: string) { + const tierTarget = placementTargetInfo.tier_targets.find( + (target: TierTarget) => target.val.storage_class === storageClass + ); + return tierTarget; + } + buildRequest() { const rawFormValue = _.cloneDeep(this.storageClassForm.value); + const zoneGroup = this.storageClassForm.get('zonegroup').value; + const storageClass = this.storageClassForm.get('storage_class').value; + const placementId = this.storageClassForm.get('placement_target').value; const requestModel: RequestModel = { - zone_group: rawFormValue.zonegroup, + zone_group: zoneGroup, placement_targets: [ { tags: [], - placement_id: rawFormValue.placement_target, - storage_class: rawFormValue.storage_class, + placement_id: placementId, + storage_class: storageClass, tier_type: CLOUD_TIER, tier_config: { endpoint: rawFormValue.endpoint, diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-list/rgw-storage-class-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-list/rgw-storage-class-list.component.html index a0efeff62f2..cdea55a32e5 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-list/rgw-storage-class-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-list/rgw-storage-class-list.component.html @@ -1,3 +1,8 @@ + + + A storage class for tiering defines the policies for automatically moving objects between different storage tiers. + + + this.selection.first() && + `${encodeURI(this.selection.first().zonegroup_name)}/${encodeURI( + this.selection.first().placement_target + )}/${encodeURI(this.selection.first().storage_class)}`; this.tableActions = [ { name: this.actionLabels.CREATE, @@ -91,6 +95,12 @@ export class RgwStorageClassListComponent extends ListWithDetails implements OnI click: () => this.router.navigate([this.urlBuilder.getCreate()]), canBePrimary: (selection: CdTableSelection) => !selection.hasSelection }, + { + name: this.actionLabels.EDIT, + permission: 'update', + icon: Icons.edit, + routerLink: () => [`/rgw/tiering/edit/${getStorageUri()}`] + }, { name: this.actionLabels.REMOVE, permission: 'delete', diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts index 6bb87d9ec36..77bb10fb077 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts @@ -338,6 +338,11 @@ const routes: Routes = [ path: URLVerbs.CREATE, component: RgwStorageClassFormComponent, data: { breadcrumbs: ActionLabels.CREATE } + }, + { + path: `${URLVerbs.EDIT}/:zonegroup_name/:placement_target/:storage_class`, + component: RgwStorageClassFormComponent, + data: { breadcrumbs: ActionLabels.EDIT } } ] }, diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-storage-class.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-storage-class.service.ts index 9c3e1fcdbcd..b6b3cbc3867 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-storage-class.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-storage-class.service.ts @@ -6,7 +6,8 @@ import { RequestModel } from '~/app/ceph/rgw/models/rgw-storage-class.model'; providedIn: 'root' }) export class RgwStorageClassService { - private url = 'api/rgw/zonegroup/storage-class'; + private baseUrl = 'api/rgw/zonegroup'; + private url = `${this.baseUrl}/storage-class`; constructor(private http: HttpClient) {} @@ -19,4 +20,12 @@ export class RgwStorageClassService { createStorageClass(requestModel: RequestModel) { return this.http.post(`${this.url}`, requestModel); } + + editStorageClass(requestModel: RequestModel) { + return this.http.put(`${this.url}`, requestModel); + } + + getPlacement_target(placement_id: string) { + return this.http.get(`${this.baseUrl}/get_placement_target_by_placement_id/${placement_id}`); + } } diff --git a/src/pybind/mgr/dashboard/openapi.yaml b/src/pybind/mgr/dashboard/openapi.yaml index c4fb7fc9465..8b6bdb57ed1 100644 --- a/src/pybind/mgr/dashboard/openapi.yaml +++ b/src/pybind/mgr/dashboard/openapi.yaml @@ -13820,6 +13820,33 @@ paths: - jwt: [] tags: - RgwZonegroup + /api/rgw/zonegroup/get_placement_target_by_placement_id/{placement_id}: + get: + parameters: + - in: path + name: placement_id + required: true + schema: + type: string + responses: + '200': + content: + application/vnd.ceph.api.v1.0+json: + type: object + description: OK + '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/storage-class: post: parameters: [] @@ -13860,6 +13887,45 @@ paths: - jwt: [] tags: - RgwZonegroup + put: + parameters: [] + requestBody: + content: + application/json: + schema: + properties: + placement_targets: + default: [] + type: string + zone_group: + type: string + required: + - zone_group + 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: + - RgwZonegroup /api/rgw/zonegroup/storage-class/{placement_id}/{storage_class}: delete: parameters: diff --git a/src/pybind/mgr/dashboard/services/rgw_client.py b/src/pybind/mgr/dashboard/services/rgw_client.py index 92c23f090e6..40007a08ec3 100755 --- a/src/pybind/mgr/dashboard/services/rgw_client.py +++ b/src/pybind/mgr/dashboard/services/rgw_client.py @@ -1694,6 +1694,19 @@ class RgwMultisite: raise DashboardException(error, http_status_code=500, component='rgw') return out + def get_placement_by_placement_id(self, placement_id: str): + radosgw_get_placement_cmd = ['zonegroup', 'placement', + 'get', '--placement-id', placement_id] + try: + exit_code, out, err = mgr.send_rgwadmin_command(radosgw_get_placement_cmd) + if exit_code > 0: + raise DashboardException(e=err, msg='Unable to get placement details by id', + http_status_code=500, component='rgw') + + except SubprocessError as error: + raise DashboardException(error, http_status_code=500, component='rgw') + return out + # If realm list is empty, restart RGW daemons. Otherwise, update the period. def ensure_realm_and_sync_period(self): rgw_realm_list = self.list_realms() @@ -1780,37 +1793,78 @@ class RgwMultisite: def modify_placement_targets(self, zonegroup_name: str, placement_targets: List[Dict]): rgw_add_placement_cmd = ['zonegroup', 'placement', 'modify'] - for placement_target in placement_targets: - cmd_add_placement_options = ['--rgw-zonegroup', zonegroup_name, - '--placement-id', placement_target['placement_id']] - if placement_target['tags']: + STANDARD_STORAGE_CLASS = "STANDARD" + CLOUD_S3_TIER_TYPE = "cloud-s3" + + for placement_target in placement_targets: # pylint: disable=R1702,line-too-long # noqa: E501 + cmd_add_placement_options = [ + '--rgw-zonegroup', zonegroup_name, + '--placement-id', placement_target['placement_id'] + ] + storage_class_name = placement_target.get('storage_class', None) + + if ( + placement_target.get('tier_type') == CLOUD_S3_TIER_TYPE + and storage_class_name != STANDARD_STORAGE_CLASS + ): + tier_config = placement_target.get('tier_config', {}) + if tier_config: + tier_config_items = ( + f'{key}={value}' for key, value in tier_config.items() + ) + tier_config_str = ','.join(tier_config_items) + cmd_add_placement_options += [ + '--tier-type', 'cloud-s3', '--tier-config', tier_config_str + ] + + if placement_target.get('tags') and storage_class_name != STANDARD_STORAGE_CLASS: cmd_add_placement_options += ['--tags', placement_target['tags']] + + storage_classes = ( + placement_target['storage_class'].split(",") + if placement_target['storage_class'] + else [] + ) rgw_add_placement_cmd += cmd_add_placement_options - storage_classes = placement_target['storage_class'].split(",") if placement_target['storage_class'] else [] # noqa E501 #pylint: disable=line-too-long - if storage_classes: - for sc in storage_classes: - cmd_add_placement_options = [] - cmd_add_placement_options = ['--storage-class', sc] - try: - exit_code, _, err = mgr.send_rgwadmin_command( - rgw_add_placement_cmd + cmd_add_placement_options) - if exit_code > 0: - raise DashboardException(e=err, - msg='Unable to add placement target {} to zonegroup {}'.format(placement_target['placement_id'], zonegroup_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') - self.update_period() - else: + + if not storage_classes: try: exit_code, _, err = mgr.send_rgwadmin_command(rgw_add_placement_cmd) if exit_code > 0: - raise DashboardException(e=err, - msg='Unable to add placement target {} to zonegroup {}'.format(placement_target['placement_id'], zonegroup_name), # noqa E501 #pylint: disable=line-too-long - http_status_code=500, component='rgw') + raise DashboardException( + e=err, + msg=( + f'Unable to add placement target ' + f'{placement_target["placement_id"]} ' + f'to zonegroup {zonegroup_name}' + ) + ) except SubprocessError as error: raise DashboardException(error, http_status_code=500, component='rgw') - self.update_period() + self.ensure_realm_and_sync_period() + + if storage_classes: + for sc in storage_classes: + if sc == storage_class_name: + cmd_add_placement_options = ['--storage-class', sc] + try: + exit_code, _, err = mgr.send_rgwadmin_command( + rgw_add_placement_cmd + cmd_add_placement_options + ) + if exit_code > 0: + raise DashboardException( + e=err, + msg=( + f'Unable to add placement target ' + f'{placement_target["placement_id"]} ' + f'to zonegroup {zonegroup_name}' + ), + http_status_code=500, + component='rgw' + ) + except SubprocessError as error: + raise DashboardException(error, http_status_code=500, component='rgw') + self.ensure_realm_and_sync_period() def delete_placement_targets(self, placement_id: str, storage_class: str): rgw_zonegroup_delete_cmd = ['zonegroup', 'placement', 'rm', -- 2.39.5