package cluster
import (
"errors"
"k8s.io/kubernetes/pkg/api"
"testing"
)
// Used as a stub for golang's net.LookupIP to avoid actual DNS lookups in tests.
func dummyDnsResolver(hostname string) ([]string, error) {
// This method should not end up getting called for actual IPs, so we only
// need to map out the hostnames we'll be "resolving":
domains := map[string][]string{
"something.example.com": {"192.168.1.1"},
"multiaddress.example.com": {"192.168.1.2", "10.0.1.1"},
"localhost": {"127.0.0.1"},
}
ip, ok := domains[hostname]
if !ok {
return nil, errors.New("Dummy DNS lookup error")
}
return ip, nil
}
func ipTester(t *testing.T, serverUrl string, expectedIps ...string) {
hostnames, err := resolveServerIP(serverUrl, dummyDnsResolver)
// If given no expected IPs we assume the caller wanted an error to occur:
if len(expectedIps) == 0 || expectedIps == nil {
if err == nil {
t.Errorf("No expected IPs given, error expected but none occurred. Got hostnames: %s", hostnames)
return
}
return
}
if err != nil || len(hostnames) == 0 {
t.Errorf("Unable to resolve IP from: %s, error: %s", serverUrl, err)
return
}
if len(hostnames) != len(expectedIps) {
t.Errorf("Expected %d IPs, got: %d", len(expectedIps), len(hostnames))
return
}
for _, expectedIp := range expectedIps {
found := false
for _, hostResult := range hostnames {
if hostResult == expectedIp {
found = true
}
}
if !found {
t.Errorf("Missing expected IP: %s, got: %s", expectedIp, hostnames)
return
}
}
}
func TestServerResolve(t *testing.T) {
ipTester(t, "https://something.example.com:8443/api/", "192.168.1.1")
}
func TestServerResolveFails(t *testing.T) {
// Our dummy DNS resolver function doesn't know this host:
ipTester(t, "https://somethingelse.example.com:8443/api/")
}
func TestServerResolveMultiAddress(t *testing.T) {
ipTester(t, "https://multiaddress.example.com:8443/api/", "192.168.1.2", "10.0.1.1")
}
func TestServerResolveNoPort(t *testing.T) {
ipTester(t, "https://something.example.com/api/", "192.168.1.1")
}
func TestServerResolveNoPortNoPath(t *testing.T) {
ipTester(t, "https://something.example.com", "192.168.1.1")
}
func TestServerResolveNoPath(t *testing.T) {
ipTester(t, "https://something.example.com:8443", "192.168.1.1")
}
func TestServerResolveLocalhost(t *testing.T) {
ipTester(t, "https://localhost:8443/api/", "127.0.0.1")
}
func TestServerResolveIP(t *testing.T) {
ipTester(t, "https://192.168.1.1:8443/api/", "192.168.1.1")
}
func TestServerResolveIPNoPort(t *testing.T) {
ipTester(t, "https://192.168.1.1/api/", "192.168.1.1")
}
func TestServerResolveIPNoPortNoPath(t *testing.T) {
ipTester(t, "https://192.168.1.1", "192.168.1.1")
}
func TestServerResolveIPNoPath(t *testing.T) {
ipTester(t, "https://192.168.1.1:8443", "192.168.1.1")
}
func TestServerResolveIPv6(t *testing.T) {
ipTester(t, "https://[2001:4860:0:2001::6]:8443/api/", "2001:4860:0:2001::6")
}
func TestServerResolveIPv6UpperCaseFull(t *testing.T) {
// net.IP normalizes to lowercase and shortens:
ipTester(t, "https://[FE80:0000:0000:0000:0202:B3FF:FE1E:8329]:8443/api/",
"fe80::202:b3ff:fe1e:8329")
}
func TestServerResolveIPv6NoPort(t *testing.T) {
ipTester(t, "https://2001:4860:0:2001::6/api/", "2001:4860:0:2001::6")
}
func TestServerResolveIPv6BracesNoPort(t *testing.T) {
// Technically bad syntax so expect an error:
ipTester(t, "https://[2001:4860:0:2001::6]/api/")
}
func TestServerResolveIPv6NoPortNoPath(t *testing.T) {
ipTester(t, "https://2001:4860:0:2001::6", "2001:4860:0:2001::6")
}
func TestServerResolveIPv6NoPath(t *testing.T) {
ipTester(t, "https://[2001:4860:0:2001::6]:8443", "2001:4860:0:2001::6")
}
func TestServerResolveIPv6BadBraces(t *testing.T) {
ipTester(t, "https://[[2001:4860:0:2001::6]:8443")
ipTester(t, "https://[[2001:4860:0:2001::6]]:8443")
ipTester(t, "https://2001:4860:0:2001::6]]:8443")
}
func TestServerResolveBadURL(t *testing.T) {
ipTester(t, "thisdoesntlooklikeaurl")
}
// createNode creates a dummy Kubernetes Node object with the IP addresses we request.
func createNode(name string, ipAddresses []string) api.Node {
// Create a Kube NodeAddress for each given IP address string:
addresses := make([]api.NodeAddress, len(ipAddresses))
for _, addr := range ipAddresses {
// We don't really care what the type is, we check them all looking for any match:
addresses = append(addresses, api.NodeAddress{Type: api.NodeExternalIP, Address: addr})
}
status := api.NodeStatus{Addresses: addresses}
node := api.Node{ObjectMeta: api.ObjectMeta{Name: name}, Status: status}
return node
}
func TestScanNodesMatchFound(t *testing.T) {
nodes := make([]api.Node, 3)
nodes = append(nodes, createNode("node1", []string{"192.168.1.1", "10.0.0.1", "24.222.0.1"}))
nodes = append(nodes, createNode("node2", []string{"192.168.1.2", "10.0.0.2", "24.222.0.2"}))
nodes = append(nodes, createNode("node3", []string{"192.168.1.3", "10.0.0.3", "24.222.0.3"}))
r := searchNodesForIP(nodes, []string{"24.222.0.3"})
if len(r.Errors()) > 0 {
t.Error("Unexpected error attempting to locate node with IP")
}
if len(r.Warnings()) > 0 {
t.Error("Unexpected warning attempting to locate node with IP")
}
}
func TestScanNodesAnyIPv6MatchFound(t *testing.T) {
nodes := make([]api.Node, 3)
nodes = append(nodes, createNode("node1", []string{"2001:4860:0:2001::6"}))
nodes = append(nodes, createNode("node2", []string{"3001:4860:0:2001::6"}))
nodes = append(nodes, createNode("node3", []string{"4001:4860:0:2001::6"}))
// First server IP won't match, second will:
r := searchNodesForIP(nodes, []string{"10.0.55.55", "2001:4860:0:2001::6"})
if len(r.Errors()) > 0 {
t.Error("Unexpected error attempting to locate node with IP")
}
if len(r.Warnings()) > 0 {
t.Error("Unexpected warning attempting to locate node with IP")
}
}