Update Windows and LCOW to use v1.0.0 runtime-spec

Signed-off-by: Darren Stahl <darst@microsoft.com>
Signed-off-by: Kenfe-Mickael Laventure <mickael.laventure@gmail.com>
This commit is contained in:
Darren Stahl 2017-08-01 10:00:38 -07:00 committed by Kenfe-Mickael Laventure
parent 45d85c9913
commit 7c29103ad9
No known key found for this signature in database
GPG Key ID: 40CF16616B361216
8 changed files with 332 additions and 391 deletions

View File

@ -22,22 +22,17 @@ func (daemon *Daemon) postRunProcessing(container *container.Container, e libcon
return err
}
newOpts := []libcontainerd.CreateOption{&libcontainerd.ServicingOption{
IsServicing: true,
}}
// Turn on servicing
spec.Windows.Servicing = true
copts, err := daemon.getLibcontainerdCreateOptions(container)
if err != nil {
return err
}
if copts != nil {
newOpts = append(newOpts, copts...)
}
// Create a new servicing container, which will start, complete the update, and merge back the
// results if it succeeded, all as part of the below function call.
if err := daemon.containerd.Create((container.ID + "_servicing"), "", "", *spec, container.InitializeStdio, newOpts...); err != nil {
if err := daemon.containerd.Create((container.ID + "_servicing"), "", "", *spec, container.InitializeStdio, copts...); err != nil {
container.SetExitCode(-1)
return fmt.Errorf("Post-run update servicing failed: %s", err)
}

View File

@ -1,13 +1,25 @@
package daemon
import (
"fmt"
"io/ioutil"
"path/filepath"
"strings"
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/container"
"github.com/docker/docker/layer"
"github.com/docker/docker/oci"
"github.com/docker/docker/pkg/sysinfo"
"github.com/docker/docker/pkg/system"
"github.com/opencontainers/runtime-spec/specs-go"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/registry"
)
const (
credentialSpecRegistryLocation = `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\Containers\CredentialSpecs`
credentialSpecFileLocation = "CredentialSpecs"
)
func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
@ -53,6 +65,10 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
isHyperV = c.HostConfig.Isolation.IsHyperV()
}
if isHyperV {
s.Windows.HyperV = &specs.WindowsHyperV{}
}
// If the container has not been started, and has configs or secrets
// secrets, create symlinks to each config and secret. If it has been
// started before, the symlinks should have already been created. Also, it
@ -105,13 +121,93 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
s.Process.Env = c.CreateDaemonEnvironment(c.Config.Tty, linkedEnv)
if c.Config.Tty {
s.Process.Terminal = c.Config.Tty
s.Process.ConsoleSize.Height = c.HostConfig.ConsoleSize[0]
s.Process.ConsoleSize.Width = c.HostConfig.ConsoleSize[1]
s.Process.ConsoleSize = &specs.Box{
Height: c.HostConfig.ConsoleSize[0],
Width: c.HostConfig.ConsoleSize[1],
}
}
s.Process.User.Username = c.Config.User
// Get the layer path for each layer.
max := len(img.RootFS.DiffIDs)
for i := 1; i <= max; i++ {
img.RootFS.DiffIDs = img.RootFS.DiffIDs[:i]
layerPath, err := layer.GetLayerPath(daemon.stores[c.Platform].layerStore, img.RootFS.ChainID())
if err != nil {
return nil, fmt.Errorf("failed to get layer path from graphdriver %s for ImageID %s - %s", daemon.stores[c.Platform].layerStore, img.RootFS.ChainID(), err)
}
// Reverse order, expecting parent most first
s.Windows.LayerFolders = append([]string{layerPath}, s.Windows.LayerFolders...)
}
m, err := c.RWLayer.Metadata()
if err != nil {
return nil, fmt.Errorf("failed to get layer metadata - %s", err)
}
s.Windows.LayerFolders = append(s.Windows.LayerFolders, m["dir"])
dnsSearch := daemon.getDNSSearchSettings(c)
// Get endpoints for the libnetwork allocated networks to the container
var epList []string
AllowUnqualifiedDNSQuery := false
gwHNSID := ""
if c.NetworkSettings != nil {
for n := range c.NetworkSettings.Networks {
sn, err := daemon.FindNetwork(n)
if err != nil {
continue
}
ep, err := c.GetEndpointInNetwork(sn)
if err != nil {
continue
}
data, err := ep.DriverInfo()
if err != nil {
continue
}
if data["GW_INFO"] != nil {
gwInfo := data["GW_INFO"].(map[string]interface{})
if gwInfo["hnsid"] != nil {
gwHNSID = gwInfo["hnsid"].(string)
}
}
if data["hnsid"] != nil {
epList = append(epList, data["hnsid"].(string))
}
if data["AllowUnqualifiedDNSQuery"] != nil {
AllowUnqualifiedDNSQuery = true
}
}
}
var networkSharedContainerID string
if c.HostConfig.NetworkMode.IsContainer() {
networkSharedContainerID = c.NetworkSharedContainerID
for _, ep := range c.SharedEndpointList {
epList = append(epList, ep)
}
}
if gwHNSID != "" {
epList = append(epList, gwHNSID)
}
s.Windows.Network = &specs.WindowsNetwork{
AllowUnqualifiedDNSQuery: AllowUnqualifiedDNSQuery,
DNSSearchList: dnsSearch,
EndpointList: epList,
NetworkSharedContainerName: networkSharedContainerID,
}
if img.OS == "windows" {
daemon.createSpecWindowsFields(c, &s, isHyperV)
if err := daemon.createSpecWindowsFields(c, &s, isHyperV); err != nil {
return nil, err
}
} else {
// TODO @jhowardmsft LCOW Support. Modify this check when running in dual-mode
if system.LCOWSupported() && img.OS == "linux" {
@ -123,7 +219,7 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
}
// Sets the Windows-specific fields of the OCI spec
func (daemon *Daemon) createSpecWindowsFields(c *container.Container, s *specs.Spec, isHyperV bool) {
func (daemon *Daemon) createSpecWindowsFields(c *container.Container, s *specs.Spec, isHyperV bool) error {
if len(s.Process.Cwd) == 0 {
// We default to C:\ to workaround the oddity of the case that the
// default directory for cmd running as LocalSystem (or
@ -138,8 +234,14 @@ func (daemon *Daemon) createSpecWindowsFields(c *container.Container, s *specs.S
s.Root.Readonly = false // Windows does not support a read-only root filesystem
if !isHyperV {
s.Root.Path = c.BaseFS // This is not set for Hyper-V containers
if !strings.HasSuffix(s.Root.Path, `\`) {
s.Root.Path = s.Root.Path + `\` // Ensure a correctly formatted volume GUID path \\?\Volume{GUID}\
}
}
// First boot optimization
s.Windows.IgnoreFlushesDuringBoot = !c.HasBeenStartedBefore
// In s.Windows.Resources
cpuShares := uint16(c.HostConfig.CPUShares)
cpuMaximum := uint16(c.HostConfig.CPUPercent) * 100
@ -179,6 +281,54 @@ func (daemon *Daemon) createSpecWindowsFields(c *container.Container, s *specs.S
Iops: &c.HostConfig.IOMaximumIOps,
},
}
// Read and add credentials from the security options if a credential spec has been provided.
if c.HostConfig.SecurityOpt != nil {
cs := ""
for _, sOpt := range c.HostConfig.SecurityOpt {
sOpt = strings.ToLower(sOpt)
if !strings.Contains(sOpt, "=") {
return fmt.Errorf("invalid security option: no equals sign in supplied value %s", sOpt)
}
var splitsOpt []string
splitsOpt = strings.SplitN(sOpt, "=", 2)
if len(splitsOpt) != 2 {
return fmt.Errorf("invalid security option: %s", sOpt)
}
if splitsOpt[0] != "credentialspec" {
return fmt.Errorf("security option not supported: %s", splitsOpt[0])
}
var (
match bool
csValue string
err error
)
if match, csValue = getCredentialSpec("file://", splitsOpt[1]); match {
if csValue == "" {
return fmt.Errorf("no value supplied for file:// credential spec security option")
}
if cs, err = readCredentialSpecFile(c.ID, daemon.root, filepath.Clean(csValue)); err != nil {
return err
}
} else if match, csValue = getCredentialSpec("registry://", splitsOpt[1]); match {
if csValue == "" {
return fmt.Errorf("no value supplied for registry:// credential spec security option")
}
if cs, err = readCredentialSpecRegistry(c.ID, csValue); err != nil {
return err
}
} else {
return fmt.Errorf("invalid credential spec security option - value must be prefixed file:// or registry:// followed by a value")
}
}
s.Windows.CredentialSpec = cs
}
// Assume we are not starting a container for a servicing operation
s.Windows.Servicing = false
return nil
}
// Sets the Linux-specific fields of the OCI spec
@ -205,3 +355,52 @@ func escapeArgs(args []string) []string {
func (daemon *Daemon) mergeUlimits(c *containertypes.HostConfig) {
return
}
// getCredentialSpec is a helper function to get the value of a credential spec supplied
// on the CLI, stripping the prefix
func getCredentialSpec(prefix, value string) (bool, string) {
if strings.HasPrefix(value, prefix) {
return true, strings.TrimPrefix(value, prefix)
}
return false, ""
}
// readCredentialSpecRegistry is a helper function to read a credential spec from
// the registry. If not found, we return an empty string and warn in the log.
// This allows for staging on machines which do not have the necessary components.
func readCredentialSpecRegistry(id, name string) (string, error) {
var (
k registry.Key
err error
val string
)
if k, err = registry.OpenKey(registry.LOCAL_MACHINE, credentialSpecRegistryLocation, registry.QUERY_VALUE); err != nil {
return "", fmt.Errorf("failed handling spec %q for container %s - %s could not be opened", name, id, credentialSpecRegistryLocation)
}
if val, _, err = k.GetStringValue(name); err != nil {
if err == registry.ErrNotExist {
return "", fmt.Errorf("credential spec %q for container %s as it was not found", name, id)
}
return "", fmt.Errorf("error %v reading credential spec %q from registry for container %s", err, name, id)
}
return val, nil
}
// readCredentialSpecFile is a helper function to read a credential spec from
// a file. If not found, we return an empty string and warn in the log.
// This allows for staging on machines which do not have the necessary components.
func readCredentialSpecFile(id, root, location string) (string, error) {
if filepath.IsAbs(location) {
return "", fmt.Errorf("invalid credential spec - file:// path cannot be absolute")
}
base := filepath.Join(root, credentialSpecFileLocation)
full := filepath.Join(base, location)
if !strings.HasPrefix(full, base) {
return "", fmt.Errorf("invalid credential spec - file:// path must be under %s", base)
}
bcontents, err := ioutil.ReadFile(full)
if err != nil {
return "", fmt.Errorf("credential spec '%s' for container %s as the file could not be read: %q", full, id, err)
}
return string(bcontents[:]), nil
}

View File

@ -1,148 +1,14 @@
package daemon
import (
"fmt"
"io/ioutil"
"path/filepath"
"strings"
"github.com/Microsoft/opengcs/client"
"github.com/docker/docker/container"
"github.com/docker/docker/layer"
"github.com/docker/docker/libcontainerd"
"golang.org/x/sys/windows/registry"
)
const (
credentialSpecRegistryLocation = `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\Containers\CredentialSpecs`
credentialSpecFileLocation = "CredentialSpecs"
)
func (daemon *Daemon) getLibcontainerdCreateOptions(container *container.Container) ([]libcontainerd.CreateOption, error) {
createOptions := []libcontainerd.CreateOption{}
// Are we going to run as a Hyper-V container?
hvOpts := &libcontainerd.HyperVIsolationOption{}
if container.HostConfig.Isolation.IsDefault() {
// Container is set to use the default, so take the default from the daemon configuration
hvOpts.IsHyperV = daemon.defaultIsolation.IsHyperV()
} else {
// Container is requesting an isolation mode. Honour it.
hvOpts.IsHyperV = container.HostConfig.Isolation.IsHyperV()
}
dnsSearch := daemon.getDNSSearchSettings(container)
// Generate the layer folder of the layer options
layerOpts := &libcontainerd.LayerOption{}
m, err := container.RWLayer.Metadata()
if err != nil {
return nil, fmt.Errorf("failed to get layer metadata - %s", err)
}
layerOpts.LayerFolderPath = m["dir"]
// Generate the layer paths of the layer options
img, err := daemon.stores[container.Platform].imageStore.Get(container.ImageID)
if err != nil {
return nil, fmt.Errorf("failed to graph.Get on ImageID %s - %s", container.ImageID, err)
}
// Get the layer path for each layer.
max := len(img.RootFS.DiffIDs)
for i := 1; i <= max; i++ {
img.RootFS.DiffIDs = img.RootFS.DiffIDs[:i]
layerPath, err := layer.GetLayerPath(daemon.stores[container.Platform].layerStore, img.RootFS.ChainID())
if err != nil {
return nil, fmt.Errorf("failed to get layer path from graphdriver %s for ImageID %s - %s", daemon.stores[container.Platform].layerStore, img.RootFS.ChainID(), err)
}
// Reverse order, expecting parent most first
layerOpts.LayerPaths = append([]string{layerPath}, layerOpts.LayerPaths...)
}
// Get endpoints for the libnetwork allocated networks to the container
var epList []string
AllowUnqualifiedDNSQuery := false
gwHNSID := ""
if container.NetworkSettings != nil {
for n := range container.NetworkSettings.Networks {
sn, err := daemon.FindNetwork(n)
if err != nil {
continue
}
ep, err := container.GetEndpointInNetwork(sn)
if err != nil {
continue
}
data, err := ep.DriverInfo()
if err != nil {
continue
}
if data["GW_INFO"] != nil {
gwInfo := data["GW_INFO"].(map[string]interface{})
if gwInfo["hnsid"] != nil {
gwHNSID = gwInfo["hnsid"].(string)
}
}
if data["hnsid"] != nil {
epList = append(epList, data["hnsid"].(string))
}
if data["AllowUnqualifiedDNSQuery"] != nil {
AllowUnqualifiedDNSQuery = true
}
}
}
if gwHNSID != "" {
epList = append(epList, gwHNSID)
}
// Read and add credentials from the security options if a credential spec has been provided.
if container.HostConfig.SecurityOpt != nil {
for _, sOpt := range container.HostConfig.SecurityOpt {
sOpt = strings.ToLower(sOpt)
if !strings.Contains(sOpt, "=") {
return nil, fmt.Errorf("invalid security option: no equals sign in supplied value %s", sOpt)
}
var splitsOpt []string
splitsOpt = strings.SplitN(sOpt, "=", 2)
if len(splitsOpt) != 2 {
return nil, fmt.Errorf("invalid security option: %s", sOpt)
}
if splitsOpt[0] != "credentialspec" {
return nil, fmt.Errorf("security option not supported: %s", splitsOpt[0])
}
credentialsOpts := &libcontainerd.CredentialsOption{}
var (
match bool
csValue string
err error
)
if match, csValue = getCredentialSpec("file://", splitsOpt[1]); match {
if csValue == "" {
return nil, fmt.Errorf("no value supplied for file:// credential spec security option")
}
if credentialsOpts.Credentials, err = readCredentialSpecFile(container.ID, daemon.root, filepath.Clean(csValue)); err != nil {
return nil, err
}
} else if match, csValue = getCredentialSpec("registry://", splitsOpt[1]); match {
if csValue == "" {
return nil, fmt.Errorf("no value supplied for registry:// credential spec security option")
}
if credentialsOpts.Credentials, err = readCredentialSpecRegistry(container.ID, csValue); err != nil {
return nil, err
}
} else {
return nil, fmt.Errorf("invalid credential spec security option - value must be prefixed file:// or registry:// followed by a value")
}
createOptions = append(createOptions, credentialsOpts)
}
}
// LCOW options.
if container.Platform == "linux" {
config := &client.Config{}
@ -173,73 +39,5 @@ func (daemon *Daemon) getLibcontainerdCreateOptions(container *container.Contain
createOptions = append(createOptions, lcowOpts)
}
// Now add the remaining options.
createOptions = append(createOptions, &libcontainerd.FlushOption{IgnoreFlushesDuringBoot: !container.HasBeenStartedBefore})
createOptions = append(createOptions, hvOpts)
createOptions = append(createOptions, layerOpts)
var networkSharedContainerID string
if container.HostConfig.NetworkMode.IsContainer() {
networkSharedContainerID = container.NetworkSharedContainerID
for _, ep := range container.SharedEndpointList {
epList = append(epList, ep)
}
}
createOptions = append(createOptions, &libcontainerd.NetworkEndpointsOption{
Endpoints: epList,
AllowUnqualifiedDNSQuery: AllowUnqualifiedDNSQuery,
DNSSearchList: dnsSearch,
NetworkSharedContainerID: networkSharedContainerID,
})
return createOptions, nil
}
// getCredentialSpec is a helper function to get the value of a credential spec supplied
// on the CLI, stripping the prefix
func getCredentialSpec(prefix, value string) (bool, string) {
if strings.HasPrefix(value, prefix) {
return true, strings.TrimPrefix(value, prefix)
}
return false, ""
}
// readCredentialSpecRegistry is a helper function to read a credential spec from
// the registry. If not found, we return an empty string and warn in the log.
// This allows for staging on machines which do not have the necessary components.
func readCredentialSpecRegistry(id, name string) (string, error) {
var (
k registry.Key
err error
val string
)
if k, err = registry.OpenKey(registry.LOCAL_MACHINE, credentialSpecRegistryLocation, registry.QUERY_VALUE); err != nil {
return "", fmt.Errorf("failed handling spec %q for container %s - %s could not be opened", name, id, credentialSpecRegistryLocation)
}
if val, _, err = k.GetStringValue(name); err != nil {
if err == registry.ErrNotExist {
return "", fmt.Errorf("credential spec %q for container %s as it was not found", name, id)
}
return "", fmt.Errorf("error %v reading credential spec %q from registry for container %s", err, name, id)
}
return val, nil
}
// readCredentialSpecFile is a helper function to read a credential spec from
// a file. If not found, we return an empty string and warn in the log.
// This allows for staging on machines which do not have the necessary components.
func readCredentialSpecFile(id, root, location string) (string, error) {
if filepath.IsAbs(location) {
return "", fmt.Errorf("invalid credential spec - file:// path cannot be absolute")
}
base := filepath.Join(root, credentialSpecFileLocation)
full := filepath.Join(base, location)
if !strings.HasPrefix(full, base) {
return "", fmt.Errorf("invalid credential spec - file:// path must be under %s", base)
}
bcontents, err := ioutil.ReadFile(full)
if err != nil {
return "", fmt.Errorf("credential spec '%s' for container %s as the file could not be read: %q", full, id, err)
}
return string(bcontents[:]), nil
}

View File

@ -8,6 +8,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"syscall"
"time"
@ -102,8 +103,11 @@ func (clnt *client) Create(containerID string, checkpoint string, checkpointDir
if b, err := json.Marshal(spec); err == nil {
logrus.Debugln("libcontainerd: client.Create() with spec", string(b))
}
osName := spec.Platform.OS
if osName == "windows" {
// spec.Linux must be nil for Windows containers, but spec.Windows will be filled in regardless of container platform.
// This is a temporary workaround due to LCOW requiring layer folder paths, which are stored under spec.Windows.
// TODO: @darrenstahlmsft fix this once the OCI spec is updated to support layer folder paths for LCOW
if spec.Linux == nil {
return clnt.createWindows(containerID, checkpoint, checkpointDir, spec, attachStdio, options...)
}
return clnt.createLinux(containerID, checkpoint, checkpointDir, spec, attachStdio, options...)
@ -114,9 +118,10 @@ func (clnt *client) createWindows(containerID string, checkpoint string, checkpo
SystemType: "Container",
Name: containerID,
Owner: defaultOwner,
IgnoreFlushesDuringBoot: false,
IgnoreFlushesDuringBoot: spec.Windows.IgnoreFlushesDuringBoot,
HostName: spec.Hostname,
HvPartition: false,
Servicing: spec.Windows.Servicing,
}
if spec.Windows.Resources != nil {
@ -155,49 +160,43 @@ func (clnt *client) createWindows(containerID string, checkpoint string, checkpo
}
}
var layerOpt *LayerOption
for _, option := range options {
if s, ok := option.(*ServicingOption); ok {
configuration.Servicing = s.IsServicing
continue
}
if f, ok := option.(*FlushOption); ok {
configuration.IgnoreFlushesDuringBoot = f.IgnoreFlushesDuringBoot
continue
}
if h, ok := option.(*HyperVIsolationOption); ok {
configuration.HvPartition = h.IsHyperV
continue
}
if l, ok := option.(*LayerOption); ok {
layerOpt = l
}
if n, ok := option.(*NetworkEndpointsOption); ok {
configuration.EndpointList = n.Endpoints
configuration.AllowUnqualifiedDNSQuery = n.AllowUnqualifiedDNSQuery
if n.DNSSearchList != nil {
configuration.DNSSearchList = strings.Join(n.DNSSearchList, ",")
}
configuration.NetworkSharedContainerName = n.NetworkSharedContainerID
continue
}
if c, ok := option.(*CredentialsOption); ok {
configuration.Credentials = c.Credentials
continue
}
if spec.Windows.HyperV != nil {
configuration.HvPartition = true
}
// We must have a layer option with at least one path
if layerOpt == nil || layerOpt.LayerPaths == nil {
return fmt.Errorf("no layer option or paths were supplied to the runtime")
if spec.Windows.Network != nil {
configuration.EndpointList = spec.Windows.Network.EndpointList
configuration.AllowUnqualifiedDNSQuery = spec.Windows.Network.AllowUnqualifiedDNSQuery
if spec.Windows.Network.DNSSearchList != nil {
configuration.DNSSearchList = strings.Join(spec.Windows.Network.DNSSearchList, ",")
}
configuration.NetworkSharedContainerName = spec.Windows.Network.NetworkSharedContainerName
}
if cs, ok := spec.Windows.CredentialSpec.(string); ok {
configuration.Credentials = cs
}
// We must have least two layers in the spec, the bottom one being a base image,
// the top one being the RW layer.
if spec.Windows.LayerFolders == nil || len(spec.Windows.LayerFolders) < 2 {
return fmt.Errorf("OCI spec is invalid - at least two LayerFolders must be supplied to the runtime")
}
// Strip off the top-most layer as that's passed in separately to HCS
configuration.LayerFolderPath = spec.Windows.LayerFolders[len(spec.Windows.LayerFolders)-1]
layerFolders := spec.Windows.LayerFolders[:len(spec.Windows.LayerFolders)-1]
if configuration.HvPartition {
// Find the upper-most utility VM image, since the utility VM does not
// use layering in RS1.
// TODO @swernli/jhowardmsft at some point post RS1 this may be re-locatable.
// We don't currently support setting the utility VM image explicitly.
// TODO @swernli/jhowardmsft circa RS3/4, this may be re-locatable.
if spec.Windows.HyperV.UtilityVMPath != "" {
return errors.New("runtime does not support an explicit utility VM path for Hyper-V containers")
}
// Find the upper-most utility VM image.
var uvmImagePath string
for _, path := range layerOpt.LayerPaths {
for _, path := range layerFolders {
fullPath := filepath.Join(path, "UtilityVM")
_, err := os.Stat(fullPath)
if err == nil {
@ -212,13 +211,24 @@ func (clnt *client) createWindows(containerID string, checkpoint string, checkpo
return errors.New("utility VM image could not be found")
}
configuration.HvRuntime = &hcsshim.HvRuntime{ImagePath: uvmImagePath}
if spec.Root.Path != "" {
return errors.New("OCI spec is invalid - Root.Path must be omitted for a Hyper-V container")
}
} else {
configuration.VolumePath = spec.Root.Path
const volumeGUIDRegex = `^\\\\\?\\(Volume)\{{0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}\}\\$`
if _, err := regexp.MatchString(volumeGUIDRegex, spec.Root.Path); err != nil {
return fmt.Errorf(`OCI spec is invalid - Root.Path '%s' must be a volume GUID path in the format '\\?\Volume{GUID}\'`, spec.Root.Path)
}
// HCS API requires the trailing backslash to be removed
configuration.VolumePath = spec.Root.Path[:len(spec.Root.Path)-1]
}
configuration.LayerFolderPath = layerOpt.LayerFolderPath
if spec.Root.Readonly {
return errors.New(`OCI spec is invalid - Root.Readonly must not be set on Windows`)
}
for _, layerPath := range layerOpt.LayerPaths {
for _, layerPath := range layerFolders {
_, filename := filepath.Split(layerPath)
g, err := hcsshim.NameToGuid(filename)
if err != nil {
@ -235,6 +245,9 @@ func (clnt *client) createWindows(containerID string, checkpoint string, checkpo
var mps []hcsshim.MappedPipe
for _, mount := range spec.Mounts {
const pipePrefix = `\\.\pipe\`
if mount.Type != "" {
return fmt.Errorf("OCI spec is invalid - Mount.Type '%s' must not be set", mount.Type)
}
if strings.HasPrefix(mount.Destination, pipePrefix) {
mp := hcsshim.MappedPipe{
HostPath: mount.Source,
@ -278,6 +291,7 @@ func (clnt *client) createWindows(containerID string, checkpoint string, checkpo
},
processes: make(map[string]*process),
},
isWindows: true,
ociSpec: spec,
hcsContainer: hcsContainer,
}
@ -306,12 +320,8 @@ func (clnt *client) createWindows(containerID string, checkpoint string, checkpo
func (clnt *client) createLinux(containerID string, checkpoint string, checkpointDir string, spec specs.Spec, attachStdio StdioCallback, options ...CreateOption) error {
logrus.Debugf("libcontainerd: createLinux(): containerId %s ", containerID)
var layerOpt *LayerOption
var lcowOpt *LCOWOption
for _, option := range options {
if layer, ok := option.(*LayerOption); ok {
layerOpt = layer
}
if lcow, ok := option.(*LCOWOption); ok {
lcowOpt = lcow
}
@ -342,14 +352,20 @@ func (clnt *client) createLinux(containerID string, checkpoint string, checkpoin
}
}
// We must have a layer option with at least one path
if layerOpt == nil || layerOpt.LayerPaths == nil {
return fmt.Errorf("no layer option or paths were supplied to the runtime")
if spec.Windows == nil {
return fmt.Errorf("spec.Windows must not be nil for LCOW containers")
}
// LayerFolderPath (writeable layer) + Layers (Guid + path)
configuration.LayerFolderPath = layerOpt.LayerFolderPath
for _, layerPath := range layerOpt.LayerPaths {
// We must have least one layer in the spec
if spec.Windows.LayerFolders == nil || len(spec.Windows.LayerFolders) == 0 {
return fmt.Errorf("OCI spec is invalid - at least one LayerFolders must be supplied to the runtime")
}
// Strip off the top-most layer as that's passed in separately to HCS
configuration.LayerFolderPath = spec.Windows.LayerFolders[len(spec.Windows.LayerFolders)-1]
layerFolders := spec.Windows.LayerFolders[:len(spec.Windows.LayerFolders)-1]
for _, layerPath := range layerFolders {
_, filename := filepath.Split(layerPath)
g, err := hcsshim.NameToGuid(filename)
if err != nil {
@ -361,16 +377,13 @@ func (clnt *client) createLinux(containerID string, checkpoint string, checkpoin
})
}
for _, option := range options {
if n, ok := option.(*NetworkEndpointsOption); ok {
configuration.EndpointList = n.Endpoints
configuration.AllowUnqualifiedDNSQuery = n.AllowUnqualifiedDNSQuery
if n.DNSSearchList != nil {
configuration.DNSSearchList = strings.Join(n.DNSSearchList, ",")
}
configuration.NetworkSharedContainerName = n.NetworkSharedContainerID
break
if spec.Windows.Network != nil {
configuration.EndpointList = spec.Windows.Network.EndpointList
configuration.AllowUnqualifiedDNSQuery = spec.Windows.Network.AllowUnqualifiedDNSQuery
if spec.Windows.Network.DNSSearchList != nil {
configuration.DNSSearchList = strings.Join(spec.Windows.Network.DNSSearchList, ",")
}
configuration.NetworkSharedContainerName = spec.Windows.Network.NetworkSharedContainerName
}
hcsContainer, err := hcsshim.CreateContainer(containerID, configuration)
@ -436,8 +449,10 @@ func (clnt *client) AddProcess(ctx context.Context, containerID, processFriendly
}
if procToAdd.Terminal {
createProcessParms.EmulateConsole = true
createProcessParms.ConsoleSize[0] = uint(procToAdd.ConsoleSize.Height)
createProcessParms.ConsoleSize[1] = uint(procToAdd.ConsoleSize.Width)
if procToAdd.ConsoleSize != nil {
createProcessParms.ConsoleSize[0] = uint(procToAdd.ConsoleSize.Height)
createProcessParms.ConsoleSize[1] = uint(procToAdd.ConsoleSize.Width)
}
}
// Take working directory from the process to add if it is defined,
@ -450,7 +465,7 @@ func (clnt *client) AddProcess(ctx context.Context, containerID, processFriendly
// Configure the environment for the process
createProcessParms.Environment = setupEnvironmentVariables(procToAdd.Env)
if container.ociSpec.Platform.OS == "windows" {
if container.isWindows {
createProcessParms.CommandLine = strings.Join(procToAdd.Args, " ")
} else {
createProcessParms.CommandArgs = procToAdd.Args
@ -614,13 +629,8 @@ func (clnt *client) Pause(containerID string) error {
return err
}
for _, option := range container.options {
if h, ok := option.(*HyperVIsolationOption); ok {
if !h.IsHyperV {
return errors.New("cannot pause Windows Server Containers")
}
break
}
if container.ociSpec.Windows.HyperV == nil {
return errors.New("cannot pause Windows Server Containers")
}
err = container.hcsContainer.Pause()
@ -654,13 +664,9 @@ func (clnt *client) Resume(containerID string) error {
}
// This should never happen, since Windows Server Containers cannot be paused
for _, option := range container.options {
if h, ok := option.(*HyperVIsolationOption); ok {
if !h.IsHyperV {
return errors.New("cannot resume Windows Server Containers")
}
break
}
if container.ociSpec.Windows.HyperV == nil {
return errors.New("cannot resume Windows Server Containers")
}
err = container.hcsContainer.Resume()

View File

@ -25,6 +25,7 @@ type container struct {
// otherwise have access to the Spec
ociSpec specs.Spec
isWindows bool
manualStopRequested bool
hcsContainer hcsshim.Container
}
@ -43,13 +44,6 @@ func (ctr *container) newProcess(friendlyName string) *process {
// Caller needs to lock container ID before calling this method.
func (ctr *container) start(attachStdio StdioCallback) error {
var err error
isServicing := false
for _, option := range ctr.options {
if s, ok := option.(*ServicingOption); ok && s.IsServicing {
isServicing = true
}
}
// Start the container. If this is a servicing container, this call will block
// until the container is done with the servicing execution.
@ -69,27 +63,39 @@ func (ctr *container) start(attachStdio StdioCallback) error {
// docker can always grab the output through logs. We also tell HCS to always
// create stdin, even if it's not used - it will be closed shortly. Stderr
// is only created if it we're not -t.
createProcessParms := &hcsshim.ProcessConfig{
EmulateConsole: ctr.ociSpec.Process.Terminal,
WorkingDirectory: ctr.ociSpec.Process.Cwd,
CreateStdInPipe: !isServicing,
CreateStdOutPipe: !isServicing,
CreateStdErrPipe: !ctr.ociSpec.Process.Terminal && !isServicing,
var (
emulateConsole bool
createStdErrPipe bool
)
if ctr.ociSpec.Process != nil {
emulateConsole = ctr.ociSpec.Process.Terminal
createStdErrPipe = !ctr.ociSpec.Process.Terminal && !ctr.ociSpec.Windows.Servicing
}
createProcessParms := &hcsshim.ProcessConfig{
EmulateConsole: emulateConsole,
WorkingDirectory: ctr.ociSpec.Process.Cwd,
CreateStdInPipe: !ctr.ociSpec.Windows.Servicing,
CreateStdOutPipe: !ctr.ociSpec.Windows.Servicing,
CreateStdErrPipe: createStdErrPipe,
}
if ctr.ociSpec.Process != nil && ctr.ociSpec.Process.ConsoleSize != nil {
createProcessParms.ConsoleSize[0] = uint(ctr.ociSpec.Process.ConsoleSize.Height)
createProcessParms.ConsoleSize[1] = uint(ctr.ociSpec.Process.ConsoleSize.Width)
}
createProcessParms.ConsoleSize[0] = uint(ctr.ociSpec.Process.ConsoleSize.Height)
createProcessParms.ConsoleSize[1] = uint(ctr.ociSpec.Process.ConsoleSize.Width)
// Configure the environment for the process
createProcessParms.Environment = setupEnvironmentVariables(ctr.ociSpec.Process.Env)
if ctr.ociSpec.Platform.OS == "windows" {
if ctr.isWindows {
createProcessParms.CommandLine = strings.Join(ctr.ociSpec.Process.Args, " ")
} else {
createProcessParms.CommandArgs = ctr.ociSpec.Process.Args
}
createProcessParms.User = ctr.ociSpec.Process.User.Username
// Linux containers requires the raw OCI spec passed through HCS and onwards to GCS for the utility VM.
if ctr.ociSpec.Platform.OS == "linux" {
// LCOW requires the raw OCI spec passed through HCS and onwards to GCS for the utility VM.
if !ctr.isWindows {
ociBuf, err := json.Marshal(ctr.ociSpec)
if err != nil {
return err
@ -118,7 +124,7 @@ func (ctr *container) start(attachStdio StdioCallback) error {
// If this is a servicing container, wait on the process synchronously here and
// if it succeeds, wait for it cleanly shutdown and merge into the parent container.
if isServicing {
if ctr.ociSpec.Windows.Servicing {
exitCode := ctr.waitProcessExitCode(&ctr.process)
if exitCode != 0 {
@ -244,7 +250,7 @@ func (ctr *container) waitExit(process *process, isFirstProcessToStart bool) err
si.State = StateExitProcess
} else {
// Pending updates is only applicable for WCOW
if ctr.ociSpec.Platform.OS == "windows" {
if ctr.isWindows {
updatePending, err := ctr.hcsContainer.HasPendingUpdates()
if err != nil {
logrus.Warnf("libcontainerd: HasPendingUpdates() failed (container may have been killed): %s", err)

View File

@ -31,49 +31,6 @@ type LCOWOption struct {
Config *opengcs.Config
}
// ServicingOption is a CreateOption with a no-op application that signifies
// the container needs to be used for a Windows servicing operation.
type ServicingOption struct {
IsServicing bool
}
// FlushOption is a CreateOption that signifies if the container should be
// started with flushes ignored until boot has completed. This is an optimisation
// for first boot of a container.
type FlushOption struct {
IgnoreFlushesDuringBoot bool
}
// HyperVIsolationOption is a CreateOption that indicates whether the runtime
// should start the container as a Hyper-V container.
type HyperVIsolationOption struct {
IsHyperV bool
}
// LayerOption is a CreateOption that indicates to the runtime the layer folder
// and layer paths for a container.
type LayerOption struct {
// LayerFolderPath is the path to the current layer folder. Empty for Hyper-V containers.
LayerFolderPath string `json:",omitempty"`
// Layer paths of the parent layers
LayerPaths []string
}
// NetworkEndpointsOption is a CreateOption that provides the runtime list
// of network endpoints to which a container should be attached during its creation.
type NetworkEndpointsOption struct {
Endpoints []string
AllowUnqualifiedDNSQuery bool
DNSSearchList []string
NetworkSharedContainerID string
}
// CredentialsOption is a CreateOption that indicates the credentials from
// a credential spec to be used to the runtime
type CredentialsOption struct {
Credentials string
}
// Checkpoint holds the details of a checkpoint (not supported in windows)
type Checkpoint struct {
Name string

View File

@ -15,36 +15,6 @@ func setupEnvironmentVariables(a []string) map[string]string {
return r
}
// Apply for a servicing option is a no-op.
func (s *ServicingOption) Apply(interface{}) error {
return nil
}
// Apply for the flush option is a no-op.
func (f *FlushOption) Apply(interface{}) error {
return nil
}
// Apply for the hypervisolation option is a no-op.
func (h *HyperVIsolationOption) Apply(interface{}) error {
return nil
}
// Apply for the layer option is a no-op.
func (h *LayerOption) Apply(interface{}) error {
return nil
}
// Apply for the network endpoints option is a no-op.
func (s *NetworkEndpointsOption) Apply(interface{}) error {
return nil
}
// Apply for the credentials option is a no-op.
func (s *CredentialsOption) Apply(interface{}) error {
return nil
}
// Apply for the LCOW option is a no-op.
func (s *LCOWOption) Apply(interface{}) error {
return nil

View File

@ -51,6 +51,8 @@ func DefaultWindowsSpec() specs.Spec {
return specs.Spec{
Version: specs.Version,
Windows: &specs.Windows{},
Process: &specs.Process{},
Root: &specs.Root{},
}
}
@ -68,6 +70,7 @@ func DefaultLinuxSpec() specs.Spec {
s := specs.Spec{
Version: specs.Version,
Process: &specs.Process{},
Root: &specs.Root{},
}
s.Mounts = []specs.Mount{
{
@ -113,11 +116,13 @@ func DefaultLinuxSpec() specs.Spec {
Options: []string{"nosuid", "noexec", "nodev", "mode=1777"},
},
}
s.Process.Capabilities = &specs.LinuxCapabilities{
Bounding: defaultCapabilities(),
Permitted: defaultCapabilities(),
Inheritable: defaultCapabilities(),
Effective: defaultCapabilities(),
s.Process = &specs.Process{
Capabilities: &specs.LinuxCapabilities{
Bounding: defaultCapabilities(),
Permitted: defaultCapabilities(),
Inheritable: defaultCapabilities(),
Effective: defaultCapabilities(),
},
}
s.Linux = &specs.Linux{
@ -207,6 +212,11 @@ func DefaultLinuxSpec() specs.Spec {
},
}
// For LCOW support, populate a blank Windows spec
if runtime.GOOS == "windows" {
s.Windows = &specs.Windows{}
}
// For LCOW support, don't mask /sys/firmware
if runtime.GOOS != "windows" {
s.Linux.MaskedPaths = append(s.Linux.MaskedPaths, "/sys/firmware")