Extend NodeUnpublish test to verify cleanup of TargetPath (#336)#338
Conversation
|
Welcome @dobsonj! |
|
Hi @dobsonj. Thanks for your PR. I'm waiting for a kubernetes-csi member to verify that this patch is reasonable to test. If it is, they should reply with Once the patch is verified, the new status will be reflected by the I understand the commands that are listed here. DetailsInstructions 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. |
|
|
||
| // NodeGetVolumeStats is expected to return NotFound | ||
| // if the TargetPath is removed by NodeUnpublishVolume | ||
| By("Checking NodeGetVolumeStats returns NotFound error") |
There was a problem hiding this comment.
This seems to make assumptions about how NodeGetVolumeStats is implemented. It could return an error because it tracks volume state and knows that NodeUnpublishVolume was called even when that function didn't remove the target path.
What you really need is to check on the node whether the directory is gone. Perhaps you can use CreateTargetDir (
Line 124 in a251c44
Line 143 in a251c44
Lines 362 to 367 in a251c44
But that looks like a hack. A cleaner solution is to introduce a CheckPath feature (with a configurable callback and command) which returns "file", "directory", "other" and "not_found", or an error, and then use that here.
There was a problem hiding this comment.
Thanks for the feedback @pohly, this was very helpful.
I implemented the CheckPath function, but with a couple of minor differences from what you described.
- I used return codes instead of strings, but it does return the 4 conditions you described. The test in node.go only checks for found or not found though. Since the spec states "file or directory" I did not want to make assumptions about the file type in the sanity test.
- I only did the configurable callback, not the command string that can override that behavior like in createMountTargetLocation() and removeMountTargetLocation(). This simplified the implementation, and I question whether the command string would be as useful in this context as it is for create / remove commands. Translating those 4 conditions into error codes (or strings) would add some complexity to the command. If the command string supports an essential use case though, I can add in.
There was a problem hiding this comment.
The command string is essential for those users who invoke the csi-sanity command to run tests. They can't extend that binary with Go callbacks.
There was a problem hiding this comment.
Thanks for elaborating on this, I added the command string option.
I tested it with the following check_path.sh bash script:
#!/bin/bash
if [ -f "$1" ]; then
echo "1" # file
elif [ -d "$1" ]; then
echo "2" # dir
elif [ ! -e "$1" ]; then
echo "3" # not found
else
echo "4" # other
fiAnd that passes against the mock driver using the following args:
$ csi-sanity -csi.checkpathcmd ./check_path.sh -csi.endpoint /tmp/csi.sock
There was a problem hiding this comment.
Why use numbers as output? I find strings as in my original proposal easier to use because there's no chance that someone, say, returns 3 when they meant 4. The same strings can be string constants in Go for the Go callback, so it could be even made type safe there.
There was a problem hiding this comment.
You're right, strings are a better approach here. When I added the Atoi call I started to question if that was really the best option. Updated.
| return "", fmt.Errorf("check path command %s failed: %v", config.CheckPathCmd, err) | ||
| } | ||
| // Convert the command's stdout to an integer. This is expected to match | ||
| // the value for PathIsFile, PathIsDir, PathIsNotFound, or PathIsOther. |
There was a problem hiding this comment.
This comment is out-dated, right?
| return prefix + uniqueSuffix | ||
| } | ||
|
|
||
| // Return codes for CheckPath |
There was a problem hiding this comment.
Please introduce a type (like type PathKind string) and use that to make the code more type safe.
| } | ||
| // Convert the command's stdout to an integer. This is expected to match | ||
| // the value for PathIsFile, PathIsDir, PathIsNotFound, or PathIsOther. | ||
| outstr := strings.TrimSpace(string(out)) |
There was a problem hiding this comment.
Here it would be good to check that the script has return a valid string. If not, raise an error.
| default: | ||
| err = fmt.Errorf("invalid PathType: %s", pk) | ||
| } | ||
| return err |
There was a problem hiding this comment.
This seems non-idiomatic. A shorter version is:
func (pk PathKind) validate() error {
switch pk {
case PathIsFile, PathIsDir, PathIsNotFound, PathIsOther:
return nil
default:
return fmt.Errorf("invalid PathType: %s", pk)
}
}
There was a problem hiding this comment.
I also find it odd to first cast a string and then call validate. It works of course, but a func IsPathKind (in string) (PathKind, error) makes the intent clearer.
There was a problem hiding this comment.
Makes sense, done. Appreciate your help with this.
pohly
left a comment
There was a problem hiding this comment.
Please squash your commits.
Except for some nits it looks okay, but I want to try it out before merging. I should get to that on Monday.
c19211e to
887c56f
Compare
| stringVar(&config.RemoveTargetPathCmd, "removemountpathcmd", "Command to run for target path removal") | ||
| stringVar(&config.RemoveStagingPathCmd, "removestagingpathcmd", "Command to run for staging path removal") | ||
| durationVar(&config.RemovePathCmdTimeout, "removepathcmdtimeout", "Timeout for the commands to remove target and staging paths, in seconds") | ||
| stringVar(&config.CheckPathCmd, "checkpathcmd", "Command to run to check a given path") |
There was a problem hiding this comment.
It's a bit hard for users of csi-sanity to find out what that command needs to do. Right now they have to dig into the source code to find the comment in pkg/sanity/sanity.go.
Perhaps add It must print "file", "directory", "not_found", "other" on stdout.?
| Expect(serverError.Code()).To(Equal(codes.InvalidArgument), "unexpected error: %s", serverError.Message()) | ||
| }) | ||
|
|
||
| It("should fail if target path was not removed by driver", func() { |
There was a problem hiding this comment.
Please replace with "should remove target path". The full test name then becomes `NodeUnpublishVolume should remove target path", which is a valid statement about the behavior of the driver.
pohly
left a comment
There was a problem hiding this comment.
Almost done. I tried this out in PMEM-CSI (intel/pmem-csi#948) and it worked as expected.
|
/ok-to-test |
|
Once this merges - we should send out an email to CSI announce list on - https://github.com/container-storage-interface/community so as CSI driver authors can fix their drivers. |
That change of behavior in Kubernetes was already announced in https://groups.google.com/g/container-storage-interface-drivers-announce/c/GgtP9kiv5Qc/m/CO2yyOJvAAAJ That announcement did not specifically talk about deleting the directory, though, because it follows from the spec. Pointing out the change of behavior of the csi-sanity suite is still worthwhile to avoid surprises. |
|
Is the mock driver not creating the target path? That might have to be added for pull-kubernetes-csi-csi-test to pass. |
|
That the test checks the path when it should exist is useful because it informs users of csi-sanity when their check path configuration isn't working. Please don't remove that. |
The mock driver works okay, but |
Then this might be exactly the issue that I mentioned: the check path feature must be configured so that it checks the path where hostpath is deployed, not where the csi-sanity test runs. The test assertion failure could be enhanced. I'll leave a comment on the code line. |
| By("Checking the target path exists") | ||
| pa, err := CheckPath(volpath, sc.Config) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
| Expect(pa).NotTo(Equal(PathIsNotFound)) |
There was a problem hiding this comment.
When this assertion fails, the resulting errors is not very helpful:
/home/prow/go/src/github.com/kubernetes-csi/csi-test/pkg/sanity/node.go:453
Expected
<sanity.PathKind>: not_found
not to equal
<sanity.PathKind>: not_found
/home/prow/go/src/github.com/kubernetes-csi/csi-test/pkg/sanity/node.go:477
You can add a description to it:
Expect(err).NotTo(HaveOccurred(), "checking path %q", volpath)
Expect(pa).NotTo(Equal(PathIsNotFound), "path %q should have been created by CSI driver and the test config should enabling testing for that path", volpath)
| By("Checking the target path was removed") | ||
| pa, err = CheckPath(volpath, sc.Config) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
| Expect(pa).To(Equal(PathIsNotFound)) |
Yeah, now I see, it needs to be configured here: csi-test/release-tools/prow.sh Lines 976 to 985 in a251c44 I'll follow up with a fix for this and the other 2 comments about improving the error messages. |
This failure shows that the new test is a breaking change: users of csi-sanity have to do something when updating, otherwise they get test failures. In this repo that causes a circular dependency: we first have to update csi-release-tools, but that then only works with an update csi-test. Let's avoid this breaking change. Two options:
The first options looks simpler at first glance, but then the Therefore I prefer the second option. |
This makes sense, I'll go with the second option as you suggest. I noticed after I pushed those changes that In any case, I'll revert the change to |
No, I see |
| cat >"${CSI_PROW_WORK}/checkdir_in_pod.sh" <<EOF | ||
| #!/bin/sh | ||
| OUT=\$(kubectl exec "${CSI_PROW_SANITY_POD}" -c "${CSI_PROW_SANITY_CONTAINER}" -- stat --printf="%F" "\$@" 2>&1) | ||
| if [ \$? -ne 0 ]; then |
There was a problem hiding this comment.
I don't see any obvious issue with this, but treating any error (including failures in kubectl) as "not_found" doesn't look right.
For example, does stat in the container support --printf?
I briefly considered using stat, but then decided against it as too brittle. Instead I went with a sequence of standard shell if checks in https://github.com/intel/pmem-csi/pull/948/files#diff-67bdd13daabc0c2eb2feaba824f0b9668e60c4cd9a68cad31357eaa568c50e8a
There was a problem hiding this comment.
I see, thank you, that seems the most likely explanation for the failure in the last test run (i.e. returning non-zero for some unexpected error).
490217e to
a209110
Compare
|
I dropped the commit that changed |
|
CI passed this time, I'll wait to squash the commits again until the review is otherwise complete. |
| Skip("CreateTargetPathCmd was set, but CheckPathCmd was not. Please update your testing configuration to enable CheckPathCmd.") | ||
| } | ||
| if sc.Config.CreateTargetDir != nil && sc.Config.CheckPath == nil { | ||
| Skip("CreateTargetDir was set, but CheckPath was not. Please update your testing configuration to enable CheckPath.") |
There was a problem hiding this comment.
Both should always be non-nil because they have defaults.
What you need to check for here is "sc.Config.CreateTargetDir not default and sc.Config.CheckPath not default".
Also, I think this only needs to be checked when sc.Config.CreateTargetPathCmd is unset. Otherwise we check and use sc.Config.CheckPathCmd.
There was a problem hiding this comment.
createMountTargetLocation has 2 separate paths, one for the custom function, but if that is nil then it calls mkdir. sc.Config.CreateTargetDir is nil by default.
https://github.com/dobsonj/csi-test/blob/a2091104af4d4e1df72bc6cfa8de4d955afa411a/pkg/sanity/sanity.go#L363-L378
Originally I did try to compare sc.Config.CheckPath != defaultCheckPath here, but got the following compile error:
pkg/sanity/node.go:463:63: invalid operation: sc.Config.CheckPath != defaultCheckPath (func can only be compared to nil)
Which is interesting... I think that should work fine in C, but apparently not in Go.
So I ended up changing CheckPath to follow the same model as createMountTargetLocation and leave sc.Config.CheckPath set to nil by default, specifically so the check on line 463 would work.
https://github.com/dobsonj/csi-test/blob/a2091104af4d4e1df72bc6cfa8de4d955afa411a/pkg/sanity/sanity.go#L539-L545
If sc.Config.CreateTargetPathCmd is set, we should hit the skip statement on line 461 and not make it down to line 464. Unless CreateTargetPathCmd, CheckPathCmd, and CreateTargetDir are all set at the same time, which seems strange. I can add a check for that though if that's what you're pointing out.
There was a problem hiding this comment.
So I ended up changing CheckPath to follow the same model as createMountTargetLocation and leave sc.Config.CheckPath set to nil by default, specifically so the check on line 463 would work.
That's the part that I had missed. Then the current code is fine.
pohly
left a comment
There was a problem hiding this comment.
Looks good now, but please squash before we merge it.
…-csi#336) The CSI spec states that the SP MUST delete the file or directory that it created as part of NodeUnpublishVolume. This adds a new scenario to the sanity test to verify that NodeUnpublishVolume removes TargetPath.
a209110 to
f531b5b
Compare
Done, thanks. |
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: dobsonj, pohly The full list of commands accepted by this bot can be found here. The pull request process is described here DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
What type of PR is this?
/kind bug
What this PR does / why we need it:
kubernetes/kubernetes#101441 deprecates removal of the CSI NodePublish target_path by the kubelet. This must be done by the CSI plugin according to the CSI spec.
This PR adds a sanity test to verify that the CSI plugin removes target_path as part of NodeUnpublish.
Which issue(s) this PR fixes:
Fixes #336
Special notes for your reviewer:
Does this PR introduce a user-facing change?:
Yes