mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
0a99a5f152
Signed-off-by: Jakub Drahos <jack.drahos@gmail.com>
385 lines
8.7 KiB
Go
385 lines
8.7 KiB
Go
// +build linux
|
|
|
|
package ipvs
|
|
|
|
import (
|
|
"net"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/docker/libnetwork/testutils"
|
|
"github.com/vishvananda/netlink"
|
|
"github.com/vishvananda/netlink/nl"
|
|
"golang.org/x/sys/unix"
|
|
"gotest.tools/assert"
|
|
is "gotest.tools/assert/cmp"
|
|
)
|
|
|
|
var (
|
|
schedMethods = []string{
|
|
RoundRobin,
|
|
LeastConnection,
|
|
DestinationHashing,
|
|
SourceHashing,
|
|
WeightedRoundRobin,
|
|
WeightedLeastConnection,
|
|
}
|
|
|
|
protocols = []string{
|
|
"TCP",
|
|
"UDP",
|
|
"FWM",
|
|
}
|
|
|
|
fwdMethods = []uint32{
|
|
ConnectionFlagMasq,
|
|
ConnectionFlagTunnel,
|
|
ConnectionFlagDirectRoute,
|
|
}
|
|
|
|
fwdMethodStrings = []string{
|
|
"Masq",
|
|
"Tunnel",
|
|
"Route",
|
|
}
|
|
)
|
|
|
|
func lookupFwMethod(fwMethod uint32) string {
|
|
|
|
switch fwMethod {
|
|
case ConnectionFlagMasq:
|
|
return fwdMethodStrings[0]
|
|
case ConnectionFlagTunnel:
|
|
return fwdMethodStrings[1]
|
|
case ConnectionFlagDirectRoute:
|
|
return fwdMethodStrings[2]
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func checkDestination(t *testing.T, i *Handle, s *Service, d *Destination, checkPresent bool) {
|
|
var dstFound bool
|
|
|
|
dstArray, err := i.GetDestinations(s)
|
|
assert.NilError(t, err)
|
|
|
|
for _, dst := range dstArray {
|
|
if dst.Address.Equal(d.Address) && dst.Port == d.Port && lookupFwMethod(dst.ConnectionFlags) == lookupFwMethod(d.ConnectionFlags) {
|
|
dstFound = true
|
|
break
|
|
}
|
|
}
|
|
|
|
switch checkPresent {
|
|
case true: //The test expects the service to be present
|
|
if !dstFound {
|
|
|
|
t.Fatalf("Did not find the service %s in ipvs output", d.Address.String())
|
|
}
|
|
case false: //The test expects that the service should not be present
|
|
if dstFound {
|
|
t.Fatalf("Did not find the destination %s fwdMethod %s in ipvs output", d.Address.String(), lookupFwMethod(d.ConnectionFlags))
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func checkService(t *testing.T, i *Handle, s *Service, checkPresent bool) {
|
|
|
|
svcArray, err := i.GetServices()
|
|
assert.NilError(t, err)
|
|
|
|
var svcFound bool
|
|
|
|
for _, svc := range svcArray {
|
|
|
|
if svc.Protocol == s.Protocol && svc.Address.String() == s.Address.String() && svc.Port == s.Port {
|
|
svcFound = true
|
|
break
|
|
}
|
|
}
|
|
|
|
switch checkPresent {
|
|
case true: //The test expects the service to be present
|
|
if !svcFound {
|
|
|
|
t.Fatalf("Did not find the service %s in ipvs output", s.Address.String())
|
|
}
|
|
case false: //The test expects that the service should not be present
|
|
if svcFound {
|
|
t.Fatalf("Did not expect the service %s in ipvs output", s.Address.String())
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func TestGetFamily(t *testing.T) {
|
|
if testutils.RunningOnCircleCI() {
|
|
t.Skip("Skipping as not supported on CIRCLE CI kernel")
|
|
}
|
|
|
|
id, err := getIPVSFamily()
|
|
assert.NilError(t, err)
|
|
assert.Check(t, 0 != id)
|
|
}
|
|
|
|
func TestService(t *testing.T) {
|
|
if testutils.RunningOnCircleCI() {
|
|
t.Skip("Skipping as not supported on CIRCLE CI kernel")
|
|
}
|
|
|
|
defer testutils.SetupTestOSContext(t)()
|
|
|
|
i, err := New("")
|
|
assert.NilError(t, err)
|
|
|
|
for _, protocol := range protocols {
|
|
for _, schedMethod := range schedMethods {
|
|
testDatas := []struct {
|
|
AddressFamily uint16
|
|
IP string
|
|
Netmask uint32
|
|
}{
|
|
{
|
|
AddressFamily: nl.FAMILY_V4,
|
|
IP: "1.2.3.4",
|
|
Netmask: 0xFFFFFFFF,
|
|
}, {
|
|
AddressFamily: nl.FAMILY_V6,
|
|
IP: "2001:db8:3c4d:15::1a00",
|
|
Netmask: 128,
|
|
},
|
|
}
|
|
for _, td := range testDatas {
|
|
s := Service{
|
|
AddressFamily: td.AddressFamily,
|
|
SchedName: schedMethod,
|
|
}
|
|
|
|
switch protocol {
|
|
case "FWM":
|
|
s.FWMark = 1234
|
|
s.Netmask = td.Netmask
|
|
case "TCP":
|
|
s.Protocol = unix.IPPROTO_TCP
|
|
s.Port = 80
|
|
s.Address = net.ParseIP(td.IP)
|
|
s.Netmask = td.Netmask
|
|
case "UDP":
|
|
s.Protocol = unix.IPPROTO_UDP
|
|
s.Port = 53
|
|
s.Address = net.ParseIP(td.IP)
|
|
s.Netmask = td.Netmask
|
|
}
|
|
|
|
err := i.NewService(&s)
|
|
assert.NilError(t, err)
|
|
checkService(t, i, &s, true)
|
|
for _, updateSchedMethod := range schedMethods {
|
|
if updateSchedMethod == schedMethod {
|
|
continue
|
|
}
|
|
|
|
s.SchedName = updateSchedMethod
|
|
err = i.UpdateService(&s)
|
|
assert.NilError(t, err)
|
|
checkService(t, i, &s, true)
|
|
|
|
scopy, err := i.GetService(&s)
|
|
assert.NilError(t, err)
|
|
assert.Check(t, is.Equal((*scopy).Address.String(), s.Address.String()))
|
|
assert.Check(t, is.Equal((*scopy).Port, s.Port))
|
|
assert.Check(t, is.Equal((*scopy).Protocol, s.Protocol))
|
|
}
|
|
|
|
err = i.DelService(&s)
|
|
assert.NilError(t, err)
|
|
checkService(t, i, &s, false)
|
|
}
|
|
}
|
|
}
|
|
|
|
svcs := []Service{
|
|
{
|
|
AddressFamily: nl.FAMILY_V4,
|
|
SchedName: RoundRobin,
|
|
Protocol: unix.IPPROTO_TCP,
|
|
Port: 80,
|
|
Address: net.ParseIP("10.20.30.40"),
|
|
Netmask: 0xFFFFFFFF,
|
|
},
|
|
{
|
|
AddressFamily: nl.FAMILY_V4,
|
|
SchedName: LeastConnection,
|
|
Protocol: unix.IPPROTO_UDP,
|
|
Port: 8080,
|
|
Address: net.ParseIP("10.20.30.41"),
|
|
Netmask: 0xFFFFFFFF,
|
|
},
|
|
}
|
|
// Create services for testing flush
|
|
for _, svc := range svcs {
|
|
if !i.IsServicePresent(&svc) {
|
|
err = i.NewService(&svc)
|
|
assert.NilError(t, err)
|
|
checkService(t, i, &svc, true)
|
|
} else {
|
|
t.Errorf("svc: %v exists", svc)
|
|
}
|
|
}
|
|
err = i.Flush()
|
|
assert.NilError(t, err)
|
|
got, err := i.GetServices()
|
|
assert.NilError(t, err)
|
|
if len(got) != 0 {
|
|
t.Errorf("Unexpected services after flush")
|
|
}
|
|
}
|
|
|
|
func createDummyInterface(t *testing.T) {
|
|
if testutils.RunningOnCircleCI() {
|
|
t.Skip("Skipping as not supported on CIRCLE CI kernel")
|
|
}
|
|
|
|
dummy := &netlink.Dummy{
|
|
LinkAttrs: netlink.LinkAttrs{
|
|
Name: "dummy",
|
|
},
|
|
}
|
|
|
|
err := netlink.LinkAdd(dummy)
|
|
assert.NilError(t, err)
|
|
|
|
dummyLink, err := netlink.LinkByName("dummy")
|
|
assert.NilError(t, err)
|
|
|
|
ip, ipNet, err := net.ParseCIDR("10.1.1.1/24")
|
|
assert.NilError(t, err)
|
|
|
|
ipNet.IP = ip
|
|
|
|
ipAddr := &netlink.Addr{IPNet: ipNet, Label: ""}
|
|
err = netlink.AddrAdd(dummyLink, ipAddr)
|
|
assert.NilError(t, err)
|
|
}
|
|
|
|
func TestDestination(t *testing.T) {
|
|
defer testutils.SetupTestOSContext(t)()
|
|
|
|
createDummyInterface(t)
|
|
i, err := New("")
|
|
assert.NilError(t, err)
|
|
|
|
for _, protocol := range protocols {
|
|
testDatas := []struct {
|
|
AddressFamily uint16
|
|
IP string
|
|
Netmask uint32
|
|
Destinations []string
|
|
}{
|
|
{
|
|
AddressFamily: nl.FAMILY_V4,
|
|
IP: "1.2.3.4",
|
|
Netmask: 0xFFFFFFFF,
|
|
Destinations: []string{"10.1.1.2", "10.1.1.3", "10.1.1.4"},
|
|
}, {
|
|
AddressFamily: nl.FAMILY_V6,
|
|
IP: "2001:db8:3c4d:15::1a00",
|
|
Netmask: 128,
|
|
Destinations: []string{"2001:db8:3c4d:15::1a2b", "2001:db8:3c4d:15::1a2c", "2001:db8:3c4d:15::1a2d"},
|
|
},
|
|
}
|
|
for _, td := range testDatas {
|
|
s := Service{
|
|
AddressFamily: td.AddressFamily,
|
|
SchedName: RoundRobin,
|
|
}
|
|
|
|
switch protocol {
|
|
case "FWM":
|
|
s.FWMark = 1234
|
|
s.Netmask = td.Netmask
|
|
case "TCP":
|
|
s.Protocol = unix.IPPROTO_TCP
|
|
s.Port = 80
|
|
s.Address = net.ParseIP(td.IP)
|
|
s.Netmask = td.Netmask
|
|
case "UDP":
|
|
s.Protocol = unix.IPPROTO_UDP
|
|
s.Port = 53
|
|
s.Address = net.ParseIP(td.IP)
|
|
s.Netmask = td.Netmask
|
|
}
|
|
|
|
err := i.NewService(&s)
|
|
assert.NilError(t, err)
|
|
checkService(t, i, &s, true)
|
|
|
|
s.SchedName = ""
|
|
for _, fwdMethod := range fwdMethods {
|
|
destinations := make([]Destination, 0)
|
|
for _, ip := range td.Destinations {
|
|
d := Destination{
|
|
AddressFamily: td.AddressFamily,
|
|
Address: net.ParseIP(ip),
|
|
Port: 5000,
|
|
Weight: 1,
|
|
ConnectionFlags: fwdMethod,
|
|
}
|
|
destinations = append(destinations, d)
|
|
err := i.NewDestination(&s, &d)
|
|
assert.NilError(t, err)
|
|
checkDestination(t, i, &s, &d, true)
|
|
}
|
|
|
|
for _, updateFwdMethod := range fwdMethods {
|
|
if updateFwdMethod == fwdMethod {
|
|
continue
|
|
}
|
|
for _, d := range destinations {
|
|
d.ConnectionFlags = updateFwdMethod
|
|
err = i.UpdateDestination(&s, &d)
|
|
assert.NilError(t, err)
|
|
checkDestination(t, i, &s, &d, true)
|
|
}
|
|
}
|
|
for _, d := range destinations {
|
|
err = i.DelDestination(&s, &d)
|
|
assert.NilError(t, err)
|
|
checkDestination(t, i, &s, &d, false)
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTimeouts(t *testing.T) {
|
|
if testutils.RunningOnCircleCI() {
|
|
t.Skip("Skipping as not supported on CIRCLE CI kernel")
|
|
}
|
|
defer testutils.SetupTestOSContext(t)()
|
|
|
|
i, err := New("")
|
|
assert.NilError(t, err)
|
|
|
|
_, err = i.GetConfig()
|
|
assert.NilError(t, err)
|
|
|
|
cfg := Config{66 * time.Second, 66 * time.Second, 66 * time.Second}
|
|
err = i.SetConfig(&cfg)
|
|
assert.NilError(t, err)
|
|
|
|
c2, err := i.GetConfig()
|
|
assert.NilError(t, err)
|
|
assert.DeepEqual(t, cfg, *c2)
|
|
|
|
// A timeout value 0 means that the current timeout value of the corresponding entry is preserved
|
|
cfg = Config{77 * time.Second, 0 * time.Second, 77 * time.Second}
|
|
err = i.SetConfig(&cfg)
|
|
assert.NilError(t, err)
|
|
|
|
c3, err := i.GetConfig()
|
|
assert.NilError(t, err)
|
|
assert.DeepEqual(t, *c3, Config{77 * time.Second, 66 * time.Second, 77 * time.Second})
|
|
}
|