result = multisite_instance.get_zonegroup(zonegroup_name)
return result
+ @Endpoint('GET')
+ @ReadPermission
+ def get_placement_target_by_placement_id(self, placement_id):
+ multisite_instance = RgwMultisite()
+ result = multisite_instance.get_placement_by_placement_id(placement_id)
+ return result
+
@Endpoint('DELETE', path='storage-class')
@DeletePermission
def remove_storage_class(self, placement_id: str, storage_class: str):
result = multisite_instance.add_placement_targets(zone_group, placement_targets)
return result
+ @Endpoint('PUT', path='storage-class')
+ @CreatePermission
+ # pylint: disable=W0102
+ def editStorageClass(self, zone_group, placement_targets: List[Dict[str, str]] = []):
+ multisite_instance = RgwMultisite()
+ result = multisite_instance.modify_placement_targets(zone_group, placement_targets)
+ return result
+
@Endpoint()
@ReadPermission
def get_all_zonegroups_info(self):
endpoint: string;
region: string;
placement_target: string;
+ zonegroup_name?: string;
}
export interface StorageClassDetails {
multipart_min_part_size: number;
multipart_sync_threshold: number;
host_style: boolean;
+ retain_head_object?: boolean;
}
export interface RequestModel {
export interface PlacementTarget {
tags: string[];
placement_id: string;
- storage_class: string;
tier_type: typeof CLOUD_TIER;
tier_config: {
endpoint: string;
multipart_sync_threshold: number;
multipart_min_part_size: number;
};
+ storage_class?: string;
+ name?: string;
+ tier_targets?: TierTarget[];
}
export const CLOUD_TIER = 'cloud-s3';
<div cdsCol
[columnNumbers]="{ md: 4 }">
- <form name="storageClassForm"
- #formDir="ngForm"
- [formGroup]="storageClassForm"
- novalidate>
- <div i18n="form title"
- class="form-header">
- {{ action | titlecase }} {{ resource | upperFirst }}
- </div>
- <legend>
- <cd-help-text i18n>
- All fields are required, except where marked optional.
- </cd-help-text>
- </legend>
- <div class="form-item form-item-append"
- cdsRow>
- <div cdsCol>
- <!-- Zone Group -->
- <cds-select
- label="Zone Group Name"
- i18n-label
- formControlName="zonegroup"
- id="zonegroup"
- [invalid]="
- storageClassForm.controls.zonegroup.invalid && storageClassForm.controls.zonegroup.dirty
- "
- (change)="onZonegroupChange()"
- [invalidText]="zonegroupError"
- >
- <option *ngFor="let zonegrp of zonegroupNames"
- [value]="zonegrp.name"
- [selected]="zonegrp.name === storageClassForm.getValue('zonegroup')"
- i18n>
- {{ zonegrp.name }}
- </option>
- </cds-select>
- <ng-template #zonegroupError>
- <span
- class="invalid-feedback"
- *ngIf="storageClassForm.showError('zonegroup', formDir, 'required')"
- i18n
- >This field is required.</span
- >
- </ng-template>
+ <ng-container *cdFormLoading="loading">
+ <form name="storageClassForm"
+ #formDir="ngForm"
+ [formGroup]="storageClassForm"
+ novalidate>
+ <div i18n="form title"
+ class="form-header">
+ {{ action | titlecase }} {{ resource | upperFirst }}
</div>
- <div cdsCol>
- <!-- Placement Target -->
- <cds-select
- label="Placement Target"
- i18n-label
- formControlName="placement_target"
- id="placement_target"
- [invalid]="
- storageClassForm.controls.placement_target.invalid &&
- storageClassForm.controls.placement_target.dirty
- "
- [invalidText]="placementError"
- >
- <option [value]=""
- i18n> --Select-- </option>
- <option *ngFor="let placementTarget of placementTargets"
- [value]="placementTarget"
- [selected]="placementTarget === storageClassForm.getValue('placement_target')"
- i18n>
- {{ placementTarget }}
- </option>
- </cds-select>
- <ng-template #placementError>
- <span
- class="invalid-feedback"
- *ngIf="storageClassForm.showError('placement_target', formDir, 'required')"
- i18n
- >This field is required.</span
+ <legend>
+ <cd-help-text i18n>
+ All fields are required, except where marked optional.
+ </cd-help-text>
+ </legend>
+ <div class="form-item form-item-append"
+ cdsRow>
+ <div cdsCol>
+ <!-- Zone Group -->
+ <cds-select
+ label="Zone Group Name"
+ i18n-label
+ formControlName="zonegroup"
+ id="zonegroup"
+ [invalid]="
+ storageClassForm.showError('zonegroup', formDir, 'required')
+ "
+ (change)="onZonegroupChange()"
+ [invalidText]="zonegroupError"
>
- </ng-template>
+ <option *ngFor="let zonegrp of zonegroupNames"
+ [value]="zonegrp.name"
+ [selected]="zonegrp.name === storageClassForm.getValue('zonegroup')"
+ i18n>
+ {{ zonegrp.name }}
+ </option>
+ </cds-select>
+ <ng-template #zonegroupError>
+ <span
+ class="invalid-feedback"
+ *ngIf="storageClassForm.showError('zonegroup', formDir, 'required')"
+ i18n
+ >This field is required.</span
+ >
+ </ng-template>
+ </div>
+ <div cdsCol>
+ <!-- Placement Target -->
+ <cds-select
+ label="Placement Target"
+ i18n-label
+ formControlName="placement_target"
+ id="placement_target"
+ [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>
+ {{ placementTarget }}
+ </option>
+ </cds-select>
+ <ng-template #placementError>
+ <span
+ class="invalid-feedback"
+ *ngIf="storageClassForm.showError('placement_target', formDir, 'required')"
+ i18n
+ >This field is required.</span
+ >
+ </ng-template>
+ </div>
</div>
- </div>
- <!-- Storage Class -->
- <div class="form-item">
- <cds-text-label
- labelInputID="storage_class"
- i18n
- [invalid]="
- storageClassForm.controls.storage_class.invalid &&
- storageClassForm.controls.storage_class.dirty
- "
- [invalidText]="storageError"
- >Storage Class Name
- <input
- cdsText
- type="type"
- id="storage_class"
- formControlName="storage_class"
- [invalid]="
- storageClassForm.controls.storage_class.invalid &&
- storageClassForm.controls.storage_class.dirty
- "
- />
- </cds-text-label>
- <ng-template #storageError>
- <span
- class="invalid-feedback"
- *ngIf="storageClassForm.showError('storage_class', formDir, 'required')"
- i18n
- >This field is required.</span
- >
- </ng-template>
- </div>
- <div class="form-item form-item-append"
- cdsRow>
- <div cdsCol>
- <!-- Target Region -->
+ <!-- Storage Class -->
+ <div class="form-item">
<cds-text-label
- labelInputID="region"
+ labelInputID="storage_class"
i18n
+ [disabled]="editing"
[invalid]="
- storageClassForm.controls.region.invalid && storageClassForm.controls.region.dirty
+ storageClassForm.controls.storage_class.invalid &&
+ storageClassForm.controls.storage_class.dirty
"
- [invalidText]="regionError"
- [helperText]="targetRegionText"
- >Target Region
+ [invalidText]="storageError"
+ >Storage Class Name
<input
cdsText
- type="text"
- id="region"
- formControlName="region"
- placeholder="e.g, us-east-1"
- i18n-placeholder
+ type="type"
+ id="storage_class"
+ formControlName="storage_class"
[invalid]="
- storageClassForm.controls.region.invalid && storageClassForm.controls.region.dirty
+ storageClassForm.showError('storage_class', formDir, 'required')
"
/>
</cds-text-label>
- <ng-template #regionError>
+ <ng-template #storageError>
<span
class="invalid-feedback"
- *ngIf="storageClassForm.showError('region', formDir, 'required')"
+ *ngIf="storageClassForm.showError('storage_class', formDir, 'required')"
i18n
>This field is required.</span
>
</ng-template>
</div>
- <div cdsCol>
- <!-- Target Endpoint -->
- <cds-text-label
- labelInputID="endpoint"
- i18n
- [invalid]="
- storageClassForm.controls.endpoint.invalid && storageClassForm.controls.endpoint.dirty
- "
- [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"
+ <div class="form-item form-item-append"
+ cdsRow>
+ <div cdsCol>
+ <!-- Target Region -->
+ <cds-text-label
+ labelInputID="region"
+ i18n
[invalid]="
- storageClassForm.controls.endpoint.invalid && storageClassForm.controls.endpoint.dirty
+ storageClassForm.showError('region', formDir, 'required')
"
- />
- </cds-text-label>
- <ng-template #endpointError>
- <span
- class="invalid-feedback"
- *ngIf="storageClassForm.showError('endpoint', 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
- >This field is required.</span
- >
- </ng-template>
+ [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>
- <!-- Access Key -->
- <div class="form-item">
- <div cdsCol
- [columnNumbers]="{ md: 12 }"
- class="d-flex">
- <cds-password-label
- labelInputID="access_key"
- [invalid]="
- !storageClassForm.controls.access_key.valid &&
- storageClassForm.controls.access_key.dirty
- "
- [invalidText]="accessError"
- [helperText]="targetAccessKeyText"
- i18n
- >Target Access Key
- <input
- cdsPassword
- type="password"
- id="access_key"
- formControlName="access_key"
+ <!-- Access Key -->
+ <div class="form-item">
+ <div cdsCol
+ [columnNumbers]="{ md: 12 }"
+ class="d-flex">
+ <cds-password-label
+ labelInputID="access_key"
[invalid]="
- !storageClassForm.controls.access_key.valid &&
- storageClassForm.controls.access_key.dirty
+ 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')"
+ [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>
+
+ <!-- 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
+ >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"
+ <!-- Target Path -->
+ <div class="form-item">
+ <cds-text-label
+ labelInputID="target_path"
+ i18n
[invalid]="
- !storageClassForm.controls.secret_key.valid &&
- storageClassForm.controls.secret_key.dirty
+ storageClassForm.showError('target_path', formDir, 'required')
"
- [invalidText]="secretError"
- i18n
- >Target Secret Key
+ [invalidText]="targetError"
+ [helperText]="targetPathText"
+ >Target Path
<input
- cdsPassword
- type="password"
- id="secret_key"
- formControlName="secret_key"
+ cdsText
+ type="text"
+ id="target_path"
+ formControlName="target_path"
[invalid]="
- !storageClassForm.controls.secret_key.valid &&
- storageClassForm.controls.secret_key.dirty
+ 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.controls.target_path.invalid &&
- storageClassForm.controls.target_path.dirty
- "
- [invalidText]="targetError"
- [helperText]="targetPathText"
- >Target Path
- <input
- cdsText
- type="text"
- id="target_path"
- formControlName="target_path"
- [invalid]="
- storageClassForm.controls.target_path.invalid &&
- storageClassForm.controls.target_path.dirty
- "
- />
- </cds-text-label>
- <ng-template #targetError>
- <span
- class="invalid-feedback"
- *ngIf="storageClassForm.showError('target_path', formDir, 'required')"
- i18n
- >This field is required.</span
- >
- </ng-template>
- </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"
- 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>
+ <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 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 class="form-item">
+ <cds-checkbox
+ id="retain_head_object"
+ formControlName="retain_head_object"
+ cdOptionalField="Retain Head Object"
+ i18n-label
+ >Retain Head Object
+ <cd-help-text>{{ retainHeadObjectText }}</cd-help-text>
+ </cds-checkbox>
</div>
- </div>
- <div class="form-item">
- <cds-checkbox
- id="retain_head_object"
- formControlName="retain_head_object"
- cdOptionalField="Retain Head Object"
- i18n-label
- >Retain Head Object
- <cd-help-text>{{ retainHeadObjectText }}</cd-help-text>
- </cds-checkbox>
- </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">
- <span i18n>RGW service would be restarted after creating the storage class.</span>
- </cd-alert-panel>
- <cd-form-button-panel
- (submitActionEvent)="submitAction()"
- [form]="storageClassForm"
- [submitText]="(action | titlecase) + ' ' + (resource | upperFirst)"
- wrappingClass="text-right"
- ></cd-form-button-panel>
- </form>
+ </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">
+ <span i18n>RGW service would be restarted after creating the storage class.</span>
+ </cd-alert-panel>
+ <cd-form-button-panel
+ (submitActionEvent)="submitAction()"
+ [form]="storageClassForm"
+ [submitText]="(action | titlecase) + ' ' + (resource | upperFirst)"
+ wrappingClass="text-right"
+ ></cd-form-button-panel>
+ </form>
+ </ng-container>
</div>
});
it('on zonegroup changes', () => {
- component.zoneGroupDeatils = {
+ component.zoneGroupDetails = {
default_zonegroup: 'zonegroup1',
name: 'zonegrp1',
zonegroups: [
import { Component, OnInit } from '@angular/core';
import { FormControl, Validators } from '@angular/forms';
-import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants';
import { CdForm } from '~/app/shared/forms/cd-form';
import { CdFormBuilder } from '~/app/shared/forms/cd-form-builder';
import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
import _ from 'lodash';
-import { Router } from '@angular/router';
+import { ActivatedRoute, Router } from '@angular/router';
import { RgwStorageClassService } from '~/app/shared/api/rgw-storage-class.service';
import { RgwZonegroupService } from '~/app/shared/api/rgw-zonegroup.service';
import {
CLOUD_TIER,
DEFAULT_PLACEMENT,
+ PlacementTarget,
RequestModel,
+ StorageClass,
Target,
+ TierTarget,
ZoneGroup,
ZoneGroupDetails
} from '../models/rgw-storage-class.model';
storageClassForm: CdFormGroup;
action: string;
resource: string;
+ editing: boolean;
targetPathText: string;
targetEndpointText: string;
targetRegionText: string;
multipartSyncThreholdText: string;
selectedZoneGroup: string;
defaultZonegroup: ZoneGroup;
- zoneGroupDeatils: ZoneGroupDetails;
+ zoneGroupDetails: ZoneGroupDetails;
targetSecretKeyText: string;
targetAccessKeyText: string;
retainHeadObjectText: string;
+ storageClassInfo: StorageClass;
+ tierTargetInfo: TierTarget;
constructor(
public actionLabels: ActionLabelsI18n,
private notificationService: NotificationService,
private rgwStorageService: RgwStorageClassService,
private rgwZoneGroupService: RgwZonegroupService,
- private router: Router
+ private router: Router,
+ private route: ActivatedRoute
) {
super();
this.resource = $localize`Tiering Storage Class`;
+ this.editing = this.router.url.startsWith(`/rgw/tiering/${URLVerbs.EDIT}`);
+ this.action = this.editing ? this.actionLabels.EDIT : this.actionLabels.CREATE;
}
ngOnInit() {
"To view or copy your secret key, go to your cloud service's user management or credentials section, find your user profile, and locate the access key. You can view and copy the key by following the instructions provided.";
this.retainHeadObjectText =
'Retain object metadata after transition to the cloud (default: deleted).';
- this.action = this.actionLabels.CREATE;
this.createForm();
+ this.loadingReady();
this.loadZoneGroup();
+ if (this.editing) {
+ this.route.params.subscribe((params: StorageClass) => {
+ this.storageClassInfo = params;
+ });
+ this.rgwStorageService
+ .getPlacement_target(this.storageClassInfo.placement_target)
+ .subscribe((placementTargetInfo: PlacementTarget) => {
+ this.tierTargetInfo = this.getTierTargetByStorageClass(
+ placementTargetInfo,
+ this.storageClassInfo.storage_class
+ );
+ let response = this.tierTargetInfo.val.s3;
+ this.storageClassForm.get('zonegroup').disable();
+ this.storageClassForm.get('placement_target').disable();
+ this.storageClassForm.get('storage_class').disable();
+ this.storageClassForm.get('zonegroup').setValue(this.storageClassInfo.zonegroup_name);
+ this.storageClassForm.get('region').setValue(response.region);
+ this.storageClassForm
+ .get('placement_target')
+ .setValue(this.storageClassInfo.placement_target);
+ this.storageClassForm.get('endpoint').setValue(response.endpoint);
+ this.storageClassForm.get('storage_class').setValue(this.storageClassInfo.storage_class);
+ this.storageClassForm.get('access_key').setValue(response.access_key);
+ this.storageClassForm.get('secret_key').setValue(response.access_key);
+ this.storageClassForm.get('target_path').setValue(response.target_path);
+ this.storageClassForm
+ .get('retain_head_object')
+ .setValue(response.retain_head_object || false);
+ this.storageClassForm
+ .get('multipart_sync_threshold')
+ .setValue(response.multipart_sync_threshold || '');
+ this.storageClassForm
+ .get('multipart_min_part_size')
+ .setValue(response.multipart_min_part_size || '');
+ });
+ }
}
createForm() {
return new Promise((resolve, reject) => {
this.rgwZoneGroupService.getAllZonegroupsInfo().subscribe(
(data: ZoneGroupDetails) => {
- this.zoneGroupDeatils = data;
+ this.zoneGroupDetails = data;
this.zonegroupNames = [];
this.placementTargets = [];
if (data.zonegroups && data.zonegroups.length > 0) {
onZonegroupChange() {
const zoneGroupControl = this.storageClassForm.get('zonegroup').value;
- const selectedZoneGroup = this.zoneGroupDeatils.zonegroups.find(
+ const selectedZoneGroup = this.zoneGroupDetails.zonegroups.find(
(zonegroup) => zonegroup.name === zoneGroupControl
);
const defaultPlacementTarget = selectedZoneGroup.placement_targets.find(
);
this.placementTargets = placementTargetNames;
}
- if (defaultPlacementTarget) {
+ if (defaultPlacementTarget && !this.editing) {
this.storageClassForm.get('placement_target').setValue(defaultPlacementTarget.name);
+ } else {
+ this.storageClassForm
+ .get('placement_target')
+ .setValue(this.storageClassInfo.placement_target);
}
}
const component = this;
const requestModel = this.buildRequest();
const storageclassName = this.storageClassForm.get('storage_class').value;
- this.rgwStorageService.createStorageClass(requestModel).subscribe(
- () => {
- this.notificationService.show(
- NotificationType.success,
- $localize`Created Storage Class '${storageclassName}'`
- );
- this.goToListView();
- },
- () => {
- component.storageClassForm.setErrors({ cdSubmitButton: true });
- }
- );
+ if (this.editing) {
+ this.rgwStorageService.editStorageClass(requestModel).subscribe(
+ () => {
+ this.notificationService.show(
+ NotificationType.success,
+ $localize`Updated Storage Class '${storageclassName}'`
+ );
+ this.goToListView();
+ },
+ () => {
+ component.storageClassForm.setErrors({ cdSubmitButton: true });
+ }
+ );
+ } else {
+ this.rgwStorageService.createStorageClass(requestModel).subscribe(
+ () => {
+ this.notificationService.show(
+ NotificationType.success,
+ $localize`Created Storage Class '${storageclassName}'`
+ );
+ this.goToListView();
+ },
+ () => {
+ component.storageClassForm.setErrors({ cdSubmitButton: true });
+ }
+ );
+ }
}
goToListView() {
this.router.navigate([`rgw/tiering`]);
}
+ getTierTargetByStorageClass(placementTargetInfo: PlacementTarget, storageClass: string) {
+ const tierTarget = placementTargetInfo.tier_targets.find(
+ (target: TierTarget) => target.val.storage_class === storageClass
+ );
+ return tierTarget;
+ }
+
buildRequest() {
const rawFormValue = _.cloneDeep(this.storageClassForm.value);
+ const zoneGroup = this.storageClassForm.get('zonegroup').value;
+ const storageClass = this.storageClassForm.get('storage_class').value;
+ const placementId = this.storageClassForm.get('placement_target').value;
const requestModel: RequestModel = {
- zone_group: rawFormValue.zonegroup,
+ zone_group: zoneGroup,
placement_targets: [
{
tags: [],
- placement_id: rawFormValue.placement_target,
- storage_class: rawFormValue.storage_class,
+ placement_id: placementId,
+ storage_class: storageClass,
tier_type: CLOUD_TIER,
tier_config: {
endpoint: rawFormValue.endpoint,
+<legend>
+ <cd-help-text i18n>
+ A storage class for tiering defines the policies for automatically moving objects between different storage tiers.
+ </cd-help-text>
+</legend>
<cd-table
[data]="storageClassList"
columnMode="flex"
import { Router } from '@angular/router';
const BASE_URL = 'rgw/tiering';
-
@Component({
selector: 'cd-rgw-storage-class-list',
templateUrl: './rgw-storage-class-list.component.html',
ngOnInit() {
this.columns = [
+ {
+ name: $localize`Storage Class`,
+ prop: 'storage_class',
+ flexGrow: 2
+ },
{
name: $localize`Zone Group`,
prop: 'zonegroup_name',
prop: 'placement_target',
flexGrow: 2
},
- {
- name: $localize`Storage Class`,
- prop: 'storage_class',
- flexGrow: 2
- },
{
name: $localize`Target Region`,
prop: 'region',
flexGrow: 2
}
];
+ const getStorageUri = () =>
+ this.selection.first() &&
+ `${encodeURI(this.selection.first().zonegroup_name)}/${encodeURI(
+ this.selection.first().placement_target
+ )}/${encodeURI(this.selection.first().storage_class)}`;
this.tableActions = [
{
name: this.actionLabels.CREATE,
click: () => this.router.navigate([this.urlBuilder.getCreate()]),
canBePrimary: (selection: CdTableSelection) => !selection.hasSelection
},
+ {
+ name: this.actionLabels.EDIT,
+ permission: 'update',
+ icon: Icons.edit,
+ routerLink: () => [`/rgw/tiering/edit/${getStorageUri()}`]
+ },
{
name: this.actionLabels.REMOVE,
permission: 'delete',
path: URLVerbs.CREATE,
component: RgwStorageClassFormComponent,
data: { breadcrumbs: ActionLabels.CREATE }
+ },
+ {
+ path: `${URLVerbs.EDIT}/:zonegroup_name/:placement_target/:storage_class`,
+ component: RgwStorageClassFormComponent,
+ data: { breadcrumbs: ActionLabels.EDIT }
}
]
},
providedIn: 'root'
})
export class RgwStorageClassService {
- private url = 'api/rgw/zonegroup/storage-class';
+ private baseUrl = 'api/rgw/zonegroup';
+ private url = `${this.baseUrl}/storage-class`;
constructor(private http: HttpClient) {}
createStorageClass(requestModel: RequestModel) {
return this.http.post(`${this.url}`, requestModel);
}
+
+ editStorageClass(requestModel: RequestModel) {
+ return this.http.put(`${this.url}`, requestModel);
+ }
+
+ getPlacement_target(placement_id: string) {
+ return this.http.get(`${this.baseUrl}/get_placement_target_by_placement_id/${placement_id}`);
+ }
}
- jwt: []
tags:
- RgwZonegroup
+ /api/rgw/zonegroup/get_placement_target_by_placement_id/{placement_id}:
+ get:
+ parameters:
+ - in: path
+ name: placement_id
+ required: true
+ schema:
+ type: string
+ responses:
+ '200':
+ content:
+ application/vnd.ceph.api.v1.0+json:
+ type: object
+ description: OK
+ '400':
+ description: Operation exception. Please check the response body for details.
+ '401':
+ description: Unauthenticated access. Please login first.
+ '403':
+ description: Unauthorized access. Please check your permissions.
+ '500':
+ description: Unexpected error. Please check the response body for the stack
+ trace.
+ security:
+ - jwt: []
+ tags:
+ - RgwZonegroup
/api/rgw/zonegroup/storage-class:
post:
parameters: []
- jwt: []
tags:
- RgwZonegroup
+ put:
+ parameters: []
+ requestBody:
+ content:
+ application/json:
+ schema:
+ properties:
+ placement_targets:
+ default: []
+ type: string
+ zone_group:
+ type: string
+ required:
+ - zone_group
+ type: object
+ responses:
+ '200':
+ content:
+ application/vnd.ceph.api.v1.0+json:
+ type: object
+ description: Resource updated.
+ '202':
+ content:
+ application/vnd.ceph.api.v1.0+json:
+ type: object
+ description: Operation is still executing. Please check the task queue.
+ '400':
+ description: Operation exception. Please check the response body for details.
+ '401':
+ description: Unauthenticated access. Please login first.
+ '403':
+ description: Unauthorized access. Please check your permissions.
+ '500':
+ description: Unexpected error. Please check the response body for the stack
+ trace.
+ security:
+ - jwt: []
+ tags:
+ - RgwZonegroup
/api/rgw/zonegroup/storage-class/{placement_id}/{storage_class}:
delete:
parameters:
raise DashboardException(error, http_status_code=500, component='rgw')
return out
+ def get_placement_by_placement_id(self, placement_id: str):
+ radosgw_get_placement_cmd = ['zonegroup', 'placement',
+ 'get', '--placement-id', placement_id]
+ try:
+ exit_code, out, err = mgr.send_rgwadmin_command(radosgw_get_placement_cmd)
+ if exit_code > 0:
+ raise DashboardException(e=err, msg='Unable to get placement details by id',
+ http_status_code=500, component='rgw')
+
+ except SubprocessError as error:
+ raise DashboardException(error, http_status_code=500, component='rgw')
+ return out
+
# If realm list is empty, restart RGW daemons. Otherwise, update the period.
def ensure_realm_and_sync_period(self):
rgw_realm_list = self.list_realms()
def modify_placement_targets(self, zonegroup_name: str, placement_targets: List[Dict]):
rgw_add_placement_cmd = ['zonegroup', 'placement', 'modify']
- for placement_target in placement_targets:
- cmd_add_placement_options = ['--rgw-zonegroup', zonegroup_name,
- '--placement-id', placement_target['placement_id']]
- if placement_target['tags']:
+ STANDARD_STORAGE_CLASS = "STANDARD"
+ CLOUD_S3_TIER_TYPE = "cloud-s3"
+
+ for placement_target in placement_targets: # pylint: disable=R1702,line-too-long # noqa: E501
+ cmd_add_placement_options = [
+ '--rgw-zonegroup', zonegroup_name,
+ '--placement-id', placement_target['placement_id']
+ ]
+ storage_class_name = placement_target.get('storage_class', None)
+
+ if (
+ placement_target.get('tier_type') == CLOUD_S3_TIER_TYPE
+ and storage_class_name != STANDARD_STORAGE_CLASS
+ ):
+ tier_config = placement_target.get('tier_config', {})
+ if tier_config:
+ tier_config_items = (
+ f'{key}={value}' for key, value in tier_config.items()
+ )
+ tier_config_str = ','.join(tier_config_items)
+ cmd_add_placement_options += [
+ '--tier-type', 'cloud-s3', '--tier-config', tier_config_str
+ ]
+
+ if placement_target.get('tags') and storage_class_name != STANDARD_STORAGE_CLASS:
cmd_add_placement_options += ['--tags', placement_target['tags']]
+
+ storage_classes = (
+ placement_target['storage_class'].split(",")
+ if placement_target['storage_class']
+ else []
+ )
rgw_add_placement_cmd += cmd_add_placement_options
- storage_classes = placement_target['storage_class'].split(",") if placement_target['storage_class'] else [] # noqa E501 #pylint: disable=line-too-long
- if storage_classes:
- for sc in storage_classes:
- cmd_add_placement_options = []
- cmd_add_placement_options = ['--storage-class', sc]
- try:
- exit_code, _, err = mgr.send_rgwadmin_command(
- rgw_add_placement_cmd + cmd_add_placement_options)
- if exit_code > 0:
- raise DashboardException(e=err,
- msg='Unable to add placement target {} to zonegroup {}'.format(placement_target['placement_id'], zonegroup_name), # noqa E501 #pylint: disable=line-too-long
- http_status_code=500, component='rgw')
- except SubprocessError as error:
- raise DashboardException(error, http_status_code=500, component='rgw')
- self.update_period()
- else:
+
+ if not storage_classes:
try:
exit_code, _, err = mgr.send_rgwadmin_command(rgw_add_placement_cmd)
if exit_code > 0:
- raise DashboardException(e=err,
- msg='Unable to add placement target {} to zonegroup {}'.format(placement_target['placement_id'], zonegroup_name), # noqa E501 #pylint: disable=line-too-long
- http_status_code=500, component='rgw')
+ raise DashboardException(
+ e=err,
+ msg=(
+ f'Unable to add placement target '
+ f'{placement_target["placement_id"]} '
+ f'to zonegroup {zonegroup_name}'
+ )
+ )
except SubprocessError as error:
raise DashboardException(error, http_status_code=500, component='rgw')
- self.update_period()
+ self.ensure_realm_and_sync_period()
+
+ if storage_classes:
+ for sc in storage_classes:
+ if sc == storage_class_name:
+ cmd_add_placement_options = ['--storage-class', sc]
+ try:
+ exit_code, _, err = mgr.send_rgwadmin_command(
+ rgw_add_placement_cmd + cmd_add_placement_options
+ )
+ if exit_code > 0:
+ raise DashboardException(
+ e=err,
+ msg=(
+ f'Unable to add placement target '
+ f'{placement_target["placement_id"]} '
+ f'to zonegroup {zonegroup_name}'
+ ),
+ http_status_code=500,
+ component='rgw'
+ )
+ except SubprocessError as error:
+ raise DashboardException(error, http_status_code=500, component='rgw')
+ self.ensure_realm_and_sync_period()
def delete_placement_targets(self, placement_id: str, storage_class: str):
rgw_zonegroup_delete_cmd = ['zonegroup', 'placement', 'rm',