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

     1  package jobs
     2  
     3  import (
     4  	. "github.com/onsi/ginkgo"
     5  	. "github.com/onsi/gomega"
     6  
     7  	"context"
     8  	"fmt"
     9  
    10  	"github.com/docker/swarmkit/api"
    11  	"github.com/docker/swarmkit/manager/orchestrator/testutils"
    12  	"github.com/docker/swarmkit/manager/state/store"
    13  )
    14  
    15  var _ = Describe("Replicated job orchestrator", func() {
    16  	var (
    17  		o          *Orchestrator
    18  		s          *store.MemoryStore
    19  		replicated *fakeReconciler
    20  		global     *fakeReconciler
    21  	)
    22  
    23  	BeforeEach(func() {
    24  		s = store.NewMemoryStore(nil)
    25  		o = NewOrchestrator(s)
    26  		replicated = &fakeReconciler{
    27  			serviceErrors: map[string]error{},
    28  		}
    29  		global = &fakeReconciler{
    30  			serviceErrors: map[string]error{},
    31  		}
    32  		o.replicatedReconciler = replicated
    33  		o.globalReconciler = global
    34  		o.restartSupervisor = &fakeRestartSupervisor{}
    35  		o.checkTasksFunc = fakeCheckTasksFunc
    36  	})
    37  
    38  	Describe("Starting and stopping", func() {
    39  		It("should stop when Stop is called", func() {
    40  			stopped := testutils.EnsureRuns(func() { o.Run(context.Background()) })
    41  			o.Stop()
    42  			// Eventually here will repeatedly run the matcher against the
    43  			// argument. This means that we will keep checking if stopped is
    44  			// closed until the test times out. Using Eventually instead of
    45  			// Expect ensure we can't race on "stopped".
    46  			Eventually(stopped).Should(BeClosed())
    47  		})
    48  	})
    49  
    50  	Describe("initialization", func() {
    51  		BeforeEach(func() {
    52  			// Create some services. 3 replicated jobs and 1 of different
    53  			// service mode
    54  			err := s.Update(func(tx store.Tx) error {
    55  				for i := 0; i < 3; i++ {
    56  					serviceReplicated := &api.Service{
    57  						ID: fmt.Sprintf("serviceReplicated%v", i),
    58  						Spec: api.ServiceSpec{
    59  							Annotations: api.Annotations{
    60  								Name: fmt.Sprintf("serviceReplicated%v", i),
    61  							},
    62  							Mode: &api.ServiceSpec_ReplicatedJob{
    63  								ReplicatedJob: &api.ReplicatedJob{},
    64  							},
    65  						},
    66  					}
    67  
    68  					serviceGlobal := &api.Service{
    69  						ID: fmt.Sprintf("serviceGlobal%v", i),
    70  						Spec: api.ServiceSpec{
    71  							Annotations: api.Annotations{
    72  								Name: fmt.Sprintf("serviceGlobal%v", i),
    73  							},
    74  							Mode: &api.ServiceSpec_GlobalJob{
    75  								GlobalJob: &api.GlobalJob{},
    76  							},
    77  						},
    78  					}
    79  
    80  					if err := store.CreateService(tx, serviceReplicated); err != nil {
    81  						return err
    82  					}
    83  					if err := store.CreateService(tx, serviceGlobal); err != nil {
    84  						return err
    85  					}
    86  				}
    87  
    88  				return nil
    89  			})
    90  
    91  			Expect(err).ToNot(HaveOccurred())
    92  		})
    93  
    94  		JustBeforeEach(func() {
    95  			testutils.EnsureRuns(func() { o.Run(context.Background()) })
    96  
    97  			// Run and then stop the orchestrator. This will cause it to
    98  			// initialize, performing the consequent reconciliation pass, and
    99  			// then immediately return and exit. Because the call to Stop
   100  			// blocks until Run completes, and because the initialization is
   101  			// not interruptible, this will cause only the initialization to
   102  			// occur.
   103  			o.Stop()
   104  		})
   105  
   106  		It("should reconcile each replicated job service that already exists", func() {
   107  			Expect(replicated.servicesReconciled).To(ConsistOf(
   108  				"serviceReplicated0", "serviceReplicated1", "serviceReplicated2",
   109  			))
   110  		})
   111  
   112  		It("should reconcile each global job service that already exists", func() {
   113  			Expect(global.servicesReconciled).To(ConsistOf(
   114  				"serviceGlobal0", "serviceGlobal1", "serviceGlobal2",
   115  			))
   116  		})
   117  
   118  		It("should call checkTasksFunc for both reconcilers", func() {
   119  			Expect(global.servicesRelated).To(ConsistOf("fakeCheckTasksFuncCalled"))
   120  			Expect(replicated.servicesRelated).To(ConsistOf("fakeCheckTasksFuncCalled"))
   121  		})
   122  
   123  		When("an error is encountered reconciling some service", func() {
   124  			BeforeEach(func() {
   125  				replicated.serviceErrors["errService"] = fmt.Errorf("someError")
   126  				err := s.Update(func(tx store.Tx) error {
   127  					errService := &api.Service{
   128  						ID: "errService",
   129  						Spec: api.ServiceSpec{
   130  							Annotations: api.Annotations{
   131  								Name: "errService",
   132  							},
   133  							Mode: &api.ServiceSpec_ReplicatedJob{
   134  								ReplicatedJob: &api.ReplicatedJob{},
   135  							},
   136  						},
   137  					}
   138  					return store.CreateService(tx, errService)
   139  				})
   140  				Expect(err).ToNot(HaveOccurred())
   141  			})
   142  
   143  			It("should continue reconciling other services", func() {
   144  				Expect(replicated.servicesReconciled).To(ConsistOf(
   145  					"serviceReplicated0", "serviceReplicated1", "serviceReplicated2", "errService",
   146  				))
   147  			})
   148  		})
   149  	})
   150  
   151  	Describe("receiving events", func() {
   152  		var stopped <-chan struct{}
   153  		BeforeEach(func() {
   154  			stopped = testutils.EnsureRuns(func() { o.Run(context.Background()) })
   155  		})
   156  
   157  		AfterEach(func() {
   158  			// If a test needs to stop early, that's no problem, because
   159  			// repeated calls to Stop have no effect.
   160  			o.Stop()
   161  			Eventually(stopped).Should(BeClosed())
   162  		})
   163  
   164  		It("should reconcile each replicated job service received", func() {
   165  			// Create some services. Wait a moment, and then check that they
   166  			// are reconciled.
   167  			err := s.Update(func(tx store.Tx) error {
   168  				for i := 0; i < 3; i++ {
   169  					service := &api.Service{
   170  						ID: fmt.Sprintf("service%v", i),
   171  						Spec: api.ServiceSpec{
   172  							Annotations: api.Annotations{
   173  								Name: fmt.Sprintf("service%v", i),
   174  							},
   175  							Mode: &api.ServiceSpec_ReplicatedJob{
   176  								ReplicatedJob: &api.ReplicatedJob{},
   177  							},
   178  						},
   179  					}
   180  
   181  					if err := store.CreateService(tx, service); err != nil {
   182  						return err
   183  					}
   184  				}
   185  				return nil
   186  			})
   187  			Expect(err).ToNot(HaveOccurred())
   188  
   189  			Eventually(replicated.getServicesReconciled).Should(ConsistOf(
   190  				"service0", "service1", "service2",
   191  			))
   192  		})
   193  
   194  		It("should reconcile each global job service received", func() {
   195  			err := s.Update(func(tx store.Tx) error {
   196  				for i := 0; i < 3; i++ {
   197  					service := &api.Service{
   198  						ID: fmt.Sprintf("service%v", i),
   199  						Spec: api.ServiceSpec{
   200  							Annotations: api.Annotations{
   201  								Name: fmt.Sprintf("service%v", i),
   202  							},
   203  							Mode: &api.ServiceSpec_GlobalJob{
   204  								GlobalJob: &api.GlobalJob{},
   205  							},
   206  						},
   207  					}
   208  
   209  					if err := store.CreateService(tx, service); err != nil {
   210  						return err
   211  					}
   212  				}
   213  				return nil
   214  			})
   215  			Expect(err).ToNot(HaveOccurred())
   216  
   217  			Eventually(global.getServicesReconciled).Should(ConsistOf(
   218  				"service0", "service1", "service2",
   219  			))
   220  		})
   221  
   222  		When("receiving task events", func() {
   223  			BeforeEach(func() {
   224  				service := &api.Service{
   225  					ID: "service0",
   226  					Spec: api.ServiceSpec{
   227  						Mode: &api.ServiceSpec_ReplicatedJob{
   228  							ReplicatedJob: &api.ReplicatedJob{},
   229  						},
   230  					},
   231  				}
   232  
   233  				err := s.Update(func(tx store.Tx) error {
   234  					if err := store.CreateService(tx, service); err != nil {
   235  						return err
   236  					}
   237  
   238  					task := &api.Task{
   239  						ID:           "someTask",
   240  						ServiceID:    "service0",
   241  						DesiredState: api.TaskStateCompleted,
   242  						Status: api.TaskStatus{
   243  							State: api.TaskStatePreparing,
   244  						},
   245  					}
   246  					return store.CreateTask(tx, task)
   247  				})
   248  				Expect(err).ToNot(HaveOccurred())
   249  				Eventually(replicated.getServicesReconciled).Should(ConsistOf(
   250  					"service0",
   251  				))
   252  			})
   253  
   254  			It("should reconcile the service of a task that has entered a terminal state", func() {
   255  				err := s.Update(func(tx store.Tx) error {
   256  					task := store.GetTask(tx, "someTask")
   257  					task.Status.State = api.TaskStateFailed
   258  					return store.UpdateTask(tx, task)
   259  				})
   260  				Expect(err).ToNot(HaveOccurred())
   261  
   262  				// we will have service0 twice -- once from the setup, and
   263  				// once from the reconciliation pass in the test.
   264  				Eventually(replicated.getServicesReconciled).Should(ConsistOf(
   265  					"service0", "service0",
   266  				))
   267  			})
   268  
   269  			It("should ignore tasks that are not in a terminal state", func() {
   270  				err := s.Update(func(tx store.Tx) error {
   271  					task := store.GetTask(tx, "someTask")
   272  					task.Status.State = api.TaskStateRunning
   273  					return store.UpdateTask(tx, task)
   274  				})
   275  				Expect(err).ToNot(HaveOccurred())
   276  
   277  				Consistently(replicated.getServicesReconciled).Should(ConsistOf(
   278  					"service0",
   279  				))
   280  			})
   281  		})
   282  
   283  		It("should not reconcile anything after calling Stop", func() {
   284  			err := s.Update(func(tx store.Tx) error {
   285  				service := &api.Service{
   286  					ID: fmt.Sprintf("service0"),
   287  					Spec: api.ServiceSpec{
   288  						Annotations: api.Annotations{
   289  							Name: fmt.Sprintf("service0"),
   290  						},
   291  						Mode: &api.ServiceSpec_ReplicatedJob{
   292  							ReplicatedJob: &api.ReplicatedJob{},
   293  						},
   294  					},
   295  				}
   296  
   297  				return store.CreateService(tx, service)
   298  			})
   299  
   300  			Expect(err).ToNot(HaveOccurred())
   301  
   302  			Eventually(replicated.getServicesReconciled).Should(ConsistOf("service0"))
   303  
   304  			o.Stop()
   305  
   306  			err = s.Update(func(tx store.Tx) error {
   307  				service := &api.Service{
   308  					ID: fmt.Sprintf("service1"),
   309  					Spec: api.ServiceSpec{
   310  						Annotations: api.Annotations{
   311  							Name: fmt.Sprintf("service1"),
   312  						},
   313  						Mode: &api.ServiceSpec_ReplicatedJob{
   314  							ReplicatedJob: &api.ReplicatedJob{},
   315  						},
   316  					},
   317  				}
   318  
   319  				return store.CreateService(tx, service)
   320  			})
   321  
   322  			// service1 should never be reconciled.
   323  			Consistently(replicated.getServicesReconciled).Should(ConsistOf("service0"))
   324  		})
   325  	})
   326  })