1// +build windows 2 3/* 4 Copyright The containerd Authors. 5 6 Licensed under the Apache License, Version 2.0 (the "License"); 7 you may not use this file except in compliance with the License. 8 You may obtain a copy of the License at 9 10 http://www.apache.org/licenses/LICENSE-2.0 11 12 Unless required by applicable law or agreed to in writing, software 13 distributed under the License is distributed on an "AS IS" BASIS, 14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 See the License for the specific language governing permissions and 16 limitations under the License. 17*/ 18 19package opts 20 21import ( 22 "context" 23 "path/filepath" 24 "sort" 25 "strings" 26 27 "github.com/containerd/containerd/containers" 28 "github.com/containerd/containerd/oci" 29 runtimespec "github.com/opencontainers/runtime-spec/specs-go" 30 "github.com/pkg/errors" 31 runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2" 32 33 osinterface "github.com/containerd/containerd/pkg/os" 34) 35 36// WithWindowsNetworkNamespace sets windows network namespace for container. 37// TODO(windows): Move this into container/containerd. 38func WithWindowsNetworkNamespace(path string) oci.SpecOpts { 39 return func(ctx context.Context, client oci.Client, c *containers.Container, s *runtimespec.Spec) error { 40 if s.Windows == nil { 41 s.Windows = &runtimespec.Windows{} 42 } 43 if s.Windows.Network == nil { 44 s.Windows.Network = &runtimespec.WindowsNetwork{} 45 } 46 s.Windows.Network.NetworkNamespace = path 47 return nil 48 } 49} 50 51// namedPipePath returns true if the given path is to a named pipe. 52func namedPipePath(p string) bool { 53 return strings.HasPrefix(p, `\\.\pipe\`) 54} 55 56// cleanMount returns a cleaned version of the mount path. The input is returned 57// as-is if it is a named pipe path. 58func cleanMount(p string) string { 59 if namedPipePath(p) { 60 return p 61 } 62 return filepath.Clean(p) 63} 64 65// WithWindowsMounts sorts and adds runtime and CRI mounts to the spec for 66// windows container. 67func WithWindowsMounts(osi osinterface.OS, config *runtime.ContainerConfig, extra []*runtime.Mount) oci.SpecOpts { 68 return func(ctx context.Context, client oci.Client, _ *containers.Container, s *runtimespec.Spec) error { 69 // mergeMounts merge CRI mounts with extra mounts. If a mount destination 70 // is mounted by both a CRI mount and an extra mount, the CRI mount will 71 // be kept. 72 var ( 73 criMounts = config.GetMounts() 74 mounts = append([]*runtime.Mount{}, criMounts...) 75 ) 76 // Copy all mounts from extra mounts, except for mounts overridden by CRI. 77 for _, e := range extra { 78 found := false 79 for _, c := range criMounts { 80 if cleanMount(e.ContainerPath) == cleanMount(c.ContainerPath) { 81 found = true 82 break 83 } 84 } 85 if !found { 86 mounts = append(mounts, e) 87 } 88 } 89 90 // Sort mounts in number of parts. This ensures that high level mounts don't 91 // shadow other mounts. 92 sort.Sort(orderedMounts(mounts)) 93 94 // Copy all mounts from default mounts, except for 95 // mounts overridden by supplied mount; 96 mountSet := make(map[string]struct{}) 97 for _, m := range mounts { 98 mountSet[cleanMount(m.ContainerPath)] = struct{}{} 99 } 100 101 defaultMounts := s.Mounts 102 s.Mounts = nil 103 104 for _, m := range defaultMounts { 105 dst := cleanMount(m.Destination) 106 if _, ok := mountSet[dst]; ok { 107 // filter out mount overridden by a supplied mount 108 continue 109 } 110 s.Mounts = append(s.Mounts, m) 111 } 112 113 for _, mount := range mounts { 114 var ( 115 dst = mount.GetContainerPath() 116 src = mount.GetHostPath() 117 ) 118 // In the case of a named pipe mount on Windows, don't stat the file 119 // or do other operations that open it, as that could interfere with 120 // the listening process. filepath.Clean also breaks named pipe 121 // paths, so don't use it. 122 if !namedPipePath(src) { 123 if _, err := osi.Stat(src); err != nil { 124 // If the source doesn't exist, return an error instead 125 // of creating the source. This aligns with Docker's 126 // behavior on windows. 127 return errors.Wrapf(err, "failed to stat %q", src) 128 } 129 var err error 130 src, err = osi.ResolveSymbolicLink(src) 131 if err != nil { 132 return errors.Wrapf(err, "failed to resolve symlink %q", src) 133 } 134 // hcsshim requires clean path, especially '/' -> '\'. 135 src = filepath.Clean(src) 136 dst = filepath.Clean(dst) 137 } 138 139 var options []string 140 // NOTE(random-liu): we don't change all mounts to `ro` when root filesystem 141 // is readonly. This is different from docker's behavior, but make more sense. 142 if mount.GetReadonly() { 143 options = append(options, "ro") 144 } else { 145 options = append(options, "rw") 146 } 147 s.Mounts = append(s.Mounts, runtimespec.Mount{ 148 Source: src, 149 Destination: dst, 150 Options: options, 151 }) 152 } 153 return nil 154 } 155} 156 157// WithWindowsResources sets the provided resource restrictions for windows. 158func WithWindowsResources(resources *runtime.WindowsContainerResources) oci.SpecOpts { 159 return func(ctx context.Context, client oci.Client, c *containers.Container, s *runtimespec.Spec) error { 160 if resources == nil { 161 return nil 162 } 163 if s.Windows == nil { 164 s.Windows = &runtimespec.Windows{} 165 } 166 if s.Windows.Resources == nil { 167 s.Windows.Resources = &runtimespec.WindowsResources{} 168 } 169 if s.Windows.Resources.CPU == nil { 170 s.Windows.Resources.CPU = &runtimespec.WindowsCPUResources{} 171 } 172 if s.Windows.Resources.Memory == nil { 173 s.Windows.Resources.Memory = &runtimespec.WindowsMemoryResources{} 174 } 175 176 var ( 177 count = uint64(resources.GetCpuCount()) 178 shares = uint16(resources.GetCpuShares()) 179 max = uint16(resources.GetCpuMaximum()) 180 limit = uint64(resources.GetMemoryLimitInBytes()) 181 ) 182 if count != 0 { 183 s.Windows.Resources.CPU.Count = &count 184 } 185 if shares != 0 { 186 s.Windows.Resources.CPU.Shares = &shares 187 } 188 if max != 0 { 189 s.Windows.Resources.CPU.Maximum = &max 190 } 191 if limit != 0 { 192 s.Windows.Resources.Memory.Limit = &limit 193 } 194 return nil 195 } 196} 197 198// WithWindowsDefaultSandboxShares sets the default sandbox CPU shares 199func WithWindowsDefaultSandboxShares(ctx context.Context, client oci.Client, c *containers.Container, s *runtimespec.Spec) error { 200 if s.Windows == nil { 201 s.Windows = &runtimespec.Windows{} 202 } 203 if s.Windows.Resources == nil { 204 s.Windows.Resources = &runtimespec.WindowsResources{} 205 } 206 if s.Windows.Resources.CPU == nil { 207 s.Windows.Resources.CPU = &runtimespec.WindowsCPUResources{} 208 } 209 i := uint16(DefaultSandboxCPUshares) 210 s.Windows.Resources.CPU.Shares = &i 211 return nil 212} 213 214// WithWindowsCredentialSpec assigns `credentialSpec` to the 215// `runtime.Spec.Windows.CredentialSpec` field. 216func WithWindowsCredentialSpec(credentialSpec string) oci.SpecOpts { 217 return func(ctx context.Context, client oci.Client, c *containers.Container, s *runtimespec.Spec) error { 218 if s.Windows == nil { 219 s.Windows = &runtimespec.Windows{} 220 } 221 s.Windows.CredentialSpec = credentialSpec 222 return nil 223 } 224} 225