github.com/adityamillind98/moby@v23.0.0-rc.4+incompatible/daemon/daemon_windows.go (about) 1 package daemon // import "github.com/docker/docker/daemon" 2 3 import ( 4 "context" 5 "fmt" 6 "math" 7 "path/filepath" 8 "runtime" 9 "strings" 10 11 "github.com/Microsoft/hcsshim" 12 "github.com/Microsoft/hcsshim/osversion" 13 "github.com/docker/docker/api/types" 14 containertypes "github.com/docker/docker/api/types/container" 15 "github.com/docker/docker/container" 16 "github.com/docker/docker/daemon/config" 17 "github.com/docker/docker/libcontainerd/local" 18 "github.com/docker/docker/libcontainerd/remote" 19 "github.com/docker/docker/libnetwork" 20 nwconfig "github.com/docker/docker/libnetwork/config" 21 "github.com/docker/docker/libnetwork/datastore" 22 winlibnetwork "github.com/docker/docker/libnetwork/drivers/windows" 23 "github.com/docker/docker/libnetwork/netlabel" 24 "github.com/docker/docker/libnetwork/options" 25 "github.com/docker/docker/pkg/containerfs" 26 "github.com/docker/docker/pkg/idtools" 27 "github.com/docker/docker/pkg/parsers" 28 "github.com/docker/docker/pkg/parsers/operatingsystem" 29 "github.com/docker/docker/pkg/platform" 30 "github.com/docker/docker/pkg/sysinfo" 31 "github.com/docker/docker/pkg/system" 32 "github.com/docker/docker/runconfig" 33 "github.com/pkg/errors" 34 "github.com/sirupsen/logrus" 35 "golang.org/x/sys/windows" 36 "golang.org/x/sys/windows/svc/mgr" 37 ) 38 39 const ( 40 isWindows = true 41 platformSupported = true 42 windowsMinCPUShares = 1 43 windowsMaxCPUShares = 10000 44 windowsMinCPUPercent = 1 45 windowsMaxCPUPercent = 100 46 47 windowsV1RuntimeName = "com.docker.hcsshim.v1" 48 windowsV2RuntimeName = "io.containerd.runhcs.v1" 49 ) 50 51 // Windows containers are much larger than Linux containers and each of them 52 // have > 20 system processes which why we use much smaller parallelism value. 53 func adjustParallelLimit(n int, limit int) int { 54 return int(math.Max(1, math.Floor(float64(runtime.NumCPU())*.8))) 55 } 56 57 // Windows has no concept of an execution state directory. So use config.Root here. 58 func getPluginExecRoot(root string) string { 59 return filepath.Join(root, "plugins") 60 } 61 62 func (daemon *Daemon) parseSecurityOpt(container *container.Container, hostConfig *containertypes.HostConfig) error { 63 return nil 64 } 65 66 func setupInitLayer(idMapping idtools.IdentityMapping) func(containerfs.ContainerFS) error { 67 return nil 68 } 69 70 // adaptContainerSettings is called during container creation to modify any 71 // settings necessary in the HostConfig structure. 72 func (daemon *Daemon) adaptContainerSettings(hostConfig *containertypes.HostConfig, adjustCPUShares bool) error { 73 return nil 74 } 75 76 // verifyPlatformContainerResources performs platform-specific validation of the container's resource-configuration 77 func verifyPlatformContainerResources(resources *containertypes.Resources, isHyperv bool) (warnings []string, err error) { 78 fixMemorySwappiness(resources) 79 if !isHyperv { 80 // The processor resource controls are mutually exclusive on 81 // Windows Server Containers, the order of precedence is 82 // CPUCount first, then CPUShares, and CPUPercent last. 83 if resources.CPUCount > 0 { 84 if resources.CPUShares > 0 { 85 warnings = append(warnings, "Conflicting options: CPU count takes priority over CPU shares on Windows Server Containers. CPU shares discarded") 86 resources.CPUShares = 0 87 } 88 if resources.CPUPercent > 0 { 89 warnings = append(warnings, "Conflicting options: CPU count takes priority over CPU percent on Windows Server Containers. CPU percent discarded") 90 resources.CPUPercent = 0 91 } 92 } else if resources.CPUShares > 0 { 93 if resources.CPUPercent > 0 { 94 warnings = append(warnings, "Conflicting options: CPU shares takes priority over CPU percent on Windows Server Containers. CPU percent discarded") 95 resources.CPUPercent = 0 96 } 97 } 98 } 99 100 if resources.CPUShares < 0 || resources.CPUShares > windowsMaxCPUShares { 101 return warnings, fmt.Errorf("range of CPUShares is from %d to %d", windowsMinCPUShares, windowsMaxCPUShares) 102 } 103 if resources.CPUPercent < 0 || resources.CPUPercent > windowsMaxCPUPercent { 104 return warnings, fmt.Errorf("range of CPUPercent is from %d to %d", windowsMinCPUPercent, windowsMaxCPUPercent) 105 } 106 if resources.CPUCount < 0 { 107 return warnings, fmt.Errorf("invalid CPUCount: CPUCount cannot be negative") 108 } 109 110 if resources.NanoCPUs > 0 && resources.CPUPercent > 0 { 111 return warnings, fmt.Errorf("conflicting options: Nano CPUs and CPU Percent cannot both be set") 112 } 113 if resources.NanoCPUs > 0 && resources.CPUShares > 0 { 114 return warnings, fmt.Errorf("conflicting options: Nano CPUs and CPU Shares cannot both be set") 115 } 116 // The precision we could get is 0.01, because on Windows we have to convert to CPUPercent. 117 // We don't set the lower limit here and it is up to the underlying platform (e.g., Windows) to return an error. 118 if resources.NanoCPUs < 0 || resources.NanoCPUs > int64(sysinfo.NumCPU())*1e9 { 119 return warnings, fmt.Errorf("range of CPUs is from 0.01 to %d.00, as there are only %d CPUs available", sysinfo.NumCPU(), sysinfo.NumCPU()) 120 } 121 122 if len(resources.BlkioDeviceReadBps) > 0 { 123 return warnings, fmt.Errorf("invalid option: Windows does not support BlkioDeviceReadBps") 124 } 125 if len(resources.BlkioDeviceReadIOps) > 0 { 126 return warnings, fmt.Errorf("invalid option: Windows does not support BlkioDeviceReadIOps") 127 } 128 if len(resources.BlkioDeviceWriteBps) > 0 { 129 return warnings, fmt.Errorf("invalid option: Windows does not support BlkioDeviceWriteBps") 130 } 131 if len(resources.BlkioDeviceWriteIOps) > 0 { 132 return warnings, fmt.Errorf("invalid option: Windows does not support BlkioDeviceWriteIOps") 133 } 134 if resources.BlkioWeight > 0 { 135 return warnings, fmt.Errorf("invalid option: Windows does not support BlkioWeight") 136 } 137 if len(resources.BlkioWeightDevice) > 0 { 138 return warnings, fmt.Errorf("invalid option: Windows does not support BlkioWeightDevice") 139 } 140 if resources.CgroupParent != "" { 141 return warnings, fmt.Errorf("invalid option: Windows does not support CgroupParent") 142 } 143 if resources.CPUPeriod != 0 { 144 return warnings, fmt.Errorf("invalid option: Windows does not support CPUPeriod") 145 } 146 if resources.CpusetCpus != "" { 147 return warnings, fmt.Errorf("invalid option: Windows does not support CpusetCpus") 148 } 149 if resources.CpusetMems != "" { 150 return warnings, fmt.Errorf("invalid option: Windows does not support CpusetMems") 151 } 152 if resources.KernelMemory != 0 { 153 return warnings, fmt.Errorf("invalid option: Windows does not support KernelMemory") 154 } 155 if resources.MemoryReservation != 0 { 156 return warnings, fmt.Errorf("invalid option: Windows does not support MemoryReservation") 157 } 158 if resources.MemorySwap != 0 { 159 return warnings, fmt.Errorf("invalid option: Windows does not support MemorySwap") 160 } 161 if resources.MemorySwappiness != nil { 162 return warnings, fmt.Errorf("invalid option: Windows does not support MemorySwappiness") 163 } 164 if resources.OomKillDisable != nil && *resources.OomKillDisable { 165 return warnings, fmt.Errorf("invalid option: Windows does not support OomKillDisable") 166 } 167 if resources.PidsLimit != nil && *resources.PidsLimit != 0 { 168 return warnings, fmt.Errorf("invalid option: Windows does not support PidsLimit") 169 } 170 if len(resources.Ulimits) != 0 { 171 return warnings, fmt.Errorf("invalid option: Windows does not support Ulimits") 172 } 173 return warnings, nil 174 } 175 176 // verifyPlatformContainerSettings performs platform-specific validation of the 177 // hostconfig and config structures. 178 func verifyPlatformContainerSettings(daemon *Daemon, hostConfig *containertypes.HostConfig, update bool) (warnings []string, err error) { 179 if hostConfig == nil { 180 return nil, nil 181 } 182 return verifyPlatformContainerResources(&hostConfig.Resources, daemon.runAsHyperVContainer(hostConfig)) 183 } 184 185 // verifyDaemonSettings performs validation of daemon config struct 186 func verifyDaemonSettings(config *config.Config) error { 187 return nil 188 } 189 190 // checkSystem validates platform-specific requirements 191 func checkSystem() error { 192 // Validate the OS version. Note that dockerd.exe must be manifested for this 193 // call to return the correct version. 194 if osversion.Get().MajorVersion < 10 || osversion.Build() < osversion.RS5 { 195 return fmt.Errorf("this version of Windows does not support the docker daemon (Windows build %d or higher is required)", osversion.RS5) 196 } 197 198 vmcompute := windows.NewLazySystemDLL("vmcompute.dll") 199 if vmcompute.Load() != nil { 200 return fmt.Errorf("failed to load vmcompute.dll, ensure that the Containers feature is installed") 201 } 202 203 // Ensure that the required Host Network Service and vmcompute services 204 // are running. Docker will fail in unexpected ways if this is not present. 205 var requiredServices = []string{"hns", "vmcompute"} 206 if err := ensureServicesInstalled(requiredServices); err != nil { 207 return errors.Wrap(err, "a required service is not installed, ensure the Containers feature is installed") 208 } 209 210 return nil 211 } 212 213 func ensureServicesInstalled(services []string) error { 214 m, err := mgr.Connect() 215 if err != nil { 216 return err 217 } 218 defer m.Disconnect() 219 for _, service := range services { 220 s, err := m.OpenService(service) 221 if err != nil { 222 return errors.Wrapf(err, "failed to open service %s", service) 223 } 224 s.Close() 225 } 226 return nil 227 } 228 229 // configureKernelSecuritySupport configures and validate security support for the kernel 230 func configureKernelSecuritySupport(config *config.Config, driverName string) error { 231 return nil 232 } 233 234 // configureMaxThreads sets the Go runtime max threads threshold 235 func configureMaxThreads(config *config.Config) error { 236 return nil 237 } 238 239 func (daemon *Daemon) initNetworkController(activeSandboxes map[string]interface{}) error { 240 netOptions, err := daemon.networkOptions(nil, nil) 241 if err != nil { 242 return err 243 } 244 daemon.netController, err = libnetwork.New(netOptions...) 245 if err != nil { 246 return errors.Wrap(err, "error obtaining controller instance") 247 } 248 249 hnsresponse, err := hcsshim.HNSListNetworkRequest("GET", "", "") 250 if err != nil { 251 return err 252 } 253 254 // Remove networks not present in HNS 255 for _, v := range daemon.netController.Networks() { 256 hnsid := v.Info().DriverOptions()[winlibnetwork.HNSID] 257 found := false 258 259 for _, v := range hnsresponse { 260 if v.Id == hnsid { 261 found = true 262 break 263 } 264 } 265 266 if !found { 267 // non-default nat networks should be re-created if missing from HNS 268 if v.Type() == "nat" && v.Name() != "nat" { 269 _, _, v4Conf, v6Conf := v.Info().IpamConfig() 270 netOption := map[string]string{} 271 for k, v := range v.Info().DriverOptions() { 272 if k != winlibnetwork.NetworkName && k != winlibnetwork.HNSID { 273 netOption[k] = v 274 } 275 } 276 name := v.Name() 277 id := v.ID() 278 279 err = v.Delete() 280 if err != nil { 281 logrus.Errorf("Error occurred when removing network %v", err) 282 } 283 284 _, err := daemon.netController.NewNetwork("nat", name, id, 285 libnetwork.NetworkOptionGeneric(options.Generic{ 286 netlabel.GenericData: netOption, 287 }), 288 libnetwork.NetworkOptionIpam("default", "", v4Conf, v6Conf, nil), 289 ) 290 if err != nil { 291 logrus.Errorf("Error occurred when creating network %v", err) 292 } 293 continue 294 } 295 296 // global networks should not be deleted by local HNS 297 if v.Info().Scope() != datastore.GlobalScope { 298 err = v.Delete() 299 if err != nil { 300 logrus.Errorf("Error occurred when removing network %v", err) 301 } 302 } 303 } 304 } 305 306 _, err = daemon.netController.NewNetwork("null", "none", "", libnetwork.NetworkOptionPersist(false)) 307 if err != nil { 308 return err 309 } 310 311 defaultNetworkExists := false 312 313 if network, err := daemon.netController.NetworkByName(runconfig.DefaultDaemonNetworkMode().NetworkName()); err == nil { 314 hnsid := network.Info().DriverOptions()[winlibnetwork.HNSID] 315 for _, v := range hnsresponse { 316 if hnsid == v.Id { 317 defaultNetworkExists = true 318 break 319 } 320 } 321 } 322 323 // discover and add HNS networks to windows 324 // network that exist are removed and added again 325 for _, v := range hnsresponse { 326 networkTypeNorm := strings.ToLower(v.Type) 327 if networkTypeNorm == "private" || networkTypeNorm == "internal" { 328 continue // workaround for HNS reporting unsupported networks 329 } 330 var n libnetwork.Network 331 s := func(current libnetwork.Network) bool { 332 hnsid := current.Info().DriverOptions()[winlibnetwork.HNSID] 333 if hnsid == v.Id { 334 n = current 335 return true 336 } 337 return false 338 } 339 340 daemon.netController.WalkNetworks(s) 341 342 drvOptions := make(map[string]string) 343 nid := "" 344 if n != nil { 345 nid = n.ID() 346 347 // global networks should not be deleted by local HNS 348 if n.Info().Scope() == datastore.GlobalScope { 349 continue 350 } 351 v.Name = n.Name() 352 // This will not cause network delete from HNS as the network 353 // is not yet populated in the libnetwork windows driver 354 355 // restore option if it existed before 356 drvOptions = n.Info().DriverOptions() 357 n.Delete() 358 } 359 netOption := map[string]string{ 360 winlibnetwork.NetworkName: v.Name, 361 winlibnetwork.HNSID: v.Id, 362 } 363 364 // add persisted driver options 365 for k, v := range drvOptions { 366 if k != winlibnetwork.NetworkName && k != winlibnetwork.HNSID { 367 netOption[k] = v 368 } 369 } 370 371 v4Conf := []*libnetwork.IpamConf{} 372 for _, subnet := range v.Subnets { 373 ipamV4Conf := libnetwork.IpamConf{} 374 ipamV4Conf.PreferredPool = subnet.AddressPrefix 375 ipamV4Conf.Gateway = subnet.GatewayAddress 376 v4Conf = append(v4Conf, &ipamV4Conf) 377 } 378 379 name := v.Name 380 381 // If there is no nat network create one from the first NAT network 382 // encountered if it doesn't already exist 383 if !defaultNetworkExists && 384 runconfig.DefaultDaemonNetworkMode() == containertypes.NetworkMode(strings.ToLower(v.Type)) && 385 n == nil { 386 name = runconfig.DefaultDaemonNetworkMode().NetworkName() 387 defaultNetworkExists = true 388 } 389 390 v6Conf := []*libnetwork.IpamConf{} 391 _, err := daemon.netController.NewNetwork(strings.ToLower(v.Type), name, nid, 392 libnetwork.NetworkOptionGeneric(options.Generic{ 393 netlabel.GenericData: netOption, 394 }), 395 libnetwork.NetworkOptionIpam("default", "", v4Conf, v6Conf, nil), 396 ) 397 398 if err != nil { 399 logrus.Errorf("Error occurred when creating network %v", err) 400 } 401 } 402 403 if !daemon.configStore.DisableBridge { 404 // Initialize default driver "bridge" 405 if err := initBridgeDriver(daemon.netController, daemon.configStore); err != nil { 406 return err 407 } 408 } 409 410 return nil 411 } 412 413 func initBridgeDriver(controller libnetwork.NetworkController, config *config.Config) error { 414 if _, err := controller.NetworkByName(runconfig.DefaultDaemonNetworkMode().NetworkName()); err == nil { 415 return nil 416 } 417 418 netOption := map[string]string{ 419 winlibnetwork.NetworkName: runconfig.DefaultDaemonNetworkMode().NetworkName(), 420 } 421 422 var ipamOption libnetwork.NetworkOption 423 var subnetPrefix string 424 425 if config.BridgeConfig.FixedCIDR != "" { 426 subnetPrefix = config.BridgeConfig.FixedCIDR 427 } 428 429 if subnetPrefix != "" { 430 ipamV4Conf := libnetwork.IpamConf{PreferredPool: subnetPrefix} 431 v4Conf := []*libnetwork.IpamConf{&ipamV4Conf} 432 v6Conf := []*libnetwork.IpamConf{} 433 ipamOption = libnetwork.NetworkOptionIpam("default", "", v4Conf, v6Conf, nil) 434 } 435 436 _, err := controller.NewNetwork(string(runconfig.DefaultDaemonNetworkMode()), runconfig.DefaultDaemonNetworkMode().NetworkName(), "", 437 libnetwork.NetworkOptionGeneric(options.Generic{ 438 netlabel.GenericData: netOption, 439 }), 440 ipamOption, 441 ) 442 if err != nil { 443 return errors.Wrap(err, "error creating default network") 444 } 445 446 return nil 447 } 448 449 // registerLinks sets up links between containers and writes the 450 // configuration out for persistence. As of Windows TP4, links are not supported. 451 func (daemon *Daemon) registerLinks(container *container.Container, hostConfig *containertypes.HostConfig) error { 452 return nil 453 } 454 455 func (daemon *Daemon) cleanupMountsByID(in string) error { 456 return nil 457 } 458 459 func (daemon *Daemon) cleanupMounts() error { 460 return nil 461 } 462 463 func recursiveUnmount(_ string) error { 464 return nil 465 } 466 467 func setupRemappedRoot(config *config.Config) (idtools.IdentityMapping, error) { 468 return idtools.IdentityMapping{}, nil 469 } 470 471 func setupDaemonRoot(config *config.Config, rootDir string, rootIdentity idtools.Identity) error { 472 config.Root = rootDir 473 // Create the root directory if it doesn't exists 474 if err := system.MkdirAllWithACL(config.Root, 0, system.SddlAdministratorsLocalSystem); err != nil { 475 return err 476 } 477 return nil 478 } 479 480 // runasHyperVContainer returns true if we are going to run as a Hyper-V container 481 func (daemon *Daemon) runAsHyperVContainer(hostConfig *containertypes.HostConfig) bool { 482 if hostConfig.Isolation.IsDefault() { 483 // Container is set to use the default, so take the default from the daemon configuration 484 return daemon.defaultIsolation.IsHyperV() 485 } 486 487 // Container is requesting an isolation mode. Honour it. 488 return hostConfig.Isolation.IsHyperV() 489 490 } 491 492 // conditionalMountOnStart is a platform specific helper function during the 493 // container start to call mount. 494 func (daemon *Daemon) conditionalMountOnStart(container *container.Container) error { 495 if daemon.runAsHyperVContainer(container.HostConfig) { 496 // We do not mount if a Hyper-V container as it needs to be mounted inside the 497 // utility VM, not the host. 498 return nil 499 } 500 return daemon.Mount(container) 501 } 502 503 // conditionalUnmountOnCleanup is a platform specific helper function called 504 // during the cleanup of a container to unmount. 505 func (daemon *Daemon) conditionalUnmountOnCleanup(container *container.Container) error { 506 if daemon.runAsHyperVContainer(container.HostConfig) { 507 // We do not unmount if a Hyper-V container 508 return nil 509 } 510 return daemon.Unmount(container) 511 } 512 513 func driverOptions(_ *config.Config) nwconfig.Option { 514 return nil 515 } 516 517 func (daemon *Daemon) stats(c *container.Container) (*types.StatsJSON, error) { 518 if !c.IsRunning() { 519 return nil, errNotRunning(c.ID) 520 } 521 522 // Obtain the stats from HCS via libcontainerd 523 stats, err := daemon.containerd.Stats(context.Background(), c.ID) 524 if err != nil { 525 if strings.Contains(err.Error(), "container not found") { 526 return nil, containerNotFound(c.ID) 527 } 528 return nil, err 529 } 530 531 // Start with an empty structure 532 s := &types.StatsJSON{} 533 s.Stats.Read = stats.Read 534 s.Stats.NumProcs = platform.NumProcs() 535 536 if stats.HCSStats != nil { 537 hcss := stats.HCSStats 538 // Populate the CPU/processor statistics 539 s.CPUStats = types.CPUStats{ 540 CPUUsage: types.CPUUsage{ 541 TotalUsage: hcss.Processor.TotalRuntime100ns, 542 UsageInKernelmode: hcss.Processor.RuntimeKernel100ns, 543 UsageInUsermode: hcss.Processor.RuntimeUser100ns, 544 }, 545 } 546 547 // Populate the memory statistics 548 s.MemoryStats = types.MemoryStats{ 549 Commit: hcss.Memory.UsageCommitBytes, 550 CommitPeak: hcss.Memory.UsageCommitPeakBytes, 551 PrivateWorkingSet: hcss.Memory.UsagePrivateWorkingSetBytes, 552 } 553 554 // Populate the storage statistics 555 s.StorageStats = types.StorageStats{ 556 ReadCountNormalized: hcss.Storage.ReadCountNormalized, 557 ReadSizeBytes: hcss.Storage.ReadSizeBytes, 558 WriteCountNormalized: hcss.Storage.WriteCountNormalized, 559 WriteSizeBytes: hcss.Storage.WriteSizeBytes, 560 } 561 562 // Populate the network statistics 563 s.Networks = make(map[string]types.NetworkStats) 564 for _, nstats := range hcss.Network { 565 s.Networks[nstats.EndpointId] = types.NetworkStats{ 566 RxBytes: nstats.BytesReceived, 567 RxPackets: nstats.PacketsReceived, 568 RxDropped: nstats.DroppedPacketsIncoming, 569 TxBytes: nstats.BytesSent, 570 TxPackets: nstats.PacketsSent, 571 TxDropped: nstats.DroppedPacketsOutgoing, 572 } 573 } 574 } 575 return s, nil 576 } 577 578 // setDefaultIsolation determine the default isolation mode for the 579 // daemon to run in. This is only applicable on Windows 580 func (daemon *Daemon) setDefaultIsolation() error { 581 // On client SKUs, default to Hyper-V. @engine maintainers. This 582 // should not be removed. Ping Microsoft folks is there are PRs to 583 // to change this. 584 if operatingsystem.IsWindowsClient() { 585 daemon.defaultIsolation = containertypes.IsolationHyperV 586 } else { 587 daemon.defaultIsolation = containertypes.IsolationProcess 588 } 589 for _, option := range daemon.configStore.ExecOptions { 590 key, val, err := parsers.ParseKeyValueOpt(option) 591 if err != nil { 592 return err 593 } 594 key = strings.ToLower(key) 595 switch key { 596 597 case "isolation": 598 if !containertypes.Isolation(val).IsValid() { 599 return fmt.Errorf("Invalid exec-opt value for 'isolation':'%s'", val) 600 } 601 if containertypes.Isolation(val).IsHyperV() { 602 daemon.defaultIsolation = containertypes.IsolationHyperV 603 } 604 if containertypes.Isolation(val).IsProcess() { 605 daemon.defaultIsolation = containertypes.IsolationProcess 606 } 607 default: 608 return fmt.Errorf("Unrecognised exec-opt '%s'\n", key) 609 } 610 } 611 612 logrus.Infof("Windows default isolation mode: %s", daemon.defaultIsolation) 613 return nil 614 } 615 616 func setupDaemonProcess(config *config.Config) error { 617 return nil 618 } 619 620 func (daemon *Daemon) setupSeccompProfile() error { 621 return nil 622 } 623 624 func (daemon *Daemon) loadRuntimes() error { 625 return nil 626 } 627 628 func setupResolvConf(config *config.Config) {} 629 630 func getSysInfo(daemon *Daemon) *sysinfo.SysInfo { 631 return sysinfo.New() 632 } 633 634 func (daemon *Daemon) initLibcontainerd(ctx context.Context) error { 635 var err error 636 637 rt := daemon.configStore.GetDefaultRuntimeName() 638 if rt == "" { 639 if daemon.configStore.ContainerdAddr == "" { 640 rt = windowsV1RuntimeName 641 } else { 642 rt = windowsV2RuntimeName 643 } 644 } 645 646 switch rt { 647 case windowsV1RuntimeName: 648 daemon.containerd, err = local.NewClient( 649 ctx, 650 daemon.containerdCli, 651 filepath.Join(daemon.configStore.ExecRoot, "containerd"), 652 daemon.configStore.ContainerdNamespace, 653 daemon, 654 ) 655 case windowsV2RuntimeName: 656 if daemon.configStore.ContainerdAddr == "" { 657 return fmt.Errorf("cannot use the specified runtime %q without containerd", rt) 658 } 659 daemon.containerd, err = remote.NewClient( 660 ctx, 661 daemon.containerdCli, 662 filepath.Join(daemon.configStore.ExecRoot, "containerd"), 663 daemon.configStore.ContainerdNamespace, 664 daemon, 665 ) 666 default: 667 return fmt.Errorf("unknown windows runtime %s", rt) 668 } 669 670 return err 671 }