-
Notifications
You must be signed in to change notification settings - Fork 39.9k
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
Support Kubelet PluginWatcher in Windows #81397
Conversation
/sig windows |
/assign @tallclair @yujuhong |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your pr!
Overall, the unix code here looks good to me. I will leave the review of the windows code to someone more experienced in that realm :)
I'm also curious... are we confident this change is the only one necessary to support the plugin watcher in windows? Do we want some form of integration testing behavior responsible for verifying this feature is working on windows? In part, I think the answer to that could relates to how difficult it would be to add e2e tests. Any thoughts on the difficulty?
@mattjmcnaughton yes this is the key change necessary in the plugin-watcher to get CSI node plugins working end2end based on a prototyping effort. This is discussed in more details in the KEP. e2e tests will certainly be put in place for Windows based CSI plugins which will exercise this code but that needs a few other pieces under development now. So the e2e test will be a separate PR. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for sending this PR!
return true, nil | ||
} | ||
return false, nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this return false or an error when the argument is a named pipe?
I'm not sure if that question even totally makes sense, I just know that named pipes are the other mechanism for IPC with Windows Kubernetes and could perhaps see someone mistakenly passing a named pipe path to this method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For a named pipe, IsUnixDomainSocket returns false
along with a non-nil err as it gets treated similar to an invalid file. Added a unit-test with a named pipe to clarify/check the behavior.
Note that in the context of the plugin watcher/fsnotify, named pipes won't be passed to IsUnixDomainSocket since the watcher is scoped to normal files with the plugin directory
On Wed, Aug 14, 2019 at 09:29:09AM -0700, Deep Debroy wrote:
@mattjmcnaughton yes this is the key change necessary in the plugin-watcher to get CSI node plugins working end2end. This is discussed in more details in the [KEP](https://github.com/kubernetes/enhancements/blob/master/keps/sig-windows/20190714-windows-csi-support.md#implementation-details).
e2e tests will certainly be put in place for Windows based CSI plugins which will exercise this code but that needs a few other pieces under development now. So the e2e test will be a separate PR.
--
You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub:
#81397 (comment)
Sounds good to me :) Small nit from me is that the pr title - "Support Kubelet
PluginWatcher in Windows" makes it sound as if this diff is the final one needed
for the feature to be completely working e2e. It sounds like that isn't quite
the case, as we still have some e2e testing PRs to come. But again, a small nit
:)
|
/lgtm |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor nits
@@ -202,6 +208,12 @@ func (w *Watcher) handleCreateEvent(event fsnotify.Event) error { | |||
} | |||
|
|||
func (w *Watcher) handlePluginRegistration(socketPath string) error { | |||
if runtime.GOOS == "windows" { | |||
socketPath = strings.Replace(socketPath, "/", "\\", -1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: strings.ReplaceAll
better?
if strings.HasPrefix(socketPath, "\\") { | ||
socketPath = "c:" + socketPath | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: maybe this could be made part of a helper package somewhere? Seems this is the exact same as
kubernetes/pkg/util/mount/mount_windows.go
Lines 250 to 256 in 091a5dc
func normalizeWindowsPath(path string) string { | |
normalizedPath := strings.Replace(path, "/", "\\", -1) | |
if strings.HasPrefix(normalizedPath, "\\") { | |
normalizedPath = "c:" + normalizedPath | |
} | |
return normalizedPath | |
} |
kubernetes/pkg/volume/util/util.go
Lines 314 to 320 in 091a5dc
func GetWindowsPath(path string) string { | |
windowsPath := strings.Replace(path, "/", "\\", -1) | |
if strings.HasPrefix(windowsPath, "\\") { | |
windowsPath = "c:" + windowsPath | |
} | |
return windowsPath | |
} |
Maybe that func could live in pkg/kubelet/util/util.go ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for highlighting the common functionality - one note, it makes me a little nervous to define a function in pkg/kubelet/util/util.go
that is then imported by pkg/volume
and pkg/util/mount
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 for factoring the function out. Maybe we should have a pkg/util/windows
package.
For this PR, I think we can put the helper function in pkg/kubelet/util
so that we don't get more code duplication within kubelet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Moved to pkg/kubelet/util for now.
if strings.HasPrefix(socketPath, "\\") { | ||
socketPath = "c:" + socketPath | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 for factoring the function out. Maybe we should have a pkg/util/windows
package.
For this PR, I think we can put the helper function in pkg/kubelet/util
so that we don't get more code duplication within kubelet.
pkg/kubelet/util/util_unix_test.go
Outdated
} | ||
for _, test := range tests { | ||
f, err := ioutil.TempFile("", "test-domain-socket") | ||
if err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: import github.com/stretchr/testify/require
and use require.NoError
Applies to all the unit tests changes in this PR :-)
pkg/kubelet/util/util_windows.go
Outdated
// Due to the absence of golang support for os.ModeSocket in Windows (https://github.com/golang/go/issues/33357) | ||
// we need to get the Reparse Points (https://docs.microsoft.com/en-us/windows/win32/fileio/reparse-points) | ||
// for the file (using FSCTL_GET_REPARSE_POINT) and check for reparse tag: reparseTagSocket | ||
fd, err := windows.CreateFile(windows.StringToUTF16Ptr(filePath), windows.GENERIC_READ, 0, nil, windows.OPEN_EXISTING, windows.FILE_FLAG_OPEN_REPARSE_POINT|windows.FILE_FLAG_BACKUP_SEMANTICS, 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would CreateFile
leave any side effect?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see a closeHandle call, does it also require a delete or remove file call?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CreateFile
, when invoked with windows.OPEN_EXISTING
(as is the case here) returns a handle to an existing file that the deferred CloseHandle
will clean up. No other side effects involved. We do not need to delete/remove anything resulting from the CreateFile
call as no new file gets created since CreateFile is invoked with windows.OPEN_EXISTING
. I will add a comment and explain this to make it clear.
pkg/kubelet/util/util_windows.go
Outdated
rdbbuf := make([]byte, syscall.MAXIMUM_REPARSE_DATA_BUFFER_SIZE) | ||
var bytesReturned uint32 | ||
// Issue FSCTL_GET_REPARSE_POINT (https://docs.microsoft.com/en-us/windows-hardware/drivers/ifs/fsctl-get-reparse-point) | ||
if err = windows.DeviceIoControl(fd, windows.FSCTL_GET_REPARSE_POINT, nil, 0, &rdbbuf[0], uint32(len(rdbbuf)), &bytesReturned, nil); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: use err :=
pkg/kubelet/util/util_windows.go
Outdated
return false, errors.Wrap(err, "FSCTL_GET_REPARSE_POINT failed") | ||
} | ||
rdb := (*reparseDataBuffer)(unsafe.Pointer(&rdbbuf[0])) | ||
if rdb.Header.ReparseTag == reparseTagSocket { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: return (rdb.Header.ReparseTag == reparseTagSocket), nil
@@ -202,6 +208,12 @@ func (w *Watcher) handleCreateEvent(event fsnotify.Event) error { | |||
} | |||
|
|||
func (w *Watcher) handlePluginRegistration(socketPath string) error { | |||
if runtime.GOOS == "windows" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What path format does fsnotify use? Is it just the typical unix /foo/bar/
path?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It reports /foo/bar
for paths in Linux and \\foo\\bar
for paths in Windows.
var pipeln net.Listener | ||
var err error | ||
if test.listenOnPipe { | ||
// generate a random pipe name to listen on |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: define a helper function getRandomPipeName := func(prefix string, len int) string{...
may make the test code more readable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
expectSocket: false, | ||
}, | ||
} | ||
for _, test := range tests { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm.....I think there's too many if/else blocks in the test and the code reuse is not so high... Maybe it'd be easier to write separate test function for each test case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@wk8 @yujuhong @jingxu97 @mattjmcnaughton thanks for the comments so far. I have addressed them. PTAL. |
/approve Leaving some time for others to chime in. |
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: ddebroy, yujuhong 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 |
/hold |
/hold cancel |
The reason for the failures observed with the FSCTL logic: when a unix domain socket file is created from within a container (in a path bind-mounted inside the container), the host upon issuing the FSCTL to the file, cannot detect the reparse points associated with unix domain sockets. Most likely the file system filter associated with bind mounting paths in a container is not passing the reparse point data through for files. Switching to a simpler logic that just tries to dial the file works. |
I tried the new approach for /lgtm |
Signed-off-by: Deep Debroy <ddebroy@docker.com>
/lgtm |
/retest |
What type of PR is this?
/kind feature
What this PR does / why we need it:
This PR enhances the kubelet plugin watcher to work with files associated with Unix Domain Sockets on Windows by correctly detecting such files using
FSCTL_GET_REPARSE_POINT
. In Windows,os.ModeSocket
does not get set correctly by golang (golang/go#33357).Which issue(s) this PR fixes:
Fixes #
Special notes for your reviewer:
Once golang adds support for
os.ModeSocket
for Windows (golang/go#33357), the functionIsUnixDomainSocket
introduced here may be removed.Does this PR introduce a user-facing change?:
Additional documentation e.g., KEPs (Kubernetes Enhancement Proposals), usage docs, etc.: