From: Dnyaneshwari Date: Thu, 24 Apr 2025 06:51:00 +0000 (+0530) Subject: mgr/dashboard: Tieiring - allow read through X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=f9b84fae92e6a0174e4161b9e89f10a2c4b9b4fa;p=ceph.git mgr/dashboard: Tieiring - allow read through Fixes: https://tracker.ceph.com/issues/71053 Signed-off-by: Dnyaneshwari Talwekar --- diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-storage-class.model.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-storage-class.model.ts index 6e7b1d745d119..3f5523dcb96df 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-storage-class.model.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-storage-class.model.ts @@ -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`; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-details/rgw-storage-class-details.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-details/rgw-storage-class-details.component.html index 378c0b6372a8b..998867d3b6f29 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-details/rgw-storage-class-details.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-details/rgw-storage-class-details.component.html @@ -12,8 +12,7 @@ Target Path - The target path specifies a prefix to which the source bucket-name/object-name is - appended. + {{ targetPathText }} @@ -24,7 +23,7 @@ Access key - Access key is the remote cloud S3 access key used for a specific connection. + {{ targetAccessKeyText }} @@ -50,7 +49,7 @@ Secret key - Secret is the secret key for the remote cloud S3 service. + {{ targetSecretKeyText }} @@ -72,48 +71,54 @@ - + Host Style - The URL format for accessing the remote S3 endpoint: 'Path' for a path-based URL - or 'Virtual' for a domain-based URL. + {{ hostStyleText }} {{ selection?.host_style }} - + Multipart Minimum Part Size - Minimum parts size to use when transitioning objects using multipart upload. + {{ multipartMinPartText }} {{ selection?.multipart_min_part_size }} - + Multipart Sync Threshold - Objects this size or larger will be transitioned to the cloud using multipart - upload. + {{ multipartSyncThreholdText }} {{ selection?.multipart_sync_threshold }} - - Retain Head Object + + Allow Read Through + + + {{ allowReadThroughText }} + + + + {{ selection?.allow_read_through }} + + + + Head Object (Stub File) - Retain object metadata after transition to the cloud (default: false). + {{ retainHeadObjectText }} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-details/rgw-storage-class-details.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-details/rgw-storage-class-details.component.spec.ts index ca7e9fcaac796..8b9f4bbe64228 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-details/rgw-storage-class-details.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-details/rgw-storage-class-details.component.spec.ts @@ -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(); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-details/rgw-storage-class-details.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-details/rgw-storage-class-details.component.ts index aeb72b0bdc213..14b01a140428e 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-details/rgw-storage-class-details.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-details/rgw-storage-class-details.component.ts @@ -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 }; } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-form/rgw-storage-class-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-form/rgw-storage-class-form.component.html index 3d930d5bdc2d7..ce50c68d11632 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-form/rgw-storage-class-form.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-form/rgw-storage-class-form.component.html @@ -125,9 +125,7 @@ placeholder="e.g, us-east-1" i18n-placeholder [invalid]=" - storageClassForm.showError('region', formDir, 'required') - " - /> + storageClassForm.showError('region', formDir, 'required')"/> +
+ Allow Read Through + {{ allowReadThroughText }} + +
+
+ Head Object (Stub File) + {{ retainHeadObjectText }} + +
-
- Retain Head Object - {{ retainHeadObjectText }} - -
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-form/rgw-storage-class-form.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-form/rgw-storage-class-form.component.spec.ts index 80f5fcf7a108d..5a8d5c3d4bb2a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-form/rgw-storage-class-form.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-form/rgw-storage-class-form.component.spec.ts @@ -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); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-form/rgw-storage-class-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-form/rgw-storage-class-form.component.ts index 643b690e57648..b2b8862109fc4 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-form/rgw-storage-class-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-storage-class-form/rgw-storage-class-form.component.ts @@ -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 diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/utils/rgw-bucket-tiering.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/utils/rgw-bucket-tiering.ts index 33cd412c3666c..9cb19b680dbce 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/utils/rgw-bucket-tiering.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/utils/rgw-bucket-tiering.ts @@ -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 }; } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-storage-class.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-storage-class.service.spec.ts index ac65bfc424da3..892e6458705bc 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-storage-class.service.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-storage-class.service.spec.ts @@ -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