Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] collect metadata in k8s cm #102

Closed
wants to merge 9 commits into from
Closed

[WIP] collect metadata in k8s cm #102

wants to merge 9 commits into from

Conversation

mickymiek
Copy link

@mickymiek mickymiek commented Nov 23, 2018

This PR gives an options to persist RBD and CephFS metadata (currently stored on node) in a k8s configmap.

Related task: #66

@gman0
Copy link
Contributor

gman0 commented Nov 25, 2018

Thanks for the work! The general idea seems ok. For the first round of comments:

Non-atomic operations on a shared ConfigMap introduce race conditions

There's a race condition in read->modify->write when adding/deleting objects in a shared ConfigMap resulting in loosing the result of either of those operations.

Unrelated to the race condition but we should also take the size limit of a single CM object into the account - etcd is limiting us at 1MB per object which seems enough but since this implementation is using a single CM for all volumes it manages, maybe this is something to look out for.

Reusing metadata persistence code in both drivers

This one is just for refactoring so that we don't have the same thing implemented twice. Perhaps we could add a new package under pkg/util- this would contain interface for metadata manipulation with type-erased data + implementations for local storage and k8s CM. Maybe something like:

type CachePersister interface {
  Get(..., data interface{}) error
  Update(..., data interface{}) error
  Delete(...) error
}

Be more explicit about the type of metadata storage

Instead of having a bool --persistmetadata cmd argument, something more explicit could be more suitable, e.g. --metadatastorage=[node|configmap]. Also, IMHO any mention of Kubernetes-specific terminology should be indicated as such e.g. by prefixing such names with k8s_ (as in k8s_configmap rather than just configmap).

@rootfs thoughts?

pkg/rbd/rbd_persist.go Outdated Show resolved Hide resolved
@mickymiek
Copy link
Author

Thanks for the feedback @gman0 !

Non-atomic operations on a shared ConfigMap introduce race conditions
There's a race condition in read->modify->write when adding/deleting objects in a shared ConfigMap resulting in loosing the result of either of those operations.

Unrelated to the race condition but we should also take the size limit of a single CM object into the account - etcd is limiting us at 1MB per object which seems enough but since this implementation is using a single CM for all volumes it manages, maybe this is something to look out for.

I haven't thought about it... Do you have an idea how to handle both of these cases?
About the size limit of CM; I can't think of a direct solution to check it. Maybe counting characters in the data object?

@rootfs
Copy link
Member

rootfs commented Nov 26, 2018

@mickymiek for a quick start, you can make one cm per volume. In the cm, add a special label so when we list all volumes, we can list by label. You can find an example here

@rootfs
Copy link
Member

rootfs commented Nov 26, 2018

cc @rohan47

@mickymiek
Copy link
Author

updated my PR, PTAL @gman0 @rootfs, thanks !

Gopkg.toml Show resolved Hide resolved
pkg/rbd/controllerserver.go Outdated Show resolved Hide resolved
pkg/util/cachepersister.go Outdated Show resolved Hide resolved
pkg/util/k8scmcache.go Outdated Show resolved Hide resolved
pkg/util/k8scmcache.go Outdated Show resolved Hide resolved
pkg/util/k8scmcache.go Outdated Show resolved Hide resolved
pkg/util/k8scmcache.go Outdated Show resolved Hide resolved
return nil
}

func (k8scm *K8sCMCache) Create(identifier string, data interface{}) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the use-cases for overwriting an existing cache entry? IMHO this is out-of-scope for Create and I'd say if an entry with this identifier already exists just exit with success, otherwise create a new CM object (and return success in case of an "ALREADY EXISTS" error). That way you also get rid of locking explicitly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still a pending issue. Is there a reason why the CM is being updated here? If so please post a link to the code, maybe I'm missing something. This method should only:

  1. Try to Get the CM object (this is to handle idempotency)
    1.1 If it already exists, return nil as to say everything is ok
  2. Otherwise, Create a new CM object
    2.2 If it already exists, also return success

pkg/util/k8scmcache.go Outdated Show resolved Hide resolved
pkg/util/nodecache.go Outdated Show resolved Hide resolved
pkg/util/nodecache.go Outdated Show resolved Hide resolved
@gman0
Copy link
Contributor

gman0 commented Dec 7, 2018

Also, please run gofmt to fix the formatting errors.

@mickymiek
Copy link
Author

Updated, PTAL @gman0!

@gman0
Copy link
Contributor

gman0 commented Dec 13, 2018

Looks better, I'll post more comments on weekend.

}

var (
controllerCacheMap = make(map[volumeID]*controllerCacheEntry)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this actually used?

@@ -125,9 +155,10 @@ func (cs *controllerServer) DeleteVolume(ctx context.Context, req *csi.DeleteVol
defer func() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we discussed offline, this whole "cache entry reinsert" block can be removed.

if err := cs.MetadataStore.Get(string(volId), ce); err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
if err := cs.MetadataStore.Delete(string(volId)); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delete the cache entry as the last step of this procedure (i.e. just before returning)

}

const (
oneGB = 1073741824
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leftovers from the merge conflicts ;)

cmLabel = "csi-metadata"
cmDataKey = "content"

csiMetadataLabelAttr = "com.ceph.ceph-csi/persister"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a nit: perhaps metadata would be more descriptive here than persister?

if err != nil {
cm, err = k8scm.createMetadataCM(identifier)
if err != nil {
if strings.Contains(err.Error(), "already exists") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The correct way to check for various kinds of errors is with apierrors pkg, see this for example https://github.com/kubernetes/kubernetes/blob/7f23a743e8c23ac6489340bbb34fa6f1d392db9d/test/utils/create_resources.go#L191

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, existing CM is not an error


**Available environmental variables:**
`HOST_ROOTFS`: rbdplugin searches `/proc` directory under the directory set by `HOST_ROOTFS`.
`KUBERNETES_CONFIG_PATH`: if you use `k8s_configmap` as metadata store, specify the path of your k8s config file (if not specified, the plugin will assume you're running it inside a k8s cluster and find the config itself).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add this to cephfs docs as well

if err != nil {
return errors.Wrapf(err, "k8s-cm-cache: couldn't delete metadata configmap %s", identifier)
}
glog.Infof("k8s-cm-cache: successfully deleted metadata configmap %s", identifier)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If Delete prints a log msg, so should Create

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use glog.V(4)

if err = encoder.Encode(data); err != nil {
return errors.Wrapf(err, "node-cache: failed to encode metadata for file: %s\n", file)
}
glog.Infof("node-cache: successfully saved metadata into file: %s\n", file)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

glog.V(4), also in Delete

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants