service.go 8.1 KB

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