Repository: NetApp/harvest
Pull request #770 (2022-01-04 10:54:48)

fix: union issue when fields applied at child
There are few logs and changes added for testing, I will remove them before final submission.

Logs after the poller start:
```
4:09PM INF goharvest2/cmd/poller/collector/helpers.go:122 > best-fit template [conf/zapi/cdot/9.8.0/volume.yaml] for [9.10.1] Poller=cluster-07 collector=Zapi:Volume
4:09PM INF goharvest2/cmd/collectors/zapi/collector/zapi.go:112 > parent [collector client_timeout schedule objects addr auth_style collectors datacenter exporters password use_insecure_tls username] Poller=cluster-07 collector=Zapi:Volume
4:09PM INF goharvest2/cmd/collectors/zapi/collector/zapi.go:113 > parent schedule [instance data] Poller=cluster-07 collector=Zapi:Volume
4:09PM INF goharvest2/cmd/collectors/zapi/collector/zapi.go:114 > parent schedule [600s 180s] Poller=cluster-07 collector=Zapi:Volume
4:09PM INF goharvest2/cmd/collectors/zapi/collector/zapi.go:115 > parent client_timeout 2m Poller=cluster-07 collector=Zapi:Volume
4:09PM INF goharvest2/cmd/collectors/zapi/collector/zapi.go:116 > child [name query object client_timeout schedule counters plugins export_options] Poller=cluster-07 collector=Zapi:Volume
4:09PM INF goharvest2/cmd/collectors/zapi/collector/zapi.go:118 > child schedule [data] Poller=cluster-07 collector=Zapi:Volume
4:09PM INF goharvest2/cmd/collectors/zapi/collector/zapi.go:119 > child schedule [90s] Poller=cluster-07 collector=Zapi:Volume
4:09PM INF goharvest2/cmd/collectors/zapi/collector/zapi.go:122 > child client_timeout 1m Poller=cluster-07 collector=Zapi:Volume
4:09PM INF goharvest2/cmd/collectors/zapi/collector/zapi.go:125 > merged schedule [instance data] Poller=cluster-07 collector=Zapi:Volume
4:09PM INF goharvest2/cmd/collectors/zapi/collector/zapi.go:126 > merged schedule [600s 90s] Poller=cluster-07 collector=Zapi:Volume
4:09PM INF goharvest2/cmd/collectors/zapi/collector/zapi.go:127 > merged client_timeout 1m Poller=cluster-07 collector=Zapi:Volume

```


Patch:
diff --git a/pkg/tree/node/node.go b/pkg/tree/node/node.go
index 7fe28cf7..f76e04f5 100644
--- a/pkg/tree/node/node.go
+++ b/pkg/tree/node/node.go
@@ -264,6 +264,12 @@ func (n *Node) Union(source *Node) {
 	for _, child := range source.Children {
 		if !n.HasChild(child.GetName()) {
 			n.AddChild(child)
+		} else if child.GetChildren() != nil {
+			// union at child level
+			n.GetChild(child.GetName()).Union(child)
+		} else {
+			// child template would take precedence over parent
+			n.SetChildContentS(child.GetNameS(), child.GetContentS())
 		}
 	}
 }


Test patch:
diff --git a/pkg/tree/node/node_test.go b/pkg/tree/node/node_test.go
index fef00b8e..95baf313 100644
--- a/pkg/tree/node/node_test.go
+++ b/pkg/tree/node/node_test.go
@@ -69,3 +69,82 @@ func makeTree(names ...string) *Node {
 	}
 	return tree.GetChildS(names[0])
 }
+
+func TestNode_Union(t *testing.T) {
+	parent := &Node{
+		name:     []byte("default"),
+		Children: make([]*Node, 0),
+	}
+	child := &Node{
+		name:     []byte("volume"),
+		Children: make([]*Node, 0),
+	}
+
+	testNodeUnionCase1(parent, child, t)
+	testNodeUnionCase2(parent, child, t)
+	testNodeUnionCase3(parent, child, t)
+}
+
+// Parent don't have field and child would have, after the union, parent will be having the field
+func testNodeUnionCase1(parent *Node, child *Node, t *testing.T) {
+	childClientTimeout := &Node{name: []byte("client_timeout"), Content: []byte("2m")}
+	child.AddChild(childClientTimeout)
+
+	parent.Union(child)
+
+	if timeout := parent.GetChildS("client_timeout"); timeout != nil {
+		if timeoutVal := timeout.GetContentS(); timeoutVal != "2m" {
+			t.Errorf("client timeout after union got=[%v], want=[%v]", timeoutVal, "2m")
+		}
+	} else {
+		t.Errorf("client timeout after union got=[%v], want=[%v]", nil, "2m")
+	}
+}
+
+// Parent and child both have field but different in sub-child level, after the union, parent will be having union of both
+func testNodeUnionCase2(parent *Node, child *Node, t *testing.T) {
+	parentScheduleInstance := &Node{name: []byte("instance"), Content: []byte("600s")}
+	parentScheduleData := &Node{name: []byte("data"), Content: []byte("180s")}
+	parentScheduleCounter := &Node{name: []byte("counter"), Content: []byte("1200s")}
+	parentSchedule := &Node{name: []byte("schedule"), Children: []*Node{parentScheduleInstance, parentScheduleData, parentScheduleCounter}}
+	parent.AddChild(parentSchedule)
+
+	childScheduleData := &Node{name: []byte("data"), Content: []byte("360s")}
+	childSchedule := &Node{name: []byte("schedule"), Children: []*Node{childScheduleData}}
+	child.AddChild(childSchedule)
+
+	parent.Union(child)
+
+	if schedule := parent.GetChildS("schedule"); schedule != nil {
+		if instanceVal := schedule.GetChildContentS("instance"); instanceVal != "600s" {
+			t.Errorf("schedule instance value after union got=[%v], want=[%v]", instanceVal, "600s")
+		}
+		if dataVal := schedule.GetChildContentS("data"); dataVal != "360s" {
+			t.Errorf("schedule data value after union got=[%v], want=[%v]", dataVal, "360s")
+		}
+		if counterVal := schedule.GetChildContentS("counter"); counterVal != "1200s" {
+			t.Errorf("schedule counter value after union got=[%v], want=[%v]", counterVal, "1200s")
+		}
+	} else {
+		t.Errorf("schedule after union got=[%v], want=[%v]", nil, "instance: 600s, data: 360s, counter: 1200s")
+	}
+}
+
+// Parent and child both have field but different value, after the union, parent will be having child's value
+func testNodeUnionCase3(parent *Node, child *Node, t *testing.T) {
+	parentClientTimeout := &Node{name: []byte("client_timeout"), Content: []byte("1m")}
+	parent.AddChild(parentClientTimeout)
+
+	childClientTimeout := &Node{name: []byte("client_timeout"), Content: []byte("3m")}
+	child.AddChild(childClientTimeout)
+
+	parent.Union(child)
+
+	if timeout := parent.GetChildS("client_timeout"); timeout != nil {
+		if timeoutVal := timeout.GetContentS(); timeoutVal != "3m" {
+			t.Errorf("client timeout after union got=[%v], want=[%v]", timeoutVal, "3m")
+		}
+	} else {
+		t.Errorf("client timeout after union got=[%v], want=[%v]", nil, "3m")
+	}
+}