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

StorageOS Volume Plugin #42156

Merged
merged 1 commit into from
Jun 10, 2017
Merged

Conversation

croomes
Copy link
Contributor

@croomes croomes commented Feb 27, 2017

What this PR does / why we need it:
This PR adds a new volume plugin for StorageOS volumes. StorageOS runs as a container on Kubelet nodes, aggregating local or attached storage and making its capacity available to all nodes within the cluster. More information at http://storageos.com.

The StorageOS plugin supports:

  1. Dynamic Provisioning using Storage Classes
  2. Persistent Volumes and Persistent Volume Claims.

Which issue this PR fixes
A feature request has been created:
kubernetes/enhancements#190

This isn't on the schedule for 1.6 as I wasn't sure when it would be ready. We intend to make the StorageOS container openly available within the 1.6 lifetime.

Special notes for your reviewer:

Separate commits for feature and godep changes.

Release note:

StorageOS Volume Driver
[StorageOS](http://www.storageos.com) can be used as a storage provider for Kubernetes.  With StorageOS, capacity from local or attached storage is pooled across the cluster, providing converged infrastructure for cloud-native applications. 

@k8s-ci-robot
Copy link
Contributor

Hi @croomes. Thanks for your PR.

I'm waiting for a kubernetes member to verify that this patch is reasonable to test. If it is, they should reply with @k8s-bot ok to test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. I understand the commands that are listed here.

@k8s-ci-robot k8s-ci-robot added the cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. label Feb 27, 2017
@k8s-reviewable
Copy link

This change is Reviewable

@k8s-github-robot k8s-github-robot added kind/api-change Categorizes issue or PR as related to adding, removing, or otherwise changing an API size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. release-note Denotes a PR that will be considered when it comes time to generate release notes. labels Feb 27, 2017
@croomes croomes changed the title Storageos Volume Plugin StorageOS Volume Plugin Feb 27, 2017
@k8s-github-robot k8s-github-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Mar 1, 2017
@grodrigues3 grodrigues3 added the needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. label Apr 5, 2017
@croomes
Copy link
Contributor Author

croomes commented Apr 7, 2017

I'll rebase this as soon as I get back from DockerCon

@k8s-github-robot k8s-github-robot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Apr 27, 2017
@rootfs
Copy link
Contributor

rootfs commented Apr 27, 2017

/assign

@rootfs
Copy link
Contributor

rootfs commented Apr 27, 2017

@k8s-bot ok to test

@k8s-ci-robot k8s-ci-robot removed the needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. label Apr 27, 2017
pkg/api/types.go Outdated
// namespace is specified, the namespace of the pod will be used. Set to
// "default" if you are not using namespaces within StorageOS.
// +optional
Namespace string
Copy link
Contributor

Choose a reason for hiding this comment

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

why do you need NameSpace in volume source?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We use namespaces within StorageOS too, so by default it will use the Pod's namespace but you can force it not to use StorageOS namespace's at all by setting to "default". You can also specify a different StorageOS namespace here. Normally the StorageOS namespaces will mirror Kubernetes'.

@@ -0,0 +1,19 @@
/*
Copyright 2015 The Kubernetes Authors.
Copy link
Contributor

Choose a reason for hiding this comment

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

2017

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@@ -0,0 +1,333 @@
/*
Copyright 2016 The Kubernetes Authors.
Copy link
Contributor

Choose a reason for hiding this comment

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

2017

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

podUID: pod.UID,
pvName: spec.Name(),
volName: source.VolumeName,
namespace: source.Namespace,
Copy link
Contributor

Choose a reason for hiding this comment

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

you can get namespace from Pod pod.Namespace

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As comment above, this is the volume namespace and could be different from the Pod namespace.

storageos: &storageos{
pvName: spec.Name(),
volName: spec.PersistentVolume.Spec.StorageOS.VolumeName,
namespace: spec.PersistentVolume.Spec.StorageOS.Namespace,
Copy link
Contributor

Choose a reason for hiding this comment

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

you can get namespace from PV's ClaimRef

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the volume's namespace, not necessarily the Pod's. Starting to think I should rename it volNamespace to make it more clear - what do you think?


// Set parameters from StorageClass
if len(v.options.Parameters) > 0 {
if description, ok := v.options.Parameters["description"]; ok {
Copy link
Contributor

Choose a reason for hiding this comment

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

iterate parameters and use switch/case

Copy link
Contributor Author

Choose a reason for hiding this comment

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

much better - done

v.volName = v.options.PVName
v.namespace = v.options.PVC.Namespace
v.labels = make(map[string]string)
for k, val := range v.options.PVC.Labels {
Copy link
Contributor

Choose a reason for hiding this comment

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

why setting labels here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We copy the labels from the PVC to the requested volume. StorageOS uses the labels from k8s to set policy. Fox example, if the PVC has environment=production set, a policy rule within StorageOS might set the number of replicas to 2.

pv.Labels = make(map[string]string)
}
for k, v := range vol.Labels {
pv.Labels[k] = v
Copy link
Contributor

Choose a reason for hiding this comment

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

passing labels from PVC to PV?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is setting labels created by StorageOS when the volume is provisioned and applying them to the PV. So in the example above, the label storageos.feature.replicas=2 would be set on the PV. These labels are only informational.

@k8s-github-robot k8s-github-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Apr 28, 2017
@croomes
Copy link
Contributor Author

croomes commented May 2, 2017

Hi @rootfs - thanks for the review. I'm updating and will reply to the remaining points soon.

@croomes croomes force-pushed the storageos branch 2 times, most recently from f8e244a to dda964f Compare June 7, 2017 17:55
@@ -251,7 +251,7 @@ func (g *Graph) AddPV(pv *api.PersistentVolume) {

// since we don't know the other end of the pvc -> pod -> node chain (or it may not even exist yet), we can't decorate these edges with kubernetes node info
g.graph.SetEdge(simple.Edge{F: pvVertex, T: g.getOrCreateVertex_locked(pvcVertexType, pv.Spec.ClaimRef.Namespace, pv.Spec.ClaimRef.Name)})
pvutil.VisitPVSecretNames(pv, func(secret string) bool {
pvutil.VisitPVSecretNames(pv, func(namespace, secret string) bool {
// This grants access to the named secret in the same namespace as the bound PVC
g.graph.SetEdge(simple.Edge{F: g.getOrCreateVertex_locked(secretVertexType, pv.Spec.ClaimRef.Namespace, secret), T: pvVertex})
Copy link
Member

Choose a reason for hiding this comment

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

use the provided namespace here instead of pv.Spec.ClaimRef.Namespace

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

for _, pv := range pvs {
VisitPVSecretNames(pv, func(name string) bool {
VisitPVSecretNames(pv, func(namespace, name string) bool {
fmt.Printf("%s,%s\n", namespace, name)
Copy link
Member

Choose a reason for hiding this comment

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

drop the printf

Copy link
Contributor Author

Choose a reason for hiding this comment

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

whoops - done


func (plugin *storageosPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) {

source, _, err := getPersistentVolumeSource(spec)
Copy link
Member

@liggitt liggitt Jun 7, 2017

Choose a reason for hiding this comment

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

doesn't this mean the storageos plugin would only support persistentvolumes, not a storageos volume source specified in a pod spec?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No - this is a mistake. I added the code to detect both in newMounterInternal but not NewMounter (my tests were failing on it). I will refactor a bit and add to both. I couldn't think of a better way to handle both cases.

Copy link
Member

@liggitt liggitt Jun 7, 2017

Choose a reason for hiding this comment

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

yeah, this is part of the ripple of not being able to share a single volume source type :-/

}

var apiCfg *storageosAPIConfig
if source.SecretRef != nil {
Copy link
Member

Choose a reason for hiding this comment

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

I expected two paths here, one using secret name/namespace for a PV source, one using secret name and pod namespace for a pod source

Copy link
Member

Choose a reason for hiding this comment

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

actually, you can do this inside newMounterInternal, where you already have two branches

@saad-ali
Copy link
Member

saad-ali commented Jun 7, 2017

@dchen1107 held the v1.7 exceptions meeting today. She left it up to @liggitt and I to decide if this PR gets in for 1.7 or not.

@liggitt and I just had a meeting to discuss the current revision of this PR where the VolumeSource for this plugin is split between a PV (with ObjectReference) and non-PV (with LocalObjectReference) VolumeSource. We agree that while this is different from existing plugins, it is necessary to handle the unique access pattern for StorageOS--where they have a global credential that is required for provisioning/mounting/unmounting/deleting. For the dynamically provisioned volumes both secret namespace and name are specified, but since the PV object is non-namespaced, it can only be created with some one with admin privileges so that is ok. In this case the secret can live in a non-user namespace and therefore even if the user deletes their entire namespace, the volume will still be deleted correctly. For direct volume references, the secret must live in the users namespace (deletion is not a worry).

So overall we are fine with this approach. @liggitt and I will both take a final pass reviewing this PR. In the meantime, @croomes please test all the paths for this code (provisioning, mounting, unmounting, deleting, etc.). Once we have a thumbs up from you on the testing front and no further feedback, we will approve/LGTM this for merge (Friday at the latest).

@croomes
Copy link
Contributor Author

croomes commented Jun 7, 2017

Fantastic, thanks! I'm going to do another push in 30 mins that should fix the last comments, but I will spend the day tomorrow double-checking everything. I've re-run our basic acceptance tests and they pass for PVs and volumes in Pod specs and dynamic provisioning. I need to do more with the various secret namespace combinations.

To help review latest, I've only changed NewMounter and newMounterInternal, and changed validation.go to return proper path to secretRef name/namespace: fldPath.Child("secretRef", "namespace")

Really appreciate all the time you've both put into helping me get this in. 👍

@k8s-github-robot k8s-github-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jun 8, 2017
}

var apiCfg *storageosAPIConfig
if source.SecretRef != nil {
Copy link
Member

Choose a reason for hiding this comment

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

actually, you can do this inside newMounterInternal, where you already have two branches

capacity := c.options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)]
c.sizeGB = int(volume.RoundUpSize(capacity.Value(), 1024*1024*1024))

if adminSecretName == "" {
Copy link
Member

Choose a reason for hiding this comment

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

|| adminSecretNamespace == ""?

Copy link
Member

Choose a reason for hiding this comment

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

actually, does requiring this mean you can no longer use default storageos credentials?


// parseSecret will lookup secret in secretNamespace if set, otherwise default
// to pod namespace.
func parseSecret(secretNamespace, secretName string, pod *v1.Pod, kubeClient clientset.Interface) (*storageosAPIConfig, error) {
Copy link
Member

Choose a reason for hiding this comment

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

I don't think parseSecret() should exist... we should either call parsePodSecret() or parsePVSecret() directly, based on whether the source of the data was a PV or a Pod


func (plugin *storageosPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) {

secretNamespace, secretName, err := getSecretInfoFromSpec(spec)
Copy link
Member

Choose a reason for hiding this comment

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

rather than spreading this logic between three functions, and inferring the source was a PV because namespace == "" in parseSecret, it would be much more straightforward to combine getSecretInfoFromSpec and parseSecret

func getAPICfg(spec *volume.Spec, pod *v1.Pod, kubeClient clientset.Interface) (*storageosAPIConfig, error) {
	if spec.PersistentVolume != nil {
		source, _, err := getPersistentVolumeSource(spec)
		if err != nil {
			return nil, err
		}
		if source.SecretRef == nil {
			// not sure whether you require apiCfg in this case
			// would you expect to fall back to default values, or return an error?
			return nil, nil
		}
		return parsePVSecret(source.SecretRef.Namespace, source.SecretRef.Name, kubeClient)
	}

	if spec.Volume != nil {
		source, _, err := getVolumeSource(spec)
		if err != nil {
			return nil, err
		}
		if source.SecretRef == nil {
			// not sure whether you require apiCfg in this case
			// would you expect to fall back to default values, or return an error?
			return nil, nil
		}
		return parsePodSecret(pod, source.SecretRef.Name, kubeClient)
	}

	return nil, fmt.Errorf("spec not Volume or PersistentVolume")
}

@@ -20,34 +20,46 @@ import (
"k8s.io/kubernetes/pkg/api"
)

func getNamespace(pv *api.PersistentVolume) string {
Copy link
Member

Choose a reason for hiding this comment

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

nit: rename this to getClaimRefNamespace

if len(storageos.VolumeNamespace) > 0 {
allErrs = append(allErrs, ValidateDNS1123Label(storageos.VolumeNamespace, fldPath.Child("volumeNamespace"))...)
}
return allErrs
Copy link
Member

Choose a reason for hiding this comment

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

if secretRef != nil, ensure a name is set?

if len(storageos.VolumeNamespace) > 0 {
allErrs = append(allErrs, ValidateDNS1123Label(storageos.VolumeNamespace, fldPath.Child("volumeNamespace"))...)
}
if storageos.SecretRef != nil && (len(storageos.SecretRef.Name) > 0 || len(storageos.SecretRef.Namespace) > 0) {
Copy link
Member

Choose a reason for hiding this comment

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

if secretRef != nil, I'd expect the name and namespace to be set... a non-nil secretRef with empty data seems wrong

@liggitt
Copy link
Member

liggitt commented Jun 8, 2017

one comment about combining getSecretInfoFromSpec and parseSecret to make it very clear that parsePVSecret() is only called with data from a PV, and a couple validation questions.

the graph and pv secret util changes LGTM

@croomes
Copy link
Contributor Author

croomes commented Jun 8, 2017

I've made the changes suggested by @liggitt (good ideas), and we've done a lot of testing. Functionally we're happy, but I want to make one final pass on the examples and README to make sure we didn't miss anything. I'll have it rebased and ready to go for start of day (PT) Friday.

@k8s-github-robot k8s-github-robot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jun 9, 2017
@croomes
Copy link
Contributor Author

croomes commented Jun 9, 2017

@saad-ali / @liggitt - we're good from our side. A few minor updates to the docs and we've re-tested everything and it all looks fine.

Copy link
Member

@saad-ali saad-ali left a comment

Choose a reason for hiding this comment

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

/lgtm
/approve

// StorageOS represents a StorageOS volume that is attached to the kubelet's host machine and mounted into the pod
// More info: https://releases.k8s.io/HEAD/examples/volumes/storageos/README.md
// +optional
StorageOS *StorageOSPersistentVolumeSource
Copy link
Member

Choose a reason for hiding this comment

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

nit: Maybe add a comment indicating why it is not StorageOSVolumeSource.

@croomes
Copy link
Contributor Author

croomes commented Jun 9, 2017

👍 It's like another election night - the team is in the pub waiting for the votes to come in. Big cheer from over here - thanks.

@saad-ali saad-ali added this to the v1.7 milestone Jun 9, 2017
@saad-ali saad-ali added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Jun 9, 2017
@k8s-github-robot
Copy link

Automatic merge from submit-queue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. kind/api-change Categorizes issue or PR as related to adding, removing, or otherwise changing an API lgtm "Looks good to me", indicates that a PR is ready to be merged. release-note Denotes a PR that will be considered when it comes time to generate release notes. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.