package scheduler import ( "reflect" "testing" "github.com/hashicorp/nomad/nomad/structs" ) func TestAnnotateTaskGroup_Updates(t *testing.T) { annotations := &structs.PlanAnnotations{ DesiredTGUpdates: map[string]*structs.DesiredUpdates{ "foo": &structs.DesiredUpdates{ Ignore: 1, Place: 2, Migrate: 3, Stop: 4, InPlaceUpdate: 5, DestructiveUpdate: 6, }, }, } tgDiff := &structs.TaskGroupDiff{ Type: structs.DiffTypeEdited, Name: "foo", } expected := &structs.TaskGroupDiff{ Type: structs.DiffTypeEdited, Name: "foo", Updates: map[string]uint64{ UpdateTypeIgnore: 1, UpdateTypeCreate: 2, UpdateTypeMigrate: 3, UpdateTypeDestroy: 4, UpdateTypeInplaceUpdate: 5, UpdateTypeDestructiveUpdate: 6, }, } if err := annotateTaskGroup(tgDiff, annotations); err != nil { t.Fatalf("annotateTaskGroup(%#v, %#v) failed: %#v", tgDiff, annotations, err) } if !reflect.DeepEqual(tgDiff, expected) { t.Fatalf("got %#v, want %#v", tgDiff, expected) } } func TestAnnotateCountChange_NonEdited(t *testing.T) { tg := &structs.TaskGroupDiff{} tgOrig := &structs.TaskGroupDiff{} annotateCountChange(tg) if !reflect.DeepEqual(tgOrig, tg) { t.Fatalf("annotateCountChange(%#v) should not have caused any annotation: %#v", tgOrig, tg) } } func TestAnnotateCountChange(t *testing.T) { up := &structs.FieldDiff{ Type: structs.DiffTypeEdited, Name: "Count", Old: "1", New: "3", } down := &structs.FieldDiff{ Type: structs.DiffTypeEdited, Name: "Count", Old: "3", New: "1", } tgUp := &structs.TaskGroupDiff{ Type: structs.DiffTypeEdited, Fields: []*structs.FieldDiff{up}, } tgDown := &structs.TaskGroupDiff{ Type: structs.DiffTypeEdited, Fields: []*structs.FieldDiff{down}, } // Test the up case if err := annotateCountChange(tgUp); err != nil { t.Fatalf("annotateCountChange(%#v) failed: %v", tgUp, err) } countDiff := tgUp.Fields[0] if len(countDiff.Annotations) != 1 || countDiff.Annotations[0] != AnnotationForcesCreate { t.Fatalf("incorrect annotation: %#v", tgUp) } // Test the down case if err := annotateCountChange(tgDown); err != nil { t.Fatalf("annotateCountChange(%#v) failed: %v", tgDown, err) } countDiff = tgDown.Fields[0] if len(countDiff.Annotations) != 1 || countDiff.Annotations[0] != AnnotationForcesDestroy { t.Fatalf("incorrect annotation: %#v", tgDown) } } func TestAnnotateTask_NonEdited(t *testing.T) { tgd := &structs.TaskGroupDiff{Type: structs.DiffTypeNone} td := &structs.TaskDiff{Type: structs.DiffTypeNone} tdOrig := &structs.TaskDiff{Type: structs.DiffTypeNone} annotateTask(td, tgd) if !reflect.DeepEqual(tdOrig, td) { t.Fatalf("annotateTask(%#v) should not have caused any annotation: %#v", tdOrig, td) } } func TestAnnotateTask(t *testing.T) { cases := []struct { Diff *structs.TaskDiff Parent *structs.TaskGroupDiff Desired string }{ { Diff: &structs.TaskDiff{ Type: structs.DiffTypeEdited, Fields: []*structs.FieldDiff{ { Type: structs.DiffTypeEdited, Name: "Driver", Old: "docker", New: "exec", }, }, }, Parent: &structs.TaskGroupDiff{Type: structs.DiffTypeEdited}, Desired: AnnotationForcesDestructiveUpdate, }, { Diff: &structs.TaskDiff{ Type: structs.DiffTypeEdited, Fields: []*structs.FieldDiff{ { Type: structs.DiffTypeEdited, Name: "User", Old: "alice", New: "bob", }, }, }, Parent: &structs.TaskGroupDiff{Type: structs.DiffTypeEdited}, Desired: AnnotationForcesDestructiveUpdate, }, { Diff: &structs.TaskDiff{ Type: structs.DiffTypeEdited, Fields: []*structs.FieldDiff{ { Type: structs.DiffTypeAdded, Name: "Env[foo]", Old: "foo", New: "bar", }, }, }, Parent: &structs.TaskGroupDiff{Type: structs.DiffTypeEdited}, Desired: AnnotationForcesDestructiveUpdate, }, { Diff: &structs.TaskDiff{ Type: structs.DiffTypeEdited, Fields: []*structs.FieldDiff{ { Type: structs.DiffTypeAdded, Name: "Meta[foo]", Old: "foo", New: "bar", }, }, }, Parent: &structs.TaskGroupDiff{Type: structs.DiffTypeEdited}, Desired: AnnotationForcesDestructiveUpdate, }, { Diff: &structs.TaskDiff{ Type: structs.DiffTypeEdited, Objects: []*structs.ObjectDiff{ { Type: structs.DiffTypeAdded, Name: "Artifact", Fields: []*structs.FieldDiff{ { Type: structs.DiffTypeAdded, Name: "GetterOptions[bam]", Old: "", New: "baz", }, { Type: structs.DiffTypeAdded, Name: "GetterSource", Old: "", New: "bam", }, { Type: structs.DiffTypeAdded, Name: "RelativeDest", Old: "", New: "bam", }, }, }, }, }, Parent: &structs.TaskGroupDiff{Type: structs.DiffTypeEdited}, Desired: AnnotationForcesDestructiveUpdate, }, { Diff: &structs.TaskDiff{ Type: structs.DiffTypeEdited, Objects: []*structs.ObjectDiff{ { Type: structs.DiffTypeEdited, Name: "Resources", Fields: []*structs.FieldDiff{ { Type: structs.DiffTypeEdited, Name: "CPU", Old: "100", New: "200", }, { Type: structs.DiffTypeEdited, Name: "DiskMB", Old: "100", New: "200", }, { Type: structs.DiffTypeEdited, Name: "IOPS", Old: "100", New: "200", }, { Type: structs.DiffTypeEdited, Name: "MemoryMB", Old: "100", New: "200", }, }, }, }, }, Parent: &structs.TaskGroupDiff{Type: structs.DiffTypeEdited}, Desired: AnnotationForcesDestructiveUpdate, }, { Diff: &structs.TaskDiff{ Type: structs.DiffTypeEdited, Objects: []*structs.ObjectDiff{ { Type: structs.DiffTypeEdited, Name: "Config", Fields: []*structs.FieldDiff{ { Type: structs.DiffTypeEdited, Name: "bam[1]", Old: "b", New: "c", }, }, }, }, }, Parent: &structs.TaskGroupDiff{Type: structs.DiffTypeEdited}, Desired: AnnotationForcesDestructiveUpdate, }, { Diff: &structs.TaskDiff{ Type: structs.DiffTypeEdited, Objects: []*structs.ObjectDiff{ { Type: structs.DiffTypeAdded, Name: "Constraint", Fields: []*structs.FieldDiff{ { Type: structs.DiffTypeAdded, Name: "LTarget", Old: "", New: "baz", }, { Type: structs.DiffTypeAdded, Name: "Operand", Old: "", New: "baz", }, { Type: structs.DiffTypeAdded, Name: "RTarget", Old: "", New: "baz", }, }, }, }, }, Parent: &structs.TaskGroupDiff{Type: structs.DiffTypeEdited}, Desired: AnnotationForcesInplaceUpdate, }, { Diff: &structs.TaskDiff{ Type: structs.DiffTypeEdited, Objects: []*structs.ObjectDiff{ { Type: structs.DiffTypeAdded, Name: "LogConfig", Fields: []*structs.FieldDiff{ { Type: structs.DiffTypeAdded, Name: "MaxFileSizeMB", Old: "", New: "10", }, { Type: structs.DiffTypeAdded, Name: "MaxFiles", Old: "", New: "1", }, }, }, }, }, Parent: &structs.TaskGroupDiff{Type: structs.DiffTypeEdited}, Desired: AnnotationForcesInplaceUpdate, }, { Diff: &structs.TaskDiff{ Type: structs.DiffTypeEdited, Objects: []*structs.ObjectDiff{ { Type: structs.DiffTypeEdited, Name: "Service", Fields: []*structs.FieldDiff{ { Type: structs.DiffTypeEdited, Name: "PortLabel", Old: "baz", New: "baz2", }, }, }, }, }, Parent: &structs.TaskGroupDiff{Type: structs.DiffTypeEdited}, Desired: AnnotationForcesInplaceUpdate, }, // Task deleted new parent { Diff: &structs.TaskDiff{ Type: structs.DiffTypeDeleted, Fields: []*structs.FieldDiff{ { Type: structs.DiffTypeAdded, Name: "Driver", Old: "", New: "exec", }, }, }, Parent: &structs.TaskGroupDiff{Type: structs.DiffTypeAdded}, Desired: AnnotationForcesDestroy, }, // Task Added new parent { Diff: &structs.TaskDiff{ Type: structs.DiffTypeAdded, Fields: []*structs.FieldDiff{ { Type: structs.DiffTypeAdded, Name: "Driver", Old: "", New: "exec", }, }, }, Parent: &structs.TaskGroupDiff{Type: structs.DiffTypeAdded}, Desired: AnnotationForcesCreate, }, // Task deleted existing parent { Diff: &structs.TaskDiff{ Type: structs.DiffTypeDeleted, Fields: []*structs.FieldDiff{ { Type: structs.DiffTypeAdded, Name: "Driver", Old: "", New: "exec", }, }, }, Parent: &structs.TaskGroupDiff{Type: structs.DiffTypeEdited}, Desired: AnnotationForcesDestructiveUpdate, }, // Task Added existing parent { Diff: &structs.TaskDiff{ Type: structs.DiffTypeAdded, Fields: []*structs.FieldDiff{ { Type: structs.DiffTypeAdded, Name: "Driver", Old: "", New: "exec", }, }, }, Parent: &structs.TaskGroupDiff{Type: structs.DiffTypeEdited}, Desired: AnnotationForcesDestructiveUpdate, }, } for i, c := range cases { annotateTask(c.Diff, c.Parent) if len(c.Diff.Annotations) != 1 || c.Diff.Annotations[0] != c.Desired { t.Fatalf("case %d not properly annotated; got %s, want %s", i+1, c.Diff.Annotations[0], c.Desired) } } }