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