github.com/rumpl/bof@v23.0.0-rc.2+incompatible/integration/container/daemon_linux_test.go (about) 1 package container // import "github.com/docker/docker/integration/container" 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "os" 8 "path/filepath" 9 "strconv" 10 "strings" 11 "testing" 12 "time" 13 14 "github.com/docker/docker/api/types" 15 containerapi "github.com/docker/docker/api/types/container" 16 realcontainer "github.com/docker/docker/container" 17 "github.com/docker/docker/integration/internal/container" 18 "github.com/docker/docker/testutil/daemon" 19 "golang.org/x/sys/unix" 20 "gotest.tools/v3/assert" 21 is "gotest.tools/v3/assert/cmp" 22 "gotest.tools/v3/skip" 23 ) 24 25 // This is a regression test for #36145 26 // It ensures that a container can be started when the daemon was improperly 27 // shutdown when the daemon is brought back up. 28 // 29 // The regression is due to improper error handling preventing a container from 30 // being restored and as such have the resources cleaned up. 31 // 32 // To test this, we need to kill dockerd, then kill both the containerd-shim and 33 // the container process, then start dockerd back up and attempt to start the 34 // container again. 35 func TestContainerStartOnDaemonRestart(t *testing.T) { 36 skip.If(t, testEnv.IsRemoteDaemon, "cannot start daemon on remote test run") 37 skip.If(t, testEnv.DaemonInfo.OSType == "windows") 38 skip.If(t, testEnv.IsRootless) 39 t.Parallel() 40 41 d := daemon.New(t) 42 d.StartWithBusybox(t, "--iptables=false") 43 defer d.Stop(t) 44 45 c := d.NewClientT(t) 46 47 ctx := context.Background() 48 49 cID := container.Create(ctx, t, c) 50 defer c.ContainerRemove(ctx, cID, types.ContainerRemoveOptions{Force: true}) 51 52 err := c.ContainerStart(ctx, cID, types.ContainerStartOptions{}) 53 assert.Check(t, err, "error starting test container") 54 55 inspect, err := c.ContainerInspect(ctx, cID) 56 assert.Check(t, err, "error getting inspect data") 57 58 ppid := getContainerdShimPid(t, inspect) 59 60 err = d.Kill() 61 assert.Check(t, err, "failed to kill test daemon") 62 63 err = unix.Kill(inspect.State.Pid, unix.SIGKILL) 64 assert.Check(t, err, "failed to kill container process") 65 66 err = unix.Kill(ppid, unix.SIGKILL) 67 assert.Check(t, err, "failed to kill containerd-shim") 68 69 d.Start(t, "--iptables=false") 70 71 err = c.ContainerStart(ctx, cID, types.ContainerStartOptions{}) 72 assert.Check(t, err, "failed to start test container") 73 } 74 75 func getContainerdShimPid(t *testing.T, c types.ContainerJSON) int { 76 statB, err := os.ReadFile(fmt.Sprintf("/proc/%d/stat", c.State.Pid)) 77 assert.Check(t, err, "error looking up containerd-shim pid") 78 79 // ppid is the 4th entry in `/proc/pid/stat` 80 ppid, err := strconv.Atoi(strings.Fields(string(statB))[3]) 81 assert.Check(t, err, "error converting ppid field to int") 82 83 assert.Check(t, ppid != 1, "got unexpected ppid") 84 return ppid 85 } 86 87 // TestDaemonRestartIpcMode makes sure a container keeps its ipc mode 88 // (derived from daemon default) even after the daemon is restarted 89 // with a different default ipc mode. 90 func TestDaemonRestartIpcMode(t *testing.T) { 91 skip.If(t, testEnv.IsRemoteDaemon, "cannot start daemon on remote test run") 92 skip.If(t, testEnv.DaemonInfo.OSType == "windows") 93 t.Parallel() 94 95 d := daemon.New(t) 96 d.StartWithBusybox(t, "--iptables=false", "--default-ipc-mode=private") 97 defer d.Stop(t) 98 99 c := d.NewClientT(t) 100 ctx := context.Background() 101 102 // check the container is created with private ipc mode as per daemon default 103 cID := container.Run(ctx, t, c, 104 container.WithCmd("top"), 105 container.WithRestartPolicy("always"), 106 ) 107 defer c.ContainerRemove(ctx, cID, types.ContainerRemoveOptions{Force: true}) 108 109 inspect, err := c.ContainerInspect(ctx, cID) 110 assert.NilError(t, err) 111 assert.Check(t, is.Equal(string(inspect.HostConfig.IpcMode), "private")) 112 113 // restart the daemon with shareable default ipc mode 114 d.Restart(t, "--iptables=false", "--default-ipc-mode=shareable") 115 116 // check the container is still having private ipc mode 117 inspect, err = c.ContainerInspect(ctx, cID) 118 assert.NilError(t, err) 119 assert.Check(t, is.Equal(string(inspect.HostConfig.IpcMode), "private")) 120 121 // check a new container is created with shareable ipc mode as per new daemon default 122 cID = container.Run(ctx, t, c) 123 defer c.ContainerRemove(ctx, cID, types.ContainerRemoveOptions{Force: true}) 124 125 inspect, err = c.ContainerInspect(ctx, cID) 126 assert.NilError(t, err) 127 assert.Check(t, is.Equal(string(inspect.HostConfig.IpcMode), "shareable")) 128 } 129 130 // TestDaemonHostGatewayIP verifies that when a magic string "host-gateway" is passed 131 // to ExtraHosts (--add-host) instead of an IP address, its value is set to 132 // 1. Daemon config flag value specified by host-gateway-ip or 133 // 2. IP of the default bridge network 134 // and is added to the /etc/hosts file 135 func TestDaemonHostGatewayIP(t *testing.T) { 136 skip.If(t, testEnv.IsRemoteDaemon) 137 skip.If(t, testEnv.DaemonInfo.OSType == "windows") 138 skip.If(t, testEnv.IsRootless, "rootless mode has different view of network") 139 t.Parallel() 140 141 // Verify the IP in /etc/hosts is same as host-gateway-ip 142 d := daemon.New(t) 143 // Verify the IP in /etc/hosts is same as the default bridge's IP 144 d.StartWithBusybox(t) 145 c := d.NewClientT(t) 146 ctx := context.Background() 147 cID := container.Run(ctx, t, c, 148 container.WithExtraHost("host.docker.internal:host-gateway"), 149 ) 150 res, err := container.Exec(ctx, c, cID, []string{"cat", "/etc/hosts"}) 151 assert.NilError(t, err) 152 assert.Assert(t, is.Len(res.Stderr(), 0)) 153 assert.Equal(t, 0, res.ExitCode) 154 inspect, err := c.NetworkInspect(ctx, "bridge", types.NetworkInspectOptions{}) 155 assert.NilError(t, err) 156 assert.Check(t, is.Contains(res.Stdout(), inspect.IPAM.Config[0].Gateway)) 157 c.ContainerRemove(ctx, cID, types.ContainerRemoveOptions{Force: true}) 158 d.Stop(t) 159 160 // Verify the IP in /etc/hosts is same as host-gateway-ip 161 d.StartWithBusybox(t, "--host-gateway-ip=6.7.8.9") 162 cID = container.Run(ctx, t, c, 163 container.WithExtraHost("host.docker.internal:host-gateway"), 164 ) 165 res, err = container.Exec(ctx, c, cID, []string{"cat", "/etc/hosts"}) 166 assert.NilError(t, err) 167 assert.Assert(t, is.Len(res.Stderr(), 0)) 168 assert.Equal(t, 0, res.ExitCode) 169 assert.Check(t, is.Contains(res.Stdout(), "6.7.8.9")) 170 c.ContainerRemove(ctx, cID, types.ContainerRemoveOptions{Force: true}) 171 d.Stop(t) 172 } 173 174 // TestRestartDaemonWithRestartingContainer simulates a case where a container is in "restarting" state when 175 // dockerd is killed (due to machine reset or something else). 176 // 177 // Related to moby/moby#41817 178 // 179 // In this test we'll change the container state to "restarting". 180 // This means that the container will not be 'alive' when we attempt to restore in on daemon startup. 181 // 182 // We could do the same with `docker run -d --resetart=always busybox:latest exit 1`, and then 183 // `kill -9` dockerd while the container is in "restarting" state. This is difficult to reproduce reliably 184 // in an automated test, so we manipulate on disk state instead. 185 func TestRestartDaemonWithRestartingContainer(t *testing.T) { 186 skip.If(t, testEnv.IsRemoteDaemon, "cannot start daemon on remote test run") 187 skip.If(t, testEnv.DaemonInfo.OSType == "windows") 188 189 t.Parallel() 190 191 d := daemon.New(t) 192 defer d.Cleanup(t) 193 194 d.StartWithBusybox(t, "--iptables=false") 195 defer d.Stop(t) 196 197 ctx := context.Background() 198 client := d.NewClientT(t) 199 200 // Just create the container, no need to start it to be started. 201 // We really want to make sure there is no process running when docker starts back up. 202 // We will manipulate the on disk state later 203 id := container.Create(ctx, t, client, container.WithRestartPolicy("always"), container.WithCmd("/bin/sh", "-c", "exit 1")) 204 205 d.Stop(t) 206 207 configPath := filepath.Join(d.Root, "containers", id, "config.v2.json") 208 configBytes, err := os.ReadFile(configPath) 209 assert.NilError(t, err) 210 211 var c realcontainer.Container 212 213 assert.NilError(t, json.Unmarshal(configBytes, &c)) 214 215 c.State = realcontainer.NewState() 216 c.SetRestarting(&realcontainer.ExitStatus{ExitCode: 1}) 217 c.HasBeenStartedBefore = true 218 219 configBytes, err = json.Marshal(&c) 220 assert.NilError(t, err) 221 assert.NilError(t, os.WriteFile(configPath, configBytes, 0600)) 222 223 d.Start(t) 224 225 ctxTimeout, cancel := context.WithTimeout(ctx, 30*time.Second) 226 defer cancel() 227 chOk, chErr := client.ContainerWait(ctxTimeout, id, containerapi.WaitConditionNextExit) 228 select { 229 case <-chOk: 230 case err := <-chErr: 231 assert.NilError(t, err) 232 } 233 }