diff --git a/nomad/structs/diff.go b/nomad/structs/diff.go index 2334af661..a5bf9a1bd 100644 --- a/nomad/structs/diff.go +++ b/nomad/structs/diff.go @@ -840,6 +840,11 @@ func (r *Resources) Diff(other *Resources, contextual bool) *ObjectDiff { diff.Objects = append(diff.Objects, nDiffs...) } + // Requested Devices diff + if nDiffs := requestedDevicesDiffs(r.Devices, other.Devices, contextual); nDiffs != nil { + diff.Objects = append(diff.Objects, nDiffs...) + } + return diff } @@ -975,6 +980,67 @@ func portDiffs(old, new []Port, dynamic bool, contextual bool) []*ObjectDiff { } +// Diff returns a diff of two requested devices. If contextual diff is enabled, +// non-changed fields will still be returned. +func (r *RequestedDevice) Diff(other *RequestedDevice, contextual bool) *ObjectDiff { + diff := &ObjectDiff{Type: DiffTypeNone, Name: "Device"} + var oldPrimitiveFlat, newPrimitiveFlat map[string]string + + if reflect.DeepEqual(r, other) { + return nil + } else if r == nil { + diff.Type = DiffTypeAdded + newPrimitiveFlat = flatmap.Flatten(other, nil, true) + } else if other == nil { + diff.Type = DiffTypeDeleted + oldPrimitiveFlat = flatmap.Flatten(r, nil, true) + } else { + diff.Type = DiffTypeEdited + oldPrimitiveFlat = flatmap.Flatten(r, nil, true) + newPrimitiveFlat = flatmap.Flatten(other, nil, true) + } + + // Diff the primitive fields. + diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual) + + return diff +} + +// requestedDevicesDiffs diffs a set of RequestedDevices. If contextual diff is enabled, +// non-changed fields will still be returned. +func requestedDevicesDiffs(old, new []*RequestedDevice, contextual bool) []*ObjectDiff { + makeSet := func(devices []*RequestedDevice) map[string]*RequestedDevice { + deviceMap := make(map[string]*RequestedDevice, len(devices)) + for _, d := range devices { + deviceMap[d.Name] = d + } + + return deviceMap + } + + oldSet := makeSet(old) + newSet := makeSet(new) + + var diffs []*ObjectDiff + for k, oldV := range oldSet { + newV := newSet[k] + if diff := oldV.Diff(newV, contextual); diff != nil { + diffs = append(diffs, diff) + } + } + for k, newV := range newSet { + if oldV, ok := oldSet[k]; !ok { + if diff := oldV.Diff(newV, contextual); diff != nil { + diffs = append(diffs, diff) + } + } + } + + sort.Sort(ObjectDiffs(diffs)) + return diffs + +} + // configDiff returns the diff of two Task Config objects. If contextual diff is // enabled, all fields will be returned, even if no diff occurred. func configDiff(old, new map[string]interface{}, contextual bool) *ObjectDiff { diff --git a/nomad/structs/diff_test.go b/nomad/structs/diff_test.go index 671752900..9dc41f10b 100644 --- a/nomad/structs/diff_test.go +++ b/nomad/structs/diff_test.go @@ -3428,6 +3428,243 @@ func TestTaskDiff(t *testing.T) { }, }, }, + { + Name: "Device Resources edited", + Old: &Task{ + Resources: &Resources{ + Devices: []*RequestedDevice{ + { + Name: "foo", + Count: 2, + }, + { + Name: "bar", + Count: 2, + }, + { + Name: "baz", + Count: 2, + }, + }, + }, + }, + New: &Task{ + Resources: &Resources{ + Devices: []*RequestedDevice{ + { + Name: "foo", + Count: 2, + }, + { + Name: "bar", + Count: 3, + }, + { + Name: "bam", + Count: 2, + }, + }, + }, + }, + Expected: &TaskDiff{ + Type: DiffTypeEdited, + Objects: []*ObjectDiff{ + { + Type: DiffTypeEdited, + Name: "Resources", + Objects: []*ObjectDiff{ + { + Type: DiffTypeEdited, + Name: "Device", + Fields: []*FieldDiff{ + { + Type: DiffTypeEdited, + Name: "Count", + Old: "2", + New: "3", + }, + }, + }, + { + Type: DiffTypeAdded, + Name: "Device", + Fields: []*FieldDiff{ + { + Type: DiffTypeAdded, + Name: "Count", + Old: "", + New: "2", + }, + { + Type: DiffTypeAdded, + Name: "Name", + Old: "", + New: "bam", + }, + }, + }, + { + Type: DiffTypeDeleted, + Name: "Device", + Fields: []*FieldDiff{ + { + Type: DiffTypeDeleted, + Name: "Count", + Old: "2", + New: "", + }, + { + Type: DiffTypeDeleted, + Name: "Name", + Old: "baz", + New: "", + }, + }, + }, + }, + }, + }, + }, + }, + { + Name: "Device Resources edited with context", + Contextual: true, + Old: &Task{ + Resources: &Resources{ + CPU: 100, + MemoryMB: 100, + DiskMB: 100, + IOPS: 100, + Devices: []*RequestedDevice{ + { + Name: "foo", + Count: 2, + }, + { + Name: "bar", + Count: 2, + }, + { + Name: "baz", + Count: 2, + }, + }, + }, + }, + New: &Task{ + Resources: &Resources{ + CPU: 100, + MemoryMB: 100, + DiskMB: 100, + IOPS: 100, + Devices: []*RequestedDevice{ + { + Name: "foo", + Count: 2, + }, + { + Name: "bar", + Count: 3, + }, + { + Name: "bam", + Count: 2, + }, + }, + }, + }, + Expected: &TaskDiff{ + Type: DiffTypeEdited, + Objects: []*ObjectDiff{ + { + Type: DiffTypeEdited, + Name: "Resources", + Fields: []*FieldDiff{ + { + Type: DiffTypeNone, + Name: "CPU", + Old: "100", + New: "100", + }, + { + Type: DiffTypeNone, + Name: "DiskMB", + Old: "100", + New: "100", + }, + { + Type: DiffTypeNone, + Name: "IOPS", + Old: "100", + New: "100", + }, + { + Type: DiffTypeNone, + Name: "MemoryMB", + Old: "100", + New: "100", + }, + }, + Objects: []*ObjectDiff{ + { + Type: DiffTypeEdited, + Name: "Device", + Fields: []*FieldDiff{ + { + Type: DiffTypeEdited, + Name: "Count", + Old: "2", + New: "3", + }, + { + Type: DiffTypeNone, + Name: "Name", + Old: "bar", + New: "bar", + }, + }, + }, + { + Type: DiffTypeAdded, + Name: "Device", + Fields: []*FieldDiff{ + { + Type: DiffTypeAdded, + Name: "Count", + Old: "", + New: "2", + }, + { + Type: DiffTypeAdded, + Name: "Name", + Old: "", + New: "bam", + }, + }, + }, + { + Type: DiffTypeDeleted, + Name: "Device", + Fields: []*FieldDiff{ + { + Type: DiffTypeDeleted, + Name: "Count", + Old: "2", + New: "", + }, + { + Type: DiffTypeDeleted, + Name: "Name", + Old: "baz", + New: "", + }, + }, + }, + }, + }, + }, + }, + }, { Name: "Config same", Old: &Task{