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  }