ceph: Add CRD for Object Store User

Add the ability to create and manage object store users with CRDs

Signed-off-by: Ben Zieglmeier <benjamin.zieglmeier@target.com>
This commit is contained in:
Ben Zieglmeier
2018-11-21 15:15:05 -06:00
parent 0a8e1096ff
commit 6d04bbf430
26 changed files with 901 additions and 47 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
---
title: Ceph Shared File System
weight: 35
weight: 36
indent: true
---
{% assign url = page.url | split: '/' %}
@@ -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.
+36 -10
View File
@@ -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: <none>
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
Generated
+10 -11
View File
@@ -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"
+1
View File
@@ -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
@@ -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"
@@ -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:
@@ -61,6 +61,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
&FilesystemList{},
&ObjectStore{},
&ObjectStoreList{},
&ObjectStoreUser{},
&ObjectStoreUserList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
+26
View File
@@ -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"`
@@ -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
@@ -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)
}
@@ -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}
}
@@ -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
}
@@ -24,4 +24,6 @@ type FilesystemExpansion interface{}
type ObjectStoreExpansion interface{}
type ObjectStoreUserExpansion interface{}
type PoolExpansion interface{}
@@ -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
}
+5
View File
@@ -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)
+217
View File
@@ -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
}
@@ -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)
}
+2 -1
View File
@@ -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,
+3 -2
View File
@@ -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))
}
}
+76
View File
@@ -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
}
+7 -5
View File
@@ -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,
}
+1 -1
View File
@@ -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 {
@@ -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
}
@@ -574,3 +574,7 @@ spec:
allNodes: false
`
}
func (m *CephManifestsV0_8) GetObjectStoreUser(namespace, name string, displayName string, store string) string {
return ""
}
+16 -16
View File
@@ -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"))