Skip to content

Commit dc9a7e6

Browse files
authored
Merge pull request #376 from philbrookes/gh-356
update tree with propagate logic and tests
2 parents 9b1c43b + cafce43 commit dc9a7e6

File tree

2 files changed

+718
-12
lines changed

2 files changed

+718
-12
lines changed

internal/common/tree.go

+171-12
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ type DNSTreeNode struct {
1313
Name string
1414
Children []*DNSTreeNode
1515
DataSets []DNSTreeNodeData
16+
Parent *DNSTreeNode
1617
}
1718

1819
// DNSTreeNodeData holds a data for the enpoint(s) that correspond to this node
@@ -25,6 +26,173 @@ type DNSTreeNodeData struct {
2526
Targets []string
2627
}
2728

29+
// PropagateStoppableLabel takes a propLabel (and value) to propagate throughout a tree, and a stopLabel
30+
// whenever the label is propagated to a dataset in a node which also has the stopLabel, this node and
31+
// all of the children of this node and any parents will have the propLabel removed.
32+
//
33+
// N.B. Any node with no parents is assumed to have the stopLabel, even when not present, to prevent the
34+
// propLabel propagating to the entire tree (if this is required, use `AddLabelToBranch` on the root node).
35+
//
36+
// The overview of the logic of this function is as follows:
37+
// - Spread propLabels as greedily as possible
38+
// - Any labelled node labels all of it's children
39+
// - Any node with all children labelled get's the propLabel too
40+
//
41+
// - Resolve the stopLabels
42+
// - Any node with the stopLabel and the propLabel:
43+
// - Has the propLabel removed from itself and all it's children
44+
// - Has the propLabel removed from any parent (or parent's parent) that has the label
45+
46+
func PropagateStoppableLabel(node *DNSTreeNode, propLabel, value, stopLabel string) {
47+
//propagate labels regardless of stop labels
48+
propagateLabel(node, propLabel, value)
49+
50+
//propagate stop labels
51+
resolveStops(node, propLabel, value, stopLabel)
52+
}
53+
54+
func resolveStops(node *DNSTreeNode, label, value, stopLabel string) {
55+
if isRoot(node) && allChildrenHaveLabel(node, label, value) {
56+
RemoveLabelFromTree(node, label)
57+
//entire tree cleaned so no need for any further checks
58+
return
59+
}
60+
61+
//remove label from stop labelled children
62+
for _, c := range node.Children {
63+
d := findDataSetForChild(node, c.Name)
64+
//has label and stop label = remove label from this dataset and all children
65+
if d != nil && d.Labels[stopLabel] != "" && d.Labels[label] != "" {
66+
delete(d.Labels, label)
67+
RemoveLabelFromTree(c, label)
68+
RemoveLabelFromParents(node, label)
69+
} else {
70+
resolveStops(c, label, value, stopLabel)
71+
}
72+
73+
}
74+
75+
}
76+
77+
func propagateLabel(node *DNSTreeNode, label, value string) {
78+
for _, c := range node.Children {
79+
d := findDataSetForChild(node, c.Name)
80+
if d != nil && d.Labels[label] != "" {
81+
//this child is labelled, indiscriminately label entire tree under this child
82+
AddLabelToTree(c, label, value)
83+
} else {
84+
// this child is not labelled, continue descending to propagate label
85+
propagateLabel(c, label, value)
86+
}
87+
}
88+
89+
// if all children are labelled, label this branch in parent node
90+
if len(node.Children) > 0 && allChildrenHaveLabel(node, label, value) && node.Parent != nil {
91+
AddLabelToBranch(node.Parent, node.Name, label, value)
92+
}
93+
}
94+
95+
func isRoot(node *DNSTreeNode) bool {
96+
return node.Parent == nil
97+
}
98+
99+
func allChildrenHaveLabel(node *DNSTreeNode, label, value string) bool {
100+
for _, c := range node.Children {
101+
if !HasLabelForBranch(node, c.Name, label, value) {
102+
return false
103+
}
104+
}
105+
return true
106+
}
107+
108+
func findDataSetForChild(node *DNSTreeNode, name string) *DNSTreeNodeData {
109+
for _, d := range node.DataSets {
110+
if slices.Contains(d.Targets, name) {
111+
return &d
112+
}
113+
}
114+
return nil
115+
}
116+
117+
func AddLabelToBranch(node *DNSTreeNode, branch, label, value string) {
118+
d := findDataSetForChild(node, branch)
119+
if d == nil {
120+
node.DataSets = append(node.DataSets, DNSTreeNodeData{
121+
Labels: endpoint.Labels{
122+
label: value,
123+
},
124+
Targets: []string{
125+
branch,
126+
},
127+
})
128+
} else {
129+
if len(d.Targets) == 1 {
130+
d.Labels[label] = value
131+
} else {
132+
//remove target from shared dataset and recreate uniquely
133+
for i, t := range d.Targets {
134+
if t == branch {
135+
d.Targets = append(d.Targets[:i], d.Targets[i+1:]...)
136+
newDS := DNSTreeNodeData{
137+
Labels: d.Labels.DeepCopy(),
138+
Targets: []string{branch},
139+
}
140+
newDS.Labels[label] = value
141+
node.DataSets = append(node.DataSets, newDS)
142+
}
143+
}
144+
}
145+
}
146+
}
147+
148+
func AddChild(parent *DNSTreeNode, child *DNSTreeNode) {
149+
parent.Children = append(parent.Children, child)
150+
child.Parent = parent
151+
}
152+
153+
func RemoveLabelFromParents(node *DNSTreeNode, label string) {
154+
if isRoot(node) {
155+
return
156+
}
157+
158+
d := findDataSetForChild(node.Parent, node.Name)
159+
if d == nil {
160+
return
161+
}
162+
163+
delete(d.Labels, label)
164+
165+
RemoveLabelFromParents(node.Parent, label)
166+
}
167+
168+
func RemoveLabelFromTree(node *DNSTreeNode, label string) {
169+
for _, d := range node.DataSets {
170+
delete(d.Labels, label)
171+
}
172+
173+
for _, c := range node.Children {
174+
RemoveLabelFromTree(c, label)
175+
}
176+
}
177+
178+
func AddLabelToTree(node *DNSTreeNode, label, value string) {
179+
for _, c := range node.Children {
180+
AddLabelToBranch(node, c.Name, label, value)
181+
AddLabelToTree(c, label, value)
182+
}
183+
}
184+
185+
func HasLabelForBranch(node *DNSTreeNode, branch, label, value string) bool {
186+
d := findDataSetForChild(node, branch)
187+
if d == nil {
188+
return false
189+
}
190+
if v, ok := d.Labels[label]; ok {
191+
return value == v
192+
}
193+
return false
194+
}
195+
28196
// RemoveNode removes a node from a tree.
29197
// If the node was the only child of the parent node,
30198
// the parent will be removed as well unless the parent is a root node
@@ -115,21 +283,11 @@ func ToEndpoints(node *DNSTreeNode, endpoints *[]*endpoint.Endpoint) *[]*endpoin
115283
if isALeafNode(node) {
116284
return endpoints
117285
}
118-
targets := []string{}
286+
119287
for _, child := range node.Children {
120-
targets = append(targets, child.Name)
121288
ToEndpoints(child, endpoints)
122289
}
123290

124-
// this should not happen. the node is either leaf or has datasets (unless the cree was made manually)
125-
if node.DataSets == nil {
126-
*endpoints = append(*endpoints, &endpoint.Endpoint{
127-
DNSName: node.Name,
128-
Targets: targets,
129-
})
130-
return endpoints
131-
}
132-
133291
for _, data := range node.DataSets {
134292
*endpoints = append(*endpoints, &endpoint.Endpoint{
135293
DNSName: node.Name,
@@ -162,6 +320,7 @@ func populateNode(node *DNSTreeNode, record *v1alpha1.DNSRecord) {
162320
}
163321

164322
for _, c := range children {
323+
c.Parent = node
165324
populateNode(c, record)
166325
}
167326
node.Children = children
@@ -204,5 +363,5 @@ func findDataSets(name string, record *v1alpha1.DNSRecord) []DNSTreeNodeData {
204363
// isALeafNode check if this is the last node in a tree
205364
func isALeafNode(node *DNSTreeNode) bool {
206365
// no children means this is pointing to an IP or a host outside of the DNS Record
207-
return node.Children == nil || len(node.Children) == 0
366+
return len(node.Children) == 0
208367
}

0 commit comments

Comments
 (0)