1package swarm 2 3import ( 4 "context" 5 "runtime" 6 "testing" 7 "time" 8 9 "github.com/docker/docker/api/types" 10 "github.com/docker/docker/api/types/filters" 11 swarmtypes "github.com/docker/docker/api/types/swarm" 12 "github.com/docker/docker/internal/test/daemon" 13 "github.com/docker/docker/internal/test/environment" 14 "gotest.tools/assert" 15 "gotest.tools/poll" 16 "gotest.tools/skip" 17) 18 19// ServicePoll tweaks the pollSettings for `service` 20func ServicePoll(config *poll.Settings) { 21 // Override the default pollSettings for `service` resource here ... 22 config.Timeout = 30 * time.Second 23 config.Delay = 100 * time.Millisecond 24 if runtime.GOARCH == "arm64" || runtime.GOARCH == "arm" { 25 config.Timeout = 90 * time.Second 26 } 27} 28 29// NetworkPoll tweaks the pollSettings for `network` 30func NetworkPoll(config *poll.Settings) { 31 // Override the default pollSettings for `network` resource here ... 32 config.Timeout = 30 * time.Second 33 config.Delay = 100 * time.Millisecond 34 35 if runtime.GOARCH == "arm64" || runtime.GOARCH == "arm" { 36 config.Timeout = 50 * time.Second 37 } 38} 39 40// ContainerPoll tweaks the pollSettings for `container` 41func ContainerPoll(config *poll.Settings) { 42 // Override the default pollSettings for `container` resource here ... 43 44 if runtime.GOARCH == "arm64" || runtime.GOARCH == "arm" { 45 config.Timeout = 30 * time.Second 46 config.Delay = 100 * time.Millisecond 47 } 48} 49 50// NewSwarm creates a swarm daemon for testing 51func NewSwarm(t *testing.T, testEnv *environment.Execution, ops ...func(*daemon.Daemon)) *daemon.Daemon { 52 t.Helper() 53 skip.If(t, testEnv.IsRemoteDaemon) 54 skip.If(t, testEnv.DaemonInfo.OSType == "windows") 55 if testEnv.DaemonInfo.ExperimentalBuild { 56 ops = append(ops, daemon.WithExperimental) 57 } 58 d := daemon.New(t, ops...) 59 d.StartAndSwarmInit(t) 60 return d 61} 62 63// ServiceSpecOpt is used with `CreateService` to pass in service spec modifiers 64type ServiceSpecOpt func(*swarmtypes.ServiceSpec) 65 66// CreateService creates a service on the passed in swarm daemon. 67func CreateService(t *testing.T, d *daemon.Daemon, opts ...ServiceSpecOpt) string { 68 t.Helper() 69 spec := defaultServiceSpec() 70 for _, o := range opts { 71 o(&spec) 72 } 73 74 client := d.NewClientT(t) 75 defer client.Close() 76 77 resp, err := client.ServiceCreate(context.Background(), spec, types.ServiceCreateOptions{}) 78 assert.NilError(t, err, "error creating service") 79 return resp.ID 80} 81 82func defaultServiceSpec() swarmtypes.ServiceSpec { 83 var spec swarmtypes.ServiceSpec 84 ServiceWithImage("busybox:latest")(&spec) 85 ServiceWithCommand([]string{"/bin/top"})(&spec) 86 ServiceWithReplicas(1)(&spec) 87 return spec 88} 89 90// ServiceWithInit sets whether the service should use init or not 91func ServiceWithInit(b *bool) func(*swarmtypes.ServiceSpec) { 92 return func(spec *swarmtypes.ServiceSpec) { 93 ensureContainerSpec(spec) 94 spec.TaskTemplate.ContainerSpec.Init = b 95 } 96} 97 98// ServiceWithImage sets the image to use for the service 99func ServiceWithImage(image string) func(*swarmtypes.ServiceSpec) { 100 return func(spec *swarmtypes.ServiceSpec) { 101 ensureContainerSpec(spec) 102 spec.TaskTemplate.ContainerSpec.Image = image 103 } 104} 105 106// ServiceWithCommand sets the command to use for the service 107func ServiceWithCommand(cmd []string) ServiceSpecOpt { 108 return func(spec *swarmtypes.ServiceSpec) { 109 ensureContainerSpec(spec) 110 spec.TaskTemplate.ContainerSpec.Command = cmd 111 } 112} 113 114// ServiceWithConfig adds the config reference to the service 115func ServiceWithConfig(configRef *swarmtypes.ConfigReference) ServiceSpecOpt { 116 return func(spec *swarmtypes.ServiceSpec) { 117 ensureContainerSpec(spec) 118 spec.TaskTemplate.ContainerSpec.Configs = append(spec.TaskTemplate.ContainerSpec.Configs, configRef) 119 } 120} 121 122// ServiceWithSecret adds the secret reference to the service 123func ServiceWithSecret(secretRef *swarmtypes.SecretReference) ServiceSpecOpt { 124 return func(spec *swarmtypes.ServiceSpec) { 125 ensureContainerSpec(spec) 126 spec.TaskTemplate.ContainerSpec.Secrets = append(spec.TaskTemplate.ContainerSpec.Secrets, secretRef) 127 } 128} 129 130// ServiceWithReplicas sets the replicas for the service 131func ServiceWithReplicas(n uint64) ServiceSpecOpt { 132 return func(spec *swarmtypes.ServiceSpec) { 133 spec.Mode = swarmtypes.ServiceMode{ 134 Replicated: &swarmtypes.ReplicatedService{ 135 Replicas: &n, 136 }, 137 } 138 } 139} 140 141// ServiceWithName sets the name of the service 142func ServiceWithName(name string) ServiceSpecOpt { 143 return func(spec *swarmtypes.ServiceSpec) { 144 spec.Annotations.Name = name 145 } 146} 147 148// ServiceWithNetwork sets the network of the service 149func ServiceWithNetwork(network string) ServiceSpecOpt { 150 return func(spec *swarmtypes.ServiceSpec) { 151 spec.TaskTemplate.Networks = append(spec.TaskTemplate.Networks, 152 swarmtypes.NetworkAttachmentConfig{Target: network}) 153 } 154} 155 156// ServiceWithEndpoint sets the Endpoint of the service 157func ServiceWithEndpoint(endpoint *swarmtypes.EndpointSpec) ServiceSpecOpt { 158 return func(spec *swarmtypes.ServiceSpec) { 159 spec.EndpointSpec = endpoint 160 } 161} 162 163// GetRunningTasks gets the list of running tasks for a service 164func GetRunningTasks(t *testing.T, d *daemon.Daemon, serviceID string) []swarmtypes.Task { 165 t.Helper() 166 client := d.NewClientT(t) 167 defer client.Close() 168 169 filterArgs := filters.NewArgs() 170 filterArgs.Add("desired-state", "running") 171 filterArgs.Add("service", serviceID) 172 173 options := types.TaskListOptions{ 174 Filters: filterArgs, 175 } 176 tasks, err := client.TaskList(context.Background(), options) 177 assert.NilError(t, err) 178 return tasks 179} 180 181// ExecTask runs the passed in exec config on the given task 182func ExecTask(t *testing.T, d *daemon.Daemon, task swarmtypes.Task, config types.ExecConfig) types.HijackedResponse { 183 t.Helper() 184 client := d.NewClientT(t) 185 defer client.Close() 186 187 ctx := context.Background() 188 resp, err := client.ContainerExecCreate(ctx, task.Status.ContainerStatus.ContainerID, config) 189 assert.NilError(t, err, "error creating exec") 190 191 startCheck := types.ExecStartCheck{} 192 attach, err := client.ContainerExecAttach(ctx, resp.ID, startCheck) 193 assert.NilError(t, err, "error attaching to exec") 194 return attach 195} 196 197func ensureContainerSpec(spec *swarmtypes.ServiceSpec) { 198 if spec.TaskTemplate.ContainerSpec == nil { 199 spec.TaskTemplate.ContainerSpec = &swarmtypes.ContainerSpec{} 200 } 201} 202