]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Glacier Storage Class - create and list 63421/head
authorDnyaneshwari <dnyaneshwari@li-9c9fbecc-2d5c-11b2-a85c-e2a7cc8a424f.ibm.com>
Thu, 22 May 2025 07:08:25 +0000 (12:38 +0530)
committerDnyaneshwari <dnyaneshwari@li-9c9fbecc-2d5c-11b2-a85c-e2a7cc8a424f.ibm.com>
Wed, 16 Jul 2025 04:50:57 +0000 (10:20 +0530)
Fixes: https://tracker.ceph.com/issues/71897
Signed-off-by: Dnyaneshwari Talwekar <dtalwekar@redhat.com>
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-storage-class.model.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-details/rgw-storage-class-details.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-details/rgw-storage-class-details.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-details/rgw-storage-class-details.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-form/rgw-storage-class-form.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-form/rgw-storage-class-form.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-form/rgw-storage-class-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-list/rgw-storage-class-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/utils/rgw-bucket-tiering.ts
src/pybind/mgr/dashboard/services/rgw_client.py

index cc7f24fcb888b75846008d6d27baebc6b077a085..15a9a972416f277c3b3abc1f6301512568dd5dd2 100644 (file)
@@ -12,17 +12,6 @@ export interface StorageClass {
   zonegroup_name?: string;
 }
 
-export interface StorageClassDetails {
-  target_path: string;
-  access_key: string;
-  secret: string;
-  multipart_min_part_size: number;
-  multipart_sync_threshold: number;
-  host_style: string;
-  retain_head_object: boolean;
-  allow_read_through: boolean;
-}
-
 export interface TierTarget {
   key: string;
   val: {
@@ -30,7 +19,10 @@ export interface TierTarget {
     tier_type: string;
     retain_head_object: boolean;
     allow_read_through: boolean;
+    read_through_restore_days: number;
+    restore_storage_class: string;
     s3?: S3Details;
+    's3-glacier': S3Glacier;
   };
 }
 
@@ -41,14 +33,21 @@ export interface Target {
 }
 
 export interface StorageClassDetails {
+  tier_type: string;
   target_path: string;
   access_key: string;
   secret: string;
   multipart_min_part_size: number;
   multipart_sync_threshold: number;
   host_style: string;
+  allow_read_through: boolean;
   zonegroup_name?: string;
   placement_targets?: string;
+  glacier_restore_days?: number;
+  glacier_restore_tier_type?: string;
+  read_through_restore_days?: number;
+  restore_storage_class?: string;
+  retain_head_object?: boolean;
 }
 
 export interface ZoneGroup {
@@ -71,13 +70,18 @@ export interface S3Details {
   retain_head_object?: boolean;
   allow_read_through?: boolean;
 }
+export interface S3Glacier {
+  glacier_restore_days: number;
+  glacier_restore_tier_type: string;
+}
+
 export interface RequestModel {
   zone_group: string;
   placement_targets: PlacementTarget[];
 }
 
 export interface PlacementTarget {
-  tags: string[];
+  tags?: string[];
   placement_id: string;
   tier_type?: TIER_TYPE;
   tier_config?: {
@@ -90,20 +94,64 @@ export interface PlacementTarget {
     region: string;
     multipart_sync_threshold: number;
     multipart_min_part_size: number;
+    glacier_restore_days?: number;
+    glacier_restore_tier_type?: string;
+    restore_storage_class?: string;
+    read_through_restore_days?: number;
   };
   storage_class?: string;
   name?: string;
   tier_targets?: TierTarget[];
 }
 
+export interface StorageClassOption {
+  value: string;
+  label: string;
+}
+
+export interface TextLabels {
+  targetPathText: string;
+  targetEndpointText: string;
+  targetRegionText: string;
+  multipartMinPartText: string;
+  storageClassText: string;
+  multipartSyncThresholdText: string;
+  targetSecretKeyText: string;
+  targetAccessKeyText: string;
+  retainHeadObjectText: string;
+  allowReadThroughText: string;
+  glacierRestoreDayText: string;
+  glacierRestoreTiertypeText: string;
+  tiertypeText: string;
+  restoreDaysText: string;
+  readthroughrestoreDaysText: string;
+  restoreStorageClassText: string;
+}
+
 export const TIER_TYPE = {
   LOCAL: 'local',
   CLOUD_TIER: 'cloud-s3',
   GLACIER: 'cloud-s3-glacier'
 } as const;
 
+export const STORAGE_CLASS_CONSTANTS = {
+  DEFAULT_GLACIER_RESTORE_DAYS: 1,
+  DEFAULT_READTHROUGH_RESTORE_DAYS: 1,
+  DEFAULT_MULTIPART_SYNC_THRESHOLD: 33554432,
+  DEFAULT_MULTIPART_MIN_PART_SIZE: 33554432,
+  DEFAULT_STORAGE_CLASS: 'Standard'
+} as const;
+
 export const DEFAULT_PLACEMENT = 'default-placement';
 
+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'
+};
+
 export const ALLOW_READ_THROUGH_TEXT =
   'Enables fetching objects from remote cloud S3 if not found locally.';
 
@@ -138,10 +186,22 @@ export const LOCAL_STORAGE_CLASS_TEXT = $localize`Local storage uses on-premises
 
 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 GLACIER_STORAGE_CLASS_TEXT = $localize`Glacier storage uses Amazon S3 Glacier for low-cost, long-term archival data storage.`;
 
-export const TIER_TYPE_DISPLAY = {
-  LOCAL: 'Local',
-  CLOUD_TIER: 'Cloud S3',
-  GLACIER: 'Cloud S3 Glacier'
-};
+export const GLACIER_RESTORE_DAY_TEXT = $localize`Refers to number of days to the object will be restored on glacier/tape endpoint.`;
+
+export const GLACIER_RESTORE_TIER_TYPE_TEXT = $localize`Restore retrieval type.`;
+
+export const STANDARD_TIER_TYPE_TEXT = $localize`Standard glacier restore tier type restores data in 3–5 hours.`;
+
+export const EXPEDITED_TIER_TYPE_TEXT = $localize`Expedited glacier restore tier type restores in 1–5 minutes (faster but costlier).`;
+
+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 RESTORE_STORAGE_CLASS_TEXT = $localize`The storage class to which object data is to be restored.`;
+
+export const ZONEGROUP_TEXT = $localize`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.`;
index 13b90e2735f2ef0314fca57b0084b75abb162fdd..baa283145fdc5e89b008b2d850ac59c5403ecb9d 100644 (file)
@@ -7,21 +7,26 @@
         data-testid="rgw-storage-details"
       >
         <tbody>
-          <tr *ngIf="selection.tier_type === TIER_TYPE_DISPLAY.LOCAL">
+          @if( isTierMatch(
+            TIER_TYPE_DISPLAY.LOCAL
+          )){
+          <tr>
             <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.
+                  {{ zoneGroupText }}
                 </span>
               </cd-helper>
             </td>
             <td>{{ selection?.zonegroup_name }}</td>
           </tr>
-          <tr *ngIf="selection.tier_type === TIER_TYPE_DISPLAY.LOCAL">
+        }
+        @if(isTierMatch(
+            TIER_TYPE_DISPLAY.LOCAL
+          )){
+          <tr>
             <td class="bold"
                 i18n>
               Placement Target
             </td>
             <td>{{ selection?.placement_target }}</td>
           </tr>
-          <tr *ngIf="selection.tier_type === TIER_TYPE_DISPLAY.CLOUD_TIER">
+        }
+        @if(isTierMatch( TIER_TYPE_DISPLAY.CLOUD_TIER, TIER_TYPE_DISPLAY.GLACIER)){
+          <tr>
             <td class="bold"
                 i18n>
               Target Path
               <cd-helper class="text-pre-wrap">
                 <span>
-                 {{ targetPathText }}
+                  {{ targetPathText }}
                 </span>
               </cd-helper>
             </td>
             <td>{{ selection?.target_path }}</td>
           </tr>
-          <tr *ngIf="selection.tier_type === TIER_TYPE_DISPLAY.CLOUD_TIER">
+        }
+        @if(isTierMatch(TIER_TYPE_DISPLAY.CLOUD_TIER, TIER_TYPE_DISPLAY.GLACIER)){
+          <tr>
             <td class="bold"
                 i18n>
               Access key
@@ -74,7 +83,9 @@
               </div>
             </td>
           </tr>
-          <tr *ngIf="selection.tier_type === TIER_TYPE_DISPLAY.CLOUD_TIER">
+        }
+        @if(isTierMatch(TIER_TYPE_DISPLAY.CLOUD_TIER, TIER_TYPE_DISPLAY.GLACIER)){
+          <tr>
             <td class="bold"
                 i18n>
               Secret key
               </div>
             </td>
           </tr>
-          <tr *ngIf="selection.tier_type === TIER_TYPE_DISPLAY.CLOUD_TIER">
+        }
+        @if(isTierMatch( TIER_TYPE_DISPLAY.CLOUD_TIER, TIER_TYPE_DISPLAY.GLACIER)){
+          <tr>
             <td class="bold"
                 i18n>
               Host Style
             </td>
             <td>{{ selection?.host_style }}</td>
           </tr>
-          <tr *ngIf="selection.tier_type === TIER_TYPE_DISPLAY.CLOUD_TIER">
+        }
+        @if(isTierMatch( TIER_TYPE_DISPLAY.CLOUD_TIER, TIER_TYPE_DISPLAY.GLACIER)){
+          <tr>
             <td class="bold"
                 i18n>
-              Multipart Minimum Part Size
+              Head Object (Stub File)
+              <cd-helper class="text-pre-wrap">
+                <span> {{ retainHeadObjectText }}</span>
+              </cd-helper>
+            </td>
+            <td>{{ selection?.retain_head_object ? 'Enabled' : 'Disabled' }}</td>
+          </tr>
+        }
+        @if(isTierMatch( TIER_TYPE_DISPLAY.CLOUD_TIER, TIER_TYPE_DISPLAY.GLACIER)){
+          <tr>
+            <td class="bold"
+                i18n>
+              Allow Read Through
               <cd-helper class="text-pre-wrap">
                 <span>
-                  {{ multipartMinPartText }}
+                  {{ allowReadThroughText }}
                 </span>
               </cd-helper>
             </td>
-            <td>{{ selection?.multipart_min_part_size }}</td>
+            <td>{{ selection?.allow_read_through ? 'Enabled' : 'Disabled' }}</td>
           </tr>
-          <tr *ngIf="selection.tier_type === TIER_TYPE_DISPLAY.CLOUD_TIER">
+        }
+        @if(isTierMatch(TIER_TYPE_DISPLAY.CLOUD_TIER, TIER_TYPE_DISPLAY.GLACIER) && (selection?.allow_read_through)) {
+          <tr *ngIf="isTierMatch(TIER_TYPE_DISPLAY.CLOUD_TIER, TIER_TYPE_DISPLAY.GLACIER) && (selection?.allow_read_through)">
             <td class="bold"
                 i18n>
-              Multipart Sync Threshold
+              Read through Restore Days
               <cd-helper class="text-pre-wrap">
                 <span>
-                  {{ multipartSyncThreholdText }}
+                  {{ readthroughrestoreDaysText }}
                 </span>
               </cd-helper>
             </td>
-            <td>{{ selection?.multipart_sync_threshold }}</td>
+            <td>{{ selection?.read_through_restore_days }}</td>
           </tr>
-          <tr *ngIf="selection.tier_type === TIER_TYPE_DISPLAY.CLOUD_TIER">
+        }
+        @if(isTierMatch( TIER_TYPE_DISPLAY.GLACIER)){
+          <tr>
             <td class="bold"
                 i18n>
-              Retain Head Object
+              Glacier Restore Days
               <cd-helper class="text-pre-wrap">
                 <span>
-                  Retain object metadata after transition to the cloud (default: false).
+                  {{ glacierRestoreDayText }}
                 </span>
               </cd-helper>
             </td>
-            <td>{{ selection?.retain_head_object }}</td>
+            <td>{{ selection?.glacier_restore_days }}</td>
           </tr>
-          <tr *ngIf="selection.tier_type === TIER_TYPE_DISPLAY.CLOUD_TIER">
+        }
+        @if(isTierMatch( TIER_TYPE_DISPLAY.GLACIER)) {
+          <tr>
             <td class="bold"
                 i18n>
-              Allow Read Through
+              Glacier Restore Tier Type
               <cd-helper class="text-pre-wrap">
                 <span>
-                  {{ allowReadThroughText }}
+                  {{ glacierRestoreTiertypeText }}
+                </span>
+              </cd-helper>
+            </td>
+            <td>{{ selection?.glacier_restore_tier_type }}</td>
+          </tr>
+        }
+        @if(isTierMatch( TIER_TYPE_DISPLAY.CLOUD_TIER, TIER_TYPE_DISPLAY.GLACIER)){
+          <tr>
+            <td class="bold"
+                i18n>
+              Restore Storage Class
+              <cd-helper class="text-pre-wrap">
+                <span>
+                  {{ restoreStorageClassText }}
+                </span>
+              </cd-helper>
+            </td>
+            <td>{{ selection?.restore_storage_class }}</td>
+          </tr>
+        }
+          @if(isTierMatch( TIER_TYPE_DISPLAY.CLOUD_TIER, TIER_TYPE_DISPLAY.GLACIER)){
+          <tr>
+            <td class="bold"
+                i18n>
+              Multipart Minimum Part Size
+              <cd-helper class="text-pre-wrap">
+                <span>
+                  {{ multipartMinPartText }}
                 </span>
               </cd-helper>
             </td>
-            <td>{{ selection?.allow_read_through }}</td>
+            <td>{{ selection?.multipart_min_part_size }}</td>
+          </tr>
+        }
+        @if(isTierMatch( TIER_TYPE_DISPLAY.CLOUD_TIER, TIER_TYPE_DISPLAY.GLACIER)){
+          <tr>
+            <td class="bold"
+                i18n>
+              Multipart Sync Threshold
+              <cd-helper class="text-pre-wrap">
+                <span>
+                  {{ multipartSyncThreholdText  }}
+                </span>
+              </cd-helper>
+            </td>
+            <td>{{ selection?.multipart_sync_threshold }}</td>
           </tr>
+        }
         </tbody>
       </table>
     </cds-tab>
index 8b9f4bbe64228b8562e85f39563d1d52ba14d2b7..6275c62d485c0cebc1a1f388e321d597bad73950 100644 (file)
@@ -40,7 +40,8 @@ describe('RgwStorageClassDetailsComponent', () => {
       multipart_sync_threshold: 200,
       host_style: 'path',
       retain_head_object: true,
-      allow_read_through: true
+      allow_read_through: true,
+      tier_type: 'local'
     };
     component.selection = mockSelection;
     component.ngOnChanges();
index 3a536f98f1c9dcc30fadd2838d18ac0bf12b0d1a..40049a69b959fdf1258a393c9e8c16288a00c8c4 100644 (file)
@@ -10,7 +10,14 @@ import {
   TARGET_ACCESS_KEY_TEXT,
   TARGET_PATH_TEXT,
   TARGET_SECRET_KEY_TEXT,
-  TIER_TYPE_DISPLAY
+  TIER_TYPE_DISPLAY,
+  TIER_TYPE,
+  GLACIER_RESTORE_DAY_TEXT,
+  GLACIER_RESTORE_TIER_TYPE_TEXT,
+  RESTORE_DAYS_TEXT,
+  READTHROUGH_RESTORE_DAYS_TEXT,
+  RESTORE_STORAGE_CLASS_TEXT,
+  ZONEGROUP_TEXT
 } from '../models/rgw-storage-class.model';
 @Component({
   selector: 'cd-rgw-storage-class-details',
@@ -31,6 +38,13 @@ export class RgwStorageClassDetailsComponent implements OnChanges {
   targetPathText = TARGET_PATH_TEXT;
   hostStyleText = HOST_STYLE;
   TIER_TYPE_DISPLAY = TIER_TYPE_DISPLAY;
+  TIER_TYPE = TIER_TYPE;
+  glacierRestoreDayText = GLACIER_RESTORE_DAY_TEXT;
+  glacierRestoreTiertypeText = GLACIER_RESTORE_TIER_TYPE_TEXT;
+  restoreDaysText = RESTORE_DAYS_TEXT;
+  readthroughrestoreDaysText = READTHROUGH_RESTORE_DAYS_TEXT;
+  restoreStorageClassText = RESTORE_STORAGE_CLASS_TEXT;
+  zoneGroupText = ZONEGROUP_TEXT;
 
   ngOnChanges() {
     if (this.selection) {
@@ -40,12 +54,22 @@ export class RgwStorageClassDetailsComponent implements OnChanges {
         access_key: this.selection.access_key,
         secret: this.selection.secret,
         target_path: this.selection.target_path,
+        tier_type: this.selection.tier_type,
         multipart_min_part_size: this.selection.multipart_min_part_size,
         multipart_sync_threshold: this.selection.multipart_sync_threshold,
         host_style: this.selection.host_style,
         retain_head_object: this.selection.retain_head_object,
-        allow_read_through: this.selection.allow_read_through
+        allow_read_through: this.selection.allow_read_through,
+        glacier_restore_days: this.selection.glacier_restore_days,
+        glacier_restore_tier_type: this.selection.glacier_restore_tier_type,
+        restore_storage_class: this.selection.restore_storage_class,
+        read_through_restore_days: this.selection.read_through_restore_days
       };
     }
   }
+
+  isTierMatch(...types: string[]): boolean {
+    const tier_type = this.selection.tier_type?.toLowerCase();
+    return types.some((type) => type.toLowerCase() === tier_type);
+  }
 }
index b34b48de67b5e51e8253f1128833ff9a847b2506..31cf2635c9529f96c33a6ba6d53fa9f1243d4516 100644 (file)
           i18n-label
           for="storageClassType"
           formControlName="storageClassType"
-          [helperText]="storageClassText"
+          [helperText]="textLabels.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>
+          <option *ngFor="let opt of storageClassOptions"
+                  [value]="opt.value"
+                  i18n>
+            {{ opt.label }}
+          </option>
         </cds-select>
         <ng-template #storageError>
           <span
           >
         </ng-template>
       </div>
-      <div *ngIf="storageClassForm.getValue('storageClassType') === TIER_TYPE.CLOUD_TIER">
+      @if( isTierMatch( TIER_TYPE.CLOUD_TIER, TIER_TYPE.GLACIER )){
+      <div>
         <div class="form-item form-item-append"
              cdsRow>
           <div cdsCol>
               i18n
               [invalid]="storageClassForm.showError('region', formDir, 'required')"
               [invalidText]="regionError"
-              [helperText]="targetRegionText"
+              [helperText]="textLabels.targetRegionText"
               >Target Region
               <input
                 cdsText
               i18n
               [invalid]="storageClassForm.showError('endpoint', formDir, 'required')"
               [invalidText]="endpointError"
-              [helperText]="targetEndpointText"
+              [helperText]="textLabels.targetEndpointText"
               >Target Endpoint
               <input
                 cdsText
             </ng-template>
           </div>
         </div>
-
         <!-- Access Key  -->
         <div class="form-item">
           <div cdsCol
               labelInputID="access_key"
               [invalid]="storageClassForm.showError('access_key', formDir, 'required')"
               [invalidText]="accessError"
-              [helperText]="targetAccessKeyText"
+              [helperText]="textLabels.targetAccessKeyText"
               i18n
               >Target Access Key
               <input
             </ng-template>
           </div>
         </div>
-
         <!-- Secret Key  -->
         <div class="form-item">
           <div cdsCol
                class="d-flex">
             <cds-password-label
               labelInputID="secret_key"
-              [helperText]="targetSecretKeyText"
+              [helperText]="textLabels.targetSecretKeyText"
               [invalid]="storageClassForm.showError('secret_key', formDir, 'required')"
               [invalidText]="secretError"
               i18n
             </ng-template>
           </div>
         </div>
-
         <!-- Target Path -->
         <div class="form-item">
           <cds-text-label
             i18n
             [invalid]="storageClassForm.showError('target_path', formDir, 'required')"
             [invalidText]="targetError"
-            [helperText]="targetPathText"
+            [helperText]="textLabels.targetPathText"
             >Target Path
             <input
               cdsText
             >
           </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>{{ textLabels?.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>{{ textLabels?.retainHeadObjectText }}</cd-help-text>
+          </cds-checkbox>
+        </div>
+        <div class="form-item form-item-append"
+             cdsRow>
+          <div cdsCol>
+            <cds-number
+              name="read_through_restore_days"
+              formControlName="read_through_restore_days"
+              id="read_through_restore_days"
+              min="1"
+              label="ReadThrough Restore Days"
+              [helperText]="textLabels.readthroughrestoreDaysText"
+              i18n-helperText
+              i18n-label
+              i18n
+              [invalid]="
+                storageClassForm.controls.read_through_restore_days.invalid &&
+                storageClassForm.controls.read_through_restore_days.dirty
+              "
+              [invalidText]="readThroughError"
+            ></cds-number>
+            <ng-template #readThroughError>
+              <span
+                class="invalid-feedback"
+                *ngIf="storageClassForm.showError('read_through_restore_days', formDir, 'pattern')"
+                i18n
+                >The entered value must be a positive integer.</span
+              >
+              <span
+                class="invalid-feedback"
+                *ngIf="storageClassForm.showError('read_through_restore_days', formDir, 'lockDays')"
+                i18n
+                >ReadThrough Restore Days must be positive.</span
+              >
+            </ng-template>
+          </div>
+          <div cdsCol>
+            <cds-select
+              formControlName="restore_storage_class"
+              label="Restore Storage Class"
+              id="restore_storage_class"
+              [helperText]="textLabels.restoreStorageClassText"
+              i18n-label
+            >
+              <option value=""
+                      i18n>-- Select the glacier restore storage class --</option>
+              <option [ngValue]="standard"
+                      i18n>Standard</option>
+            </cds-select>
+          </div>
+        </div>
       </div>
-      <div *ngIf="storageClassForm.getValue('storageClassType') === TIER_TYPE.CLOUD_TIER">
-        <ng-template #title>
-          <h5 class="cds--accordion__title cd-header">Advanced</h5>
-        </ng-template>
-        <fieldset>
-          <cds-accordion size="lg"
-                         class="form-item">
-            <cds-accordion-item
-              [title]="title"
-              id="advanced-fieldset"
-              (selected)="showAdvanced = !showAdvanced"
+      } @if(isTierMatch(TIER_TYPE.GLACIER)){
+      <div>
+        <legend class="cd-header"
+                i18n>Glacier Configuration</legend>
+        <div class="form-item form-item-append"
+             cdsRow>
+          <div cdsCol>
+            <cds-select
+              formControlName="glacier_restore_tier_type"
+              label="Glacier Restore Tier Type"
+              cdRequiredField="Glacier Restore Tier Type"
+              id="glacier_restore_tier_type"
+              [invalid]="
+                storageClassForm.controls.glacier_restore_tier_type.invalid &&
+                storageClassForm.controls.glacier_restore_tier_type.dirty
+              "
+              [invalidText]="glacierError"
+              [helperText]="textLabels.tiertypeText"
+              i18n-label
             >
-              <!-- 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
-                  (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>
-            </cds-accordion-item>
-          </cds-accordion>
-        </fieldset>
+              <option value=""
+                      i18n>-- Select the glacier restore tier type --</option>
+              <option [ngValue]="standard"
+                      i18n>Standard</option>
+              <option [ngValue]="expedited"
+                      i18n>Expedited</option>
+            </cds-select>
+            <ng-template #glacierError>
+              <span
+                class="invalid-feedback"
+                *ngIf="storageClassForm.showError('glacier_restore_tier_type', formDir, 'required')"
+                i18n
+                >This field is required.</span
+              >
+            </ng-template>
+          </div>
+          <div cdsCol>
+            <cds-number
+              [id]="'glacier_restore_days'"
+              [formControlName]="'glacier_restore_days'"
+              [label]="'Glacier Restore Days'"
+              [helperText]="textLabels.restoreDaysText"
+              [min]="1"
+              i18n-helperText
+              i18n-label
+              i18n
+              [invalid]="
+                storageClassForm.controls.glacier_restore_days.invalid &&
+                storageClassForm.controls.glacier_restore_days.dirty
+              "
+              [invalidText]="glacierRestoreError"
+            >
+            </cds-number>
+            <ng-template #glacierRestoreError>
+              <span
+                class="invalid-feedback"
+                *ngIf="storageClassForm.showError('glacier_restore_days', formDir, 'pattern')"
+                i18n
+                >The entered value must be a positive integer.</span
+              >
+              <span
+                class="invalid-feedback"
+                *ngIf="storageClassForm.showError('glacier_restore_days', formDir, 'lockDays')"
+                i18n
+                >Glacier Restore Days must be positive.</span
+              >
+            </ng-template>
+          </div>
+        </div>
       </div>
-      <cd-alert-panel
-        type="warning"
-        spacingClass="mb-2"
-        *ngIf="storageClassForm.getValue('storageClassType') === TIER_TYPE.CLOUD_TIER"
-      >
+      } @if( isTierMatch( TIER_TYPE.CLOUD_TIER, TIER_TYPE.GLACIER )){
+      <fieldset>
+        <div>
+          <ng-template #title>
+            <h5 class="cds--accordion__title cd-header">Advanced</h5>
+          </ng-template>
+          <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]="textLabels.multipartSyncThresholdText"
+                      cdOptionalField="Multipart Sync Threshold"
+                      >Multipart Sync Threshold
+                      <input
+                        cdsText
+                        type="text"
+                        id="multipart_sync_threshold"
+                        formControlName="multipart_sync_threshold"
+                        cdDimlessBinary
+                      />
+                    </cds-text-label>
+                  </div>
+                  <div cdsCol>
+                    <cds-text-label
+                      labelInputID="multipart_min_part_size"
+                      i18n
+                      [helperText]="textLabels.multipartMinPartText"
+                      cdOptionalField="Multipart Minimum Part Size"
+                      >Multipart Minimum Part Size
+                      <input
+                        cdsText
+                        type="text"
+                        id="multipart_min_part_size"
+                        formControlName="multipart_min_part_size"
+                        cdDimlessBinary
+                      />
+                    </cds-text-label>
+                  </div>
+                </div>
+              </cds-accordion-item>
+            </cds-accordion>
+          </fieldset>
+        </div>
+      </fieldset>
+      } @if( isTierMatch( TIER_TYPE.CLOUD_TIER, TIER_TYPE.GLACIER )){
+      <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"
index 7417529f7b65edbbc5946038691adeb97d94e5c3..c7e7a6b5c5adb07ff6583a83fba6786b76b006ea 100644 (file)
@@ -14,6 +14,7 @@ import {
 } from 'carbon-components-angular';
 import { CoreModule } from '~/app/core/core.module';
 import { RgwStorageClassFormComponent } from './rgw-storage-class-form.component';
+import { TIER_TYPE_DISPLAY } from '../models/rgw-storage-class.model';
 
 describe('RgwStorageClassFormComponent', () => {
   let component: RgwStorageClassFormComponent;
@@ -74,6 +75,8 @@ describe('RgwStorageClassFormComponent', () => {
                     retain_head_object: true,
                     storage_class: 'CLOUDIBM',
                     allow_read_through: true,
+                    read_through_restore_days: 1,
+                    restore_storage_class: 'test67',
                     s3: {
                       storage_class: 'CLOUDIBM',
                       endpoint: 'https://s3.amazonaws.com',
@@ -85,6 +88,10 @@ describe('RgwStorageClassFormComponent', () => {
                       multipart_min_part_size: 87877,
                       multipart_sync_threshold: 987877,
                       host_style: true
+                    },
+                    's3-glacier': {
+                      glacier_restore_days: 5,
+                      glacier_restore_tier_type: 'Standard'
                     }
                   }
                 }
@@ -118,4 +125,59 @@ describe('RgwStorageClassFormComponent', () => {
     component.submitAction();
     expect(component).toBeTruthy();
   });
+
+  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'];
+    requiredFields.forEach((field) => {
+      const control = component.storageClassForm.get(field);
+      control.setValue('');
+      control.updateValueAndValidity();
+    });
+    ['glacier_restore_tier_type', 'restore_storage_class'].forEach((field) => {
+      const control = component.storageClassForm.get(field);
+      control.setValue('');
+      control.updateValueAndValidity();
+      expect(component).toBeTruthy();
+    });
+  });
+
+  it('should set required validators for GLACIER fields', () => {
+    (component as any).updateValidatorsBasedOnStorageClass(TIER_TYPE_DISPLAY.GLACIER);
+    const requiredFields = [
+      'region',
+      'endpoint',
+      'access_key',
+      'secret_key',
+      'target_path',
+      'glacier_restore_tier_type',
+      'restore_storage_class'
+    ];
+    requiredFields.forEach((field) => {
+      const control = component.storageClassForm.get(field);
+      control.setValue('');
+      control.updateValueAndValidity();
+      expect(component).toBeTruthy();
+    });
+  });
+
+  it('should clear validators for LOCAL fields', () => {
+    (component as any).updateValidatorsBasedOnStorageClass(TIER_TYPE_DISPLAY.LOCAL);
+
+    const allFields = [
+      'region',
+      'endpoint',
+      'access_key',
+      'secret_key',
+      'target_path',
+      'glacier_restore_tier_type',
+      'restore_storage_class'
+    ];
+    allFields.forEach((field) => {
+      const control = component.storageClassForm.get(field);
+      control.setValue('');
+      control.updateValueAndValidity();
+      expect(component).toBeTruthy();
+    });
+  });
 });
index 04b0d4a2b03dede61786f972079ab5f063bd0d35..2e813005ae59af122cd96edc9a58c29d4b067ed1 100644 (file)
@@ -1,5 +1,5 @@
 import { Component, OnInit } from '@angular/core';
-import { FormControl, Validators } from '@angular/forms';
+import { AbstractControl, FormControl, Validators } from '@angular/forms';
 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';
@@ -28,7 +28,20 @@ import {
   ZoneGroup,
   ZoneGroupDetails,
   CLOUDS3_STORAGE_CLASS_TEXT,
-  LOCAL_STORAGE_CLASS_TEXT
+  LOCAL_STORAGE_CLASS_TEXT,
+  GLACIER_STORAGE_CLASS_TEXT,
+  GLACIER_RESTORE_DAY_TEXT,
+  GLACIER_RESTORE_TIER_TYPE_TEXT,
+  RESTORE_DAYS_TEXT,
+  READTHROUGH_RESTORE_DAYS_TEXT,
+  RESTORE_STORAGE_CLASS_TEXT,
+  TIER_TYPE_DISPLAY,
+  S3Glacier,
+  StorageClassOption,
+  STORAGE_CLASS_CONSTANTS,
+  STANDARD_TIER_TYPE_TEXT,
+  EXPEDITED_TIER_TYPE_TEXT,
+  TextLabels
 } from '../models/rgw-storage-class.model';
 import { NotificationType } from '~/app/shared/enum/notification-type.enum';
 import { NotificationService } from '~/app/shared/services/notification.service';
@@ -44,27 +57,21 @@ export class RgwStorageClassFormComponent extends CdForm implements OnInit {
   action: string;
   resource: string;
   editing: boolean;
-  targetPathText: string;
-  targetEndpointText: string;
-  targetRegionText: string;
   showAdvanced: boolean = false;
   defaultZoneGroup: string;
   zonegroupNames: ZoneGroup[];
   placementTargets: string[] = [];
-  multipartMinPartText: string;
-  storageClassText: string;
-  multipartSyncThreholdText: string;
   selectedZoneGroup: string;
   defaultZonegroup: ZoneGroup;
   zoneGroupDetails: ZoneGroupDetails;
-  targetSecretKeyText: string;
-  targetAccessKeyText: string;
-  retainHeadObjectText: string;
   storageClassInfo: StorageClass;
   tierTargetInfo: TierTarget;
-  allowReadThroughText: string;
+  glacierStorageClassDetails: S3Glacier;
   allowReadThrough: boolean = false;
   TIER_TYPE = TIER_TYPE;
+  TIER_TYPE_DISPLAY = TIER_TYPE_DISPLAY;
+  storageClassOptions: StorageClassOption[];
+  textLabels: TextLabels;
 
   constructor(
     public actionLabels: ActionLabelsI18n,
@@ -82,18 +89,32 @@ export class RgwStorageClassFormComponent extends CdForm implements OnInit {
   }
 
   ngOnInit() {
-    this.multipartMinPartText = MULTIPART_MIN_PART_TEXT;
-    this.multipartSyncThreholdText = MULTIPART_SYNC_THRESHOLD_TEXT;
-    this.targetPathText = TARGET_PATH_TEXT;
-    this.targetRegionText = TARGET_REGION_TEXT;
-    this.targetEndpointText = TARGET_ENDPOINT_TEXT;
-    this.targetAccessKeyText = TARGET_ACCESS_KEY_TEXT;
-    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.textLabels = {
+      targetPathText: TARGET_PATH_TEXT,
+      targetEndpointText: TARGET_ENDPOINT_TEXT,
+      targetRegionText: TARGET_REGION_TEXT,
+      targetAccessKeyText: TARGET_ACCESS_KEY_TEXT,
+      targetSecretKeyText: TARGET_SECRET_KEY_TEXT,
+      retainHeadObjectText: RETAIN_HEAD_OBJECT_TEXT,
+      allowReadThroughText: ALLOW_READ_THROUGH_TEXT,
+      storageClassText: LOCAL_STORAGE_CLASS_TEXT,
+      multipartMinPartText: MULTIPART_MIN_PART_TEXT,
+      multipartSyncThresholdText: MULTIPART_SYNC_THRESHOLD_TEXT,
+      tiertypeText: STANDARD_TIER_TYPE_TEXT,
+      glacierRestoreDayText: GLACIER_RESTORE_DAY_TEXT,
+      glacierRestoreTiertypeText: GLACIER_RESTORE_TIER_TYPE_TEXT,
+      restoreDaysText: RESTORE_DAYS_TEXT,
+      readthroughrestoreDaysText: READTHROUGH_RESTORE_DAYS_TEXT,
+      restoreStorageClassText: RESTORE_STORAGE_CLASS_TEXT
+    };
+    this.storageClassOptions = [
+      { value: TIER_TYPE.LOCAL, label: TIER_TYPE_DISPLAY.LOCAL },
+      { value: TIER_TYPE.CLOUD_TIER, label: TIER_TYPE_DISPLAY.CLOUD_TIER },
+      { value: TIER_TYPE.GLACIER, label: TIER_TYPE_DISPLAY.GLACIER }
+    ];
     this.createForm();
+    this.storageClassTypeText();
+    this.TierTypeText();
     this.loadingReady();
     this.loadZoneGroup();
     if (this.editing) {
@@ -124,44 +145,97 @@ export class RgwStorageClassFormComponent extends CdForm implements OnInit {
             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
+            allow_read_through: this.tierTargetInfo?.val?.allow_read_through || false,
+            restore_storage_class: this.tierTargetInfo?.val?.restore_storage_class,
+            read_through_restore_days: this.tierTargetInfo?.val?.read_through_restore_days
           });
+          if (this.tierTargetInfo?.val?.tier_type == TIER_TYPE.GLACIER) {
+            let glacierResponse = this.tierTargetInfo?.val['s3-glacier'];
+            this.storageClassForm.patchValue({
+              glacier_restore_tier_type: glacierResponse.glacier_restore_tier_type,
+              glacier_restore_days: glacierResponse.glacier_restore_days
+            });
+          }
         });
     }
-    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('storageClassType').valueChanges.subscribe((value) => {
+      this.updateValidatorsBasedOnStorageClass(value);
     });
     this.storageClassForm.get('allow_read_through').valueChanges.subscribe((value) => {
       this.onAllowReadThroughChange(value);
     });
   }
 
+  private updateValidatorsBasedOnStorageClass(value: string) {
+    const controlsToUpdate = [
+      'region',
+      'endpoint',
+      'access_key',
+      'secret_key',
+      'target_path',
+      'glacier_restore_tier_type',
+      'restore_storage_class'
+    ];
+
+    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)) ||
+        (value === TIER_TYPE.GLACIER &&
+          [
+            'glacier_restore_tier_type',
+            'restore_storage_class',
+            'region',
+            'endpoint',
+            'access_key',
+            'secret_key',
+            'target_path'
+          ].includes(field))
+      ) {
+        control.setValidators([Validators.required]);
+      } else {
+        control.clearValidators();
+      }
+
+      control.updateValueAndValidity();
+    });
+  }
+
   storageClassTypeText() {
     this.storageClassForm?.get('storageClassType')?.valueChanges.subscribe((value) => {
       if (value === TIER_TYPE.LOCAL) {
-        this.storageClassText = LOCAL_STORAGE_CLASS_TEXT;
+        this.textLabels.storageClassText = LOCAL_STORAGE_CLASS_TEXT;
       } else if (value === TIER_TYPE.CLOUD_TIER) {
-        this.storageClassText = CLOUDS3_STORAGE_CLASS_TEXT;
+        this.textLabels.storageClassText = CLOUDS3_STORAGE_CLASS_TEXT;
+      } else if (value === TIER_TYPE.GLACIER) {
+        this.textLabels.storageClassText = GLACIER_STORAGE_CLASS_TEXT;
+      }
+    });
+  }
+
+  TierTypeText() {
+    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;
       } else {
-        this.storageClassText = LOCAL_STORAGE_CLASS_TEXT;
+        this.textLabels.tiertypeText = EXPEDITED_TIER_TYPE_TEXT;
       }
     });
   }
 
   createForm() {
+    const self = this;
+
+    const lockDaysValidator = CdValidators.custom('lockDays', () => {
+      if (!self.storageClassForm || !self.storageClassForm.getRawValue()) {
+        return false;
+      }
+
+      const lockDays = Number(self.storageClassForm.getValue('read_through_restore_days'));
+      return !Number.isInteger(lockDays) || lockDays === 0;
+    });
     this.storageClassForm = this.formBuilder.group({
       storage_class: new FormControl('', {
         validators: [Validators.required]
@@ -188,8 +262,35 @@ export class RgwStorageClassFormComponent extends CdForm implements OnInit {
         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),
+      glacier_restore_tier_type: new FormControl(STORAGE_CLASS_CONSTANTS.DEFAULT_STORAGE_CLASS, [
+        CdValidators.composeIf({ storageClassType: TIER_TYPE.GLACIER }, [Validators.required])
+      ]),
+      glacier_restore_days: new FormControl(STORAGE_CLASS_CONSTANTS.DEFAULT_GLACIER_RESTORE_DAYS, [
+        CdValidators.composeIf({ storageClassType: TIER_TYPE.GLACIER || TIER_TYPE.CLOUD_TIER }, [
+          CdValidators.number(false),
+          lockDaysValidator
+        ])
+      ]),
+      restore_storage_class: new FormControl(STORAGE_CLASS_CONSTANTS.DEFAULT_STORAGE_CLASS),
+      read_through_restore_days: new FormControl(
+        {
+          value: STORAGE_CLASS_CONSTANTS.DEFAULT_READTHROUGH_RESTORE_DAYS,
+          disabled: true
+        },
+        CdValidators.composeIf(
+          (form: AbstractControl) => {
+            const type = form.get('storageClassType')?.value;
+            return type === TIER_TYPE.GLACIER || type === TIER_TYPE.CLOUD_TIER;
+          },
+          [CdValidators.number(false), lockDaysValidator]
+        )
+      ),
+      multipart_sync_threshold: new FormControl(
+        STORAGE_CLASS_CONSTANTS.DEFAULT_MULTIPART_SYNC_THRESHOLD
+      ),
+      multipart_min_part_size: new FormControl(
+        STORAGE_CLASS_CONSTANTS.DEFAULT_MULTIPART_MIN_PART_SIZE
+      ),
       allow_read_through: new FormControl(false),
       storageClassType: new FormControl(TIER_TYPE.LOCAL, Validators.required)
     });
@@ -213,7 +314,6 @@ export class RgwStorageClassFormComponent extends CdForm implements OnInit {
           this.defaultZonegroup = this.zonegroupNames.find(
             (zonegroups: ZoneGroup) => zonegroups.id === data.default_zonegroup
           );
-
           this.storageClassForm.get('zonegroup').setValue(this.defaultZonegroup.name);
           this.onZonegroupChange();
           resolve();
@@ -292,14 +392,22 @@ export class RgwStorageClassFormComponent extends CdForm implements OnInit {
 
   onAllowReadThroughChange(checked: boolean): void {
     this.allowReadThrough = checked;
+    const readThroughDaysControl = this.storageClassForm.get('read_through_restore_days');
     if (this.allowReadThrough) {
       this.storageClassForm.get('retain_head_object')?.setValue(true);
       this.storageClassForm.get('retain_head_object')?.disable();
+      readThroughDaysControl?.enable();
     } else {
       this.storageClassForm.get('retain_head_object')?.enable();
+      readThroughDaysControl?.disable();
     }
   }
 
+  isTierMatch(...types: string[]): boolean {
+    const tierType = this.storageClassForm.getValue('storageClassType');
+    return types.includes(tierType);
+  }
+
   buildRequest() {
     if (this.storageClassForm.errors) return null;
 
@@ -309,7 +417,6 @@ export class RgwStorageClassFormComponent extends CdForm implements OnInit {
     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;
-
     return this.buildPlacementTargets(
       storageClassType,
       zoneGroup,
@@ -328,44 +435,68 @@ export class RgwStorageClassFormComponent extends CdForm implements OnInit {
     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
+    const baseTarget = {
+      placement_id: placementId,
+      storage_class: storageClass
+    };
+
+    if (storageClassType === TIER_TYPE.LOCAL) {
+      return {
+        zone_group: zoneGroup,
+        placement_targets: [baseTarget]
+      };
+    }
+
+    const tierConfig = {
+      endpoint: rawFormValue.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,
+      restore_storage_class: rawFormValue.restore_storage_class,
+      ...(rawFormValue.allow_read_through
+        ? { read_through_restore_days: rawFormValue.read_through_restore_days }
+        : {})
+    };
+
+    if (storageClassType === TIER_TYPE.CLOUD_TIER) {
+      return {
+        zone_group: zoneGroup,
+        placement_targets: [
+          {
+            ...baseTarget,
+            tier_type: TIER_TYPE.CLOUD_TIER,
+            tier_config: {
+              ...tierConfig
             }
-          ]
-        };
+          }
+        ]
+      };
+    }
 
-      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
-              }
+    if (storageClassType === TIER_TYPE.GLACIER) {
+      return {
+        zone_group: zoneGroup,
+        placement_targets: [
+          {
+            ...baseTarget,
+            tier_type: TIER_TYPE.GLACIER,
+            tier_config: {
+              ...tierConfig,
+              glacier_restore_days: rawFormValue.glacier_restore_days,
+              glacier_restore_tier_type: rawFormValue.glacier_restore_tier_type
             }
-          ]
-        };
-      default:
-        return null;
+          }
+        ]
+      };
     }
+    return {
+      zone_group: zoneGroup,
+      placement_targets: [baseTarget]
+    };
   }
 }
index 714d0234314541898ecf9a83c16bcc3ba3f27cc6..f8ddae11909c353a74820baefa1f8bd84fe812f6 100644 (file)
@@ -124,15 +124,27 @@ export class RgwStorageClassListComponent extends ListWithDetails implements OnI
         (data: ZoneGroupDetails) => {
           this.storageClassList = [];
           const tierObj = BucketTieringUtils.filterAndMapTierTargets(data);
-          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
-          }));
+          const tierConfig = tierObj.map((item) => {
+            let tierTypeDisplay;
+
+            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();
index 8b8e2076850f2116f38de111c1d588899e283c9f..c075ed1d267b9ac368184b43d4826270d279d8d4 100644 (file)
@@ -32,23 +32,33 @@ export class BucketTieringUtils {
 
   private static getTierTargets(tierTarget: TierTarget, zoneGroup: string, targetName: string) {
     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 {
+    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,
+      allow_read_through: val.allow_read_through,
+      restore_storage_class: val.restore_storage_class,
+      read_through_restore_days: val.read_through_restore_days,
+      ...val.s3
+    };
+
+    if (tierType === TIER_TYPE.GLACIER) {
       return {
-        zonegroup_name: zoneGroup,
-        placement_target: targetName,
-        storage_class: val.storage_class,
-        tier_type: TIER_TYPE.LOCAL
+        ...cloudProps,
+        ...val['s3-glacier']
       };
     }
+    return cloudProps;
   }
 }
index 10cb5a60eba8d340cc9cc8c11502b5c92cb3e5ae..411264c9aee0e06b47e479216d23a68e4185b947 100755 (executable)
@@ -2033,7 +2033,7 @@ class RgwMultisite:
     def add_placement_targets(self, zonegroup_name: str, placement_targets: List[Dict]):
         rgw_add_placement_cmd = ['zonegroup', 'placement', 'add']
         STANDARD_STORAGE_CLASS = "STANDARD"
-        CLOUD_S3_TIER_TYPE = "cloud-s3"
+        CLOUD_S3_TIER_TYPES = ["cloud-s3", "cloud-s3-glacier"]
 
         for placement_target in placement_targets:  # pylint: disable=R1702
             cmd_add_placement_options = [
@@ -2043,16 +2043,14 @@ class RgwMultisite:
             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
-                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:
@@ -2079,7 +2077,7 @@ class RgwMultisite:
                         )
                 except SubprocessError as error:
                     raise DashboardException(error, http_status_code=500, component='rgw')
-                if tier_type == CLOUD_S3_TIER_TYPE:
+                if tier_type in CLOUD_S3_TIER_TYPES:
                     self.ensure_realm_and_sync_period()
 
             if storage_classes:
@@ -2103,13 +2101,13 @@ class RgwMultisite:
                                 )
                         except SubprocessError as error:
                             raise DashboardException(error, http_status_code=500, component='rgw')
-                        if tier_type == CLOUD_S3_TIER_TYPE:
+                        if tier_type in CLOUD_S3_TIER_TYPES:
                             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']
         STANDARD_STORAGE_CLASS = "STANDARD"
-        CLOUD_S3_TIER_TYPE = "cloud-s3"
+        CLOUD_S3_TIER_TYPES = ["cloud-s3", "cloud-s3-glacier"]
 
         for placement_target in placement_targets:  # pylint: disable=R1702,line-too-long # noqa: E501
             cmd_add_placement_options = [
@@ -2117,9 +2115,10 @@ class RgwMultisite:
                 '--placement-id', placement_target['placement_id']
             ]
             storage_class_name = placement_target.get('storage_class', None)
+            tier_type = placement_target.get('tier_type')
 
             if (
-                placement_target.get('tier_type') == CLOUD_S3_TIER_TYPE
+                placement_target.get('tier_type') == CLOUD_S3_TIER_TYPES
                 and storage_class_name != STANDARD_STORAGE_CLASS
             ):
                 tier_config = placement_target.get('tier_config', {})
@@ -2154,7 +2153,8 @@ class RgwMultisite:
                         )
                 except SubprocessError as error:
                     raise DashboardException(error, http_status_code=500, component='rgw')
-                self.ensure_realm_and_sync_period()
+                if tier_type in CLOUD_S3_TIER_TYPES:
+                    self.ensure_realm_and_sync_period()
 
             if storage_classes:
                 for sc in storage_classes:
@@ -2177,7 +2177,8 @@ class RgwMultisite:
                                 )
                         except SubprocessError as error:
                             raise DashboardException(error, http_status_code=500, component='rgw')
-                        self.ensure_realm_and_sync_period()
+                        if tier_type in CLOUD_S3_TIER_TYPES:
+                            self.ensure_realm_and_sync_period()
 
     def delete_placement_targets(self, placement_id: str, storage_class: str):
         rgw_zonegroup_delete_cmd = ['zonegroup', 'placement', 'rm',