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