Add fake files

including
* add fake openstack client framework.
* add fake iptables.
* add proxier unit tests.

Change-Id: I8e47ecd33a103ac736e0619e35cfe59cca8ef2e2
Implements: blueprint enhance-unit-testing
Signed-off-by: mozhuli <21621232@zju.edu.cn>
This commit is contained in:
mozhuli 2017-08-22 21:33:47 +08:00
parent 7e2fc21c2c
commit 1f950f63f0
8 changed files with 1164 additions and 22 deletions

View File

@ -75,7 +75,7 @@ func (os *OpenStack) getNetworkIDByNamespace(namespace string) (string, error) {
// Only support one network and network's name is same with namespace.
// TODO: make it general after multi-network is supported.
networkName := util.BuildNetworkName(namespace, namespace)
network, err := os.Client.GetNetwork(networkName)
network, err := os.Client.GetNetworkByName(networkName)
if err != nil {
glog.Errorf("Get network by name %q failed: %v", networkName, err)
return "", err

View File

@ -82,8 +82,8 @@ func (c *NetworkController) addNetworkToDriver(kubeNetwork *crv1.Network) error
glog.V(4).Infof("[NetworkController]: adding network %s", driverNetwork.Name)
// Check if tenant id exist
check, err := c.driver.CheckTenantID(driverNetwork.TenantID)
// Check if tenant exist or not by tenantID.
check, err := c.driver.CheckTenantByID(driverNetwork.TenantID)
if err != nil {
glog.Errorf("[NetworkController]: check tenantID failed: %v", err)
return err
@ -107,7 +107,7 @@ func (c *NetworkController) addNetworkToDriver(kubeNetwork *crv1.Network) error
newNetworkStatus = crv1.NetworkFailed
} else {
// Check if provider network has already created
_, err := c.driver.GetNetwork(networkName)
_, err := c.driver.GetNetworkByName(networkName)
if err == nil {
glog.Infof("[NetworkController]: network %s has already created", networkName)
} else if err.Error() == util.ErrNotFound.Error() {

View File

@ -71,8 +71,8 @@ type Interface interface {
DeleteTenant(tenantName string) error
// GetTenantIDFromName gets tenantID by tenantName.
GetTenantIDFromName(tenantName string) (string, error)
// CheckTenantID checks tenant exist or not.
CheckTenantID(tenantID string) (bool, error)
// CheckTenantByID checks tenant exist or not by tenantID.
CheckTenantByID(tenantID string) (bool, error)
// CreateUser creates user with username, password in the tenant.
CreateUser(username, password, tenantID string) error
// DeleteAllUsersOnTenant deletes all users on the tenant.
@ -81,8 +81,8 @@ type Interface interface {
CreateNetwork(network *drivertypes.Network) error
// GetNetworkByID gets network by networkID.
GetNetworkByID(networkID string) (*drivertypes.Network, error)
// GetNetwork gets networks by networkName.
GetNetwork(networkName string) (*drivertypes.Network, error)
// GetNetworkByName gets network by networkName.
GetNetworkByName(networkName string) (*drivertypes.Network, error)
// DeleteNetwork deletes network by networkName.
DeleteNetwork(networkName string) error
// GetProviderSubnet gets provider subnet by id
@ -235,6 +235,7 @@ func (os *Client) GetIntegrationBridge() string {
return os.IntegrationBridge
}
// GetTenantIDFromName gets tenantID by tenantName.
func (os *Client) GetTenantIDFromName(tenantName string) (string, error) {
if util.IsSystemNamespace(tenantName) {
tenantName = util.SystemTenant
@ -276,6 +277,7 @@ func (os *Client) GetTenantIDFromName(tenantName string) (string, error) {
return tenantID, nil
}
// CreateTenant creates tenant by tenantname.
func (os *Client) CreateTenant(tenantName string) (string, error) {
createOpts := tenants.CreateOpts{
Name: tenantName,
@ -296,6 +298,7 @@ func (os *Client) CreateTenant(tenantName string) (string, error) {
return tenantID, nil
}
// DeleteTenant deletes tenant by tenantName.
func (os *Client) DeleteTenant(tenantName string) error {
return tenants.List(os.Identity, nil).EachPage(func(page pagination.Page) (bool, error) {
tenantList, err := tenants.ExtractTenants(page)
@ -317,6 +320,7 @@ func (os *Client) DeleteTenant(tenantName string) error {
})
}
// CreateUser creates user with username, password in the tenant.
func (os *Client) CreateUser(username, password, tenantID string) error {
opts := users.CreateOpts{
Name: username,
@ -333,6 +337,7 @@ func (os *Client) CreateUser(username, password, tenantID string) error {
return nil
}
// DeleteAllUsersOnTenant deletes all users on the tenant.
func (os *Client) DeleteAllUsersOnTenant(tenantName string) error {
tenantID, err := os.GetTenantIDFromName(tenantName)
if err != nil {
@ -368,7 +373,7 @@ func reasonForError(err error) int {
return 0
}
// Get tenant's network by tenantID(tenant and network are one to one mapping in stackube)
// GetOpenStackNetworkByTenantID gets tenant's network by tenantID(tenant and network are one to one mapping in stackube)
func (os *Client) GetOpenStackNetworkByTenantID(tenantID string) (*networks.Network, error) {
opts := networks.ListOpts{TenantID: tenantID}
return os.getOpenStackNetwork(&opts)
@ -410,7 +415,7 @@ func (os *Client) getOpenStackNetwork(opts *networks.ListOpts) (*networks.Networ
return osNetwork, err
}
// Get provider subnet by id
// GetProviderSubnet gets provider subnet by subnetID
func (os *Client) GetProviderSubnet(osSubnetID string) (*drivertypes.Subnet, error) {
s, err := subnets.Get(os.Network, osSubnetID).Extract()
if err != nil {
@ -439,7 +444,7 @@ func (os *Client) GetProviderSubnet(osSubnetID string) (*drivertypes.Subnet, err
return &providerSubnet, nil
}
// Get network by networkID
// GetNetworkByID gets network by networkID
func (os *Client) GetNetworkByID(networkID string) (*drivertypes.Network, error) {
osNetwork, err := os.getOpenStackNetworkByID(networkID)
if err != nil {
@ -450,8 +455,8 @@ func (os *Client) GetNetworkByID(networkID string) (*drivertypes.Network, error)
return os.OSNetworktoProviderNetwork(osNetwork)
}
// Get network by networkName
func (os *Client) GetNetwork(networkName string) (*drivertypes.Network, error) {
// GetNetworkByName gets network by networkName
func (os *Client) GetNetworkByName(networkName string) (*drivertypes.Network, error) {
osNetwork, err := os.getOpenStackNetworkByName(networkName)
if err != nil {
glog.Warningf("try to fetch openstack network by name: %v but failed: %v", networkName, err)
@ -495,7 +500,7 @@ func (os *Client) ToProviderStatus(status string) string {
}
}
// Create network
// CreateNetwork creates network.
func (os *Client) CreateNetwork(network *drivertypes.Network) error {
if len(network.Subnets) == 0 {
return errors.New("Subnets is null")
@ -573,7 +578,7 @@ func (os *Client) CreateNetwork(network *drivertypes.Network) error {
return nil
}
// Update network
// UpdateNetwork updates network.
func (os *Client) UpdateNetwork(network *drivertypes.Network) error {
// TODO: update network subnets
return nil
@ -601,7 +606,7 @@ func (os *Client) getRouterByName(name string) (*routers.Router, error) {
return result, nil
}
// Delete network by networkName
// DeleteNetwork deletes network by networkName.
func (os *Client) DeleteNetwork(networkName string) error {
osNetwork, err := os.getOpenStackNetworkByName(networkName)
if err != nil {
@ -681,8 +686,8 @@ func (os *Client) DeleteNetwork(networkName string) error {
return nil
}
// Check the tenant id exist
func (os *Client) CheckTenantID(tenantID string) (bool, error) {
// CheckTenantByID checks tenant exist or not by tenantID.
func (os *Client) CheckTenantByID(tenantID string) (bool, error) {
opts := tenants.ListOpts{}
pager := tenants.List(os.Identity, &opts)
@ -710,6 +715,7 @@ func (os *Client) CheckTenantID(tenantID string) (bool, error) {
return found, err
}
// GetPort gets port by portName.
func (os *Client) GetPort(name string) (*ports.Port, error) {
opts := ports.ListOpts{Name: name}
pager := ports.List(os.Network, opts)
@ -831,7 +837,7 @@ func (os *Client) ensureSecurityGroup(tenantID string) (string, error) {
return securitygroup.ID, nil
}
// Create an port
// CreatePort creates port by neworkID, tenantID and portName.
func (os *Client) CreatePort(networkID, tenantID, portName string) (*portsbinding.Port, error) {
securitygroup, err := os.ensureSecurityGroup(tenantID)
if err != nil {
@ -860,7 +866,7 @@ func (os *Client) CreatePort(networkID, tenantID, portName string) (*portsbindin
return port, nil
}
// List all ports in the network
// ListPorts lists ports by networkID and deviceOwner.
func (os *Client) ListPorts(networkID, deviceOwner string) ([]ports.Port, error) {
var results []ports.Port
opts := ports.ListOpts{

View File

@ -0,0 +1,400 @@
/*
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 openstack
import (
"crypto/sha1"
"errors"
"fmt"
"io"
crv1 "git.openstack.org/openstack/stackube/pkg/apis/v1"
crdClient "git.openstack.org/openstack/stackube/pkg/kubecrd"
drivertypes "git.openstack.org/openstack/stackube/pkg/openstack/types"
"git.openstack.org/openstack/stackube/pkg/util"
"github.com/gophercloud/gophercloud/openstack/identity/v2/tenants"
"github.com/gophercloud/gophercloud/openstack/identity/v2/users"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/portsbinding"
"github.com/gophercloud/gophercloud/openstack/networking/v2/ports"
"github.com/gophercloud/gophercloud/openstack/networking/v2/subnets"
)
var ErrAlreadyExist = errors.New("AlreadyExist")
// FakeOSClient is a simple fake openstack client, so that stackube
// can be run for testing without requiring a real openstack setup.
type FakeOSClient struct {
Tenants map[string]*tenants.Tenant
Users map[string]*users.User
Networks map[string]*drivertypes.Network
Subnets map[string]*subnets.Subnet
Routers map[string]*routers.Router
Ports map[string][]ports.Port
// TODO(mozhuli): Add fakeCRDClient.
CRDClient *crdClient.CRDClient
PluginName string
IntegrationBridge string
}
var _ = Interface(&FakeOSClient{})
// NewFake creates a new FakeOSClient.
func NewFake() *FakeOSClient {
return &FakeOSClient{
Tenants: make(map[string]*tenants.Tenant),
Users: make(map[string]*users.User),
Networks: make(map[string]*drivertypes.Network),
Subnets: make(map[string]*subnets.Subnet),
Routers: make(map[string]*routers.Router),
Ports: make(map[string][]ports.Port),
PluginName: "ovs",
IntegrationBridge: "bi-int",
}
}
// SetTenant injects fake tenant.
func (os *FakeOSClient) SetTenant(tenantName, tenantID string) {
tenant := &tenants.Tenant{
Name: tenantName,
ID: tenantID,
}
os.Tenants[tenantName] = tenant
}
// SetUser injects fake user.
func (os *FakeOSClient) SetUser(userName, userID, tenantID string) {
user := &users.User{
Username: userName,
ID: userID,
TenantID: tenantID,
}
os.Users[tenantID] = user
}
// SetNetwork injects fake network.
func (os *FakeOSClient) SetNetwork(networkName, networkID string) {
network := &drivertypes.Network{
Name: networkName,
Uid: networkID,
}
os.Networks[networkName] = network
}
// SetPort injects fake port.
func (os *FakeOSClient) SetPort(networkID, deviceOwner, deviceID string) {
netPorts, ok := os.Ports[networkID]
p := ports.Port{
NetworkID: networkID,
DeviceOwner: deviceOwner,
DeviceID: deviceID,
}
if !ok {
var ps []ports.Port
ps = append(ps, p)
os.Ports[networkID] = ps
}
netPorts = append(netPorts, p)
os.Ports[networkID] = netPorts
}
func tenantIDHash(tenantName string) string {
return idHash(tenantName)
}
func userIDHash(userName, tenantID string) string {
return idHash(userName)
}
func networkIDHash(networkName string) string {
return idHash(networkName)
}
func subnetIDHash(subnetName string) string {
return idHash(subnetName)
}
func routerIDHash(routerName string) string {
return idHash(routerName)
}
func portdeviceIDHash(networkID, deviceOwner string) string {
return idHash(networkID, deviceOwner)
}
func idHash(data ...string) string {
var s string
for _, d := range data {
s += d
}
h := sha1.New()
io.WriteString(h, s)
return fmt.Sprintf("%x", h.Sum(nil))[:16]
}
// CreateTenant creates tenant by tenantname.
func (os *FakeOSClient) CreateTenant(tenantName string) (string, error) {
if t, ok := os.Tenants[tenantName]; ok {
return t.ID, nil
}
tenant := &tenants.Tenant{
Name: tenantName,
ID: tenantIDHash(tenantName),
}
os.Tenants[tenantName] = tenant
return tenant.ID, nil
}
// DeleteTenant deletes tenant by tenantName.
func (os *FakeOSClient) DeleteTenant(tenantName string) error {
delete(os.Tenants, tenantName)
return nil
}
// GetTenantIDFromName gets tenantID by tenantName.
func (os *FakeOSClient) GetTenantIDFromName(tenantName string) (string, error) {
if util.IsSystemNamespace(tenantName) {
tenantName = util.SystemTenant
}
// If tenantID is specified, return it directly
var (
tenant *crv1.Tenant
err error
)
// TODO(mozhuli): use fakeCRDClient.
if tenant, err = os.CRDClient.GetTenant(tenantName); err != nil {
return "", err
}
if tenant.Spec.TenantID != "" {
return tenant.Spec.TenantID, nil
}
t, ok := os.Tenants[tenantName]
if !ok {
return "", nil
}
return t.ID, nil
}
// CheckTenantByID checks tenant exist or not by tenantID.
func (os *FakeOSClient) CheckTenantByID(tenantID string) (bool, error) {
for _, tenent := range os.Tenants {
if tenent.ID == tenantID {
return true, nil
}
}
return false, ErrNotFound
}
// CreateUser creates user with username, password in the tenant.
func (os *FakeOSClient) CreateUser(username, password, tenantID string) error {
user := &users.User{
Name: username,
TenantID: tenantID,
ID: userIDHash(username, tenantID),
}
os.Users[tenantID] = user
return nil
}
// DeleteAllUsersOnTenant deletes all users on the tenant.
func (os *FakeOSClient) DeleteAllUsersOnTenant(tenantName string) error {
tenant := os.Tenants[tenantName]
delete(os.Users, tenant.ID)
return nil
}
func (os *FakeOSClient) createNetwork(networkName, tenantID string) error {
if _, ok := os.Networks[networkName]; ok {
return ErrAlreadyExist
}
network := &drivertypes.Network{
Name: networkName,
Uid: networkIDHash(networkName),
TenantID: tenantID,
}
os.Networks[networkName] = network
return nil
}
func (os *FakeOSClient) deleteNetwork(networkName string) error {
delete(os.Networks, networkName)
return nil
}
func (os *FakeOSClient) createRouter(routerName, tenantID string) error {
if _, ok := os.Routers[routerName]; ok {
return ErrAlreadyExist
}
router := &routers.Router{
Name: routerName,
TenantID: tenantID,
ID: routerIDHash(routerName),
}
os.Routers[routerName] = router
return nil
}
func (os *FakeOSClient) deleteRouter(routerName string) error {
delete(os.Routers, routerName)
return nil
}
func (os *FakeOSClient) createSubnet(subnetName, networkID, tenantID string) error {
if _, ok := os.Subnets[subnetName]; ok {
return ErrAlreadyExist
}
subnet := &subnets.Subnet{
Name: subnetName,
TenantID: tenantID,
NetworkID: networkID,
ID: subnetIDHash(subnetName),
}
os.Subnets[subnetName] = subnet
return nil
}
// CreateNetwork creates network.
// TODO(mozhuli): make it more general.
func (os *FakeOSClient) CreateNetwork(network *drivertypes.Network) error {
if len(network.Subnets) == 0 {
return errors.New("Subnets is null")
}
// create network
err := os.createNetwork(network.Name, network.TenantID)
if err != nil {
return errors.New("Create network failed")
}
// create router, and use network name as router name for convenience.
err = os.createRouter(network.Name, network.TenantID)
if err != nil {
os.deleteNetwork(network.Name)
return errors.New("Create router failed")
}
// create subnets and connect them to router
err = os.createSubnet(network.Subnets[0].Name, network.Uid, network.TenantID)
if err != nil {
os.deleteRouter(network.Name)
os.deleteNetwork(network.Name)
return errors.New("Create subnet failed")
}
return nil
}
// GetNetworkByID gets network by networkID.
func (os *FakeOSClient) GetNetworkByID(networkID string) (*drivertypes.Network, error) {
return nil, nil
}
// GetNetworkByName get network by networkName
func (os *FakeOSClient) GetNetworkByName(networkName string) (*drivertypes.Network, error) {
network, ok := os.Networks[networkName]
if !ok {
return nil, ErrNotFound
}
return network, nil
}
// DeleteNetwork deletes network by networkName.
func (os *FakeOSClient) DeleteNetwork(networkName string) error {
return nil
}
// GetProviderSubnet gets provider subnet by id
func (os *FakeOSClient) GetProviderSubnet(osSubnetID string) (*drivertypes.Subnet, error) {
return nil, nil
}
// CreatePort creates port by neworkID, tenantID and portName.
func (os *FakeOSClient) CreatePort(networkID, tenantID, portName string) (*portsbinding.Port, error) {
return nil, nil
}
// GetPort gets port by portName.
func (os *FakeOSClient) GetPort(name string) (*ports.Port, error) {
return nil, nil
}
// ListPorts list all ports which have the deviceOwner in the network.
func (os *FakeOSClient) ListPorts(networkID, deviceOwner string) ([]ports.Port, error) {
var results []ports.Port
portList, ok := os.Ports[networkID]
if !ok {
return results, nil
}
for _, port := range portList {
if port.DeviceOwner == deviceOwner {
results = append(results, port)
}
}
return results, nil
}
// DeletePortByName deletes port by portName.
func (os *FakeOSClient) DeletePortByName(portName string) error {
return nil
}
// DeletePortByID deletes port by portID.
func (os *FakeOSClient) DeletePortByID(portID string) error {
return nil
}
// UpdatePortsBinding updates port binding.
func (os *FakeOSClient) UpdatePortsBinding(portID, deviceOwner string) error {
return nil
}
// LoadBalancerExist returns whether a load balancer has already been exist.
func (os *FakeOSClient) LoadBalancerExist(name string) (bool, error) {
return true, nil
}
// EnsureLoadBalancer ensures a load balancer is created.
func (os *FakeOSClient) EnsureLoadBalancer(lb *LoadBalancer) (*LoadBalancerStatus, error) {
return nil, nil
}
// EnsureLoadBalancerDeleted ensures a load balancer is deleted.
func (os *FakeOSClient) EnsureLoadBalancerDeleted(name string) error {
return nil
}
// GetCRDClient returns the CRDClient.
// TODO(mozhuli): use fakeCRDClient.
func (os *FakeOSClient) GetCRDClient() *crdClient.CRDClient {
return os.CRDClient
}
// GetPluginName returns the plugin name.
func (os *FakeOSClient) GetPluginName() string {
return os.PluginName
}
// GetIntegrationBridge returns the integration bridge name.
func (os *FakeOSClient) GetIntegrationBridge() string {
return os.IntegrationBridge
}

View File

@ -0,0 +1,99 @@
/*
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"
"strings"
)
const (
Destination = "-d "
Source = "-s "
DPort = "--dport "
Protocol = "-p "
Jump = "-j "
ToDest = "--to-destination "
)
// Rule represents chain's rule.
type Rule map[string]string
// FakeIPTables have noop implementation of fake iptables function.
type FakeIPTables struct {
namespace string
NSLines map[string][]byte
}
// NewFake return new FakeIPTables.
func NewFake() *FakeIPTables {
return &FakeIPTables{
NSLines: make(map[string][]byte),
}
}
func (f *FakeIPTables) ensureChain() error {
return nil
}
func (f *FakeIPTables) ensureRule(op, chain string, args []string) error {
return nil
}
func (f *FakeIPTables) restoreAll(data []byte) error {
d := make([]byte, len(data))
copy(d, data)
f.NSLines[f.namespace] = d
return nil
}
func (f *FakeIPTables) netnsExist() bool {
return true
}
func (f *FakeIPTables) setNetns(netns string) {
f.namespace = netns
}
func getToken(line, seperator string) string {
tokens := strings.Split(line, seperator)
if len(tokens) == 2 {
return strings.Split(tokens[1], " ")[0]
}
return ""
}
// GetRules returns a list of rules for the given chain.
// The chain name must match exactly.
// The matching is pretty dumb, don't rely on it for anything but testing.
func (f *FakeIPTables) GetRules(chainName, namespace string) (rules []Rule) {
for _, l := range strings.Split(string(f.NSLines[namespace]), "\n") {
if strings.Contains(l, fmt.Sprintf("-A %v", chainName)) {
newRule := Rule(map[string]string{})
for _, arg := range []string{Destination, Source, DPort, Protocol, Jump, ToDest} {
tok := getToken(l, arg)
if tok != "" {
newRule[arg] = tok
}
}
rules = append(rules, newRule)
}
}
return
}
var _ = iptablesInterface(&FakeIPTables{})

View File

@ -201,7 +201,7 @@ func (p *Proxier) getRouterForNamespace(namespace string) (string, error) {
// Only support one network and network's name is same with namespace.
// TODO: make it general after multi-network is supported.
networkName := util.BuildNetworkName(namespace, namespace)
network, err := p.osClient.GetNetwork(networkName)
network, err := p.osClient.GetNetworkByName(networkName)
if err != nil {
glog.Errorf("Get network by name %q failed: %v", networkName, err)
return "", err

637
pkg/proxy/proxier_test.go Normal file
View File

@ -0,0 +1,637 @@
/*
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"
"git.openstack.org/openstack/stackube/pkg/openstack"
"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"
)
func newFakeServiceInfo(serviceName string, ip net.IP) *serviceInfo {
return &serviceInfo{
name: serviceName,
clusterIP: ip,
}
}
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()
//Create a fake openstack client.
osClient := openstack.NewFake()
// Injects fake network.
networkName := util.BuildNetworkName(testNamespace, testNamespace)
osClient.SetNetwork(networkName, "123")
// Injects fake port.
osClient.SetPort("123", "network:router_interface", "123")
// 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(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()
// Create a fake openstack client.
osClient := openstack.NewFake()
// Injects fake network.
networkName := util.BuildNetworkName(testNamespace, testNamespace)
osClient.SetNetwork(networkName, "123")
// Injects fake port.
osClient.SetPort("123", "network:router_interface", "123")
// 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(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()
// Create a fake openstack client.
osClient := openstack.NewFake()
// Injects fake network.
networkName := util.BuildNetworkName(testNamespace, testNamespace)
osClient.SetNetwork(networkName, "123")
// Injects fake port.
osClient.SetPort("123", "network:router_interface", "123")
// 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()
// Create a fake openstack client.
osClient := openstack.NewFake()
// Injects fake network.
networkName1 := util.BuildNetworkName(ns1, ns1)
osClient.SetNetwork(networkName1, "123")
networkName2 := util.BuildNetworkName(ns2, ns2)
osClient.SetNetwork(networkName2, "456")
// Injects fake port.
osClient.SetPort("123", "network:router_interface", "123")
osClient.SetPort("456", "network:router_interface", "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]))
}
}
}
}
}
}

View File

@ -332,7 +332,7 @@ func (s *ServiceController) createLoadBalancer(service *v1.Service) (*v1.LoadBal
// Only support one network and network's name is same with namespace.
networkName := util.BuildNetworkName(service.Namespace, service.Namespace)
network, err := s.osClient.GetNetwork(networkName)
network, err := s.osClient.GetNetworkByName(networkName)
if err != nil {
glog.Errorf("Get network by name %q failed: %v", networkName, err)
return nil, err