github.com/rumpl/bof@v23.0.0-rc.2+incompatible/integration/service/create_test.go (about) 1 package service // import "github.com/docker/docker/integration/service" 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "strings" 8 "testing" 9 "time" 10 11 "github.com/docker/docker/api/types" 12 "github.com/docker/docker/api/types/filters" 13 "github.com/docker/docker/api/types/strslice" 14 swarmtypes "github.com/docker/docker/api/types/swarm" 15 "github.com/docker/docker/api/types/versions" 16 "github.com/docker/docker/client" 17 "github.com/docker/docker/errdefs" 18 "github.com/docker/docker/integration/internal/network" 19 "github.com/docker/docker/integration/internal/swarm" 20 "github.com/docker/docker/testutil/daemon" 21 "gotest.tools/v3/assert" 22 is "gotest.tools/v3/assert/cmp" 23 "gotest.tools/v3/poll" 24 "gotest.tools/v3/skip" 25 ) 26 27 func TestServiceCreateInit(t *testing.T) { 28 defer setupTest(t)() 29 t.Run("daemonInitDisabled", testServiceCreateInit(false)) 30 t.Run("daemonInitEnabled", testServiceCreateInit(true)) 31 } 32 33 func testServiceCreateInit(daemonEnabled bool) func(t *testing.T) { 34 return func(t *testing.T) { 35 var ops = []daemon.Option{} 36 37 if daemonEnabled { 38 ops = append(ops, daemon.WithInit()) 39 } 40 d := swarm.NewSwarm(t, testEnv, ops...) 41 defer d.Stop(t) 42 client := d.NewClientT(t) 43 defer client.Close() 44 45 booleanTrue := true 46 booleanFalse := false 47 48 serviceID := swarm.CreateService(t, d) 49 poll.WaitOn(t, swarm.RunningTasksCount(client, serviceID, 1), swarm.ServicePoll) 50 i := inspectServiceContainer(t, client, serviceID) 51 // HostConfig.Init == nil means that it delegates to daemon configuration 52 assert.Check(t, i.HostConfig.Init == nil) 53 54 serviceID = swarm.CreateService(t, d, swarm.ServiceWithInit(&booleanTrue)) 55 poll.WaitOn(t, swarm.RunningTasksCount(client, serviceID, 1), swarm.ServicePoll) 56 i = inspectServiceContainer(t, client, serviceID) 57 assert.Check(t, is.Equal(true, *i.HostConfig.Init)) 58 59 serviceID = swarm.CreateService(t, d, swarm.ServiceWithInit(&booleanFalse)) 60 poll.WaitOn(t, swarm.RunningTasksCount(client, serviceID, 1), swarm.ServicePoll) 61 i = inspectServiceContainer(t, client, serviceID) 62 assert.Check(t, is.Equal(false, *i.HostConfig.Init)) 63 } 64 } 65 66 func inspectServiceContainer(t *testing.T, client client.APIClient, serviceID string) types.ContainerJSON { 67 t.Helper() 68 filter := filters.NewArgs() 69 filter.Add("label", fmt.Sprintf("com.docker.swarm.service.id=%s", serviceID)) 70 containers, err := client.ContainerList(context.Background(), types.ContainerListOptions{Filters: filter}) 71 assert.NilError(t, err) 72 assert.Check(t, is.Len(containers, 1)) 73 74 i, err := client.ContainerInspect(context.Background(), containers[0].ID) 75 assert.NilError(t, err) 76 return i 77 } 78 79 func TestCreateServiceMultipleTimes(t *testing.T) { 80 skip.If(t, testEnv.DaemonInfo.OSType == "windows") 81 defer setupTest(t)() 82 d := swarm.NewSwarm(t, testEnv) 83 defer d.Stop(t) 84 client := d.NewClientT(t) 85 defer client.Close() 86 ctx := context.Background() 87 88 overlayName := "overlay1_" + t.Name() 89 overlayID := network.CreateNoError(ctx, t, client, overlayName, 90 network.WithCheckDuplicate(), 91 network.WithDriver("overlay"), 92 ) 93 94 var instances uint64 = 4 95 96 serviceName := "TestService_" + t.Name() 97 serviceSpec := []swarm.ServiceSpecOpt{ 98 swarm.ServiceWithReplicas(instances), 99 swarm.ServiceWithName(serviceName), 100 swarm.ServiceWithNetwork(overlayName), 101 } 102 103 serviceID := swarm.CreateService(t, d, serviceSpec...) 104 poll.WaitOn(t, swarm.RunningTasksCount(client, serviceID, instances), swarm.ServicePoll) 105 106 _, _, err := client.ServiceInspectWithRaw(context.Background(), serviceID, types.ServiceInspectOptions{}) 107 assert.NilError(t, err) 108 109 err = client.ServiceRemove(context.Background(), serviceID) 110 assert.NilError(t, err) 111 112 poll.WaitOn(t, swarm.NoTasksForService(ctx, client, serviceID), swarm.ServicePoll) 113 114 serviceID2 := swarm.CreateService(t, d, serviceSpec...) 115 poll.WaitOn(t, swarm.RunningTasksCount(client, serviceID2, instances), swarm.ServicePoll) 116 117 err = client.ServiceRemove(context.Background(), serviceID2) 118 assert.NilError(t, err) 119 120 // we can't just wait on no tasks for the service, counter-intuitively. 121 // Tasks may briefly exist but not show up, if they are are in the process 122 // of being deallocated. To avoid this case, we should retry network remove 123 // a few times, to give tasks time to be deallcoated 124 poll.WaitOn(t, swarm.NoTasksForService(ctx, client, serviceID2), swarm.ServicePoll) 125 126 for retry := 0; retry < 5; retry++ { 127 err = client.NetworkRemove(context.Background(), overlayID) 128 // TODO(dperny): using strings.Contains for error checking is awful, 129 // but so is the fact that swarm functions don't return errdefs errors. 130 // I don't have time at this moment to fix the latter, so I guess I'll 131 // go with the former. 132 // 133 // The full error we're looking for is something like this: 134 // 135 // Error response from daemon: rpc error: code = FailedPrecondition desc = network %v is in use by task %v 136 // 137 // The safest way to catch this, I think, will be to match on "is in 138 // use by", as this is an uninterrupted string that best identifies 139 // this error. 140 if err == nil || !strings.Contains(err.Error(), "is in use by") { 141 // if there is no error, or the error isn't this kind of error, 142 // then we'll break the loop body, and either fail the test or 143 // continue. 144 break 145 } 146 } 147 assert.NilError(t, err) 148 149 poll.WaitOn(t, network.IsRemoved(context.Background(), client, overlayID), poll.WithTimeout(1*time.Minute), poll.WithDelay(10*time.Second)) 150 } 151 152 func TestCreateServiceConflict(t *testing.T) { 153 skip.If(t, testEnv.DaemonInfo.OSType == "windows") 154 defer setupTest(t)() 155 d := swarm.NewSwarm(t, testEnv) 156 defer d.Stop(t) 157 c := d.NewClientT(t) 158 defer c.Close() 159 ctx := context.Background() 160 161 serviceName := "TestService_" + t.Name() 162 serviceSpec := []swarm.ServiceSpecOpt{ 163 swarm.ServiceWithName(serviceName), 164 } 165 166 swarm.CreateService(t, d, serviceSpec...) 167 168 spec := swarm.CreateServiceSpec(t, serviceSpec...) 169 _, err := c.ServiceCreate(ctx, spec, types.ServiceCreateOptions{}) 170 assert.Check(t, errdefs.IsConflict(err)) 171 assert.ErrorContains(t, err, "service "+serviceName+" already exists") 172 } 173 174 func TestCreateServiceMaxReplicas(t *testing.T) { 175 defer setupTest(t)() 176 d := swarm.NewSwarm(t, testEnv) 177 defer d.Stop(t) 178 client := d.NewClientT(t) 179 defer client.Close() 180 181 var maxReplicas uint64 = 2 182 serviceSpec := []swarm.ServiceSpecOpt{ 183 swarm.ServiceWithReplicas(maxReplicas), 184 swarm.ServiceWithMaxReplicas(maxReplicas), 185 } 186 187 serviceID := swarm.CreateService(t, d, serviceSpec...) 188 poll.WaitOn(t, swarm.RunningTasksCount(client, serviceID, maxReplicas), swarm.ServicePoll) 189 190 _, _, err := client.ServiceInspectWithRaw(context.Background(), serviceID, types.ServiceInspectOptions{}) 191 assert.NilError(t, err) 192 } 193 194 func TestCreateWithDuplicateNetworkNames(t *testing.T) { 195 skip.If(t, testEnv.DaemonInfo.OSType == "windows") 196 defer setupTest(t)() 197 d := swarm.NewSwarm(t, testEnv) 198 defer d.Stop(t) 199 client := d.NewClientT(t) 200 defer client.Close() 201 ctx := context.Background() 202 203 name := "foo_" + t.Name() 204 n1 := network.CreateNoError(ctx, t, client, name, network.WithDriver("bridge")) 205 n2 := network.CreateNoError(ctx, t, client, name, network.WithDriver("bridge")) 206 207 // Duplicates with name but with different driver 208 n3 := network.CreateNoError(ctx, t, client, name, network.WithDriver("overlay")) 209 210 // Create Service with the same name 211 var instances uint64 = 1 212 213 serviceName := "top_" + t.Name() 214 serviceID := swarm.CreateService(t, d, 215 swarm.ServiceWithReplicas(instances), 216 swarm.ServiceWithName(serviceName), 217 swarm.ServiceWithNetwork(name), 218 ) 219 220 poll.WaitOn(t, swarm.RunningTasksCount(client, serviceID, instances), swarm.ServicePoll) 221 222 resp, _, err := client.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{}) 223 assert.NilError(t, err) 224 assert.Check(t, is.Equal(n3, resp.Spec.TaskTemplate.Networks[0].Target)) 225 226 // Remove Service, and wait for its tasks to be removed 227 err = client.ServiceRemove(ctx, serviceID) 228 assert.NilError(t, err) 229 poll.WaitOn(t, swarm.NoTasksForService(ctx, client, serviceID), swarm.ServicePoll) 230 231 // Remove networks 232 err = client.NetworkRemove(context.Background(), n3) 233 assert.NilError(t, err) 234 235 err = client.NetworkRemove(context.Background(), n2) 236 assert.NilError(t, err) 237 238 err = client.NetworkRemove(context.Background(), n1) 239 assert.NilError(t, err) 240 241 // Make sure networks have been destroyed. 242 poll.WaitOn(t, network.IsRemoved(context.Background(), client, n3), poll.WithTimeout(1*time.Minute), poll.WithDelay(10*time.Second)) 243 poll.WaitOn(t, network.IsRemoved(context.Background(), client, n2), poll.WithTimeout(1*time.Minute), poll.WithDelay(10*time.Second)) 244 poll.WaitOn(t, network.IsRemoved(context.Background(), client, n1), poll.WithTimeout(1*time.Minute), poll.WithDelay(10*time.Second)) 245 } 246 247 func TestCreateServiceSecretFileMode(t *testing.T) { 248 skip.If(t, testEnv.DaemonInfo.OSType == "windows") 249 defer setupTest(t)() 250 d := swarm.NewSwarm(t, testEnv) 251 defer d.Stop(t) 252 client := d.NewClientT(t) 253 defer client.Close() 254 255 ctx := context.Background() 256 secretName := "TestSecret_" + t.Name() 257 secretResp, err := client.SecretCreate(ctx, swarmtypes.SecretSpec{ 258 Annotations: swarmtypes.Annotations{ 259 Name: secretName, 260 }, 261 Data: []byte("TESTSECRET"), 262 }) 263 assert.NilError(t, err) 264 265 var instances uint64 = 1 266 serviceName := "TestService_" + t.Name() 267 serviceID := swarm.CreateService(t, d, 268 swarm.ServiceWithReplicas(instances), 269 swarm.ServiceWithName(serviceName), 270 swarm.ServiceWithCommand([]string{"/bin/sh", "-c", "ls -l /etc/secret && sleep inf"}), 271 swarm.ServiceWithSecret(&swarmtypes.SecretReference{ 272 File: &swarmtypes.SecretReferenceFileTarget{ 273 Name: "/etc/secret", 274 UID: "0", 275 GID: "0", 276 Mode: 0777, 277 }, 278 SecretID: secretResp.ID, 279 SecretName: secretName, 280 }), 281 ) 282 283 poll.WaitOn(t, swarm.RunningTasksCount(client, serviceID, instances), swarm.ServicePoll) 284 285 body, err := client.ServiceLogs(ctx, serviceID, types.ContainerLogsOptions{ 286 Tail: "1", 287 ShowStdout: true, 288 }) 289 assert.NilError(t, err) 290 defer body.Close() 291 292 content, err := io.ReadAll(body) 293 assert.NilError(t, err) 294 assert.Check(t, is.Contains(string(content), "-rwxrwxrwx")) 295 296 err = client.ServiceRemove(ctx, serviceID) 297 assert.NilError(t, err) 298 poll.WaitOn(t, swarm.NoTasksForService(ctx, client, serviceID), swarm.ServicePoll) 299 300 err = client.SecretRemove(ctx, secretName) 301 assert.NilError(t, err) 302 } 303 304 func TestCreateServiceConfigFileMode(t *testing.T) { 305 skip.If(t, testEnv.DaemonInfo.OSType == "windows") 306 defer setupTest(t)() 307 d := swarm.NewSwarm(t, testEnv) 308 defer d.Stop(t) 309 client := d.NewClientT(t) 310 defer client.Close() 311 312 ctx := context.Background() 313 configName := "TestConfig_" + t.Name() 314 configResp, err := client.ConfigCreate(ctx, swarmtypes.ConfigSpec{ 315 Annotations: swarmtypes.Annotations{ 316 Name: configName, 317 }, 318 Data: []byte("TESTCONFIG"), 319 }) 320 assert.NilError(t, err) 321 322 var instances uint64 = 1 323 serviceName := "TestService_" + t.Name() 324 serviceID := swarm.CreateService(t, d, 325 swarm.ServiceWithName(serviceName), 326 swarm.ServiceWithCommand([]string{"/bin/sh", "-c", "ls -l /etc/config && sleep inf"}), 327 swarm.ServiceWithReplicas(instances), 328 swarm.ServiceWithConfig(&swarmtypes.ConfigReference{ 329 File: &swarmtypes.ConfigReferenceFileTarget{ 330 Name: "/etc/config", 331 UID: "0", 332 GID: "0", 333 Mode: 0777, 334 }, 335 ConfigID: configResp.ID, 336 ConfigName: configName, 337 }), 338 ) 339 340 poll.WaitOn(t, swarm.RunningTasksCount(client, serviceID, instances)) 341 342 body, err := client.ServiceLogs(ctx, serviceID, types.ContainerLogsOptions{ 343 Tail: "1", 344 ShowStdout: true, 345 }) 346 assert.NilError(t, err) 347 defer body.Close() 348 349 content, err := io.ReadAll(body) 350 assert.NilError(t, err) 351 assert.Check(t, is.Contains(string(content), "-rwxrwxrwx")) 352 353 err = client.ServiceRemove(ctx, serviceID) 354 assert.NilError(t, err) 355 poll.WaitOn(t, swarm.NoTasksForService(ctx, client, serviceID)) 356 357 err = client.ConfigRemove(ctx, configName) 358 assert.NilError(t, err) 359 } 360 361 // TestServiceCreateSysctls tests that a service created with sysctl options in 362 // the ContainerSpec correctly applies those options. 363 // 364 // To test this, we're going to create a service with the sysctl option 365 // 366 // {"net.ipv4.ip_nonlocal_bind": "0"} 367 // 368 // We'll get the service's tasks to get the container ID, and then we'll 369 // inspect the container. If the output of the container inspect contains the 370 // sysctl option with the correct value, we can assume that the sysctl has been 371 // plumbed correctly. 372 // 373 // Next, we'll remove that service and create a new service with that option 374 // set to 1. This means that no matter what the default is, we can be confident 375 // that the sysctl option is applying as intended. 376 // 377 // Additionally, we'll do service and task inspects to verify that the inspect 378 // output includes the desired sysctl option. 379 // 380 // We're using net.ipv4.ip_nonlocal_bind because it's something that I'm fairly 381 // confident won't be modified by the container runtime, and won't blow 382 // anything up in the test environment 383 func TestCreateServiceSysctls(t *testing.T) { 384 skip.If( 385 t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.40"), 386 "setting service sysctls is unsupported before api v1.40", 387 ) 388 389 defer setupTest(t)() 390 d := swarm.NewSwarm(t, testEnv) 391 defer d.Stop(t) 392 client := d.NewClientT(t) 393 defer client.Close() 394 395 ctx := context.Background() 396 397 // run thie block twice, so that no matter what the default value of 398 // net.ipv4.ip_nonlocal_bind is, we can verify that setting the sysctl 399 // options works 400 for _, expected := range []string{"0", "1"} { 401 // store the map we're going to be using everywhere. 402 expectedSysctls := map[string]string{"net.ipv4.ip_nonlocal_bind": expected} 403 404 // Create the service with the sysctl options 405 var instances uint64 = 1 406 serviceID := swarm.CreateService(t, d, 407 swarm.ServiceWithSysctls(expectedSysctls), 408 ) 409 410 // wait for the service to converge to 1 running task as expected 411 poll.WaitOn(t, swarm.RunningTasksCount(client, serviceID, instances)) 412 413 // we're going to check 3 things: 414 // 415 // 1. Does the container, when inspected, have the sysctl option set? 416 // 2. Does the task have the sysctl in the spec? 417 // 3. Does the service have the sysctl in the spec? 418 // 419 // if all 3 of these things are true, we know that the sysctl has been 420 // plumbed correctly through the engine. 421 // 422 // We don't actually have to get inside the container and check its 423 // logs or anything. If we see the sysctl set on the container inspect, 424 // we know that the sysctl is plumbed correctly. everything below that 425 // level has been tested elsewhere. (thanks @thaJeztah, because an 426 // earlier version of this test had to get container logs and was much 427 // more complex) 428 429 // get all of the tasks of the service, so we can get the container 430 filter := filters.NewArgs() 431 filter.Add("service", serviceID) 432 tasks, err := client.TaskList(ctx, types.TaskListOptions{ 433 Filters: filter, 434 }) 435 assert.NilError(t, err) 436 assert.Check(t, is.Equal(len(tasks), 1)) 437 438 // verify that the container has the sysctl option set 439 ctnr, err := client.ContainerInspect(ctx, tasks[0].Status.ContainerStatus.ContainerID) 440 assert.NilError(t, err) 441 assert.DeepEqual(t, ctnr.HostConfig.Sysctls, expectedSysctls) 442 443 // verify that the task has the sysctl option set in the task object 444 assert.DeepEqual(t, tasks[0].Spec.ContainerSpec.Sysctls, expectedSysctls) 445 446 // verify that the service also has the sysctl set in the spec. 447 service, _, err := client.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{}) 448 assert.NilError(t, err) 449 assert.DeepEqual(t, 450 service.Spec.TaskTemplate.ContainerSpec.Sysctls, expectedSysctls, 451 ) 452 } 453 } 454 455 // TestServiceCreateCapabilities tests that a service created with capabilities options in 456 // the ContainerSpec correctly applies those options. 457 // 458 // To test this, we're going to create a service with the capabilities option 459 // 460 // []string{"CAP_NET_RAW", "CAP_SYS_CHROOT"} 461 // 462 // We'll get the service's tasks to get the container ID, and then we'll 463 // inspect the container. If the output of the container inspect contains the 464 // capabilities option with the correct value, we can assume that the capabilities has been 465 // plumbed correctly. 466 func TestCreateServiceCapabilities(t *testing.T) { 467 skip.If( 468 t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.41"), 469 "setting service capabilities is unsupported before api v1.41", 470 ) 471 472 defer setupTest(t)() 473 d := swarm.NewSwarm(t, testEnv) 474 defer d.Stop(t) 475 client := d.NewClientT(t) 476 defer client.Close() 477 478 ctx := context.Background() 479 480 // store the map we're going to be using everywhere. 481 capAdd := []string{"CAP_SYS_CHROOT"} 482 capDrop := []string{"CAP_NET_RAW"} 483 484 // Create the service with the capabilities options 485 var instances uint64 = 1 486 serviceID := swarm.CreateService(t, d, 487 swarm.ServiceWithCapabilities(capAdd, capDrop), 488 ) 489 490 // wait for the service to converge to 1 running task as expected 491 poll.WaitOn(t, swarm.RunningTasksCount(client, serviceID, instances)) 492 493 // we're going to check 3 things: 494 // 495 // 1. Does the container, when inspected, have the capabilities option set? 496 // 2. Does the task have the capabilities in the spec? 497 // 3. Does the service have the capabilities in the spec? 498 // 499 // if all 3 of these things are true, we know that the capabilities has been 500 // plumbed correctly through the engine. 501 // 502 // We don't actually have to get inside the container and check its 503 // logs or anything. If we see the capabilities set on the container inspect, 504 // we know that the capabilities is plumbed correctly. everything below that 505 // level has been tested elsewhere. 506 507 // get all of the tasks of the service, so we can get the container 508 filter := filters.NewArgs() 509 filter.Add("service", serviceID) 510 tasks, err := client.TaskList(ctx, types.TaskListOptions{ 511 Filters: filter, 512 }) 513 assert.NilError(t, err) 514 assert.Check(t, is.Equal(len(tasks), 1)) 515 516 // verify that the container has the capabilities option set 517 ctnr, err := client.ContainerInspect(ctx, tasks[0].Status.ContainerStatus.ContainerID) 518 assert.NilError(t, err) 519 assert.DeepEqual(t, ctnr.HostConfig.CapAdd, strslice.StrSlice(capAdd)) 520 assert.DeepEqual(t, ctnr.HostConfig.CapDrop, strslice.StrSlice(capDrop)) 521 522 // verify that the task has the capabilities option set in the task object 523 assert.DeepEqual(t, tasks[0].Spec.ContainerSpec.CapabilityAdd, capAdd) 524 assert.DeepEqual(t, tasks[0].Spec.ContainerSpec.CapabilityDrop, capDrop) 525 526 // verify that the service also has the capabilities set in the spec. 527 service, _, err := client.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{}) 528 assert.NilError(t, err) 529 assert.DeepEqual(t, service.Spec.TaskTemplate.ContainerSpec.CapabilityAdd, capAdd) 530 assert.DeepEqual(t, service.Spec.TaskTemplate.ContainerSpec.CapabilityDrop, capDrop) 531 }