]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/blob
5288e7d3d971bcdd97fa459e6500db1cdbcdb285
[ceph-ci.git] /
1 import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
2 import {
3   AbstractControl,
4   FormArray,
5   FormControl,
6   ValidationErrors,
7   Validators
8 } from '@angular/forms';
9 import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants';
10 import { CdForm } from '~/app/shared/forms/cd-form';
11 import { CdFormBuilder } from '~/app/shared/forms/cd-form-builder';
12 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
13 import _ from 'lodash';
14 import { ActivatedRoute, Router } from '@angular/router';
15 import { RgwStorageClassService } from '~/app/shared/api/rgw-storage-class.service';
16 import { RgwZonegroupService } from '~/app/shared/api/rgw-zonegroup.service';
17
18 import {
19   ALLOW_READ_THROUGH_TEXT,
20   DEFAULT_PLACEMENT,
21   MULTIPART_MIN_PART_TEXT,
22   MULTIPART_SYNC_THRESHOLD_TEXT,
23   PlacementTarget,
24   RequestModel,
25   RETAIN_HEAD_OBJECT_TEXT,
26   StorageClass,
27   Target,
28   TARGET_ACCESS_KEY_TEXT,
29   TARGET_ENDPOINT_TEXT,
30   TARGET_PATH_TEXT,
31   TARGET_REGION_TEXT,
32   TARGET_SECRET_KEY_TEXT,
33   TierTarget,
34   TIER_TYPE,
35   ZoneGroup,
36   ZoneGroupDetails,
37   CLOUDS3_STORAGE_CLASS_TEXT,
38   LOCAL_STORAGE_CLASS_TEXT,
39   GLACIER_STORAGE_CLASS_TEXT,
40   GLACIER_RESTORE_DAY_TEXT,
41   GLACIER_RESTORE_TIER_TYPE_TEXT,
42   RESTORE_DAYS_TEXT,
43   READTHROUGH_RESTORE_DAYS_TEXT,
44   RESTORE_STORAGE_CLASS_TEXT,
45   TIER_TYPE_DISPLAY,
46   S3Glacier,
47   TypeOption,
48   STORAGE_CLASS_CONSTANTS,
49   STANDARD_TIER_TYPE_TEXT,
50   EXPEDITED_TIER_TYPE_TEXT,
51   TextLabels,
52   CLOUD_TIER_REQUIRED_FIELDS,
53   GLACIER_REQUIRED_FIELDS,
54   GLACIER_TARGET_STORAGE_CLASS,
55   AclHelperText,
56   AclTypeLabel,
57   AclFieldType,
58   TierConfigRm,
59   ACL,
60   AclTypeOptions,
61   AclTypeConst,
62   ACLVal,
63   AclLabel,
64   AclType,
65   ZoneRequest,
66   AllZonesResponse,
67   POOL
68 } from '../models/rgw-storage-class.model';
69 import { NotificationType } from '~/app/shared/enum/notification-type.enum';
70 import { NotificationService } from '~/app/shared/services/notification.service';
71 import { CdValidators } from '~/app/shared/forms/cd-validators';
72 import { FormatterService } from '~/app/shared/services/formatter.service';
73 import validator from 'validator';
74 import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
75 import { PoolService } from '~/app/shared/api/pool.service';
76 import { Pool } from '../../pool/pool';
77 import { catchError, map, switchMap } from 'rxjs/operators';
78 import { RGW } from '../utils/constants';
79 import { forkJoin, of } from 'rxjs';
80 import { RgwZoneService } from '~/app/shared/api/rgw-zone.service';
81 import { BucketTieringUtils } from '../utils/rgw-bucket-tiering';
82
83 @Component({
84   selector: 'cd-rgw-storage-class-form',
85   templateUrl: './rgw-storage-class-form.component.html',
86   styleUrls: ['./rgw-storage-class-form.component.scss']
87 })
88 export class RgwStorageClassFormComponent extends CdForm implements OnInit {
89   storageClassForm: CdFormGroup;
90   action: string;
91   resource: string;
92   editing: boolean;
93   showAdvanced: boolean = false;
94   defaultZoneGroup: string;
95   zonegroupNames: ZoneGroup[];
96   placementTargets: string[] = [];
97   selectedZoneGroup: string;
98   selectedZone: string;
99   defaultZonegroup: ZoneGroup;
100   zoneGroupDetails: ZoneGroupDetails;
101   storageClassInfo: StorageClass;
102   tierTargetInfo: TierTarget;
103   glacierStorageClassDetails: S3Glacier;
104   allowReadThrough: boolean = false;
105   TIER_TYPE = TIER_TYPE;
106   TIER_TYPE_DISPLAY = TIER_TYPE_DISPLAY;
107   storageClassOptions: TypeOption[];
108   helpTextLabels: TextLabels;
109   typeOptions: TypeOption[];
110   aclTypeLabel = AclTypeLabel;
111   aclHelperText = AclHelperText;
112   aclList: ACL[] = [];
113   removedAclSourceIds: string[] = [];
114   urlValidator = (control: AbstractControl): ValidationErrors | null => {
115     const value = control.value;
116     return !value || validator.isURL(value) ? null : { invalidUrl: true };
117   };
118   rgwPools: Pool[];
119   zones: any[];
120   POOL = POOL;
121
122   constructor(
123     public actionLabels: ActionLabelsI18n,
124     private formBuilder: CdFormBuilder,
125     private notificationService: NotificationService,
126     private rgwStorageService: RgwStorageClassService,
127     private rgwZoneGroupService: RgwZonegroupService,
128     private router: Router,
129     private route: ActivatedRoute,
130     public formatter: FormatterService,
131     private cdRef: ChangeDetectorRef,
132     private poolService: PoolService,
133     private dimlessBinary: DimlessBinaryPipe,
134     private rgwZoneService: RgwZoneService
135   ) {
136     super();
137     this.resource = $localize`Tiering Storage Class`;
138     this.editing = this.router.url.startsWith(`/rgw/tiering/${URLVerbs.EDIT}`);
139     this.action = this.editing ? this.actionLabels.EDIT : this.actionLabels.CREATE;
140   }
141
142   ngOnInit() {
143     this.helpTextLabels = {
144       targetPathText: TARGET_PATH_TEXT,
145       targetEndpointText: TARGET_ENDPOINT_TEXT,
146       targetRegionText: TARGET_REGION_TEXT,
147       targetAccessKeyText: TARGET_ACCESS_KEY_TEXT,
148       targetSecretKeyText: TARGET_SECRET_KEY_TEXT,
149       retainHeadObjectText: RETAIN_HEAD_OBJECT_TEXT,
150       allowReadThroughText: ALLOW_READ_THROUGH_TEXT,
151       storageClassText: LOCAL_STORAGE_CLASS_TEXT,
152       multipartMinPartText: MULTIPART_MIN_PART_TEXT,
153       multipartSyncThresholdText: MULTIPART_SYNC_THRESHOLD_TEXT,
154       tiertypeText: STANDARD_TIER_TYPE_TEXT,
155       glacierRestoreDayText: GLACIER_RESTORE_DAY_TEXT,
156       glacierRestoreTiertypeText: GLACIER_RESTORE_TIER_TYPE_TEXT,
157       restoreDaysText: RESTORE_DAYS_TEXT,
158       readthroughrestoreDaysText: READTHROUGH_RESTORE_DAYS_TEXT,
159       restoreStorageClassText: RESTORE_STORAGE_CLASS_TEXT
160     };
161     this.storageClassOptions = [
162       { value: TIER_TYPE.LOCAL, label: TIER_TYPE_DISPLAY.LOCAL },
163       { value: TIER_TYPE.CLOUD_TIER, label: TIER_TYPE_DISPLAY.CLOUD_TIER },
164       { value: TIER_TYPE.GLACIER, label: TIER_TYPE_DISPLAY.GLACIER }
165     ];
166     this.typeOptions = [...AclTypeOptions];
167     this.createForm();
168     this.storageClassTypeText();
169     this.updateTierTypeHelpText();
170     this.loadZoneGroup();
171     if (this.editing) {
172       this.route.params.subscribe((params: StorageClass) => {
173         this.storageClassInfo = params;
174       });
175       this.rgwStorageService
176         .getPlacement_target(this.storageClassInfo.placement_target)
177         .pipe(
178           switchMap((placementTargetInfo: PlacementTarget) => {
179             // Set the tierTargetInfo based on the placementTargetInfo and storageClassInfo
180             this.tierTargetInfo = this.getTierTargetByStorageClass(
181               placementTargetInfo,
182               this.storageClassInfo.storage_class
183             );
184             const tierType = this.tierTargetInfo?.val?.tier_type ?? TIER_TYPE.LOCAL;
185
186             // If tierType is LOCAL, make the second API calls
187             if (tierType === TIER_TYPE.LOCAL) {
188               return forkJoin([
189                 this.poolService.getList(),
190                 this.rgwZoneService.getAllZonesInfo()
191               ]).pipe(map(([pools, zones]) => ({ placementTargetInfo, pools, zones })));
192             }
193
194             // If tierType is not LOCAL, just return placementTargetInfo with null pools and zones
195             return of({ placementTargetInfo, pools: null, zones: null });
196           }),
197           map(({ placementTargetInfo, pools, zones }) => {
198             return { placementTargetInfo, pools, zones };
199           }),
200           catchError(() => {
201             return of({
202               placementTargetInfo: null,
203               pools: null,
204               zones: null
205             });
206           })
207         )
208         .subscribe(
209           (data: {
210             placementTargetInfo: PlacementTarget;
211             pools: Pool[] | null;
212             zones: AllZonesResponse | null;
213           }) => {
214             let response = this.tierTargetInfo?.val?.s3;
215             this.aclList = response?.acl_mappings || [];
216             this.storageClassForm.patchValue({
217               zonegroup: this.storageClassInfo?.zonegroup_name,
218               region: response?.region,
219               placement_target: this.storageClassInfo?.placement_target,
220               storageClassType: this.tierTargetInfo?.val?.tier_type ?? TIER_TYPE.LOCAL,
221               target_endpoint: response?.endpoint,
222               storage_class: this.storageClassInfo?.storage_class,
223               access_key: response?.access_key,
224               secret_key: response?.secret,
225               target_path: response?.target_path,
226               retain_head_object: this.tierTargetInfo?.val?.retain_head_object || false,
227               multipart_sync_threshold:
228                 this.dimlessBinary.transform(response?.multipart_sync_threshold) || '',
229               multipart_min_part_size:
230                 this.dimlessBinary.transform(response?.multipart_min_part_size) || '',
231               allow_read_through: this.tierTargetInfo?.val?.allow_read_through || false,
232               restore_storage_class: this.tierTargetInfo?.val?.restore_storage_class,
233               read_through_restore_days: this.tierTargetInfo?.val?.read_through_restore_days,
234               acl_mappings: response?.acl_mappings || []
235             });
236             if (
237               this.storageClassForm.get('storageClassType')?.value === TIER_TYPE.CLOUD_TIER ||
238               this.storageClassForm.get('storageClassType')?.value === TIER_TYPE.GLACIER
239             ) {
240               this.acls?.clear();
241               if (this.aclList.length > 0) {
242                 this.aclList.forEach((acl) => {
243                   this.acls?.push(
244                     this.formBuilder.group({
245                       source_id: [acl.val?.source_id || ''],
246                       dest_id: [acl.val?.dest_id || ''],
247                       type: [acl.val?.type || AclTypeConst.ID, Validators.required]
248                     })
249                   );
250                 });
251               } else {
252                 this.addAcls();
253               }
254             }
255             if (this.tierTargetInfo?.val?.tier_type == TIER_TYPE.GLACIER) {
256               let glacierResponse = this.tierTargetInfo?.val['s3-glacier'];
257               this.storageClassForm.patchValue({
258                 glacier_restore_tier_type: glacierResponse.glacier_restore_tier_type,
259                 glacier_restore_days: glacierResponse.glacier_restore_days
260               });
261             }
262             const zoneInfo = BucketTieringUtils.getZoneInfoHelper(data.zones?.zones, {
263               placement_target: this.storageClassInfo?.placement_target,
264               storage_class: this.storageClassInfo?.storage_class
265             });
266             if (data.pools) {
267               this.rgwPools = data.pools.filter((pool: Pool) =>
268                 pool.application_metadata?.includes(RGW)
269               );
270               this.storageClassForm.get('pool').setValue(zoneInfo.data_pool);
271               this.storageClassForm.get('zone').setValue(zoneInfo.zone_name);
272             }
273             this.loadingReady();
274           }
275         );
276       this.storageClassForm.get('zonegroup').disable();
277       this.storageClassForm.get('placement_target').disable();
278       this.storageClassForm.get('storage_class').disable();
279       this.storageClassForm.get('zone').disable();
280       this.storageClassForm.get('storageClassType').disable();
281     } else {
282       this.addAcls();
283       this.poolService.getList().subscribe((resp: Pool[]) => {
284         // Filter only pools with "rgw" in application_metadata
285         this.rgwPools = resp.filter((pool: Pool) => pool.application_metadata?.includes(RGW));
286         this.loadingReady();
287       });
288     }
289     this.storageClassForm.get('storageClassType').valueChanges.subscribe((value) => {
290       this.updateValidatorsBasedOnStorageClass(value);
291     });
292     this.storageClassForm.get('allow_read_through').valueChanges.subscribe((value) => {
293       this.onAllowReadThroughChange(value);
294     });
295   }
296
297   public createAcls(): CdFormGroup {
298     const group = this.formBuilder.group({
299       type: new FormControl(AclTypeConst.ID, Validators.required),
300       source_id: new FormControl('', [
301         CdValidators.composeIf(
302           {
303             type: AclTypeConst.EMAIL
304           },
305           [Validators.email]
306         ),
307         CdValidators.composeIf(
308           {
309             type: AclTypeConst.URI
310           },
311           [this.urlValidator]
312         )
313       ]),
314       dest_id: new FormControl('', [
315         CdValidators.composeIf(
316           {
317             type: AclTypeConst.EMAIL
318           },
319           [Validators.email]
320         ),
321         CdValidators.composeIf(
322           {
323             type: AclTypeConst.URI
324           },
325           [this.urlValidator]
326         )
327       ])
328     });
329     return group;
330   }
331
332   get acls(): FormArray {
333     return this.storageClassForm.get('acls') as FormArray;
334   }
335
336   private updateValidatorsBasedOnStorageClass(value: string) {
337     GLACIER_REQUIRED_FIELDS.forEach((field) => {
338       const control = this.storageClassForm.get(field);
339
340       if (
341         (value === TIER_TYPE.CLOUD_TIER && CLOUD_TIER_REQUIRED_FIELDS.includes(field)) ||
342         (value === TIER_TYPE.GLACIER && GLACIER_REQUIRED_FIELDS.includes(field))
343       ) {
344         control.setValidators([Validators.required]);
345       } else {
346         control.clearValidators();
347       }
348       control.updateValueAndValidity();
349     });
350
351     if (this.editing) {
352       const defaultValues: {
353         allow_read_through: boolean;
354         read_through_restore_days: number;
355         restore_storage_class: string;
356         multipart_min_part_size: number;
357         multipart_sync_threshold: number;
358       } = {
359         allow_read_through: false,
360         read_through_restore_days: STORAGE_CLASS_CONSTANTS.DEFAULT_READTHROUGH_RESTORE_DAYS,
361         restore_storage_class: STORAGE_CLASS_CONSTANTS.DEFAULT_STORAGE_CLASS,
362         multipart_min_part_size: STORAGE_CLASS_CONSTANTS.DEFAULT_MULTIPART_MIN_PART_SIZE,
363         multipart_sync_threshold: STORAGE_CLASS_CONSTANTS.DEFAULT_MULTIPART_SYNC_THRESHOLD
364       };
365       (Object.keys(defaultValues) as Array<keyof typeof defaultValues>).forEach((key) => {
366         this.storageClassForm.get(key)?.setValue(defaultValues[key]);
367       });
368     }
369   }
370
371   addAcls() {
372     this.acls.push(this.createAcls());
373   }
374
375   removeAcl(index: number) {
376     if (this.acls.length > 1) {
377       this.acls.removeAt(index);
378     } else {
379       const removedAcl = this.acls.at(0).value;
380
381       if (removedAcl?.source_id) {
382         this.removedAclSourceIds.push(removedAcl.source_id);
383       }
384       const newGroup = this.createAcls();
385       this.acls.setControl(0, newGroup);
386     }
387
388     this.cdRef.detectChanges();
389   }
390
391   getAclLabel(field: AclFieldType, type?: string): string {
392     if (!type) {
393       return field === AclFieldType.Source ? AclLabel.source : AclLabel.destination;
394     }
395     return (
396       this.aclTypeLabel[type]?.[field] ||
397       (field === AclFieldType.Source ? AclLabel.source : AclLabel.destination)
398     );
399   }
400
401   getAclHelperText(type: string, field: AclFieldType): string {
402     return this.aclHelperText[type]?.[field] || '';
403   }
404
405   storageClassTypeText() {
406     this.storageClassForm?.get('storageClassType')?.valueChanges.subscribe((value) => {
407       if (value === TIER_TYPE.LOCAL) {
408         this.helpTextLabels.storageClassText = LOCAL_STORAGE_CLASS_TEXT;
409       } else if (value === TIER_TYPE.CLOUD_TIER) {
410         this.helpTextLabels.storageClassText = CLOUDS3_STORAGE_CLASS_TEXT;
411       } else if (value === TIER_TYPE.GLACIER) {
412         this.helpTextLabels.storageClassText = GLACIER_STORAGE_CLASS_TEXT;
413       }
414     });
415   }
416
417   updateTierTypeHelpText() {
418     this.storageClassForm?.get('glacier_restore_tier_type')?.valueChanges.subscribe((value) => {
419       if (value === STORAGE_CLASS_CONSTANTS.DEFAULT_STORAGE_CLASS) {
420         this.helpTextLabels.tiertypeText = STANDARD_TIER_TYPE_TEXT;
421       } else {
422         this.helpTextLabels.tiertypeText = EXPEDITED_TIER_TYPE_TEXT;
423       }
424     });
425   }
426
427   createForm() {
428     const self = this;
429
430     const lockDaysValidator = CdValidators.custom('lockDays', () => {
431       if (!self.storageClassForm || !self.storageClassForm.getRawValue()) {
432         return false;
433       }
434
435       const lockDays = Number(self.storageClassForm.getValue('read_through_restore_days'));
436       return !Number.isInteger(lockDays) || lockDays === 0;
437     });
438     this.storageClassForm = this.formBuilder.group({
439       storage_class: new FormControl('', {
440         validators: [Validators.required]
441       }),
442       zonegroup: new FormControl(this.selectedZoneGroup, {
443         validators: [Validators.required]
444       }),
445       region: new FormControl('', [
446         CdValidators.composeIf({ storageClassType: TIER_TYPE.CLOUD_TIER }, [Validators.required])
447       ]),
448       placement_target: new FormControl('', {
449         validators: [Validators.required]
450       }),
451       target_endpoint: new FormControl('', [Validators.required, this.urlValidator]),
452       access_key: new FormControl(null, [
453         CdValidators.composeIf({ storageClassType: TIER_TYPE.CLOUD_TIER }, [Validators.required])
454       ]),
455       secret_key: new FormControl(null, [
456         CdValidators.composeIf({ storageClassType: TIER_TYPE.CLOUD_TIER }, [Validators.required])
457       ]),
458       target_path: new FormControl('', [
459         CdValidators.composeIf({ storageClassType: TIER_TYPE.CLOUD_TIER }, [Validators.required])
460       ]),
461       retain_head_object: new FormControl(true),
462       glacier_restore_tier_type: new FormControl(STORAGE_CLASS_CONSTANTS.DEFAULT_STORAGE_CLASS, [
463         CdValidators.composeIf({ storageClassType: TIER_TYPE.GLACIER }, [Validators.required])
464       ]),
465       glacier_restore_days: new FormControl(STORAGE_CLASS_CONSTANTS.DEFAULT_GLACIER_RESTORE_DAYS, [
466         CdValidators.composeIf({ storageClassType: TIER_TYPE.GLACIER || TIER_TYPE.CLOUD_TIER }, [
467           CdValidators.number(false),
468           lockDaysValidator
469         ])
470       ]),
471       restore_storage_class: new FormControl(STORAGE_CLASS_CONSTANTS.DEFAULT_STORAGE_CLASS),
472       read_through_restore_days: new FormControl(
473         {
474           value: STORAGE_CLASS_CONSTANTS.DEFAULT_READTHROUGH_RESTORE_DAYS,
475           disabled: true
476         },
477         CdValidators.composeIf(
478           (form: AbstractControl) => {
479             const type = form.get('storageClassType')?.value;
480             return type === TIER_TYPE.GLACIER || type === TIER_TYPE.CLOUD_TIER;
481           },
482           [CdValidators.number(false), lockDaysValidator]
483         )
484       ),
485       multipart_sync_threshold: new FormControl(
486         STORAGE_CLASS_CONSTANTS.DEFAULT_MULTIPART_SYNC_THRESHOLD
487       ),
488       multipart_min_part_size: new FormControl(
489         STORAGE_CLASS_CONSTANTS.DEFAULT_MULTIPART_MIN_PART_SIZE
490       ),
491       allow_read_through: new FormControl(false),
492       storageClassType: new FormControl(TIER_TYPE.LOCAL, Validators.required),
493       pool: new FormControl('', [
494         CdValidators.composeIf({ storageClassType: TIER_TYPE.LOCAL }, [Validators.required])
495       ]),
496       zone: new FormControl(null, [
497         CdValidators.composeIf({ storageClassType: TIER_TYPE.LOCAL }, [Validators.required])
498       ]),
499       acls: new FormArray([])
500     });
501     this.storageClassForm.get('storageClassType')?.valueChanges.subscribe((type: string) => {
502       if (type === TIER_TYPE.CLOUD_TIER) {
503         const aclsArray = this.storageClassForm.get('acls') as FormArray;
504         aclsArray.clear();
505         aclsArray.push(this.createAcls());
506       }
507     });
508   }
509
510   loadZoneGroup(): Promise<void> {
511     return new Promise((resolve, reject) => {
512       this.rgwZoneGroupService.getAllZonegroupsInfo().subscribe(
513         (data: ZoneGroupDetails) => {
514           this.zoneGroupDetails = data;
515           this.zonegroupNames = [];
516           this.zones = [];
517           if (data.zonegroups && data.zonegroups.length > 0) {
518             this.zonegroupNames = data.zonegroups.map((zoneGroup: ZoneGroup) => ({
519               id: zoneGroup.id,
520               name: zoneGroup.name,
521               zones: zoneGroup.zones
522             }));
523           }
524           this.defaultZonegroup = this.zonegroupNames.find(
525             (zonegroup) => zonegroup.id === data?.default_zonegroup
526           );
527           this.storageClassForm.get('zonegroup').setValue(this.defaultZonegroup?.name);
528           this.onZonegroupChange();
529
530           resolve();
531         },
532         (error) => {
533           reject(error);
534         }
535       );
536     });
537   }
538
539   onZonegroupChange(): void {
540     const zoneGroupControl = this.storageClassForm.get('zonegroup').value;
541     const selectedZoneGroup = this.zoneGroupDetails?.zonegroups?.find(
542       (zonegroup) => zonegroup?.name === zoneGroupControl
543     );
544     const defaultPlacementTarget = selectedZoneGroup?.placement_targets?.find(
545       (target: Target) => target.name === DEFAULT_PLACEMENT
546     );
547
548     if (selectedZoneGroup?.placement_targets) {
549       this.placementTargets = selectedZoneGroup.placement_targets.map(
550         (target: Target) => target.name
551       );
552     }
553     if (defaultPlacementTarget && !this.editing) {
554       this.storageClassForm.get('placement_target').setValue(defaultPlacementTarget.name);
555     } else {
556       this.storageClassForm
557         .get('placement_target')
558         .setValue(this.storageClassInfo?.placement_target || null);
559     }
560     this.zones = selectedZoneGroup?.zones;
561   }
562
563   submitAction() {
564     const component = this;
565     const requestModel = this.buildRequest();
566     const rawFormValue = _.cloneDeep(this.storageClassForm.getRawValue());
567     const zoneRequest: ZoneRequest = {
568       zone_name: this.storageClassForm.get('zone').value,
569       placement_target: this.storageClassForm.get('placement_target').value,
570       storage_class: this.storageClassForm.get('storage_class').value,
571       data_pool: this.storageClassForm.get('pool')?.value || ''
572     };
573
574     const storageclassName = this.storageClassForm.get('storage_class').value;
575     if (this.editing) {
576       const editStorageClass$ = this.rgwStorageService.editStorageClass(requestModel);
577
578       const editZone$ =
579         rawFormValue.storageClassType === TIER_TYPE.LOCAL
580           ? editStorageClass$.pipe(
581               switchMap(() => this.rgwStorageService.editStorageClassZone(zoneRequest))
582             )
583           : editStorageClass$;
584
585       editZone$.subscribe(
586         () => {
587           this.notificationService.show(
588             NotificationType.success,
589             $localize`Edited Storage Class '${storageclassName}'`
590           );
591           this.goToListView();
592         },
593         () => {
594           component.storageClassForm.setErrors({ cdSubmitButton: true });
595         }
596       );
597     } else {
598       const createStorageClass$ = this.rgwStorageService.createStorageClass(requestModel);
599
600       const createZone$ =
601         rawFormValue.storageClassType === TIER_TYPE.LOCAL
602           ? createStorageClass$.pipe(
603               switchMap(() => this.rgwStorageService.createStorageClassZone(zoneRequest))
604             )
605           : createStorageClass$;
606
607       createZone$.subscribe(
608         () => {
609           this.notificationService.show(
610             NotificationType.success,
611             $localize`Created Storage Class '${storageclassName}'`
612           );
613           this.goToListView();
614         },
615         () => {
616           component.storageClassForm.setErrors({ cdSubmitButton: true });
617         }
618       );
619     }
620   }
621   goToListView() {
622     this.router.navigate([`rgw/tiering`]);
623   }
624
625   getTierTargetByStorageClass(placementTargetInfo: PlacementTarget, storageClass: string) {
626     const tierTarget = placementTargetInfo?.tier_targets?.find(
627       (target: TierTarget) => target.val.storage_class === storageClass
628     );
629     return tierTarget;
630   }
631
632   onAllowReadThroughChange(checked: boolean): void {
633     this.allowReadThrough = checked;
634     const readThroughDaysControl = this.storageClassForm.get('read_through_restore_days');
635     if (this.allowReadThrough) {
636       this.storageClassForm.get('retain_head_object')?.setValue(true);
637       this.storageClassForm.get('retain_head_object')?.disable();
638       readThroughDaysControl?.enable();
639     } else {
640       this.storageClassForm.get('retain_head_object')?.enable();
641       readThroughDaysControl?.disable();
642     }
643   }
644
645   isTierMatch(...types: string[]): boolean {
646     const tierType = this.storageClassForm.getValue('storageClassType');
647     return types.includes(tierType);
648   }
649
650   buildRequest() {
651     if (this.storageClassForm.errors) return null;
652     const rawFormValue = _.cloneDeep(this.storageClassForm.value);
653     const zoneGroup = this.storageClassForm.get('zonegroup').value;
654     const storageClass = this.storageClassForm.get('storage_class').value;
655     const placementId = this.storageClassForm.get('placement_target').value;
656     const storageClassType = this.storageClassForm.get('storageClassType').value;
657     const retain_head_object = this.storageClassForm.get('retain_head_object').value;
658     const multipart_min_part_size = this.formatter.toBytes(
659       this.storageClassForm.get('multipart_min_part_size').value
660     );
661     const multipart_sync_threshold = this.formatter.toBytes(
662       this.storageClassForm.get('multipart_sync_threshold').value
663     );
664
665     const removeAclList: ACLVal[] = rawFormValue.acls || [];
666     const tier_config_rm: TierConfigRm = {};
667     this.removedAclSourceIds.forEach((sourceId: string, index: number) => {
668       tier_config_rm[`acls[${index}].source_id`] = sourceId;
669     });
670     if (this.aclList?.length > rawFormValue.acls?.length) {
671       this.aclList?.forEach((acl: ACL, index: number) => {
672         const sourceId = acl?.val?.source_id;
673         const ifExist = removeAclList.find((acl: ACLVal) => acl?.source_id === sourceId);
674
675         if (!ifExist) {
676           tier_config_rm[`acls[${index}].source_id`] = sourceId;
677         }
678       });
679     }
680
681     return this.buildPlacementTargets(
682       storageClassType,
683       zoneGroup,
684       placementId,
685       storageClass,
686       retain_head_object,
687       rawFormValue,
688       multipart_sync_threshold,
689       multipart_min_part_size,
690       tier_config_rm
691     );
692   }
693
694   private buildPlacementTargets(
695     storageClassType: string,
696     zoneGroup: string,
697     placementId: string,
698     storageClass: string,
699     retain_head_object: boolean,
700     rawFormValue: any,
701     multipart_sync_threshold: number,
702     multipart_min_part_size: number,
703     tier_config_rm: TierConfigRm
704   ): RequestModel {
705     const baseTarget = {
706       placement_id: placementId,
707       storage_class: storageClass,
708       tier_config_rm: tier_config_rm
709     };
710
711     if (storageClassType === TIER_TYPE.LOCAL) {
712       return {
713         zone_group: zoneGroup,
714         placement_targets: [baseTarget]
715       };
716     }
717
718     const aclConfig: { [key: string]: string } = {};
719
720     rawFormValue.acls.forEach((acl: ACLVal, index: number) => {
721       const sourceId = acl?.source_id?.trim();
722       if (!sourceId) return;
723
724       const destId = acl?.dest_id?.trim() || '';
725       const type = acl?.type?.trim() || AclTypeConst.ID;
726
727       aclConfig[`acls[${index}].source_id`] = sourceId;
728       aclConfig[`acls[${index}].dest_id`] = destId;
729       aclConfig[`acls[${index}].type`] = type as AclType;
730     });
731     const tierConfig = {
732       endpoint: rawFormValue.target_endpoint,
733       access_key: rawFormValue.access_key,
734       secret: rawFormValue.secret_key,
735       target_path: rawFormValue.target_path,
736       retain_head_object,
737       allow_read_through: rawFormValue.allow_read_through,
738       region: rawFormValue.region,
739       multipart_sync_threshold,
740       multipart_min_part_size,
741       restore_storage_class: rawFormValue.restore_storage_class,
742       ...(rawFormValue.allow_read_through
743         ? { read_through_restore_days: rawFormValue.read_through_restore_days }
744         : {}),
745       ...aclConfig
746     };
747
748     if (storageClassType === TIER_TYPE.CLOUD_TIER) {
749       return {
750         zone_group: zoneGroup,
751         placement_targets: [
752           {
753             ...baseTarget,
754             tier_type: TIER_TYPE.CLOUD_TIER,
755             tier_config_rm: tier_config_rm,
756             tier_config: {
757               ...tierConfig
758             }
759           }
760         ]
761       };
762     }
763
764     if (storageClassType === TIER_TYPE.GLACIER) {
765       return {
766         zone_group: zoneGroup,
767         placement_targets: [
768           {
769             ...baseTarget,
770             tier_type: TIER_TYPE.GLACIER,
771             tier_config_rm: tier_config_rm,
772             tier_config: {
773               ...tierConfig,
774               glacier_restore_days: rawFormValue.glacier_restore_days,
775               glacier_restore_tier_type: rawFormValue.glacier_restore_tier_type,
776               target_storage_class: GLACIER_TARGET_STORAGE_CLASS
777             }
778           }
779         ]
780       };
781     }
782
783     this.removedAclSourceIds = [];
784     return {
785       zone_group: zoneGroup,
786       placement_targets: [baseTarget]
787     };
788   }
789 }