| package swarm | |
| import ( | |
| "context" | |
| "runtime" | |
| "testing" | |
| "time" | |
| swarmtypes "github.com/moby/moby/api/types/swarm" | |
| "github.com/moby/moby/client" | |
| "github.com/moby/moby/v2/internal/testutil/daemon" | |
| "github.com/moby/moby/v2/internal/testutil/environment" | |
| "gotest.tools/v3/assert" | |
| "gotest.tools/v3/poll" | |
| "gotest.tools/v3/skip" | |
| ) | |
| // ServicePoll tweaks the pollSettings for `service` | |
| func ServicePoll(config *poll.Settings) { | |
| // Override the default pollSettings for `service` resource here ... | |
| config.Timeout = 15 * time.Second | |
| config.Delay = 100 * time.Millisecond | |
| if runtime.GOARCH == "arm64" || runtime.GOARCH == "arm" { | |
| config.Timeout = 90 * time.Second | |
| } | |
| } | |
| // NetworkPoll tweaks the pollSettings for `network` | |
| func NetworkPoll(config *poll.Settings) { | |
| // Override the default pollSettings for `network` resource here ... | |
| config.Timeout = 30 * time.Second | |
| config.Delay = 100 * time.Millisecond | |
| if runtime.GOARCH == "arm64" || runtime.GOARCH == "arm" { | |
| config.Timeout = 50 * time.Second | |
| } | |
| } | |
| // NewSwarm creates a swarm daemon for testing | |
| func NewSwarm(ctx context.Context, t *testing.T, testEnv *environment.Execution, ops ...daemon.Option) *daemon.Daemon { | |
| t.Helper() | |
| skip.If(t, testEnv.IsRemoteDaemon) | |
| skip.If(t, testEnv.DaemonInfo.OSType == "windows") | |
| skip.If(t, testEnv.IsRootless, "rootless mode doesn't support Swarm-mode") | |
| if testEnv.DaemonInfo.ExperimentalBuild { | |
| ops = append(ops, daemon.WithExperimental()) | |
| } | |
| d := daemon.New(t, ops...) | |
| d.StartAndSwarmInit(ctx, t) | |
| return d | |
| } | |
| // ServiceSpecOpt is used with `CreateService` to pass in service spec modifiers | |
| type ServiceSpecOpt func(*swarmtypes.ServiceSpec) | |
| // CreateService creates a service on the passed in swarm daemon. | |
| func CreateService(ctx context.Context, t *testing.T, d *daemon.Daemon, opts ...ServiceSpecOpt) string { | |
| t.Helper() | |
| apiClient := d.NewClientT(t) | |
| defer apiClient.Close() | |
| spec := CreateServiceSpec(t, opts...) | |
| resp, err := apiClient.ServiceCreate(ctx, client.ServiceCreateOptions{ | |
| Spec: spec, | |
| }) | |
| assert.NilError(t, err, "error creating service") | |
| return resp.ID | |
| } | |
| // CreateServiceSpec creates a default service-spec, and applies the provided options | |
| func CreateServiceSpec(t *testing.T, opts ...ServiceSpecOpt) swarmtypes.ServiceSpec { | |
| t.Helper() | |
| var spec swarmtypes.ServiceSpec | |
| ServiceWithImage("busybox:latest")(&spec) | |
| ServiceWithCommand([]string{"/bin/top"})(&spec) | |
| ServiceWithReplicas(1)(&spec) | |
| for _, o := range opts { | |
| o(&spec) | |
| } | |
| return spec | |
| } | |
| // ServiceWithMode sets the mode of the service to the provided mode. | |
| func ServiceWithMode(mode swarmtypes.ServiceMode) func(*swarmtypes.ServiceSpec) { | |
| return func(spec *swarmtypes.ServiceSpec) { | |
| spec.Mode = mode | |
| } | |
| } | |
| // ServiceWithInit sets whether the service should use init or not | |
| func ServiceWithInit(b *bool) func(*swarmtypes.ServiceSpec) { | |
| return func(spec *swarmtypes.ServiceSpec) { | |
| ensureContainerSpec(spec) | |
| spec.TaskTemplate.ContainerSpec.Init = b | |
| } | |
| } | |
| // ServiceWithImage sets the image to use for the service | |
| func ServiceWithImage(image string) func(*swarmtypes.ServiceSpec) { | |
| return func(spec *swarmtypes.ServiceSpec) { | |
| ensureContainerSpec(spec) | |
| spec.TaskTemplate.ContainerSpec.Image = image | |
| } | |
| } | |
| // ServiceWithCommand sets the command to use for the service | |
| func ServiceWithCommand(cmd []string) ServiceSpecOpt { | |
| return func(spec *swarmtypes.ServiceSpec) { | |
| ensureContainerSpec(spec) | |
| spec.TaskTemplate.ContainerSpec.Command = cmd | |
| } | |
| } | |
| // ServiceWithConfig adds the config reference to the service | |
| func ServiceWithConfig(configRef *swarmtypes.ConfigReference) ServiceSpecOpt { | |
| return func(spec *swarmtypes.ServiceSpec) { | |
| ensureContainerSpec(spec) | |
| spec.TaskTemplate.ContainerSpec.Configs = append(spec.TaskTemplate.ContainerSpec.Configs, configRef) | |
| } | |
| } | |
| // ServiceWithSecret adds the secret reference to the service | |
| func ServiceWithSecret(secretRef *swarmtypes.SecretReference) ServiceSpecOpt { | |
| return func(spec *swarmtypes.ServiceSpec) { | |
| ensureContainerSpec(spec) | |
| spec.TaskTemplate.ContainerSpec.Secrets = append(spec.TaskTemplate.ContainerSpec.Secrets, secretRef) | |
| } | |
| } | |
| // ServiceWithReplicas sets the replicas for the service | |
| func ServiceWithReplicas(n uint64) ServiceSpecOpt { | |
| return func(spec *swarmtypes.ServiceSpec) { | |
| spec.Mode = swarmtypes.ServiceMode{ | |
| Replicated: &swarmtypes.ReplicatedService{ | |
| Replicas: &n, | |
| }, | |
| } | |
| } | |
| } | |
| // ServiceWithMaxReplicas sets the max replicas for the service | |
| func ServiceWithMaxReplicas(n uint64) ServiceSpecOpt { | |
| return func(spec *swarmtypes.ServiceSpec) { | |
| ensurePlacement(spec) | |
| spec.TaskTemplate.Placement.MaxReplicas = n | |
| } | |
| } | |
| func ServiceWithPlacementConstraints(constraints ...string) ServiceSpecOpt { | |
| return func(spec *swarmtypes.ServiceSpec) { | |
| ensurePlacement(spec) | |
| spec.TaskTemplate.Placement.Constraints = constraints | |
| } | |
| } | |
| // ServiceWithName sets the name of the service | |
| func ServiceWithName(name string) ServiceSpecOpt { | |
| return func(spec *swarmtypes.ServiceSpec) { | |
| spec.Annotations.Name = name | |
| } | |
| } | |
| // ServiceWithNetwork sets the network of the service | |
| func ServiceWithNetwork(network string) ServiceSpecOpt { | |
| return func(spec *swarmtypes.ServiceSpec) { | |
| spec.TaskTemplate.Networks = append(spec.TaskTemplate.Networks, | |
| swarmtypes.NetworkAttachmentConfig{Target: network}) | |
| } | |
| } | |
| // ServiceWithEndpoint sets the Endpoint of the service | |
| func ServiceWithEndpoint(endpoint *swarmtypes.EndpointSpec) ServiceSpecOpt { | |
| return func(spec *swarmtypes.ServiceSpec) { | |
| spec.EndpointSpec = endpoint | |
| } | |
| } | |
| // ServiceWithSysctls sets the Sysctls option of the service's ContainerSpec. | |
| func ServiceWithSysctls(sysctls map[string]string) ServiceSpecOpt { | |
| return func(spec *swarmtypes.ServiceSpec) { | |
| ensureContainerSpec(spec) | |
| spec.TaskTemplate.ContainerSpec.Sysctls = sysctls | |
| } | |
| } | |
| // ServiceWithCapabilities sets the Capabilities option of the service's ContainerSpec. | |
| func ServiceWithCapabilities(add []string, drop []string) ServiceSpecOpt { | |
| return func(spec *swarmtypes.ServiceSpec) { | |
| ensureContainerSpec(spec) | |
| spec.TaskTemplate.ContainerSpec.CapabilityAdd = add | |
| spec.TaskTemplate.ContainerSpec.CapabilityDrop = drop | |
| } | |
| } | |
| // ServiceWithPidsLimit sets the PidsLimit option of the service's Resources.Limits. | |
| func ServiceWithPidsLimit(limit int64) ServiceSpecOpt { | |
| return func(spec *swarmtypes.ServiceSpec) { | |
| ensureResources(spec) | |
| spec.TaskTemplate.Resources.Limits.Pids = limit | |
| } | |
| } | |
| func ServiceWithMemorySwap(swap *int64, memoryLimit int64) ServiceSpecOpt { | |
| return func(spec *swarmtypes.ServiceSpec) { | |
| ensureResources(spec) | |
| spec.TaskTemplate.Resources.SwapBytes = swap | |
| spec.TaskTemplate.Resources.Limits.MemoryBytes = memoryLimit | |
| } | |
| } | |
| func ServiceWithMemorySwappiness(swappiness *int64) ServiceSpecOpt { | |
| return func(spec *swarmtypes.ServiceSpec) { | |
| ensureResources(spec) | |
| spec.TaskTemplate.Resources.MemorySwappiness = swappiness | |
| } | |
| } | |
| // GetRunningTasks gets the list of running tasks for a service | |
| func GetRunningTasks(ctx context.Context, t *testing.T, c client.TaskAPIClient, serviceID string) []swarmtypes.Task { | |
| t.Helper() | |
| taskList, err := c.TaskList(ctx, client.TaskListOptions{ | |
| Filters: make(client.Filters). | |
| Add("service", serviceID). | |
| Add("desired-state", "running"), | |
| }) | |
| assert.NilError(t, err) | |
| return taskList.Items | |
| } | |
| // ExecTask runs the passed in exec config on the given task | |
| func ExecTask(ctx context.Context, t *testing.T, d *daemon.Daemon, task swarmtypes.Task, options client.ExecCreateOptions) client.HijackedResponse { | |
| t.Helper() | |
| apiClient := d.NewClientT(t) | |
| defer apiClient.Close() | |
| res, err := apiClient.ExecCreate(ctx, task.Status.ContainerStatus.ContainerID, options) | |
| assert.NilError(t, err, "error creating exec") | |
| attach, err := apiClient.ExecAttach(ctx, res.ID, client.ExecAttachOptions{}) | |
| assert.NilError(t, err, "error attaching to exec") | |
| return attach.HijackedResponse | |
| } | |
| func ensureResources(spec *swarmtypes.ServiceSpec) { | |
| if spec.TaskTemplate.Resources == nil { | |
| spec.TaskTemplate.Resources = &swarmtypes.ResourceRequirements{} | |
| } | |
| if spec.TaskTemplate.Resources.Limits == nil { | |
| spec.TaskTemplate.Resources.Limits = &swarmtypes.Limit{} | |
| } | |
| if spec.TaskTemplate.Resources.Reservations == nil { | |
| spec.TaskTemplate.Resources.Reservations = &swarmtypes.Resources{} | |
| } | |
| } | |
| func ensureContainerSpec(spec *swarmtypes.ServiceSpec) { | |
| if spec.TaskTemplate.ContainerSpec == nil { | |
| spec.TaskTemplate.ContainerSpec = &swarmtypes.ContainerSpec{} | |
| } | |
| } | |
| func ensurePlacement(spec *swarmtypes.ServiceSpec) { | |
| if spec.TaskTemplate.Placement == nil { | |
| spec.TaskTemplate.Placement = &swarmtypes.Placement{} | |
| } | |
| } |