github.com/emate/nomad@v0.8.2-wo-binpacking/api/tasks_test.go (about) 1 package api 2 3 import ( 4 "reflect" 5 "testing" 6 "time" 7 8 "github.com/hashicorp/nomad/helper" 9 "github.com/hashicorp/nomad/nomad/structs" 10 "github.com/stretchr/testify/assert" 11 ) 12 13 func TestTaskGroup_NewTaskGroup(t *testing.T) { 14 t.Parallel() 15 grp := NewTaskGroup("grp1", 2) 16 expect := &TaskGroup{ 17 Name: helper.StringToPtr("grp1"), 18 Count: helper.IntToPtr(2), 19 } 20 if !reflect.DeepEqual(grp, expect) { 21 t.Fatalf("expect: %#v, got: %#v", expect, grp) 22 } 23 } 24 25 func TestTaskGroup_Constrain(t *testing.T) { 26 t.Parallel() 27 grp := NewTaskGroup("grp1", 1) 28 29 // Add a constraint to the group 30 out := grp.Constrain(NewConstraint("kernel.name", "=", "darwin")) 31 if n := len(grp.Constraints); n != 1 { 32 t.Fatalf("expected 1 constraint, got: %d", n) 33 } 34 35 // Check that the group was returned 36 if out != grp { 37 t.Fatalf("expected: %#v, got: %#v", grp, out) 38 } 39 40 // Add a second constraint 41 grp.Constrain(NewConstraint("memory.totalbytes", ">=", "128000000")) 42 expect := []*Constraint{ 43 { 44 LTarget: "kernel.name", 45 RTarget: "darwin", 46 Operand: "=", 47 }, 48 { 49 LTarget: "memory.totalbytes", 50 RTarget: "128000000", 51 Operand: ">=", 52 }, 53 } 54 if !reflect.DeepEqual(grp.Constraints, expect) { 55 t.Fatalf("expect: %#v, got: %#v", expect, grp.Constraints) 56 } 57 } 58 59 func TestTaskGroup_SetMeta(t *testing.T) { 60 t.Parallel() 61 grp := NewTaskGroup("grp1", 1) 62 63 // Initializes an empty map 64 out := grp.SetMeta("foo", "bar") 65 if grp.Meta == nil { 66 t.Fatalf("should be initialized") 67 } 68 69 // Check that we returned the group 70 if out != grp { 71 t.Fatalf("expect: %#v, got: %#v", grp, out) 72 } 73 74 // Add a second meta k/v 75 grp.SetMeta("baz", "zip") 76 expect := map[string]string{"foo": "bar", "baz": "zip"} 77 if !reflect.DeepEqual(grp.Meta, expect) { 78 t.Fatalf("expect: %#v, got: %#v", expect, grp.Meta) 79 } 80 } 81 82 func TestTaskGroup_AddTask(t *testing.T) { 83 t.Parallel() 84 grp := NewTaskGroup("grp1", 1) 85 86 // Add the task to the task group 87 out := grp.AddTask(NewTask("task1", "java")) 88 if n := len(grp.Tasks); n != 1 { 89 t.Fatalf("expected 1 task, got: %d", n) 90 } 91 92 // Check that we returned the group 93 if out != grp { 94 t.Fatalf("expect: %#v, got: %#v", grp, out) 95 } 96 97 // Add a second task 98 grp.AddTask(NewTask("task2", "exec")) 99 expect := []*Task{ 100 { 101 Name: "task1", 102 Driver: "java", 103 }, 104 { 105 Name: "task2", 106 Driver: "exec", 107 }, 108 } 109 if !reflect.DeepEqual(grp.Tasks, expect) { 110 t.Fatalf("expect: %#v, got: %#v", expect, grp.Tasks) 111 } 112 } 113 114 func TestTask_NewTask(t *testing.T) { 115 t.Parallel() 116 task := NewTask("task1", "exec") 117 expect := &Task{ 118 Name: "task1", 119 Driver: "exec", 120 } 121 if !reflect.DeepEqual(task, expect) { 122 t.Fatalf("expect: %#v, got: %#v", expect, task) 123 } 124 } 125 126 func TestTask_SetConfig(t *testing.T) { 127 t.Parallel() 128 task := NewTask("task1", "exec") 129 130 // Initializes an empty map 131 out := task.SetConfig("foo", "bar") 132 if task.Config == nil { 133 t.Fatalf("should be initialized") 134 } 135 136 // Check that we returned the task 137 if out != task { 138 t.Fatalf("expect: %#v, got: %#v", task, out) 139 } 140 141 // Set another config value 142 task.SetConfig("baz", "zip") 143 expect := map[string]interface{}{"foo": "bar", "baz": "zip"} 144 if !reflect.DeepEqual(task.Config, expect) { 145 t.Fatalf("expect: %#v, got: %#v", expect, task.Config) 146 } 147 } 148 149 func TestTask_SetMeta(t *testing.T) { 150 t.Parallel() 151 task := NewTask("task1", "exec") 152 153 // Initializes an empty map 154 out := task.SetMeta("foo", "bar") 155 if task.Meta == nil { 156 t.Fatalf("should be initialized") 157 } 158 159 // Check that we returned the task 160 if out != task { 161 t.Fatalf("expect: %#v, got: %#v", task, out) 162 } 163 164 // Set another meta k/v 165 task.SetMeta("baz", "zip") 166 expect := map[string]string{"foo": "bar", "baz": "zip"} 167 if !reflect.DeepEqual(task.Meta, expect) { 168 t.Fatalf("expect: %#v, got: %#v", expect, task.Meta) 169 } 170 } 171 172 func TestTask_Require(t *testing.T) { 173 t.Parallel() 174 task := NewTask("task1", "exec") 175 176 // Create some require resources 177 resources := &Resources{ 178 CPU: helper.IntToPtr(1250), 179 MemoryMB: helper.IntToPtr(128), 180 DiskMB: helper.IntToPtr(2048), 181 IOPS: helper.IntToPtr(500), 182 Networks: []*NetworkResource{ 183 { 184 CIDR: "0.0.0.0/0", 185 MBits: helper.IntToPtr(100), 186 ReservedPorts: []Port{{"", 80}, {"", 443}}, 187 }, 188 }, 189 } 190 out := task.Require(resources) 191 if !reflect.DeepEqual(task.Resources, resources) { 192 t.Fatalf("expect: %#v, got: %#v", resources, task.Resources) 193 } 194 195 // Check that we returned the task 196 if out != task { 197 t.Fatalf("expect: %#v, got: %#v", task, out) 198 } 199 } 200 201 func TestTask_Constrain(t *testing.T) { 202 t.Parallel() 203 task := NewTask("task1", "exec") 204 205 // Add a constraint to the task 206 out := task.Constrain(NewConstraint("kernel.name", "=", "darwin")) 207 if n := len(task.Constraints); n != 1 { 208 t.Fatalf("expected 1 constraint, got: %d", n) 209 } 210 211 // Check that the task was returned 212 if out != task { 213 t.Fatalf("expected: %#v, got: %#v", task, out) 214 } 215 216 // Add a second constraint 217 task.Constrain(NewConstraint("memory.totalbytes", ">=", "128000000")) 218 expect := []*Constraint{ 219 { 220 LTarget: "kernel.name", 221 RTarget: "darwin", 222 Operand: "=", 223 }, 224 { 225 LTarget: "memory.totalbytes", 226 RTarget: "128000000", 227 Operand: ">=", 228 }, 229 } 230 if !reflect.DeepEqual(task.Constraints, expect) { 231 t.Fatalf("expect: %#v, got: %#v", expect, task.Constraints) 232 } 233 } 234 235 func TestTask_Artifact(t *testing.T) { 236 t.Parallel() 237 a := TaskArtifact{ 238 GetterSource: helper.StringToPtr("http://localhost/foo.txt"), 239 GetterMode: helper.StringToPtr("file"), 240 } 241 a.Canonicalize() 242 if *a.GetterMode != "file" { 243 t.Errorf("expected file but found %q", *a.GetterMode) 244 } 245 if *a.RelativeDest != "local/foo.txt" { 246 t.Errorf("expected local/foo.txt but found %q", *a.RelativeDest) 247 } 248 } 249 250 // Ensures no regression on https://github.com/hashicorp/nomad/issues/3132 251 func TestTaskGroup_Canonicalize_Update(t *testing.T) { 252 job := &Job{ 253 ID: helper.StringToPtr("test"), 254 Update: &UpdateStrategy{ 255 AutoRevert: helper.BoolToPtr(false), 256 Canary: helper.IntToPtr(0), 257 HealthCheck: helper.StringToPtr(""), 258 HealthyDeadline: helper.TimeToPtr(0), 259 MaxParallel: helper.IntToPtr(0), 260 MinHealthyTime: helper.TimeToPtr(0), 261 Stagger: helper.TimeToPtr(0), 262 }, 263 } 264 job.Canonicalize() 265 tg := &TaskGroup{ 266 Name: helper.StringToPtr("foo"), 267 } 268 tg.Canonicalize(job) 269 assert.Nil(t, tg.Update) 270 } 271 272 // Verifies that reschedule policy is merged correctly 273 func TestTaskGroup_Canonicalize_ReschedulePolicy(t *testing.T) { 274 type testCase struct { 275 desc string 276 jobReschedulePolicy *ReschedulePolicy 277 taskReschedulePolicy *ReschedulePolicy 278 expected *ReschedulePolicy 279 } 280 281 testCases := []testCase{ 282 { 283 desc: "Default", 284 jobReschedulePolicy: nil, 285 taskReschedulePolicy: nil, 286 expected: &ReschedulePolicy{ 287 Attempts: helper.IntToPtr(structs.DefaultBatchJobReschedulePolicy.Attempts), 288 Interval: helper.TimeToPtr(structs.DefaultBatchJobReschedulePolicy.Interval), 289 Delay: helper.TimeToPtr(structs.DefaultBatchJobReschedulePolicy.Delay), 290 DelayFunction: helper.StringToPtr(structs.DefaultBatchJobReschedulePolicy.DelayFunction), 291 MaxDelay: helper.TimeToPtr(structs.DefaultBatchJobReschedulePolicy.MaxDelay), 292 Unlimited: helper.BoolToPtr(structs.DefaultBatchJobReschedulePolicy.Unlimited), 293 }, 294 }, 295 { 296 desc: "Empty job reschedule policy", 297 jobReschedulePolicy: &ReschedulePolicy{ 298 Attempts: helper.IntToPtr(0), 299 Interval: helper.TimeToPtr(0), 300 Delay: helper.TimeToPtr(0), 301 MaxDelay: helper.TimeToPtr(0), 302 DelayFunction: helper.StringToPtr(""), 303 Unlimited: helper.BoolToPtr(false), 304 }, 305 taskReschedulePolicy: nil, 306 expected: &ReschedulePolicy{ 307 Attempts: helper.IntToPtr(0), 308 Interval: helper.TimeToPtr(0), 309 Delay: helper.TimeToPtr(0), 310 MaxDelay: helper.TimeToPtr(0), 311 DelayFunction: helper.StringToPtr(""), 312 Unlimited: helper.BoolToPtr(false), 313 }, 314 }, 315 { 316 desc: "Inherit from job", 317 jobReschedulePolicy: &ReschedulePolicy{ 318 Attempts: helper.IntToPtr(1), 319 Interval: helper.TimeToPtr(20 * time.Second), 320 Delay: helper.TimeToPtr(20 * time.Second), 321 MaxDelay: helper.TimeToPtr(10 * time.Minute), 322 DelayFunction: helper.StringToPtr("constant"), 323 Unlimited: helper.BoolToPtr(false), 324 }, 325 taskReschedulePolicy: nil, 326 expected: &ReschedulePolicy{ 327 Attempts: helper.IntToPtr(1), 328 Interval: helper.TimeToPtr(20 * time.Second), 329 Delay: helper.TimeToPtr(20 * time.Second), 330 MaxDelay: helper.TimeToPtr(10 * time.Minute), 331 DelayFunction: helper.StringToPtr("constant"), 332 Unlimited: helper.BoolToPtr(false), 333 }, 334 }, 335 { 336 desc: "Set in task", 337 jobReschedulePolicy: nil, 338 taskReschedulePolicy: &ReschedulePolicy{ 339 Attempts: helper.IntToPtr(5), 340 Interval: helper.TimeToPtr(2 * time.Minute), 341 Delay: helper.TimeToPtr(20 * time.Second), 342 MaxDelay: helper.TimeToPtr(10 * time.Minute), 343 DelayFunction: helper.StringToPtr("constant"), 344 Unlimited: helper.BoolToPtr(false), 345 }, 346 expected: &ReschedulePolicy{ 347 Attempts: helper.IntToPtr(5), 348 Interval: helper.TimeToPtr(2 * time.Minute), 349 Delay: helper.TimeToPtr(20 * time.Second), 350 MaxDelay: helper.TimeToPtr(10 * time.Minute), 351 DelayFunction: helper.StringToPtr("constant"), 352 Unlimited: helper.BoolToPtr(false), 353 }, 354 }, 355 { 356 desc: "Merge from job", 357 jobReschedulePolicy: &ReschedulePolicy{ 358 Attempts: helper.IntToPtr(1), 359 Delay: helper.TimeToPtr(20 * time.Second), 360 MaxDelay: helper.TimeToPtr(10 * time.Minute), 361 }, 362 taskReschedulePolicy: &ReschedulePolicy{ 363 Interval: helper.TimeToPtr(5 * time.Minute), 364 DelayFunction: helper.StringToPtr("constant"), 365 Unlimited: helper.BoolToPtr(false), 366 }, 367 expected: &ReschedulePolicy{ 368 Attempts: helper.IntToPtr(1), 369 Interval: helper.TimeToPtr(5 * time.Minute), 370 Delay: helper.TimeToPtr(20 * time.Second), 371 MaxDelay: helper.TimeToPtr(10 * time.Minute), 372 DelayFunction: helper.StringToPtr("constant"), 373 Unlimited: helper.BoolToPtr(false), 374 }, 375 }, 376 { 377 desc: "Override from group", 378 jobReschedulePolicy: &ReschedulePolicy{ 379 Attempts: helper.IntToPtr(1), 380 MaxDelay: helper.TimeToPtr(10 * time.Second), 381 }, 382 taskReschedulePolicy: &ReschedulePolicy{ 383 Attempts: helper.IntToPtr(5), 384 Delay: helper.TimeToPtr(20 * time.Second), 385 MaxDelay: helper.TimeToPtr(20 * time.Minute), 386 DelayFunction: helper.StringToPtr("constant"), 387 Unlimited: helper.BoolToPtr(false), 388 }, 389 expected: &ReschedulePolicy{ 390 Attempts: helper.IntToPtr(5), 391 Interval: helper.TimeToPtr(structs.DefaultBatchJobReschedulePolicy.Interval), 392 Delay: helper.TimeToPtr(20 * time.Second), 393 MaxDelay: helper.TimeToPtr(20 * time.Minute), 394 DelayFunction: helper.StringToPtr("constant"), 395 Unlimited: helper.BoolToPtr(false), 396 }, 397 }, 398 { 399 desc: "Attempts from job, default interval", 400 jobReschedulePolicy: &ReschedulePolicy{ 401 Attempts: helper.IntToPtr(1), 402 }, 403 taskReschedulePolicy: nil, 404 expected: &ReschedulePolicy{ 405 Attempts: helper.IntToPtr(1), 406 Interval: helper.TimeToPtr(structs.DefaultBatchJobReschedulePolicy.Interval), 407 Delay: helper.TimeToPtr(structs.DefaultBatchJobReschedulePolicy.Delay), 408 DelayFunction: helper.StringToPtr(structs.DefaultBatchJobReschedulePolicy.DelayFunction), 409 MaxDelay: helper.TimeToPtr(structs.DefaultBatchJobReschedulePolicy.MaxDelay), 410 Unlimited: helper.BoolToPtr(structs.DefaultBatchJobReschedulePolicy.Unlimited), 411 }, 412 }, 413 } 414 415 for _, tc := range testCases { 416 t.Run(tc.desc, func(t *testing.T) { 417 job := &Job{ 418 ID: helper.StringToPtr("test"), 419 Reschedule: tc.jobReschedulePolicy, 420 Type: helper.StringToPtr(JobTypeBatch), 421 } 422 job.Canonicalize() 423 tg := &TaskGroup{ 424 Name: helper.StringToPtr("foo"), 425 ReschedulePolicy: tc.taskReschedulePolicy, 426 } 427 tg.Canonicalize(job) 428 assert.Equal(t, tc.expected, tg.ReschedulePolicy) 429 }) 430 } 431 } 432 433 // Verifies that migrate strategy is merged correctly 434 func TestTaskGroup_Canonicalize_MigrateStrategy(t *testing.T) { 435 type testCase struct { 436 desc string 437 jobType string 438 jobMigrate *MigrateStrategy 439 taskMigrate *MigrateStrategy 440 expected *MigrateStrategy 441 } 442 443 testCases := []testCase{ 444 { 445 desc: "Default batch", 446 jobType: "batch", 447 jobMigrate: nil, 448 taskMigrate: nil, 449 expected: nil, 450 }, 451 { 452 desc: "Default service", 453 jobType: "service", 454 jobMigrate: nil, 455 taskMigrate: nil, 456 expected: &MigrateStrategy{ 457 MaxParallel: helper.IntToPtr(1), 458 HealthCheck: helper.StringToPtr("checks"), 459 MinHealthyTime: helper.TimeToPtr(10 * time.Second), 460 HealthyDeadline: helper.TimeToPtr(5 * time.Minute), 461 }, 462 }, 463 { 464 desc: "Empty job migrate strategy", 465 jobType: "service", 466 jobMigrate: &MigrateStrategy{ 467 MaxParallel: helper.IntToPtr(0), 468 HealthCheck: helper.StringToPtr(""), 469 MinHealthyTime: helper.TimeToPtr(0), 470 HealthyDeadline: helper.TimeToPtr(0), 471 }, 472 taskMigrate: nil, 473 expected: &MigrateStrategy{ 474 MaxParallel: helper.IntToPtr(0), 475 HealthCheck: helper.StringToPtr(""), 476 MinHealthyTime: helper.TimeToPtr(0), 477 HealthyDeadline: helper.TimeToPtr(0), 478 }, 479 }, 480 { 481 desc: "Inherit from job", 482 jobType: "service", 483 jobMigrate: &MigrateStrategy{ 484 MaxParallel: helper.IntToPtr(3), 485 HealthCheck: helper.StringToPtr("checks"), 486 MinHealthyTime: helper.TimeToPtr(2), 487 HealthyDeadline: helper.TimeToPtr(2), 488 }, 489 taskMigrate: nil, 490 expected: &MigrateStrategy{ 491 MaxParallel: helper.IntToPtr(3), 492 HealthCheck: helper.StringToPtr("checks"), 493 MinHealthyTime: helper.TimeToPtr(2), 494 HealthyDeadline: helper.TimeToPtr(2), 495 }, 496 }, 497 { 498 desc: "Set in task", 499 jobType: "service", 500 jobMigrate: nil, 501 taskMigrate: &MigrateStrategy{ 502 MaxParallel: helper.IntToPtr(3), 503 HealthCheck: helper.StringToPtr("checks"), 504 MinHealthyTime: helper.TimeToPtr(2), 505 HealthyDeadline: helper.TimeToPtr(2), 506 }, 507 expected: &MigrateStrategy{ 508 MaxParallel: helper.IntToPtr(3), 509 HealthCheck: helper.StringToPtr("checks"), 510 MinHealthyTime: helper.TimeToPtr(2), 511 HealthyDeadline: helper.TimeToPtr(2), 512 }, 513 }, 514 { 515 desc: "Merge from job", 516 jobType: "service", 517 jobMigrate: &MigrateStrategy{ 518 MaxParallel: helper.IntToPtr(11), 519 }, 520 taskMigrate: &MigrateStrategy{ 521 HealthCheck: helper.StringToPtr("checks"), 522 MinHealthyTime: helper.TimeToPtr(2), 523 HealthyDeadline: helper.TimeToPtr(2), 524 }, 525 expected: &MigrateStrategy{ 526 MaxParallel: helper.IntToPtr(11), 527 HealthCheck: helper.StringToPtr("checks"), 528 MinHealthyTime: helper.TimeToPtr(2), 529 HealthyDeadline: helper.TimeToPtr(2), 530 }, 531 }, 532 { 533 desc: "Override from group", 534 jobType: "service", 535 jobMigrate: &MigrateStrategy{ 536 MaxParallel: helper.IntToPtr(11), 537 }, 538 taskMigrate: &MigrateStrategy{ 539 MaxParallel: helper.IntToPtr(5), 540 HealthCheck: helper.StringToPtr("checks"), 541 MinHealthyTime: helper.TimeToPtr(2), 542 HealthyDeadline: helper.TimeToPtr(2), 543 }, 544 expected: &MigrateStrategy{ 545 MaxParallel: helper.IntToPtr(5), 546 HealthCheck: helper.StringToPtr("checks"), 547 MinHealthyTime: helper.TimeToPtr(2), 548 HealthyDeadline: helper.TimeToPtr(2), 549 }, 550 }, 551 { 552 desc: "Parallel from job, defaulting", 553 jobType: "service", 554 jobMigrate: &MigrateStrategy{ 555 MaxParallel: helper.IntToPtr(5), 556 }, 557 taskMigrate: nil, 558 expected: &MigrateStrategy{ 559 MaxParallel: helper.IntToPtr(5), 560 HealthCheck: helper.StringToPtr("checks"), 561 MinHealthyTime: helper.TimeToPtr(10 * time.Second), 562 HealthyDeadline: helper.TimeToPtr(5 * time.Minute), 563 }, 564 }, 565 } 566 567 for _, tc := range testCases { 568 t.Run(tc.desc, func(t *testing.T) { 569 job := &Job{ 570 ID: helper.StringToPtr("test"), 571 Migrate: tc.jobMigrate, 572 Type: helper.StringToPtr(tc.jobType), 573 } 574 job.Canonicalize() 575 tg := &TaskGroup{ 576 Name: helper.StringToPtr("foo"), 577 Migrate: tc.taskMigrate, 578 } 579 tg.Canonicalize(job) 580 assert.Equal(t, tc.expected, tg.Migrate) 581 }) 582 } 583 } 584 585 // TestService_CheckRestart asserts Service.CheckRestart settings are properly 586 // inherited by Checks. 587 func TestService_CheckRestart(t *testing.T) { 588 job := &Job{Name: helper.StringToPtr("job")} 589 tg := &TaskGroup{Name: helper.StringToPtr("group")} 590 task := &Task{Name: "task"} 591 service := &Service{ 592 CheckRestart: &CheckRestart{ 593 Limit: 11, 594 Grace: helper.TimeToPtr(11 * time.Second), 595 IgnoreWarnings: true, 596 }, 597 Checks: []ServiceCheck{ 598 { 599 Name: "all-set", 600 CheckRestart: &CheckRestart{ 601 Limit: 22, 602 Grace: helper.TimeToPtr(22 * time.Second), 603 IgnoreWarnings: true, 604 }, 605 }, 606 { 607 Name: "some-set", 608 CheckRestart: &CheckRestart{ 609 Limit: 33, 610 Grace: helper.TimeToPtr(33 * time.Second), 611 }, 612 }, 613 { 614 Name: "unset", 615 }, 616 }, 617 } 618 619 service.Canonicalize(task, tg, job) 620 assert.Equal(t, service.Checks[0].CheckRestart.Limit, 22) 621 assert.Equal(t, *service.Checks[0].CheckRestart.Grace, 22*time.Second) 622 assert.True(t, service.Checks[0].CheckRestart.IgnoreWarnings) 623 624 assert.Equal(t, service.Checks[1].CheckRestart.Limit, 33) 625 assert.Equal(t, *service.Checks[1].CheckRestart.Grace, 33*time.Second) 626 assert.True(t, service.Checks[1].CheckRestart.IgnoreWarnings) 627 628 assert.Equal(t, service.Checks[2].CheckRestart.Limit, 11) 629 assert.Equal(t, *service.Checks[2].CheckRestart.Grace, 11*time.Second) 630 assert.True(t, service.Checks[2].CheckRestart.IgnoreWarnings) 631 }