}
export interface StorageClass {
- storage_class: string;
- endpoint: string;
- region: string;
placement_target: string;
+ storage_class?: string;
+ endpoint?: string;
+ region?: string;
zonegroup_name?: string;
}
}
export interface TierTarget {
+ key: string;
val: {
storage_class: string;
tier_type: string;
retain_head_object: boolean;
allow_read_through: boolean;
- s3: S3Details;
+ s3?: S3Details;
};
}
export interface Target {
name: string;
tier_targets: TierTarget[];
+ storage_classes?: string[];
+}
+
+export interface StorageClassDetails {
+ target_path: string;
+ access_key: string;
+ secret: string;
+ multipart_min_part_size: number;
+ multipart_sync_threshold: number;
+ host_style: string;
+ zonegroup_name?: string;
+ placement_targets?: string;
}
export interface ZoneGroup {
export interface PlacementTarget {
tags: string[];
placement_id: string;
- tier_type: typeof CLOUD_TIER;
- tier_config: {
+ tier_type?: TIER_TYPE;
+ tier_config?: {
endpoint: string;
access_key: string;
secret: string;
tier_targets?: TierTarget[];
}
-export const CLOUD_TIER = 'cloud-s3';
+export const TIER_TYPE = {
+ LOCAL: 'local',
+ CLOUD_TIER: 'cloud-s3',
+ GLACIER: 'cloud-s3-glacier'
+} as const;
export const DEFAULT_PLACEMENT = 'default-placement';
export const HOST_STYLE = `The URL format for accessing the remote S3 endpoint:
- 'Path': Use for a path-based URL
- 'Virtual': Use for a domain-based URL`;
+
+export const LOCAL_STORAGE_CLASS_TEXT = $localize`Local storage uses on-premises or directly attached devices for data storage.`;
+
+export const CLOUDS3_STORAGE_CLASS_TEXT = $localize`Cloud S3 storage uses Amazon S3-compatible cloud services for tiering.`;
+
+export type TIER_TYPE = typeof TIER_TYPE[keyof typeof TIER_TYPE];
+
+export const TIER_TYPE_DISPLAY = {
+ LOCAL: 'Local',
+ CLOUD_TIER: 'Cloud S3',
+ GLACIER: 'Cloud S3 Glacier'
+};
data-testid="rgw-storage-details"
>
<tbody>
- <tr>
- <td class="bold">
+ <tr *ngIf="selection.tier_type === TIER_TYPE_DISPLAY.LOCAL">
+ <td class="bold"
+ i18n>
+ Zone Group
+ <cd-helper class="text-pre-wrap">
+ <span>
+ A Zone Group is a logical grouping of one or more zones that share the same data
+ and metadata, allowing for multi-site replication and geographic distribution of
+ data.
+ </span>
+ </cd-helper>
+ </td>
+ <td>{{ selection?.zonegroup_name }}</td>
+ </tr>
+ <tr *ngIf="selection.tier_type === TIER_TYPE_DISPLAY.LOCAL">
+ <td class="bold"
+ i18n>
+ Placement Target
+ <cd-helper class="text-pre-wrap">
+ <span>
+ Placement Target defines the destination and rules for moving objects between
+ storage tiers.
+ </span>
+ </cd-helper>
+ </td>
+ <td>{{ selection?.placement_target }}</td>
+ </tr>
+ <tr *ngIf="selection.tier_type === TIER_TYPE_DISPLAY.CLOUD_TIER">
+ <td class="bold"
+ i18n>
Target Path
<cd-helper class="text-pre-wrap">
- <span i18n>
+ <span>
{{ targetPathText }}
</span>
</cd-helper>
</td>
<td>{{ selection?.target_path }}</td>
</tr>
- <tr>
- <td class="bold">
+ <tr *ngIf="selection.tier_type === TIER_TYPE_DISPLAY.CLOUD_TIER">
+ <td class="bold"
+ i18n>
Access key
<cd-helper class="text-pre-wrap">
- <span i18n>
+ <span>
{{ targetAccessKeyText }}
</span>
</cd-helper>
</div>
</td>
</tr>
- <tr>
- <td class="bold">
+ <tr *ngIf="selection.tier_type === TIER_TYPE_DISPLAY.CLOUD_TIER">
+ <td class="bold"
+ i18n>
Secret key
<cd-helper class="text-pre-wrap">
- <span i18n> {{ targetSecretKeyText }} </span>
+ <span> {{ targetSecretKeyText }} </span>
</cd-helper>
</td>
<td>
</div>
</td>
</tr>
- <tr>
- <td class="bold">
+ <tr *ngIf="selection.tier_type === TIER_TYPE_DISPLAY.CLOUD_TIER">
+ <td class="bold"
+ i18n>
Host Style
<cd-helper class="text-pre-wrap">
- <span i18n>{{ hostStyleText }}</span
- >
+ <span>{{ hostStyleText }}</span>
</cd-helper>
</td>
<td>{{ selection?.host_style }}</td>
</tr>
- <tr>
- <td class="bold">
+ <tr *ngIf="selection.tier_type === TIER_TYPE_DISPLAY.CLOUD_TIER">
+ <td class="bold"
+ i18n>
Multipart Minimum Part Size
<cd-helper class="text-pre-wrap">
- <span i18n>
+ <span>
{{ multipartMinPartText }}
</span>
</cd-helper>
</td>
<td>{{ selection?.multipart_min_part_size }}</td>
</tr>
- <tr>
- <td class="bold">
+ <tr *ngIf="selection.tier_type === TIER_TYPE_DISPLAY.CLOUD_TIER">
+ <td class="bold"
+ i18n>
Multipart Sync Threshold
<cd-helper class="text-pre-wrap">
- <span i18n>
- {{ multipartSyncThreholdText }}
+ <span>
+ {{ multipartSyncThreholdText }}
</span>
</cd-helper>
</td>
<td>{{ selection?.multipart_sync_threshold }}</td>
</tr>
- <tr>
- <td class="bold">
- <span i18n>Allow Read Through </span>
+ <tr *ngIf="selection.tier_type === TIER_TYPE_DISPLAY.CLOUD_TIER">
+ <td class="bold"
+ i18n>
+ Retain Head Object
<cd-helper class="text-pre-wrap">
- <span i18n>
- {{ allowReadThroughText }}
+ <span>
+ Retain object metadata after transition to the cloud (default: false).
</span>
</cd-helper>
</td>
- <td>{{ selection?.allow_read_through }}</td>
+ <td>{{ selection?.retain_head_object }}</td>
</tr>
- <tr>
- <td class="bold">
- Head Object (Stub File)
+ <tr *ngIf="selection.tier_type === TIER_TYPE_DISPLAY.CLOUD_TIER">
+ <td class="bold"
+ i18n>
+ Allow Read Through
<cd-helper class="text-pre-wrap">
- <span i18n>
- {{ retainHeadObjectText }}
+ <span>
+ {{ allowReadThroughText }}
</span>
</cd-helper>
</td>
- <td>{{ selection?.retain_head_object }}</td>
+ <td>{{ selection?.allow_read_through }}</td>
</tr>
</tbody>
</table>
StorageClassDetails,
TARGET_ACCESS_KEY_TEXT,
TARGET_PATH_TEXT,
- TARGET_SECRET_KEY_TEXT
+ TARGET_SECRET_KEY_TEXT,
+ TIER_TYPE_DISPLAY
} from '../models/rgw-storage-class.model';
-
@Component({
selector: 'cd-rgw-storage-class-details',
templateUrl: './rgw-storage-class-details.component.html',
targetSecretKeyText = TARGET_SECRET_KEY_TEXT;
targetPathText = TARGET_PATH_TEXT;
hostStyleText = HOST_STYLE;
+ TIER_TYPE_DISPLAY = TIER_TYPE_DISPLAY;
ngOnChanges() {
if (this.selection) {
this.storageDetails = {
+ zonegroup_name: this.selection.zonegroup_name,
+ placement_targets: this.selection.placement_targets,
access_key: this.selection.access_key,
secret: this.selection.secret,
target_path: this.selection.target_path,
<cd-help-text [formAllFieldsRequired]="true"></cd-help-text>
</div>
+ <div class="form-item">
+ <cds-select
+ label="Type"
+ i18n-label
+ for="storageClassType"
+ formControlName="storageClassType"
+ [helperText]="storageClassText"
+ id="storageClassType"
+ [invalid]="storageClassForm.showError('storageClassType', formDir, 'required')"
+ [invalidText]="storageError"
+ >
+ <option value=""
+ i18n>-- Select Storage Class --</option>
+ <option value="local"
+ i18n>Local</option>
+ <option value="cloud-s3"
+ i18n>Cloud S3</option>
+ </cds-select>
+ <ng-template #storageError>
+ <span
+ class="invalid-feedback"
+ *ngIf="storageClassForm.showError('storageClassType', formDir, 'required')"
+ i18n
+ >This field is required.</span
+ >
+ </ng-template>
+ </div>
<div class="form-item form-item-append"
cdsRow>
<div cdsCol>
i18n-label
formControlName="zonegroup"
id="zonegroup"
- [invalid]="
- storageClassForm.showError('zonegroup', formDir, 'required')
- "
+ [invalid]="storageClassForm.showError('zonegroup', formDir, 'required')"
(change)="onZonegroupChange()"
[invalidText]="zonegroupError"
>
- <option *ngFor="let zonegrp of zonegroupNames"
- [value]="zonegrp.name"
- [selected]="zonegrp.name === storageClassForm.getValue('zonegroup')"
- i18n>
+ <option
+ *ngFor="let zonegrp of zonegroupNames"
+ [value]="zonegrp.name"
+ [selected]="zonegrp.name === storageClassForm.getValue('zonegroup')"
+ i18n
+ >
{{ zonegrp.name }}
</option>
</cds-select>
i18n-label
formControlName="placement_target"
id="placement_target"
- [invalid]="
- storageClassForm.showError('placement_target', formDir, 'required')
- "
+ [invalid]="storageClassForm.showError('placement_target', formDir, 'required')"
[invalidText]="placementError"
>
- <option [value]=""
- i18n> --Select-- </option>
- <option *ngFor="let placementTarget of placementTargets"
- [value]="placementTarget"
- [selected]="placementTarget === storageClassForm.getValue('placement_target')"
- i18n>
+ <option [value]=""
+ i18n>--Select--</option>
+ <option
+ *ngFor="let placementTarget of placementTargets"
+ [value]="placementTarget"
+ [selected]="placementTarget === storageClassForm.getValue('placement_target')"
+ i18n
+ >
{{ placementTarget }}
- </option>
+ </option>
</cds-select>
<ng-template #placementError>
<span
storageClassForm.controls.storage_class.invalid &&
storageClassForm.controls.storage_class.dirty
"
- [invalidText]="storageError"
- >Storage Class Name
+ [invalidText]="storageClassError"
+ >Name
<input
cdsText
type="type"
id="storage_class"
formControlName="storage_class"
- [invalid]="
- storageClassForm.showError('storage_class', formDir, 'required')
- "
+ [invalid]="storageClassForm.showError('storage_class', formDir, 'required')"
/>
</cds-text-label>
- <ng-template #storageError>
+ <ng-template #storageClassError>
<span
class="invalid-feedback"
*ngIf="storageClassForm.showError('storage_class', formDir, 'required')"
>
</ng-template>
</div>
- <div class="form-item form-item-append"
- cdsRow>
- <div cdsCol>
- <!-- Target Region -->
- <cds-text-label
- labelInputID="region"
- i18n
- [invalid]="
- storageClassForm.showError('region', formDir, 'required')
- "
- [invalidText]="regionError"
- [helperText]="targetRegionText"
- >Target Region
- <input
- cdsText
- type="text"
- id="region"
- formControlName="region"
- placeholder="e.g, us-east-1"
- i18n-placeholder
- [invalid]="
- storageClassForm.showError('region', formDir, 'required')"/>
- </cds-text-label>
- <ng-template #regionError>
- <span
- class="invalid-feedback"
- *ngIf="storageClassForm.showError('region', formDir, 'required')"
+ <div *ngIf="storageClassForm.getValue('storageClassType') === TIER_TYPE.CLOUD_TIER">
+ <div class="form-item form-item-append"
+ cdsRow>
+ <div cdsCol>
+ <!-- Target Region -->
+ <cds-text-label
+ labelInputID="region"
i18n
- >This field is required.</span
- >
- </ng-template>
+ [invalid]="storageClassForm.showError('region', formDir, 'required')"
+ [invalidText]="regionError"
+ [helperText]="targetRegionText"
+ >Target Region
+ <input
+ cdsText
+ type="text"
+ id="region"
+ formControlName="region"
+ placeholder="e.g, us-east-1"
+ i18n-placeholder
+ [invalid]="storageClassForm.showError('region', formDir, 'required')"
+ />
+ </cds-text-label>
+ <ng-template #regionError>
+ <span
+ class="invalid-feedback"
+ *ngIf="storageClassForm.showError('region', formDir, 'required')"
+ i18n
+ >This field is required.</span
+ >
+ </ng-template>
+ </div>
+ <div cdsCol>
+ <!-- Target Endpoint -->
+ <cds-text-label
+ labelInputID="endpoint"
+ i18n
+ [invalid]="storageClassForm.showError('endpoint', formDir, 'required')"
+ [invalidText]="endpointError"
+ [helperText]="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>
+ <ng-template #endpointError>
+ <span
+ class="invalid-feedback"
+ *ngIf="storageClassForm.showError('endpoint', formDir, 'required')"
+ i18n
+ >This field is required.</span
+ >
+ </ng-template>
+ </div>
</div>
- <div cdsCol>
- <!-- Target Endpoint -->
- <cds-text-label
- labelInputID="endpoint"
- i18n
- [invalid]="
- storageClassForm.showError('endpoint', formDir, 'required')
- "
- [invalidText]="endpointError"
- [helperText]="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>
- <ng-template #endpointError>
- <span
- class="invalid-feedback"
- *ngIf="storageClassForm.showError('endpoint', formDir, 'required')"
+
+ <!-- Access Key -->
+ <div class="form-item">
+ <div cdsCol
+ [columnNumbers]="{ md: 12 }"
+ class="d-flex">
+ <cds-password-label
+ labelInputID="access_key"
+ [invalid]="storageClassForm.showError('access_key', formDir, 'required')"
+ [invalidText]="accessError"
+ [helperText]="targetAccessKeyText"
i18n
- >This field is required.</span
- >
- </ng-template>
+ >Target Access Key
+ <input
+ cdsPassword
+ type="password"
+ id="access_key"
+ formControlName="access_key"
+ [invalid]="storageClassForm.showError('access_key', formDir, 'required')"
+ />
+ </cds-password-label>
+ <cd-copy-2-clipboard-button class="clipboard"> </cd-copy-2-clipboard-button>
+ <ng-template #accessError>
+ <span
+ class="invalid-feedback"
+ *ngIf="storageClassForm.showError('access_key', formDir, 'required')"
+ i18n
+ >This field is required.</span
+ >
+ </ng-template>
+ </div>
</div>
- </div>
- <!-- Access Key -->
- <div class="form-item">
- <div cdsCol
- [columnNumbers]="{ md: 12 }"
- class="d-flex">
- <cds-password-label
- labelInputID="access_key"
- [invalid]="
- storageClassForm.showError('access_key', formDir, 'required')
- "
- [invalidText]="accessError"
- [helperText]="targetAccessKeyText"
- i18n
- >Target Access Key
- <input
- cdsPassword
- type="password"
- id="access_key"
- formControlName="access_key"
- [invalid]="
- storageClassForm.showError('access_key', formDir, 'required')
- "
- />
- </cds-password-label>
- <cd-copy-2-clipboard-button class="clipboard"> </cd-copy-2-clipboard-button>
- <ng-template #accessError>
- <span
- class="invalid-feedback"
- *ngIf="storageClassForm.showError('access_key', formDir, 'required')"
+ <!-- Secret Key -->
+ <div class="form-item">
+ <div cdsCol
+ [columnNumbers]="{ md: 12 }"
+ class="d-flex">
+ <cds-password-label
+ labelInputID="secret_key"
+ [helperText]="targetSecretKeyText"
+ [invalid]="storageClassForm.showError('secret_key', formDir, 'required')"
+ [invalidText]="secretError"
i18n
- >This field is required.</span
- >
- </ng-template>
+ >Target Secret Key
+ <input
+ cdsPassword
+ type="password"
+ id="secret_key"
+ formControlName="secret_key"
+ [invalid]="storageClassForm.showError('secret_key', formDir, 'required')"
+ />
+ </cds-password-label>
+ <cd-copy-2-clipboard-button class="clipboard"> </cd-copy-2-clipboard-button>
+ <ng-template #secretError>
+ <span
+ class="invalid-feedback"
+ *ngIf="storageClassForm.showError('secret_key', formDir, 'required')"
+ i18n
+ >This field is required.</span
+ >
+ </ng-template>
+ </div>
</div>
- </div>
- <!-- Secret Key -->
- <div class="form-item">
- <div cdsCol
- [columnNumbers]="{ md: 12 }"
- class="d-flex">
- <cds-password-label
- labelInputID="secret_key"
- [helperText]="targetSecretKeyText"
- [invalid]="
- storageClassForm.showError('secret_key', formDir, 'required')
- "
- [invalidText]="secretError"
+ <!-- Target Path -->
+ <div class="form-item">
+ <cds-text-label
+ labelInputID="target_path"
i18n
- >Target Secret Key
+ [invalid]="storageClassForm.showError('target_path', formDir, 'required')"
+ [invalidText]="targetError"
+ [helperText]="targetPathText"
+ >Target Path
<input
- cdsPassword
- type="password"
- id="secret_key"
- formControlName="secret_key"
- [invalid]="
- storageClassForm.showError('secret_key', formDir, 'required')
- "
+ cdsText
+ type="text"
+ id="target_path"
+ formControlName="target_path"
+ [invalid]="storageClassForm.showError('target_path', formDir, 'required')"
/>
- </cds-password-label>
- <cd-copy-2-clipboard-button class="clipboard"> </cd-copy-2-clipboard-button>
- <ng-template #secretError>
+ </cds-text-label>
+ <ng-template #targetError>
<span
class="invalid-feedback"
- *ngIf="storageClassForm.showError('secret_key', formDir, 'required')"
+ *ngIf="storageClassForm.showError('target_path', formDir, 'required')"
i18n
>This field is required.</span
>
</ng-template>
</div>
</div>
-
- <!-- Target Path -->
- <div class="form-item">
- <cds-text-label
- labelInputID="target_path"
- i18n
- [invalid]="
- storageClassForm.showError('target_path', formDir, 'required')
- "
- [invalidText]="targetError"
- [helperText]="targetPathText"
- >Target Path
- <input
- cdsText
- type="text"
- id="target_path"
- formControlName="target_path"
- [invalid]="
- storageClassForm.showError('target_path', formDir, 'required')
- "
- />
- </cds-text-label>
- <ng-template #targetError>
- <span
- class="invalid-feedback"
- *ngIf="storageClassForm.showError('target_path', formDir, 'required')"
- i18n
- >This field is required.</span
- >
+ <div *ngIf="storageClassForm.getValue('storageClassType') === TIER_TYPE.CLOUD_TIER">
+ <ng-template #title>
+ <h5 class="cds--accordion__title cd-header">Advanced</h5>
</ng-template>
- </div>
- <div class="form-item">
- <cds-checkbox
- id="allow_read_through"
- formControlName="allow_read_through"
- cdOptionalField="Allow Read Through"
- i18n
- (change)="onAllowReadThroughChange($event)"
- >Allow Read Through
- <cd-help-text>{{ allowReadThroughText }}</cd-help-text>
- </cds-checkbox>
- </div>
- <div class="form-item">
- <cds-checkbox
- id="retain_head_object"
- formControlName="retain_head_object"
- cdOptionalField="Head Object (Stub File)"
- i18n
- >Head Object (Stub File)
- <cd-help-text>{{ retainHeadObjectText }}</cd-help-text>
- </cds-checkbox>
- </div>
-
- <fieldset>
- <cds-accordion size="lg"
- class="form-item">
- <cds-accordion-item
- [title]="title"
- id="advanced-fieldset"
- (selected)="showAdvanced = !showAdvanced"
- >
- <!-- Multi Part Sync Threshold -->
- <div class="form-item form-item-append"
- cdsRow>
- <div cdsCol>
- <cds-text-label
- labelInputID="multipart_sync_threshold"
+ <fieldset>
+ <cds-accordion size="lg"
+ class="form-item">
+ <cds-accordion-item
+ [title]="title"
+ id="advanced-fieldset"
+ (selected)="showAdvanced = !showAdvanced"
+ >
+ <!-- Multi Part Sync Threshold -->
+ <div class="form-item form-item-append"
+ cdsRow>
+ <div cdsCol>
+ <cds-text-label
+ labelInputID="multipart_sync_threshold"
+ i18n
+ [helperText]="multipartSyncThreholdText"
+ cdOptionalField="Multipart Sync Threshold"
+ >Multipart Sync Threshold
+ <input
+ cdsText
+ type="text"
+ id="multipart_sync_threshold"
+ formControlName="multipart_sync_threshold"
+ />
+ </cds-text-label>
+ </div>
+ <div cdsCol>
+ <cds-text-label
+ labelInputID="multipart_min_part_size"
+ i18n
+ [helperText]="multipartMinPartText"
+ cdOptionalField="Multipart Minimum Part Size"
+ >Multipart Minimum Part Size
+ <input
+ cdsText
+ type="text"
+ id="multipart_min_part_size"
+ formControlName="multipart_min_part_size"
+ />
+ </cds-text-label>
+ </div>
+ </div>
+ <div class="form-item">
+ <cds-checkbox
+ id="allow_read_through"
+ formControlName="allow_read_through"
+ cdOptionalField="Allow Read Through"
i18n
- [helperText]="multipartSyncThreholdText"
- cdOptionalField="Multipart Sync Threshold"
- >Multipart Sync Threshold
- <input
- cdsText
- type="text"
- id="multipart_sync_threshold"
- formControlName="multipart_sync_threshold"
- />
- </cds-text-label>
+ (change)="onAllowReadThroughChange($event)"
+ >Allow Read Through
+ <cd-help-text>{{ allowReadThroughText }}</cd-help-text>
+ </cds-checkbox>
</div>
- <div cdsCol>
- <cds-text-label
- labelInputID="multipart_min_part_size"
+ <div class="form-item">
+ <cds-checkbox
+ id="retain_head_object"
+ formControlName="retain_head_object"
+ cdOptionalField="Head Object (Stub File)"
i18n
- [helperText]="multipartMinPartText"
- cdOptionalField="Multipart Minimum Part Size"
- >Multipart Minimum Part Size
- <input
- cdsText
- type="text"
- id="multipart_min_part_size"
- formControlName="multipart_min_part_size"
- />
- </cds-text-label>
+ >Head Object (Stub File)
+ <cd-help-text>{{ retainHeadObjectText }}</cd-help-text>
+ </cds-checkbox>
</div>
- </div>
- </cds-accordion-item>
- </cds-accordion>
- <ng-template #title>
- <h5 class="cds--accordion__title cd-header">Advanced</h5>
- </ng-template>
- </fieldset>
- <cd-alert-panel type="warning"
- spacingClass="mb-2">
+ </cds-accordion-item>
+ </cds-accordion>
+ </fieldset>
+ </div>
+ <cd-alert-panel
+ type="warning"
+ spacingClass="mb-2"
+ *ngIf="storageClassForm.getValue('storageClassType') === TIER_TYPE.CLOUD_TIER"
+ >
<span i18n>RGW service would be restarted after creating the storage class.</span>
</cd-alert-panel>
<cd-form-button-panel
name: 'default-placement',
tier_targets: [
{
+ key: 'test',
val: {
- storage_class: 'CLOUDIBM',
tier_type: 'cloud-s3',
retain_head_object: true,
+ storage_class: 'CLOUDIBM',
allow_read_through: true,
s3: {
+ storage_class: 'CLOUDIBM',
endpoint: 'https://s3.amazonaws.com',
access_key: 'ACCESSKEY',
- storage_class: 'STANDARD',
target_path: '/path/to/storage',
target_storage_class: 'STANDARD',
region: 'useastr1',
}
}
]
- },
- {
- name: 'placement1',
- tier_targets: [
- {
- val: {
- storage_class: 'CloudIBM',
- tier_type: 'cloud-s3',
- retain_head_object: true,
- allow_read_through: true,
- s3: {
- endpoint: 'https://s3.amazonaws.com',
- access_key: 'ACCESSKEY',
- storage_class: 'GLACIER',
- target_path: '/pathStorage',
- target_storage_class: 'CloudIBM',
- region: 'useast1',
- secret: 'SECRETKEY',
- multipart_min_part_size: 187988787,
- multipart_sync_threshold: 878787878,
- host_style: false
- }
- }
- }
- ]
}
]
}
};
component.storageClassForm.get('zonegroup').setValue('zonegroup1');
component.onZonegroupChange();
- expect(component.placementTargets).toEqual(['default-placement', 'placement1']);
+ expect(component.placementTargets).toEqual(['default-placement']);
expect(component.storageClassForm.get('placement_target').value).toBe('default-placement');
});
import { RgwZonegroupService } from '~/app/shared/api/rgw-zonegroup.service';
import {
ALLOW_READ_THROUGH_TEXT,
- CLOUD_TIER,
DEFAULT_PLACEMENT,
MULTIPART_MIN_PART_TEXT,
MULTIPART_SYNC_THRESHOLD_TEXT,
TARGET_REGION_TEXT,
TARGET_SECRET_KEY_TEXT,
TierTarget,
+ TIER_TYPE,
ZoneGroup,
- ZoneGroupDetails
+ ZoneGroupDetails,
+ CLOUDS3_STORAGE_CLASS_TEXT,
+ LOCAL_STORAGE_CLASS_TEXT
} 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';
@Component({
selector: 'cd-rgw-storage-class-form',
zonegroupNames: ZoneGroup[];
placementTargets: string[] = [];
multipartMinPartText: string;
+ storageClassText: string;
multipartSyncThreholdText: string;
selectedZoneGroup: string;
defaultZonegroup: ZoneGroup;
tierTargetInfo: TierTarget;
allowReadThroughText: string;
allowReadThrough: boolean = false;
+ TIER_TYPE = TIER_TYPE;
constructor(
public actionLabels: ActionLabelsI18n,
this.targetSecretKeyText = TARGET_SECRET_KEY_TEXT;
this.retainHeadObjectText = RETAIN_HEAD_OBJECT_TEXT;
this.allowReadThroughText = ALLOW_READ_THROUGH_TEXT;
+ this.storageClassText = LOCAL_STORAGE_CLASS_TEXT;
+ this.storageClassTypeText();
this.createForm();
this.loadingReady();
this.loadZoneGroup();
placementTargetInfo,
this.storageClassInfo.storage_class
);
- let response = this.tierTargetInfo.val.s3;
+ 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.secret);
- this.storageClassForm.get('target_path').setValue(response.target_path);
- this.storageClassForm
- .get('retain_head_object')
- .setValue(this.tierTargetInfo?.val?.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 || '');
- this.storageClassForm
- .get('allow_read_through')
- .setValue(this.tierTargetInfo?.val?.allow_read_through || false);
+ 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,
+ storage_class: this.storageClassInfo?.storage_class,
+ access_key: response?.access_key,
+ secret_key: response?.secret,
+ target_path: response?.target_path,
+ retain_head_object: this.tierTargetInfo?.val?.retain_head_object || false,
+ multipart_sync_threshold: response?.multipart_sync_threshold || '',
+ multipart_min_part_size: response?.multipart_min_part_size || '',
+ allow_read_through: this.tierTargetInfo?.val?.allow_read_through || false
+ });
});
}
+ this.storageClassForm?.get('storageClassType')?.valueChanges.subscribe((value) => {
+ const controlsToUpdate = ['region', 'endpoint', 'access_key', 'secret_key', 'target_path'];
+ controlsToUpdate.forEach((field) => {
+ const control = this.storageClassForm.get(field);
+ if (
+ value === TIER_TYPE.CLOUD_TIER &&
+ ['region', 'endpoint', 'access_key', 'secret_key', 'target_path'].includes(field)
+ ) {
+ control.setValidators([Validators.required]);
+ } else {
+ control.clearValidators();
+ }
+
+ control.updateValueAndValidity();
+ });
+ });
this.storageClassForm.get('allow_read_through').valueChanges.subscribe((value) => {
this.onAllowReadThroughChange(value);
});
}
+ storageClassTypeText() {
+ this.storageClassForm?.get('storageClassType')?.valueChanges.subscribe((value) => {
+ if (value === TIER_TYPE.LOCAL) {
+ this.storageClassText = LOCAL_STORAGE_CLASS_TEXT;
+ } else if (value === TIER_TYPE.CLOUD_TIER) {
+ this.storageClassText = CLOUDS3_STORAGE_CLASS_TEXT;
+ } else {
+ this.storageClassText = LOCAL_STORAGE_CLASS_TEXT;
+ }
+ });
+ }
+
createForm() {
this.storageClassForm = this.formBuilder.group({
storage_class: new FormControl('', {
zonegroup: new FormControl(this.selectedZoneGroup, {
validators: [Validators.required]
}),
- region: new FormControl('', {
- validators: [Validators.required]
- }),
+ region: new FormControl('', [
+ CdValidators.composeIf({ storageClassType: TIER_TYPE.CLOUD_TIER }, [Validators.required])
+ ]),
placement_target: new FormControl('', {
validators: [Validators.required]
}),
- endpoint: new FormControl(null, {
- validators: [Validators.required]
- }),
- access_key: new FormControl(null, Validators.required),
- secret_key: new FormControl(null, Validators.required),
- target_path: new FormControl('', {
- validators: [Validators.required]
- }),
+ endpoint: new FormControl(null, [
+ CdValidators.composeIf({ storageClassType: TIER_TYPE.CLOUD_TIER }, [Validators.required])
+ ]),
+ access_key: new FormControl(null, [
+ CdValidators.composeIf({ storageClassType: TIER_TYPE.CLOUD_TIER }, [Validators.required])
+ ]),
+ secret_key: new FormControl(null, [
+ CdValidators.composeIf({ storageClassType: TIER_TYPE.CLOUD_TIER }, [Validators.required])
+ ]),
+ target_path: new FormControl('', [
+ CdValidators.composeIf({ storageClassType: TIER_TYPE.CLOUD_TIER }, [Validators.required])
+ ]),
retain_head_object: new FormControl(true),
multipart_sync_threshold: new FormControl(33554432),
multipart_min_part_size: new FormControl(33554432),
- allow_read_through: new FormControl(false)
+ allow_read_through: new FormControl(false),
+ storageClassType: new FormControl(TIER_TYPE.LOCAL, Validators.required)
});
}
}
buildRequest() {
+ if (this.storageClassForm.errors) return null;
+
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 headObject = this.storageClassForm.get('retain_head_object').value;
- const requestModel: RequestModel = {
- zone_group: zoneGroup,
- placement_targets: [
- {
- tags: [],
- placement_id: placementId,
- storage_class: storageClass,
- tier_type: CLOUD_TIER,
- tier_config: {
- endpoint: rawFormValue.endpoint,
- access_key: rawFormValue.access_key,
- secret: rawFormValue.secret_key,
- target_path: rawFormValue.target_path,
- retain_head_object: headObject,
- 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
- }
- }
- ]
- };
- return requestModel;
+ const storageClassType = this.storageClassForm.get('storageClassType').value;
+ const retain_head_object = this.storageClassForm.get('retain_head_object').value;
+
+ return this.buildPlacementTargets(
+ storageClassType,
+ zoneGroup,
+ placementId,
+ storageClass,
+ retain_head_object,
+ rawFormValue
+ );
+ }
+
+ private buildPlacementTargets(
+ storageClassType: string,
+ zoneGroup: string,
+ placementId: string,
+ storageClass: string,
+ retain_head_object: boolean,
+ rawFormValue: any
+ ): RequestModel {
+ switch (storageClassType) {
+ case TIER_TYPE.LOCAL:
+ return {
+ zone_group: zoneGroup,
+ placement_targets: [
+ {
+ tags: [],
+ placement_id: placementId,
+ storage_class: storageClass
+ }
+ ]
+ };
+
+ case TIER_TYPE.CLOUD_TIER:
+ return {
+ zone_group: zoneGroup,
+ placement_targets: [
+ {
+ tags: [],
+ placement_id: placementId,
+ storage_class: storageClass,
+ tier_type: TIER_TYPE.CLOUD_TIER,
+ tier_config: {
+ endpoint: rawFormValue.endpoint,
+ access_key: rawFormValue.access_key,
+ secret: rawFormValue.secret_key,
+ target_path: rawFormValue.target_path,
+ retain_head_object: 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
+ }
+ }
+ ]
+ };
+ default:
+ return null;
+ }
}
}
columnMode="flex"
[columns]="columns"
(fetchData)="loadStorageClass()"
+ identifier="uniqueId"
+ [forceIdentifier]="true"
selectionType="single"
[hasDetails]="true"
(setExpandedRow)="setExpandedRow($event)"
import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
-import { StorageClass, ZoneGroupDetails } from '../models/rgw-storage-class.model';
+import {
+ StorageClass,
+ TIER_TYPE,
+ TIER_TYPE_DISPLAY,
+ ZoneGroupDetails
+} from '../models/rgw-storage-class.model';
import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
import { FinishedTask } from '~/app/shared/models/finished-task';
import { Icons } from '~/app/shared/enum/icons.enum';
import { URLBuilderService } from '~/app/shared/services/url-builder.service';
import { Permission } from '~/app/shared/models/permissions';
import { BucketTieringUtils } from '../utils/rgw-bucket-tiering';
-
import { Router } from '@angular/router';
const BASE_URL = 'rgw/tiering';
ngOnInit() {
this.columns = [
+ {
+ prop: 'uniqueId',
+ isInvisible: true,
+ isHidden: true
+ },
{
name: $localize`Storage Class`,
prop: 'storage_class',
flexGrow: 2
},
+ {
+ name: $localize`Type`,
+ prop: 'tier_type',
+ flexGrow: 2
+ },
{
name: $localize`Zone Group`,
prop: 'zonegroup_name',
(data: ZoneGroupDetails) => {
this.storageClassList = [];
const tierObj = BucketTieringUtils.filterAndMapTierTargets(data);
- this.storageClassList.push(...tierObj);
+ const tierConfig = tierObj.map((item) => ({
+ ...item,
+ tier_type:
+ item.tier_type?.toLowerCase() === TIER_TYPE.CLOUD_TIER
+ ? TIER_TYPE_DISPLAY.CLOUD_TIER
+ : item.tier_type?.toLowerCase() === TIER_TYPE.LOCAL
+ ? TIER_TYPE_DISPLAY.LOCAL
+ : item.tier_type
+ }));
+ this.transformTierData(tierConfig);
+ this.storageClassList.push(...tierConfig);
resolve();
},
(error) => {
});
}
+ transformTierData(tierConfig: any[]) {
+ tierConfig.forEach((item, index) => {
+ const zone_group = item?.zone_group;
+ const storageClass = item?.storage_class;
+ const uniqueId = `${zone_group}-${storageClass}-${index}`;
+ item.uniqueId = uniqueId;
+ });
+ return tierConfig;
+ }
+
removeStorageClassModal() {
const storage_class = this.selection.first().storage_class;
const placement_target = this.selection.first().placement_target;
import {
- CLOUD_TIER,
Target,
TierTarget,
+ TIER_TYPE,
ZoneGroup,
ZoneGroupDetails
} from '../models/rgw-storage-class.model';
export class BucketTieringUtils {
static filterAndMapTierTargets(zonegroupData: ZoneGroupDetails) {
return zonegroupData.zonegroups.flatMap((zoneGroup: ZoneGroup) =>
- zoneGroup.placement_targets
- .filter((target: Target) => target.tier_targets)
- .flatMap((target: Target) =>
- target.tier_targets
- .filter((tierTarget: TierTarget) => tierTarget.val.tier_type === CLOUD_TIER)
- .map((tierTarget: TierTarget) => {
- return this.getTierTargets(tierTarget, zoneGroup.name, target.name);
- })
- )
+ zoneGroup.placement_targets.flatMap((target: Target) => {
+ const storage_class = new Set<string>(
+ (target.tier_targets || []).map((tier_target: TierTarget) => tier_target.key)
+ );
+ const tierTargetDetails = (target.tier_targets || []).map((tierTarget: TierTarget) =>
+ this.getTierTargets(tierTarget, zoneGroup.name, target.name)
+ );
+ const localStorageClasses = (target.storage_classes || [])
+ .filter((storageClass) => storageClass !== 'STANDARD' && !storage_class.has(storageClass))
+ .map((storageClass) => ({
+ zonegroup_name: zoneGroup.name,
+ placement_target: target.name,
+ storage_class: storageClass,
+ tier_type: TIER_TYPE.LOCAL
+ }));
+
+ return [...tierTargetDetails, ...localStorageClasses];
+ })
);
}
private static getTierTargets(tierTarget: TierTarget, zoneGroup: string, targetName: string) {
- if (tierTarget.val.tier_type !== CLOUD_TIER) return null;
- return {
- zonegroup_name: zoneGroup,
- placement_target: targetName,
- storage_class: tierTarget.val.storage_class,
- retain_head_object: tierTarget.val.retain_head_object,
- allow_read_through: tierTarget.val.allow_read_through,
- ...tierTarget.val.s3
- };
+ const val = tierTarget.val;
+ if (val.tier_type === TIER_TYPE.CLOUD_TIER) {
+ return {
+ zonegroup_name: zoneGroup,
+ placement_target: targetName,
+ storage_class: val.storage_class,
+ retain_head_object: val.retain_head_object,
+ allow_read_through: val.allow_read_through,
+ tier_type: val.tier_type,
+ ...val.s3
+ };
+ } else {
+ return {
+ zonegroup_name: zoneGroup,
+ placement_target: targetName,
+ storage_class: val.storage_class,
+ tier_type: TIER_TYPE.LOCAL
+ };
+ }
}
}
'--placement-id', placement_target['placement_id']
]
storage_class_name = placement_target.get('storage_class', None)
+ tier_type = placement_target.get('tier_type', None)
if (
placement_target.get('tier_type') == CLOUD_S3_TIER_TYPE
)
except SubprocessError as error:
raise DashboardException(error, http_status_code=500, component='rgw')
- self.ensure_realm_and_sync_period()
+ if tier_type == CLOUD_S3_TIER_TYPE:
+ self.ensure_realm_and_sync_period()
if storage_classes:
for sc in storage_classes:
)
except SubprocessError as error:
raise DashboardException(error, http_status_code=500, component='rgw')
- self.ensure_realm_and_sync_period()
+ if tier_type == CLOUD_S3_TIER_TYPE:
+ self.ensure_realm_and_sync_period()
def modify_placement_targets(self, zonegroup_name: str, placement_targets: List[Dict]):
rgw_add_placement_cmd = ['zonegroup', 'placement', 'modify']