read_through_restore_days: number;
restore_storage_class: string;
s3?: S3Details;
- 's3-glacier': S3Glacier;
+ 's3-glacier'?: S3Glacier;
};
}
}
export interface PlacementTarget {
- tags?: string[];
placement_id: string;
+ tags?: string[];
tier_type?: TIER_TYPE;
tier_config?: {
endpoint: string;
restoreStorageClassText: string;
}
+export const CLOUD_TIER_REQUIRED_FIELDS = [
+ 'region',
+ 'target_endpoint',
+ 'access_key',
+ 'secret_key',
+ 'target_path'
+];
+
+export const GLACIER_REQUIRED_FIELDS = [
+ 'region',
+ 'target_endpoint',
+ 'access_key',
+ 'secret_key',
+ 'target_path',
+ 'glacier_restore_tier_type',
+ 'restore_storage_class'
+];
+
export const TIER_TYPE = {
LOCAL: 'local',
CLOUD_TIER: 'cloud-s3',
export const RESTORE_DAYS_TEXT = $localize`Refers to number of days to the object will be restored on glacier/tape endpoint.`;
-export const READTHROUGH_RESTORE_DAYS_TEXT = $localize`The duration for which objects restored via read-through are retained.`;
+export const READTHROUGH_RESTORE_DAYS_TEXT = $localize`The days for which objects restored via read-through are retained.`;
export const RESTORE_STORAGE_CLASS_TEXT = $localize`The storage class to which object data is to be restored.`;
</span>
</cd-helper>
</td>
- <td>{{ selection?.multipart_min_part_size }}</td>
+ <td>{{ selection?.multipart_min_part_size | dimlessBinary }}</td>
</tr>
}
@if(isTierMatch( TIER_TYPE_DISPLAY.CLOUD_TIER, TIER_TYPE_DISPLAY.GLACIER)){
</span>
</cd-helper>
</td>
- <td>{{ selection?.multipart_sync_threshold }}</td>
+ <td>{{ selection?.multipart_sync_threshold | dimlessBinary }}</td>
</tr>
}
</tbody>
i18n-label
for="storageClassType"
formControlName="storageClassType"
- [helperText]="textLabels.storageClassText"
+ [helperText]="helpTextLabels.storageClassText"
id="storageClassType"
[invalid]="storageClassForm.showError('storageClassType', formDir, 'required')"
[invalidText]="storageError"
i18n
[invalid]="storageClassForm.showError('region', formDir, 'required')"
[invalidText]="regionError"
- [helperText]="textLabels.targetRegionText"
+ [helperText]="helpTextLabels.targetRegionText"
>Target Region
<input
cdsText
<div cdsCol>
<!-- Target Endpoint -->
<cds-text-label
- labelInputID="endpoint"
- i18n
- [invalid]="storageClassForm.showError('endpoint', formDir, 'required')"
+ labelInputID="target_endpoint"
+ i18n
+ [invalid]="storageClassForm.showError('target_endpoint', formDir, 'invalidURL') || storageClassForm.showError('target_endpoint', formDir, 'required')"
+ [invalidText]="endpointError"
+ [helperText]="helpTextLabels.targetEndpointText"
+ >Target Endpoint
+ <input
+ cdsText
+ type="text"
+ placeholder="e.g, http://ceph-node-00.com:80"
+ i18n-placeholder
+ id="target_endpoint"
+ formControlName="target_endpoint"
+ [invalid]="storageClassForm.showError('target_endpoint', formDir, 'invalidURL') || storageClassForm.showError('target_endpoint', formDir, 'required')"
[invalidText]="endpointError"
- [helperText]="textLabels.targetEndpointText"
- >Target Endpoint
- <input
- cdsText
- type="text"
- placeholder="e.g, http://ceph-node-00.com:80"
- i18n-placeholder
- id="endpoint"
- formControlName="endpoint"
- [invalid]="storageClassForm.showError('endpoint', formDir, 'required')"
- />
- </cds-text-label>
+ />
+ </cds-text-label>
<ng-template #endpointError>
<span
class="invalid-feedback"
- *ngIf="storageClassForm.showError('endpoint', formDir, 'required')"
+ *ngIf="storageClassForm.showError('target_endpoint', formDir, 'required')"
i18n
>This field is required.</span
>
+ <span class="invalid-feedback"
+ *ngIf="storageClassForm.showError('target_endpoint', formDir, 'invalidURL')"
+ i18n>Please enter a valid URL.</span>
</ng-template>
</div>
</div>
labelInputID="access_key"
[invalid]="storageClassForm.showError('access_key', formDir, 'required')"
[invalidText]="accessError"
- [helperText]="textLabels.targetAccessKeyText"
+ [helperText]="helpTextLabels.targetAccessKeyText"
i18n
>Target Access Key
<input
class="d-flex">
<cds-password-label
labelInputID="secret_key"
- [helperText]="textLabels.targetSecretKeyText"
+ [helperText]="helpTextLabels.targetSecretKeyText"
[invalid]="storageClassForm.showError('secret_key', formDir, 'required')"
[invalidText]="secretError"
i18n
i18n
[invalid]="storageClassForm.showError('target_path', formDir, 'required')"
[invalidText]="targetError"
- [helperText]="textLabels.targetPathText"
+ [helperText]="helpTextLabels.targetPathText"
>Target Path
<input
cdsText
i18n
(change)="onAllowReadThroughChange($event)"
>Allow Read Through
- <cd-help-text>{{ textLabels?.allowReadThroughText }}</cd-help-text>
+ <cd-help-text>{{ helpTextLabels?.allowReadThroughText }}</cd-help-text>
</cds-checkbox>
</div>
<div class="form-item">
cdOptionalField="Head Object (Stub File)"
i18n
>Head Object (Stub File)
- <cd-help-text>{{ textLabels?.retainHeadObjectText }}</cd-help-text>
+ <cd-help-text>{{ helpTextLabels?.retainHeadObjectText }}</cd-help-text>
</cds-checkbox>
</div>
<div class="form-item form-item-append"
id="read_through_restore_days"
min="1"
label="ReadThrough Restore Days"
- [helperText]="textLabels.readthroughrestoreDaysText"
+ [helperText]="helpTextLabels.readthroughrestoreDaysText"
i18n-helperText
i18n-label
i18n
formControlName="restore_storage_class"
label="Restore Storage Class"
id="restore_storage_class"
- [helperText]="textLabels.restoreStorageClassText"
+ [helperText]="helpTextLabels.restoreStorageClassText"
i18n-label
>
<option value=""
storageClassForm.controls.glacier_restore_tier_type.dirty
"
[invalidText]="glacierError"
- [helperText]="textLabels.tiertypeText"
+ [helperText]="helpTextLabels.tiertypeText"
i18n-label
>
<option value=""
[id]="'glacier_restore_days'"
[formControlName]="'glacier_restore_days'"
[label]="'Glacier Restore Days'"
- [helperText]="textLabels.restoreDaysText"
+ [helperText]="helpTextLabels.restoreDaysText"
[min]="1"
i18n-helperText
i18n-label
<cds-text-label
labelInputID="multipart_sync_threshold"
i18n
- [helperText]="textLabels.multipartSyncThresholdText"
+ [helperText]="helpTextLabels.multipartSyncThresholdText"
cdOptionalField="Multipart Sync Threshold"
>Multipart Sync Threshold
<input
<cds-text-label
labelInputID="multipart_min_part_size"
i18n
- [helperText]="textLabels.multipartMinPartText"
+ [helperText]="helpTextLabels.multipartMinPartText"
cdOptionalField="Multipart Minimum Part Size"
>Multipart Minimum Part Size
<input
component.storageClassForm.get('storage_class').setValue(storageClassName);
component.storageClassForm.get('zonegroup').setValue('zonegroup1');
component.storageClassForm.get('placement_target').setValue('placement1');
- component.storageClassForm.get('endpoint').setValue('http://ams03.com');
+ component.storageClassForm.get('target_endpoint').setValue('http://ceph-node-00:8090');
component.storageClassForm.get('access_key').setValue('accesskey');
component.storageClassForm.get('secret_key').setValue('secretkey');
component.storageClassForm.get('target_path').setValue('/target');
it('should set required validators for CLOUD_TIER fields', () => {
(component as any).updateValidatorsBasedOnStorageClass(TIER_TYPE_DISPLAY.CLOUD_TIER);
- const requiredFields = ['region', 'endpoint', 'access_key', 'secret_key', 'target_path'];
+ const requiredFields = ['region', 'target_endpoint', 'access_key', 'secret_key', 'target_path'];
requiredFields.forEach((field) => {
const control = component.storageClassForm.get(field);
control.setValue('');
(component as any).updateValidatorsBasedOnStorageClass(TIER_TYPE_DISPLAY.GLACIER);
const requiredFields = [
'region',
- 'endpoint',
+ 'target_endpoint',
'access_key',
'secret_key',
'target_path',
const allFields = [
'region',
- 'endpoint',
+ 'target_endpoint',
'access_key',
'secret_key',
'target_path',
STORAGE_CLASS_CONSTANTS,
STANDARD_TIER_TYPE_TEXT,
EXPEDITED_TIER_TYPE_TEXT,
- TextLabels
+ TextLabels,
+ CLOUD_TIER_REQUIRED_FIELDS,
+ GLACIER_REQUIRED_FIELDS
} from '../models/rgw-storage-class.model';
import { NotificationType } from '~/app/shared/enum/notification-type.enum';
import { NotificationService } from '~/app/shared/services/notification.service';
import { CdValidators } from '~/app/shared/forms/cd-validators';
+import { FormatterService } from '~/app/shared/services/formatter.service';
@Component({
selector: 'cd-rgw-storage-class-form',
TIER_TYPE = TIER_TYPE;
TIER_TYPE_DISPLAY = TIER_TYPE_DISPLAY;
storageClassOptions: StorageClassOption[];
- textLabels: TextLabels;
+ helpTextLabels: TextLabels;
constructor(
public actionLabels: ActionLabelsI18n,
private rgwStorageService: RgwStorageClassService,
private rgwZoneGroupService: RgwZonegroupService,
private router: Router,
- private route: ActivatedRoute
+ private route: ActivatedRoute,
+ public formatter: FormatterService
) {
super();
this.resource = $localize`Tiering Storage Class`;
}
ngOnInit() {
- this.textLabels = {
+ this.helpTextLabels = {
targetPathText: TARGET_PATH_TEXT,
targetEndpointText: TARGET_ENDPOINT_TEXT,
targetRegionText: TARGET_REGION_TEXT,
];
this.createForm();
this.storageClassTypeText();
- this.TierTypeText();
+ this.updateTierTypeHelpText();
this.loadingReady();
this.loadZoneGroup();
if (this.editing) {
this.storageClassForm.get('zonegroup').disable();
this.storageClassForm.get('placement_target').disable();
this.storageClassForm.get('storage_class').disable();
+ if (
+ this.tierTargetInfo?.val?.tier_type === TIER_TYPE.CLOUD_TIER ||
+ this.tierTargetInfo?.val?.tier_type === TIER_TYPE.GLACIER
+ ) {
+ this.storageClassForm.get('storageClassType').disable();
+ }
this.storageClassForm.patchValue({
zonegroup: this.storageClassInfo?.zonegroup_name,
region: response?.region,
placement_target: this.storageClassInfo?.placement_target,
storageClassType: this.tierTargetInfo?.val?.tier_type ?? TIER_TYPE.LOCAL,
- endpoint: response?.endpoint,
+ target_endpoint: response?.endpoint,
storage_class: this.storageClassInfo?.storage_class,
access_key: response?.access_key,
secret_key: response?.secret,
}
private updateValidatorsBasedOnStorageClass(value: string) {
- const controlsToUpdate = [
- 'region',
- 'endpoint',
- 'access_key',
- 'secret_key',
- 'target_path',
- 'glacier_restore_tier_type',
- 'restore_storage_class'
- ];
-
- controlsToUpdate.forEach((field) => {
+ GLACIER_REQUIRED_FIELDS.forEach((field) => {
const control = this.storageClassForm.get(field);
if (
- (value === TIER_TYPE.CLOUD_TIER &&
- ['region', 'endpoint', 'access_key', 'secret_key', 'target_path'].includes(field)) ||
- (value === TIER_TYPE.GLACIER &&
- [
- 'glacier_restore_tier_type',
- 'restore_storage_class',
- 'region',
- 'endpoint',
- 'access_key',
- 'secret_key',
- 'target_path'
- ].includes(field))
+ (value === TIER_TYPE.CLOUD_TIER && CLOUD_TIER_REQUIRED_FIELDS.includes(field)) ||
+ (value === TIER_TYPE.GLACIER && GLACIER_REQUIRED_FIELDS.includes(field))
) {
control.setValidators([Validators.required]);
} else {
control.clearValidators();
}
-
control.updateValueAndValidity();
});
+
+ if (this.editing) {
+ const defaultValues = {
+ allow_read_through: false,
+ read_through_restore_days: STORAGE_CLASS_CONSTANTS.DEFAULT_READTHROUGH_RESTORE_DAYS,
+ restore_storage_class: STORAGE_CLASS_CONSTANTS.DEFAULT_STORAGE_CLASS,
+ multipart_min_part_size: STORAGE_CLASS_CONSTANTS.DEFAULT_MULTIPART_MIN_PART_SIZE,
+ multipart_sync_threshold: STORAGE_CLASS_CONSTANTS.DEFAULT_MULTIPART_SYNC_THRESHOLD
+ };
+ Object.keys(defaultValues).forEach((key) => {
+ this.storageClassForm.get(key).setValue(defaultValues[key]);
+ });
+ }
}
storageClassTypeText() {
this.storageClassForm?.get('storageClassType')?.valueChanges.subscribe((value) => {
if (value === TIER_TYPE.LOCAL) {
- this.textLabels.storageClassText = LOCAL_STORAGE_CLASS_TEXT;
+ this.helpTextLabels.storageClassText = LOCAL_STORAGE_CLASS_TEXT;
} else if (value === TIER_TYPE.CLOUD_TIER) {
- this.textLabels.storageClassText = CLOUDS3_STORAGE_CLASS_TEXT;
+ this.helpTextLabels.storageClassText = CLOUDS3_STORAGE_CLASS_TEXT;
} else if (value === TIER_TYPE.GLACIER) {
- this.textLabels.storageClassText = GLACIER_STORAGE_CLASS_TEXT;
+ this.helpTextLabels.storageClassText = GLACIER_STORAGE_CLASS_TEXT;
}
});
}
- TierTypeText() {
+ updateTierTypeHelpText() {
this.storageClassForm?.get('glacier_restore_tier_type')?.valueChanges.subscribe((value) => {
if (value === STORAGE_CLASS_CONSTANTS.DEFAULT_STORAGE_CLASS) {
- this.textLabels.tiertypeText = STANDARD_TIER_TYPE_TEXT;
+ this.helpTextLabels.tiertypeText = STANDARD_TIER_TYPE_TEXT;
} else {
- this.textLabels.tiertypeText = EXPEDITED_TIER_TYPE_TEXT;
+ this.helpTextLabels.tiertypeText = EXPEDITED_TIER_TYPE_TEXT;
}
});
}
placement_target: new FormControl('', {
validators: [Validators.required]
}),
- endpoint: new FormControl(null, [
- CdValidators.composeIf({ storageClassType: TIER_TYPE.CLOUD_TIER }, [Validators.required])
- ]),
+ target_endpoint: new FormControl(null, {
+ validators: [CdValidators.url, Validators.required]
+ }),
access_key: new FormControl(null, [
CdValidators.composeIf({ storageClassType: TIER_TYPE.CLOUD_TIER }, [Validators.required])
]),
}
getTierTargetByStorageClass(placementTargetInfo: PlacementTarget, storageClass: string) {
- const tierTarget = placementTargetInfo.tier_targets.find(
+ const tierTarget = placementTargetInfo?.tier_targets?.find(
(target: TierTarget) => target.val.storage_class === storageClass
);
return tierTarget;
const placementId = this.storageClassForm.get('placement_target').value;
const storageClassType = this.storageClassForm.get('storageClassType').value;
const retain_head_object = this.storageClassForm.get('retain_head_object').value;
+ const multipart_min_part_size = this.formatter.toBytes(
+ this.storageClassForm.get('multipart_min_part_size').value
+ );
+ const multipart_sync_threshold = this.formatter.toBytes(
+ this.storageClassForm.get('multipart_sync_threshold').value
+ );
return this.buildPlacementTargets(
storageClassType,
zoneGroup,
placementId,
storageClass,
retain_head_object,
- rawFormValue
+ rawFormValue,
+ multipart_sync_threshold,
+ multipart_min_part_size
);
}
placementId: string,
storageClass: string,
retain_head_object: boolean,
- rawFormValue: any
+ rawFormValue: any,
+ multipart_sync_threshold: number,
+ multipart_min_part_size: number
): RequestModel {
const baseTarget = {
placement_id: placementId,
}
const tierConfig = {
- endpoint: rawFormValue.endpoint,
+ endpoint: rawFormValue.target_endpoint,
access_key: rawFormValue.access_key,
secret: rawFormValue.secret_key,
target_path: rawFormValue.target_path,
retain_head_object,
allow_read_through: rawFormValue.allow_read_through,
region: rawFormValue.region,
- multipart_sync_threshold: rawFormValue.multipart_sync_threshold,
- multipart_min_part_size: rawFormValue.multipart_min_part_size,
+ multipart_sync_threshold: multipart_sync_threshold,
+ multipart_min_part_size: multipart_min_part_size,
restore_storage_class: rawFormValue.restore_storage_class,
...(rawFormValue.allow_read_through
? { read_through_restore_days: rawFormValue.read_through_restore_days }
(data: ZoneGroupDetails) => {
this.storageClassList = [];
const tierObj = BucketTieringUtils.filterAndMapTierTargets(data);
- const tierConfig = tierObj.map((item) => {
- let tierTypeDisplay;
+ const tierConfig = tierObj.map((tier) => ({
+ ...tier,
+ tier_type: this.mapTierTypeDisplay(tier.tier_type)
+ }));
- switch (item.tier_type?.toLowerCase()) {
- case TIER_TYPE.CLOUD_TIER:
- tierTypeDisplay = TIER_TYPE_DISPLAY.CLOUD_TIER;
- break;
- case TIER_TYPE.LOCAL:
- tierTypeDisplay = TIER_TYPE_DISPLAY.LOCAL;
- break;
- case TIER_TYPE.GLACIER:
- tierTypeDisplay = TIER_TYPE_DISPLAY.GLACIER;
- break;
- default:
- tierTypeDisplay = item.tier_type;
- }
- return {
- ...item,
- tier_type: tierTypeDisplay
- };
- });
this.transformTierData(tierConfig);
this.storageClassList.push(...tierConfig);
resolve();
});
}
+ private mapTierTypeDisplay(tierType: string): string {
+ switch (tierType?.toLowerCase()) {
+ case TIER_TYPE.CLOUD_TIER:
+ return TIER_TYPE_DISPLAY.CLOUD_TIER;
+ case TIER_TYPE.LOCAL:
+ return TIER_TYPE_DISPLAY.LOCAL;
+ case TIER_TYPE.GLACIER:
+ return TIER_TYPE_DISPLAY.GLACIER;
+ default:
+ return tierType;
+ }
+ }
+
transformTierData(tierConfig: any[]) {
tierConfig.forEach((item, index) => {
const zone_group = item?.zone_group;
private static getTierTargets(tierTarget: TierTarget, zoneGroup: string, targetName: string) {
const val = tierTarget.val;
const tierType = val.tier_type;
-
const commonProps = {
zonegroup_name: zoneGroup,
placement_target: targetName,
storage_class: val.storage_class,
tier_type: tierType
};
-
- if (!tierType || tierType === TIER_TYPE.LOCAL) {
- return commonProps;
- }
const cloudProps = {
...commonProps,
retain_head_object: val.retain_head_object,
...val.s3
};
+ if (!tierType || tierType === TIER_TYPE.LOCAL) {
+ return commonProps;
+ }
+
if (tierType === TIER_TYPE.GLACIER) {
return {
...cloudProps,
'--placement-id', placement_target['placement_id']
]
storage_class_name = placement_target.get('storage_class', None)
- tier_type = placement_target.get('tier_type')
+ tier_type = placement_target.get('tier_type', None)
- if (
- placement_target.get('tier_type') == CLOUD_S3_TIER_TYPES
- and storage_class_name != STANDARD_STORAGE_CLASS
- ):
+ if tier_type in CLOUD_S3_TIER_TYPES and storage_class_name != STANDARD_STORAGE_CLASS:
tier_config = placement_target.get('tier_config', {})
if tier_config:
tier_config_items = self.modify_retain_head(tier_config)
tier_config_str = ','.join(tier_config_items)
cmd_add_placement_options += [
- '--tier-type', 'cloud-s3', '--tier-config', tier_config_str
+ '--tier-type', tier_type, '--tier-config', tier_config_str
]
if placement_target.get('tags') and storage_class_name != STANDARD_STORAGE_CLASS: