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 })