github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/swarmkit/manager/orchestrator/update/updater_test.go (about) 1 package update 2 3 import ( 4 "context" 5 "testing" 6 "time" 7 8 "github.com/docker/swarmkit/api" 9 "github.com/docker/swarmkit/manager/orchestrator" 10 "github.com/docker/swarmkit/manager/orchestrator/restart" 11 "github.com/docker/swarmkit/manager/state" 12 "github.com/docker/swarmkit/manager/state/store" 13 gogotypes "github.com/gogo/protobuf/types" 14 "github.com/stretchr/testify/assert" 15 "github.com/stretchr/testify/require" 16 ) 17 18 func getRunnableSlotSlice(t *testing.T, s *store.MemoryStore, service *api.Service) []orchestrator.Slot { 19 var ( 20 tasks []*api.Task 21 err error 22 ) 23 s.View(func(tx store.ReadTx) { 24 tasks, err = store.FindTasks(tx, store.ByServiceID(service.ID)) 25 }) 26 require.NoError(t, err) 27 28 runningSlots := make(map[uint64]orchestrator.Slot) 29 for _, t := range tasks { 30 if t.DesiredState <= api.TaskStateRunning { 31 runningSlots[t.Slot] = append(runningSlots[t.Slot], t) 32 } 33 } 34 35 var runnableSlice []orchestrator.Slot 36 for _, slot := range runningSlots { 37 runnableSlice = append(runnableSlice, slot) 38 } 39 return runnableSlice 40 } 41 42 func getRunningServiceTasks(t *testing.T, s *store.MemoryStore, service *api.Service) []*api.Task { 43 var ( 44 err error 45 tasks []*api.Task 46 ) 47 48 s.View(func(tx store.ReadTx) { 49 tasks, err = store.FindTasks(tx, store.ByServiceID(service.ID)) 50 }) 51 assert.NoError(t, err) 52 53 running := []*api.Task{} 54 for _, task := range tasks { 55 if task.Status.State == api.TaskStateRunning { 56 running = append(running, task) 57 } 58 } 59 return running 60 } 61 62 func TestUpdater(t *testing.T) { 63 ctx := context.Background() 64 s := store.NewMemoryStore(nil) 65 assert.NotNil(t, s) 66 defer s.Close() 67 68 // Move tasks to their desired state. 69 watch, cancel := state.Watch(s.WatchQueue(), api.EventUpdateTask{}) 70 defer cancel() 71 go func() { 72 for e := range watch { 73 task := e.(api.EventUpdateTask).Task 74 if task.Status.State == task.DesiredState { 75 continue 76 } 77 err := s.Update(func(tx store.Tx) error { 78 task = store.GetTask(tx, task.ID) 79 task.Status.State = task.DesiredState 80 return store.UpdateTask(tx, task) 81 }) 82 assert.NoError(t, err) 83 } 84 }() 85 86 instances := 3 87 cluster := &api.Cluster{ 88 // test cluster configuration propagation to task creation. 89 Spec: api.ClusterSpec{ 90 Annotations: api.Annotations{ 91 Name: store.DefaultClusterName, 92 }, 93 }, 94 } 95 96 service := &api.Service{ 97 ID: "id1", 98 Spec: api.ServiceSpec{ 99 Annotations: api.Annotations{ 100 Name: "name1", 101 }, 102 Mode: &api.ServiceSpec_Replicated{ 103 Replicated: &api.ReplicatedService{ 104 Replicas: uint64(instances), 105 }, 106 }, 107 Task: api.TaskSpec{ 108 Runtime: &api.TaskSpec_Container{ 109 Container: &api.ContainerSpec{ 110 Image: "v:1", 111 }, 112 }, 113 }, 114 Update: &api.UpdateConfig{ 115 // avoid having Run block for a long time to watch for failures 116 Monitor: gogotypes.DurationProto(50 * time.Millisecond), 117 }, 118 }, 119 } 120 121 // Create the cluster, service, and tasks for the service. 122 err := s.Update(func(tx store.Tx) error { 123 assert.NoError(t, store.CreateCluster(tx, cluster)) 124 assert.NoError(t, store.CreateService(tx, service)) 125 for i := 0; i < instances; i++ { 126 assert.NoError(t, store.CreateTask(tx, orchestrator.NewTask(cluster, service, uint64(i), ""))) 127 } 128 return nil 129 }) 130 assert.NoError(t, err) 131 132 originalTasks := getRunnableSlotSlice(t, s, service) 133 for _, slot := range originalTasks { 134 for _, task := range slot { 135 assert.Equal(t, "v:1", task.Spec.GetContainer().Image) 136 assert.Nil(t, task.LogDriver) // should be left alone 137 } 138 } 139 140 // Change the image and log driver to force an update. 141 service.Spec.Task.GetContainer().Image = "v:2" 142 service.Spec.Task.LogDriver = &api.Driver{Name: "tasklogdriver"} 143 updater := NewUpdater(s, restart.NewSupervisor(s), cluster, service) 144 updater.Run(ctx, getRunnableSlotSlice(t, s, service)) 145 updatedTasks := getRunnableSlotSlice(t, s, service) 146 for _, slot := range updatedTasks { 147 for _, task := range slot { 148 assert.Equal(t, "v:2", task.Spec.GetContainer().Image) 149 assert.Equal(t, service.Spec.Task.LogDriver, task.LogDriver) // pick up from task 150 } 151 } 152 153 // Update the spec again to force an update. 154 service.Spec.Task.GetContainer().Image = "v:3" 155 cluster.Spec.TaskDefaults.LogDriver = &api.Driver{Name: "clusterlogdriver"} // make cluster default logdriver. 156 service.Spec.Update = &api.UpdateConfig{ 157 Parallelism: 1, 158 Monitor: gogotypes.DurationProto(50 * time.Millisecond), 159 } 160 updater = NewUpdater(s, restart.NewSupervisor(s), cluster, service) 161 updater.Run(ctx, getRunnableSlotSlice(t, s, service)) 162 updatedTasks = getRunnableSlotSlice(t, s, service) 163 for _, slot := range updatedTasks { 164 for _, task := range slot { 165 assert.Equal(t, "v:3", task.Spec.GetContainer().Image) 166 assert.Equal(t, service.Spec.Task.LogDriver, task.LogDriver) // still pick up from task 167 } 168 } 169 170 service.Spec.Task.GetContainer().Image = "v:4" 171 service.Spec.Task.LogDriver = nil // use cluster default now. 172 service.Spec.Update = &api.UpdateConfig{ 173 Parallelism: 1, 174 Delay: 10 * time.Millisecond, 175 Monitor: gogotypes.DurationProto(50 * time.Millisecond), 176 } 177 updater = NewUpdater(s, restart.NewSupervisor(s), cluster, service) 178 updater.Run(ctx, getRunnableSlotSlice(t, s, service)) 179 updatedTasks = getRunnableSlotSlice(t, s, service) 180 for _, slot := range updatedTasks { 181 for _, task := range slot { 182 assert.Equal(t, "v:4", task.Spec.GetContainer().Image) 183 assert.Equal(t, cluster.Spec.TaskDefaults.LogDriver, task.LogDriver) // pick up from cluster 184 } 185 } 186 187 service.Spec.Task.GetContainer().Image = "v:5" 188 service.Spec.Update = &api.UpdateConfig{ 189 Parallelism: 1, 190 Delay: 10 * time.Millisecond, 191 Order: api.UpdateConfig_START_FIRST, 192 Monitor: gogotypes.DurationProto(50 * time.Millisecond), 193 } 194 updater = NewUpdater(s, restart.NewSupervisor(s), cluster, service) 195 updater.Run(ctx, getRunnableSlotSlice(t, s, service)) 196 updatedTasks = getRunnableSlotSlice(t, s, service) 197 assert.Equal(t, instances, len(updatedTasks)) 198 for _, instance := range updatedTasks { 199 for _, task := range instance { 200 assert.Equal(t, "v:5", task.Spec.GetContainer().Image) 201 } 202 } 203 204 // Update pull options with new registry auth. 205 service.Spec.Task.GetContainer().PullOptions = &api.ContainerSpec_PullOptions{ 206 RegistryAuth: "opaque-token-1", 207 } 208 originalTasks = getRunnableSlotSlice(t, s, service) 209 updater = NewUpdater(s, restart.NewSupervisor(s), cluster, service) 210 updater.Run(ctx, originalTasks) 211 updatedTasks = getRunnableSlotSlice(t, s, service) 212 assert.Len(t, updatedTasks, instances) 213 214 // Confirm that the original runnable tasks are all still there. 215 runnableTaskIDs := make(map[string]struct{}, len(updatedTasks)) 216 for _, slot := range updatedTasks { 217 for _, task := range slot { 218 runnableTaskIDs[task.ID] = struct{}{} 219 } 220 } 221 assert.Len(t, runnableTaskIDs, instances) 222 for _, slot := range originalTasks { 223 for _, task := range slot { 224 assert.Contains(t, runnableTaskIDs, task.ID) 225 } 226 } 227 228 // Update the desired state of the tasks to SHUTDOWN to simulate the 229 // case where images failed to pull due to bad registry auth. 230 taskSlots := make([]orchestrator.Slot, len(updatedTasks)) 231 assert.NoError(t, s.Update(func(tx store.Tx) error { 232 for i, slot := range updatedTasks { 233 taskSlots[i] = make(orchestrator.Slot, len(slot)) 234 for j, task := range slot { 235 task = store.GetTask(tx, task.ID) 236 task.DesiredState = api.TaskStateShutdown 237 task.Status.State = task.DesiredState 238 assert.NoError(t, store.UpdateTask(tx, task)) 239 taskSlots[i][j] = task 240 } 241 } 242 return nil 243 })) 244 245 // Update pull options again with a different registry auth. 246 service.Spec.Task.GetContainer().PullOptions = &api.ContainerSpec_PullOptions{ 247 RegistryAuth: "opaque-token-2", 248 } 249 updater = NewUpdater(s, restart.NewSupervisor(s), cluster, service) 250 updater.Run(ctx, taskSlots) // Note that these tasks are all shutdown. 251 updatedTasks = getRunnableSlotSlice(t, s, service) 252 assert.Len(t, updatedTasks, instances) 253 } 254 255 func TestUpdaterPlacement(t *testing.T) { 256 ctx := context.Background() 257 s := store.NewMemoryStore(nil) 258 assert.NotNil(t, s) 259 defer s.Close() 260 261 // Move tasks to their desired state. 262 watch, cancel := state.Watch(s.WatchQueue(), api.EventUpdateTask{}) 263 defer cancel() 264 go func() { 265 for e := range watch { 266 task := e.(api.EventUpdateTask).Task 267 if task.Status.State == task.DesiredState { 268 continue 269 } 270 err := s.Update(func(tx store.Tx) error { 271 task = store.GetTask(tx, task.ID) 272 task.Status.State = task.DesiredState 273 return store.UpdateTask(tx, task) 274 }) 275 assert.NoError(t, err) 276 } 277 }() 278 279 instances := 3 280 cluster := &api.Cluster{ 281 // test cluster configuration propagation to task creation. 282 Spec: api.ClusterSpec{ 283 Annotations: api.Annotations{ 284 Name: store.DefaultClusterName, 285 }, 286 }, 287 } 288 289 service := &api.Service{ 290 ID: "id1", 291 SpecVersion: &api.Version{Index: 1}, 292 Spec: api.ServiceSpec{ 293 Annotations: api.Annotations{ 294 Name: "name1", 295 }, 296 Mode: &api.ServiceSpec_Replicated{ 297 Replicated: &api.ReplicatedService{ 298 Replicas: uint64(instances), 299 }, 300 }, 301 Task: api.TaskSpec{ 302 Runtime: &api.TaskSpec_Container{ 303 Container: &api.ContainerSpec{ 304 Image: "v:1", 305 }, 306 }, 307 }, 308 Update: &api.UpdateConfig{ 309 // avoid having Run block for a long time to watch for failures 310 Monitor: gogotypes.DurationProto(50 * time.Millisecond), 311 }, 312 }, 313 } 314 315 node := &api.Node{ID: "node1"} 316 317 // Create the cluster, service, and tasks for the service. 318 err := s.Update(func(tx store.Tx) error { 319 assert.NoError(t, store.CreateCluster(tx, cluster)) 320 assert.NoError(t, store.CreateService(tx, service)) 321 store.CreateNode(tx, node) 322 for i := 0; i < instances; i++ { 323 assert.NoError(t, store.CreateTask(tx, orchestrator.NewTask(cluster, service, uint64(i), "node1"))) 324 } 325 return nil 326 }) 327 assert.NoError(t, err) 328 329 originalTasks := getRunnableSlotSlice(t, s, service) 330 originalTasksMaps := make([]map[string]*api.Task, len(originalTasks)) 331 originalTaskCount := 0 332 for i, slot := range originalTasks { 333 originalTasksMaps[i] = make(map[string]*api.Task) 334 for _, task := range slot { 335 originalTasksMaps[i][task.GetID()] = task 336 assert.Equal(t, "v:1", task.Spec.GetContainer().Image) 337 assert.Nil(t, task.Spec.Placement) 338 originalTaskCount++ 339 } 340 } 341 342 // Change the placement constraints 343 service.SpecVersion.Index++ 344 service.Spec.Task.Placement = &api.Placement{} 345 service.Spec.Task.Placement.Constraints = append(service.Spec.Task.Placement.Constraints, "node.name=*") 346 updater := NewUpdater(s, restart.NewSupervisor(s), cluster, service) 347 updater.Run(ctx, getRunnableSlotSlice(t, s, service)) 348 updatedTasks := getRunnableSlotSlice(t, s, service) 349 updatedTaskCount := 0 350 for _, slot := range updatedTasks { 351 for _, task := range slot { 352 for i, slot := range originalTasks { 353 originalTasksMaps[i] = make(map[string]*api.Task) 354 for _, tasko := range slot { 355 if task.GetID() == tasko.GetID() { 356 updatedTaskCount++ 357 } 358 } 359 } 360 } 361 } 362 assert.Equal(t, originalTaskCount, updatedTaskCount) 363 } 364 365 func TestUpdaterFailureAction(t *testing.T) { 366 t.Parallel() 367 368 ctx := context.Background() 369 s := store.NewMemoryStore(nil) 370 assert.NotNil(t, s) 371 defer s.Close() 372 373 // Fail new tasks the updater tries to run 374 watch, cancel := state.Watch(s.WatchQueue(), api.EventUpdateTask{}) 375 defer cancel() 376 go func() { 377 for e := range watch { 378 task := e.(api.EventUpdateTask).Task 379 if task.DesiredState == api.TaskStateRunning && task.Status.State != api.TaskStateFailed { 380 err := s.Update(func(tx store.Tx) error { 381 task = store.GetTask(tx, task.ID) 382 task.Status.State = api.TaskStateFailed 383 return store.UpdateTask(tx, task) 384 }) 385 assert.NoError(t, err) 386 } else if task.DesiredState > api.TaskStateRunning { 387 err := s.Update(func(tx store.Tx) error { 388 task = store.GetTask(tx, task.ID) 389 task.Status.State = task.DesiredState 390 return store.UpdateTask(tx, task) 391 }) 392 assert.NoError(t, err) 393 } 394 } 395 }() 396 397 instances := 3 398 cluster := &api.Cluster{ 399 Spec: api.ClusterSpec{ 400 Annotations: api.Annotations{ 401 Name: store.DefaultClusterName, 402 }, 403 }, 404 } 405 406 service := &api.Service{ 407 ID: "id1", 408 Spec: api.ServiceSpec{ 409 Annotations: api.Annotations{ 410 Name: "name1", 411 }, 412 Mode: &api.ServiceSpec_Replicated{ 413 Replicated: &api.ReplicatedService{ 414 Replicas: uint64(instances), 415 }, 416 }, 417 Task: api.TaskSpec{ 418 Runtime: &api.TaskSpec_Container{ 419 Container: &api.ContainerSpec{ 420 Image: "v:1", 421 }, 422 }, 423 }, 424 Update: &api.UpdateConfig{ 425 FailureAction: api.UpdateConfig_PAUSE, 426 Parallelism: 1, 427 Delay: 500 * time.Millisecond, 428 Monitor: gogotypes.DurationProto(500 * time.Millisecond), 429 }, 430 }, 431 } 432 433 err := s.Update(func(tx store.Tx) error { 434 assert.NoError(t, store.CreateCluster(tx, cluster)) 435 assert.NoError(t, store.CreateService(tx, service)) 436 for i := 0; i < instances; i++ { 437 assert.NoError(t, store.CreateTask(tx, orchestrator.NewTask(cluster, service, uint64(i), ""))) 438 } 439 return nil 440 }) 441 assert.NoError(t, err) 442 443 originalTasks := getRunnableSlotSlice(t, s, service) 444 for _, slot := range originalTasks { 445 for _, task := range slot { 446 assert.Equal(t, "v:1", task.Spec.GetContainer().Image) 447 } 448 } 449 450 service.Spec.Task.GetContainer().Image = "v:2" 451 updater := NewUpdater(s, restart.NewSupervisor(s), cluster, service) 452 updater.Run(ctx, getRunnableSlotSlice(t, s, service)) 453 updatedTasks := getRunnableSlotSlice(t, s, service) 454 v1Counter := 0 455 v2Counter := 0 456 for _, slot := range updatedTasks { 457 for _, task := range slot { 458 if task.Spec.GetContainer().Image == "v:1" { 459 v1Counter++ 460 } else if task.Spec.GetContainer().Image == "v:2" { 461 v2Counter++ 462 } 463 } 464 } 465 assert.Equal(t, instances-1, v1Counter) 466 assert.Equal(t, 1, v2Counter) 467 468 s.View(func(tx store.ReadTx) { 469 service = store.GetService(tx, service.ID) 470 }) 471 assert.Equal(t, api.UpdateStatus_PAUSED, service.UpdateStatus.State) 472 473 // Updating again should do nothing while the update is PAUSED 474 updater = NewUpdater(s, restart.NewSupervisor(s), cluster, service) 475 updater.Run(ctx, getRunnableSlotSlice(t, s, service)) 476 updatedTasks = getRunnableSlotSlice(t, s, service) 477 v1Counter = 0 478 v2Counter = 0 479 for _, slot := range updatedTasks { 480 for _, task := range slot { 481 if task.Spec.GetContainer().Image == "v:1" { 482 v1Counter++ 483 } else if task.Spec.GetContainer().Image == "v:2" { 484 v2Counter++ 485 } 486 } 487 } 488 assert.Equal(t, instances-1, v1Counter) 489 assert.Equal(t, 1, v2Counter) 490 491 // Switch to a service with FailureAction: CONTINUE 492 err = s.Update(func(tx store.Tx) error { 493 service = store.GetService(tx, service.ID) 494 service.Spec.Update.FailureAction = api.UpdateConfig_CONTINUE 495 service.UpdateStatus = nil 496 assert.NoError(t, store.UpdateService(tx, service)) 497 return nil 498 }) 499 assert.NoError(t, err) 500 501 service.Spec.Task.GetContainer().Image = "v:3" 502 updater = NewUpdater(s, restart.NewSupervisor(s), cluster, service) 503 updater.Run(ctx, getRunnableSlotSlice(t, s, service)) 504 updatedTasks = getRunnableSlotSlice(t, s, service) 505 v2Counter = 0 506 v3Counter := 0 507 for _, slot := range updatedTasks { 508 for _, task := range slot { 509 if task.Spec.GetContainer().Image == "v:2" { 510 v2Counter++ 511 } else if task.Spec.GetContainer().Image == "v:3" { 512 v3Counter++ 513 } 514 } 515 } 516 517 assert.Equal(t, 0, v2Counter) 518 assert.Equal(t, instances, v3Counter) 519 } 520 521 func TestUpdaterTaskTimeout(t *testing.T) { 522 ctx := context.Background() 523 s := store.NewMemoryStore(nil) 524 assert.NotNil(t, s) 525 defer s.Close() 526 527 // Move tasks to their desired state. 528 watch, cancel := state.Watch(s.WatchQueue(), api.EventUpdateTask{}) 529 defer cancel() 530 go func() { 531 for e := range watch { 532 task := e.(api.EventUpdateTask).Task 533 err := s.Update(func(tx store.Tx) error { 534 task = store.GetTask(tx, task.ID) 535 // Explicitly do not set task state to 536 // DEAD to trigger TaskTimeout 537 if task.DesiredState == api.TaskStateRunning && task.Status.State != api.TaskStateRunning { 538 task.Status.State = api.TaskStateRunning 539 return store.UpdateTask(tx, task) 540 } 541 return nil 542 }) 543 assert.NoError(t, err) 544 } 545 }() 546 547 var instances uint64 = 3 548 service := &api.Service{ 549 ID: "id1", 550 Spec: api.ServiceSpec{ 551 Annotations: api.Annotations{ 552 Name: "name1", 553 }, 554 Task: api.TaskSpec{ 555 Runtime: &api.TaskSpec_Container{ 556 Container: &api.ContainerSpec{ 557 Image: "v:1", 558 }, 559 }, 560 }, 561 Mode: &api.ServiceSpec_Replicated{ 562 Replicated: &api.ReplicatedService{ 563 Replicas: instances, 564 }, 565 }, 566 Update: &api.UpdateConfig{ 567 // avoid having Run block for a long time to watch for failures 568 Monitor: gogotypes.DurationProto(50 * time.Millisecond), 569 }, 570 }, 571 } 572 573 err := s.Update(func(tx store.Tx) error { 574 assert.NoError(t, store.CreateService(tx, service)) 575 for i := uint64(0); i < instances; i++ { 576 task := orchestrator.NewTask(nil, service, uint64(i), "") 577 task.Status.State = api.TaskStateRunning 578 assert.NoError(t, store.CreateTask(tx, task)) 579 } 580 return nil 581 }) 582 assert.NoError(t, err) 583 584 originalTasks := getRunnableSlotSlice(t, s, service) 585 for _, slot := range originalTasks { 586 for _, task := range slot { 587 assert.Equal(t, "v:1", task.Spec.GetContainer().Image) 588 } 589 } 590 591 before := time.Now() 592 593 service.Spec.Task.GetContainer().Image = "v:2" 594 updater := NewUpdater(s, restart.NewSupervisor(s), nil, service) 595 // Override the default (1 minute) to speed up the test. 596 updater.restarts.TaskTimeout = 100 * time.Millisecond 597 updater.Run(ctx, getRunnableSlotSlice(t, s, service)) 598 updatedTasks := getRunnableSlotSlice(t, s, service) 599 for _, slot := range updatedTasks { 600 for _, task := range slot { 601 assert.Equal(t, "v:2", task.Spec.GetContainer().Image) 602 } 603 } 604 605 after := time.Now() 606 607 // At least 100 ms should have elapsed. Only check the lower bound, 608 // because the system may be slow and it could have taken longer. 609 if after.Sub(before) < 100*time.Millisecond { 610 t.Fatal("stop timeout should have elapsed") 611 } 612 } 613 614 func TestUpdaterOrder(t *testing.T) { 615 ctx := context.Background() 616 s := store.NewMemoryStore(nil) 617 assert.NotNil(t, s) 618 619 // Move tasks to their desired state. 620 watch, cancel := state.Watch(s.WatchQueue(), api.EventUpdateTask{}) 621 defer cancel() 622 go func() { 623 for e := range watch { 624 task := e.(api.EventUpdateTask).Task 625 if task.Status.State == task.DesiredState { 626 continue 627 } 628 if task.DesiredState == api.TaskStateShutdown { 629 // dont progress, simulate that task takes time to shutdown 630 continue 631 } 632 err := s.Update(func(tx store.Tx) error { 633 task = store.GetTask(tx, task.ID) 634 task.Status.State = task.DesiredState 635 return store.UpdateTask(tx, task) 636 }) 637 assert.NoError(t, err) 638 } 639 }() 640 641 instances := 3 642 service := &api.Service{ 643 ID: "id1", 644 Spec: api.ServiceSpec{ 645 Annotations: api.Annotations{ 646 Name: "name1", 647 }, 648 Task: api.TaskSpec{ 649 Runtime: &api.TaskSpec_Container{ 650 Container: &api.ContainerSpec{ 651 Image: "v:1", 652 StopGracePeriod: gogotypes.DurationProto(time.Hour), 653 }, 654 }, 655 }, 656 Mode: &api.ServiceSpec_Replicated{ 657 Replicated: &api.ReplicatedService{ 658 Replicas: uint64(instances), 659 }, 660 }, 661 }, 662 } 663 664 err := s.Update(func(tx store.Tx) error { 665 assert.NoError(t, store.CreateService(tx, service)) 666 for i := 0; i < instances; i++ { 667 assert.NoError(t, store.CreateTask(tx, orchestrator.NewTask(nil, service, uint64(i), ""))) 668 } 669 return nil 670 }) 671 assert.NoError(t, err) 672 673 originalTasks := getRunnableSlotSlice(t, s, service) 674 for _, instance := range originalTasks { 675 for _, task := range instance { 676 assert.Equal(t, "v:1", task.Spec.GetContainer().Image) 677 // progress task from New to Running 678 err := s.Update(func(tx store.Tx) error { 679 task = store.GetTask(tx, task.ID) 680 task.Status.State = task.DesiredState 681 return store.UpdateTask(tx, task) 682 }) 683 assert.NoError(t, err) 684 } 685 } 686 service.Spec.Task.GetContainer().Image = "v:2" 687 service.Spec.Update = &api.UpdateConfig{ 688 Parallelism: 1, 689 Order: api.UpdateConfig_START_FIRST, 690 Delay: 10 * time.Millisecond, 691 Monitor: gogotypes.DurationProto(50 * time.Millisecond), 692 } 693 updater := NewUpdater(s, restart.NewSupervisor(s), nil, service) 694 updater.Run(ctx, getRunnableSlotSlice(t, s, service)) 695 allTasks := getRunningServiceTasks(t, s, service) 696 assert.Equal(t, instances*2, len(allTasks)) 697 for _, task := range allTasks { 698 if task.Spec.GetContainer().Image == "v:1" { 699 assert.Equal(t, task.DesiredState, api.TaskStateShutdown) 700 } else if task.Spec.GetContainer().Image == "v:2" { 701 assert.Equal(t, task.DesiredState, api.TaskStateRunning) 702 } 703 } 704 }