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