2015-07-09 22:12:36 +00:00
|
|
|
// +build !windows
|
|
|
|
|
2018-02-05 21:05:59 +00:00
|
|
|
package runconfig // import "github.com/docker/docker/runconfig"
|
2015-07-01 08:16:36 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"testing"
|
2015-12-18 18:36:17 +00:00
|
|
|
|
2016-09-06 18:18:12 +00:00
|
|
|
"github.com/docker/docker/api/types/container"
|
2016-06-07 19:05:43 +00:00
|
|
|
"github.com/docker/docker/pkg/sysinfo"
|
2018-06-11 13:32:11 +00:00
|
|
|
"gotest.tools/assert"
|
|
|
|
is "gotest.tools/assert/cmp"
|
2015-07-01 08:16:36 +00:00
|
|
|
)
|
|
|
|
|
2015-07-09 22:12:36 +00:00
|
|
|
// TODO Windows: This will need addressing for a Windows daemon.
|
2015-07-01 08:16:36 +00:00
|
|
|
func TestNetworkModeTest(t *testing.T) {
|
2015-12-18 18:36:17 +00:00
|
|
|
networkModes := map[container.NetworkMode][]bool{
|
2015-07-01 08:16:36 +00:00
|
|
|
// private, bridge, host, container, none, default
|
|
|
|
"": {true, false, false, false, false, false},
|
|
|
|
"something:weird": {true, false, false, false, false, false},
|
|
|
|
"bridge": {true, true, false, false, false, false},
|
|
|
|
DefaultDaemonNetworkMode(): {true, true, false, false, false, false},
|
2018-07-04 18:11:49 +00:00
|
|
|
"host": {false, false, true, false, false, false},
|
|
|
|
"container:name": {false, false, false, true, false, false},
|
|
|
|
"none": {true, false, false, false, true, false},
|
|
|
|
"default": {true, false, false, false, false, true},
|
2015-07-01 08:16:36 +00:00
|
|
|
}
|
2015-12-18 18:36:17 +00:00
|
|
|
networkModeNames := map[container.NetworkMode]string{
|
2015-07-01 08:16:36 +00:00
|
|
|
"": "",
|
2015-09-25 10:19:17 +00:00
|
|
|
"something:weird": "something:weird",
|
2015-07-01 08:16:36 +00:00
|
|
|
"bridge": "bridge",
|
|
|
|
DefaultDaemonNetworkMode(): "bridge",
|
2018-07-04 18:11:49 +00:00
|
|
|
"host": "host",
|
|
|
|
"container:name": "container",
|
|
|
|
"none": "none",
|
|
|
|
"default": "default",
|
2015-07-01 08:16:36 +00:00
|
|
|
}
|
|
|
|
for networkMode, state := range networkModes {
|
|
|
|
if networkMode.IsPrivate() != state[0] {
|
|
|
|
t.Fatalf("NetworkMode.IsPrivate for %v should have been %v but was %v", networkMode, state[0], networkMode.IsPrivate())
|
|
|
|
}
|
|
|
|
if networkMode.IsBridge() != state[1] {
|
|
|
|
t.Fatalf("NetworkMode.IsBridge for %v should have been %v but was %v", networkMode, state[1], networkMode.IsBridge())
|
|
|
|
}
|
|
|
|
if networkMode.IsHost() != state[2] {
|
|
|
|
t.Fatalf("NetworkMode.IsHost for %v should have been %v but was %v", networkMode, state[2], networkMode.IsHost())
|
|
|
|
}
|
|
|
|
if networkMode.IsContainer() != state[3] {
|
|
|
|
t.Fatalf("NetworkMode.IsContainer for %v should have been %v but was %v", networkMode, state[3], networkMode.IsContainer())
|
|
|
|
}
|
|
|
|
if networkMode.IsNone() != state[4] {
|
|
|
|
t.Fatalf("NetworkMode.IsNone for %v should have been %v but was %v", networkMode, state[4], networkMode.IsNone())
|
|
|
|
}
|
|
|
|
if networkMode.IsDefault() != state[5] {
|
|
|
|
t.Fatalf("NetworkMode.IsDefault for %v should have been %v but was %v", networkMode, state[5], networkMode.IsDefault())
|
|
|
|
}
|
|
|
|
if networkMode.NetworkName() != networkModeNames[networkMode] {
|
|
|
|
t.Fatalf("Expected name %v, got %v", networkModeNames[networkMode], networkMode.NetworkName())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestIpcModeTest(t *testing.T) {
|
Implement none, private, and shareable ipc modes
Since the commit d88fe447df0e8 ("Add support for sharing /dev/shm/ and
/dev/mqueue between containers") container's /dev/shm is mounted on the
host first, then bind-mounted inside the container. This is done that
way in order to be able to share this container's IPC namespace
(and the /dev/shm mount point) with another container.
Unfortunately, this functionality breaks container checkpoint/restore
(even if IPC is not shared). Since /dev/shm is an external mount, its
contents is not saved by `criu checkpoint`, and so upon restore any
application that tries to access data under /dev/shm is severily
disappointed (which usually results in a fatal crash).
This commit solves the issue by introducing new IPC modes for containers
(in addition to 'host' and 'container:ID'). The new modes are:
- 'shareable': enables sharing this container's IPC with others
(this used to be the implicit default);
- 'private': disables sharing this container's IPC.
In 'private' mode, container's /dev/shm is truly mounted inside the
container, without any bind-mounting from the host, which solves the
issue.
While at it, let's also implement 'none' mode. The motivation, as
eloquently put by Justin Cormack, is:
> I wondered a while back about having a none shm mode, as currently it is
> not possible to have a totally unwriteable container as there is always
> a /dev/shm writeable mount. It is a bit of a niche case (and clearly
> should never be allowed to be daemon default) but it would be trivial to
> add now so maybe we should...
...so here's yet yet another mode:
- 'none': no /dev/shm mount inside the container (though it still
has its own private IPC namespace).
Now, to ultimately solve the abovementioned checkpoint/restore issue, we'd
need to make 'private' the default mode, but unfortunately it breaks the
backward compatibility. So, let's make the default container IPC mode
per-daemon configurable (with the built-in default set to 'shareable'
for now). The default can be changed either via a daemon CLI option
(--default-shm-mode) or a daemon.json configuration file parameter
of the same name.
Note one can only set either 'shareable' or 'private' IPC modes as a
daemon default (i.e. in this context 'host', 'container', or 'none'
do not make much sense).
Some other changes this patch introduces are:
1. A mount for /dev/shm is added to default OCI Linux spec.
2. IpcMode.Valid() is simplified to remove duplicated code that parsed
'container:ID' form. Note the old version used to check that ID does
not contain a semicolon -- this is no longer the case (tests are
modified accordingly). The motivation is we should either do a
proper check for container ID validity, or don't check it at all
(since it is checked in other places anyway). I chose the latter.
3. IpcMode.Container() is modified to not return container ID if the
mode value does not start with "container:", unifying the check to
be the same as in IpcMode.IsContainer().
3. IPC mode unit tests (runconfig/hostconfig_test.go) are modified
to add checks for newly added values.
[v2: addressed review at https://github.com/moby/moby/pull/34087#pullrequestreview-51345997]
[v3: addressed review at https://github.com/moby/moby/pull/34087#pullrequestreview-53902833]
[v4: addressed the case of upgrading from older daemon, in this case
container.HostConfig.IpcMode is unset and this is valid]
[v5: document old and new IpcMode values in api/swagger.yaml]
[v6: add the 'none' mode, changelog entry to docs/api/version-history.md]
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2017-06-27 21:58:50 +00:00
|
|
|
ipcModes := map[container.IpcMode]struct {
|
|
|
|
private bool
|
|
|
|
host bool
|
|
|
|
container bool
|
|
|
|
shareable bool
|
|
|
|
valid bool
|
|
|
|
ctrName string
|
|
|
|
}{
|
|
|
|
"": {valid: true},
|
|
|
|
"private": {private: true, valid: true},
|
|
|
|
"something:weird": {},
|
|
|
|
":weird": {},
|
|
|
|
"host": {host: true, valid: true},
|
|
|
|
"container": {},
|
|
|
|
"container:": {container: true, valid: true, ctrName: ""},
|
|
|
|
"container:name": {container: true, valid: true, ctrName: "name"},
|
|
|
|
"container:name1:name2": {container: true, valid: true, ctrName: "name1:name2"},
|
|
|
|
"shareable": {shareable: true, valid: true},
|
2015-07-01 08:16:36 +00:00
|
|
|
}
|
Implement none, private, and shareable ipc modes
Since the commit d88fe447df0e8 ("Add support for sharing /dev/shm/ and
/dev/mqueue between containers") container's /dev/shm is mounted on the
host first, then bind-mounted inside the container. This is done that
way in order to be able to share this container's IPC namespace
(and the /dev/shm mount point) with another container.
Unfortunately, this functionality breaks container checkpoint/restore
(even if IPC is not shared). Since /dev/shm is an external mount, its
contents is not saved by `criu checkpoint`, and so upon restore any
application that tries to access data under /dev/shm is severily
disappointed (which usually results in a fatal crash).
This commit solves the issue by introducing new IPC modes for containers
(in addition to 'host' and 'container:ID'). The new modes are:
- 'shareable': enables sharing this container's IPC with others
(this used to be the implicit default);
- 'private': disables sharing this container's IPC.
In 'private' mode, container's /dev/shm is truly mounted inside the
container, without any bind-mounting from the host, which solves the
issue.
While at it, let's also implement 'none' mode. The motivation, as
eloquently put by Justin Cormack, is:
> I wondered a while back about having a none shm mode, as currently it is
> not possible to have a totally unwriteable container as there is always
> a /dev/shm writeable mount. It is a bit of a niche case (and clearly
> should never be allowed to be daemon default) but it would be trivial to
> add now so maybe we should...
...so here's yet yet another mode:
- 'none': no /dev/shm mount inside the container (though it still
has its own private IPC namespace).
Now, to ultimately solve the abovementioned checkpoint/restore issue, we'd
need to make 'private' the default mode, but unfortunately it breaks the
backward compatibility. So, let's make the default container IPC mode
per-daemon configurable (with the built-in default set to 'shareable'
for now). The default can be changed either via a daemon CLI option
(--default-shm-mode) or a daemon.json configuration file parameter
of the same name.
Note one can only set either 'shareable' or 'private' IPC modes as a
daemon default (i.e. in this context 'host', 'container', or 'none'
do not make much sense).
Some other changes this patch introduces are:
1. A mount for /dev/shm is added to default OCI Linux spec.
2. IpcMode.Valid() is simplified to remove duplicated code that parsed
'container:ID' form. Note the old version used to check that ID does
not contain a semicolon -- this is no longer the case (tests are
modified accordingly). The motivation is we should either do a
proper check for container ID validity, or don't check it at all
(since it is checked in other places anyway). I chose the latter.
3. IpcMode.Container() is modified to not return container ID if the
mode value does not start with "container:", unifying the check to
be the same as in IpcMode.IsContainer().
3. IPC mode unit tests (runconfig/hostconfig_test.go) are modified
to add checks for newly added values.
[v2: addressed review at https://github.com/moby/moby/pull/34087#pullrequestreview-51345997]
[v3: addressed review at https://github.com/moby/moby/pull/34087#pullrequestreview-53902833]
[v4: addressed the case of upgrading from older daemon, in this case
container.HostConfig.IpcMode is unset and this is valid]
[v5: document old and new IpcMode values in api/swagger.yaml]
[v6: add the 'none' mode, changelog entry to docs/api/version-history.md]
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2017-06-27 21:58:50 +00:00
|
|
|
|
2015-07-01 08:16:36 +00:00
|
|
|
for ipcMode, state := range ipcModes {
|
2018-03-13 19:28:34 +00:00
|
|
|
assert.Check(t, is.Equal(state.private, ipcMode.IsPrivate()), "IpcMode.IsPrivate() parsing failed for %q", ipcMode)
|
|
|
|
assert.Check(t, is.Equal(state.host, ipcMode.IsHost()), "IpcMode.IsHost() parsing failed for %q", ipcMode)
|
|
|
|
assert.Check(t, is.Equal(state.container, ipcMode.IsContainer()), "IpcMode.IsContainer() parsing failed for %q", ipcMode)
|
|
|
|
assert.Check(t, is.Equal(state.shareable, ipcMode.IsShareable()), "IpcMode.IsShareable() parsing failed for %q", ipcMode)
|
|
|
|
assert.Check(t, is.Equal(state.valid, ipcMode.Valid()), "IpcMode.Valid() parsing failed for %q", ipcMode)
|
|
|
|
assert.Check(t, is.Equal(state.ctrName, ipcMode.Container()), "IpcMode.Container() parsing failed for %q", ipcMode)
|
2015-07-01 08:16:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestUTSModeTest(t *testing.T) {
|
2015-12-18 18:36:17 +00:00
|
|
|
utsModes := map[container.UTSMode][]bool{
|
2015-07-01 08:16:36 +00:00
|
|
|
// private, host, valid
|
|
|
|
"": {true, false, true},
|
|
|
|
"something:weird": {true, false, false},
|
|
|
|
"host": {false, true, true},
|
|
|
|
"host:name": {true, false, true},
|
|
|
|
}
|
|
|
|
for utsMode, state := range utsModes {
|
|
|
|
if utsMode.IsPrivate() != state[0] {
|
|
|
|
t.Fatalf("UtsMode.IsPrivate for %v should have been %v but was %v", utsMode, state[0], utsMode.IsPrivate())
|
|
|
|
}
|
|
|
|
if utsMode.IsHost() != state[1] {
|
|
|
|
t.Fatalf("UtsMode.IsHost for %v should have been %v but was %v", utsMode, state[1], utsMode.IsHost())
|
|
|
|
}
|
|
|
|
if utsMode.Valid() != state[2] {
|
|
|
|
t.Fatalf("UtsMode.Valid for %v should have been %v but was %v", utsMode, state[2], utsMode.Valid())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-08 14:23:24 +00:00
|
|
|
func TestUsernsModeTest(t *testing.T) {
|
|
|
|
usrensMode := map[container.UsernsMode][]bool{
|
|
|
|
// private, host, valid
|
|
|
|
"": {true, false, true},
|
|
|
|
"something:weird": {true, false, false},
|
|
|
|
"host": {false, true, true},
|
|
|
|
"host:name": {true, false, true},
|
|
|
|
}
|
|
|
|
for usernsMode, state := range usrensMode {
|
|
|
|
if usernsMode.IsPrivate() != state[0] {
|
|
|
|
t.Fatalf("UsernsMode.IsPrivate for %v should have been %v but was %v", usernsMode, state[0], usernsMode.IsPrivate())
|
|
|
|
}
|
|
|
|
if usernsMode.IsHost() != state[1] {
|
|
|
|
t.Fatalf("UsernsMode.IsHost for %v should have been %v but was %v", usernsMode, state[1], usernsMode.IsHost())
|
|
|
|
}
|
|
|
|
if usernsMode.Valid() != state[2] {
|
|
|
|
t.Fatalf("UsernsMode.Valid for %v should have been %v but was %v", usernsMode, state[2], usernsMode.Valid())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-01 08:16:36 +00:00
|
|
|
func TestPidModeTest(t *testing.T) {
|
2015-12-18 18:36:17 +00:00
|
|
|
pidModes := map[container.PidMode][]bool{
|
2015-07-01 08:16:36 +00:00
|
|
|
// private, host, valid
|
|
|
|
"": {true, false, true},
|
|
|
|
"something:weird": {true, false, false},
|
|
|
|
"host": {false, true, true},
|
|
|
|
"host:name": {true, false, true},
|
|
|
|
}
|
|
|
|
for pidMode, state := range pidModes {
|
|
|
|
if pidMode.IsPrivate() != state[0] {
|
|
|
|
t.Fatalf("PidMode.IsPrivate for %v should have been %v but was %v", pidMode, state[0], pidMode.IsPrivate())
|
|
|
|
}
|
|
|
|
if pidMode.IsHost() != state[1] {
|
|
|
|
t.Fatalf("PidMode.IsHost for %v should have been %v but was %v", pidMode, state[1], pidMode.IsHost())
|
|
|
|
}
|
|
|
|
if pidMode.Valid() != state[2] {
|
|
|
|
t.Fatalf("PidMode.Valid for %v should have been %v but was %v", pidMode, state[2], pidMode.Valid())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRestartPolicy(t *testing.T) {
|
2015-12-18 18:36:17 +00:00
|
|
|
restartPolicies := map[container.RestartPolicy][]bool{
|
2015-07-01 08:16:36 +00:00
|
|
|
// none, always, failure
|
2017-05-26 06:25:58 +00:00
|
|
|
{}: {true, false, false},
|
|
|
|
{Name: "something", MaximumRetryCount: 0}: {false, false, false},
|
|
|
|
{Name: "no", MaximumRetryCount: 0}: {true, false, false},
|
|
|
|
{Name: "always", MaximumRetryCount: 0}: {false, true, false},
|
|
|
|
{Name: "on-failure", MaximumRetryCount: 0}: {false, false, true},
|
2015-07-01 08:16:36 +00:00
|
|
|
}
|
|
|
|
for restartPolicy, state := range restartPolicies {
|
|
|
|
if restartPolicy.IsNone() != state[0] {
|
|
|
|
t.Fatalf("RestartPolicy.IsNone for %v should have been %v but was %v", restartPolicy, state[0], restartPolicy.IsNone())
|
|
|
|
}
|
|
|
|
if restartPolicy.IsAlways() != state[1] {
|
|
|
|
t.Fatalf("RestartPolicy.IsAlways for %v should have been %v but was %v", restartPolicy, state[1], restartPolicy.IsAlways())
|
|
|
|
}
|
|
|
|
if restartPolicy.IsOnFailure() != state[2] {
|
|
|
|
t.Fatalf("RestartPolicy.IsOnFailure for %v should have been %v but was %v", restartPolicy, state[2], restartPolicy.IsOnFailure())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
func TestDecodeHostConfig(t *testing.T) {
|
|
|
|
fixtures := []struct {
|
|
|
|
file string
|
|
|
|
}{
|
2015-09-10 02:23:06 +00:00
|
|
|
{"fixtures/unix/container_hostconfig_1_14.json"},
|
|
|
|
{"fixtures/unix/container_hostconfig_1_19.json"},
|
2015-07-01 08:16:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, f := range fixtures {
|
|
|
|
b, err := ioutil.ReadFile(f.file)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2017-07-19 14:20:13 +00:00
|
|
|
c, err := decodeHostConfig(bytes.NewReader(b))
|
2015-07-01 08:16:36 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(fmt.Errorf("Error parsing %s: %v", f, err))
|
|
|
|
}
|
|
|
|
|
2018-03-13 19:28:34 +00:00
|
|
|
assert.Check(t, !c.Privileged)
|
2015-07-01 08:16:36 +00:00
|
|
|
|
2015-07-12 17:12:48 +00:00
|
|
|
if l := len(c.Binds); l != 1 {
|
|
|
|
t.Fatalf("Expected 1 bind, found %d\n", l)
|
2015-07-01 08:16:36 +00:00
|
|
|
}
|
|
|
|
|
2016-02-29 11:28:37 +00:00
|
|
|
if len(c.CapAdd) != 1 && c.CapAdd[0] != "NET_ADMIN" {
|
2015-07-01 08:16:36 +00:00
|
|
|
t.Fatalf("Expected CapAdd NET_ADMIN, got %v", c.CapAdd)
|
|
|
|
}
|
|
|
|
|
2016-02-29 11:28:37 +00:00
|
|
|
if len(c.CapDrop) != 1 && c.CapDrop[0] != "NET_ADMIN" {
|
2016-12-01 09:07:38 +00:00
|
|
|
t.Fatalf("Expected CapDrop NET_ADMIN, got %v", c.CapDrop)
|
2015-07-01 08:16:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-06-07 19:05:43 +00:00
|
|
|
|
|
|
|
func TestValidateResources(t *testing.T) {
|
|
|
|
type resourceTest struct {
|
|
|
|
ConfigCPURealtimePeriod int64
|
|
|
|
ConfigCPURealtimeRuntime int64
|
|
|
|
SysInfoCPURealtimePeriod bool
|
|
|
|
SysInfoCPURealtimeRuntime bool
|
|
|
|
ErrorExpected bool
|
|
|
|
FailureMsg string
|
|
|
|
}
|
|
|
|
|
|
|
|
tests := []resourceTest{
|
|
|
|
{
|
|
|
|
ConfigCPURealtimePeriod: 1000,
|
|
|
|
ConfigCPURealtimeRuntime: 1000,
|
|
|
|
SysInfoCPURealtimePeriod: true,
|
|
|
|
SysInfoCPURealtimeRuntime: true,
|
|
|
|
ErrorExpected: false,
|
|
|
|
FailureMsg: "Expected valid configuration",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
ConfigCPURealtimePeriod: 5000,
|
|
|
|
ConfigCPURealtimeRuntime: 5000,
|
|
|
|
SysInfoCPURealtimePeriod: false,
|
|
|
|
SysInfoCPURealtimeRuntime: true,
|
|
|
|
ErrorExpected: true,
|
|
|
|
FailureMsg: "Expected failure when cpu-rt-period is set but kernel doesn't support it",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
ConfigCPURealtimePeriod: 5000,
|
|
|
|
ConfigCPURealtimeRuntime: 5000,
|
|
|
|
SysInfoCPURealtimePeriod: true,
|
|
|
|
SysInfoCPURealtimeRuntime: false,
|
|
|
|
ErrorExpected: true,
|
|
|
|
FailureMsg: "Expected failure when cpu-rt-runtime is set but kernel doesn't support it",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
ConfigCPURealtimePeriod: 5000,
|
|
|
|
ConfigCPURealtimeRuntime: 10000,
|
|
|
|
SysInfoCPURealtimePeriod: true,
|
|
|
|
SysInfoCPURealtimeRuntime: false,
|
|
|
|
ErrorExpected: true,
|
|
|
|
FailureMsg: "Expected failure when cpu-rt-runtime is greater than cpu-rt-period",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, rt := range tests {
|
|
|
|
var hc container.HostConfig
|
|
|
|
hc.Resources.CPURealtimePeriod = rt.ConfigCPURealtimePeriod
|
|
|
|
hc.Resources.CPURealtimeRuntime = rt.ConfigCPURealtimeRuntime
|
|
|
|
|
|
|
|
var si sysinfo.SysInfo
|
|
|
|
si.CPURealtimePeriod = rt.SysInfoCPURealtimePeriod
|
|
|
|
si.CPURealtimeRuntime = rt.SysInfoCPURealtimeRuntime
|
|
|
|
|
2017-03-10 17:39:22 +00:00
|
|
|
if err := validateResources(&hc, &si); (err != nil) != rt.ErrorExpected {
|
2016-06-07 19:05:43 +00:00
|
|
|
t.Fatal(rt.FailureMsg, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|