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

     1  package jobs
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"time"
     7  
     8  	. "github.com/onsi/ginkgo"
     9  	. "github.com/onsi/gomega"
    10  
    11  	"github.com/docker/go-events"
    12  	gogotypes "github.com/gogo/protobuf/types"
    13  
    14  	"github.com/docker/swarmkit/api"
    15  	"github.com/docker/swarmkit/manager/state/store"
    16  )
    17  
    18  // passEventsUntil is a helper method that calls handleEvent on all events from
    19  // the orchestrator's watchChan until the provided closure returns True.
    20  func passEventsUntil(o *Orchestrator, check func(event events.Event) bool) {
    21  	deadline := time.NewTimer(10 * time.Second)
    22  	defer deadline.Stop()
    23  	for {
    24  		select {
    25  		case event := <-o.watchChan:
    26  			// regardless of whether this is the desired event,
    27  			// pass it down to the orchestrator to handle.
    28  			o.handleEvent(context.Background(), event)
    29  			if check(event) {
    30  				return
    31  			}
    32  		// on the off chance that we never get an event, bail out of the
    33  		// loop after 10 seconds.
    34  		case <-deadline.C:
    35  			return
    36  		}
    37  	}
    38  }
    39  
    40  var _ = Describe("Jobs RestartSupervisor Integration", func() {
    41  	var (
    42  		s *store.MemoryStore
    43  		o *Orchestrator
    44  
    45  		service *api.Service
    46  	)
    47  
    48  	BeforeEach(func() {
    49  		s = store.NewMemoryStore(nil)
    50  		Expect(s).ToNot(BeNil())
    51  
    52  		service = &api.Service{
    53  			ID: "norestartservice",
    54  			Spec: api.ServiceSpec{
    55  				Annotations: api.Annotations{
    56  					Name: "norestartservice",
    57  				},
    58  				Mode: &api.ServiceSpec_ReplicatedJob{
    59  					ReplicatedJob: &api.ReplicatedJob{
    60  						MaxConcurrent:    uint64(1),
    61  						TotalCompletions: uint64(1),
    62  					},
    63  				},
    64  				Task: api.TaskSpec{},
    65  			},
    66  			JobStatus: &api.JobStatus{
    67  				JobIteration: api.Version{
    68  					Index: 0,
    69  				},
    70  			},
    71  		}
    72  
    73  		o = NewOrchestrator(s)
    74  	})
    75  
    76  	JustBeforeEach(func() {
    77  		o.init(context.Background())
    78  	})
    79  
    80  	AfterEach(func() {
    81  		o.watchCancel()
    82  	})
    83  
    84  	It("should not restart tasks if the Condition is RestartOnNone", func() {
    85  		service.Spec.Task.Restart = &api.RestartPolicy{
    86  			Condition: api.RestartOnNone,
    87  		}
    88  		err := s.Update(func(tx store.Tx) error {
    89  			return store.CreateService(tx, service)
    90  		})
    91  		Expect(err).ToNot(HaveOccurred())
    92  
    93  		// read out from the events channel until we send down the
    94  		// EventCreateService for the service we just created. By doing this
    95  		// manually and in a blocking fashion like this, we avoid relying on
    96  		// the go scheduler to pick up this routine.
    97  		passEventsUntil(o, serviceCreated(service))
    98  
    99  		// now, after having handled the creation event, we should have created
   100  		// a Task
   101  		found := false
   102  		s.View(func(tx store.ReadTx) {
   103  			var tasks []*api.Task
   104  			tasks, err = store.FindTasks(tx, store.ByServiceID(service.ID))
   105  			if err != nil {
   106  				return
   107  			}
   108  
   109  			// the task is found if it exists, its job iteration is 0, its
   110  			// state is New, and it's DesiredState is Completed.
   111  			found = len(tasks) == 1 &&
   112  				tasks[0].JobIteration != nil &&
   113  				tasks[0].JobIteration.Index == 0 &&
   114  				tasks[0].Status.State == api.TaskStateNew &&
   115  				tasks[0].DesiredState == api.TaskStateCompleted
   116  		})
   117  		Expect(err).ToNot(HaveOccurred())
   118  		Expect(found).To(BeTrue())
   119  
   120  		// now, fail the task
   121  		err = s.Update(func(tx store.Tx) error {
   122  			tasks, err := store.FindTasks(tx, store.ByServiceID(service.ID))
   123  			if err != nil {
   124  				return err
   125  			}
   126  			if len(tasks) != 1 {
   127  				return fmt.Errorf("there should only be one task at this stage, but there are %v", len(tasks))
   128  			}
   129  			tasks[0].Status.State = api.TaskStateFailed
   130  			return store.UpdateTask(tx, tasks[0])
   131  		})
   132  		Expect(err).ToNot(HaveOccurred())
   133  
   134  		// pass events again, this time until the TaskUpdate event with the
   135  		// failed state goes through
   136  		passEventsUntil(o, func(event events.Event) bool {
   137  			updateEv, ok := event.(api.EventUpdateTask)
   138  			return ok && updateEv.Task.Status.State == api.TaskStateFailed
   139  		})
   140  
   141  		// now, having processed that event, we should verify that no new Task
   142  		// has been created.
   143  		count := 0
   144  		s.View(func(tx store.ReadTx) {
   145  			var tasks []*api.Task
   146  			tasks, err = store.FindTasks(tx, store.ByServiceID(service.ID))
   147  			count = len(tasks)
   148  		})
   149  		Expect(count).To(Equal(1))
   150  	})
   151  
   152  	It("should only restart tasks MaxAttempt times", func() {
   153  		service.Spec.Task.Restart = &api.RestartPolicy{
   154  			Condition:   api.RestartOnFailure,
   155  			MaxAttempts: 3,
   156  			// set a low but non-zero delay duration, so we avoid default
   157  			// duration, which may be long.
   158  			Delay: gogotypes.DurationProto(100 * time.Millisecond),
   159  		}
   160  		err := s.Update(func(tx store.Tx) error {
   161  			return store.CreateService(tx, service)
   162  		})
   163  		Expect(err).ToNot(HaveOccurred())
   164  
   165  		passEventsUntil(o, serviceCreated(service))
   166  
   167  		// first, we will check that the previous iteration successfully
   168  		// created the task. if so, we fail it. then, we wait until the task
   169  		// has successfully fully started. this means we fail 3 tasks in total.
   170  		// failing the fourth task happens after this loop, and is the
   171  		// iteration that should not create a new task.
   172  		for i := 0; i < 3; i++ {
   173  			err = s.Update(func(tx store.Tx) error {
   174  				tasks, err := store.FindTasks(tx, store.ByTaskState(api.TaskStateNew))
   175  				if err != nil {
   176  					return err
   177  				}
   178  				if len(tasks) < 1 {
   179  					return fmt.Errorf("could not find new task")
   180  				}
   181  				if len(tasks) > 1 {
   182  					return fmt.Errorf("too many new tasks, there are %v", len(tasks))
   183  				}
   184  				tasks[0].Status.State = api.TaskStateFailed
   185  				return store.UpdateTask(tx, tasks[0])
   186  			})
   187  			Expect(err).ToNot(HaveOccurred())
   188  
   189  			// first, make sure the task fail event is handled
   190  			passEventsUntil(o, taskFailed)
   191  
   192  			// now wait for the new task to be created, and for it to move past
   193  			// desired state READY. the task delay means this part is async,
   194  			// but we have a long timeout and a short delay, so the scheduler
   195  			// shouldn't mess this up.
   196  			passEventsUntil(o, func(event events.Event) bool {
   197  				updated, ok := event.(api.EventUpdateTask)
   198  				return ok &&
   199  					updated.Task.DesiredState == api.TaskStateCompleted &&
   200  					updated.OldTask.DesiredState == api.TaskStateReady
   201  			})
   202  		}
   203  
   204  		err = s.Update(func(tx store.Tx) error {
   205  			tasks, err := store.FindTasks(tx, store.ByTaskState(api.TaskStateNew))
   206  			if err != nil {
   207  				return err
   208  			}
   209  			if len(tasks) < 1 {
   210  				return fmt.Errorf("could not find new task")
   211  			}
   212  			if len(tasks) > 1 {
   213  				return fmt.Errorf("too many new tasks, there are %v", len(tasks))
   214  			}
   215  			tasks[0].Status.State = api.TaskStateFailed
   216  			return store.UpdateTask(tx, tasks[0])
   217  		})
   218  
   219  		passEventsUntil(o, taskFailed)
   220  
   221  		// now check that no new task has been created
   222  		var tasks []*api.Task
   223  		s.View(func(tx store.ReadTx) {
   224  			tasks, err = store.FindTasks(tx, store.All)
   225  		})
   226  		Expect(err).ToNot(HaveOccurred())
   227  		Expect(tasks).To(HaveLen(4))
   228  
   229  		for _, task := range tasks {
   230  			Expect(task.Status.State).To(Equal(api.TaskStateFailed))
   231  		}
   232  	})
   233  })
   234  
   235  func serviceCreated(service *api.Service) func(events.Event) bool {
   236  	return func(event events.Event) bool {
   237  		create, ok := event.(api.EventCreateService)
   238  		return ok && create.Service.ID == service.ID
   239  	}
   240  }
   241  
   242  func taskFailed(event events.Event) bool {
   243  	updated, ok := event.(api.EventUpdateTask)
   244  	return ok &&
   245  		updated.Task.Status.State == api.TaskStateFailed &&
   246  		updated.OldTask.Status.State == api.TaskStateNew
   247  }