github.com/kaisenlinux/docker@v0.0.0-20230510090727-ea55db55fac7/swarmkit/agent/exec/dockerapi/controller_test.go (about) 1 package dockerapi 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "reflect" 10 "runtime" 11 "testing" 12 "time" 13 14 "github.com/docker/docker/api/types" 15 containertypes "github.com/docker/docker/api/types/container" 16 "github.com/docker/docker/api/types/events" 17 "github.com/docker/docker/api/types/network" 18 "github.com/docker/swarmkit/agent/exec" 19 "github.com/docker/swarmkit/api" 20 "github.com/docker/swarmkit/identity" 21 "github.com/docker/swarmkit/log" 22 gogotypes "github.com/gogo/protobuf/types" 23 "github.com/stretchr/testify/assert" 24 ) 25 26 var tenSecond = 10 * time.Second 27 28 func TestControllerPrepare(t *testing.T) { 29 task := genTask(t) 30 ctx, client, ctlr, config, finish := genTestControllerEnv(t, task) 31 defer func() { 32 finish() 33 assert.Equal(t, 1, client.calls["ImagePull"]) 34 assert.Equal(t, 1, client.calls["ContainerCreate"]) 35 }() 36 37 client.ImagePullFn = func(_ context.Context, refStr string, options types.ImagePullOptions) (io.ReadCloser, error) { 38 if refStr == config.image() { 39 return ioutil.NopCloser(bytes.NewBuffer([]byte{})), nil 40 } 41 panic("unexpected call of ImagePull") 42 } 43 44 client.ContainerCreateFn = func(_ context.Context, cConfig *containertypes.Config, hConfig *containertypes.HostConfig, nConfig *network.NetworkingConfig, containerName string) (containertypes.ContainerCreateCreatedBody, error) { 45 if reflect.DeepEqual(*cConfig, *config.config()) && 46 reflect.DeepEqual(*hConfig, *config.hostConfig()) && 47 reflect.DeepEqual(*nConfig, *config.networkingConfig()) && 48 containerName == config.name() { 49 return containertypes.ContainerCreateCreatedBody{ID: "container-id-" + task.ID}, nil 50 } 51 panic("unexpected call to ContainerCreate") 52 } 53 54 assert.NoError(t, ctlr.Prepare(ctx)) 55 } 56 57 func TestControllerPrepareAlreadyPrepared(t *testing.T) { 58 task := genTask(t) 59 ctx, client, ctlr, config, finish := genTestControllerEnv(t, task) 60 defer func() { 61 finish() 62 assert.Equal(t, 1, client.calls["ImagePull"]) 63 assert.Equal(t, 1, client.calls["ContainerCreate"]) 64 assert.Equal(t, 1, client.calls["ContainerInspect"]) 65 }() 66 67 client.ImagePullFn = func(_ context.Context, refStr string, options types.ImagePullOptions) (io.ReadCloser, error) { 68 if refStr == config.image() { 69 return ioutil.NopCloser(bytes.NewBuffer([]byte{})), nil 70 } 71 panic("unexpected call of ImagePull") 72 } 73 74 client.ContainerCreateFn = func(_ context.Context, cConfig *containertypes.Config, hostConfig *containertypes.HostConfig, networking *network.NetworkingConfig, containerName string) (containertypes.ContainerCreateCreatedBody, error) { 75 if reflect.DeepEqual(*cConfig, *config.config()) && 76 reflect.DeepEqual(*networking, *config.networkingConfig()) && 77 containerName == config.name() { 78 return containertypes.ContainerCreateCreatedBody{}, fmt.Errorf("Conflict. The name") 79 } 80 panic("unexpected call of ContainerCreate") 81 } 82 83 client.ContainerInspectFn = func(_ context.Context, containerName string) (types.ContainerJSON, error) { 84 if containerName == config.name() { 85 return types.ContainerJSON{}, nil 86 } 87 panic("unexpected call of ContainerInspect") 88 } 89 90 // ensure idempotence 91 if err := ctlr.Prepare(ctx); err != exec.ErrTaskPrepared { 92 t.Fatalf("expected error %v, got %v", exec.ErrTaskPrepared, err) 93 } 94 } 95 96 func TestControllerStart(t *testing.T) { 97 task := genTask(t) 98 ctx, client, ctlr, config, finish := genTestControllerEnv(t, task) 99 defer func() { 100 finish() 101 assert.Equal(t, 1, client.calls["ContainerInspect"]) 102 assert.Equal(t, 1, client.calls["ContainerStart"]) 103 }() 104 105 client.ContainerInspectFn = func(_ context.Context, containerName string) (types.ContainerJSON, error) { 106 if containerName == config.name() { 107 return types.ContainerJSON{ 108 ContainerJSONBase: &types.ContainerJSONBase{ 109 State: &types.ContainerState{ 110 Status: "created", 111 }, 112 }, 113 }, nil 114 } 115 panic("unexpected call of ContainerInspect") 116 } 117 118 client.ContainerStartFn = func(_ context.Context, containerName string, options types.ContainerStartOptions) error { 119 if containerName == config.name() && reflect.DeepEqual(options, types.ContainerStartOptions{}) { 120 return nil 121 } 122 panic("unexpected call of ContainerStart") 123 } 124 125 assert.NoError(t, ctlr.Start(ctx)) 126 } 127 128 func TestControllerStartAlreadyStarted(t *testing.T) { 129 task := genTask(t) 130 ctx, client, ctlr, config, finish := genTestControllerEnv(t, task) 131 defer func() { 132 finish() 133 assert.Equal(t, 1, client.calls["ContainerInspect"]) 134 }() 135 136 client.ContainerInspectFn = func(_ context.Context, containerName string) (types.ContainerJSON, error) { 137 if containerName == config.name() { 138 return types.ContainerJSON{ 139 ContainerJSONBase: &types.ContainerJSONBase{ 140 State: &types.ContainerState{ 141 Status: "notcreated", // can be anything but created 142 }, 143 }, 144 }, nil 145 } 146 panic("unexpected call of ContainerInspect") 147 } 148 149 // ensure idempotence 150 if err := ctlr.Start(ctx); err != exec.ErrTaskStarted { 151 t.Fatalf("expected error %v, got %v", exec.ErrTaskPrepared, err) 152 } 153 } 154 155 func TestControllerWait(t *testing.T) { 156 task := genTask(t) 157 ctx, client, ctlr, config, finish := genTestControllerEnv(t, task) 158 defer func() { 159 finish() 160 assert.Equal(t, 2, client.calls["ContainerInspect"]) 161 assert.Equal(t, 1, client.calls["Events"]) 162 }() 163 164 client.ContainerInspectFn = func(_ context.Context, container string) (types.ContainerJSON, error) { 165 if client.calls["ContainerInspect"] == 1 && container == config.name() { 166 return types.ContainerJSON{ 167 ContainerJSONBase: &types.ContainerJSONBase{ 168 State: &types.ContainerState{ 169 Status: "running", 170 }, 171 }, 172 }, nil 173 } else if client.calls["ContainerInspect"] == 2 && container == config.name() { 174 return types.ContainerJSON{ 175 ContainerJSONBase: &types.ContainerJSONBase{ 176 State: &types.ContainerState{ 177 Status: "stopped", // can be anything but created 178 }, 179 }, 180 }, nil 181 } 182 panic("unexpected call of ContainerInspect") 183 } 184 185 client.EventsFn = func(_ context.Context, options types.EventsOptions) (<-chan events.Message, <-chan error) { 186 if reflect.DeepEqual(options, types.EventsOptions{ 187 Since: "0", 188 Filters: config.eventFilter(), 189 }) { 190 return makeEvents(t, config, "create", "die") 191 } 192 panic("unexpected call of Events") 193 } 194 195 assert.NoError(t, ctlr.Wait(ctx)) 196 } 197 198 func TestControllerWaitUnhealthy(t *testing.T) { 199 task := genTask(t) 200 ctx, client, ctlr, config, finish := genTestControllerEnv(t, task) 201 defer func() { 202 finish() 203 assert.Equal(t, 1, client.calls["ContainerInspect"]) 204 assert.Equal(t, 1, client.calls["Events"]) 205 assert.Equal(t, 1, client.calls["ContainerStop"]) 206 }() 207 client.ContainerInspectFn = func(_ context.Context, containerName string) (types.ContainerJSON, error) { 208 if containerName == config.name() { 209 return types.ContainerJSON{ 210 ContainerJSONBase: &types.ContainerJSONBase{ 211 State: &types.ContainerState{ 212 Status: "running", 213 }, 214 }, 215 }, nil 216 } 217 panic("unexpected call ContainerInspect") 218 } 219 evs, errs := makeEvents(t, config, "create", "health_status: unhealthy") 220 client.EventsFn = func(_ context.Context, options types.EventsOptions) (<-chan events.Message, <-chan error) { 221 if reflect.DeepEqual(options, types.EventsOptions{ 222 Since: "0", 223 Filters: config.eventFilter(), 224 }) { 225 return evs, errs 226 } 227 panic("unexpected call of Events") 228 } 229 client.ContainerStopFn = func(_ context.Context, containerName string, timeout *time.Duration) error { 230 if containerName == config.name() && *timeout == tenSecond { 231 return nil 232 } 233 panic("unexpected call of ContainerStop") 234 } 235 236 assert.Equal(t, ctlr.Wait(ctx), ErrContainerUnhealthy) 237 } 238 239 func TestControllerWaitExitError(t *testing.T) { 240 task := genTask(t) 241 ctx, client, ctlr, config, finish := genTestControllerEnv(t, task) 242 defer func() { 243 finish() 244 assert.Equal(t, 2, client.calls["ContainerInspect"]) 245 assert.Equal(t, 1, client.calls["Events"]) 246 }() 247 248 client.ContainerInspectFn = func(_ context.Context, containerName string) (types.ContainerJSON, error) { 249 if client.calls["ContainerInspect"] == 1 && containerName == config.name() { 250 return types.ContainerJSON{ 251 ContainerJSONBase: &types.ContainerJSONBase{ 252 State: &types.ContainerState{ 253 Status: "running", 254 }, 255 }, 256 }, nil 257 } else if client.calls["ContainerInspect"] == 2 && containerName == config.name() { 258 return types.ContainerJSON{ 259 ContainerJSONBase: &types.ContainerJSONBase{ 260 ID: "cid", 261 State: &types.ContainerState{ 262 Status: "exited", // can be anything but created 263 ExitCode: 1, 264 Pid: 1, 265 }, 266 }, 267 }, nil 268 } 269 panic("unexpected call of ContainerInspect") 270 } 271 272 client.EventsFn = func(_ context.Context, options types.EventsOptions) (<-chan events.Message, <-chan error) { 273 if reflect.DeepEqual(options, types.EventsOptions{ 274 Since: "0", 275 Filters: config.eventFilter(), 276 }) { 277 return makeEvents(t, config, "create", "die") 278 } 279 panic("unexpected call of Events") 280 } 281 282 err := ctlr.Wait(ctx) 283 checkExitError(t, 1, err) 284 } 285 286 func checkExitError(t *testing.T, expectedCode int, err error) { 287 ec, ok := err.(exec.ExitCoder) 288 if !ok { 289 t.Fatalf("expected an exit error, got: %v", err) 290 } 291 292 assert.Equal(t, expectedCode, ec.ExitCode()) 293 } 294 295 func TestControllerWaitExitedClean(t *testing.T) { 296 task := genTask(t) 297 ctx, client, ctlr, config, finish := genTestControllerEnv(t, task) 298 defer func() { 299 finish() 300 assert.Equal(t, 1, client.calls["ContainerInspect"]) 301 }() 302 303 client.ContainerInspectFn = func(_ context.Context, container string) (types.ContainerJSON, error) { 304 if container == config.name() { 305 return types.ContainerJSON{ 306 ContainerJSONBase: &types.ContainerJSONBase{ 307 State: &types.ContainerState{ 308 Status: "exited", 309 }, 310 }, 311 }, nil 312 } 313 panic("unexpected call of ContainerInspect") 314 } 315 316 err := ctlr.Wait(ctx) 317 assert.Nil(t, err) 318 } 319 320 func TestControllerWaitExitedError(t *testing.T) { 321 task := genTask(t) 322 ctx, client, ctlr, config, finish := genTestControllerEnv(t, task) 323 defer func() { 324 finish() 325 assert.Equal(t, 1, client.calls["ContainerInspect"]) 326 }() 327 328 client.ContainerInspectFn = func(_ context.Context, containerName string) (types.ContainerJSON, error) { 329 if containerName == config.name() { 330 return types.ContainerJSON{ 331 ContainerJSONBase: &types.ContainerJSONBase{ 332 ID: "cid", 333 State: &types.ContainerState{ 334 Status: "exited", 335 ExitCode: 1, 336 Pid: 1, 337 }, 338 }, 339 }, nil 340 } 341 panic("unexpected call of ContainerInspect") 342 } 343 344 err := ctlr.Wait(ctx) 345 checkExitError(t, 1, err) 346 } 347 348 func TestControllerShutdown(t *testing.T) { 349 task := genTask(t) 350 ctx, client, ctlr, config, finish := genTestControllerEnv(t, task) 351 defer func() { 352 finish() 353 assert.Equal(t, 1, client.calls["ContainerStop"]) 354 }() 355 356 client.ContainerStopFn = func(_ context.Context, containerName string, timeout *time.Duration) error { 357 if containerName == config.name() && *timeout == tenSecond { 358 return nil 359 } 360 panic("unexpected call of ContainerStop") 361 } 362 363 assert.NoError(t, ctlr.Shutdown(ctx)) 364 } 365 366 func TestControllerTerminate(t *testing.T) { 367 task := genTask(t) 368 ctx, client, ctlr, config, finish := genTestControllerEnv(t, task) 369 defer func() { 370 finish() 371 assert.Equal(t, 1, client.calls["ContainerKill"]) 372 }() 373 374 client.ContainerKillFn = func(_ context.Context, containerName, signal string) error { 375 if containerName == config.name() && signal == "" { 376 return nil 377 } 378 panic("unexpected call of ContainerKill") 379 } 380 381 assert.NoError(t, ctlr.Terminate(ctx)) 382 } 383 384 func TestControllerRemove(t *testing.T) { 385 task := genTask(t) 386 ctx, client, ctlr, config, finish := genTestControllerEnv(t, task) 387 defer func() { 388 finish() 389 assert.Equal(t, 1, client.calls["ContainerStop"]) 390 assert.Equal(t, 1, client.calls["ContainerRemove"]) 391 }() 392 393 client.ContainerStopFn = func(_ context.Context, container string, timeout *time.Duration) error { 394 if container == config.name() && *timeout == tenSecond { 395 return nil 396 } 397 panic("unexpected call of ContainerStop") 398 } 399 400 client.ContainerRemoveFn = func(_ context.Context, container string, options types.ContainerRemoveOptions) error { 401 if container == config.name() && reflect.DeepEqual(options, types.ContainerRemoveOptions{ 402 RemoveVolumes: true, 403 Force: true, 404 }) { 405 return nil 406 } 407 panic("unexpected call of ContainerRemove") 408 } 409 410 assert.NoError(t, ctlr.Remove(ctx)) 411 } 412 413 func genTestControllerEnv(t *testing.T, task *api.Task) (context.Context, *StubAPIClient, exec.Controller, *containerConfig, func()) { 414 testNodeDescription := &api.NodeDescription{ 415 Hostname: "testHostname", 416 Platform: &api.Platform{ 417 OS: "linux", 418 Architecture: "x86_64", 419 }, 420 } 421 422 client := NewStubAPIClient() 423 ctlr, err := newController(client, testNodeDescription, task, nil) 424 assert.NoError(t, err) 425 426 config, err := newContainerConfig(testNodeDescription, task) 427 assert.NoError(t, err) 428 assert.NotNil(t, config) 429 430 ctx := context.Background() 431 432 // Put test name into log messages. Awesome! 433 pc, _, _, ok := runtime.Caller(1) 434 if ok { 435 fn := runtime.FuncForPC(pc) 436 ctx = log.WithLogger(ctx, log.L.WithField("test", fn.Name())) 437 } 438 439 ctx, cancel := context.WithCancel(ctx) 440 return ctx, client, ctlr, config, cancel 441 } 442 443 func genTask(t *testing.T) *api.Task { 444 const ( 445 nodeID = "dockerexec-test-node-id" 446 serviceID = "dockerexec-test-service" 447 reference = "stevvooe/foo:latest" 448 ) 449 450 return &api.Task{ 451 ID: identity.NewID(), 452 ServiceID: serviceID, 453 NodeID: nodeID, 454 Spec: api.TaskSpec{ 455 Runtime: &api.TaskSpec_Container{ 456 Container: &api.ContainerSpec{ 457 Image: reference, 458 StopGracePeriod: gogotypes.DurationProto(10 * time.Second), 459 }, 460 }, 461 }, 462 } 463 } 464 465 func makeEvents(t *testing.T, container *containerConfig, actions ...string) (<-chan events.Message, <-chan error) { 466 evs := make(chan events.Message, len(actions)) 467 for _, action := range actions { 468 evs <- events.Message{ 469 Type: events.ContainerEventType, 470 Action: action, 471 Actor: events.Actor{ 472 // TODO(stevvooe): Resolve container id. 473 Attributes: map[string]string{ 474 "name": container.name(), 475 }, 476 }, 477 } 478 } 479 close(evs) 480 481 return evs, nil 482 }