Add service diff

This commit is contained in:
Alex Dadgar 2016-05-11 15:25:59 -07:00
parent 967011e25c
commit 59e244d18e
2 changed files with 578 additions and 0 deletions

View File

@ -388,6 +388,11 @@ func (t *Task) Diff(other *Task, contextual bool) (*TaskDiff, error) {
diff.Objects = append(diff.Objects, diffs...)
}
// Services diff
if sDiffs := serviceDiffs(t.Services, other.Services, contextual); sDiffs != nil {
diff.Objects = append(diff.Objects, sDiffs...)
}
return diff, nil
}
@ -454,6 +459,133 @@ func (t TaskDiffs) Len() int { return len(t) }
func (t TaskDiffs) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
func (t TaskDiffs) Less(i, j int) bool { return t[i].Name < t[j].Name }
// serviceDiff returns the diff of two service objects. If contextual diff is
// enabled, all fields will be returned, even if no diff occured.
func serviceDiff(old, new *Service, contextual bool) *ObjectDiff {
diff := &ObjectDiff{Type: DiffTypeNone, Name: "Service"}
var oldPrimitiveFlat, newPrimitiveFlat map[string]string
if reflect.DeepEqual(old, new) {
return nil
} else if old == nil {
old = &Service{}
diff.Type = DiffTypeAdded
newPrimitiveFlat = flatmap.Flatten(new, nil, true)
} else if new == nil {
new = &Service{}
diff.Type = DiffTypeDeleted
oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
} else {
diff.Type = DiffTypeEdited
oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
newPrimitiveFlat = flatmap.Flatten(new, nil, true)
}
// Diff the primitive fields.
diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
// Checks diffs
if cDiffs := serviceCheckDiffs(old.Checks, new.Checks, contextual); cDiffs != nil {
diff.Objects = append(diff.Objects, cDiffs...)
}
return diff
}
// serviceDiffs diffs a set of services. If contextual diff is enabled, unchanged
// fields within objects nested in the tasks will be returned.
func serviceDiffs(old, new []*Service, contextual bool) []*ObjectDiff {
oldMap := make(map[string]*Service, len(old))
newMap := make(map[string]*Service, len(new))
for _, o := range old {
oldMap[o.Name] = o
}
for _, n := range new {
newMap[n.Name] = n
}
var diffs []*ObjectDiff
for name, oldService := range oldMap {
// Diff the same, deleted and edited
if diff := serviceDiff(oldService, newMap[name], contextual); diff != nil {
diffs = append(diffs, diff)
}
}
for name, newService := range newMap {
// Diff the added
if old, ok := oldMap[name]; !ok {
if diff := serviceDiff(old, newService, contextual); diff != nil {
diffs = append(diffs, diff)
}
}
}
sort.Sort(ObjectDiffs(diffs))
return diffs
}
// serviceCheckDiff returns the diff of two service check objects. If contextual
// diff is enabled, all fields will be returned, even if no diff occured.
func serviceCheckDiff(old, new *ServiceCheck, contextual bool) *ObjectDiff {
diff := &ObjectDiff{Type: DiffTypeNone, Name: "Check"}
var oldPrimitiveFlat, newPrimitiveFlat map[string]string
if reflect.DeepEqual(old, new) {
return nil
} else if old == nil {
old = &ServiceCheck{}
diff.Type = DiffTypeAdded
newPrimitiveFlat = flatmap.Flatten(new, nil, true)
} else if new == nil {
new = &ServiceCheck{}
diff.Type = DiffTypeDeleted
oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
} else {
diff.Type = DiffTypeEdited
oldPrimitiveFlat = flatmap.Flatten(old, nil, true)
newPrimitiveFlat = flatmap.Flatten(new, nil, true)
}
// Diff the primitive fields.
diff.Fields = fieldDiffs(oldPrimitiveFlat, newPrimitiveFlat, contextual)
return diff
}
// serviceCheckDiffs diffs a set of service checks. If contextual diff is
// enabled, unchanged fields within objects nested in the tasks will be
// returned.
func serviceCheckDiffs(old, new []*ServiceCheck, contextual bool) []*ObjectDiff {
oldMap := make(map[string]*ServiceCheck, len(old))
newMap := make(map[string]*ServiceCheck, len(new))
for _, o := range old {
oldMap[o.Name] = o
}
for _, n := range new {
newMap[n.Name] = n
}
var diffs []*ObjectDiff
for name, oldService := range oldMap {
// Diff the same, deleted and edited
if diff := serviceCheckDiff(oldService, newMap[name], contextual); diff != nil {
diffs = append(diffs, diff)
}
}
for name, newService := range newMap {
// Diff the added
if old, ok := oldMap[name]; !ok {
if diff := serviceCheckDiff(old, newService, contextual); diff != nil {
diffs = append(diffs, diff)
}
}
}
sort.Sort(ObjectDiffs(diffs))
return diffs
}
// Diff returns a diff of two resource objects. If contextual diff is enabled,
// non-changed fields will still be returned.
func (r *Resources) Diff(other *Resources, contextual bool) *ObjectDiff {

View File

@ -2263,6 +2263,452 @@ func TestTaskDiff(t *testing.T) {
},
},
},
{
// Services edited (no checks)
Old: &Task{
Services: []*Service{
{
Name: "foo",
PortLabel: "foo",
},
{
Name: "bar",
PortLabel: "bar",
},
{
Name: "baz",
PortLabel: "baz",
},
},
},
New: &Task{
Services: []*Service{
{
Name: "bar",
PortLabel: "bar",
},
{
Name: "baz",
PortLabel: "baz2",
},
{
Name: "bam",
PortLabel: "bam",
},
},
},
Expected: &TaskDiff{
Type: DiffTypeEdited,
Objects: []*ObjectDiff{
{
Type: DiffTypeEdited,
Name: "Service",
Fields: []*FieldDiff{
{
Type: DiffTypeEdited,
Name: "PortLabel",
Old: "baz",
New: "baz2",
},
},
},
{
Type: DiffTypeAdded,
Name: "Service",
Fields: []*FieldDiff{
{
Type: DiffTypeAdded,
Name: "Name",
Old: "",
New: "bam",
},
{
Type: DiffTypeAdded,
Name: "PortLabel",
Old: "",
New: "bam",
},
},
},
{
Type: DiffTypeDeleted,
Name: "Service",
Fields: []*FieldDiff{
{
Type: DiffTypeDeleted,
Name: "Name",
Old: "foo",
New: "",
},
{
Type: DiffTypeDeleted,
Name: "PortLabel",
Old: "foo",
New: "",
},
},
},
},
},
},
{
// Services edited (no checks) with context
Contextual: true,
Old: &Task{
Services: []*Service{
{
Name: "foo",
PortLabel: "foo",
},
},
},
New: &Task{
Services: []*Service{
{
Name: "foo",
PortLabel: "bar",
},
},
},
Expected: &TaskDiff{
Type: DiffTypeEdited,
Objects: []*ObjectDiff{
{
Type: DiffTypeEdited,
Name: "Service",
Fields: []*FieldDiff{
{
Type: DiffTypeNone,
Name: "Name",
Old: "foo",
New: "foo",
},
{
Type: DiffTypeEdited,
Name: "PortLabel",
Old: "foo",
New: "bar",
},
},
},
},
},
},
{
// Service Checks edited
Old: &Task{
Services: []*Service{
{
Name: "foo",
Checks: []*ServiceCheck{
{
Name: "foo",
Type: "http",
Command: "foo",
Args: []string{"foo"},
Path: "foo",
Protocol: "http",
Interval: 1 * time.Second,
Timeout: 1 * time.Second,
},
{
Name: "bar",
Type: "http",
Command: "foo",
Args: []string{"foo"},
Path: "foo",
Protocol: "http",
Interval: 1 * time.Second,
Timeout: 1 * time.Second,
},
{
Name: "baz",
Type: "http",
Command: "foo",
Args: []string{"foo"},
Path: "foo",
Protocol: "http",
Interval: 1 * time.Second,
Timeout: 1 * time.Second,
},
},
},
},
},
New: &Task{
Services: []*Service{
{
Name: "foo",
Checks: []*ServiceCheck{
{
Name: "bar",
Type: "http",
Command: "foo",
Args: []string{"foo"},
Path: "foo",
Protocol: "http",
Interval: 1 * time.Second,
Timeout: 1 * time.Second,
},
{
Name: "baz",
Type: "tcp",
Command: "foo",
Args: []string{"foo"},
Path: "foo",
Protocol: "http",
Interval: 1 * time.Second,
Timeout: 1 * time.Second,
},
{
Name: "bam",
Type: "http",
Command: "foo",
Args: []string{"foo"},
Path: "foo",
Protocol: "http",
Interval: 1 * time.Second,
Timeout: 1 * time.Second,
},
},
},
},
},
Expected: &TaskDiff{
Type: DiffTypeEdited,
Objects: []*ObjectDiff{
{
Type: DiffTypeEdited,
Name: "Service",
Objects: []*ObjectDiff{
{
Type: DiffTypeEdited,
Name: "Check",
Fields: []*FieldDiff{
{
Type: DiffTypeEdited,
Name: "Type",
Old: "http",
New: "tcp",
},
},
},
{
Type: DiffTypeAdded,
Name: "Check",
Fields: []*FieldDiff{
{
Type: DiffTypeAdded,
Name: "Command",
Old: "",
New: "foo",
},
{
Type: DiffTypeAdded,
Name: "Interval",
Old: "",
New: "1000000000",
},
{
Type: DiffTypeAdded,
Name: "Name",
Old: "",
New: "bam",
},
{
Type: DiffTypeAdded,
Name: "Path",
Old: "",
New: "foo",
},
{
Type: DiffTypeAdded,
Name: "Protocol",
Old: "",
New: "http",
},
{
Type: DiffTypeAdded,
Name: "Timeout",
Old: "",
New: "1000000000",
},
{
Type: DiffTypeAdded,
Name: "Type",
Old: "",
New: "http",
},
},
},
{
Type: DiffTypeDeleted,
Name: "Check",
Fields: []*FieldDiff{
{
Type: DiffTypeDeleted,
Name: "Command",
Old: "foo",
New: "",
},
{
Type: DiffTypeDeleted,
Name: "Interval",
Old: "1000000000",
New: "",
},
{
Type: DiffTypeDeleted,
Name: "Name",
Old: "foo",
New: "",
},
{
Type: DiffTypeDeleted,
Name: "Path",
Old: "foo",
New: "",
},
{
Type: DiffTypeDeleted,
Name: "Protocol",
Old: "http",
New: "",
},
{
Type: DiffTypeDeleted,
Name: "Timeout",
Old: "1000000000",
New: "",
},
{
Type: DiffTypeDeleted,
Name: "Type",
Old: "http",
New: "",
},
},
},
},
},
},
},
},
{
// Service Checks edited with context
Contextual: true,
Old: &Task{
Services: []*Service{
{
Name: "foo",
Checks: []*ServiceCheck{
{
Name: "foo",
Type: "http",
Command: "foo",
Args: []string{"foo"},
Path: "foo",
Protocol: "http",
Interval: 1 * time.Second,
Timeout: 1 * time.Second,
},
},
},
},
},
New: &Task{
Services: []*Service{
{
Name: "foo",
Checks: []*ServiceCheck{
{
Name: "foo",
Type: "tcp",
Command: "foo",
Args: []string{"foo"},
Path: "foo",
Protocol: "http",
Interval: 1 * time.Second,
Timeout: 1 * time.Second,
},
},
},
},
},
Expected: &TaskDiff{
Type: DiffTypeEdited,
Objects: []*ObjectDiff{
{
Type: DiffTypeEdited,
Name: "Service",
Fields: []*FieldDiff{
{
Type: DiffTypeNone,
Name: "Name",
Old: "foo",
New: "foo",
},
{
Type: DiffTypeNone,
Name: "PortLabel",
Old: "",
New: "",
},
},
Objects: []*ObjectDiff{
{
Type: DiffTypeEdited,
Name: "Check",
Fields: []*FieldDiff{
{
Type: DiffTypeNone,
Name: "Command",
Old: "foo",
New: "foo",
},
{
Type: DiffTypeNone,
Name: "Interval",
Old: "1000000000",
New: "1000000000",
},
{
Type: DiffTypeNone,
Name: "Name",
Old: "foo",
New: "foo",
},
{
Type: DiffTypeNone,
Name: "Path",
Old: "foo",
New: "foo",
},
{
Type: DiffTypeNone,
Name: "Protocol",
Old: "http",
New: "http",
},
{
Type: DiffTypeNone,
Name: "Timeout",
Old: "1000000000",
New: "1000000000",
},
{
Type: DiffTypeEdited,
Name: "Type",
Old: "http",
New: "tcp",
},
},
},
},
},
},
},
},
}
for i, c := range cases {