github.com/rumpl/bof@v23.0.0-rc.2+incompatible/integration/internal/swarm/service.go (about) 1 package swarm 2 3 import ( 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/client" 13 "github.com/docker/docker/testutil/daemon" 14 "github.com/docker/docker/testutil/environment" 15 "gotest.tools/v3/assert" 16 "gotest.tools/v3/poll" 17 "gotest.tools/v3/skip" 18 ) 19 20 // ServicePoll tweaks the pollSettings for `service` 21 func ServicePoll(config *poll.Settings) { 22 // Override the default pollSettings for `service` resource here ... 23 config.Timeout = 15 * time.Second 24 config.Delay = 100 * time.Millisecond 25 if runtime.GOARCH == "arm64" || runtime.GOARCH == "arm" { 26 config.Timeout = 90 * time.Second 27 } 28 } 29 30 // NetworkPoll tweaks the pollSettings for `network` 31 func NetworkPoll(config *poll.Settings) { 32 // Override the default pollSettings for `network` resource here ... 33 config.Timeout = 30 * time.Second 34 config.Delay = 100 * time.Millisecond 35 36 if runtime.GOARCH == "arm64" || runtime.GOARCH == "arm" { 37 config.Timeout = 50 * time.Second 38 } 39 } 40 41 // ContainerPoll tweaks the pollSettings for `container` 42 func ContainerPoll(config *poll.Settings) { 43 // Override the default pollSettings for `container` resource here ... 44 45 if runtime.GOARCH == "arm64" || runtime.GOARCH == "arm" { 46 config.Timeout = 30 * time.Second 47 config.Delay = 100 * time.Millisecond 48 } 49 } 50 51 // NewSwarm creates a swarm daemon for testing 52 func NewSwarm(t *testing.T, testEnv *environment.Execution, ops ...daemon.Option) *daemon.Daemon { 53 t.Helper() 54 skip.If(t, testEnv.IsRemoteDaemon) 55 skip.If(t, testEnv.DaemonInfo.OSType == "windows") 56 skip.If(t, testEnv.IsRootless, "rootless mode doesn't support Swarm-mode") 57 if testEnv.DaemonInfo.ExperimentalBuild { 58 ops = append(ops, daemon.WithExperimental()) 59 } 60 d := daemon.New(t, ops...) 61 d.StartAndSwarmInit(t) 62 return d 63 } 64 65 // ServiceSpecOpt is used with `CreateService` to pass in service spec modifiers 66 type ServiceSpecOpt func(*swarmtypes.ServiceSpec) 67 68 // CreateService creates a service on the passed in swarm daemon. 69 func CreateService(t *testing.T, d *daemon.Daemon, opts ...ServiceSpecOpt) string { 70 t.Helper() 71 72 client := d.NewClientT(t) 73 defer client.Close() 74 75 spec := CreateServiceSpec(t, opts...) 76 resp, err := client.ServiceCreate(context.Background(), spec, types.ServiceCreateOptions{}) 77 assert.NilError(t, err, "error creating service") 78 return resp.ID 79 } 80 81 // CreateServiceSpec creates a default service-spec, and applies the provided options 82 func CreateServiceSpec(t *testing.T, opts ...ServiceSpecOpt) swarmtypes.ServiceSpec { 83 t.Helper() 84 var spec swarmtypes.ServiceSpec 85 ServiceWithImage("busybox:latest")(&spec) 86 ServiceWithCommand([]string{"/bin/top"})(&spec) 87 ServiceWithReplicas(1)(&spec) 88 89 for _, o := range opts { 90 o(&spec) 91 } 92 return spec 93 } 94 95 // ServiceWithMode sets the mode of the service to the provided mode. 96 func ServiceWithMode(mode swarmtypes.ServiceMode) func(*swarmtypes.ServiceSpec) { 97 return func(spec *swarmtypes.ServiceSpec) { 98 spec.Mode = mode 99 } 100 } 101 102 // ServiceWithInit sets whether the service should use init or not 103 func ServiceWithInit(b *bool) func(*swarmtypes.ServiceSpec) { 104 return func(spec *swarmtypes.ServiceSpec) { 105 ensureContainerSpec(spec) 106 spec.TaskTemplate.ContainerSpec.Init = b 107 } 108 } 109 110 // ServiceWithImage sets the image to use for the service 111 func ServiceWithImage(image string) func(*swarmtypes.ServiceSpec) { 112 return func(spec *swarmtypes.ServiceSpec) { 113 ensureContainerSpec(spec) 114 spec.TaskTemplate.ContainerSpec.Image = image 115 } 116 } 117 118 // ServiceWithCommand sets the command to use for the service 119 func ServiceWithCommand(cmd []string) ServiceSpecOpt { 120 return func(spec *swarmtypes.ServiceSpec) { 121 ensureContainerSpec(spec) 122 spec.TaskTemplate.ContainerSpec.Command = cmd 123 } 124 } 125 126 // ServiceWithConfig adds the config reference to the service 127 func ServiceWithConfig(configRef *swarmtypes.ConfigReference) ServiceSpecOpt { 128 return func(spec *swarmtypes.ServiceSpec) { 129 ensureContainerSpec(spec) 130 spec.TaskTemplate.ContainerSpec.Configs = append(spec.TaskTemplate.ContainerSpec.Configs, configRef) 131 } 132 } 133 134 // ServiceWithSecret adds the secret reference to the service 135 func ServiceWithSecret(secretRef *swarmtypes.SecretReference) ServiceSpecOpt { 136 return func(spec *swarmtypes.ServiceSpec) { 137 ensureContainerSpec(spec) 138 spec.TaskTemplate.ContainerSpec.Secrets = append(spec.TaskTemplate.ContainerSpec.Secrets, secretRef) 139 } 140 } 141 142 // ServiceWithReplicas sets the replicas for the service 143 func ServiceWithReplicas(n uint64) ServiceSpecOpt { 144 return func(spec *swarmtypes.ServiceSpec) { 145 spec.Mode = swarmtypes.ServiceMode{ 146 Replicated: &swarmtypes.ReplicatedService{ 147 Replicas: &n, 148 }, 149 } 150 } 151 } 152 153 // ServiceWithMaxReplicas sets the max replicas for the service 154 func ServiceWithMaxReplicas(n uint64) ServiceSpecOpt { 155 return func(spec *swarmtypes.ServiceSpec) { 156 ensurePlacement(spec) 157 spec.TaskTemplate.Placement.MaxReplicas = n 158 } 159 } 160 161 // ServiceWithName sets the name of the service 162 func ServiceWithName(name string) ServiceSpecOpt { 163 return func(spec *swarmtypes.ServiceSpec) { 164 spec.Annotations.Name = name 165 } 166 } 167 168 // ServiceWithNetwork sets the network of the service 169 func ServiceWithNetwork(network string) ServiceSpecOpt { 170 return func(spec *swarmtypes.ServiceSpec) { 171 spec.TaskTemplate.Networks = append(spec.TaskTemplate.Networks, 172 swarmtypes.NetworkAttachmentConfig{Target: network}) 173 } 174 } 175 176 // ServiceWithEndpoint sets the Endpoint of the service 177 func ServiceWithEndpoint(endpoint *swarmtypes.EndpointSpec) ServiceSpecOpt { 178 return func(spec *swarmtypes.ServiceSpec) { 179 spec.EndpointSpec = endpoint 180 } 181 } 182 183 // ServiceWithSysctls sets the Sysctls option of the service's ContainerSpec. 184 func ServiceWithSysctls(sysctls map[string]string) ServiceSpecOpt { 185 return func(spec *swarmtypes.ServiceSpec) { 186 ensureContainerSpec(spec) 187 spec.TaskTemplate.ContainerSpec.Sysctls = sysctls 188 } 189 } 190 191 // ServiceWithCapabilities sets the Capabilities option of the service's ContainerSpec. 192 func ServiceWithCapabilities(add []string, drop []string) ServiceSpecOpt { 193 return func(spec *swarmtypes.ServiceSpec) { 194 ensureContainerSpec(spec) 195 spec.TaskTemplate.ContainerSpec.CapabilityAdd = add 196 spec.TaskTemplate.ContainerSpec.CapabilityDrop = drop 197 } 198 } 199 200 // ServiceWithPidsLimit sets the PidsLimit option of the service's Resources.Limits. 201 func ServiceWithPidsLimit(limit int64) ServiceSpecOpt { 202 return func(spec *swarmtypes.ServiceSpec) { 203 ensureResources(spec) 204 spec.TaskTemplate.Resources.Limits.Pids = limit 205 } 206 } 207 208 // GetRunningTasks gets the list of running tasks for a service 209 func GetRunningTasks(t *testing.T, c client.ServiceAPIClient, serviceID string) []swarmtypes.Task { 210 t.Helper() 211 212 tasks, err := c.TaskList(context.Background(), types.TaskListOptions{ 213 Filters: filters.NewArgs( 214 filters.Arg("service", serviceID), 215 filters.Arg("desired-state", "running"), 216 ), 217 }) 218 219 assert.NilError(t, err) 220 return tasks 221 } 222 223 // ExecTask runs the passed in exec config on the given task 224 func ExecTask(t *testing.T, d *daemon.Daemon, task swarmtypes.Task, config types.ExecConfig) types.HijackedResponse { 225 t.Helper() 226 client := d.NewClientT(t) 227 defer client.Close() 228 229 ctx := context.Background() 230 resp, err := client.ContainerExecCreate(ctx, task.Status.ContainerStatus.ContainerID, config) 231 assert.NilError(t, err, "error creating exec") 232 233 startCheck := types.ExecStartCheck{} 234 attach, err := client.ContainerExecAttach(ctx, resp.ID, startCheck) 235 assert.NilError(t, err, "error attaching to exec") 236 return attach 237 } 238 239 func ensureResources(spec *swarmtypes.ServiceSpec) { 240 if spec.TaskTemplate.Resources == nil { 241 spec.TaskTemplate.Resources = &swarmtypes.ResourceRequirements{} 242 } 243 if spec.TaskTemplate.Resources.Limits == nil { 244 spec.TaskTemplate.Resources.Limits = &swarmtypes.Limit{} 245 } 246 if spec.TaskTemplate.Resources.Reservations == nil { 247 spec.TaskTemplate.Resources.Reservations = &swarmtypes.Resources{} 248 } 249 } 250 251 func ensureContainerSpec(spec *swarmtypes.ServiceSpec) { 252 if spec.TaskTemplate.ContainerSpec == nil { 253 spec.TaskTemplate.ContainerSpec = &swarmtypes.ContainerSpec{} 254 } 255 } 256 257 func ensurePlacement(spec *swarmtypes.ServiceSpec) { 258 if spec.TaskTemplate.Placement == nil { 259 spec.TaskTemplate.Placement = &swarmtypes.Placement{} 260 } 261 }