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  }