github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/swarmkit/manager/orchestrator/jobs/global/reconciler_test.go (about)

     1  package global
     2  
     3  import (
     4  	. "github.com/onsi/ginkgo"
     5  	. "github.com/onsi/gomega"
     6  
     7  	"context"
     8  
     9  	"github.com/docker/swarmkit/api"
    10  	"github.com/docker/swarmkit/manager/orchestrator"
    11  	"github.com/docker/swarmkit/manager/state/store"
    12  )
    13  
    14  type fakeRestartSupervisor struct {
    15  	tasks []string
    16  }
    17  
    18  func (f *fakeRestartSupervisor) Restart(_ context.Context, _ store.Tx, _ *api.Cluster, _ *api.Service, task api.Task) error {
    19  	f.tasks = append(f.tasks, task.ID)
    20  	return nil
    21  }
    22  
    23  var _ = Describe("Global Job Reconciler", func() {
    24  	var (
    25  		r *Reconciler
    26  		s *store.MemoryStore
    27  		f *fakeRestartSupervisor
    28  	)
    29  
    30  	BeforeEach(func() {
    31  		s = store.NewMemoryStore(nil)
    32  		Expect(s).ToNot(BeNil())
    33  
    34  		f = &fakeRestartSupervisor{}
    35  
    36  		r = &Reconciler{
    37  			store:   s,
    38  			restart: f,
    39  		}
    40  	})
    41  
    42  	AfterEach(func() {
    43  		s.Close()
    44  	})
    45  
    46  	Describe("ReconcileService", func() {
    47  		var (
    48  			serviceID string
    49  			service   *api.Service
    50  			cluster   *api.Cluster
    51  			nodes     []*api.Node
    52  			tasks     []*api.Task
    53  		)
    54  
    55  		BeforeEach(func() {
    56  			serviceID = "someService"
    57  
    58  			// Set up the service and nodes. We can change these later
    59  			service = &api.Service{
    60  				ID: serviceID,
    61  				Spec: api.ServiceSpec{
    62  					Mode: &api.ServiceSpec_GlobalJob{
    63  						// GlobalJob has no parameters
    64  						GlobalJob: &api.GlobalJob{},
    65  					},
    66  				},
    67  				JobStatus: &api.JobStatus{},
    68  			}
    69  
    70  			cluster = &api.Cluster{
    71  				ID: "someCluster",
    72  				Spec: api.ClusterSpec{
    73  					Annotations: api.Annotations{
    74  						Name: "someCluster",
    75  					},
    76  					TaskDefaults: api.TaskDefaults{
    77  						LogDriver: &api.Driver{
    78  							Name: "someDriver",
    79  						},
    80  					},
    81  				},
    82  			}
    83  
    84  			// at the beginning of each test, initialize tasks to be empty
    85  			tasks = nil
    86  
    87  			nodes = []*api.Node{
    88  				{
    89  					ID: "node1",
    90  					Spec: api.NodeSpec{
    91  						Annotations: api.Annotations{
    92  							Name: "name1",
    93  						},
    94  						Availability: api.NodeAvailabilityActive,
    95  					},
    96  					Status: api.NodeStatus{
    97  						State: api.NodeStatus_READY,
    98  					},
    99  				},
   100  				{
   101  					ID: "node2",
   102  					Spec: api.NodeSpec{
   103  						Annotations: api.Annotations{
   104  							Name: "name2",
   105  						},
   106  						Availability: api.NodeAvailabilityActive,
   107  					},
   108  					Status: api.NodeStatus{
   109  						State: api.NodeStatus_READY,
   110  					},
   111  				},
   112  				{
   113  					ID: "node3",
   114  					Spec: api.NodeSpec{
   115  						Annotations: api.Annotations{
   116  							Name: "name3",
   117  						},
   118  						Availability: api.NodeAvailabilityActive,
   119  					},
   120  					Status: api.NodeStatus{
   121  						State: api.NodeStatus_READY,
   122  					},
   123  				},
   124  			}
   125  		})
   126  
   127  		JustBeforeEach(func() {
   128  			// Just before the test executes, we'll create all of the objects
   129  			// needed by the test and execute reconcileService. If we need
   130  			// to change these objects, then we can do so in BeforeEach blocks
   131  			err := s.Update(func(tx store.Tx) error {
   132  				if service != nil {
   133  					if err := store.CreateService(tx, service); err != nil {
   134  						return err
   135  					}
   136  				}
   137  
   138  				if cluster != nil {
   139  					if err := store.CreateCluster(tx, cluster); err != nil {
   140  						return err
   141  					}
   142  				}
   143  
   144  				for _, node := range nodes {
   145  					if err := store.CreateNode(tx, node); err != nil {
   146  						return err
   147  					}
   148  				}
   149  				for _, task := range tasks {
   150  					if err := store.CreateTask(tx, task); err != nil {
   151  						return err
   152  					}
   153  				}
   154  
   155  				return nil
   156  			})
   157  
   158  			Expect(err).ToNot(HaveOccurred())
   159  
   160  			err = r.ReconcileService(serviceID)
   161  			Expect(err).ToNot(HaveOccurred())
   162  		})
   163  
   164  		When("the service is updated", func() {
   165  			var allTasks []*api.Task
   166  
   167  			JustBeforeEach(func() {
   168  				// this JustBeforeEach will run after the one where the service
   169  				// etc is created and reconciled.
   170  				err := s.Update(func(tx store.Tx) error {
   171  					service := store.GetService(tx, serviceID)
   172  					service.JobStatus.JobIteration.Index++
   173  					service.Spec.Task.ForceUpdate++
   174  					return store.UpdateService(tx, service)
   175  				})
   176  				Expect(err).ToNot(HaveOccurred())
   177  
   178  				err = r.ReconcileService(serviceID)
   179  				Expect(err).ToNot(HaveOccurred())
   180  
   181  				s.View(func(tx store.ReadTx) {
   182  					allTasks, err = store.FindTasks(tx, store.ByServiceID(serviceID))
   183  				})
   184  				Expect(err).ToNot(HaveOccurred())
   185  			})
   186  
   187  			It("should remove tasks belonging to old iterations", func() {
   188  				count := 0
   189  				for _, task := range allTasks {
   190  					Expect(task.JobIteration).ToNot(BeNil())
   191  					if task.JobIteration.Index == 0 {
   192  						Expect(task.DesiredState).To(Equal(api.TaskStateRemove))
   193  						count++
   194  					}
   195  				}
   196  				Expect(count).To(Equal(len(nodes)))
   197  			})
   198  
   199  			It("should create new tasks with the new iteration", func() {
   200  				count := 0
   201  				for _, task := range allTasks {
   202  					Expect(task.JobIteration).ToNot(BeNil())
   203  					if task.JobIteration.Index == 1 {
   204  						Expect(task.DesiredState).To(Equal(api.TaskStateCompleted))
   205  						count++
   206  					}
   207  				}
   208  				Expect(count).To(Equal(len(nodes)))
   209  			})
   210  		})
   211  
   212  		When("there are failed tasks", func() {
   213  			BeforeEach(func() {
   214  				// all but the last node should be filled.
   215  				for _, node := range nodes[:len(nodes)-1] {
   216  					task := orchestrator.NewTask(cluster, service, 0, node.ID)
   217  					task.JobIteration = &api.Version{}
   218  					task.DesiredState = api.TaskStateCompleted
   219  					task.Status.State = api.TaskStateFailed
   220  					tasks = append(tasks, task)
   221  				}
   222  			})
   223  
   224  			It("should not replace the failing tasks", func() {
   225  				s.View(func(tx store.ReadTx) {
   226  					tasks, err := store.FindTasks(tx, store.All)
   227  					Expect(err).ToNot(HaveOccurred())
   228  					Expect(tasks).To(HaveLen(len(nodes)))
   229  
   230  					// this is a sanity check
   231  					numFailedTasks := 0
   232  					numNewTasks := 0
   233  					for _, task := range tasks {
   234  						if task.Status.State == api.TaskStateNew {
   235  							numNewTasks++
   236  						}
   237  						if task.Status.State == api.TaskStateFailed {
   238  							numFailedTasks++
   239  						}
   240  					}
   241  
   242  					Expect(numNewTasks).To(Equal(1))
   243  					Expect(numFailedTasks).To(Equal(len(nodes) - 1))
   244  				})
   245  			})
   246  
   247  			It("should call the restartSupervisor with the failed task", func() {
   248  				taskIDs := []string{}
   249  				// all of the tasks in the list should be the failed tasks.
   250  				for _, task := range tasks {
   251  					taskIDs = append(taskIDs, task.ID)
   252  				}
   253  				Expect(f.tasks).To(ConsistOf(taskIDs))
   254  			})
   255  		})
   256  
   257  		When("creating new tasks", func() {
   258  			It("should create a task for each node", func() {
   259  				s.View(func(tx store.ReadTx) {
   260  					for _, node := range nodes {
   261  						nodeTasks, err := store.FindTasks(tx, store.ByNodeID(node.ID))
   262  						Expect(err).ToNot(HaveOccurred())
   263  						Expect(nodeTasks).To(HaveLen(1))
   264  					}
   265  
   266  				})
   267  			})
   268  
   269  			It("should set the desired state of each new task to COMPLETE", func() {
   270  				s.View(func(tx store.ReadTx) {
   271  					tasks, err := store.FindTasks(tx, store.All)
   272  					Expect(err).ToNot(HaveOccurred())
   273  					for _, task := range tasks {
   274  						Expect(task.DesiredState).To(Equal(api.TaskStateCompleted))
   275  					}
   276  				})
   277  			})
   278  
   279  			It("should pick up and use the cluster object", func() {
   280  				s.View(func(tx store.ReadTx) {
   281  					tasks, err := store.FindTasks(tx, store.All)
   282  					Expect(err).ToNot(HaveOccurred())
   283  					for _, task := range tasks {
   284  						Expect(task.LogDriver).ToNot(BeNil())
   285  						Expect(task.LogDriver.Name).To(Equal("someDriver"))
   286  					}
   287  				})
   288  			})
   289  
   290  			When("there are already existing tasks", func() {
   291  				BeforeEach(func() {
   292  					// create a random task for node 1 that's in state Running
   293  					tasks = append(tasks, &api.Task{
   294  						ID:           "existingTask1",
   295  						Spec:         service.Spec.Task,
   296  						ServiceID:    serviceID,
   297  						NodeID:       "node1",
   298  						DesiredState: api.TaskStateCompleted,
   299  						Status: api.TaskStatus{
   300  							State: api.TaskStateRunning,
   301  						},
   302  						JobIteration: &api.Version{},
   303  					})
   304  					tasks = append(tasks, &api.Task{
   305  						ID:           "existingTask2",
   306  						Spec:         service.Spec.Task,
   307  						ServiceID:    serviceID,
   308  						NodeID:       "node2",
   309  						DesiredState: api.TaskStateCompleted,
   310  						Status: api.TaskStatus{
   311  							State: api.TaskStateCompleted,
   312  						},
   313  						JobIteration: &api.Version{},
   314  					})
   315  				})
   316  
   317  				It("should only create tasks for nodes that need them", func() {
   318  					s.View(func(tx store.ReadTx) {
   319  						tasks, err := store.FindTasks(tx, store.All)
   320  						Expect(err).ToNot(HaveOccurred())
   321  						Expect(tasks).To(HaveLen(3))
   322  						Expect(tasks).To(
   323  							ContainElement(
   324  								WithTransform(func(t *api.Task) string {
   325  									return t.ID
   326  								}, Equal("existingTask1")),
   327  							),
   328  						)
   329  					})
   330  				})
   331  			})
   332  
   333  			When("there are placement constraints", func() {
   334  				// This isn't a rigorous test of whether every placement
   335  				// constraint works. Constraints are handled by another
   336  				// package, so we have no need to test that. We just need to be
   337  				// sure that placement constraints are correctly checked and
   338  				// used.
   339  
   340  				BeforeEach(func() {
   341  					// set a constraint on the task to be only node1
   342  					service.Spec.Task.Placement = &api.Placement{
   343  						Constraints: []string{"node.id==node1"},
   344  					}
   345  				})
   346  
   347  				It("should only create tasks on nodes matching the constraints", func() {
   348  					s.View(func(tx store.ReadTx) {
   349  						tasks, err := store.FindTasks(tx, store.All)
   350  						Expect(err).ToNot(HaveOccurred())
   351  						Expect(tasks).To(HaveLen(1))
   352  						Expect(tasks[0].NodeID).To(Equal("node1"))
   353  					})
   354  				})
   355  			})
   356  
   357  			When("the service no longer exists", func() {
   358  				BeforeEach(func() {
   359  					service = nil
   360  				})
   361  
   362  				It("should create no tasks", func() {
   363  					s.View(func(tx store.ReadTx) {
   364  						tasks, err := store.FindTasks(tx, store.All)
   365  						Expect(err).ToNot(HaveOccurred())
   366  						Expect(tasks).To(BeEmpty())
   367  					})
   368  				})
   369  			})
   370  
   371  			When("a node is drained or paused", func() {
   372  				BeforeEach(func() {
   373  					// set node1 to drain, set node2 to pause
   374  					nodes[0].Spec.Availability = api.NodeAvailabilityDrain
   375  					nodes[1].Spec.Availability = api.NodeAvailabilityPause
   376  					tasks = append(tasks, &api.Task{
   377  						ID:           "someID",
   378  						ServiceID:    service.ID,
   379  						NodeID:       nodes[0].ID,
   380  						DesiredState: api.TaskStateCompleted,
   381  						Status: api.TaskStatus{
   382  							State: api.TaskStateRunning,
   383  						},
   384  						JobIteration: &api.Version{
   385  							Index: service.JobStatus.JobIteration.Index,
   386  						},
   387  					})
   388  				})
   389  
   390  				It("should not create tasks for those nodes", func() {
   391  					s.View(func(tx store.ReadTx) {
   392  						tasks, err := store.FindTasks(tx, store.All)
   393  						Expect(err).ToNot(HaveOccurred())
   394  						Expect(tasks).To(HaveLen(2))
   395  
   396  						node2Tasks, err := store.FindTasks(tx, store.ByNodeID(nodes[2].ID))
   397  						Expect(err).ToNot(HaveOccurred())
   398  						Expect(node2Tasks).To(HaveLen(1))
   399  						Expect(node2Tasks[0].DesiredState).To(Equal(api.TaskStateCompleted))
   400  					})
   401  				})
   402  
   403  				It("should shut down tasks on drained nodes", func() {
   404  					s.View(func(tx store.ReadTx) {
   405  						node0Tasks, err := store.FindTasks(tx, store.ByNodeID(nodes[0].ID))
   406  						Expect(err).ToNot(HaveOccurred())
   407  						Expect(node0Tasks[0].DesiredState).To(Equal(api.TaskStateShutdown))
   408  					})
   409  				})
   410  			})
   411  
   412  			When("a node is not READY", func() {
   413  				BeforeEach(func() {
   414  					nodes[0].Status.State = api.NodeStatus_DOWN
   415  				})
   416  				It("should not create tasks for that node", func() {
   417  					s.View(func(tx store.ReadTx) {
   418  						node0Tasks, err := store.FindTasks(tx, store.ByNodeID(nodes[0].ID))
   419  						Expect(err).ToNot(HaveOccurred())
   420  						Expect(node0Tasks).To(BeEmpty())
   421  
   422  						for _, node := range nodes[1:] {
   423  							tasks, err := store.FindTasks(tx, store.ByNodeID(node.ID))
   424  							Expect(err).ToNot(HaveOccurred())
   425  							Expect(tasks).To(HaveLen(1))
   426  						}
   427  					})
   428  				})
   429  			})
   430  		})
   431  
   432  	})
   433  
   434  	Describe("FixTask", func() {
   435  		It("should take no action if the task is already desired to be shutdown", func() {
   436  			task := &api.Task{
   437  				ID:           "someTask",
   438  				NodeID:       "someNode",
   439  				DesiredState: api.TaskStateShutdown,
   440  				Status: api.TaskStatus{
   441  					State: api.TaskStateShutdown,
   442  				},
   443  			}
   444  			err := s.Update(func(tx store.Tx) error {
   445  				return store.CreateTask(tx, task)
   446  			})
   447  			Expect(err).ToNot(HaveOccurred())
   448  
   449  			err = s.Batch(func(batch *store.Batch) error {
   450  				r.FixTask(context.Background(), batch, task)
   451  				return nil
   452  			})
   453  			Expect(err).ToNot(HaveOccurred())
   454  		})
   455  
   456  		It("should shut down the task if its node is no longer valid", func() {
   457  			// we can cover this case by just not creating the node, as a nil
   458  			// node is invalid and this isn't a test of InvalidNode
   459  			task := &api.Task{
   460  				ID:           "someTask",
   461  				NodeID:       "someNode",
   462  				DesiredState: api.TaskStateCompleted,
   463  				Status: api.TaskStatus{
   464  					State: api.TaskStateFailed,
   465  				},
   466  			}
   467  			err := s.Update(func(tx store.Tx) error {
   468  				return store.CreateTask(tx, task)
   469  			})
   470  			Expect(err).ToNot(HaveOccurred())
   471  
   472  			err = s.Batch(func(batch *store.Batch) error {
   473  				r.FixTask(context.Background(), batch, task)
   474  				return nil
   475  			})
   476  			Expect(err).ToNot(HaveOccurred())
   477  			s.View(func(tx store.ReadTx) {
   478  				t := store.GetTask(tx, task.ID)
   479  				Expect(t.DesiredState).To(Equal(api.TaskStateShutdown))
   480  			})
   481  		})
   482  	})
   483  })