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  }