stackube/pkg/proxy/proxier_test.go
mozhulee 604dd5b0c9 Add service-controller unit tests
Change-Id: I3fd5bc4b62d26c7e6d118ba08b6c001c871fcbd3
Implements: blueprint service-controller-test
Signed-off-by: mozhuli <21621232@zju.edu.cn>
2017-08-28 09:43:43 +08:00

673 lines
18 KiB
Go

/*
Copyright (c) 2017 OpenStack Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package proxy
import (
"fmt"
"net"
"testing"
"time"
"github.com/davecgh/go-spew/spew"
crdClient "git.openstack.org/openstack/stackube/pkg/kubecrd"
"git.openstack.org/openstack/stackube/pkg/openstack"
drivertypes "git.openstack.org/openstack/stackube/pkg/openstack/types"
"git.openstack.org/openstack/stackube/pkg/util"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/kubernetes/pkg/util/async"
)
const (
defaultNetworkID = "123"
defaultPortID = "123"
deviceOwner = "network:router_interface"
)
func newFakeServiceInfo(serviceName string, ip net.IP) *serviceInfo {
return &serviceInfo{
name: serviceName,
clusterIP: ip,
}
}
func defaultNetwork(networkName, uid string) *drivertypes.Network {
return &drivertypes.Network{
Name: networkName,
Uid: uid,
}
}
func Test_getServiceIP(t *testing.T) {
fp := NewFakeProxier(nil, nil)
testCases := []struct {
serviceInfo *serviceInfo
expected string
}{{
// Case[0]: kube-dns service.
serviceInfo: newFakeServiceInfo("kube-dns", net.IPv4(1, 2, 3, 4)),
expected: testclusterDNS,
}, {
// Case[1]: other service.
serviceInfo: newFakeServiceInfo("test", net.IPv4(1, 2, 3, 4)),
expected: "1.2.3.4",
},
}
for tci, tc := range testCases {
// outputs
clusterIP := fp.getServiceIP(tc.serviceInfo)
if clusterIP != tc.expected {
t.Errorf("Case[%d] expected %#v, got %#v", tci, tc.expected, clusterIP)
}
}
}
const testclusterDNS = "10.20.30.40"
func NewFakeProxier(ipt iptablesInterface, osClient openstack.Interface) *Proxier {
p := &Proxier{
clusterDNS: testclusterDNS,
osClient: osClient,
iptables: ipt,
endpointsChanges: newEndpointsChangeMap(""),
serviceChanges: newServiceChangeMap(),
namespaceChanges: newNamespaceChangeMap(),
serviceMap: make(proxyServiceMap),
endpointsMap: make(proxyEndpointsMap),
namespaceMap: make(map[string]*namespaceInfo),
serviceNSMap: make(map[string]proxyServiceMap),
}
p.syncRunner = async.NewBoundedFrequencyRunner("test-sync-runner", p.syncProxyRules, 0, time.Minute, 1)
return p
}
func hasDNAT(rules []Rule, endpoint string) bool {
for _, r := range rules {
if r[ToDest] == endpoint {
return true
}
}
return false
}
func makeTestService(namespace, name string, svcFunc func(*v1.Service)) *v1.Service {
svc := &v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Annotations: map[string]string{},
},
Spec: v1.ServiceSpec{},
Status: v1.ServiceStatus{},
}
svcFunc(svc)
return svc
}
func makeServiceMap(proxier *Proxier, allServices ...*v1.Service) {
for i := range allServices {
proxier.onServiceAdded(allServices[i])
}
proxier.mu.Lock()
defer proxier.mu.Unlock()
proxier.servicesSynced = true
}
func makeTestEndpoints(namespace, name string, eptFunc func(*v1.Endpoints)) *v1.Endpoints {
ept := &v1.Endpoints{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
}
eptFunc(ept)
return ept
}
func makeEndpointsMap(proxier *Proxier, allEndpoints ...*v1.Endpoints) {
for i := range allEndpoints {
proxier.onEndpointsAdded(allEndpoints[i])
}
proxier.mu.Lock()
defer proxier.mu.Unlock()
proxier.endpointsSynced = true
}
func makeTestNamespace(name string) *v1.Namespace {
ns := &v1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Annotations: map[string]string{},
},
Spec: v1.NamespaceSpec{},
Status: v1.NamespaceStatus{},
}
return ns
}
func makeNamespaceMap(proxier *Proxier, allNamespaces ...*v1.Namespace) {
for i := range allNamespaces {
proxier.onNamespaceAdded(allNamespaces[i])
}
proxier.mu.Lock()
defer proxier.mu.Unlock()
proxier.namespaceSynced = true
}
func makeNSN(namespace, name string) types.NamespacedName {
return types.NamespacedName{Namespace: namespace, Name: name}
}
func makeServicePortName(ns, name, port string) servicePortName {
return servicePortName{
NamespacedName: makeNSN(ns, name),
Port: port,
}
}
func errorf(msg string, rules []Rule, t *testing.T) {
for _, r := range rules {
t.Logf("%q", r)
}
t.Errorf("%v", msg)
}
func TestClusterNoEndpoint(t *testing.T) {
testNamespace := "test"
svcIP := "1.2.3.4"
svcPort := 80
svcPortName := servicePortName{
NamespacedName: makeNSN(testNamespace, "svc1"),
Port: "80",
}
// Creates fake iptables.
ipt := NewFake()
// Creates fake CRD client.
crdClient, err := crdClient.NewFake()
if err != nil {
t.Fatal("Failed init fake CRD client")
}
// Create a fake openstack client.
osClient := openstack.NewFake(crdClient)
// Injects fake network.
networkName := util.BuildNetworkName(testNamespace, testNamespace)
osClient.SetNetwork(defaultNetwork(networkName, defaultNetworkID))
// Injects fake port.
osClient.SetPort(defaultNetworkID, deviceOwner, defaultPortID)
// Creates a new fake proxier.
fp := NewFakeProxier(ipt, osClient)
makeServiceMap(fp,
makeTestService(svcPortName.Namespace, svcPortName.Name, func(svc *v1.Service) {
svc.Spec.ClusterIP = svcIP
svc.Spec.Type = v1.ServiceTypeNodePort
svc.Spec.Ports = []v1.ServicePort{{
Name: svcPortName.Port,
Port: int32(svcPort),
Protocol: v1.ProtocolTCP,
}}
}),
)
makeEndpointsMap(fp)
makeNamespaceMap(fp, makeTestNamespace(svcPortName.Namespace))
fp.syncProxyRules()
stackubeRules := ipt.GetRules(ChainSKPrerouting, "qrouter-123")
if len(stackubeRules) != 0 {
errorf(fmt.Sprintf("Unexpected rule for chain %v without endpoints in namespace %v", ChainSKPrerouting, svcPortName.Namespace), stackubeRules, t)
}
}
func noClusterIPType(t *testing.T, svcType v1.ServiceType) []Rule {
testNamespace := "test"
svcIP := "1.2.3.4"
svcPort := 80
svcPortName := servicePortName{
NamespacedName: makeNSN(testNamespace, "svc1"),
Port: "80",
}
// Creates fake iptables.
ipt := NewFake()
// Creates fake CRD client.
crdClient, err := crdClient.NewFake()
if err != nil {
t.Fatal("Failed init fake CRD client")
}
// Create a fake openstack client.
osClient := openstack.NewFake(crdClient)
// Injects fake network.
networkName := util.BuildNetworkName(testNamespace, testNamespace)
osClient.SetNetwork(defaultNetwork(networkName, defaultNetworkID))
// Injects fake port.
osClient.SetPort(defaultNetworkID, deviceOwner, defaultPortID)
// Creates a new fake proxier.
fp := NewFakeProxier(ipt, osClient)
makeServiceMap(fp,
makeTestService(svcPortName.Namespace, svcPortName.Namespace, func(svc *v1.Service) {
svc.Spec.ClusterIP = svcIP
svc.Spec.Type = svcType
svc.Spec.Ports = []v1.ServicePort{{
Name: svcPortName.Port,
Port: int32(svcPort),
Protocol: v1.ProtocolTCP,
}}
}),
)
makeEndpointsMap(fp)
makeNamespaceMap(fp, makeTestNamespace(svcPortName.Namespace))
fp.syncProxyRules()
stackubeRules := ipt.GetRules(ChainSKPrerouting, "qrouter-123")
return stackubeRules
}
func TestNoClusterIPType(t *testing.T) {
testCases := map[string]v1.ServiceType{
"case 1": v1.ServiceTypeNodePort,
"case 2": v1.ServiceTypeLoadBalancer,
"case 3": v1.ServiceTypeExternalName,
}
for k, tc := range testCases {
got := noClusterIPType(t, tc)
if len(got) != 0 {
errorf(fmt.Sprintf("%v: unexpected rule for chain %v without ClusterIP service type", k, ChainSKPrerouting), got, t)
}
}
}
func TestClusterIPEndpointsJump(t *testing.T) {
testNamespace := "test"
svcIP := "1.2.3.4"
svcPort := 80
svcPortName := servicePortName{
NamespacedName: makeNSN(testNamespace, "svc1"),
Port: "80",
}
// Creates fake iptables.
ipt := NewFake()
// Creates fake CRD client.
crdClient, err := crdClient.NewFake()
if err != nil {
t.Fatal("Failed init fake CRD client")
}
// Create a fake openstack client.
osClient := openstack.NewFake(crdClient)
// Injects fake network.
networkName := util.BuildNetworkName(testNamespace, testNamespace)
osClient.SetNetwork(defaultNetwork(networkName, defaultNetworkID))
// Injects fake port.
osClient.SetPort(defaultNetworkID, deviceOwner, defaultPortID)
// Creates a new fake proxier.
fp := NewFakeProxier(ipt, osClient)
makeServiceMap(fp,
makeTestService(svcPortName.Namespace, svcPortName.Name, func(svc *v1.Service) {
svc.Spec.ClusterIP = svcIP
svc.Spec.Ports = []v1.ServicePort{{
Name: svcPortName.Port,
Port: int32(svcPort),
Protocol: v1.ProtocolTCP,
}}
}),
)
epIP := "192.168.0.1"
makeEndpointsMap(fp,
makeTestEndpoints(svcPortName.Namespace, svcPortName.Name, func(ept *v1.Endpoints) {
ept.Subsets = []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{
IP: epIP,
}},
Ports: []v1.EndpointPort{{
Name: svcPortName.Port,
Port: int32(svcPort),
}},
}}
}),
)
makeNamespaceMap(fp, makeTestNamespace(svcPortName.Namespace))
fp.syncProxyRules()
epStr := fmt.Sprintf("%s:%d", epIP, svcPort)
stackubeRules := ipt.GetRules(string(ChainSKPrerouting), "qrouter-123")
if len(stackubeRules) == 0 {
errorf(fmt.Sprintf("Unexpected rule for chain %v with endpoints in namespace %v", ChainSKPrerouting, svcPortName.Namespace), stackubeRules, t)
}
if !hasDNAT(stackubeRules, epStr) {
errorf(fmt.Sprintf("Chain %v lacks DNAT to %v", ChainSKPrerouting, epStr), stackubeRules, t)
}
}
func TestMultiNamespacesService(t *testing.T) {
ns1 := "ns1"
svcIP1 := "1.2.3.4"
svcPort1 := 80
svcPortName1 := servicePortName{
NamespacedName: makeNSN(ns1, "svc1"),
Port: "80",
}
ns2 := "ns2"
svcIP2 := "1.2.3.5"
svcPort2 := 8080
svcPortName2 := servicePortName{
NamespacedName: makeNSN(ns2, "svc1"),
Port: "8080",
}
// Creates fake iptables.
ipt := NewFake()
// Creates fake CRD client.
crdClient, err := crdClient.NewFake()
if err != nil {
t.Fatal("Failed init fake CRD client")
}
// Create a fake openstack client.
osClient := openstack.NewFake(crdClient)
// Injects fake network.
networkName1 := util.BuildNetworkName(ns1, ns1)
osClient.SetNetwork(defaultNetwork(networkName1, "123"))
networkName2 := util.BuildNetworkName(ns2, ns2)
osClient.SetNetwork(defaultNetwork(networkName2, "456"))
// Injects fake port.
osClient.SetPort("123", deviceOwner, "123")
osClient.SetPort("456", deviceOwner, "456")
// Creates a new fake proxier.
fp := NewFakeProxier(ipt, osClient)
makeServiceMap(fp,
makeTestService(svcPortName1.Namespace, svcPortName1.Name, func(svc *v1.Service) {
svc.Spec.ClusterIP = svcIP1
svc.Spec.Ports = []v1.ServicePort{{
Name: svcPortName1.Port,
Port: int32(svcPort1),
Protocol: v1.ProtocolTCP,
}}
}),
makeTestService(svcPortName2.Namespace, svcPortName2.Name, func(svc *v1.Service) {
svc.Spec.ClusterIP = svcIP2
svc.Spec.Ports = []v1.ServicePort{{
Name: svcPortName2.Port,
Port: int32(svcPort2),
Protocol: v1.ProtocolTCP,
}}
}),
)
epIP1 := "192.168.0.1"
epIP2 := "192.168.1.1"
makeEndpointsMap(fp,
makeTestEndpoints(svcPortName1.Namespace, svcPortName1.Name, func(ept *v1.Endpoints) {
ept.Subsets = []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{
IP: epIP1,
}},
Ports: []v1.EndpointPort{{
Name: svcPortName1.Port,
Port: int32(svcPort1),
}},
}}
}),
makeTestEndpoints(svcPortName2.Namespace, svcPortName2.Name, func(ept *v1.Endpoints) {
ept.Subsets = []v1.EndpointSubset{{
Addresses: []v1.EndpointAddress{{
IP: epIP2,
}},
Ports: []v1.EndpointPort{{
Name: svcPortName2.Port,
Port: int32(svcPort2),
}},
}}
}),
)
makeNamespaceMap(fp,
makeTestNamespace(svcPortName1.Namespace),
makeTestNamespace(svcPortName2.Namespace),
)
fp.syncProxyRules()
epStr1 := fmt.Sprintf("%s:%d", epIP1, svcPort1)
ns1Rules := ipt.GetRules(string(ChainSKPrerouting), "qrouter-123")
if len(ns1Rules) == 0 {
errorf(fmt.Sprintf("Unexpected rule for chain %v with endpoints in namespace %v", ChainSKPrerouting, svcPortName1.Namespace), ns1Rules, t)
}
if !hasDNAT(ns1Rules, epStr1) {
errorf(fmt.Sprintf("Chain %v lacks DNAT to %v", ChainSKPrerouting, epStr1), ns1Rules, t)
}
epStr2 := fmt.Sprintf("%s:%d", epIP2, svcPort2)
ns2Rules := ipt.GetRules(string(ChainSKPrerouting), "qrouter-456")
if len(ns2Rules) == 0 {
errorf(fmt.Sprintf("Unexpected rule for chain %v with endpoints in namespace %v", ChainSKPrerouting, svcPortName2.Namespace), ns2Rules, t)
}
if !hasDNAT(ns2Rules, epStr2) {
errorf(fmt.Sprintf("Chain %v lacks DNAT to %v", ChainSKPrerouting, epStr2), ns2Rules, t)
}
}
// This is a coarse test, but it offers some modicum of confidence as the code is evolved.
func Test_endpointsToEndpointsMap(t *testing.T) {
testCases := []struct {
newEndpoints *v1.Endpoints
expected map[servicePortName][]*endpointsInfo
}{{
// Case[0]: nothing
newEndpoints: makeTestEndpoints("ns1", "ep1", func(ept *v1.Endpoints) {}),
expected: map[servicePortName][]*endpointsInfo{},
}, {
// Case[1]: no changes, unnamed port
newEndpoints: makeTestEndpoints("ns1", "ep1", func(ept *v1.Endpoints) {
ept.Subsets = []v1.EndpointSubset{
{
Addresses: []v1.EndpointAddress{{
IP: "1.1.1.1",
}},
Ports: []v1.EndpointPort{{
Name: "",
Port: 11,
}},
},
}
}),
expected: map[servicePortName][]*endpointsInfo{
makeServicePortName("ns1", "ep1", ""): {
{endpoint: "1.1.1.1:11", isLocal: false},
},
},
}, {
// Case[2]: no changes, named port
newEndpoints: makeTestEndpoints("ns1", "ep1", func(ept *v1.Endpoints) {
ept.Subsets = []v1.EndpointSubset{
{
Addresses: []v1.EndpointAddress{{
IP: "1.1.1.1",
}},
Ports: []v1.EndpointPort{{
Name: "port",
Port: 11,
}},
},
}
}),
expected: map[servicePortName][]*endpointsInfo{
makeServicePortName("ns1", "ep1", "port"): {
{endpoint: "1.1.1.1:11", isLocal: false},
},
},
}, {
// Case[3]: new port
newEndpoints: makeTestEndpoints("ns1", "ep1", func(ept *v1.Endpoints) {
ept.Subsets = []v1.EndpointSubset{
{
Addresses: []v1.EndpointAddress{{
IP: "1.1.1.1",
}},
Ports: []v1.EndpointPort{{
Port: 11,
}},
},
}
}),
expected: map[servicePortName][]*endpointsInfo{
makeServicePortName("ns1", "ep1", ""): {
{endpoint: "1.1.1.1:11", isLocal: false},
},
},
}, {
// Case[4]: remove port
newEndpoints: makeTestEndpoints("ns1", "ep1", func(ept *v1.Endpoints) {}),
expected: map[servicePortName][]*endpointsInfo{},
}, {
// Case[5]: new IP and port
newEndpoints: makeTestEndpoints("ns1", "ep1", func(ept *v1.Endpoints) {
ept.Subsets = []v1.EndpointSubset{
{
Addresses: []v1.EndpointAddress{{
IP: "1.1.1.1",
}, {
IP: "2.2.2.2",
}},
Ports: []v1.EndpointPort{{
Name: "p1",
Port: 11,
}, {
Name: "p2",
Port: 22,
}},
},
}
}),
expected: map[servicePortName][]*endpointsInfo{
makeServicePortName("ns1", "ep1", "p1"): {
{endpoint: "1.1.1.1:11", isLocal: false},
{endpoint: "2.2.2.2:11", isLocal: false},
},
makeServicePortName("ns1", "ep1", "p2"): {
{endpoint: "1.1.1.1:22", isLocal: false},
{endpoint: "2.2.2.2:22", isLocal: false},
},
},
}, {
// Case[6]: remove IP and port
newEndpoints: makeTestEndpoints("ns1", "ep1", func(ept *v1.Endpoints) {
ept.Subsets = []v1.EndpointSubset{
{
Addresses: []v1.EndpointAddress{{
IP: "1.1.1.1",
}},
Ports: []v1.EndpointPort{{
Name: "p1",
Port: 11,
}},
},
}
}),
expected: map[servicePortName][]*endpointsInfo{
makeServicePortName("ns1", "ep1", "p1"): {
{endpoint: "1.1.1.1:11", isLocal: false},
},
},
}, {
// Case[7]: rename port
newEndpoints: makeTestEndpoints("ns1", "ep1", func(ept *v1.Endpoints) {
ept.Subsets = []v1.EndpointSubset{
{
Addresses: []v1.EndpointAddress{{
IP: "1.1.1.1",
}},
Ports: []v1.EndpointPort{{
Name: "p2",
Port: 11,
}},
},
}
}),
expected: map[servicePortName][]*endpointsInfo{
makeServicePortName("ns1", "ep1", "p2"): {
{endpoint: "1.1.1.1:11", isLocal: false},
},
},
}, {
// Case[8]: renumber port
newEndpoints: makeTestEndpoints("ns1", "ep1", func(ept *v1.Endpoints) {
ept.Subsets = []v1.EndpointSubset{
{
Addresses: []v1.EndpointAddress{{
IP: "1.1.1.1",
}},
Ports: []v1.EndpointPort{{
Name: "p1",
Port: 22,
}},
},
}
}),
expected: map[servicePortName][]*endpointsInfo{
makeServicePortName("ns1", "ep1", "p1"): {
{endpoint: "1.1.1.1:22", isLocal: false},
},
},
}}
for tci, tc := range testCases {
// outputs
newEndpoints := endpointsToEndpointsMap(tc.newEndpoints, "host")
if len(newEndpoints) != len(tc.expected) {
t.Errorf("[%d] expected %d new, got %d: %v", tci, len(tc.expected), len(newEndpoints), spew.Sdump(newEndpoints))
}
for x := range tc.expected {
if len(newEndpoints[x]) != len(tc.expected[x]) {
t.Errorf("[%d] expected %d endpoints for %v, got %d", tci, len(tc.expected[x]), x, len(newEndpoints[x]))
} else {
for i := range newEndpoints[x] {
if *(newEndpoints[x][i]) != *(tc.expected[x][i]) {
t.Errorf("[%d] expected new[%v][%d] to be %v, got %v", tci, x, i, tc.expected[x][i], *(newEndpoints[x][i]))
}
}
}
}
}
}