From 66a1ef25e07650fc9358e639b0fcccf200418624 Mon Sep 17 00:00:00 2001 From: Jitendra Bhurat Date: Tue, 1 Nov 2016 10:10:59 -0400 Subject: [PATCH] Fixing Volumes on Windows --- pkg/api/validation/validation.go | 2 -- pkg/kubelet/dockertools/docker_manager.go | 13 +++++++--- pkg/kubelet/kubelet_pods.go | 27 +++++++++++++++------ pkg/volume/secret/secret.go | 16 +++++++++++-- pkg/volume/util/atomic_writer.go | 16 +++++++++++-- pkg/volume/volume.go | 29 +++++++++++++++++++---- 6 files changed, 83 insertions(+), 20 deletions(-) diff --git a/pkg/api/validation/validation.go b/pkg/api/validation/validation.go index 871ff7b22b..412b597060 100644 --- a/pkg/api/validation/validation.go +++ b/pkg/api/validation/validation.go @@ -1463,8 +1463,6 @@ func validateVolumeMounts(mounts []api.VolumeMount, volumes sets.String, fldPath } if len(mnt.MountPath) == 0 { allErrs = append(allErrs, field.Required(idxPath.Child("mountPath"), "")) - } else if strings.Contains(mnt.MountPath, ":") { - allErrs = append(allErrs, field.Invalid(idxPath.Child("mountPath"), mnt.MountPath, "must not contain ':'")) } if mountpoints.Has(mnt.MountPath) { allErrs = append(allErrs, field.Invalid(idxPath.Child("mountPath"), mnt.MountPath, "must be unique")) diff --git a/pkg/kubelet/dockertools/docker_manager.go b/pkg/kubelet/dockertools/docker_manager.go index b7210b939b..31540f8ef8 100644 --- a/pkg/kubelet/dockertools/docker_manager.go +++ b/pkg/kubelet/dockertools/docker_manager.go @@ -28,6 +28,7 @@ import ( "os/exec" "path" "path/filepath" + "runtime" "strconv" "strings" "sync" @@ -57,7 +58,7 @@ import ( "k8s.io/kubernetes/pkg/kubelet/types" "k8s.io/kubernetes/pkg/kubelet/util/cache" "k8s.io/kubernetes/pkg/kubelet/util/format" - "k8s.io/kubernetes/pkg/runtime" + kruntime "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/security/apparmor" "k8s.io/kubernetes/pkg/securitycontext" kubetypes "k8s.io/kubernetes/pkg/types" @@ -606,7 +607,7 @@ func (dm *DockerManager) runContainer( // TODO: This is kind of hacky, we should really just encode the bits we need. // TODO: This is hacky because the Kubelet should be parameterized to encode a specific version // and needs to be able to migrate this whenever we deprecate v1. Should be a member of DockerManager. - if data, err := runtime.Encode(api.Codecs.LegacyCodec(unversioned.GroupVersion{Group: api.GroupName, Version: "v1"}), pod); err == nil { + if data, err := kruntime.Encode(api.Codecs.LegacyCodec(unversioned.GroupVersion{Group: api.GroupName, Version: "v1"}), pod); err == nil { labels[kubernetesPodLabel] = string(data) } else { glog.Errorf("Failed to encode pod: %s for prestop hook", pod.Name) @@ -677,6 +678,12 @@ func (dm *DockerManager) runContainer( SecurityOpt: fmtSecurityOpts, } + // There is no /etc/resolv.conf in Windows, DNS and DNSSearch options would have to be passed to Docker runtime instead + if runtime.GOOS == "windows" { + hc.DNS = opts.DNS + hc.DNSSearch = opts.DNSSearch + } + // Set sysctls if requested sysctls, unsafeSysctls, err := api.SysctlsFromPodAnnotations(pod.Annotations) if err != nil { @@ -1586,7 +1593,7 @@ func containerAndPodFromLabels(inspect *dockertypes.ContainerJSON) (pod *api.Pod // the pod data may not be set if body, found := labels[kubernetesPodLabel]; found { pod = &api.Pod{} - if err = runtime.DecodeInto(api.Codecs.UniversalDecoder(), []byte(body), pod); err == nil { + if err = kruntime.DecodeInto(api.Codecs.UniversalDecoder(), []byte(body), pod); err == nil { name := labels[types.KubernetesContainerNameLabel] for ix := range pod.Spec.Containers { if pod.Spec.Containers[ix].Name == name { diff --git a/pkg/kubelet/kubelet_pods.go b/pkg/kubelet/kubelet_pods.go index 8574a0ee5c..de9d2e2dfc 100644 --- a/pkg/kubelet/kubelet_pods.go +++ b/pkg/kubelet/kubelet_pods.go @@ -82,7 +82,8 @@ func makeMounts(pod *api.Pod, podDir string, container *api.Container, hostName, // - container is not an infrastructure(pause) container // - container is not already mounting on /etc/hosts // When the pause container is being created, its IP is still unknown. Hence, PodIP will not have been set. - mountEtcHostsFile := (pod.Spec.SecurityContext == nil || !pod.Spec.SecurityContext.HostNetwork) && len(podIP) > 0 + // OS is not Windows + mountEtcHostsFile := (pod.Spec.SecurityContext == nil || !pod.Spec.SecurityContext.HostNetwork) && len(podIP) > 0 && runtime.GOOS != "windows" glog.V(3).Infof("container: %v/%v/%v podIP: %q creating hosts mount: %v", pod.Namespace, pod.Name, container.Name, podIP, mountEtcHostsFile) mounts := []kubecontainer.Mount{} for _, mount := range container.VolumeMounts { @@ -108,9 +109,21 @@ func makeMounts(pod *api.Pod, podDir string, container *api.Container, hostName, if mount.SubPath != "" { hostPath = filepath.Join(hostPath, mount.SubPath) } + + // Docker Volume Mounts fail on Windows if it is not of the form C:/ + containerPath := mount.MountPath + if runtime.GOOS == "windows" { + if strings.HasPrefix(hostPath, "/") && !strings.Contains(hostPath, ":") { + hostPath = "c:" + hostPath + } + if strings.HasPrefix(containerPath, "/") && !strings.Contains(containerPath, ":") { + containerPath = "c:" + containerPath + } + } + mounts = append(mounts, kubecontainer.Mount{ Name: mount.Name, - ContainerPath: mount.MountPath, + ContainerPath: containerPath, HostPath: hostPath, ReadOnly: mount.ReadOnly, SELinuxRelabel: relabelVolume, @@ -262,17 +275,17 @@ func (kl *Kubelet) GenerateRunContainerOptions(pod *api.Pod, container *api.Cont } } - if runtime.GOOS != "windows" { - opts.Mounts, err = makeMounts(pod, kl.getPodDir(pod.UID), container, hostname, hostDomainName, podIP, volumes) - if err != nil { - return nil, err - } + opts.Mounts, err = makeMounts(pod, kl.getPodDir(pod.UID), container, hostname, hostDomainName, podIP, volumes) + if err != nil { + return nil, err } opts.Envs, err = kl.makeEnvironmentVariables(pod, container, podIP) if err != nil { return nil, err } + // Disabling adding TerminationMessagePath on Windows as these files would be mounted as docker volume and + // Docker for Windows has a bug where only directories can be mounted if len(container.TerminationMessagePath) != 0 && runtime.GOOS != "windows" { p := kl.getPodContainerDir(pod.UID, container.Name) if err := os.MkdirAll(p, 0750); err != nil { diff --git a/pkg/volume/secret/secret.go b/pkg/volume/secret/secret.go index 116d519bce..cbb7b39d75 100644 --- a/pkg/volume/secret/secret.go +++ b/pkg/volume/secret/secret.go @@ -18,6 +18,8 @@ package secret import ( "fmt" + "path/filepath" + "runtime" "github.com/golang/glog" "k8s.io/kubernetes/pkg/api" @@ -157,7 +159,12 @@ func (sv *secretVolume) GetAttributes() volume.Attributes { } } func (b *secretVolumeMounter) SetUp(fsGroup *int64) error { - return b.SetUpAt(b.GetPath(), fsGroup) + // Update each Slash "/" character for Windows with seperator character + dir := b.GetPath() + if runtime.GOOS == "windows" { + dir = filepath.FromSlash(dir) + } + return b.SetUpAt(dir, fsGroup) } func (b *secretVolumeMounter) SetUpAt(dir string, fsGroup *int64) error { @@ -269,7 +276,12 @@ type secretVolumeUnmounter struct { var _ volume.Unmounter = &secretVolumeUnmounter{} func (c *secretVolumeUnmounter) TearDown() error { - return c.TearDownAt(c.GetPath()) + // Update each Slash "/" character for Windows with seperator character + dir := c.GetPath() + if runtime.GOOS == "windows" { + dir = filepath.FromSlash(dir) + } + return c.TearDownAt(dir) } func (c *secretVolumeUnmounter) TearDownAt(dir string) error { diff --git a/pkg/volume/util/atomic_writer.go b/pkg/volume/util/atomic_writer.go index 8e2dad20ea..8b84710fc3 100644 --- a/pkg/volume/util/atomic_writer.go +++ b/pkg/volume/util/atomic_writer.go @@ -23,6 +23,7 @@ import ( "os" "path" "path/filepath" + "runtime" "strings" "time" @@ -183,7 +184,14 @@ func (w *AtomicWriter) Write(payload map[string]FileProjection) error { } // (9) - if err = os.Rename(newDataDirPath, dataDirPath); err != nil { + if runtime.GOOS == "windows" { + os.Remove(dataDirPath) + err = os.Symlink(tsDirName, dataDirPath) + os.Remove(newDataDirPath) + } else { + err = os.Rename(newDataDirPath, dataDirPath) + } + if err != nil { os.Remove(newDataDirPath) os.RemoveAll(tsDir) glog.Errorf("%s: error renaming symbolic link for data directory %s: %v", w.logContext, newDataDirPath, err) @@ -303,7 +311,11 @@ func (w *AtomicWriter) pathsToRemove(payload map[string]FileProjection) (sets.St } relativePath := strings.TrimPrefix(path, w.targetDir) - relativePath = strings.TrimPrefix(relativePath, "/") + if runtime.GOOS == "windows" { + relativePath = strings.TrimPrefix(relativePath, "\\") + } else { + relativePath = strings.TrimPrefix(relativePath, "/") + } if strings.HasPrefix(relativePath, "..") { return nil } diff --git a/pkg/volume/volume.go b/pkg/volume/volume.go index 09f4186931..4f4ccd62ff 100644 --- a/pkg/volume/volume.go +++ b/pkg/volume/volume.go @@ -20,10 +20,11 @@ import ( "io" "io/ioutil" "os" - "path" + filepath "path/filepath" "runtime" "time" + "github.com/golang/glog" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/types" @@ -211,7 +212,7 @@ func (err deletedVolumeInUseError) Error() string { } func RenameDirectory(oldPath, newName string) (string, error) { - newPath, err := ioutil.TempDir(path.Dir(oldPath), newName) + newPath, err := ioutil.TempDir(filepath.Dir(oldPath), newName) if err != nil { return "", err } @@ -221,6 +222,7 @@ func RenameDirectory(oldPath, newName string) (string, error) { if runtime.GOOS == "windows" { err = copyFolder(oldPath, newPath) if err != nil { + glog.Errorf("Error copying folder from: %s to: %s with error: %v", oldPath, newPath, err) return "", err } os.RemoveAll(oldPath) @@ -235,12 +237,31 @@ func RenameDirectory(oldPath, newName string) (string, error) { } func copyFolder(source string, dest string) (err error) { + fi, err := os.Lstat(source) + if err != nil { + glog.Errorf("Error getting stats for %s. %v", source, err) + return err + } + + err = os.MkdirAll(dest, fi.Mode()) + if err != nil { + glog.Errorf("Unable to create %s directory %v", dest, err) + } + directory, _ := os.Open(source) + + defer directory.Close() + objects, err := directory.Readdir(-1) for _, obj := range objects { - sourcefilepointer := source + "/" + obj.Name() - destinationfilepointer := dest + "/" + obj.Name() + if obj.Mode()&os.ModeSymlink != 0 { + continue + } + + sourcefilepointer := source + "\\" + obj.Name() + destinationfilepointer := dest + "\\" + obj.Name() + if obj.IsDir() { err = copyFolder(sourcefilepointer, destinationfilepointer) if err != nil {