github.com/moby/docker@v26.1.3+incompatible/integration/service/update_test.go (about) 1 package service // import "github.com/docker/docker/integration/service" 2 3 import ( 4 "context" 5 "testing" 6 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/integration/internal/network" 12 "github.com/docker/docker/integration/internal/swarm" 13 "github.com/docker/docker/testutil" 14 "gotest.tools/v3/assert" 15 is "gotest.tools/v3/assert/cmp" 16 "gotest.tools/v3/poll" 17 "gotest.tools/v3/skip" 18 ) 19 20 func TestServiceUpdateLabel(t *testing.T) { 21 skip.If(t, testEnv.DaemonInfo.OSType != "linux") 22 ctx := setupTest(t) 23 24 d := swarm.NewSwarm(ctx, t, testEnv) 25 defer d.Stop(t) 26 cli := d.NewClientT(t) 27 defer cli.Close() 28 29 serviceName := "TestService_" + t.Name() 30 serviceID := swarm.CreateService(ctx, t, d, swarm.ServiceWithName(serviceName)) 31 service := getService(ctx, t, cli, serviceID) 32 assert.Check(t, is.DeepEqual(service.Spec.Labels, map[string]string{})) 33 34 // add label to empty set 35 service.Spec.Labels["foo"] = "bar" 36 _, err := cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{}) 37 assert.NilError(t, err) 38 poll.WaitOn(t, serviceSpecIsUpdated(ctx, cli, serviceID, service.Version.Index), swarm.ServicePoll) 39 service = getService(ctx, t, cli, serviceID) 40 assert.Check(t, is.DeepEqual(service.Spec.Labels, map[string]string{"foo": "bar"})) 41 42 // add label to non-empty set 43 service.Spec.Labels["foo2"] = "bar" 44 _, err = cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{}) 45 assert.NilError(t, err) 46 poll.WaitOn(t, serviceSpecIsUpdated(ctx, cli, serviceID, service.Version.Index), swarm.ServicePoll) 47 service = getService(ctx, t, cli, serviceID) 48 assert.Check(t, is.DeepEqual(service.Spec.Labels, map[string]string{"foo": "bar", "foo2": "bar"})) 49 50 delete(service.Spec.Labels, "foo2") 51 _, err = cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{}) 52 assert.NilError(t, err) 53 poll.WaitOn(t, serviceSpecIsUpdated(ctx, cli, serviceID, service.Version.Index), swarm.ServicePoll) 54 service = getService(ctx, t, cli, serviceID) 55 assert.Check(t, is.DeepEqual(service.Spec.Labels, map[string]string{"foo": "bar"})) 56 57 delete(service.Spec.Labels, "foo") 58 _, err = cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{}) 59 assert.NilError(t, err) 60 poll.WaitOn(t, serviceSpecIsUpdated(ctx, cli, serviceID, service.Version.Index), swarm.ServicePoll) 61 service = getService(ctx, t, cli, serviceID) 62 assert.Check(t, is.DeepEqual(service.Spec.Labels, map[string]string{})) 63 64 // now make sure we can add again 65 service.Spec.Labels["foo"] = "bar" 66 _, err = cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{}) 67 assert.NilError(t, err) 68 poll.WaitOn(t, serviceSpecIsUpdated(ctx, cli, serviceID, service.Version.Index), swarm.ServicePoll) 69 service = getService(ctx, t, cli, serviceID) 70 assert.Check(t, is.DeepEqual(service.Spec.Labels, map[string]string{"foo": "bar"})) 71 72 err = cli.ServiceRemove(ctx, serviceID) 73 assert.NilError(t, err) 74 } 75 76 func TestServiceUpdateSecrets(t *testing.T) { 77 skip.If(t, testEnv.DaemonInfo.OSType != "linux") 78 ctx := setupTest(t) 79 80 d := swarm.NewSwarm(ctx, t, testEnv) 81 defer d.Stop(t) 82 cli := d.NewClientT(t) 83 defer cli.Close() 84 85 secretName := "TestSecret_" + t.Name() 86 secretTarget := "targetName" 87 resp, err := cli.SecretCreate(ctx, swarmtypes.SecretSpec{ 88 Annotations: swarmtypes.Annotations{ 89 Name: secretName, 90 }, 91 Data: []byte("TESTINGDATA"), 92 }) 93 assert.NilError(t, err) 94 assert.Check(t, resp.ID != "") 95 96 serviceName := "TestService_" + t.Name() 97 serviceID := swarm.CreateService(ctx, t, d, swarm.ServiceWithName(serviceName)) 98 service := getService(ctx, t, cli, serviceID) 99 100 // add secret 101 service.Spec.TaskTemplate.ContainerSpec.Secrets = append(service.Spec.TaskTemplate.ContainerSpec.Secrets, 102 &swarmtypes.SecretReference{ 103 File: &swarmtypes.SecretReferenceFileTarget{ 104 Name: secretTarget, 105 UID: "0", 106 GID: "0", 107 Mode: 0o600, 108 }, 109 SecretID: resp.ID, 110 SecretName: secretName, 111 }, 112 ) 113 _, err = cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{}) 114 assert.NilError(t, err) 115 poll.WaitOn(t, serviceIsUpdated(ctx, cli, serviceID), swarm.ServicePoll) 116 117 service = getService(ctx, t, cli, serviceID) 118 secrets := service.Spec.TaskTemplate.ContainerSpec.Secrets 119 assert.Assert(t, is.Equal(1, len(secrets))) 120 121 secret := *secrets[0] 122 assert.Check(t, is.Equal(secretName, secret.SecretName)) 123 assert.Check(t, nil != secret.File) 124 assert.Check(t, is.Equal(secretTarget, secret.File.Name)) 125 126 // remove 127 service.Spec.TaskTemplate.ContainerSpec.Secrets = []*swarmtypes.SecretReference{} 128 _, err = cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{}) 129 assert.NilError(t, err) 130 poll.WaitOn(t, serviceIsUpdated(ctx, cli, serviceID), swarm.ServicePoll) 131 service = getService(ctx, t, cli, serviceID) 132 assert.Check(t, is.Equal(0, len(service.Spec.TaskTemplate.ContainerSpec.Secrets))) 133 134 err = cli.ServiceRemove(ctx, serviceID) 135 assert.NilError(t, err) 136 } 137 138 func TestServiceUpdateConfigs(t *testing.T) { 139 skip.If(t, testEnv.DaemonInfo.OSType != "linux") 140 ctx := setupTest(t) 141 142 d := swarm.NewSwarm(ctx, t, testEnv) 143 defer d.Stop(t) 144 cli := d.NewClientT(t) 145 defer cli.Close() 146 147 configName := "TestConfig_" + t.Name() 148 configTarget := "targetName" 149 resp, err := cli.ConfigCreate(ctx, swarmtypes.ConfigSpec{ 150 Annotations: swarmtypes.Annotations{ 151 Name: configName, 152 }, 153 Data: []byte("TESTINGDATA"), 154 }) 155 assert.NilError(t, err) 156 assert.Check(t, resp.ID != "") 157 158 serviceName := "TestService_" + t.Name() 159 serviceID := swarm.CreateService(ctx, t, d, swarm.ServiceWithName(serviceName)) 160 service := getService(ctx, t, cli, serviceID) 161 162 // add config 163 service.Spec.TaskTemplate.ContainerSpec.Configs = append(service.Spec.TaskTemplate.ContainerSpec.Configs, 164 &swarmtypes.ConfigReference{ 165 File: &swarmtypes.ConfigReferenceFileTarget{ 166 Name: configTarget, 167 UID: "0", 168 GID: "0", 169 Mode: 0o600, 170 }, 171 ConfigID: resp.ID, 172 ConfigName: configName, 173 }, 174 ) 175 _, err = cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{}) 176 assert.NilError(t, err) 177 poll.WaitOn(t, serviceIsUpdated(ctx, cli, serviceID), swarm.ServicePoll) 178 179 service = getService(ctx, t, cli, serviceID) 180 configs := service.Spec.TaskTemplate.ContainerSpec.Configs 181 assert.Assert(t, is.Equal(1, len(configs))) 182 183 config := *configs[0] 184 assert.Check(t, is.Equal(configName, config.ConfigName)) 185 assert.Check(t, nil != config.File) 186 assert.Check(t, is.Equal(configTarget, config.File.Name)) 187 188 // remove 189 service.Spec.TaskTemplate.ContainerSpec.Configs = []*swarmtypes.ConfigReference{} 190 _, err = cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{}) 191 assert.NilError(t, err) 192 poll.WaitOn(t, serviceIsUpdated(ctx, cli, serviceID), swarm.ServicePoll) 193 service = getService(ctx, t, cli, serviceID) 194 assert.Check(t, is.Equal(0, len(service.Spec.TaskTemplate.ContainerSpec.Configs))) 195 196 err = cli.ServiceRemove(ctx, serviceID) 197 assert.NilError(t, err) 198 } 199 200 func TestServiceUpdateNetwork(t *testing.T) { 201 skip.If(t, testEnv.DaemonInfo.OSType != "linux") 202 ctx := setupTest(t) 203 204 d := swarm.NewSwarm(ctx, t, testEnv) 205 defer d.Stop(t) 206 cli := d.NewClientT(t) 207 defer cli.Close() 208 209 // Create a overlay network 210 testNet := "testNet" + t.Name() 211 overlayID := network.CreateNoError(ctx, t, cli, testNet, 212 network.WithDriver("overlay")) 213 214 var instances uint64 = 1 215 // Create service with the overlay network 216 serviceName := "TestServiceUpdateNetworkRM_" + t.Name() 217 serviceID := swarm.CreateService(ctx, t, d, 218 swarm.ServiceWithReplicas(instances), 219 swarm.ServiceWithName(serviceName), 220 swarm.ServiceWithNetwork(testNet)) 221 222 poll.WaitOn(t, swarm.RunningTasksCount(ctx, cli, serviceID, instances), swarm.ServicePoll) 223 service := getService(ctx, t, cli, serviceID) 224 netInfo, err := cli.NetworkInspect(ctx, testNet, types.NetworkInspectOptions{ 225 Verbose: true, 226 Scope: "swarm", 227 }) 228 assert.NilError(t, err) 229 assert.Assert(t, len(netInfo.Containers) == 2, "Expected 2 endpoints, one for container and one for LB Sandbox") 230 231 // Remove network from service 232 service.Spec.TaskTemplate.Networks = []swarmtypes.NetworkAttachmentConfig{} 233 _, err = cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{}) 234 assert.NilError(t, err) 235 poll.WaitOn(t, serviceIsUpdated(ctx, cli, serviceID), swarm.ServicePoll) 236 237 netInfo, err = cli.NetworkInspect(ctx, testNet, types.NetworkInspectOptions{ 238 Verbose: true, 239 Scope: "swarm", 240 }) 241 242 assert.NilError(t, err) 243 assert.Assert(t, len(netInfo.Containers) == 0, "Load balancing endpoint still exists in network") 244 245 err = cli.NetworkRemove(ctx, overlayID) 246 assert.NilError(t, err) 247 248 err = cli.ServiceRemove(ctx, serviceID) 249 assert.NilError(t, err) 250 } 251 252 // TestServiceUpdatePidsLimit tests creating and updating a service with PidsLimit 253 func TestServiceUpdatePidsLimit(t *testing.T) { 254 skip.If(t, testEnv.DaemonInfo.OSType != "linux") 255 tests := []struct { 256 name string 257 pidsLimit int64 258 expected int64 259 }{ 260 { 261 name: "create service with PidsLimit 300", 262 pidsLimit: 300, 263 expected: 300, 264 }, 265 { 266 name: "unset PidsLimit to 0", 267 pidsLimit: 0, 268 expected: 0, 269 }, 270 { 271 name: "update PidsLimit to 100", 272 pidsLimit: 100, 273 expected: 100, 274 }, 275 } 276 277 ctx := setupTest(t) 278 279 d := swarm.NewSwarm(ctx, t, testEnv) 280 defer d.Stop(t) 281 cli := d.NewClientT(t) 282 defer func() { _ = cli.Close() }() 283 var ( 284 serviceID string 285 service swarmtypes.Service 286 ) 287 for i, tc := range tests { 288 tc := tc 289 t.Run(tc.name, func(t *testing.T) { 290 ctx := testutil.StartSpan(ctx, t) 291 if i == 0 { 292 serviceID = swarm.CreateService(ctx, t, d, swarm.ServiceWithPidsLimit(tc.pidsLimit)) 293 } else { 294 service = getService(ctx, t, cli, serviceID) 295 if service.Spec.TaskTemplate.Resources == nil { 296 service.Spec.TaskTemplate.Resources = &swarmtypes.ResourceRequirements{} 297 } 298 if service.Spec.TaskTemplate.Resources.Limits == nil { 299 service.Spec.TaskTemplate.Resources.Limits = &swarmtypes.Limit{} 300 } 301 service.Spec.TaskTemplate.Resources.Limits.Pids = tc.pidsLimit 302 _, err := cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{}) 303 assert.NilError(t, err) 304 poll.WaitOn(t, serviceIsUpdated(ctx, cli, serviceID), swarm.ServicePoll) 305 } 306 307 poll.WaitOn(t, swarm.RunningTasksCount(ctx, cli, serviceID, 1), swarm.ServicePoll) 308 service = getService(ctx, t, cli, serviceID) 309 container := getServiceTaskContainer(ctx, t, cli, serviceID) 310 assert.Equal(t, service.Spec.TaskTemplate.Resources.Limits.Pids, tc.expected) 311 if tc.expected == 0 { 312 if container.HostConfig.Resources.PidsLimit != nil { 313 t.Fatalf("Expected container.HostConfig.Resources.PidsLimit to be nil") 314 } 315 } else { 316 assert.Assert(t, container.HostConfig.Resources.PidsLimit != nil) 317 assert.Equal(t, *container.HostConfig.Resources.PidsLimit, tc.expected) 318 } 319 }) 320 } 321 322 err := cli.ServiceRemove(ctx, serviceID) 323 assert.NilError(t, err) 324 } 325 326 func getServiceTaskContainer(ctx context.Context, t *testing.T, cli client.APIClient, serviceID string) types.ContainerJSON { 327 t.Helper() 328 tasks, err := cli.TaskList(ctx, types.TaskListOptions{ 329 Filters: filters.NewArgs( 330 filters.Arg("service", serviceID), 331 filters.Arg("desired-state", "running"), 332 ), 333 }) 334 assert.NilError(t, err) 335 assert.Assert(t, len(tasks) > 0) 336 337 ctr, err := cli.ContainerInspect(ctx, tasks[0].Status.ContainerStatus.ContainerID) 338 assert.NilError(t, err) 339 assert.Equal(t, ctr.State.Running, true) 340 return ctr 341 } 342 343 func getService(ctx context.Context, t *testing.T, cli client.ServiceAPIClient, serviceID string) swarmtypes.Service { 344 t.Helper() 345 service, _, err := cli.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{}) 346 assert.NilError(t, err) 347 return service 348 } 349 350 func serviceIsUpdated(ctx context.Context, client client.ServiceAPIClient, serviceID string) func(log poll.LogT) poll.Result { 351 return func(log poll.LogT) poll.Result { 352 service, _, err := client.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{}) 353 switch { 354 case err != nil: 355 return poll.Error(err) 356 case service.UpdateStatus != nil && service.UpdateStatus.State == swarmtypes.UpdateStateCompleted: 357 return poll.Success() 358 default: 359 if service.UpdateStatus != nil { 360 return poll.Continue("waiting for service %s to be updated, state: %s, message: %s", serviceID, service.UpdateStatus.State, service.UpdateStatus.Message) 361 } 362 return poll.Continue("waiting for service %s to be updated", serviceID) 363 } 364 } 365 } 366 367 func serviceSpecIsUpdated(ctx context.Context, client client.ServiceAPIClient, serviceID string, serviceOldVersion uint64) func(log poll.LogT) poll.Result { 368 return func(log poll.LogT) poll.Result { 369 service, _, err := client.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{}) 370 switch { 371 case err != nil: 372 return poll.Error(err) 373 case service.Version.Index > serviceOldVersion: 374 return poll.Success() 375 default: 376 return poll.Continue("waiting for service %s to be updated", serviceID) 377 } 378 } 379 }