Skip to content

Commit 1c91774

Browse files
authored
Merge pull request rook#14827 from jhoblitt/feature/parseAdditionalConfig
object: set obc user quota(s) in one SetUserQuota() call
2 parents be23171 + 9243764 commit 1c91774

File tree

3 files changed

+116
-77
lines changed

3 files changed

+116
-77
lines changed

pkg/operator/ceph/object/bucket/provisioner.go

Lines changed: 44 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ import (
2424
"github.com/aws/aws-sdk-go/aws/awserr"
2525
"github.com/ceph/go-ceph/rgw/admin"
2626
"github.com/coreos/pkg/capnslog"
27+
"github.com/google/go-cmp/cmp"
2728
bktv1alpha1 "github.com/kube-object-storage/lib-bucket-provisioner/pkg/apis/objectbucket.io/v1alpha1"
2829
apibkt "github.com/kube-object-storage/lib-bucket-provisioner/pkg/provisioner/api"
2930
opcontroller "github.com/rook/rook/pkg/operator/ceph/controller"
3031
"github.com/rook/rook/pkg/operator/ceph/object"
3132
storagev1 "k8s.io/api/storage/v1"
32-
"k8s.io/apimachinery/pkg/api/resource"
3333

3434
"github.com/pkg/errors"
3535
"github.com/rook/rook/pkg/clusterd"
@@ -56,6 +56,11 @@ type Provisioner struct {
5656
adminOpsClient *admin.API
5757
}
5858

59+
type additionalConfigSpec struct {
60+
maxObjects *int64
61+
maxSize *int64
62+
}
63+
5964
var _ apibkt.Provisioner = &Provisioner{}
6065

6166
func NewProvisioner(context *clusterd.Context, clusterInfo *client.ClusterInfo) *Provisioner {
@@ -128,7 +133,7 @@ func (p Provisioner) Provision(options *apibkt.BucketOptions) (*bktv1alpha1.Obje
128133

129134
err = p.setAdditionalSettings(options)
130135
if err != nil {
131-
return nil, errors.Wrapf(err, "failed to set additional settings for OBC %q", options.ObjectBucketClaim.Name)
136+
return nil, errors.Wrapf(err, "failed to set additional settings for OBC %q in NS %q associated with CephObjectStore %q in NS %q", options.ObjectBucketClaim.Name, options.ObjectBucketClaim.Namespace, p.objectStoreName, p.clusterInfo.Namespace)
132137
}
133138

134139
return p.composeObjectBucket(), nil
@@ -218,7 +223,7 @@ func (p Provisioner) Grant(options *apibkt.BucketOptions) (*bktv1alpha1.ObjectBu
218223
// setting quota limit if it is enabled
219224
err = p.setAdditionalSettings(options)
220225
if err != nil {
221-
return nil, err
226+
return nil, errors.Wrapf(err, "failed to set additional settings for OBC %q in NS %q associated with CephObjectStore %q in NS %q", options.ObjectBucketClaim.Name, options.ObjectBucketClaim.Namespace, p.objectStoreName, p.clusterInfo.Namespace)
222227
}
223228

224229
// returned ob with connection info
@@ -568,75 +573,61 @@ func (p *Provisioner) populateDomainAndPort(sc *storagev1.StorageClass) error {
568573

569574
// Check for additional options mentioned in OBC and set them accordingly
570575
func (p *Provisioner) setAdditionalSettings(options *apibkt.BucketOptions) error {
571-
var maxObjectsInt64 int64 = -1
572-
var maxSizeInt64 int64 = -1
573-
var err error
574-
var quotaEnabled bool
575-
576-
maxObjects := MaxObjectQuota(options.ObjectBucketClaim.Spec.AdditionalConfig)
577-
if maxObjects != "" {
578-
quotaEnabled = true
579-
580-
maxObjectsInt64, err = toInt64(maxObjects)
581-
if err != nil {
582-
return errors.Wrapf(err, "failed to parse maxObjects quota for user %q", p.cephUserName)
583-
}
584-
}
585-
586-
maxSize := MaxSizeQuota(options.ObjectBucketClaim.Spec.AdditionalConfig)
587-
if maxSize != "" {
588-
quotaEnabled = true
589-
590-
maxSizeInt64, err = toInt64(maxSize)
591-
if err != nil {
592-
return errors.Wrapf(err, "failed to parse maxSize quota for user %q", p.cephUserName)
593-
}
576+
additionalConfig, err := additionalConfigSpecFromMap(options.ObjectBucketClaim.Spec.AdditionalConfig)
577+
if err != nil {
578+
return errors.Wrap(err, "failed to process additionalConfig")
594579
}
595580

596-
objectUser, err := p.adminOpsClient.GetUser(p.clusterInfo.Context, admin.User{ID: p.cephUserName})
581+
liveQuota, err := p.adminOpsClient.GetUserQuota(p.clusterInfo.Context, admin.QuotaSpec{UID: p.cephUserName})
597582
if err != nil {
598583
return errors.Wrapf(err, "failed to fetch user %q", p.cephUserName)
599584
}
600585

601-
// enable or disable quota for user
602-
if *objectUser.UserQuota.Enabled != quotaEnabled {
603-
err = p.adminOpsClient.SetUserQuota(p.clusterInfo.Context, admin.QuotaSpec{UID: p.cephUserName, Enabled: &quotaEnabled})
604-
if err != nil {
605-
return errors.Wrapf(err, "failed to set user %q quota enabled=%v for obc", p.cephUserName, quotaEnabled)
606-
}
586+
// Copy only the fields that are actively managed by the provisioner to
587+
// prevent passing back undesirable combinations of fields. It is
588+
// known to be problematic to set both MaxSize and MaxSizeKB.
589+
currentQuota := admin.QuotaSpec{
590+
Enabled: liveQuota.Enabled,
591+
MaxObjects: liveQuota.MaxObjects,
592+
MaxSize: liveQuota.MaxSize,
607593
}
594+
targetQuota := currentQuota
608595

609-
if !quotaEnabled {
610-
// no need to process anything else if quotas are disabled
611-
return nil
596+
// enable or disable quota for user
597+
quotaEnabled := (additionalConfig.maxObjects != nil) || (additionalConfig.maxSize != nil)
598+
599+
targetQuota.Enabled = &quotaEnabled
600+
601+
if additionalConfig.maxObjects != nil {
602+
targetQuota.MaxObjects = additionalConfig.maxObjects
603+
} else if currentQuota.MaxObjects != nil && *currentQuota.MaxObjects >= 0 {
604+
// if the existing value is already negative, we don't want to change it
605+
var objects int64 = -1
606+
targetQuota.MaxObjects = &objects
612607
}
613608

614-
if *objectUser.UserQuota.MaxObjects != maxObjectsInt64 {
615-
err = p.adminOpsClient.SetUserQuota(p.clusterInfo.Context, admin.QuotaSpec{UID: p.cephUserName, MaxObjects: &maxObjectsInt64})
616-
if err != nil {
617-
return errors.Wrapf(err, "failed to set MaxObjects=%v to user %q", maxObjectsInt64, p.cephUserName)
618-
}
609+
if additionalConfig.maxSize != nil {
610+
targetQuota.MaxSize = additionalConfig.maxSize
611+
} else if currentQuota.MaxSize != nil && *currentQuota.MaxSize >= 0 {
612+
// if the existing value is already negative, we don't want to change it
613+
var size int64 = -1
614+
targetQuota.MaxSize = &size
619615
}
620616

621-
if objectUser.UserQuota.MaxSize != &maxSizeInt64 {
622-
err = p.adminOpsClient.SetUserQuota(p.clusterInfo.Context, admin.QuotaSpec{UID: p.cephUserName, MaxSize: &maxSizeInt64})
617+
diff := cmp.Diff(currentQuota, targetQuota)
618+
if diff != "" {
619+
logger.Debugf("Quota for user %q has changed. diff:%s", p.cephUserName, diff)
620+
// UID is not set in the QuotaSpec returned by GetUser()/GetUserQuota()
621+
targetQuota.UID = p.cephUserName
622+
err = p.adminOpsClient.SetUserQuota(p.clusterInfo.Context, targetQuota)
623623
if err != nil {
624-
return errors.Wrapf(err, "failed to set MaxSize=%v to user %q", maxSizeInt64, p.cephUserName)
624+
return errors.Wrapf(err, "failed to set user %q quota enabled=%v %+v", p.cephUserName, quotaEnabled, additionalConfig)
625625
}
626626
}
627627

628628
return nil
629629
}
630630

631-
func toInt64(maxSize string) (int64, error) {
632-
maxSizeInt, err := resource.ParseQuantity(maxSize)
633-
if err != nil {
634-
return 0, errors.Wrap(err, "failed to parse quantity")
635-
}
636-
637-
return maxSizeInt.Value(), nil
638-
}
639-
640631
func (p *Provisioner) setTlsCaCert() error {
641632
objStore, err := p.getObjectStore()
642633
if err != nil {

pkg/operator/ceph/object/bucket/provisioner_test.go

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -112,29 +112,30 @@ func TestPopulateDomainAndPort(t *testing.T) {
112112
assert.Equal(t, "rook-ceph-rgw-test-store.ns.svc", p.storeDomainName)
113113
}
114114

115-
func TestMaxSizeToInt64(t *testing.T) {
116-
type args struct {
117-
maxSize string
118-
}
115+
func TestQuanityToInt64(t *testing.T) {
119116
tests := []struct {
120117
name string
121-
args args
122-
want int64
118+
input string
119+
want *int64
123120
wantErr bool
124121
}{
125-
{"invalid size", args{maxSize: "foo"}, 0, true},
126-
{"2gb size is invalid", args{maxSize: "2g"}, 0, true},
127-
{"2G size is valid", args{maxSize: "2G"}, 2000000000, false},
122+
{"foo is invalid", "foo", nil, true},
123+
{"2gb size is invalid", "2g", nil, true},
124+
{"2G size is valid", "2G", &(&struct{ i int64 }{2000000000}).i, false},
128125
}
129126
for _, tt := range tests {
130127
t.Run(tt.name, func(t *testing.T) {
131-
got, err := toInt64(tt.args.maxSize)
128+
got, err := quanityToInt64(tt.input)
132129
if (err != nil) != tt.wantErr {
133-
t.Errorf("maxSizeToInt64() error = %v, wantErr %v", err, tt.wantErr)
130+
t.Errorf("quanityToInt64() error = %v, wantErr %v", err, tt.wantErr)
134131
return
135132
}
136-
if got != tt.want {
137-
t.Errorf("maxSizeToInt64() = %v, want %v", got, tt.want)
133+
if got != nil && tt.want != nil && *got != *tt.want {
134+
t.Errorf("quanityToInt64() = %v, want %v", *got, *tt.want)
135+
} else if got != nil && tt.want == nil {
136+
t.Errorf("quanityToInt64() = %v, want %v", *got, tt.want)
137+
} else if got == nil && tt.want != nil {
138+
t.Errorf("quanityToInt64() = %v, want %v", got, *tt.want)
138139
}
139140
})
140141
}
@@ -191,7 +192,7 @@ func TestProvisioner_setAdditionalSettings(t *testing.T) {
191192
t.Run("quota should remain disabled", func(t *testing.T) {
192193
putValsSeen := []string{}
193194
p := newProvisioner(t,
194-
`{"user_quota":{"enabled":false,"max_size":-1,"max_objects":-1}}`,
195+
`{"enabled":false,"check_on_raw":false,"max_size":-1024,"max_size_kb":0,"max_objects":-1}`,
195196
&putValsSeen,
196197
)
197198

@@ -209,7 +210,7 @@ func TestProvisioner_setAdditionalSettings(t *testing.T) {
209210
t.Run("quota should be disabled", func(t *testing.T) {
210211
putValsSeen := []string{}
211212
p := newProvisioner(t,
212-
`{"user_quota":{"enabled":true,"max_size":-1,"max_objects":2}}`,
213+
`{"enabled":true,"check_on_raw":false,"max_size":-1024,"max_size_kb":0,"max_objects":2}`,
213214
&putValsSeen,
214215
)
215216

@@ -230,7 +231,7 @@ func TestProvisioner_setAdditionalSettings(t *testing.T) {
230231
t.Run("maxSize quota should be enabled", func(t *testing.T) {
231232
putValsSeen := []string{}
232233
p := newProvisioner(t,
233-
`{"user_quota":{"enabled":false,"max_size":-1,"max_objects":-1}}`,
234+
`{"enabled":false,"check_on_raw":false,"max_size":-1024,"max_size_kb":0,"max_objects":-1}`,
234235
&putValsSeen,
235236
)
236237

@@ -258,7 +259,7 @@ func TestProvisioner_setAdditionalSettings(t *testing.T) {
258259
t.Run("maxObjects quota should be enabled", func(t *testing.T) {
259260
putValsSeen := []string{}
260261
p := newProvisioner(t,
261-
`{"user_quota":{"enabled":false,"max_size":-1,"max_objects":-1}}`,
262+
`{"enabled":false,"check_on_raw":false,"max_size":-1024,"max_size_kb":0,"max_objects":-1}`,
262263
&putValsSeen,
263264
)
264265

@@ -286,7 +287,7 @@ func TestProvisioner_setAdditionalSettings(t *testing.T) {
286287
t.Run("maxObjects and maxSize quotas should be enabled", func(t *testing.T) {
287288
putValsSeen := []string{}
288289
p := newProvisioner(t,
289-
`{"user_quota":{"enabled":false,"max_size":-1,"max_objects":-1}}`,
290+
`{"enabled":false,"check_on_raw":false,"max_size":-1024,"max_size_kb":0,"max_objects":-1}`,
290291
&putValsSeen,
291292
)
292293

@@ -318,7 +319,7 @@ func TestProvisioner_setAdditionalSettings(t *testing.T) {
318319
t.Run("quotas are enabled and need updated enabled", func(t *testing.T) {
319320
putValsSeen := []string{}
320321
p := newProvisioner(t,
321-
`{"user_quota":{"enabled":true,"max_size":1,"max_objects":1}}`,
322+
`{"enabled":true,"check_on_raw":false,"max_size":1,"max_size_kb":0,"max_objects":1}`,
322323
&putValsSeen,
323324
)
324325

@@ -346,6 +347,28 @@ func TestProvisioner_setAdditionalSettings(t *testing.T) {
346347
})
347348
}
348349

350+
func TestProvisioner_additionalConfigSpecFromMap(t *testing.T) {
351+
t.Run("does not fail on empty map", func(t *testing.T) {
352+
spec, err := additionalConfigSpecFromMap(map[string]string{})
353+
assert.NoError(t, err)
354+
assert.Equal(t, additionalConfigSpec{}, *spec)
355+
})
356+
357+
t.Run("maxObjects field should be set", func(t *testing.T) {
358+
spec, err := additionalConfigSpecFromMap(map[string]string{"maxObjects": "2"})
359+
assert.NoError(t, err)
360+
var i int64 = 2
361+
assert.Equal(t, additionalConfigSpec{maxObjects: &i}, *spec)
362+
})
363+
364+
t.Run("maxSize field should be set", func(t *testing.T) {
365+
spec, err := additionalConfigSpecFromMap(map[string]string{"maxSize": "3"})
366+
assert.NoError(t, err)
367+
var i int64 = 3
368+
assert.Equal(t, additionalConfigSpec{maxSize: &i}, *spec)
369+
})
370+
}
371+
349372
func numberOfPutsWithValue(substr string, strs []string) int {
350373
count := 0
351374
for _, s := range strs {

pkg/operator/ceph/object/bucket/util.go

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
cephObject "github.com/rook/rook/pkg/operator/ceph/object"
2828
storagev1 "k8s.io/api/storage/v1"
2929
kerrors "k8s.io/apimachinery/pkg/api/errors"
30+
"k8s.io/apimachinery/pkg/api/resource"
3031
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3132
"k8s.io/apimachinery/pkg/types"
3233
"k8s.io/client-go/rest"
@@ -87,12 +88,25 @@ func (p *Provisioner) getObjectStore() (*cephv1.CephObjectStore, error) {
8788
return store, err
8889
}
8990

90-
func MaxObjectQuota(AdditionalConfig map[string]string) string {
91-
return AdditionalConfig["maxObjects"]
92-
}
91+
func additionalConfigSpecFromMap(config map[string]string) (*additionalConfigSpec, error) {
92+
var err error
93+
spec := additionalConfigSpec{}
94+
95+
if _, ok := config["maxObjects"]; ok {
96+
spec.maxObjects, err = quanityToInt64(config["maxObjects"])
97+
if err != nil {
98+
return nil, errors.Wrapf(err, "failed to parse maxObjects quota")
99+
}
100+
}
101+
102+
if _, ok := config["maxSize"]; ok {
103+
spec.maxSize, err = quanityToInt64(config["maxSize"])
104+
if err != nil {
105+
return nil, errors.Wrapf(err, "failed to parse maxSize quota")
106+
}
107+
}
93108

94-
func MaxSizeQuota(AdditionalConfig map[string]string) string {
95-
return AdditionalConfig["maxSize"]
109+
return &spec, nil
96110
}
97111

98112
func GetObjectStoreNameFromBucket(ob *bktv1alpha1.ObjectBucket) (types.NamespacedName, error) {
@@ -128,3 +142,14 @@ func getNSNameFromAdditionalState(state map[string]string) (types.NamespacedName
128142
}
129143
return types.NamespacedName{Name: name, Namespace: namespace}, nil
130144
}
145+
146+
func quanityToInt64(qty string) (*int64, error) {
147+
n, err := resource.ParseQuantity(qty)
148+
if err != nil {
149+
return nil, errors.Wrapf(err, "failed to parse %q as a quantity", qty)
150+
}
151+
152+
value := n.Value()
153+
154+
return &value, nil
155+
}

0 commit comments

Comments
 (0)