// DiffGetter returns a FileGetCloser that can read files from the directory that // contains files for the layer differences. Used for direct access for tar-split. func (d *Driver) DiffGetter(id string) (graphdriver.FileGetCloser, error) { id, err := d.resolveID(id) if err != nil { return nil, err } if hcsshim.IsTP4() { // The export format for TP4 is different from the contents of the layer, so // fall back to exporting the layer and getting file contents from there. layerChain, err := d.getLayerChain(id) if err != nil { return nil, err } var tempFolder string tempFolder, err = ioutil.TempDir("", "hcs") if err != nil { return nil, err } defer func() { if err != nil { os.RemoveAll(tempFolder) } }() if err = hcsshim.ExportLayer(d.info, id, tempFolder, layerChain); err != nil { return nil, err } return &fileGetDestroyCloser{storage.NewPathFileGetter(tempFolder), tempFolder}, nil } return &fileGetCloserWithBackupPrivileges{d.dir(id)}, nil }
// NewDriver returns a new windows driver, called from NewDriver of execdriver. func NewDriver(root string, options []string) (*Driver, error) { for _, option := range options { key, val, err := parsers.ParseKeyValueOpt(option) if err != nil { return nil, err } key = strings.ToLower(key) switch key { case "dummy": switch val { case "1": dummyMode = true logrus.Warn("Using dummy mode in Windows exec driver. This is for development use only!") } case "forcekill": switch val { case "1": forceKill = true logrus.Warn("Using force kill mode in Windows exec driver. This is for testing purposes only.") } case "isolation": if !container.Isolation(val).IsValid() { return nil, fmt.Errorf("Unrecognised exec driver option 'isolation':'%s'", val) } if container.Isolation(val).IsHyperV() { DefaultIsolation = "hyperv" } logrus.Infof("Windows default isolation: '%s'", val) default: return nil, fmt.Errorf("Unrecognised exec driver option %s\n", key) } } // TODO Windows TP5 timeframe. Remove this next block of code once TP4 // is no longer supported. Also remove the workaround in run.go. // // Hack for TP4. // This overcomes an issue on TP4 which causes CreateComputeSystem to // intermittently fail. It's predominantly here to make Windows to Windows // CI more reliable. TP4RetryHack = hcsshim.IsTP4() return &Driver{ root: root, activeContainers: make(map[string]*activeContainer), }, nil }
// exportLayer generates an archive from a layer based on the given ID. func (d *Driver) exportLayer(id string, parentLayerPaths []string) (archive.Archive, error) { if hcsshim.IsTP4() { // Export in TP4 format to maintain compatibility with existing images and // because ExportLayer is somewhat broken on TP4 and can't work with the new // scheme. tempFolder, err := ioutil.TempDir("", "hcs") if err != nil { return nil, err } defer func() { if err != nil { os.RemoveAll(tempFolder) } }() if err = hcsshim.ExportLayer(d.info, id, tempFolder, parentLayerPaths); err != nil { return nil, err } archive, err := archive.Tar(tempFolder, archive.Uncompressed) if err != nil { return nil, err } return ioutils.NewReadCloserWrapper(archive, func() error { err := archive.Close() os.RemoveAll(tempFolder) return err }), nil } var r hcsshim.LayerReader r, err := hcsshim.NewLayerReader(d.info, id, parentLayerPaths) if err != nil { return nil, err } archive, w := io.Pipe() go func() { err := writeTarFromLayer(r, w) cerr := r.Close() if err == nil { err = cerr } w.CloseWithError(err) }() return archive, nil }
// importLayer adds a new layer to the tag and graph store based on the given data. func (d *Driver) importLayer(id string, layerData archive.Reader, parentLayerPaths []string) (size int64, err error) { if hcsshim.IsTP4() { // Import from TP4 format to maintain compatibility with existing images. var tempFolder string tempFolder, err = ioutil.TempDir("", "hcs") if err != nil { return } defer os.RemoveAll(tempFolder) if size, err = chrootarchive.ApplyLayer(tempFolder, layerData); err != nil { return } if err = hcsshim.ImportLayer(d.info, id, tempFolder, parentLayerPaths); err != nil { return } return } var w hcsshim.LayerWriter w, err = hcsshim.NewLayerWriter(d.info, id, parentLayerPaths) if err != nil { return } size, err = writeLayerFromTar(layerData, w) if err != nil { w.Close() return } err = w.Close() if err != nil { return } return }
// Create is the entrypoint to create a container from a spec, and if successfully // created, start it too. func (clnt *client) Create(containerID string, spec Spec, options ...CreateOption) error { logrus.Debugln("LCD client.Create() with spec", spec) cu := &containerInit{ SystemType: "Container", Name: containerID, Owner: defaultOwner, VolumePath: spec.Root.Path, IgnoreFlushesDuringBoot: spec.Windows.FirstStart, LayerFolderPath: spec.Windows.LayerFolder, HostName: spec.Hostname, } if spec.Windows.Networking != nil { cu.EndpointList = spec.Windows.Networking.EndpointList } if spec.Windows.Resources != nil { if spec.Windows.Resources.CPU != nil { if spec.Windows.Resources.CPU.Shares != nil { cu.ProcessorWeight = *spec.Windows.Resources.CPU.Shares } if spec.Windows.Resources.CPU.Percent != nil { cu.ProcessorMaximum = *spec.Windows.Resources.CPU.Percent * 100 // ProcessorMaximum is a value between 1 and 10000 } } if spec.Windows.Resources.Memory != nil { if spec.Windows.Resources.Memory.Limit != nil { cu.MemoryMaximumInMB = *spec.Windows.Resources.Memory.Limit / 1024 / 1024 } } if spec.Windows.Resources.Storage != nil { if spec.Windows.Resources.Storage.Bps != nil { cu.StorageBandwidthMaximum = *spec.Windows.Resources.Storage.Bps } if spec.Windows.Resources.Storage.Iops != nil { cu.StorageIOPSMaximum = *spec.Windows.Resources.Storage.Iops } if spec.Windows.Resources.Storage.SandboxSize != nil { cu.StorageSandboxSize = *spec.Windows.Resources.Storage.SandboxSize } } } cu.HvPartition = (spec.Windows.HvRuntime != nil) // TODO Windows @jhowardmsft. FIXME post TP5. // if spec.Windows.HvRuntime != nil { // if spec.WIndows.HVRuntime.ImagePath != "" { // cu.TBD = spec.Windows.HvRuntime.ImagePath // } // } if cu.HvPartition { cu.SandboxPath = filepath.Dir(spec.Windows.LayerFolder) } else { cu.VolumePath = spec.Root.Path cu.LayerFolderPath = spec.Windows.LayerFolder } for _, layerPath := range spec.Windows.LayerPaths { _, filename := filepath.Split(layerPath) g, err := hcsshim.NameToGuid(filename) if err != nil { return err } cu.Layers = append(cu.Layers, layer{ ID: g.ToString(), Path: layerPath, }) } // Add the mounts (volumes, bind mounts etc) to the structure mds := make([]mappedDir, len(spec.Mounts)) for i, mount := range spec.Mounts { mds[i] = mappedDir{ HostPath: mount.Source, ContainerPath: mount.Destination, ReadOnly: mount.Readonly} } cu.MappedDirectories = mds // TODO Windows: vv START OF TP4 BLOCK OF CODE. REMOVE ONCE TP4 IS NO LONGER SUPPORTED if hcsshim.IsTP4() && spec.Windows.Networking != nil && spec.Windows.Networking.Bridge != "" { // Enumerate through the port bindings specified by the user and convert // them into the internal structure matching the JSON blob that can be // understood by the HCS. var pbs []portBinding for i, v := range spec.Windows.Networking.PortBindings { proto := strings.ToUpper(i.Proto()) if proto != "TCP" && proto != "UDP" { return fmt.Errorf("invalid protocol %s", i.Proto()) } if len(v) > 1 { return fmt.Errorf("Windows does not support more than one host port in NAT settings") } for _, v2 := range v { var ( iPort, ePort int err error ) if len(v2.HostIP) != 0 { return fmt.Errorf("Windows does not support host IP addresses in NAT settings") } if ePort, err = strconv.Atoi(v2.HostPort); err != nil { return fmt.Errorf("invalid container port %s: %s", v2.HostPort, err) } if iPort, err = strconv.Atoi(i.Port()); err != nil { return fmt.Errorf("invalid internal port %s: %s", i.Port(), err) } if iPort < 0 || iPort > 65535 || ePort < 0 || ePort > 65535 { return fmt.Errorf("specified NAT port is not in allowed range") } pbs = append(pbs, portBinding{ExternalPort: ePort, InternalPort: iPort, Protocol: proto}) } } dev := device{ DeviceType: "Network", Connection: &networkConnection{ NetworkName: spec.Windows.Networking.Bridge, Nat: natSettings{ Name: defaultContainerNAT, PortBindings: pbs, }, }, } if spec.Windows.Networking.MacAddress != "" { windowsStyleMAC := strings.Replace( spec.Windows.Networking.MacAddress, ":", "-", -1) dev.Settings = networkSettings{ MacAddress: windowsStyleMAC, } } cu.Devices = append(cu.Devices, dev) } else { logrus.Debugln("No network interface") } // TODO Windows: ^^ END OF TP4 BLOCK OF CODE. REMOVE ONCE TP4 IS NO LONGER SUPPORTED configurationb, err := json.Marshal(cu) if err != nil { return err } configuration := string(configurationb) // TODO Windows TP5 timeframe. Remove when TP4 is no longer supported. // The following a workaround for Windows TP4 which has a networking // bug which fairly frequently returns an error. Back off and retry. if !hcsshim.IsTP4() { if err := hcsshim.CreateComputeSystem(containerID, configuration); err != nil { return err } } else { maxAttempts := 5 for i := 1; i <= maxAttempts; i++ { err = hcsshim.CreateComputeSystem(containerID, configuration) if err == nil { break } if herr, ok := err.(*hcsshim.HcsError); ok { if herr.Err != syscall.ERROR_NOT_FOUND && // Element not found herr.Err != syscall.ERROR_FILE_NOT_FOUND && // The system cannot find the file specified herr.Err != ErrorNoNetwork && // The network is not present or not started herr.Err != ErrorBadPathname && // The specified path is invalid herr.Err != CoEClassstring && // Invalid class string herr.Err != ErrorInvalidObject { // The object identifier does not represent a valid object logrus.Debugln("Failed to create temporary container ", err) return err } logrus.Warnf("Invoking Windows TP4 retry hack (%d of %d)", i, maxAttempts-1) time.Sleep(50 * time.Millisecond) } } } // Construct a container object for calling start on it. container := &container{ containerCommon: containerCommon{ process: process{ processCommon: processCommon{ containerID: containerID, client: clnt, friendlyName: InitFriendlyName, }, }, processes: make(map[string]*process), }, ociSpec: spec, } container.options = options for _, option := range options { if err := option.Apply(container); err != nil { logrus.Error(err) } } // Call start, and if it fails, delete the container from our // internal structure, and also keep HCS in sync by deleting the // container there. logrus.Debugf("Create() id=%s, Calling start()", containerID) if err := container.start(); err != nil { clnt.deleteContainer(containerID) return err } logrus.Debugf("Create() id=%s completed successfully", containerID) return nil }