]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Tieiring - allow read through 62943/head
authorDnyaneshwari <dnyaneshwari@li-9c9fbecc-2d5c-11b2-a85c-e2a7cc8a424f.ibm.com>
Thu, 24 Apr 2025 06:51:00 +0000 (12:21 +0530)
committerDnyaneshwari <dnyaneshwari@li-9c9fbecc-2d5c-11b2-a85c-e2a7cc8a424f.ibm.com>
Wed, 18 Jun 2025 14:42:14 +0000 (20:12 +0530)
Fixes: https://tracker.ceph.com/issues/71053
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/utils/rgw-bucket-tiering.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-storage-class.service.spec.ts

index 6e7b1d745d119fbafeb88994811d0758645c3cce..3f5523dcb96df75dbeb1952e53caed1de595bd12 100644 (file)
@@ -20,19 +20,7 @@ export interface StorageClassDetails {
   multipart_sync_threshold: number;
   host_style: string;
   retain_head_object: boolean;
-}
-
-export interface S3Details {
-  endpoint: string;
-  access_key: string;
-  storage_class: string;
-  target_path: string;
-  target_storage_class: string;
-  region: string;
-  secret: string;
-  multipart_min_part_size: number;
-  multipart_sync_threshold: number;
-  host_style: boolean;
+  allow_read_through: boolean;
 }
 
 export interface TierTarget {
@@ -40,6 +28,7 @@ export interface TierTarget {
     storage_class: string;
     tier_type: string;
     retain_head_object: boolean;
+    allow_read_through: boolean;
     s3: S3Details;
   };
 }
@@ -49,15 +38,6 @@ export interface Target {
   tier_targets: TierTarget[];
 }
 
-export interface StorageClassDetails {
-  target_path: string;
-  access_key: string;
-  secret: string;
-  multipart_min_part_size: number;
-  multipart_sync_threshold: number;
-  host_style: string;
-}
-
 export interface ZoneGroup {
   name: string;
   id: string;
@@ -76,6 +56,7 @@ export interface S3Details {
   multipart_sync_threshold: number;
   host_style: boolean;
   retain_head_object?: boolean;
+  allow_read_through?: boolean;
 }
 export interface RequestModel {
   zone_group: string;
@@ -92,6 +73,7 @@ export interface PlacementTarget {
     secret: string;
     target_path: string;
     retain_head_object: boolean;
+    allow_read_through: boolean;
     region: string;
     multipart_sync_threshold: number;
     multipart_min_part_size: number;
@@ -104,3 +86,33 @@ export interface PlacementTarget {
 export const CLOUD_TIER = 'cloud-s3';
 
 export const DEFAULT_PLACEMENT = 'default-placement';
+
+export const ALLOW_READ_THROUGH_TEXT =
+  'Enables fetching objects from remote cloud S3 if not found locally.';
+
+export const MULTIPART_MIN_PART_TEXT =
+  'It specifies that objects this size or larger are transitioned to the cloud using multipart upload.';
+
+export const MULTIPART_SYNC_THRESHOLD_TEXT =
+  'It specifies the minimum part size to use when transitioning objects using multipart upload.';
+
+export const TARGET_PATH_TEXT =
+  'Target Path refers to the storage location (e.g., bucket or container) in the cloud where data will be stored.';
+
+export const TARGET_REGION_TEXT =
+  'The region of the remote cloud service where storage is located.';
+
+export const TARGET_ENDPOINT_TEXT =
+  'The URL endpoint of the remote cloud service for accessing storage.';
+
+export const TARGET_ACCESS_KEY_TEXT =
+  "To view or copy your access 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.";
+
+export const TARGET_SECRET_KEY_TEXT =
+  "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 secret key. You can view and copy the key by following the instructions provided.";
+
+export const RETAIN_HEAD_OBJECT_TEXT = 'Retain object metadata after transition to the cloud.';
+
+export const HOST_STYLE = `The URL format for accessing the remote S3 endpoint:
+  - 'Path': Use for a path-based URL
+  - 'Virtual': Use for a domain-based URL`;
index 378c0b6372a8b93de524ce5bdbd2764015c0453c..998867d3b6f29e9ad084458e3219caec2ff061ae 100644 (file)
@@ -12,8 +12,7 @@
               Target Path
               <cd-helper class="text-pre-wrap">
                 <span i18n>
-                  The target path specifies a prefix to which the source bucket-name/object-name is
-                  appended.
+                 {{ targetPathText }}
                 </span>
               </cd-helper>
             </td>
@@ -24,7 +23,7 @@
               Access key
               <cd-helper class="text-pre-wrap">
                 <span i18n>
-                  Access key is the remote cloud S3 access key used for a specific connection.
+                  {{ targetAccessKeyText }}
                 </span>
               </cd-helper>
             </td>
@@ -50,7 +49,7 @@
             <td class="bold">
               Secret key
               <cd-helper class="text-pre-wrap">
-                <span i18n> Secret is the secret key for the remote cloud S3 service. </span>
+                <span i18n> {{ targetSecretKeyText }} </span>
               </cd-helper>
             </td>
             <td>
             </td>
           </tr>
           <tr>
-            <td
-                class="bold">
+            <td class="bold">
               Host Style
               <cd-helper class="text-pre-wrap">
-                <span i18n>The URL format for accessing the remote S3 endpoint: 'Path' for a path-based URL
-                  or 'Virtual' for a domain-based URL.</span>
+                <span i18n>{{ hostStyleText }}</span
+                >
               </cd-helper>
             </td>
             <td>{{ selection?.host_style }}</td>
           </tr>
           <tr>
-            <td
-                class="bold">
+            <td class="bold">
               Multipart Minimum Part Size
               <cd-helper class="text-pre-wrap">
                 <span i18n>
-                  Minimum parts size to use when transitioning objects using multipart upload.
+                  {{ multipartMinPartText }}
                 </span>
               </cd-helper>
             </td>
             <td>{{ selection?.multipart_min_part_size }}</td>
           </tr>
           <tr>
-            <td
-                class="bold">
+            <td class="bold">
               Multipart Sync Threshold
               <cd-helper class="text-pre-wrap">
                 <span i18n>
-                  Objects this size or larger will be transitioned to the cloud using multipart
-                  upload.
+                 {{ multipartSyncThreholdText }}
                 </span>
               </cd-helper>
             </td>
             <td>{{ selection?.multipart_sync_threshold }}</td>
           </tr>
           <tr>
-            <td
-                class="bold">
-              Retain Head Object
+            <td class="bold">
+              <span i18n>Allow Read Through </span>
+              <cd-helper class="text-pre-wrap">
+                <span i18n>
+                  {{ allowReadThroughText }}
+                </span>
+              </cd-helper>
+            </td>
+            <td>{{ selection?.allow_read_through }}</td>
+          </tr>
+          <tr>
+            <td class="bold">
+              Head Object (Stub File)
               <cd-helper class="text-pre-wrap">
                 <span i18n>
-                  Retain object metadata after transition to the cloud (default: false).
+                  {{ retainHeadObjectText }}
                 </span>
               </cd-helper>
             </td>
index ca7e9fcaac79636bbce2eea2fe3774d5e9bed8a5..8b9f4bbe64228b8562e85f39563d1d52ba14d2b7 100644 (file)
@@ -39,7 +39,8 @@ describe('RgwStorageClassDetailsComponent', () => {
       multipart_min_part_size: 100,
       multipart_sync_threshold: 200,
       host_style: 'path',
-      retain_head_object: true
+      retain_head_object: true,
+      allow_read_through: true
     };
     component.selection = mockSelection;
     component.ngOnChanges();
index aeb72b0bdc213ea5848073f3d02c546f291709d0..14b01a140428ea0f3696799e0469053be6a86763 100644 (file)
@@ -1,6 +1,16 @@
 import { Component, Input, OnChanges } from '@angular/core';
 import { CdTableColumn } from '~/app/shared/models/cd-table-column';
-import { StorageClassDetails } from '../models/rgw-storage-class.model';
+import {
+  ALLOW_READ_THROUGH_TEXT,
+  HOST_STYLE,
+  MULTIPART_MIN_PART_TEXT,
+  MULTIPART_SYNC_THRESHOLD_TEXT,
+  RETAIN_HEAD_OBJECT_TEXT,
+  StorageClassDetails,
+  TARGET_ACCESS_KEY_TEXT,
+  TARGET_PATH_TEXT,
+  TARGET_SECRET_KEY_TEXT
+} from '../models/rgw-storage-class.model';
 
 @Component({
   selector: 'cd-rgw-storage-class-details',
@@ -12,6 +22,14 @@ export class RgwStorageClassDetailsComponent implements OnChanges {
   selection: StorageClassDetails;
   columns: CdTableColumn[] = [];
   storageDetails: StorageClassDetails;
+  allowReadThroughText = ALLOW_READ_THROUGH_TEXT;
+  retainHeadObjectText = RETAIN_HEAD_OBJECT_TEXT;
+  multipartMinPartText = MULTIPART_MIN_PART_TEXT;
+  multipartSyncThreholdText = MULTIPART_SYNC_THRESHOLD_TEXT;
+  targetAccessKeyText = TARGET_ACCESS_KEY_TEXT;
+  targetSecretKeyText = TARGET_SECRET_KEY_TEXT;
+  targetPathText = TARGET_PATH_TEXT;
+  hostStyleText = HOST_STYLE;
 
   ngOnChanges() {
     if (this.selection) {
@@ -22,7 +40,8 @@ export class RgwStorageClassDetailsComponent implements OnChanges {
         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
+        retain_head_object: this.selection.retain_head_object,
+        allow_read_through: this.selection.allow_read_through
       };
     }
   }
index 3d930d5bdc2d75e20300f5a1da303a8493d5fd07..ce50c68d116320daee9c46f83721f68fe8122668 100644 (file)
               placeholder="e.g, us-east-1"
               i18n-placeholder
               [invalid]="
-              storageClassForm.showError('region', formDir, 'required')
-              "
-            />
+              storageClassForm.showError('region', formDir, 'required')"/>
           </cds-text-label>
           <ng-template #regionError>
             <span
           >
         </ng-template>
       </div>
+      <div class="form-item">
+        <cds-checkbox
+          id="allow_read_through"
+          formControlName="allow_read_through"
+          cdOptionalField="Allow Read Through"
+          i18n
+          (change)="onAllowReadThroughChange($event)"
+          >Allow Read Through
+          <cd-help-text>{{ allowReadThroughText }}</cd-help-text>
+        </cds-checkbox>
+      </div>
+      <div class="form-item">
+        <cds-checkbox
+          id="retain_head_object"
+          formControlName="retain_head_object"
+          cdOptionalField="Head Object (Stub File)"
+          i18n
+          >Head Object (Stub File)
+          <cd-help-text>{{ retainHeadObjectText }}</cd-help-text>
+        </cds-checkbox>
+      </div>
 
       <fieldset>
         <cds-accordion size="lg"
                 </cds-text-label>
               </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>
index 80f5fcf7a108d7ba2d86f5f35cefb5e328b4e50b..5a8d5c3d4bb2a09070e71e5ded469a523fa42cde 100644 (file)
@@ -72,6 +72,7 @@ describe('RgwStorageClassFormComponent', () => {
                     storage_class: 'CLOUDIBM',
                     tier_type: 'cloud-s3',
                     retain_head_object: true,
+                    allow_read_through: true,
                     s3: {
                       endpoint: 'https://s3.amazonaws.com',
                       access_key: 'ACCESSKEY',
@@ -96,6 +97,7 @@ describe('RgwStorageClassFormComponent', () => {
                     storage_class: 'CloudIBM',
                     tier_type: 'cloud-s3',
                     retain_head_object: true,
+                    allow_read_through: true,
                     s3: {
                       endpoint: 'https://s3.amazonaws.com',
                       access_key: 'ACCESSKEY',
@@ -132,6 +134,7 @@ describe('RgwStorageClassFormComponent', () => {
     component.storageClassForm.get('secret_key').setValue('secretkey');
     component.storageClassForm.get('target_path').setValue('/target');
     component.storageClassForm.get('retain_head_object').setValue(true);
+    component.storageClassForm.get('allow_read_through').setValue(true);
     component.storageClassForm.get('region').setValue('useast1');
     component.storageClassForm.get('multipart_sync_threshold').setValue(1024);
     component.storageClassForm.get('multipart_min_part_size').setValue(256);
index 643b690e57648dde13eab333678a40d2e2a646bf..b2b8862109fc46a2db8c8c35b24e055069cfd872 100644 (file)
@@ -9,12 +9,21 @@ 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 {
+  ALLOW_READ_THROUGH_TEXT,
   CLOUD_TIER,
   DEFAULT_PLACEMENT,
+  MULTIPART_MIN_PART_TEXT,
+  MULTIPART_SYNC_THRESHOLD_TEXT,
   PlacementTarget,
   RequestModel,
+  RETAIN_HEAD_OBJECT_TEXT,
   StorageClass,
   Target,
+  TARGET_ACCESS_KEY_TEXT,
+  TARGET_ENDPOINT_TEXT,
+  TARGET_PATH_TEXT,
+  TARGET_REGION_TEXT,
+  TARGET_SECRET_KEY_TEXT,
   TierTarget,
   ZoneGroup,
   ZoneGroupDetails
@@ -49,6 +58,8 @@ export class RgwStorageClassFormComponent extends CdForm implements OnInit {
   retainHeadObjectText: string;
   storageClassInfo: StorageClass;
   tierTargetInfo: TierTarget;
+  allowReadThroughText: string;
+  allowReadThrough: boolean = false;
 
   constructor(
     public actionLabels: ActionLabelsI18n,
@@ -66,21 +77,15 @@ export class RgwStorageClassFormComponent extends CdForm implements OnInit {
   }
 
   ngOnInit() {
-    this.multipartMinPartText =
-      'It specifies that objects this size or larger are transitioned to the cloud using multipart upload.';
-    this.multipartSyncThreholdText =
-      'It specifies the minimum part size to use when transitioning objects using multipart upload.';
-    this.targetPathText =
-      'Target Path refers to the storage location (e.g., bucket or container) in the cloud where data will be stored.';
-    this.targetRegionText = 'The region of the remote cloud service where storage is located.';
-    this.targetEndpointText = 'The URL endpoint of the remote cloud service for accessing storage.';
-    this.targetAccessKeyText =
-      "To view or copy your access 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.targetSecretKeyText =
-      "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: false).';
+    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.createForm();
     this.loadingReady();
     this.loadZoneGroup();
@@ -118,8 +123,14 @@ export class RgwStorageClassFormComponent extends CdForm implements OnInit {
           this.storageClassForm
             .get('multipart_min_part_size')
             .setValue(response.multipart_min_part_size || '');
+          this.storageClassForm
+            .get('allow_read_through')
+            .setValue(this.tierTargetInfo?.val?.allow_read_through || false);
         });
     }
+    this.storageClassForm.get('allow_read_through').valueChanges.subscribe((value) => {
+      this.onAllowReadThroughChange(value);
+    });
   }
 
   createForm() {
@@ -144,9 +155,10 @@ export class RgwStorageClassFormComponent extends CdForm implements OnInit {
       target_path: new FormControl('', {
         validators: [Validators.required]
       }),
-      retain_head_object: new FormControl(false),
+      retain_head_object: new FormControl(true),
       multipart_sync_threshold: new FormControl(33554432),
-      multipart_min_part_size: new FormControl(33554432)
+      multipart_min_part_size: new FormControl(33554432),
+      allow_read_through: new FormControl(false)
     });
   }
 
@@ -245,11 +257,22 @@ export class RgwStorageClassFormComponent extends CdForm implements OnInit {
     return tierTarget;
   }
 
+  onAllowReadThroughChange(checked: boolean): void {
+    this.allowReadThrough = checked;
+    if (this.allowReadThrough) {
+      this.storageClassForm.get('retain_head_object')?.setValue(true);
+      this.storageClassForm.get('retain_head_object')?.disable();
+    } else {
+      this.storageClassForm.get('retain_head_object')?.enable();
+    }
+  }
+
   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 headObject = this.storageClassForm.get('retain_head_object').value;
     const requestModel: RequestModel = {
       zone_group: zoneGroup,
       placement_targets: [
@@ -263,7 +286,8 @@ export class RgwStorageClassFormComponent extends CdForm implements OnInit {
             access_key: rawFormValue.access_key,
             secret: rawFormValue.secret_key,
             target_path: rawFormValue.target_path,
-            retain_head_object: rawFormValue.retain_head_object,
+            retain_head_object: headObject,
+            allow_read_through: rawFormValue.allow_read_through,
             region: rawFormValue.region,
             multipart_sync_threshold: rawFormValue.multipart_sync_threshold,
             multipart_min_part_size: rawFormValue.multipart_min_part_size
index 33cd412c3666cd6f6c47d5a4038cd36a20cc7e12..9cb19b680dbce2cc503cb66e0c44efdd5a3fbe38 100644 (file)
@@ -28,6 +28,7 @@ export class BucketTieringUtils {
       placement_target: targetName,
       storage_class: tierTarget.val.storage_class,
       retain_head_object: tierTarget.val.retain_head_object,
+      allow_read_through: tierTarget.val.allow_read_through,
       ...tierTarget.val.s3
     };
   }
index ac65bfc424da3897ddf1aa7a560ad8cf42fb28f2..892e6458705bcf1c9bf962974645b13127484497 100644 (file)
@@ -47,6 +47,7 @@ describe('RgwStorageClassService', () => {
             secret: 'test56',
             target_path: 'tsest-dnyanee',
             retain_head_object: false,
+            allow_read_through: true,
             region: 'ams3d',
             multipart_sync_threshold: 33554432,
             multipart_min_part_size: 33554432