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

CSI MountDevice/UnmountDevice Implementation #60115

Merged
merged 1 commit into from
Feb 27, 2018

Conversation

davidz627
Copy link
Contributor

Fixes #60114

What this PR does / why we need it:
This PR Implements MountDevice and UnmountDevice for the CSI Plugin, the functions will call through to NodeStageVolume/NodeUnstageVolume for CSI plugins.

/sig storage

Implements MountDevice and UnmountDevice for the CSI Plugin, the functions will call through to NodeStageVolume/NodeUnstageVolume for CSI plugins.

@k8s-ci-robot k8s-ci-robot added release-note Denotes a PR that will be considered when it comes time to generate release notes. sig/storage Categorizes an issue or PR as relevant to SIG Storage. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. labels Feb 21, 2018
@davidz627
Copy link
Contributor Author

/assign saad-ali

@davidz627
Copy link
Contributor Author

/assign vladimirvivien

@davidz627 davidz627 force-pushed the csiMountDevice branch 3 times, most recently from 2f64af5 to f8d0d31 Compare February 22, 2018 01:20
return "", fmt.Errorf("attacher.GetDeviceMountPath failed, driver name empty")
}

deviceMountPath := path.Join(c.plugin.host.GetPluginDir(csiSource.Driver), "mounts", csiSource.VolumeHandle)
Copy link
Member

Choose a reason for hiding this comment

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

Should this be {pluginDir}/csi/{csiDriverName}/mounts/{csiVolumeHandle}? Otherwise if someone names their plugin kubernetes.io/gce-pd they could stomp on the internal GCE PD directories right?

Also instead of "mounts" use mount.MountsInGlobalPDPath.

Copy link
Member

Choose a reason for hiding this comment

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

And maybe sanitize the volume and driver names (EscapeQualifiedNameForDisk or something like that)?

Copy link
Member

Choose a reason for hiding this comment

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

@davidz627 as a followup to Saad's, I would use the method defined here https://github.com/davidz627/kubernetes/blob/f8d0d3162ada15d8a28aed55431d3e66b7fec30f/pkg/volume/csi/csi_mounter.go#L441 as a central location to generate the device mount path. Make all adjustment there.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

// getPluginDir returns the pluginName for the given pluginDir that
// was created by getPluginDir
func (kl *Kubelet) getPluginNameFromDir(pluginDir string) string {
return filepath.Base(pluginDir)
Copy link
Member

Choose a reason for hiding this comment

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

You've defined plugin path as:

deviceMountPath := path.Join(c.plugin.host.GetPluginDir(csiSource.Driver), "mounts", csiSource.VolumeHandle)

So wouldn't this just return the csiSource.VolumeHandle?

Copy link
Member

Choose a reason for hiding this comment

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

Oh it looks like getDriverAndVolNameFromDeviceMountPath(...) is called first that splits at / boundaries, and then getPluginNameFromDir(...) is called to finish everything off.

Why not just do GetPluginDir(...) inside getPluginNameFromDir(...) and move this logic there? That way you could eliminate this method all together. It doesn't look like it is used anywhere else.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

overhauled to use pv.

glog.V(4).Infof(log("attacher.MountDevice(%s, %s)", devicePath, deviceMountPath))

// Setup
ctx, cancel := grpctx.WithTimeout(grpctx.Background(), csiTimeout)
Copy link
Member

Choose a reason for hiding this comment

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

Move creating ctx down to right before you actually use it for the first time. No need to waste cycles if you end up not using it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

return fmt.Errorf("attacher.MountDevice failed while getting node capabilities: %v", err)
}

if capabilities == nil {
Copy link
Member

Choose a reason for hiding this comment

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

Move this nil check inside hasStageUnstageCapability(...)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

return fmt.Errorf("attacher.MountDevice failed, deviceMountPath is empty")
}

mounted, err := isDirMounted(c.plugin, deviceMountPath)
Copy link
Member

Choose a reason for hiding this comment

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

Could you put this check first, and if it is false, then create a context, client, etc.?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

}

nodeName := string(c.plugin.host.GetNodeName())
fmt.Printf("voldid: %v, drivername: %v, nodeName: %v", csiSource.VolumeHandle, csiSource.Driver, nodeName)
Copy link
Member

Choose a reason for hiding this comment

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

Remember to remove debug statement or convert to glog with appropriate verbosity

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed


// probe driver
// TODO (vladimirvivien) move probe call where it is done only when it is needed.
if err := csi.NodeProbe(ctx, csiVersion); err != nil {
Copy link
Member

Choose a reason for hiding this comment

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

You can remove probe call altogether. It has been removed in the latest (HEAD) version of CSI.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

publishVolumeInfo := attachment.Status.AttachmentMetadata

// get volume attributes
// TODO: for alpha vol attributes are passed via PV.Annotations
Copy link
Member

Choose a reason for hiding this comment

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

Make sure to do a follow up PR to fix this, create a bug or something to remind you.

Copy link
Member

Choose a reason for hiding this comment

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

PR #58762 has the fix. Simply spec.VolumeAttribs as the parameter in call to NodeStageVolume

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

was in source.VolumeAttributes. lmk if this is not the correct one you were referring to. Looks right to me.

return stageUnstageSet, nil
}

func getDriverAndVolNameFromDeviceMountPath(deviceMountPath string, plugin *csiPlugin) (string, string, error) {
Copy link
Member

Choose a reason for hiding this comment

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

Oh man this is a fun bit of code.

Copy link
Member

Choose a reason for hiding this comment

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

It would be helpful to document the format deviceMountPath (i.e. {pluginDir}/csi/etc so future maintainers would have an idea why this works.

@k8s-ci-robot k8s-ci-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Feb 23, 2018
Copy link
Member

@vladimirvivien vladimirvivien left a comment

Choose a reason for hiding this comment

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

Please take a look at comments.

return "", fmt.Errorf("attacher.GetDeviceMountPath failed, driver name empty")
}

deviceMountPath := path.Join(c.plugin.host.GetPluginDir(csiSource.Driver), "mounts", csiSource.VolumeHandle)
Copy link
Member

Choose a reason for hiding this comment

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

@davidz627 as a followup to Saad's, I would use the method defined here https://github.com/davidz627/kubernetes/blob/f8d0d3162ada15d8a28aed55431d3e66b7fec30f/pkg/volume/csi/csi_mounter.go#L441 as a central location to generate the device mount path. Make all adjustment there.

attachID := getAttachmentName(csiSource.VolumeHandle, csiSource.Driver, nodeName)

// ensure version is supported
if err := csi.AssertSupportedVersion(ctx, csiVersion); err != nil {
Copy link
Member

Choose a reason for hiding this comment

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

We dont have to asssertSupportedVersion anymore that if block can be removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed for MountDevice. Still exists in SetUpAt and TearDownAt unless thats been changed in the last day or two.

publishVolumeInfo := attachment.Status.AttachmentMetadata

// get volume attributes
// TODO: for alpha vol attributes are passed via PV.Annotations
Copy link
Member

Choose a reason for hiding this comment

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

PR #58762 has the fix. Simply spec.VolumeAttribs as the parameter in call to NodeStageVolume

return stageUnstageSet, nil
}

func getDriverAndVolNameFromDeviceMountPath(deviceMountPath string, plugin *csiPlugin) (string, string, error) {
Copy link
Member

Choose a reason for hiding this comment

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

It would be helpful to document the format deviceMountPath (i.e. {pluginDir}/csi/etc so future maintainers would have an idea why this works.

}
volID := strings.Join(splitted[len(splitted)-3:], "/")
dir := strings.Join(splitted[:len(splitted)-4], "/")
pluginName := plugin.host.GetPluginNameFromDir(dir)
Copy link
Member

Choose a reason for hiding this comment

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

Assuming this{pluginDir}/csi/{csiDriverName}/mounts/{csiVolumeHandle} is the format, then
strings.Join(splitted[len(splitted)-3:], "/") will return slice like {csiDriverName}/mounts/{csiVolumeHandle} . Uneasy about the hardcoded offsets, seems a bit brittle.

Also, consider using filepath.Base(), filepath.Dir(), filepath.Join() for more portable path nomenclatures.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree that the hardcoded offesets are brittle but I couldn't find a better way to do this without a hardcoded number of "Dir"'s anyway (3 of them). Because {csiVolumeHandle} itself is a 3 part object of form project/zone/name. That fact isn't really captured anywhere (maybe I should have a comment). Open to suggestions for better ways to do this.

I tried using Rel which ended up being more brittle because even small changes or differences to directory structure would completely destroy the resulting path.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

overhauled. PTAL and review new versions of makeDeviceMountPath and getDriverAndVolNameFromDeviceMountPath.

@k8s-ci-robot k8s-ci-robot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Feb 24, 2018
@davidz627
Copy link
Contributor Author

@saad-ali @vladimirvivien Addressed all comments. Biggest change is in makeDeviceMountPath and getDriverAndVolNameFromDeviceMountPath. Please take a closer look there. Thanks!

@davidz627
Copy link
Contributor Author

@vladimirvivien the PV method in getDriverAndVolNameFromDeviceMountPath was created to be able to reliably retrieve the volume handle.

Since there are no character restrictions in the CSI spec on volume handles there were no guaranteed ways to parse the volume handle out of the DeviceMountPath even with sanitation.

@davidz627
Copy link
Contributor Author

/retest

@davidz627
Copy link
Contributor Author

/retest

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.

Almost there!

}

func getDriverAndVolNameFromDeviceMountPath(k8s kubernetes.Interface, deviceMountPath string) (string, string, error) {
// deviceMountPath structure: /var/lib/kubelet/plugins/kubernetes.io/csi/pv/{pvname}/globalmount
Copy link
Member

Choose a reason for hiding this comment

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

Maybe sanity check assert that path terminates in /globalmount or /globalmount/?

return err
}

req := &csipb.NodeStageVolumeRequest{
Copy link
Member

Choose a reason for hiding this comment

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

Pass in secret (node_stage_secrets field). It is specified on the Kubernetes side in the NodeStageSecretRef in CSIPersistentVolumeSource.

Examples here:
#60118
#60382

return fmt.Errorf("attacher.MountDevice failed while getting node capabilities: %v", err)
}

stageUnstageSet, err := hasStageUnstageCapability(capabilities)
Copy link
Member

Choose a reason for hiding this comment

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

Since you always call NodeGetCapabilities(...) before hasStageUnstageCapability(...). Maybe move NodeGetCapabilities(...) inside hasStageUnstageCapability(...)

@vladimirvivien
Copy link
Member

@davidz627 taking a look right now.

@davidz627
Copy link
Contributor Author

/retest

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

@k8s-ci-robot k8s-ci-robot added lgtm "Looks good to me", indicates that a PR is ready to be merged. approved Indicates a PR has been approved by an approver from all required OWNERS files. labels Feb 26, 2018
@saad-ali saad-ali added priority/important-soon Must be staffed and worked on either currently, or very soon, ideally in time for the next release. kind/feature Categorizes issue or PR as related to a new feature. labels Feb 26, 2018
@saad-ali saad-ali added this to the v1.10 milestone Feb 26, 2018
Copy link
Member

@vladimirvivien vladimirvivien left a comment

Choose a reason for hiding this comment

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

Added some comments. Getting there.

if file := filepath.Base(deviceMountPath); file != globalMountInGlobalPath {
return "", "", fmt.Errorf("getDriverAndVolNameFromDeviceMountPath failed, path did not end in %s", globalMountInGlobalPath)
}
// dir is now /var/lib/kubelet/plugins/kubernetes.io/csi/pv/{pvname}
Copy link
Member

Choose a reason for hiding this comment

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

Is {pvname} sanitized elsewhere (to remove path demarkation chars) ? If so, it probably need to return back to its original state. If no sanitization occurred, ignore.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am not doing sanitation anywhere, so the name should be same in both the creation and the extraction.

pvName := filepath.Base(dir)

// Get PV and check for errors
pv, err := k8s.CoreV1().PersistentVolumes().Get(pvName, meta.GetOptions{})
Copy link
Member

Choose a reason for hiding this comment

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

Do we need to worry about namespace here? If not, there maybe name collision.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

PV's are not namespaced in Kubernetes.

}

// Get VolumeHandle and PluginName from pv
csiSource := pv.Spec.CSI
Copy link
Member

Choose a reason for hiding this comment

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

Do a nil check here for pv.Spec.CSI

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 5 lines above already

@davidz627
Copy link
Contributor Author

/retest

@saad-ali
Copy link
Member

Will let @vladimirvivien review and LGTM

/lgtm cancel

@k8s-ci-robot k8s-ci-robot removed lgtm "Looks good to me", indicates that a PR is ready to be merged. approved Indicates a PR has been approved by an approver from all required OWNERS files. labels Feb 26, 2018
@saad-ali
Copy link
Member

/approve

@k8s-ci-robot k8s-ci-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Feb 26, 2018
@davidz627
Copy link
Contributor Author

/retest

…nstageVolume for CSI Volume Plugin. Added related unit tests. Vendored CSI Spec to HEAD
@davidz627
Copy link
Contributor Author

/retest

@davidz627
Copy link
Contributor Author

/retest

@vladimirvivien
Copy link
Member

/LGTM

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Feb 27, 2018
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: davidz627, saad-ali, vladimirvivien

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-github-robot
Copy link

/test all [submit-queue is verifying that this PR is safe to merge]

@k8s-github-robot
Copy link

Automatic merge from submit-queue (batch tested with PRs 60430, 60115, 58052, 60355, 60116). If you want to cherry-pick this change to another branch, please follow the instructions here.

@k8s-github-robot k8s-github-robot merged commit 0a8e5f8 into kubernetes:master Feb 27, 2018
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/feature Categorizes issue or PR as related to a new feature. lgtm "Looks good to me", indicates that a PR is ready to be merged. priority/important-soon Must be staffed and worked on either currently, or very soon, ideally in time for the next release. release-note Denotes a PR that will be considered when it comes time to generate release notes. sig/storage Categorizes an issue or PR as relevant to SIG Storage. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants