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