From 6d04bbf430d7ea6424f40dfa2b35a66ada4e962d Mon Sep 17 00:00:00 2001 From: Ben Zieglmeier Date: Fri, 9 Nov 2018 09:41:00 -0600 Subject: [PATCH] ceph: Add CRD for Object Store User Add the ability to create and manage object store users with CRDs Signed-off-by: Ben Zieglmeier --- Documentation/ceph-filesystem-crd.md | 2 +- Documentation/ceph-object-store-user-crd.md | 35 +++ Documentation/object.md | 46 +++- Gopkg.lock | 21 +- PendingReleaseNotes.md | 1 + .../examples/kubernetes/ceph/object-user.yaml | 8 + .../examples/kubernetes/ceph/operator.yaml | 16 ++ pkg/apis/ceph.rook.io/v1beta1/register.go | 2 + pkg/apis/ceph.rook.io/v1beta1/types.go | 26 +++ .../v1beta1/zz_generated.deepcopy.go | 78 +++++++ .../v1beta1/ceph.rook.io_client.go | 5 + .../v1beta1/fake/fake_ceph.rook.io_client.go | 4 + .../v1beta1/fake/fake_objectstoreuser.go | 126 ++++++++++ .../v1beta1/generated_expansion.go | 2 + .../ceph.rook.io/v1beta1/objectstoreuser.go | 155 +++++++++++++ pkg/operator/ceph/cluster/controller.go | 5 + pkg/operator/ceph/object/user/controller.go | 217 ++++++++++++++++++ .../ceph/object/user/controller_test.go | 37 +++ pkg/operator/ceph/operator.go | 3 +- pkg/operator/ceph/operator_test.go | 5 +- tests/framework/clients/object_user.go | 76 ++++++ tests/framework/clients/test_client.go | 12 +- tests/framework/installer/ceph_installer.go | 2 +- tests/framework/installer/ceph_manifests.go | 28 +++ .../installer/ceph_manifests_v0.8.go | 4 + tests/integration/base_object_test.go | 32 +-- 26 files changed, 901 insertions(+), 47 deletions(-) create mode 100644 Documentation/ceph-object-store-user-crd.md create mode 100644 cluster/examples/kubernetes/ceph/object-user.yaml create mode 100644 pkg/client/clientset/versioned/typed/ceph.rook.io/v1beta1/fake/fake_objectstoreuser.go create mode 100644 pkg/client/clientset/versioned/typed/ceph.rook.io/v1beta1/objectstoreuser.go create mode 100644 pkg/operator/ceph/object/user/controller.go create mode 100644 pkg/operator/ceph/object/user/controller_test.go create mode 100644 tests/framework/clients/object_user.go diff --git a/Documentation/ceph-filesystem-crd.md b/Documentation/ceph-filesystem-crd.md index 71f3525e2..f0c1c52d7 100644 --- a/Documentation/ceph-filesystem-crd.md +++ b/Documentation/ceph-filesystem-crd.md @@ -1,6 +1,6 @@ --- title: Ceph Shared File System -weight: 35 +weight: 36 indent: true --- {% assign url = page.url | split: '/' %} diff --git a/Documentation/ceph-object-store-user-crd.md b/Documentation/ceph-object-store-user-crd.md new file mode 100644 index 000000000..bbd0c19ec --- /dev/null +++ b/Documentation/ceph-object-store-user-crd.md @@ -0,0 +1,35 @@ +--- +title: Ceph Object Store User +weight: 35 +indent: true +--- + +# Ceph Object Store User CRD + +Rook allows creation and customization of object store users through the custom resource definitions (CRDs). The following settings are available +for Ceph object store users. + +## Sample + +```yaml +apiVersion: ceph.rook.io/v1beta1 +kind: ObjectStoreUser +metadata: + name: my-user + namespace: rook-ceph +spec: + store: my-store + displayName: my-display-name +``` + +## Object Store User Settings + +### Metadata + +- `name`: The name of the object store user to create, which will be reflected in the secret and other resource names. +- `namespace`: The namespace of the Rook cluster where the object store user is created. + +### Spec + +- `store`: The object store in which the user will be created. This matches the name of the objectstore CRD. +- `displayName`: The display name which will be passed to the `radosgw-admin user create` command. diff --git a/Documentation/object.md b/Documentation/object.md index cebb38ca2..763ba1b03 100644 --- a/Documentation/object.md +++ b/Documentation/object.md @@ -51,20 +51,46 @@ kubectl -n rook-ceph get pod -l app=rook-ceph-rgw ## Create a User -Creating an object storage user requires running a `radosgw-admin` command with the [Rook toolbox](ceph-quickstart.md#tools) pod. This will be simplified in the future with a CRD for the object store users. +Next we will create the object store user, which calls the RGW service in the cluster with the S3 API. +Specify your desired settings for the object store user in the `object-user.yaml`. For more details on the settings see the [Object Store User CRD](ceph-object-store-user-crd.md). + +```yaml +apiVersion: ceph.rook.io/v1beta1 +kind: ObjectStoreUser +metadata: + name: my-user + namespace: rook-ceph +spec: + store: my-store + displayName: "my display name" +``` + +When the object store user is created the Rook operator will create the RGW user on the object store specified, and store the Access Key and Secret Key in a kubernetes secret in the same namespace as the object store user. ```bash -radosgw-admin user create --uid rook-user --display-name "A rook rgw User" --rgw-realm=my-store --rgw-zonegroup=my-store +# Create the object store user +kubectl create -f object-user.yaml + +# To confirm the object store user is configured, describe the secret +kubectl -n rook-ceph describe secret rook-ceph-object-user-my-user + +Name: rook-ceph-object-user-my-user +Namespace: rook-ceph +Labels: app=rook-ceph-rgw + rook_cluster=rook-ceph + rook_object_store=my-store +Annotations: + +Type: kubernetes.io/rook + +Data +==== +AccessKey: 20 bytes +SecretKey: 40 bytes ``` -The object store is now available by using the creds of `rook-user`. Take note of the `access_key` and `secret_key` printed by the user creation. For example: -```json -{ - "user": "rook-user", - "access_key": "XEZDB3UJ6X7HVBE7X7MA", - "secret_key": "7yGIZON7EhFORz0I40BFniML36D2rl8CQQ5kXU6l" -} -``` +The AccessKey and SecretKey data fields can be mounted in a pod as an environment variable. More information on consuming +kubernetes secrets can be found on [The kubernetes website](https://kubernetes.io/docs/concepts/configuration/secret/) ## Consume the Object Storage diff --git a/Gopkg.lock b/Gopkg.lock index 9c8a96cbf..d76fedc85 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -35,7 +35,7 @@ "private/protocol/restxml", "private/protocol/xml/xmlutil", "service/s3", - "service/sts", + "service/sts" ] pruneopts = "UT" revision = "a8ff9e4804fc89c994b731b1c057640ab2aecff3" @@ -128,7 +128,7 @@ name = "github.com/gogo/protobuf" packages = [ "proto", - "sortkeys", + "sortkeys" ] pruneopts = "UT" revision = "636bf0302bc95575d69441b25a2603156ffdddf1" @@ -158,7 +158,7 @@ "ptypes", "ptypes/any", "ptypes/duration", - "ptypes/timestamp", + "ptypes/timestamp" ] pruneopts = "UT" revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5" @@ -194,7 +194,7 @@ packages = [ "OpenAPIv2", "compiler", - "extensions", + "extensions" ] pruneopts = "UT" revision = "7c663266750e7d82587642f65e60bc4083f1f84e" @@ -206,7 +206,7 @@ name = "github.com/gregjones/httpcache" packages = [ ".", - "diskcache", + "diskcache" ] pruneopts = "UT" revision = "9cad4c3443a7200dd6400aef47183728de563a38" @@ -216,7 +216,7 @@ name = "github.com/hashicorp/golang-lru" packages = [ ".", - "simplelru", + "simplelru" ] pruneopts = "UT" revision = "20f1fb78b0740ba8c3cb143a61e86ba5c8669768" @@ -231,7 +231,6 @@ revision = "4178557ae428460c3780a381c824a1f3aceb6325" [[projects]] - digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be" name = "github.com/inconshreveable/mousetrap" packages = ["."] pruneopts = "UT" @@ -348,7 +347,7 @@ packages = [ "expfmt", "internal/bitbucket.org/ww/goautoneg", - "model", + "model" ] pruneopts = "UT" revision = "c7de2306084e37d54b8be01f3541a8464345e9a5" @@ -407,7 +406,7 @@ packages = [ "assert", "require", - "suite", + "suite" ] pruneopts = "UT" revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686" @@ -632,7 +631,7 @@ "pkg/version", "pkg/watch", "third_party/forked/golang/json", - "third_party/forked/golang/reflect", + "third_party/forked/golang/reflect" ] pruneopts = "UT" revision = "def12e63c512da17043b4f0293f52d1006603d9f" @@ -839,7 +838,7 @@ "generator", "namer", "parser", - "types", + "types" ] pruneopts = "T" revision = "4242d8e6c5dba56827bb7bcf14ad11cda38f3991" diff --git a/PendingReleaseNotes.md b/PendingReleaseNotes.md index eeb17fa6b..d1bb15da8 100644 --- a/PendingReleaseNotes.md +++ b/PendingReleaseNotes.md @@ -17,6 +17,7 @@ - The toolbox manifest now creates a deployment based on the `rook/ceph` image instead of creating a pod on a specialized `rook/ceph-toolbox` image. - The frequency of discovering devices on a node is reduced to 60 minutes by default, and is configurable with the setting `ROOK_DISCOVER_DEVICES_INTERVAL` in operator.yaml. - The number of mons can be changed by updating the `mon.count` in the cluster CRD. +- Object Store User creation via CRD for Ceph clusters. ## Breaking Changes diff --git a/cluster/examples/kubernetes/ceph/object-user.yaml b/cluster/examples/kubernetes/ceph/object-user.yaml new file mode 100644 index 000000000..edc95a2ee --- /dev/null +++ b/cluster/examples/kubernetes/ceph/object-user.yaml @@ -0,0 +1,8 @@ +apiVersion: ceph.rook.io/v1beta1 +kind: ObjectStoreUser +metadata: + name: my-user + namespace: rook-ceph +spec: + store: my-store + displayName: "my display name" diff --git a/cluster/examples/kubernetes/ceph/operator.yaml b/cluster/examples/kubernetes/ceph/operator.yaml index d8b91679f..d710e68c5 100755 --- a/cluster/examples/kubernetes/ceph/operator.yaml +++ b/cluster/examples/kubernetes/ceph/operator.yaml @@ -100,6 +100,22 @@ spec: --- apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition +metadata: + name: objectstoreusers.ceph.rook.io +spec: + group: ceph.rook.io + names: + kind: ObjectStoreUser + listKind: ObjectStoreUserList + plural: objectstoreusers + singular: objectstoreuser + shortNames: + - rcou + scope: Namespaced + version: v1beta1 +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition metadata: name: pools.ceph.rook.io spec: diff --git a/pkg/apis/ceph.rook.io/v1beta1/register.go b/pkg/apis/ceph.rook.io/v1beta1/register.go index d645a293a..2f7e1b2f5 100644 --- a/pkg/apis/ceph.rook.io/v1beta1/register.go +++ b/pkg/apis/ceph.rook.io/v1beta1/register.go @@ -61,6 +61,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { &FilesystemList{}, &ObjectStore{}, &ObjectStoreList{}, + &ObjectStoreUser{}, + &ObjectStoreUserList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil diff --git a/pkg/apis/ceph.rook.io/v1beta1/types.go b/pkg/apis/ceph.rook.io/v1beta1/types.go index 94fc44d40..4f4ba4380 100755 --- a/pkg/apis/ceph.rook.io/v1beta1/types.go +++ b/pkg/apis/ceph.rook.io/v1beta1/types.go @@ -241,6 +241,32 @@ type ObjectStoreSpec struct { Gateway GatewaySpec `json:"gateway"` } +// +genclient +// +genclient:noStatus +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +type ObjectStoreUser struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata"` + Spec ObjectStoreUserSpec `json:"spec"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +type ObjectStoreUserList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + Items []ObjectStoreUser `json:"items"` +} + +// ObjectStoreUserSpec represent the spec of an Objectstoreuser +type ObjectStoreUserSpec struct { + //The store the user will be created in + Store string `json:"store,omitempty"` + //The display name for the ceph users + DisplayName string `json:"displayName,omitempty"` +} + type GatewaySpec struct { // The port the rgw service will be listening on (http) Port int32 `json:"port"` diff --git a/pkg/apis/ceph.rook.io/v1beta1/zz_generated.deepcopy.go b/pkg/apis/ceph.rook.io/v1beta1/zz_generated.deepcopy.go index ddb38375f..f96fb7765 100644 --- a/pkg/apis/ceph.rook.io/v1beta1/zz_generated.deepcopy.go +++ b/pkg/apis/ceph.rook.io/v1beta1/zz_generated.deepcopy.go @@ -399,6 +399,84 @@ func (in *ObjectStoreSpec) DeepCopy() *ObjectStoreSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ObjectStoreUser) DeepCopyInto(out *ObjectStoreUser) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectStoreUser. +func (in *ObjectStoreUser) DeepCopy() *ObjectStoreUser { + if in == nil { + return nil + } + out := new(ObjectStoreUser) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ObjectStoreUser) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ObjectStoreUserList) DeepCopyInto(out *ObjectStoreUserList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ObjectStoreUser, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectStoreUserList. +func (in *ObjectStoreUserList) DeepCopy() *ObjectStoreUserList { + if in == nil { + return nil + } + out := new(ObjectStoreUserList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ObjectStoreUserList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ObjectStoreUserSpec) DeepCopyInto(out *ObjectStoreUserSpec) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectStoreUserSpec. +func (in *ObjectStoreUserSpec) DeepCopy() *ObjectStoreUserSpec { + if in == nil { + return nil + } + out := new(ObjectStoreUserSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Pool) DeepCopyInto(out *Pool) { *out = *in diff --git a/pkg/client/clientset/versioned/typed/ceph.rook.io/v1beta1/ceph.rook.io_client.go b/pkg/client/clientset/versioned/typed/ceph.rook.io/v1beta1/ceph.rook.io_client.go index e4b5a87a1..0e8ac0ed7 100644 --- a/pkg/client/clientset/versioned/typed/ceph.rook.io/v1beta1/ceph.rook.io_client.go +++ b/pkg/client/clientset/versioned/typed/ceph.rook.io/v1beta1/ceph.rook.io_client.go @@ -30,6 +30,7 @@ type CephV1beta1Interface interface { ClustersGetter FilesystemsGetter ObjectStoresGetter + ObjectStoreUsersGetter PoolsGetter } @@ -50,6 +51,10 @@ func (c *CephV1beta1Client) ObjectStores(namespace string) ObjectStoreInterface return newObjectStores(c, namespace) } +func (c *CephV1beta1Client) ObjectStoreUsers(namespace string) ObjectStoreUserInterface { + return newObjectStoreUsers(c, namespace) +} + func (c *CephV1beta1Client) Pools(namespace string) PoolInterface { return newPools(c, namespace) } diff --git a/pkg/client/clientset/versioned/typed/ceph.rook.io/v1beta1/fake/fake_ceph.rook.io_client.go b/pkg/client/clientset/versioned/typed/ceph.rook.io/v1beta1/fake/fake_ceph.rook.io_client.go index 6a81ac64d..3e074a551 100644 --- a/pkg/client/clientset/versioned/typed/ceph.rook.io/v1beta1/fake/fake_ceph.rook.io_client.go +++ b/pkg/client/clientset/versioned/typed/ceph.rook.io/v1beta1/fake/fake_ceph.rook.io_client.go @@ -40,6 +40,10 @@ func (c *FakeCephV1beta1) ObjectStores(namespace string) v1beta1.ObjectStoreInte return &FakeObjectStores{c, namespace} } +func (c *FakeCephV1beta1) ObjectStoreUsers(namespace string) v1beta1.ObjectStoreUserInterface { + return &FakeObjectStoreUsers{c, namespace} +} + func (c *FakeCephV1beta1) Pools(namespace string) v1beta1.PoolInterface { return &FakePools{c, namespace} } diff --git a/pkg/client/clientset/versioned/typed/ceph.rook.io/v1beta1/fake/fake_objectstoreuser.go b/pkg/client/clientset/versioned/typed/ceph.rook.io/v1beta1/fake/fake_objectstoreuser.go new file mode 100644 index 000000000..8c21dde86 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/ceph.rook.io/v1beta1/fake/fake_objectstoreuser.go @@ -0,0 +1,126 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package fake + +import ( + v1beta1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1beta1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeObjectStoreUsers implements ObjectStoreUserInterface +type FakeObjectStoreUsers struct { + Fake *FakeCephV1beta1 + ns string +} + +var objectstoreusersResource = schema.GroupVersionResource{Group: "ceph.rook.io", Version: "v1beta1", Resource: "objectstoreusers"} + +var objectstoreusersKind = schema.GroupVersionKind{Group: "ceph.rook.io", Version: "v1beta1", Kind: "ObjectStoreUser"} + +// Get takes name of the objectStoreUser, and returns the corresponding objectStoreUser object, and an error if there is any. +func (c *FakeObjectStoreUsers) Get(name string, options v1.GetOptions) (result *v1beta1.ObjectStoreUser, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(objectstoreusersResource, c.ns, name), &v1beta1.ObjectStoreUser{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ObjectStoreUser), err +} + +// List takes label and field selectors, and returns the list of ObjectStoreUsers that match those selectors. +func (c *FakeObjectStoreUsers) List(opts v1.ListOptions) (result *v1beta1.ObjectStoreUserList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(objectstoreusersResource, objectstoreusersKind, c.ns, opts), &v1beta1.ObjectStoreUserList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1beta1.ObjectStoreUserList{} + for _, item := range obj.(*v1beta1.ObjectStoreUserList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested objectStoreUsers. +func (c *FakeObjectStoreUsers) Watch(opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(objectstoreusersResource, c.ns, opts)) + +} + +// Create takes the representation of a objectStoreUser and creates it. Returns the server's representation of the objectStoreUser, and an error, if there is any. +func (c *FakeObjectStoreUsers) Create(objectStoreUser *v1beta1.ObjectStoreUser) (result *v1beta1.ObjectStoreUser, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(objectstoreusersResource, c.ns, objectStoreUser), &v1beta1.ObjectStoreUser{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ObjectStoreUser), err +} + +// Update takes the representation of a objectStoreUser and updates it. Returns the server's representation of the objectStoreUser, and an error, if there is any. +func (c *FakeObjectStoreUsers) Update(objectStoreUser *v1beta1.ObjectStoreUser) (result *v1beta1.ObjectStoreUser, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(objectstoreusersResource, c.ns, objectStoreUser), &v1beta1.ObjectStoreUser{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ObjectStoreUser), err +} + +// Delete takes name of the objectStoreUser and deletes it. Returns an error if one occurs. +func (c *FakeObjectStoreUsers) Delete(name string, options *v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(objectstoreusersResource, c.ns, name), &v1beta1.ObjectStoreUser{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeObjectStoreUsers) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(objectstoreusersResource, c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1beta1.ObjectStoreUserList{}) + return err +} + +// Patch applies the patch and returns the patched objectStoreUser. +func (c *FakeObjectStoreUsers) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1beta1.ObjectStoreUser, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(objectstoreusersResource, c.ns, name, data, subresources...), &v1beta1.ObjectStoreUser{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ObjectStoreUser), err +} diff --git a/pkg/client/clientset/versioned/typed/ceph.rook.io/v1beta1/generated_expansion.go b/pkg/client/clientset/versioned/typed/ceph.rook.io/v1beta1/generated_expansion.go index b406cc421..bc7aaff3c 100644 --- a/pkg/client/clientset/versioned/typed/ceph.rook.io/v1beta1/generated_expansion.go +++ b/pkg/client/clientset/versioned/typed/ceph.rook.io/v1beta1/generated_expansion.go @@ -24,4 +24,6 @@ type FilesystemExpansion interface{} type ObjectStoreExpansion interface{} +type ObjectStoreUserExpansion interface{} + type PoolExpansion interface{} diff --git a/pkg/client/clientset/versioned/typed/ceph.rook.io/v1beta1/objectstoreuser.go b/pkg/client/clientset/versioned/typed/ceph.rook.io/v1beta1/objectstoreuser.go new file mode 100644 index 000000000..c51793d65 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/ceph.rook.io/v1beta1/objectstoreuser.go @@ -0,0 +1,155 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + v1beta1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1beta1" + scheme "github.com/rook/rook/pkg/client/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// ObjectStoreUsersGetter has a method to return a ObjectStoreUserInterface. +// A group's client should implement this interface. +type ObjectStoreUsersGetter interface { + ObjectStoreUsers(namespace string) ObjectStoreUserInterface +} + +// ObjectStoreUserInterface has methods to work with ObjectStoreUser resources. +type ObjectStoreUserInterface interface { + Create(*v1beta1.ObjectStoreUser) (*v1beta1.ObjectStoreUser, error) + Update(*v1beta1.ObjectStoreUser) (*v1beta1.ObjectStoreUser, error) + Delete(name string, options *v1.DeleteOptions) error + DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error + Get(name string, options v1.GetOptions) (*v1beta1.ObjectStoreUser, error) + List(opts v1.ListOptions) (*v1beta1.ObjectStoreUserList, error) + Watch(opts v1.ListOptions) (watch.Interface, error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1beta1.ObjectStoreUser, err error) + ObjectStoreUserExpansion +} + +// objectStoreUsers implements ObjectStoreUserInterface +type objectStoreUsers struct { + client rest.Interface + ns string +} + +// newObjectStoreUsers returns a ObjectStoreUsers +func newObjectStoreUsers(c *CephV1beta1Client, namespace string) *objectStoreUsers { + return &objectStoreUsers{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the objectStoreUser, and returns the corresponding objectStoreUser object, and an error if there is any. +func (c *objectStoreUsers) Get(name string, options v1.GetOptions) (result *v1beta1.ObjectStoreUser, err error) { + result = &v1beta1.ObjectStoreUser{} + err = c.client.Get(). + Namespace(c.ns). + Resource("objectstoreusers"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of ObjectStoreUsers that match those selectors. +func (c *objectStoreUsers) List(opts v1.ListOptions) (result *v1beta1.ObjectStoreUserList, err error) { + result = &v1beta1.ObjectStoreUserList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("objectstoreusers"). + VersionedParams(&opts, scheme.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested objectStoreUsers. +func (c *objectStoreUsers) Watch(opts v1.ListOptions) (watch.Interface, error) { + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("objectstoreusers"). + VersionedParams(&opts, scheme.ParameterCodec). + Watch() +} + +// Create takes the representation of a objectStoreUser and creates it. Returns the server's representation of the objectStoreUser, and an error, if there is any. +func (c *objectStoreUsers) Create(objectStoreUser *v1beta1.ObjectStoreUser) (result *v1beta1.ObjectStoreUser, err error) { + result = &v1beta1.ObjectStoreUser{} + err = c.client.Post(). + Namespace(c.ns). + Resource("objectstoreusers"). + Body(objectStoreUser). + Do(). + Into(result) + return +} + +// Update takes the representation of a objectStoreUser and updates it. Returns the server's representation of the objectStoreUser, and an error, if there is any. +func (c *objectStoreUsers) Update(objectStoreUser *v1beta1.ObjectStoreUser) (result *v1beta1.ObjectStoreUser, err error) { + result = &v1beta1.ObjectStoreUser{} + err = c.client.Put(). + Namespace(c.ns). + Resource("objectstoreusers"). + Name(objectStoreUser.Name). + Body(objectStoreUser). + Do(). + Into(result) + return +} + +// Delete takes name of the objectStoreUser and deletes it. Returns an error if one occurs. +func (c *objectStoreUsers) Delete(name string, options *v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("objectstoreusers"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *objectStoreUsers) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("objectstoreusers"). + VersionedParams(&listOptions, scheme.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Patch applies the patch and returns the patched objectStoreUser. +func (c *objectStoreUsers) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1beta1.ObjectStoreUser, err error) { + result = &v1beta1.ObjectStoreUser{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("objectstoreusers"). + SubResource(subresources...). + Name(name). + Body(data). + Do(). + Into(result) + return +} diff --git a/pkg/operator/ceph/cluster/controller.go b/pkg/operator/ceph/cluster/controller.go index 93e17ddff..cf8320222 100644 --- a/pkg/operator/ceph/cluster/controller.go +++ b/pkg/operator/ceph/cluster/controller.go @@ -34,6 +34,7 @@ import ( "github.com/rook/rook/pkg/operator/ceph/cluster/osd" "github.com/rook/rook/pkg/operator/ceph/file" "github.com/rook/rook/pkg/operator/ceph/object" + "github.com/rook/rook/pkg/operator/ceph/object/user" "github.com/rook/rook/pkg/operator/ceph/pool" "github.com/rook/rook/pkg/operator/discover" "github.com/rook/rook/pkg/operator/k8sutil" @@ -233,6 +234,10 @@ func (c *ClusterController) onAdd(obj interface{}) { objectStoreController := object.NewObjectStoreController(c.context, c.rookImage, cluster.Spec.CephVersion, cluster.Spec.Network.HostNetwork, cluster.ownerRef) objectStoreController.StartWatch(cluster.Namespace, cluster.stopCh) + // Start object store user CRD watcher + objectStoreUserController := objectuser.NewObjectStoreUserController(c.context, cluster.ownerRef) + objectStoreUserController.StartWatch(cluster.Namespace, cluster.stopCh) + // Start file system CRD watcher fileController := file.NewFilesystemController(c.context, c.rookImage, cluster.Spec.CephVersion, cluster.Spec.Network.HostNetwork, cluster.ownerRef) fileController.StartWatch(cluster.Namespace, cluster.stopCh) diff --git a/pkg/operator/ceph/object/user/controller.go b/pkg/operator/ceph/object/user/controller.go new file mode 100644 index 000000000..5ac171b61 --- /dev/null +++ b/pkg/operator/ceph/object/user/controller.go @@ -0,0 +1,217 @@ +/* +Copyright 2018 The Rook Authors. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package objectuser to manage a rook object store user. +package objectuser + +import ( + "fmt" + "reflect" + + "github.com/coreos/pkg/capnslog" + opkit "github.com/rook/operator-kit" + cephv1beta1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1beta1" + "github.com/rook/rook/pkg/clusterd" + cephrgw "github.com/rook/rook/pkg/daemon/ceph/rgw" + "github.com/rook/rook/pkg/operator/k8sutil" + "k8s.io/api/core/v1" + apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/cache" +) + +const ( + customResourceName = "objectstoreuser" + customResourceNamePlural = "objectstoreusers" + AppName = "rook-ceph-rgw" +) + +var logger = capnslog.NewPackageLogger("github.com/rook/rook", "op-object") + +// ObjectStoreResource represents the object store user custom resource +var ObjectStoreUserResource = opkit.CustomResource{ + Name: customResourceName, + Plural: customResourceNamePlural, + Group: cephv1beta1.CustomResourceGroup, + Version: cephv1beta1.Version, + Scope: apiextensionsv1beta1.NamespaceScoped, + Kind: reflect.TypeOf(cephv1beta1.ObjectStoreUser{}).Name(), +} + +// ObjectStoreUserController represents a controller object for object store user custom resources +type ObjectStoreUserController struct { + context *clusterd.Context + ownerRef metav1.OwnerReference +} + +// NewObjectStoreUserController create controller for watching object store user custom resources created +func NewObjectStoreUserController(context *clusterd.Context, ownerRef metav1.OwnerReference) *ObjectStoreUserController { + return &ObjectStoreUserController{ + context: context, + ownerRef: ownerRef, + } +} + +// StartWatch watches for instances of ObjectStoreUser custom resources and acts on them +func (c *ObjectStoreUserController) StartWatch(namespace string, stopCh chan struct{}) error { + + resourceHandlerFuncs := cache.ResourceEventHandlerFuncs{ + AddFunc: c.onAdd, + UpdateFunc: c.onUpdate, + DeleteFunc: c.onDelete, + } + + logger.Infof("start watching object store user resources in namespace %s", namespace) + watcher := opkit.NewWatcher(ObjectStoreUserResource, namespace, resourceHandlerFuncs, c.context.RookClientset.CephV1beta1().RESTClient()) + go watcher.Watch(&cephv1beta1.ObjectStoreUser{}, stopCh) + + return nil +} + +func (c *ObjectStoreUserController) onAdd(obj interface{}) { + user, err := getObjectStoreUserObject(obj) + if err != nil { + logger.Errorf("failed to get objectstoreuser object: %+v", err) + return + } + + if err = c.createUser(c.context, user); err != nil { + logger.Errorf("failed to create object store user %s. %+v", user.Name, err) + } +} + +func (c *ObjectStoreUserController) onUpdate(oldObj, newObj interface{}) { + // TODO: Add update code here after features are added which require updates. +} + +func (c *ObjectStoreUserController) onDelete(obj interface{}) { + user, err := getObjectStoreUserObject(obj) + if err != nil { + logger.Errorf("failed to get objectstoreuser object: %+v", err) + return + } + + if err = deleteUser(c.context, user); err != nil { + logger.Errorf("failed to delete object store user %s. %+v", user.Name, err) + } +} + +func (c *ObjectStoreUserController) storeUserOwners(store *cephv1beta1.ObjectStoreUser) []metav1.OwnerReference { + // Only set the cluster crd as the owner of the object store user resources. + // If the object store user crd is deleted, the operator will explicitly remove the object store user resources. + // If the object store user crd still exists when the cluster crd is deleted, this will make sure the object store user + // resources are cleaned up. + return []metav1.OwnerReference{c.ownerRef} +} + +func getObjectStoreUserObject(obj interface{}) (objectstoreuser *cephv1beta1.ObjectStoreUser, err error) { + var ok bool + objectstoreuser, ok = obj.(*cephv1beta1.ObjectStoreUser) + if ok { + // the objectstoreuser object is of the latest type, simply return it + return objectstoreuser.DeepCopy(), nil + } + return nil, fmt.Errorf("not a known objectstoreuser object: %+v", obj) +} + +// Create the user +func (c *ObjectStoreUserController) createUser(context *clusterd.Context, u *cephv1beta1.ObjectStoreUser) error { + // validate the user settings + if err := ValidateUser(context, u); err != nil { + return fmt.Errorf("invalid user %s arguments. %+v", u.Name, err) + } + //Set DisplayName to match Name if DisplayName is not set + displayName := u.Spec.DisplayName + if len(displayName) == 0 { + displayName = u.Name + } + + // create the user + logger.Infof("creating user %s in namespace %s", u.Name, u.Namespace) + userConfig := cephrgw.ObjectUser{ + UserID: u.Name, + DisplayName: &displayName, + } + objContext := cephrgw.NewContext(context, u.Spec.Store, u.Namespace) + + user, rgwerr, err := cephrgw.CreateUser(objContext, userConfig) + if err != nil { + return fmt.Errorf("failed to create user %s. RadosGW returned error %d: %+v", u.Name, rgwerr, err) + } + + // Store the keys in a secret + secrets := map[string]string{ + "AccessKey": *user.AccessKey, + "SecretKey": *user.SecretKey, + } + secret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("rook-ceph-object-user-%s-%s", u.Spec.Store, u.Name), + Namespace: u.Namespace, + Labels: map[string]string{ + "app": AppName, + "user": u.Name, + "rook_cluster": u.Namespace, + "rook_object_store": u.Spec.Store, + }, + }, + StringData: secrets, + Type: k8sutil.RookType, + } + k8sutil.SetOwnerRef(context.Clientset, u.Namespace, &secret.ObjectMeta, &c.ownerRef) + + _, err = context.Clientset.CoreV1().Secrets(u.Namespace).Create(secret) + if err != nil { + return fmt.Errorf("failed to save user %s secret. %+v", u.Name, err) + } + logger.Infof("created user %s", u.Name) + return nil +} + +// Delete the user +func deleteUser(context *clusterd.Context, u *cephv1beta1.ObjectStoreUser) error { + objContext := cephrgw.NewContext(context, u.Spec.Store, u.Namespace) + _, rgwerr, err := cephrgw.DeleteUser(objContext, u.Name) + if err != nil { + if rgwerr == 3 { + logger.Infof("user %s does not exist in store %s", u.Name, u.Spec.Store) + } else { + return fmt.Errorf("failed to delete user '%s': %+v", u.Name, err) + } + } + + err = context.Clientset.CoreV1().Secrets(u.Namespace).Delete("rook-ceph-object-user-"+u.Name, &metav1.DeleteOptions{}) + if err != nil { + logger.Warningf("failed to delete user %s secret. %+v", u.Name, err) + } + + logger.Infof("user %s deleted successfully", u.Name) + return nil +} + +// Validate the user arguments +func ValidateUser(context *clusterd.Context, u *cephv1beta1.ObjectStoreUser) error { + if u.Name == "" { + return fmt.Errorf("missing name") + } + if u.Namespace == "" { + return fmt.Errorf("missing namespace") + } + if u.Spec.Store == "" { + return fmt.Errorf("missing store") + } + return nil +} diff --git a/pkg/operator/ceph/object/user/controller_test.go b/pkg/operator/ceph/object/user/controller_test.go new file mode 100644 index 000000000..9a67929f5 --- /dev/null +++ b/pkg/operator/ceph/object/user/controller_test.go @@ -0,0 +1,37 @@ +/* +Copyright 2018 The Rook Authors. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package objectuser to manage a rook object store. +package objectuser + +import ( + "testing" + + cephv1beta1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1beta1" + "github.com/stretchr/testify/assert" +) + +func TestGetObjectStoreUserObject(t *testing.T) { + // get a current version objectstoreuser object, should return with no error + objectuser, err := getObjectStoreUserObject(&cephv1beta1.ObjectStoreUser{}) + assert.NotNil(t, objectuser) + assert.Nil(t, err) + + // try to get an object that isn't a objectstoreuser, should return with an error + objectuser, err = getObjectStoreUserObject(&map[string]string{}) + assert.Nil(t, objectuser) + assert.NotNil(t, err) +} diff --git a/pkg/operator/ceph/operator.go b/pkg/operator/ceph/operator.go index 6c6875610..0625cda96 100644 --- a/pkg/operator/ceph/operator.go +++ b/pkg/operator/ceph/operator.go @@ -35,6 +35,7 @@ import ( "github.com/rook/rook/pkg/operator/ceph/cluster" "github.com/rook/rook/pkg/operator/ceph/file" "github.com/rook/rook/pkg/operator/ceph/object" + "github.com/rook/rook/pkg/operator/ceph/object/user" "github.com/rook/rook/pkg/operator/ceph/pool" "github.com/rook/rook/pkg/operator/ceph/provisioner" "github.com/rook/rook/pkg/operator/ceph/provisioner/controller" @@ -74,7 +75,7 @@ type Operator struct { func New(context *clusterd.Context, volumeAttachmentWrapper attachment.Attachment, rookImage, securityAccount string) *Operator { clusterController := cluster.NewClusterController(context, rookImage, volumeAttachmentWrapper) - schemes := []opkit.CustomResource{cluster.ClusterResource, pool.PoolResource, object.ObjectStoreResource, + schemes := []opkit.CustomResource{cluster.ClusterResource, pool.PoolResource, object.ObjectStoreResource, objectuser.ObjectStoreUserResource, file.FilesystemResource, attachment.VolumeResource} return &Operator{ context: context, diff --git a/pkg/operator/ceph/operator_test.go b/pkg/operator/ceph/operator_test.go index b67bef5fb..4b520c802 100644 --- a/pkg/operator/ceph/operator_test.go +++ b/pkg/operator/ceph/operator_test.go @@ -25,6 +25,7 @@ import ( "github.com/rook/rook/pkg/operator/ceph/cluster" "github.com/rook/rook/pkg/operator/ceph/file" "github.com/rook/rook/pkg/operator/ceph/object" + "github.com/rook/rook/pkg/operator/ceph/object/user" "github.com/rook/rook/pkg/operator/ceph/pool" "github.com/rook/rook/pkg/operator/test" "github.com/stretchr/testify/assert" @@ -39,10 +40,10 @@ func TestOperator(t *testing.T) { assert.NotNil(t, o.clusterController) assert.NotNil(t, o.resources) assert.Equal(t, context, o.context) - assert.Equal(t, len(o.resources), 5) + assert.Equal(t, len(o.resources), 6) for _, r := range o.resources { if r.Name != cluster.ClusterResource.Name && r.Name != pool.PoolResource.Name && r.Name != object.ObjectStoreResource.Name && - r.Name != file.FilesystemResource.Name && r.Name != attachment.VolumeResource.Name { + r.Name != file.FilesystemResource.Name && r.Name != attachment.VolumeResource.Name && r.Name != objectuser.ObjectStoreUserResource.Name { assert.Fail(t, fmt.Sprintf("Resource %s is not valid", r.Name)) } } diff --git a/tests/framework/clients/object_user.go b/tests/framework/clients/object_user.go new file mode 100644 index 000000000..386b2c0ee --- /dev/null +++ b/tests/framework/clients/object_user.go @@ -0,0 +1,76 @@ +/* +Copyright 2018 The Rook Authors. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package clients + +import ( + "fmt" + "github.com/rook/rook/pkg/daemon/ceph/rgw" + "github.com/rook/rook/tests/framework/installer" + "github.com/rook/rook/tests/framework/utils" +) + +// ObjectUserOperation is wrapper for k8s rook object user operations +type ObjectUserOperation struct { + k8sh *utils.K8sHelper + manifests installer.CephManifests +} + +// CreateObjectUserOperation creates new rook object user client +func CreateObjectUserOperation(k8sh *utils.K8sHelper, manifests installer.CephManifests) *ObjectUserOperation { + return &ObjectUserOperation{k8sh, manifests} +} + +// ObjectUserGet Function to get the details of an object user from radosgw +func (o *ObjectUserOperation) GetUser(namespace string, store string, userid string) (*rgw.ObjectUser, error) { + context := o.k8sh.MakeContext() + rgwcontext := rgw.NewContext(context, store, namespace) + userinfo, _, err := rgw.GetUser(rgwcontext, userid) + if err != nil { + return nil, fmt.Errorf("failed to get user info: %+v", err) + } + return userinfo, nil +} + +// UserSecretExists Function to check that user secret was created +func (o *ObjectUserOperation) UserSecretExists(namespace string, store string, userid string) bool { + _, err := o.k8sh.GetResource("-n", namespace, "secrets", "-l", "rook_object_store="+store, "-l", "user="+userid) + if err == nil { + logger.Infof("Object User Secret Exists") + return true + } + logger.Infof("Unable to find user secret") + return false +} + +// ObjectUserCreate Function to create a object store user in rook +func (o *ObjectUserOperation) Create(namespace string, userid string, displayName string, store string) error { + + logger.Infof("creating the object store user via CRD") + if _, err := o.k8sh.ResourceOperation("create", o.manifests.GetObjectStoreUser(namespace, userid, displayName, store)); err != nil { + return err + } + return nil +} + +func (o *ObjectUserOperation) Delete(namespace string, userid string) error { + + logger.Infof("Deleting the object store user via CRD") + if _, err := o.k8sh.DeleteResource("-n", namespace, "ObjectStoreUser", userid); err != nil { + return err + } + return nil +} diff --git a/tests/framework/clients/test_client.go b/tests/framework/clients/test_client.go index db87010b8..6e777eaa6 100644 --- a/tests/framework/clients/test_client.go +++ b/tests/framework/clients/test_client.go @@ -30,11 +30,12 @@ var ( //TestClient is a wrapper for test client, containing interfaces for all rook operations type TestClient struct { - BlockClient *BlockOperation - FSClient *FilesystemOperation - ObjectClient *ObjectOperation - PoolClient *PoolOperation - k8sh *utils.K8sHelper + BlockClient *BlockOperation + FSClient *FilesystemOperation + ObjectClient *ObjectOperation + ObjectUserClient *ObjectUserOperation + PoolClient *PoolOperation + k8sh *utils.K8sHelper } const ( @@ -48,6 +49,7 @@ func CreateTestClient(k8sHelper *utils.K8sHelper, manifests installer.CephManife CreateBlockOperation(k8sHelper, manifests), CreateFilesystemOperation(k8sHelper, manifests), CreateObjectOperation(k8sHelper, manifests), + CreateObjectUserOperation(k8sHelper, manifests), CreatePoolOperation(k8sHelper, manifests), k8sHelper, } diff --git a/tests/framework/installer/ceph_installer.go b/tests/framework/installer/ceph_installer.go index 6d192e4a3..b23095be1 100644 --- a/tests/framework/installer/ceph_installer.go +++ b/tests/framework/installer/ceph_installer.go @@ -382,7 +382,7 @@ func (h *CephInstaller) UninstallRookFromMultipleNS(helmInstalled bool, systemNa } logger.Infof("removing the operator from namespace %s", systemNamespace) - _, err = h.k8shelper.DeleteResource("crd", "clusters.ceph.rook.io", "pools.ceph.rook.io", "objectstores.ceph.rook.io", "filesystems.ceph.rook.io", "volumes.rook.io") + _, err = h.k8shelper.DeleteResource("crd", "clusters.ceph.rook.io", "pools.ceph.rook.io", "objectstores.ceph.rook.io", "objectstoreusers.ceph.rook.io", "filesystems.ceph.rook.io", "volumes.rook.io") checkError(h.T(), err, "cannot delete CRDs") if helmInstalled { diff --git a/tests/framework/installer/ceph_manifests.go b/tests/framework/installer/ceph_manifests.go index b3d95b94b..9f2a9a85d 100644 --- a/tests/framework/installer/ceph_manifests.go +++ b/tests/framework/installer/ceph_manifests.go @@ -38,6 +38,7 @@ type CephManifests interface { GetBlockPoolStorageClass(namespace string, poolName string, storageClassName string, reclaimPolicy string) string GetFilesystem(namepace, name string, activeCount int) string GetObjectStore(namespace, name string, replicaCount, port int) string + GetObjectStoreUser(namespace, name string, displayName string, store string) string } type ClusterSettings struct { @@ -110,6 +111,22 @@ spec: --- apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition +metadata: + name: objectstoreusers.ceph.rook.io +spec: + group: ceph.rook.io + names: + kind: ObjectStoreUser + listKind: ObjectStoreUserList + plural: objectstoreusers + singular: objectstoreuser + shortNames: + - rcou + scope: Namespaced + version: v1beta1 +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition metadata: name: pools.ceph.rook.io spec: @@ -619,3 +636,14 @@ spec: allNodes: false ` } + +func (m *CephManifestsMaster) GetObjectStoreUser(namespace, name string, displayName string, store string) string { + return `apiVersion: ceph.rook.io/v1beta1 +kind: ObjectStoreUser +metadata: + name: ` + name + ` + namespace: ` + namespace + ` +spec: + displayName: ` + displayName + ` + store: ` + store +} diff --git a/tests/framework/installer/ceph_manifests_v0.8.go b/tests/framework/installer/ceph_manifests_v0.8.go index cf1c51316..3b57aa4da 100644 --- a/tests/framework/installer/ceph_manifests_v0.8.go +++ b/tests/framework/installer/ceph_manifests_v0.8.go @@ -574,3 +574,7 @@ spec: allNodes: false ` } + +func (m *CephManifestsV0_8) GetObjectStoreUser(namespace, name string, displayName string, store string) string { + return "" +} diff --git a/tests/integration/base_object_test.go b/tests/integration/base_object_test.go index 888dea418..f803400ae 100644 --- a/tests/integration/base_object_test.go +++ b/tests/integration/base_object_test.go @@ -25,6 +25,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" + "time" ) var ( @@ -51,20 +52,20 @@ func runObjectE2ETest(helper *clients.TestClient, k8sh *utils.K8sHelper, s suite require.Nil(s.T(), cobsErr) logger.Infof("Object store created successfully") - /* TODO: Reenable this test after we have the object user CRD - logger.Infof("Step 1 : Create Object Store User") - initialUsers, _ := helper.ObjectClient.ObjectListUser(storeName) - _, cosuErr := helper.ObjectClient.CreateUser(storeName, userid, userdisplayname) + cosuErr := helper.ObjectUserClient.Create(namespace, userid, userdisplayname, storeName) require.Nil(s.T(), cosuErr) - usersAfterCreate, _ := helper.ObjectClient.ObjectListUser(storeName) - require.Equal(s.T(), len(initialUsers)+1, len(usersAfterCreate), "Make sure user list count is increased by 1") - getuserData, guErr := helper.ObjectClient.ObjectGetUser(storeName, userid) - require.Nil(s.T(), guErr) - require.Equal(s.T(), userid, getuserData.UserID, "Check user id returned") - require.Equal(s.T(), userdisplayname, *getuserData.DisplayName, "Check user name returned") + logger.Infof("Waiting 5 seconds to ensure user was created") + time.Sleep(5 * time.Second) + require.True(s.T(), helper.ObjectUserClient.UserSecretExists(namespace, userid, storeName), "make sure user secret was created") + userInfo, gosuErr := helper.ObjectUserClient.GetUser(namespace, storeName, userid) + require.Nil(s.T(), gosuErr) + require.Equal(s.T(), userid, userInfo.UserID) + require.Equal(s.T(), userdisplayname, *userInfo.DisplayName) logger.Infof("Object store user created successfully") + /* TODO: We need bucket management tests. + logger.Infof("Step 2 : Get connection information") conninfo, conninfoError := helper.ObjectClient.ObjectGetUser(storeName, userid) require.Nil(s.T(), conninfoError) @@ -115,13 +116,12 @@ func runObjectE2ETest(helper *clients.TestClient, k8sh *utils.K8sHelper, s suite require.Equal(s.T(), len(initialBuckets), len(BucketsAfterDelete), "Make sure new bucket is deleted") logger.Infof("Bucket deleted successfully") - logger.Infof("Step 8 : Delete User") - usersBeforeDelete, _ := helper.ObjectClient.ObjectListUser(storeName) - helper.ObjectClient.DeleteUser(storeName, userid) - usersAfterDelete, _ := helper.ObjectClient.ObjectListUser(storeName) - require.Equal(s.T(), len(usersBeforeDelete)-1, len(usersAfterDelete), "Make sure user list count is reduced by 1") + */ // End of object operation tests + + logger.Infof("Step 2 : Test Deleting User") + dosuErr := helper.ObjectUserClient.Delete(namespace, userid) + require.Nil(s.T(), dosuErr) logger.Infof("Object store user deleted successfully") - */ logger.Infof("Check that MGRs are not in a crashloop") assert.True(s.T(), k8sh.CheckPodCountAndState("rook-ceph-mgr", namespace, 1, "Running"))