diff --git a/Documentation/CRDs/specification.md b/Documentation/CRDs/specification.md index dd6949d64..e0b015109 100644 --- a/Documentation/CRDs/specification.md +++ b/Documentation/CRDs/specification.md @@ -10959,6 +10959,17 @@ string +default
+ +bool + + + +

Sets given placement as default. Only one placement in the list can be marked as default.

+ + + + metadataPoolName
string diff --git a/Documentation/Storage-Configuration/Object-Storage-RGW/object-storage.md b/Documentation/Storage-Configuration/Object-Storage-RGW/object-storage.md index 3ada73c7e..10123bef9 100644 --- a/Documentation/Storage-Configuration/Object-Storage-RGW/object-storage.md +++ b/Documentation/Storage-Configuration/Object-Storage-RGW/object-storage.md @@ -201,14 +201,15 @@ Object Storage API allows users to override where bucket data will be stored dur To enable this feature, configure `poolPlacements` representing a list of possible bucket data locations. Each `poolPlacement` must have: -* a **unique** `name` to refer to it in `` or `X-Storage-Policy`. A placement with reserved name `default` will be used by default if no location constraint is provided. +* a **unique** `name` to refer to it in `` or `X-Storage-Policy`. Name `default-placement` is reserved and can be used **only** if placement also marked as `default`. +* **optional** `default` flag to use given placement by default, meaning that it will be used if no location constraint is provided. Only one placement in the list can be marked as default. * `dataPoolName` and `metadataPoolName` representing object data and metadata locations. In Rook, these data locations are backed by `CephBlockPool`. `poolPlacements` and `storageClasses` specs refer pools by name. This means that all pools should be defined in advance. Similarly to [sharedPools](#create-local-object-stores-with-shared-pools), the same pool can be reused across multiple ObjectStores and/or poolPlacements/storageClasses because of RADOS namespaces. Here, each pool will be namespaced with `..` key. * **optional** `dataNonECPoolName` - extra pool for data that cannot use erasure coding (ex: multi-part uploads). If not set, `metadataPoolName` will be used. * **optional** list of placement `storageClasses`. Classes defined per placement, which means that even classes of `default` placement will be available only within this placement and not others. Each placement will automatically have default storage class named `STANDARD`. `STANDARD` class always points to placement `dataPoolName` and cannot be removed or redefined. Each storage class must have: * `name` (unique within placement). RGW allows arbitrary name for StorageClasses, however some clients/libs insist on AWS names so it is recommended to use one of the valid `x-amz-storage-class` values for better compatibility: `STANDARD | REDUCED_REDUNDANCY | STANDARD_IA | ONEZONE_IA | INTELLIGENT_TIERING | GLACIER | DEEP_ARCHIVE | OUTPOSTS | GLACIER_IR | SNOW | EXPRESS_ONEZONE`. See [AWS docs](https://aws.amazon.com/s3/storage-classes/). * `dataPoolName` - overrides placement data pool when this class is selected by user. -Example: Configure `CephObjectStore` with `default` placement pointing to `us` pools and placement `europe` pointing to pools in corresponding geographies. These geographical locations are only an example. Placement name can be arbitrary and could reflect the backing pool's replication factor, device class, or failure domain. This example also defines storage class `REDUCED_REDUNDANCY` for each placement. +Example: Configure `CephObjectStore` with `default` placement `us` pools and placement `europe` pointing to pools in corresponding geographies. These geographical locations are only an example. Placement name can be arbitrary and could reflect the backing pool's replication factor, device class, or failure domain. This example also defines storage class `REDUCED_REDUNDANCY` for each placement. ```yaml apiVersion: ceph.rook.io/v1 @@ -222,7 +223,8 @@ spec: instances: 1 sharedPools: poolPlacements: - - name: default + - name: us + default: true metadataPoolName: "us-data-pool" dataPoolName: "us-meta-pool" storageClasses: diff --git a/deploy/charts/rook-ceph/templates/resources.yaml b/deploy/charts/rook-ceph/templates/resources.yaml index 12c2d1ffb..77ff73089 100644 --- a/deploy/charts/rook-ceph/templates/resources.yaml +++ b/deploy/charts/rook-ceph/templates/resources.yaml @@ -12383,6 +12383,9 @@ spec: description: The data pool used to store ObjectStore objects data. minLength: 1 type: string + default: + description: Sets given placement as default. Only one placement in the list can be marked as default. + type: boolean metadataPoolName: description: The metadata pool used to store ObjectStore bucket index. minLength: 1 @@ -12420,6 +12423,7 @@ spec: type: array required: - dataPoolName + - default - metadataPoolName - name type: object @@ -13305,6 +13309,9 @@ spec: description: The data pool used to store ObjectStore objects data. minLength: 1 type: string + default: + description: Sets given placement as default. Only one placement in the list can be marked as default. + type: boolean metadataPoolName: description: The metadata pool used to store ObjectStore bucket index. minLength: 1 @@ -13342,6 +13349,7 @@ spec: type: array required: - dataPoolName + - default - metadataPoolName - name type: object diff --git a/deploy/examples/crds.yaml b/deploy/examples/crds.yaml index 1ffc720b8..b10029784 100644 --- a/deploy/examples/crds.yaml +++ b/deploy/examples/crds.yaml @@ -12374,6 +12374,9 @@ spec: description: The data pool used to store ObjectStore objects data. minLength: 1 type: string + default: + description: Sets given placement as default. Only one placement in the list can be marked as default. + type: boolean metadataPoolName: description: The metadata pool used to store ObjectStore bucket index. minLength: 1 @@ -12411,6 +12414,7 @@ spec: type: array required: - dataPoolName + - default - metadataPoolName - name type: object @@ -13293,6 +13297,9 @@ spec: description: The data pool used to store ObjectStore objects data. minLength: 1 type: string + default: + description: Sets given placement as default. Only one placement in the list can be marked as default. + type: boolean metadataPoolName: description: The metadata pool used to store ObjectStore bucket index. minLength: 1 @@ -13330,6 +13337,7 @@ spec: type: array required: - dataPoolName + - default - metadataPoolName - name type: object diff --git a/pkg/apis/ceph.rook.io/v1/types.go b/pkg/apis/ceph.rook.io/v1/types.go index 61812212b..633b9153c 100755 --- a/pkg/apis/ceph.rook.io/v1/types.go +++ b/pkg/apis/ceph.rook.io/v1/types.go @@ -1563,6 +1563,9 @@ type PoolPlacementSpec struct { // +kubebuilder:validation:Pattern=`^[a-zA-Z0-9._/-]+$` Name string `json:"name"` + // Sets given placement as default. Only one placement in the list can be marked as default. + Default bool `json:"default"` + // The metadata pool used to store ObjectStore bucket index. // +kubebuilder:validation:Required // +kubebuilder:validation:MinLength=1 diff --git a/pkg/operator/ceph/object/objectstore.go b/pkg/operator/ceph/object/objectstore.go index 63f28dbed..e2e79304a 100644 --- a/pkg/operator/ceph/object/objectstore.go +++ b/pkg/operator/ceph/object/objectstore.go @@ -800,7 +800,8 @@ func ConfigureSharedPoolsForZone(objContext *Context, sharedPools cephv1.ObjectS if err != nil { return err } - zoneGroupUpdated, err := adjustZoneGroupPlacementTargets(zoneGroupConfig, zoneUpdated) + defaultPlacement := getDefaultPlacementName(sharedPools) + zoneGroupUpdated, err := adjustZoneGroupPlacementTargets(zoneGroupConfig, zoneUpdated, defaultPlacement) if err != nil { return err } @@ -1401,10 +1402,10 @@ func ValidateObjectStorePoolsConfig(metadataPool, dataPool cephv1.PoolSpec, shar return err } if !EmptyPool(dataPool) && sharedPools.DataPoolName != "" { - return fmt.Errorf("invalidObjStorePoolCofig: object store dataPool and sharedPools.dataPool=%s are mutually exclusive. Only one of them can be set.", sharedPools.DataPoolName) + return fmt.Errorf("invalidObjStorePoolConfig: object store dataPool and sharedPools.dataPool=%s are mutually exclusive. Only one of them can be set.", sharedPools.DataPoolName) } if !EmptyPool(metadataPool) && sharedPools.MetadataPoolName != "" { - return fmt.Errorf("invalidObjStorePoolCofig: object store metadataPool and sharedPools.metadataPool=%s are mutually exclusive. Only one of them can be set.", sharedPools.MetadataPoolName) + return fmt.Errorf("invalidObjStorePoolConfig: object store metadataPool and sharedPools.metadataPool=%s are mutually exclusive. Only one of them can be set.", sharedPools.MetadataPoolName) } return nil } diff --git a/pkg/operator/ceph/object/objectstore_test.go b/pkg/operator/ceph/object/objectstore_test.go index 4662349a5..ee5aa4c83 100644 --- a/pkg/operator/ceph/object/objectstore_test.go +++ b/pkg/operator/ceph/object/objectstore_test.go @@ -152,7 +152,7 @@ const ( "notif_pool": "%[1]s:store-a.log.notif" }` - objectZonegroupJson = `{ + objectZonegroupJsonTempl = `{ "id": "610c9e3d-19e7-40b0-9f88-03319c4bc65a", "name": "store-a", "api_name": "test", @@ -174,14 +174,14 @@ const ( ], "placement_targets": [ { - "name": "default-placement", + "name": "%[1]s", "tags": [], "storage_classes": [ "STANDARD" ] } ], - "default_placement": "default-placement", + "default_placement": "%[1]s", "realm_id": "29e28253-be54-4581-90dd-206020d2fcdd", "sync_policy": { "groups": [] @@ -229,6 +229,7 @@ func TestReconcileRealm(t *testing.T) { func TestConfigureStoreWithSharedPools(t *testing.T) { sharedMetaPoolAlreadySet, sharedDataPoolAlreadySet := "", "" + defaultPlacement := defaultPlacementCephConfigName zoneGetCalled := false zoneSetCalled := false zoneGroupGetCalled := false @@ -273,7 +274,7 @@ func TestConfigureStoreWithSharedPools(t *testing.T) { } else if args[0] == "zonegroup" { if args[1] == "get" { zoneGroupGetCalled = true - return objectZonegroupJson, nil + return fmt.Sprintf(objectZonegroupJsonTempl, defaultPlacement), nil } else if args[1] == "set" { zoneGroupSetCalled = true for _, arg := range args { @@ -287,7 +288,7 @@ func TestConfigureStoreWithSharedPools(t *testing.T) { } return string(inBytes), nil } - return objectZonegroupJson, nil + return fmt.Sprintf(objectZonegroupJsonTempl, defaultPlacement), nil } } return "", errors.Errorf("unexpected ceph command %q", args) @@ -334,12 +335,14 @@ func TestConfigureStoreWithSharedPools(t *testing.T) { sharedPools := cephv1.ObjectSharedPoolsSpec{ PoolPlacements: []cephv1.PoolPlacementSpec{ { - Name: defaultPlacementName, + Name: "default", + Default: true, MetadataPoolName: "test-meta", DataPoolName: "test-data", }, }, } + defaultPlacement = "default" err := ConfigureSharedPoolsForZone(context, sharedPools) assert.NoError(t, err) assert.True(t, zoneGetCalled) @@ -354,6 +357,7 @@ func TestConfigureStoreWithSharedPools(t *testing.T) { MetadataPoolName: "test-meta", DataPoolName: "test-data", } + defaultPlacement = defaultPlacementCephConfigName sharedMetaPoolAlreadySet, sharedDataPoolAlreadySet = "test-meta", "test-data" zoneGetCalled = false zoneSetCalled = false @@ -370,7 +374,8 @@ func TestConfigureStoreWithSharedPools(t *testing.T) { sharedPools := cephv1.ObjectSharedPoolsSpec{ PoolPlacements: []cephv1.PoolPlacementSpec{ { - Name: defaultPlacementName, + Name: "default", + Default: true, MetadataPoolName: "test-meta", DataPoolName: "test-data", }, diff --git a/pkg/operator/ceph/object/shared_pools.go b/pkg/operator/ceph/object/shared_pools.go index 7627a08a8..b447cdf46 100644 --- a/pkg/operator/ceph/object/shared_pools.go +++ b/pkg/operator/ceph/object/shared_pools.go @@ -13,14 +13,13 @@ import ( ) const ( - defaultPlacementName = "default" defaultPlacementCephConfigName = "default-placement" defaultPlacementStorageClass = "STANDARD" ) func IsNeedToCreateObjectStorePools(sharedPools cephv1.ObjectSharedPoolsSpec) bool { for _, pp := range sharedPools.PoolPlacements { - if pp.Name == defaultPlacementName { + if pp.Default { // No need to create pools. External pools from default placement will be used return false } @@ -33,14 +32,25 @@ func IsNeedToCreateObjectStorePools(sharedPools cephv1.ObjectSharedPoolsSpec) bo } func validatePoolPlacements(placements []cephv1.PoolPlacementSpec) error { + hasDefault := false names := make(map[string]struct{}, len(placements)) for _, p := range placements { - if _, ok := names[p.Name]; ok { - return fmt.Errorf("invalidObjStorePoolCofig: invalid placement %s: placement names must be unique", p.Name) + if hasDefault && p.Default { + return fmt.Errorf("invalidObjStorePoolConfig: only one placement can be set as default") } + hasDefault = hasDefault || p.Default + + if _, ok := names[p.Name]; ok { + return fmt.Errorf("invalidObjStorePoolConfig: invalid placement %s: placement names must be unique", p.Name) + } + + if p.Name == defaultPlacementCephConfigName && !p.Default { + return fmt.Errorf("invalidObjStorePoolConfig: placement with name %s must be marked as default", defaultPlacementCephConfigName) + } + names[p.Name] = struct{}{} if err := validatePoolPlacementStorageClasses(p.StorageClasses); err != nil { - return fmt.Errorf("invalidObjStorePoolCofig: invalid placement %s: %w", p.Name, err) + return fmt.Errorf("invalidObjStorePoolConfig: invalid placement %s: %w", p.Name, err) } } return nil @@ -79,6 +89,7 @@ func adjustZonePlacementPools(zone map[string]interface{}, spec cephv1.ObjectSha fromSpec := toZonePlacementPools(spec, name) + defaultPlacementName := getDefaultPlacementName(spec) inConfig := map[string]struct{}{} idxToRemove := map[int]struct{}{} for i, p := range placements { @@ -91,7 +102,7 @@ func adjustZonePlacementPools(zone map[string]interface{}, spec cephv1.ObjectSha return nil, fmt.Errorf("unable to get pool placement name for zone %s: %w", name, err) } // check if placement should be removed - if _, inSpec := fromSpec[placementID]; !inSpec && placementID != defaultPlacementCephConfigName { + if _, inSpec := fromSpec[placementID]; !inSpec && placementID != defaultPlacementName { // remove placement if it is not in spec, but don't remove default placement idxToRemove[i] = struct{}{} continue @@ -151,9 +162,18 @@ func adjustZonePlacementPools(zone map[string]interface{}, spec cephv1.ObjectSha return zone, nil } +func getDefaultPlacementName(spec cephv1.ObjectSharedPoolsSpec) string { + for _, p := range spec.PoolPlacements { + if p.Default { + return p.Name + } + } + return defaultPlacementCephConfigName +} + func getDefaultMetadataPool(spec cephv1.ObjectSharedPoolsSpec) string { for _, p := range spec.PoolPlacements { - if p.Name == defaultPlacementName { + if p.Default { return p.MetadataPoolName } } @@ -165,12 +185,10 @@ func toZonePlacementPools(spec cephv1.ObjectSharedPoolsSpec, ns string) map[stri hasDefault := false res := make(map[string]ZonePlacementPool, len(spec.PoolPlacements)+1) for _, pp := range spec.PoolPlacements { - name := pp.Name - if pp.Name == defaultPlacementName { + if pp.Default { hasDefault = true - name = defaultPlacementCephConfigName } - res[name] = toZonePlacementPool(pp, ns) + res[pp.Name] = toZonePlacementPool(pp, ns) } if !hasDefault && spec.DataPoolName != "" && spec.MetadataPoolName != "" { // set shared pools as default if no default placement was provided @@ -197,10 +215,7 @@ func toZonePlacementPools(spec cephv1.ObjectSharedPoolsSpec, ns string) map[stri } func toZonePlacementPool(spec cephv1.PoolPlacementSpec, ns string) ZonePlacementPool { - placementNS := ns - if spec.Name != defaultPlacementName { - placementNS += "." + spec.Name - } + placementNS := ns + "." + spec.Name // The extra pool is for omap data for multi-part uploads, so we use // the metadata pool instead of the data pool. nonECPool := spec.MetadataPoolName + ":" + placementNS + ".data.non-ec" @@ -218,15 +233,12 @@ func toZonePlacementPool(spec cephv1.PoolPlacementSpec, ns string) ZonePlacement DataPool: spec.DataPoolName + ":" + placementNS + ".data", }, }, - // Workaround: radosgw-admin set zone json command sets incorrect default value for placement inline_data field. + // Workaround: 'radosgw-admin set zone json' command sets incorrect default value for placement inline_data field. // So we should set default value (true) explicitly. // See: https://tracker.ceph.com/issues/67933 InlineData: true, }, } - if res.Key == defaultPlacementName { - res.Key = defaultPlacementCephConfigName - } for _, v := range spec.StorageClasses { res.Val.StorageClasses[v.Name] = ZonePlacementStorageClass{ DataPool: v.DataPoolName + ":" + ns + "." + v.Name, @@ -235,7 +247,7 @@ func toZonePlacementPool(spec cephv1.PoolPlacementSpec, ns string) ZonePlacement return res } -func adjustZoneGroupPlacementTargets(group, zone map[string]interface{}) (map[string]interface{}, error) { +func adjustZoneGroupPlacementTargets(group, zone map[string]interface{}, defaultPlacement string) (map[string]interface{}, error) { name, err := getObjProperty[string](group, "name") if err != nil { return nil, fmt.Errorf("unable to get zonegroup name: %w", err) @@ -247,7 +259,7 @@ func adjustZoneGroupPlacementTargets(group, zone map[string]interface{}) (map[st return nil, fmt.Errorf("unable to deep copy config for zonegroup %s: %w", name, err) } - _, err = updateObjProperty(group, defaultPlacementCephConfigName, "default_placement") + _, err = updateObjProperty(group, defaultPlacement, "default_placement") if err != nil { return nil, fmt.Errorf("unable to set default_placement for zonegroup %s: %w", name, err) } diff --git a/pkg/operator/ceph/object/shared_pools_test.go b/pkg/operator/ceph/object/shared_pools_test.go index 4ef29275f..f40306141 100644 --- a/pkg/operator/ceph/object/shared_pools_test.go +++ b/pkg/operator/ceph/object/shared_pools_test.go @@ -24,6 +24,7 @@ func Test_validatePoolPlacements(t *testing.T) { placements: []cephv1.PoolPlacementSpec{ { Name: "name1", + Default: true, MetadataPoolName: "", // handled by CRD validation DataPoolName: "", // handled by CRD validation DataNonECPoolName: "", // handled by CRD validation @@ -31,6 +32,7 @@ func Test_validatePoolPlacements(t *testing.T) { }, { Name: "name2", + Default: false, MetadataPoolName: "", // handled by CRD validation DataPoolName: "", // handled by CRD validation DataNonECPoolName: "", // handled by CRD validation @@ -62,6 +64,78 @@ func Test_validatePoolPlacements(t *testing.T) { }, wantErr: true, }, + { + name: "invalid: more than one default placement", + args: args{ + placements: []cephv1.PoolPlacementSpec{ + { + Name: "one", + Default: true, + MetadataPoolName: "", // handled by CRD validation + DataPoolName: "", // handled by CRD validation + DataNonECPoolName: "", // handled by CRD validation + StorageClasses: []cephv1.PlacementStorageClassSpec{}, + }, + { + Name: "two", + Default: true, + MetadataPoolName: "", // handled by CRD validation + DataPoolName: "", // handled by CRD validation + DataNonECPoolName: "", // handled by CRD validation + StorageClasses: []cephv1.PlacementStorageClassSpec{}, + }, + }, + }, + wantErr: true, + }, + { + name: "invalid: non-default placement with reserved name", + args: args{ + placements: []cephv1.PoolPlacementSpec{ + { + Name: defaultPlacementCephConfigName, + Default: false, + MetadataPoolName: "", // handled by CRD validation + DataPoolName: "", // handled by CRD validation + DataNonECPoolName: "", // handled by CRD validation + StorageClasses: []cephv1.PlacementStorageClassSpec{}, + }, + { + Name: "two", + Default: true, + MetadataPoolName: "", // handled by CRD validation + DataPoolName: "", // handled by CRD validation + DataNonECPoolName: "", // handled by CRD validation + StorageClasses: []cephv1.PlacementStorageClassSpec{}, + }, + }, + }, + wantErr: true, + }, + { + name: "valid: default placement with reserved name", + args: args{ + placements: []cephv1.PoolPlacementSpec{ + { + Name: defaultPlacementCephConfigName, + Default: true, + MetadataPoolName: "", // handled by CRD validation + DataPoolName: "", // handled by CRD validation + DataNonECPoolName: "", // handled by CRD validation + StorageClasses: []cephv1.PlacementStorageClassSpec{}, + }, + { + Name: "two", + Default: false, + MetadataPoolName: "", // handled by CRD validation + DataPoolName: "", // handled by CRD validation + DataNonECPoolName: "", // handled by CRD validation + StorageClasses: []cephv1.PlacementStorageClassSpec{}, + }, + }, + }, + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -165,7 +239,8 @@ func TestIsNeedToCreateObjectStorePools(t *testing.T) { PreserveRadosNamespaceDataOnDelete: false, PoolPlacements: []cephv1.PoolPlacementSpec{ { - Name: "default", + Name: "name", + Default: true, MetadataPoolName: "", // handled by CRD validation DataPoolName: "", // handled by CRD validation DataNonECPoolName: "", // handled by CRD validation @@ -221,7 +296,8 @@ func TestIsNeedToCreateObjectStorePools(t *testing.T) { PreserveRadosNamespaceDataOnDelete: false, PoolPlacements: []cephv1.PoolPlacementSpec{ { - Name: "fast", + Name: "default", + Default: false, MetadataPoolName: "", // handled by CRD validation DataPoolName: "", // handled by CRD validation DataNonECPoolName: "", // handled by CRD validation @@ -267,7 +343,8 @@ func Test_getDefaultMetadataPool(t *testing.T) { StorageClasses: []cephv1.PlacementStorageClassSpec{}, }, { - Name: defaultPlacementName, + Name: "some_name_2", + Default: true, MetadataPoolName: "meta2", DataPoolName: "data2", DataNonECPoolName: "data-non-ec2", @@ -294,7 +371,8 @@ func Test_getDefaultMetadataPool(t *testing.T) { StorageClasses: []cephv1.PlacementStorageClassSpec{}, }, { - Name: defaultPlacementName, + Name: "some_name_2", + Default: true, MetadataPoolName: "meta2", DataPoolName: "data2", DataNonECPoolName: "data-non-ec2", @@ -314,7 +392,8 @@ func Test_getDefaultMetadataPool(t *testing.T) { PreserveRadosNamespaceDataOnDelete: false, PoolPlacements: []cephv1.PoolPlacementSpec{ { - Name: "some_name", + Name: "default", + Default: false, MetadataPoolName: "meta1", DataPoolName: "data1", DataNonECPoolName: "data-non-ec1", @@ -334,7 +413,8 @@ func Test_getDefaultMetadataPool(t *testing.T) { PreserveRadosNamespaceDataOnDelete: false, PoolPlacements: []cephv1.PoolPlacementSpec{ { - Name: "some_name", + Name: "default", + Default: false, MetadataPoolName: "meta1", DataPoolName: "data1", DataNonECPoolName: "data-non-ec1", @@ -369,7 +449,8 @@ func Test_toZonePlacementPool(t *testing.T) { name: "map default placement without non-ec to config", args: args{ spec: cephv1.PoolPlacementSpec{ - Name: defaultPlacementName, + Name: defaultPlacementCephConfigName, + Default: true, MetadataPoolName: "meta", DataPoolName: "data", DataNonECPoolName: "", @@ -385,11 +466,11 @@ func Test_toZonePlacementPool(t *testing.T) { want: ZonePlacementPool{ Key: defaultPlacementCephConfigName, Val: ZonePlacementPoolVal{ - DataExtraPool: "meta:ns.data.non-ec", - IndexPool: "meta:ns.index", + DataExtraPool: "meta:ns.default-placement.data.non-ec", + IndexPool: "meta:ns.default-placement.index", StorageClasses: map[string]ZonePlacementStorageClass{ defaultPlacementStorageClass: { - DataPool: "data:ns.data", + DataPool: "data:ns.default-placement.data", }, "REDUCED_REDUNDANCY": { DataPool: "reduced:ns.REDUCED_REDUNDANCY", @@ -403,7 +484,8 @@ func Test_toZonePlacementPool(t *testing.T) { name: "map default placement to config", args: args{ spec: cephv1.PoolPlacementSpec{ - Name: defaultPlacementName, + Name: "fast", + Default: true, MetadataPoolName: "meta", DataPoolName: "data", DataNonECPoolName: "repl", @@ -417,13 +499,13 @@ func Test_toZonePlacementPool(t *testing.T) { ns: "ns", }, want: ZonePlacementPool{ - Key: defaultPlacementCephConfigName, + Key: "fast", Val: ZonePlacementPoolVal{ - DataExtraPool: "repl:ns.data.non-ec", - IndexPool: "meta:ns.index", + DataExtraPool: "repl:ns.fast.data.non-ec", + IndexPool: "meta:ns.fast.index", StorageClasses: map[string]ZonePlacementStorageClass{ defaultPlacementStorageClass: { - DataPool: "data:ns.data", + DataPool: "data:ns.fast.data", }, "REDUCED_REDUNDANCY": { DataPool: "reduced:ns.REDUCED_REDUNDANCY", @@ -433,37 +515,12 @@ func Test_toZonePlacementPool(t *testing.T) { }, }, }, - { - name: "map default placement without extra SC to config", - args: args{ - spec: cephv1.PoolPlacementSpec{ - Name: defaultPlacementName, - MetadataPoolName: "meta", - DataPoolName: "data", - DataNonECPoolName: "repl", - StorageClasses: []cephv1.PlacementStorageClassSpec{}, - }, - ns: "ns", - }, - want: ZonePlacementPool{ - Key: defaultPlacementCephConfigName, - Val: ZonePlacementPoolVal{ - DataExtraPool: "repl:ns.data.non-ec", - IndexPool: "meta:ns.index", - StorageClasses: map[string]ZonePlacementStorageClass{ - defaultPlacementStorageClass: { - DataPool: "data:ns.data", - }, - }, - InlineData: true, - }, - }, - }, { name: "map non-default placement without non-ec to config", args: args{ spec: cephv1.PoolPlacementSpec{ Name: "placement", + Default: false, MetadataPoolName: "meta", DataPoolName: "data", DataNonECPoolName: "", @@ -498,6 +555,7 @@ func Test_toZonePlacementPool(t *testing.T) { args: args{ spec: cephv1.PoolPlacementSpec{ Name: "placement", + Default: false, MetadataPoolName: "meta", DataPoolName: "data", DataNonECPoolName: "repl", @@ -580,7 +638,8 @@ func Test_toZonePlacementPools(t *testing.T) { PreserveRadosNamespaceDataOnDelete: false, PoolPlacements: []cephv1.PoolPlacementSpec{ { - Name: defaultPlacementName, + Name: "some_name", + Default: true, MetadataPoolName: "meta1", DataPoolName: "data1", DataNonECPoolName: "data-non-ec", @@ -596,14 +655,14 @@ func Test_toZonePlacementPools(t *testing.T) { ns: "rgw-instance", }, want: map[string]ZonePlacementPool{ - defaultPlacementCephConfigName: { - Key: defaultPlacementCephConfigName, + "some_name": { + Key: "some_name", Val: ZonePlacementPoolVal{ - DataExtraPool: "data-non-ec:rgw-instance.data.non-ec", - IndexPool: "meta1:rgw-instance.index", + DataExtraPool: "data-non-ec:rgw-instance.some_name.data.non-ec", + IndexPool: "meta1:rgw-instance.some_name.index", StorageClasses: map[string]ZonePlacementStorageClass{ defaultPlacementStorageClass: { - DataPool: "data1:rgw-instance.data", + DataPool: "data1:rgw-instance.some_name.data", }, "REDUCED_REDUNDANCY": { DataPool: "reduced:rgw-instance.REDUCED_REDUNDANCY", @@ -962,7 +1021,8 @@ func Test_adjustZoneDefaultPools(t *testing.T) { PreserveRadosNamespaceDataOnDelete: false, PoolPlacements: []cephv1.PoolPlacementSpec{ { - Name: defaultPlacementName, + Name: "some_name", + Default: true, MetadataPoolName: "meta-pool", DataPoolName: "data-pool", }, @@ -1418,8 +1478,9 @@ func Test_adjustZonePlacementPools(t *testing.T) { func Test_adjustZoneGroupPlacementTargets(t *testing.T) { type args struct { - zone string - groupBefore string + zone string + groupBefore string + defaultPlacement string } tests := []struct { name string @@ -1431,6 +1492,7 @@ func Test_adjustZoneGroupPlacementTargets(t *testing.T) { { name: "nothing changed", args: args{ + defaultPlacement: defaultPlacementCephConfigName, groupBefore: `{ "id": "610c9e3d-19e7-40b0-9f88-03319c4bc65a", "name": "test", @@ -1492,6 +1554,7 @@ func Test_adjustZoneGroupPlacementTargets(t *testing.T) { { name: "default changed", args: args{ + defaultPlacement: defaultPlacementCephConfigName, groupBefore: `{ "id": "610c9e3d-19e7-40b0-9f88-03319c4bc65a", "name": "test", @@ -1553,6 +1616,7 @@ func Test_adjustZoneGroupPlacementTargets(t *testing.T) { { name: "storage class added", args: args{ + defaultPlacement: defaultPlacementCephConfigName, groupBefore: `{ "id": "610c9e3d-19e7-40b0-9f88-03319c4bc65a", "name": "test", @@ -1617,6 +1681,7 @@ func Test_adjustZoneGroupPlacementTargets(t *testing.T) { { name: "placement added", args: args{ + defaultPlacement: defaultPlacementCephConfigName, groupBefore: `{ "id": "610c9e3d-19e7-40b0-9f88-03319c4bc65a", "name": "test", @@ -1697,6 +1762,7 @@ func Test_adjustZoneGroupPlacementTargets(t *testing.T) { { name: "placement and sc removed", args: args{ + defaultPlacement: defaultPlacementCephConfigName, groupBefore: `{ "id": "610c9e3d-19e7-40b0-9f88-03319c4bc65a", "name": "test", @@ -1769,7 +1835,7 @@ func Test_adjustZoneGroupPlacementTargets(t *testing.T) { srcGroup := map[string]interface{}{} err = json.Unmarshal([]byte(tt.args.groupBefore), &srcGroup) assert.NoError(t, err) - changedGroup, err := adjustZoneGroupPlacementTargets(srcGroup, zj) + changedGroup, err := adjustZoneGroupPlacementTargets(srcGroup, zj, tt.args.defaultPlacement) orig := map[string]interface{}{} jErr := json.Unmarshal([]byte(tt.args.groupBefore), &orig) @@ -1869,3 +1935,76 @@ func Test_createPlacementTargetsFromZonePoolPlacements(t *testing.T) { }) } } + +func Test_getDefaultPlacementName(t *testing.T) { + type args struct { + spec cephv1.ObjectSharedPoolsSpec + } + tests := []struct { + name string + args args + want string + }{ + { + name: "no default placement set in spec", + args: args{ + spec: cephv1.ObjectSharedPoolsSpec{ + PoolPlacements: []cephv1.PoolPlacementSpec{ + { + Name: "one", + Default: false, + }, + { + Name: "two", + Default: false, + }, + }, + }, + }, + want: defaultPlacementCephConfigName, + }, + { + name: "first placement set as defaultu in spec", + args: args{ + spec: cephv1.ObjectSharedPoolsSpec{ + PoolPlacements: []cephv1.PoolPlacementSpec{ + { + Name: "one", + Default: true, + }, + { + Name: "two", + Default: false, + }, + }, + }, + }, + want: "one", + }, + { + name: "second placement set as default in spec", + args: args{ + spec: cephv1.ObjectSharedPoolsSpec{ + PoolPlacements: []cephv1.PoolPlacementSpec{ + { + Name: "one", + Default: false, + }, + { + Name: "two", + Default: true, + }, + }, + }, + }, + want: "two", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := getDefaultPlacementName(tt.args.spec); got != tt.want { + t.Errorf("getDefaultPlacementName() = %v, want %v", got, tt.want) + } + }) + } +}