Browse code

Add support for multiples runtimes

Signed-off-by: Kenfe-Mickael Laventure <mickael.laventure@gmail.com>

Kenfe-Mickael Laventure authored on 2016/05/24 06:49:50
Showing 25 changed files
... ...
@@ -55,7 +55,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
55 55
 	ioutils.FprintfIfNotEmpty(cli.out, "Logging Driver: %s\n", info.LoggingDriver)
56 56
 	ioutils.FprintfIfNotEmpty(cli.out, "Cgroup Driver: %s\n", info.CgroupDriver)
57 57
 
58
-	fmt.Fprintf(cli.out, "Plugins: \n")
58
+	fmt.Fprintf(cli.out, "Plugins:\n")
59 59
 	fmt.Fprintf(cli.out, " Volume:")
60 60
 	fmt.Fprintf(cli.out, " %s", strings.Join(info.Plugins.Volume, " "))
61 61
 	fmt.Fprintf(cli.out, "\n")
... ...
@@ -84,6 +84,16 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
84 84
 			fmt.Fprintf(cli.out, " IsManager: No\n")
85 85
 		}
86 86
 	}
87
+
88
+	if len(info.Runtimes) > 0 {
89
+		fmt.Fprintf(cli.out, "Runtimes:")
90
+		for name := range info.Runtimes {
91
+			fmt.Fprintf(cli.out, " %s", name)
92
+		}
93
+		fmt.Fprint(cli.out, "\n")
94
+		fmt.Fprintf(cli.out, "Default Runtime: %s\n", info.DefaultRuntime)
95
+	}
96
+
87 97
 	ioutils.FprintfIfNotEmpty(cli.out, "Kernel Version: %s\n", info.KernelVersion)
88 98
 	ioutils.FprintfIfNotEmpty(cli.out, "Operating System: %s\n", info.OperatingSystem)
89 99
 	ioutils.FprintfIfNotEmpty(cli.out, "OSType: %s\n", info.OSType)
... ...
@@ -388,6 +388,10 @@ func loadDaemonCliConfig(config *daemon.Config, flags *flag.FlagSet, commonConfi
388 388
 		}
389 389
 	}
390 390
 
391
+	if err := daemon.ValidateConfiguration(config); err != nil {
392
+		return nil, err
393
+	}
394
+
391 395
 	// Regardless of whether the user sets it to true or false, if they
392 396
 	// specify TLSVerify at all then we need to turn on TLS
393 397
 	if config.IsValueSet(cliflags.TLSVerifyKey) {
... ...
@@ -74,6 +74,7 @@ func (cli *DaemonCli) getPlatformRemoteOptions() []libcontainerd.RemoteOption {
74 74
 	if cli.Config.LiveRestore {
75 75
 		opts = append(opts, libcontainerd.WithLiveRestore(true))
76 76
 	}
77
+	opts = append(opts, libcontainerd.WithRuntimePath(daemon.DefaultRuntimeBinary))
77 78
 	return opts
78 79
 }
79 80
 
... ...
@@ -14,6 +14,7 @@ import (
14 14
 	"github.com/docker/docker/pkg/discovery"
15 15
 	flag "github.com/docker/docker/pkg/mflag"
16 16
 	"github.com/docker/docker/registry"
17
+	"github.com/docker/engine-api/types"
17 18
 	"github.com/imdario/mergo"
18 19
 )
19 20
 
... ...
@@ -40,6 +41,7 @@ const (
40 40
 var flatOptions = map[string]bool{
41 41
 	"cluster-store-opts": true,
42 42
 	"log-opts":           true,
43
+	"runtimes":           true,
43 44
 }
44 45
 
45 46
 // LogConfig represents the default log configuration.
... ...
@@ -200,7 +202,7 @@ func ReloadConfiguration(configFile string, flags *flag.FlagSet, reload func(*Co
200 200
 		return err
201 201
 	}
202 202
 
203
-	if err := validateConfiguration(newConfig); err != nil {
203
+	if err := ValidateConfiguration(newConfig); err != nil {
204 204
 		return fmt.Errorf("file configuration validation failed (%v)", err)
205 205
 	}
206 206
 
... ...
@@ -224,7 +226,7 @@ func MergeDaemonConfigurations(flagsConfig *Config, flags *flag.FlagSet, configF
224 224
 		return nil, err
225 225
 	}
226 226
 
227
-	if err := validateConfiguration(fileConfig); err != nil {
227
+	if err := ValidateConfiguration(fileConfig); err != nil {
228 228
 		return nil, fmt.Errorf("file configuration validation failed (%v)", err)
229 229
 	}
230 230
 
... ...
@@ -233,6 +235,12 @@ func MergeDaemonConfigurations(flagsConfig *Config, flags *flag.FlagSet, configF
233 233
 		return nil, err
234 234
 	}
235 235
 
236
+	// We need to validate again once both fileConfig and flagsConfig
237
+	// have been merged
238
+	if err := ValidateConfiguration(fileConfig); err != nil {
239
+		return nil, fmt.Errorf("file configuration validation failed (%v)", err)
240
+	}
241
+
236 242
 	return fileConfig, nil
237 243
 }
238 244
 
... ...
@@ -381,10 +389,10 @@ func findConfigurationConflicts(config map[string]interface{}, flags *flag.FlagS
381 381
 	return nil
382 382
 }
383 383
 
384
-// validateConfiguration validates some specific configs.
384
+// ValidateConfiguration validates some specific configs.
385 385
 // such as config.DNS, config.Labels, config.DNSSearch,
386 386
 // as well as config.MaxConcurrentDownloads, config.MaxConcurrentUploads.
387
-func validateConfiguration(config *Config) error {
387
+func ValidateConfiguration(config *Config) error {
388 388
 	// validate DNS
389 389
 	for _, dns := range config.DNS {
390 390
 		if _, err := opts.ValidateIPAddress(dns); err != nil {
... ...
@@ -415,5 +423,20 @@ func validateConfiguration(config *Config) error {
415 415
 	if config.IsValueSet("max-concurrent-uploads") && config.MaxConcurrentUploads != nil && *config.MaxConcurrentUploads < 0 {
416 416
 		return fmt.Errorf("invalid max concurrent uploads: %d", *config.MaxConcurrentUploads)
417 417
 	}
418
+
419
+	// validate that "default" runtime is not reset
420
+	if runtimes := config.GetAllRuntimes(); len(runtimes) > 0 {
421
+		if _, ok := runtimes[types.DefaultRuntimeName]; ok {
422
+			return fmt.Errorf("runtime name '%s' is reserved", types.DefaultRuntimeName)
423
+		}
424
+	}
425
+
426
+	if defaultRuntime := config.GetDefaultRuntimeName(); defaultRuntime != "" && defaultRuntime != types.DefaultRuntimeName {
427
+		runtimes := config.GetAllRuntimes()
428
+		if _, ok := runtimes[defaultRuntime]; !ok {
429
+			return fmt.Errorf("specified default runtime '%s' does not exist", defaultRuntime)
430
+		}
431
+	}
432
+
418 433
 	return nil
419 434
 }
... ...
@@ -216,7 +216,7 @@ func TestValidateConfiguration(t *testing.T) {
216 216
 		},
217 217
 	}
218 218
 
219
-	err := validateConfiguration(c1)
219
+	err := ValidateConfiguration(c1)
220 220
 	if err == nil {
221 221
 		t.Fatal("expected error, got nil")
222 222
 	}
... ...
@@ -227,7 +227,7 @@ func TestValidateConfiguration(t *testing.T) {
227 227
 		},
228 228
 	}
229 229
 
230
-	err = validateConfiguration(c2)
230
+	err = ValidateConfiguration(c2)
231 231
 	if err != nil {
232 232
 		t.Fatalf("expected no error, got error %v", err)
233 233
 	}
... ...
@@ -238,7 +238,7 @@ func TestValidateConfiguration(t *testing.T) {
238 238
 		},
239 239
 	}
240 240
 
241
-	err = validateConfiguration(c3)
241
+	err = ValidateConfiguration(c3)
242 242
 	if err != nil {
243 243
 		t.Fatalf("expected no error, got error %v", err)
244 244
 	}
... ...
@@ -249,7 +249,7 @@ func TestValidateConfiguration(t *testing.T) {
249 249
 		},
250 250
 	}
251 251
 
252
-	err = validateConfiguration(c4)
252
+	err = ValidateConfiguration(c4)
253 253
 	if err == nil {
254 254
 		t.Fatal("expected error, got nil")
255 255
 	}
... ...
@@ -260,7 +260,7 @@ func TestValidateConfiguration(t *testing.T) {
260 260
 		},
261 261
 	}
262 262
 
263
-	err = validateConfiguration(c5)
263
+	err = ValidateConfiguration(c5)
264 264
 	if err != nil {
265 265
 		t.Fatalf("expected no error, got error %v", err)
266 266
 	}
... ...
@@ -271,7 +271,7 @@ func TestValidateConfiguration(t *testing.T) {
271 271
 		},
272 272
 	}
273 273
 
274
-	err = validateConfiguration(c6)
274
+	err = ValidateConfiguration(c6)
275 275
 	if err == nil {
276 276
 		t.Fatal("expected error, got nil")
277 277
 	}
... ...
@@ -8,6 +8,7 @@ import (
8 8
 	"github.com/docker/docker/opts"
9 9
 	flag "github.com/docker/docker/pkg/mflag"
10 10
 	runconfigopts "github.com/docker/docker/runconfig/opts"
11
+	"github.com/docker/engine-api/types"
11 12
 	"github.com/docker/go-units"
12 13
 )
13 14
 
... ...
@@ -30,6 +31,8 @@ type Config struct {
30 30
 	ExecRoot             string                   `json:"exec-root,omitempty"`
31 31
 	RemappedRoot         string                   `json:"userns-remap,omitempty"`
32 32
 	Ulimits              map[string]*units.Ulimit `json:"default-ulimits,omitempty"`
33
+	Runtimes             map[string]types.Runtime `json:"runtimes,omitempty"`
34
+	DefaultRuntime       string                   `json:"default-runtime,omitempty"`
33 35
 }
34 36
 
35 37
 // bridgeConfig stores all the bridge driver specific
... ...
@@ -83,6 +86,37 @@ func (config *Config) InstallFlags(cmd *flag.FlagSet, usageFn func(string) strin
83 83
 	cmd.StringVar(&config.RemappedRoot, []string{"-userns-remap"}, "", usageFn("User/Group setting for user namespaces"))
84 84
 	cmd.StringVar(&config.ContainerdAddr, []string{"-containerd"}, "", usageFn("Path to containerd socket"))
85 85
 	cmd.BoolVar(&config.LiveRestore, []string{"-live-restore"}, false, usageFn("Enable live restore of docker when containers are still running"))
86
+	config.Runtimes = make(map[string]types.Runtime)
87
+	cmd.Var(runconfigopts.NewNamedRuntimeOpt("runtimes", &config.Runtimes), []string{"-add-runtime"}, usageFn("Register an additional OCI compatible runtime"))
88
+	cmd.StringVar(&config.DefaultRuntime, []string{"-default-runtime"}, types.DefaultRuntimeName, usageFn("Default OCI runtime to be used"))
86 89
 
87 90
 	config.attachExperimentalFlags(cmd, usageFn)
88 91
 }
92
+
93
+// GetRuntime returns the runtime path and arguments for a given
94
+// runtime name
95
+func (config *Config) GetRuntime(name string) *types.Runtime {
96
+	config.reloadLock.Lock()
97
+	defer config.reloadLock.Unlock()
98
+	if rt, ok := config.Runtimes[name]; ok {
99
+		return &rt
100
+	}
101
+	return nil
102
+}
103
+
104
+// GetDefaultRuntimeName returns the current default runtime
105
+func (config *Config) GetDefaultRuntimeName() string {
106
+	config.reloadLock.Lock()
107
+	rt := config.DefaultRuntime
108
+	config.reloadLock.Unlock()
109
+
110
+	return rt
111
+}
112
+
113
+// GetAllRuntimes returns a copy of the runtimes map
114
+func (config *Config) GetAllRuntimes() map[string]types.Runtime {
115
+	config.reloadLock.Lock()
116
+	rts := config.Runtimes
117
+	config.reloadLock.Unlock()
118
+	return rts
119
+}
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"os"
5 5
 
6 6
 	flag "github.com/docker/docker/pkg/mflag"
7
+	"github.com/docker/engine-api/types"
7 8
 )
8 9
 
9 10
 var (
... ...
@@ -40,3 +41,19 @@ func (config *Config) InstallFlags(cmd *flag.FlagSet, usageFn func(string) strin
40 40
 	cmd.StringVar(&config.bridgeConfig.Iface, []string{"b", "-bridge"}, "", "Attach containers to a virtual switch")
41 41
 	cmd.StringVar(&config.SocketGroup, []string{"G", "-group"}, "", usageFn("Users or groups that can access the named pipe"))
42 42
 }
43
+
44
+// GetRuntime returns the runtime path and arguments for a given
45
+// runtime name
46
+func (config *Config) GetRuntime(name string) *types.Runtime {
47
+	return nil
48
+}
49
+
50
+// GetDefaultRuntimeName returns the current default runtime
51
+func (config *Config) GetDefaultRuntimeName() string {
52
+	return types.DefaultRuntimeName
53
+}
54
+
55
+// GetAllRuntimes returns a copy of the runtimes map
56
+func (config *Config) GetAllRuntimes() map[string]types.Runtime {
57
+	return map[string]types.Runtime{}
58
+}
... ...
@@ -60,6 +60,10 @@ import (
60 60
 )
61 61
 
62 62
 var (
63
+	// DefaultRuntimeBinary is the default runtime to be used by
64
+	// containerd if none is specified
65
+	DefaultRuntimeBinary = "docker-runc"
66
+
63 67
 	errSystemNotSupported = fmt.Errorf("The Docker daemon is not supported on this platform.")
64 68
 )
65 69
 
... ...
@@ -811,10 +815,24 @@ func (daemon *Daemon) initDiscovery(config *Config) error {
811 811
 // - Cluster discovery (reconfigure and restart).
812 812
 // - Daemon live restore
813 813
 func (daemon *Daemon) Reload(config *Config) error {
814
+	var err error
815
+	// used to hold reloaded changes
816
+	attributes := map[string]string{}
817
+
818
+	// We need defer here to ensure the lock is released as
819
+	// daemon.SystemInfo() will try to get it too
820
+	defer func() {
821
+		if err == nil {
822
+			daemon.LogDaemonEventWithAttributes("reload", attributes)
823
+		}
824
+	}()
825
+
814 826
 	daemon.configStore.reloadLock.Lock()
815 827
 	defer daemon.configStore.reloadLock.Unlock()
816 828
 
817
-	if err := daemon.reloadClusterDiscovery(config); err != nil {
829
+	daemon.platformReload(config, &attributes)
830
+
831
+	if err = daemon.reloadClusterDiscovery(config); err != nil {
818 832
 		return err
819 833
 	}
820 834
 
... ...
@@ -859,7 +877,6 @@ func (daemon *Daemon) Reload(config *Config) error {
859 859
 	}
860 860
 
861 861
 	// We emit daemon reload event here with updatable configurations
862
-	attributes := map[string]string{}
863 862
 	attributes["debug"] = fmt.Sprintf("%t", daemon.configStore.Debug)
864 863
 	attributes["cluster-store"] = daemon.configStore.ClusterStore
865 864
 	if daemon.configStore.ClusterOpts != nil {
... ...
@@ -877,7 +894,6 @@ func (daemon *Daemon) Reload(config *Config) error {
877 877
 	}
878 878
 	attributes["max-concurrent-downloads"] = fmt.Sprintf("%d", *daemon.configStore.MaxConcurrentDownloads)
879 879
 	attributes["max-concurrent-uploads"] = fmt.Sprintf("%d", *daemon.configStore.MaxConcurrentUploads)
880
-	daemon.LogDaemonEventWithAttributes("reload", attributes)
881 880
 
882 881
 	return nil
883 882
 }
... ...
@@ -73,6 +73,10 @@ func verifyPlatformContainerSettings(daemon *Daemon, hostConfig *containertypes.
73 73
 	return warnings, nil
74 74
 }
75 75
 
76
+// platformReload update configuration with platform specific options
77
+func (daemon *Daemon) platformReload(config *Config, attributes *map[string]string) {
78
+}
79
+
76 80
 // verifyDaemonSettings performs validation of daemon config struct
77 81
 func verifyDaemonSettings(config *Config) error {
78 82
 	// checkSystem validates platform-specific requirements
... ...
@@ -3,6 +3,7 @@
3 3
 package daemon
4 4
 
5 5
 import (
6
+	"bytes"
6 7
 	"fmt"
7 8
 	"io/ioutil"
8 9
 	"net"
... ...
@@ -515,9 +516,42 @@ func verifyPlatformContainerSettings(daemon *Daemon, hostConfig *containertypes.
515 515
 			return warnings, fmt.Errorf("cgroup-parent for systemd cgroup should be a valid slice named as \"xxx.slice\"")
516 516
 		}
517 517
 	}
518
+	if hostConfig.Runtime == "" {
519
+		hostConfig.Runtime = daemon.configStore.GetDefaultRuntimeName()
520
+	}
521
+
522
+	if rt := daemon.configStore.GetRuntime(hostConfig.Runtime); rt == nil {
523
+		return warnings, fmt.Errorf("Unknown runtime specified %s", hostConfig.Runtime)
524
+	}
525
+
518 526
 	return warnings, nil
519 527
 }
520 528
 
529
+// platformReload update configuration with platform specific options
530
+func (daemon *Daemon) platformReload(config *Config, attributes *map[string]string) {
531
+	if config.IsValueSet("runtimes") {
532
+		daemon.configStore.Runtimes = config.Runtimes
533
+		// Always set the default one
534
+		daemon.configStore.Runtimes[types.DefaultRuntimeName] = types.Runtime{Path: DefaultRuntimeBinary}
535
+	}
536
+
537
+	if config.DefaultRuntime != "" {
538
+		daemon.configStore.DefaultRuntime = config.DefaultRuntime
539
+	}
540
+
541
+	// Update attributes
542
+	var runtimeList bytes.Buffer
543
+	for name, rt := range daemon.configStore.Runtimes {
544
+		if runtimeList.Len() > 0 {
545
+			runtimeList.WriteRune(' ')
546
+		}
547
+		runtimeList.WriteString(fmt.Sprintf("%s:%s", name, rt))
548
+	}
549
+
550
+	(*attributes)["runtimes"] = runtimeList.String()
551
+	(*attributes)["default-runtime"] = daemon.configStore.DefaultRuntime
552
+}
553
+
521 554
 // verifyDaemonSettings performs validation of daemon config struct
522 555
 func verifyDaemonSettings(config *Config) error {
523 556
 	// Check for mutually incompatible config options
... ...
@@ -538,6 +572,15 @@ func verifyDaemonSettings(config *Config) error {
538 538
 			return fmt.Errorf("cgroup-parent for systemd cgroup should be a valid slice named as \"xxx.slice\"")
539 539
 		}
540 540
 	}
541
+
542
+	if config.DefaultRuntime == "" {
543
+		config.DefaultRuntime = types.DefaultRuntimeName
544
+	}
545
+	if config.Runtimes == nil {
546
+		config.Runtimes = make(map[string]types.Runtime)
547
+	}
548
+	config.Runtimes[types.DefaultRuntimeName] = types.Runtime{Path: DefaultRuntimeBinary}
549
+
541 550
 	return nil
542 551
 }
543 552
 
... ...
@@ -156,6 +156,10 @@ func verifyPlatformContainerSettings(daemon *Daemon, hostConfig *containertypes.
156 156
 	return warnings, nil
157 157
 }
158 158
 
159
+// platformReload update configuration with platform specific options
160
+func (daemon *Daemon) platformReload(config *Config, attributes *map[string]string) {
161
+}
162
+
159 163
 // verifyDaemonSettings performs validation of daemon config struct
160 164
 func verifyDaemonSettings(config *Config) error {
161 165
 	return nil
... ...
@@ -131,6 +131,8 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) {
131 131
 		v.CPUCfsQuota = sysInfo.CPUCfsQuota
132 132
 		v.CPUShares = sysInfo.CPUShares
133 133
 		v.CPUSet = sysInfo.Cpuset
134
+		v.Runtimes = daemon.configStore.GetAllRuntimes()
135
+		v.DefaultRuntime = daemon.configStore.GetDefaultRuntimeName()
134 136
 	}
135 137
 
136 138
 	hostname := ""
... ...
@@ -132,15 +132,25 @@ func (daemon *Daemon) containerStart(container *container.Container) (err error)
132 132
 		return err
133 133
 	}
134 134
 
135
-	if err := daemon.containerd.Create(container.ID, *spec, libcontainerd.WithRestartManager(container.RestartManager(true))); err != nil {
135
+	createOptions := []libcontainerd.CreateOption{libcontainerd.WithRestartManager(container.RestartManager(true))}
136
+	copts, err := daemon.getLibcontainerdCreateOptions(container)
137
+	if err != nil {
138
+		return err
139
+	}
140
+	if copts != nil {
141
+		createOptions = append(createOptions, *copts...)
142
+	}
143
+
144
+	if err := daemon.containerd.Create(container.ID, *spec, createOptions...); err != nil {
136 145
 		errDesc := grpc.ErrorDesc(err)
137 146
 		logrus.Errorf("Create container failed with error: %s", errDesc)
138 147
 		// if we receive an internal error from the initial start of a container then lets
139 148
 		// return it instead of entering the restart loop
140 149
 		// set to 127 for container cmd not found/does not exist)
141
-		if strings.Contains(errDesc, "executable file not found") ||
142
-			strings.Contains(errDesc, "no such file or directory") ||
143
-			strings.Contains(errDesc, "system cannot find the file specified") {
150
+		if strings.Contains(errDesc, container.Path) &&
151
+			(strings.Contains(errDesc, "executable file not found") ||
152
+				strings.Contains(errDesc, "no such file or directory") ||
153
+				strings.Contains(errDesc, "system cannot find the file specified")) {
144 154
 			container.ExitCode = 127
145 155
 		}
146 156
 		// set to 126 for container cmd can't be invoked errors
147 157
new file mode 100644
... ...
@@ -0,0 +1,20 @@
0
+package daemon
1
+
2
+import (
3
+	"fmt"
4
+
5
+	"github.com/docker/docker/container"
6
+	"github.com/docker/docker/libcontainerd"
7
+)
8
+
9
+func (daemon *Daemon) getLibcontainerdCreateOptions(container *container.Container) (*[]libcontainerd.CreateOption, error) {
10
+	createOptions := []libcontainerd.CreateOption{}
11
+
12
+	rt := daemon.configStore.GetRuntime(container.HostConfig.Runtime)
13
+	if rt == nil {
14
+		return nil, fmt.Errorf("No such runtime '%s'", container.HostConfig.Runtime)
15
+	}
16
+	createOptions = append(createOptions, libcontainerd.WithRuntime(rt.Path, rt.Args))
17
+
18
+	return &createOptions, nil
19
+}
0 20
new file mode 100644
... ...
@@ -0,0 +1,10 @@
0
+package daemon
1
+
2
+import (
3
+	"github.com/docker/docker/container"
4
+	"github.com/docker/docker/libcontainerd"
5
+)
6
+
7
+func (daemon *Daemon) getLibcontainerdCreateOptions(container *container.Container) (*[]libcontainerd.CreateOption, error) {
8
+	return &[]libcontainerd.CreateOption{}, nil
9
+}
... ...
@@ -78,6 +78,7 @@ Creates a new container.
78 78
       --privileged                  Give extended privileges to this container
79 79
       --read-only                   Mount the container's root filesystem as read only
80 80
       --restart="no"                Restart policy (no, on-failure[:max-retry], always, unless-stopped)
81
+      --runtime=""                  Name of the runtime to be used for that container
81 82
       --security-opt=[]             Security options
82 83
       --stop-signal="SIGTERM"       Signal to stop a container
83 84
       --shm-size=[]                 Size of `/dev/shm`. The format is `<number><unit>`. `number` must be greater than `0`.  Unit is optional and can be `b` (bytes), `k` (kilobytes), `m` (megabytes), or `g` (gigabytes). If you omit the unit, the system uses bytes. If you omit the size entirely, the system uses `64m`.
... ...
@@ -60,6 +60,7 @@ weight = -1
60 60
       -p, --pidfile="/var/run/docker.pid"    Path to use for daemon PID file
61 61
       --raw-logs                             Full timestamps without ANSI coloring
62 62
       --registry-mirror=[]                   Preferred Docker registry mirror
63
+      --add-runtime=[]                       Register an additional OCI compatible runtime
63 64
       -s, --storage-driver=""                Storage driver to use
64 65
       --selinux-enabled                      Enable selinux support
65 66
       --storage-opt=[]                       Set storage driver options
... ...
@@ -572,6 +573,31 @@ The Docker daemon relies on a
572 572
 (invoked via the `containerd` daemon) as its interface to the Linux
573 573
 kernel `namespaces`, `cgroups`, and `SELinux`.
574 574
 
575
+Runtimes can be registered with the daemon either via the
576
+configuration file or using the `--add-runtime` command line argument.
577
+
578
+The following is an example adding 2 runtimes via the configuration:
579
+```json
580
+	"default-runtime": "runc",
581
+	"runtimes": {
582
+		"runc": {
583
+			"path": "runc"
584
+		},
585
+		"custom": {
586
+			"path": "/usr/local/bin/my-runc-replacement",
587
+			"runtimeArgs": [
588
+				"--debug"
589
+			]
590
+		}
591
+	}
592
+```
593
+
594
+This is the same example via the command line:
595
+
596
+    $ sudo dockerd --add-runtime runc=runc --add-runtime custom=/usr/local/bin/my-runc-replacement
597
+
598
+**Note**: defining runtime arguments via the command line is not supported.
599
+
575 600
 ## Options for the runtime
576 601
 
577 602
 You can configure the runtime using options specified
... ...
@@ -1014,7 +1040,19 @@ This is a full example of the allowed configuration options in the file:
1014 1014
 	"raw-logs": false,
1015 1015
 	"registry-mirrors": [],
1016 1016
 	"insecure-registries": [],
1017
-	"disable-legacy-registry": false
1017
+	"disable-legacy-registry": false,
1018
+	"default-runtime": "runc",
1019
+	"runtimes": {
1020
+		"runc": {
1021
+			"path": "runc"
1022
+		},
1023
+		"custom": {
1024
+			"path": "/usr/local/bin/my-runc-replacement",
1025
+			"runtimeArgs": [
1026
+				"--debug"
1027
+			]
1028
+		}
1029
+	}
1018 1030
 }
1019 1031
 ```
1020 1032
 
... ...
@@ -1036,6 +1074,11 @@ The list of currently supported options that can be reconfigured is this:
1036 1036
 - `labels`: it replaces the daemon labels with a new set of labels.
1037 1037
 - `max-concurrent-downloads`: it updates the max concurrent downloads for each pull.
1038 1038
 - `max-concurrent-uploads`: it updates the max concurrent uploads for each push.
1039
+- `default-runtime`: it updates the runtime to be used if not is
1040
+  specified at container creation. It defaults to "default" which is
1041
+  the runtime shipped with the official docker packages.
1042
+- `runtimes`: it updates the list of available OCI runtimes that can
1043
+  be used to run containers
1039 1044
 
1040 1045
 Updating and reloading the cluster configurations such as `--cluster-store`,
1041 1046
 `--cluster-advertise` and `--cluster-store-opts` will take effect only if
... ...
@@ -89,6 +89,7 @@ parent = "smn_cli"
89 89
       --read-only                   Mount the container's root filesystem as read only
90 90
       --restart="no"                Restart policy (no, on-failure[:max-retry], always, unless-stopped)
91 91
       --rm                          Automatically remove the container when it exits
92
+      --runtime=""                  Name of the runtime to be used for that container
92 93
       --shm-size=[]                 Size of `/dev/shm`. The format is `<number><unit>`. `number` must be greater than `0`.  Unit is optional and can be `b` (bytes), `k` (kilobytes), `m` (megabytes), or `g` (gigabytes). If you omit the unit, the system uses bytes. If you omit the size entirely, the system uses `64m`.
93 94
       --security-opt=[]             Security Options
94 95
       --sig-proxy=true              Proxy received signals to the process
... ...
@@ -2378,3 +2378,183 @@ func (s *DockerDaemonSuite) TestDaemonDnsOptionsInHostMode(c *check.C) {
2378 2378
 	out, _ := s.d.Cmd("run", "--net=host", "busybox", "cat", "/etc/resolv.conf")
2379 2379
 	c.Assert(out, checker.Contains, expectedOutput, check.Commentf("Expected '%s', but got %q", expectedOutput, out))
2380 2380
 }
2381
+
2382
+func (s *DockerDaemonSuite) TestRunWithRuntimeFromConfigFile(c *check.C) {
2383
+	conf, err := ioutil.TempFile("", "config-file-")
2384
+	c.Assert(err, check.IsNil)
2385
+	configName := conf.Name()
2386
+	conf.Close()
2387
+	defer os.Remove(configName)
2388
+
2389
+	config := `
2390
+{
2391
+    "runtimes": {
2392
+        "oci": {
2393
+            "path": "docker-runc"
2394
+        },
2395
+        "vm": {
2396
+            "path": "/usr/local/bin/vm-manager",
2397
+            "runtimeArgs": [
2398
+                "--debug"
2399
+            ]
2400
+        }
2401
+    }
2402
+}
2403
+`
2404
+	ioutil.WriteFile(configName, []byte(config), 0644)
2405
+	err = s.d.Start("--config-file", configName)
2406
+	c.Assert(err, check.IsNil)
2407
+
2408
+	// Run with default runtime
2409
+	out, err := s.d.Cmd("run", "--rm", "busybox", "ls")
2410
+	c.Assert(err, check.IsNil, check.Commentf(out))
2411
+
2412
+	// Run with default runtime explicitely
2413
+	out, err = s.d.Cmd("run", "--rm", "--runtime=default", "busybox", "ls")
2414
+	c.Assert(err, check.IsNil, check.Commentf(out))
2415
+
2416
+	// Run with oci (same path as default) but keep it around
2417
+	out, err = s.d.Cmd("run", "--name", "oci-runtime-ls", "--runtime=oci", "busybox", "ls")
2418
+	c.Assert(err, check.IsNil, check.Commentf(out))
2419
+
2420
+	// Run with "vm"
2421
+	out, err = s.d.Cmd("run", "--rm", "--runtime=vm", "busybox", "ls")
2422
+	c.Assert(err, check.NotNil, check.Commentf(out))
2423
+	c.Assert(out, checker.Contains, "/usr/local/bin/vm-manager: no such file or directory")
2424
+
2425
+	// Reset config to only have the default
2426
+	config = `
2427
+{
2428
+    "runtimes": {
2429
+    }
2430
+}
2431
+`
2432
+	ioutil.WriteFile(configName, []byte(config), 0644)
2433
+	syscall.Kill(s.d.cmd.Process.Pid, syscall.SIGHUP)
2434
+	// Give daemon time to reload config
2435
+	<-time.After(1 * time.Second)
2436
+
2437
+	// Run with default runtime
2438
+	out, err = s.d.Cmd("run", "--rm", "--runtime=default", "busybox", "ls")
2439
+	c.Assert(err, check.IsNil, check.Commentf(out))
2440
+
2441
+	// Run with "oci"
2442
+	out, err = s.d.Cmd("run", "--rm", "--runtime=oci", "busybox", "ls")
2443
+	c.Assert(err, check.NotNil, check.Commentf(out))
2444
+	c.Assert(out, checker.Contains, "Unknown runtime specified oci")
2445
+
2446
+	// Start previously created container with oci
2447
+	out, err = s.d.Cmd("start", "oci-runtime-ls")
2448
+	c.Assert(err, check.NotNil, check.Commentf(out))
2449
+	c.Assert(out, checker.Contains, "Unknown runtime specified oci")
2450
+
2451
+	// Check that we can't override the default runtime
2452
+	config = `
2453
+{
2454
+    "runtimes": {
2455
+        "default": {
2456
+            "path": "docker-runc"
2457
+        }
2458
+    }
2459
+}
2460
+`
2461
+	ioutil.WriteFile(configName, []byte(config), 0644)
2462
+	syscall.Kill(s.d.cmd.Process.Pid, syscall.SIGHUP)
2463
+	// Give daemon time to reload config
2464
+	<-time.After(1 * time.Second)
2465
+
2466
+	content, _ := ioutil.ReadFile(s.d.logFile.Name())
2467
+	c.Assert(string(content), checker.Contains, `file configuration validation failed (runtime name 'default' is reserved)`)
2468
+
2469
+	// Check that we can select a default runtime
2470
+	config = `
2471
+{
2472
+    "default-runtime": "vm",
2473
+    "runtimes": {
2474
+        "oci": {
2475
+            "path": "docker-runc"
2476
+        },
2477
+        "vm": {
2478
+            "path": "/usr/local/bin/vm-manager",
2479
+            "runtimeArgs": [
2480
+                "--debug"
2481
+            ]
2482
+        }
2483
+    }
2484
+}
2485
+`
2486
+	ioutil.WriteFile(configName, []byte(config), 0644)
2487
+	syscall.Kill(s.d.cmd.Process.Pid, syscall.SIGHUP)
2488
+	// Give daemon time to reload config
2489
+	<-time.After(1 * time.Second)
2490
+
2491
+	out, err = s.d.Cmd("run", "--rm", "busybox", "ls")
2492
+	c.Assert(err, check.NotNil, check.Commentf(out))
2493
+	c.Assert(out, checker.Contains, "/usr/local/bin/vm-manager: no such file or directory")
2494
+
2495
+	// Run with default runtime explicitely
2496
+	out, err = s.d.Cmd("run", "--rm", "--runtime=default", "busybox", "ls")
2497
+	c.Assert(err, check.IsNil, check.Commentf(out))
2498
+}
2499
+
2500
+func (s *DockerDaemonSuite) TestRunWithRuntimeFromCommandLine(c *check.C) {
2501
+	err := s.d.Start("--add-runtime", "oci=docker-runc", "--add-runtime", "vm=/usr/local/bin/vm-manager")
2502
+	c.Assert(err, check.IsNil)
2503
+
2504
+	// Run with default runtime
2505
+	out, err := s.d.Cmd("run", "--rm", "busybox", "ls")
2506
+	c.Assert(err, check.IsNil, check.Commentf(out))
2507
+
2508
+	// Run with default runtime explicitely
2509
+	out, err = s.d.Cmd("run", "--rm", "--runtime=default", "busybox", "ls")
2510
+	c.Assert(err, check.IsNil, check.Commentf(out))
2511
+
2512
+	// Run with oci (same path as default) but keep it around
2513
+	out, err = s.d.Cmd("run", "--name", "oci-runtime-ls", "--runtime=oci", "busybox", "ls")
2514
+	c.Assert(err, check.IsNil, check.Commentf(out))
2515
+
2516
+	// Run with "vm"
2517
+	out, err = s.d.Cmd("run", "--rm", "--runtime=vm", "busybox", "ls")
2518
+	c.Assert(err, check.NotNil, check.Commentf(out))
2519
+	c.Assert(out, checker.Contains, "/usr/local/bin/vm-manager: no such file or directory")
2520
+
2521
+	// Start a daemon without any extra runtimes
2522
+	s.d.Stop()
2523
+	err = s.d.Start()
2524
+	c.Assert(err, check.IsNil)
2525
+
2526
+	// Run with default runtime
2527
+	out, err = s.d.Cmd("run", "--rm", "--runtime=default", "busybox", "ls")
2528
+	c.Assert(err, check.IsNil, check.Commentf(out))
2529
+
2530
+	// Run with "oci"
2531
+	out, err = s.d.Cmd("run", "--rm", "--runtime=oci", "busybox", "ls")
2532
+	c.Assert(err, check.NotNil, check.Commentf(out))
2533
+	c.Assert(out, checker.Contains, "Unknown runtime specified oci")
2534
+
2535
+	// Start previously created container with oci
2536
+	out, err = s.d.Cmd("start", "oci-runtime-ls")
2537
+	c.Assert(err, check.NotNil, check.Commentf(out))
2538
+	c.Assert(out, checker.Contains, "Unknown runtime specified oci")
2539
+
2540
+	// Check that we can't override the default runtime
2541
+	s.d.Stop()
2542
+	err = s.d.Start("--add-runtime", "default=docker-runc")
2543
+	c.Assert(err, check.NotNil)
2544
+
2545
+	content, _ := ioutil.ReadFile(s.d.logFile.Name())
2546
+	c.Assert(string(content), checker.Contains, `runtime name 'default' is reserved`)
2547
+
2548
+	// Check that we can select a default runtime
2549
+	s.d.Stop()
2550
+	err = s.d.Start("--default-runtime=vm", "--add-runtime", "oci=docker-runc", "--add-runtime", "vm=/usr/local/bin/vm-manager")
2551
+	c.Assert(err, check.IsNil)
2552
+
2553
+	out, err = s.d.Cmd("run", "--rm", "busybox", "ls")
2554
+	c.Assert(err, check.NotNil, check.Commentf(out))
2555
+	c.Assert(out, checker.Contains, "/usr/local/bin/vm-manager: no such file or directory")
2556
+
2557
+	// Run with default runtime explicitely
2558
+	out, err = s.d.Cmd("run", "--rm", "--runtime=default", "busybox", "ls")
2559
+	c.Assert(err, check.IsNil, check.Commentf(out))
2560
+}
... ...
@@ -436,7 +436,8 @@ func (s *DockerDaemonSuite) TestDaemonEvents(c *check.C) {
436 436
 
437 437
 	out, err = s.d.Cmd("events", "--since=0", "--until", daemonUnixTime(c))
438 438
 	c.Assert(err, checker.IsNil)
439
-	c.Assert(out, checker.Contains, fmt.Sprintf("daemon reload %s (cluster-advertise=, cluster-store=, cluster-store-opts={}, debug=true, labels=[\"bar=foo\"], max-concurrent-downloads=1, max-concurrent-uploads=5, name=%s)", daemonID, daemonName))
439
+
440
+	c.Assert(out, checker.Contains, fmt.Sprintf("daemon reload %s (cluster-advertise=, cluster-store=, cluster-store-opts={}, debug=true, default-runtime=default, labels=[\"bar=foo\"], max-concurrent-downloads=1, max-concurrent-uploads=5, name=%s, runtimes=default:{docker-runc []})", daemonID, daemonName))
440 441
 }
441 442
 
442 443
 func (s *DockerDaemonSuite) TestDaemonEventsWithFilters(c *check.C) {
... ...
@@ -34,6 +34,10 @@ func (s *DockerSuite) TestInfoEnsureSucceeds(c *check.C) {
34 34
 		"Network:",
35 35
 	}
36 36
 
37
+	if DaemonIsLinux.Condition() {
38
+		stringsToCheck = append(stringsToCheck, "Runtimes:", "Default Runtime: default")
39
+	}
40
+
37 41
 	if utils.ExperimentalBuild() {
38 42
 		stringsToCheck = append(stringsToCheck, "Experimental: true")
39 43
 	}
... ...
@@ -21,7 +21,27 @@ type container struct {
21 21
 
22 22
 	// Platform specific fields are below here.
23 23
 	pauseMonitor
24
-	oom bool
24
+	oom         bool
25
+	runtime     string
26
+	runtimeArgs []string
27
+}
28
+
29
+type runtime struct {
30
+	path string
31
+	args []string
32
+}
33
+
34
+// WithRuntime sets the runtime to be used for the created container
35
+func WithRuntime(path string, args []string) CreateOption {
36
+	return runtime{path, args}
37
+}
38
+
39
+func (rt runtime) Apply(p interface{}) error {
40
+	if pr, ok := p.(*container); ok {
41
+		pr.runtime = rt.path
42
+		pr.runtimeArgs = rt.args
43
+	}
44
+	return nil
25 45
 }
26 46
 
27 47
 func (ctr *container) clean() error {
... ...
@@ -84,6 +104,8 @@ func (ctr *container) start() error {
84 84
 		Stderr:     ctr.fifo(syscall.Stderr),
85 85
 		// check to see if we are running in ramdisk to disable pivot root
86 86
 		NoPivotRoot: os.Getenv("DOCKER_RAMDISK") != "",
87
+		Runtime:     ctr.runtime,
88
+		RuntimeArgs: ctr.runtimeArgs,
87 89
 	}
88 90
 	ctr.client.appendContainer(ctr)
89 91
 
... ...
@@ -50,6 +50,7 @@ type remote struct {
50 50
 	clients       []*client
51 51
 	eventTsPath   string
52 52
 	pastEvents    map[string]*containerd.Event
53
+	runtime       string
53 54
 	runtimeArgs   []string
54 55
 	daemonWaitCh  chan struct{}
55 56
 	liveRestore   bool
... ...
@@ -366,11 +367,14 @@ func (r *remote) runContainerdDaemon() error {
366 366
 	args := []string{
367 367
 		"-l", fmt.Sprintf("unix://%s", r.rpcAddr),
368 368
 		"--shim", "docker-containerd-shim",
369
-		"--runtime", "docker-runc",
370 369
 		"--metrics-interval=0",
371 370
 		"--start-timeout", "2m",
372 371
 		"--state-dir", filepath.Join(r.stateDir, containerdStateDir),
373 372
 	}
373
+	if r.runtime != "" {
374
+		args = append(args, "--runtime")
375
+		args = append(args, r.runtime)
376
+	}
374 377
 	if r.debugLog {
375 378
 		args = append(args, "--debug")
376 379
 	}
... ...
@@ -428,6 +432,22 @@ func (a rpcAddr) Apply(r Remote) error {
428 428
 	return fmt.Errorf("WithRemoteAddr option not supported for this remote")
429 429
 }
430 430
 
431
+// WithRuntimePath sets the path of the runtime to be used as the
432
+// default by containerd
433
+func WithRuntimePath(rt string) RemoteOption {
434
+	return runtimePath(rt)
435
+}
436
+
437
+type runtimePath string
438
+
439
+func (rt runtimePath) Apply(r Remote) error {
440
+	if remote, ok := r.(*remote); ok {
441
+		remote.runtime = string(rt)
442
+		return nil
443
+	}
444
+	return fmt.Errorf("WithRuntime option not supported for this remote")
445
+}
446
+
431 447
 // WithRuntimeArgs sets the list of runtime args passed to containerd
432 448
 func WithRuntimeArgs(args []string) RemoteOption {
433 449
 	return runtimeArgs(args)
... ...
@@ -101,6 +101,7 @@ type ContainerOptions struct {
101 101
 	flHealthInterval    *time.Duration
102 102
 	flHealthTimeout     *time.Duration
103 103
 	flHealthRetries     *int
104
+	flRuntime           *string
104 105
 
105 106
 	Image string
106 107
 	Args  []string
... ...
@@ -189,6 +190,7 @@ func AddFlags(flags *pflag.FlagSet) *ContainerOptions {
189 189
 		flHealthInterval:    flags.Duration("health-interval", 0, "Time between running the check"),
190 190
 		flHealthTimeout:     flags.Duration("health-timeout", 0, "Maximum time to allow one check to run"),
191 191
 		flHealthRetries:     flags.Int("health-retries", 0, "Consecutive failures needed to report unhealthy"),
192
+		flRuntime:           flags.String("runtime", "", "Runtime to use for this container"),
192 193
 	}
193 194
 
194 195
 	flags.VarP(&copts.flAttach, "attach", "a", "Attach to STDIN, STDOUT or STDERR")
... ...
@@ -229,7 +231,6 @@ func AddFlags(flags *pflag.FlagSet) *ContainerOptions {
229 229
 // a HostConfig and returns them with the specified command.
230 230
 // If the specified args are not valid, it will return an error.
231 231
 func Parse(flags *pflag.FlagSet, copts *ContainerOptions) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
232
-
233 232
 	var (
234 233
 		attachStdin  = copts.flAttach.Get("stdin")
235 234
 		attachStdout = copts.flAttach.Get("stdout")
... ...
@@ -564,6 +565,7 @@ func Parse(flags *pflag.FlagSet, copts *ContainerOptions) (*container.Config, *c
564 564
 		Resources:      resources,
565 565
 		Tmpfs:          tmpfs,
566 566
 		Sysctls:        copts.flSysctls.GetAll(),
567
+		Runtime:        *copts.flRuntime,
567 568
 	}
568 569
 
569 570
 	// When allocating stdin in attached mode, close stdin at client disconnect
570 571
new file mode 100644
... ...
@@ -0,0 +1,73 @@
0
+package opts
1
+
2
+import (
3
+	"fmt"
4
+	"strings"
5
+
6
+	"github.com/docker/engine-api/types"
7
+)
8
+
9
+// RuntimeOpt defines a map of Runtimes
10
+type RuntimeOpt struct {
11
+	name   string
12
+	values *map[string]types.Runtime
13
+}
14
+
15
+// NewNamedRuntimeOpt creates a new RuntimeOpt
16
+func NewNamedRuntimeOpt(name string, ref *map[string]types.Runtime) *RuntimeOpt {
17
+	if ref == nil {
18
+		ref = &map[string]types.Runtime{}
19
+	}
20
+	return &RuntimeOpt{name: name, values: ref}
21
+}
22
+
23
+// Name returns the name of the NamedListOpts in the configuration.
24
+func (o *RuntimeOpt) Name() string {
25
+	return o.name
26
+}
27
+
28
+// Set validates and updates the list of Runtimes
29
+func (o *RuntimeOpt) Set(val string) error {
30
+	parts := strings.SplitN(val, "=", 2)
31
+	if len(parts) != 2 {
32
+		return fmt.Errorf("invalid runtime argument: %s", val)
33
+	}
34
+
35
+	parts[0] = strings.TrimSpace(parts[0])
36
+	parts[1] = strings.TrimSpace(parts[1])
37
+	if parts[0] == "" || parts[1] == "" {
38
+		return fmt.Errorf("invalid runtime argument: %s", val)
39
+	}
40
+
41
+	parts[0] = strings.ToLower(parts[0])
42
+	if parts[0] == types.DefaultRuntimeName {
43
+		return fmt.Errorf("runtime name 'default' is reserved")
44
+	}
45
+
46
+	if _, ok := (*o.values)[parts[0]]; ok {
47
+		return fmt.Errorf("runtime '%s' was already defined", parts[0])
48
+	}
49
+
50
+	(*o.values)[parts[0]] = types.Runtime{Path: parts[1]}
51
+
52
+	return nil
53
+}
54
+
55
+// String returns Runtime values as a string.
56
+func (o *RuntimeOpt) String() string {
57
+	var out []string
58
+	for k := range *o.values {
59
+		out = append(out, k)
60
+	}
61
+
62
+	return fmt.Sprintf("%v", out)
63
+}
64
+
65
+// GetMap returns a map of Runtimes (name: path)
66
+func (o *RuntimeOpt) GetMap() map[string]types.Runtime {
67
+	if o.values != nil {
68
+		return *o.values
69
+	}
70
+
71
+	return map[string]types.Runtime{}
72
+}