github.com/cilium/cilium@v1.16.2/pkg/health/server/prober_test.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package server 5 6 import ( 7 "bytes" 8 "fmt" 9 "net" 10 "sort" 11 "testing" 12 13 "github.com/stretchr/testify/require" 14 15 ciliumModels "github.com/cilium/cilium/api/v1/health/models" 16 "github.com/cilium/cilium/api/v1/models" 17 ) 18 19 func makeHealthNode(nodeIdx, healthIdx int) (healthNode, net.IP, net.IP) { 20 nodeIP := fmt.Sprintf("192.0.2.%d", nodeIdx) 21 healthIP := fmt.Sprintf("10.0.2.%d", healthIdx) 22 return healthNode{ 23 NodeElement: &models.NodeElement{ 24 Name: fmt.Sprintf("node-%d", nodeIdx), 25 PrimaryAddress: &models.NodeAddressing{ 26 IPV4: &models.NodeAddressingElement{ 27 IP: nodeIP, 28 Enabled: true, 29 }, 30 IPV6: &models.NodeAddressingElement{ 31 Enabled: false, 32 }, 33 }, 34 HealthEndpointAddress: &models.NodeAddressing{ 35 IPV4: &models.NodeAddressingElement{ 36 IP: healthIP, 37 Enabled: true, 38 }, 39 IPV6: &models.NodeAddressingElement{ 40 Enabled: false, 41 }, 42 }, 43 }, 44 }, net.ParseIP(nodeIP), net.ParseIP(healthIP) 45 } 46 47 func makeHealthNodeNil(nodeIdx, healthIdx int) (healthNode, net.IP, net.IP) { 48 nodeIP := fmt.Sprintf("192.0.2.%d", nodeIdx) 49 healthIP := fmt.Sprintf("10.0.2.%d", healthIdx) 50 return healthNode{ 51 NodeElement: &models.NodeElement{ 52 Name: fmt.Sprintf("node-%d", nodeIdx), 53 PrimaryAddress: nil, 54 HealthEndpointAddress: &models.NodeAddressing{ 55 IPV4: &models.NodeAddressingElement{ 56 IP: healthIP, 57 Enabled: true, 58 }, 59 IPV6: &models.NodeAddressingElement{ 60 Enabled: false, 61 }, 62 }, 63 }, 64 }, net.ParseIP(nodeIP), net.ParseIP(healthIP) 65 } 66 67 func sortNodes(nodes map[string][]*net.IPAddr) map[string][]*net.IPAddr { 68 for _, slice := range nodes { 69 sort.Slice(slice, func(i, j int) bool { 70 iLength := len(slice[i].IP) 71 jLength := len(slice[j].IP) 72 if iLength == jLength { 73 return bytes.Compare(slice[i].IP, slice[j].IP) < 0 74 } 75 return iLength < jLength 76 }) 77 } 78 return nodes 79 } 80 81 func TestProbersetNodes(t *testing.T) { 82 node1, node1IP, node1HealthIP := makeHealthNode(1, 1) 83 newNodes := nodeMap{ 84 ipString(node1.Name): node1, 85 } 86 87 // First up: Just create a prober with some nodes. 88 prober := newProber(&Server{}, newNodes) 89 nodes := prober.getIPsByNode() 90 expected := map[string][]*net.IPAddr{ 91 node1.Name: {{ 92 IP: node1IP, 93 }, { 94 IP: node1HealthIP, 95 }}, 96 } 97 require.Equal(t, sortNodes(expected), sortNodes(nodes)) 98 99 // Update the health IP and observe that it is updated. 100 // Note that update consists of delete and add in setNodes(). 101 node1, node1IP, node1HealthIP = makeHealthNode(1, 2) 102 modifiedNodes := nodeMap{ 103 ipString(node1.Name): node1, 104 } 105 prober.setNodes(modifiedNodes, newNodes) 106 nodes = prober.getIPsByNode() 107 expected = map[string][]*net.IPAddr{ 108 node1.Name: {{ 109 IP: node1IP, 110 }, { 111 IP: node1HealthIP, 112 }}, 113 } 114 require.Equal(t, sortNodes(expected), sortNodes(nodes)) 115 116 // Remove the nodes; they shouldn't be there any more 117 prober.setNodes(nil, modifiedNodes) 118 nodes = prober.getIPsByNode() 119 expected = map[string][]*net.IPAddr{} 120 require.Equal(t, sortNodes(expected), sortNodes(nodes)) 121 122 // Add back two nodes 123 node2, node2IP, node2HealthIP := makeHealthNode(2, 20) 124 updatedNodes := nodeMap{ 125 ipString(node1.Name): node1, 126 ipString(node2.Name): node2, 127 } 128 prober.setNodes(updatedNodes, nil) 129 nodes = prober.getIPsByNode() 130 expected = map[string][]*net.IPAddr{ 131 node1.Name: {{ 132 IP: node1IP, 133 }, { 134 IP: node1HealthIP, 135 }}, 136 node2.Name: {{ 137 IP: node2IP, 138 }, { 139 IP: node2HealthIP, 140 }}, 141 } 142 require.Equal(t, sortNodes(expected), sortNodes(nodes)) 143 // Set result of probing before updating the nodes. 144 // The result should not be deleted after node update. 145 if elem, ok := prober.results[ipString(node1.NodeElement.PrimaryAddress.IPV4.IP)]; ok { 146 elem.Icmp = &ciliumModels.ConnectivityStatus{ 147 Status: "Some status", 148 } 149 } else { 150 t.Errorf("expected to find result element for node's ip %s", node1.NodeElement.PrimaryAddress.IPV4.IP) 151 } 152 // Update node 1. Node 2 should remain unaffected. 153 modifiedNodesOld := nodeMap{ 154 ipString(node1.Name): node1, 155 } 156 node1, node1IP, node1HealthIP = makeHealthNode(1, 5) 157 modifiedNodesNew := nodeMap{ 158 ipString(node1.Name): node1, 159 } 160 prober.setNodes(modifiedNodesNew, modifiedNodesOld) 161 nodes = prober.getIPsByNode() 162 expected[node1.Name] = []*net.IPAddr{{ 163 IP: node1IP, 164 }, { 165 IP: node1HealthIP, 166 }} 167 require.Equal(t, sortNodes(expected), sortNodes(nodes)) 168 if elem, ok := prober.results[ipString(node1.NodeElement.PrimaryAddress.IPV4.IP)]; !ok { 169 t.Errorf("expected to find result element for node's ip %s", node1.NodeElement.PrimaryAddress.IPV4.IP) 170 } else { 171 // Check that status was not removed when updating node 172 require.NotNil(t, elem.Icmp) 173 require.Equal(t, "Some status", elem.Icmp.Status) 174 } 175 176 // Remove node 1. Again, Node 2 should remain. 177 removedNodes := nodeMap{ 178 ipString(node1.Name): node1, 179 } 180 prober.setNodes(nil, removedNodes) 181 nodes = prober.getIPsByNode() 182 expected = map[string][]*net.IPAddr{ 183 node2.Name: {{ 184 IP: node2IP, 185 }, { 186 IP: node2HealthIP, 187 }}, 188 } 189 require.Equal(t, sortNodes(expected), sortNodes(nodes)) 190 191 // check if primary node is nil (it shouldn't show up) 192 node3, _, node3HealthIP := makeHealthNodeNil(1, 1) 193 194 newNodes3 := nodeMap{ 195 ipString(node3.Name): node3, 196 } 197 nodes3 := newProber(&Server{}, newNodes3).getIPsByNode() 198 expected3 := map[string][]*net.IPAddr{ 199 node3.Name: {{ 200 IP: node3HealthIP, 201 }}, 202 } 203 require.Equal(t, sortNodes(expected3), sortNodes(nodes3)) 204 205 // node4 has a PrimaryAddress with IPV4 enabled but an empty IP address. 206 // It should not show up in the prober. 207 node4, _, node4HealthIP := makeHealthNodeNil(4, 4) 208 node4.PrimaryAddress = &models.NodeAddressing{ 209 IPV4: &models.NodeAddressingElement{ 210 IP: "", 211 Enabled: true, 212 }, 213 IPV6: &models.NodeAddressingElement{ 214 Enabled: false, 215 }, 216 } 217 218 newNodes4 := nodeMap{ 219 ipString(node4.Name): node4, 220 } 221 prober4 := newProber(&Server{}, newNodes4) 222 nodes4 := prober4.getIPsByNode() 223 expected4 := map[string][]*net.IPAddr{ 224 node4.Name: {{ 225 IP: node4HealthIP, 226 }}, 227 } 228 require.Equal(t, sortNodes(expected4), sortNodes(nodes4)) 229 }