|
@@ -0,0 +1,321 @@
|
|
|
+// +build linux
|
|
|
+
|
|
|
+package ipvs
|
|
|
+
|
|
|
+import (
|
|
|
+ "fmt"
|
|
|
+ "net"
|
|
|
+ "os/exec"
|
|
|
+ "strings"
|
|
|
+ "syscall"
|
|
|
+ "testing"
|
|
|
+
|
|
|
+ "github.com/docker/libnetwork/testutils"
|
|
|
+ "github.com/stretchr/testify/assert"
|
|
|
+ "github.com/stretchr/testify/require"
|
|
|
+ "github.com/vishvananda/netlink"
|
|
|
+ "github.com/vishvananda/netlink/nl"
|
|
|
+)
|
|
|
+
|
|
|
+var (
|
|
|
+ schedMethods = []string{
|
|
|
+ RoundRobin,
|
|
|
+ LeastConnection,
|
|
|
+ DestinationHashing,
|
|
|
+ SourceHashing,
|
|
|
+ }
|
|
|
+
|
|
|
+ protocols = []string{
|
|
|
+ "TCP",
|
|
|
+ "UDP",
|
|
|
+ "FWM",
|
|
|
+ }
|
|
|
+
|
|
|
+ fwdMethods = []uint32{
|
|
|
+ ConnectionFlagMasq,
|
|
|
+ ConnectionFlagTunnel,
|
|
|
+ ConnectionFlagDirectRoute,
|
|
|
+ }
|
|
|
+
|
|
|
+ fwdMethodStrings = []string{
|
|
|
+ "Masq",
|
|
|
+ "Tunnel",
|
|
|
+ "Route",
|
|
|
+ }
|
|
|
+)
|
|
|
+
|
|
|
+func checkDestination(t *testing.T, checkPresent bool, protocol, serviceAddress, realAddress, fwdMethod string) {
|
|
|
+ var (
|
|
|
+ realServerStart bool
|
|
|
+ realServers []string
|
|
|
+ )
|
|
|
+
|
|
|
+ out, err := exec.Command("ipvsadm", "-Ln").CombinedOutput()
|
|
|
+ require.NoError(t, err)
|
|
|
+
|
|
|
+ for _, o := range strings.Split(string(out), "\n") {
|
|
|
+ cmpStr := serviceAddress
|
|
|
+ if protocol == "FWM" {
|
|
|
+ cmpStr = " " + cmpStr
|
|
|
+ }
|
|
|
+
|
|
|
+ if strings.Contains(o, cmpStr) {
|
|
|
+ realServerStart = true
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ if realServerStart {
|
|
|
+ if !strings.Contains(o, "->") {
|
|
|
+ break
|
|
|
+ }
|
|
|
+
|
|
|
+ realServers = append(realServers, o)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, r := range realServers {
|
|
|
+ if strings.Contains(r, realAddress) {
|
|
|
+ parts := strings.Fields(r)
|
|
|
+ assert.Equal(t, fwdMethod, parts[2])
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if checkPresent {
|
|
|
+ t.Fatalf("Did not find the destination %s fwdMethod %s in ipvs output", realAddress, fwdMethod)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func checkService(t *testing.T, checkPresent bool, protocol, schedMethod, serviceAddress string) {
|
|
|
+ out, err := exec.Command("ipvsadm", "-Ln").CombinedOutput()
|
|
|
+ require.NoError(t, err)
|
|
|
+
|
|
|
+ for _, o := range strings.Split(string(out), "\n") {
|
|
|
+ cmpStr := serviceAddress
|
|
|
+ if protocol == "FWM" {
|
|
|
+ cmpStr = " " + cmpStr
|
|
|
+ }
|
|
|
+
|
|
|
+ if strings.Contains(o, cmpStr) {
|
|
|
+ parts := strings.Split(o, " ")
|
|
|
+ assert.Equal(t, protocol, parts[0])
|
|
|
+ assert.Equal(t, serviceAddress, parts[2])
|
|
|
+ assert.Equal(t, schedMethod, parts[3])
|
|
|
+
|
|
|
+ if !checkPresent {
|
|
|
+ t.Fatalf("Did not expect the service %s in ipvs output", serviceAddress)
|
|
|
+ }
|
|
|
+
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if checkPresent {
|
|
|
+ t.Fatalf("Did not find the service %s in ipvs output", serviceAddress)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestGetFamily(t *testing.T) {
|
|
|
+ if testutils.RunningOnCircleCI() {
|
|
|
+ t.Skipf("Skipping as not supported on CIRCLE CI kernel")
|
|
|
+ }
|
|
|
+
|
|
|
+ id, err := getIPVSFamily()
|
|
|
+ require.NoError(t, err)
|
|
|
+ assert.NotEqual(t, 0, id)
|
|
|
+}
|
|
|
+
|
|
|
+func TestService(t *testing.T) {
|
|
|
+ if testutils.RunningOnCircleCI() {
|
|
|
+ t.Skipf("Skipping as not supported on CIRCLE CI kernel")
|
|
|
+ }
|
|
|
+
|
|
|
+ defer testutils.SetupTestOSContext(t)()
|
|
|
+
|
|
|
+ i, err := New("")
|
|
|
+ require.NoError(t, err)
|
|
|
+
|
|
|
+ for _, protocol := range protocols {
|
|
|
+ for _, schedMethod := range schedMethods {
|
|
|
+ var serviceAddress string
|
|
|
+
|
|
|
+ s := Service{
|
|
|
+ AddressFamily: nl.FAMILY_V4,
|
|
|
+ SchedName: schedMethod,
|
|
|
+ }
|
|
|
+
|
|
|
+ switch protocol {
|
|
|
+ case "FWM":
|
|
|
+ s.FWMark = 1234
|
|
|
+ serviceAddress = fmt.Sprintf("%d", 1234)
|
|
|
+ case "TCP":
|
|
|
+ s.Protocol = syscall.IPPROTO_TCP
|
|
|
+ s.Port = 80
|
|
|
+ s.Address = net.ParseIP("1.2.3.4")
|
|
|
+ s.Netmask = 0xFFFFFFFF
|
|
|
+ serviceAddress = "1.2.3.4:80"
|
|
|
+ case "UDP":
|
|
|
+ s.Protocol = syscall.IPPROTO_UDP
|
|
|
+ s.Port = 53
|
|
|
+ s.Address = net.ParseIP("2.3.4.5")
|
|
|
+ serviceAddress = "2.3.4.5:53"
|
|
|
+ }
|
|
|
+
|
|
|
+ err := i.NewService(&s)
|
|
|
+ assert.NoError(t, err)
|
|
|
+ checkService(t, true, protocol, schedMethod, serviceAddress)
|
|
|
+ var lastMethod string
|
|
|
+ for _, updateSchedMethod := range schedMethods {
|
|
|
+ if updateSchedMethod == schedMethod {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ s.SchedName = updateSchedMethod
|
|
|
+ err = i.UpdateService(&s)
|
|
|
+ assert.NoError(t, err)
|
|
|
+ checkService(t, true, protocol, updateSchedMethod, serviceAddress)
|
|
|
+ lastMethod = updateSchedMethod
|
|
|
+ }
|
|
|
+
|
|
|
+ err = i.DelService(&s)
|
|
|
+ checkService(t, false, protocol, lastMethod, serviceAddress)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+func createDummyInterface(t *testing.T) {
|
|
|
+ if testutils.RunningOnCircleCI() {
|
|
|
+ t.Skipf("Skipping as not supported on CIRCLE CI kernel")
|
|
|
+ }
|
|
|
+
|
|
|
+ dummy := &netlink.Dummy{
|
|
|
+ LinkAttrs: netlink.LinkAttrs{
|
|
|
+ Name: "dummy",
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ err := netlink.LinkAdd(dummy)
|
|
|
+ require.NoError(t, err)
|
|
|
+
|
|
|
+ dummyLink, err := netlink.LinkByName("dummy")
|
|
|
+ require.NoError(t, err)
|
|
|
+
|
|
|
+ ip, ipNet, err := net.ParseCIDR("10.1.1.1/24")
|
|
|
+ require.NoError(t, err)
|
|
|
+
|
|
|
+ ipNet.IP = ip
|
|
|
+
|
|
|
+ ipAddr := &netlink.Addr{IPNet: ipNet, Label: ""}
|
|
|
+ err = netlink.AddrAdd(dummyLink, ipAddr)
|
|
|
+ require.NoError(t, err)
|
|
|
+}
|
|
|
+
|
|
|
+func TestDestination(t *testing.T) {
|
|
|
+ defer testutils.SetupTestOSContext(t)()
|
|
|
+
|
|
|
+ createDummyInterface(t)
|
|
|
+ i, err := New("")
|
|
|
+ require.NoError(t, err)
|
|
|
+
|
|
|
+ for _, protocol := range []string{"TCP"} {
|
|
|
+ var serviceAddress string
|
|
|
+
|
|
|
+ s := Service{
|
|
|
+ AddressFamily: nl.FAMILY_V4,
|
|
|
+ SchedName: RoundRobin,
|
|
|
+ }
|
|
|
+
|
|
|
+ switch protocol {
|
|
|
+ case "FWM":
|
|
|
+ s.FWMark = 1234
|
|
|
+ serviceAddress = fmt.Sprintf("%d", 1234)
|
|
|
+ case "TCP":
|
|
|
+ s.Protocol = syscall.IPPROTO_TCP
|
|
|
+ s.Port = 80
|
|
|
+ s.Address = net.ParseIP("1.2.3.4")
|
|
|
+ s.Netmask = 0xFFFFFFFF
|
|
|
+ serviceAddress = "1.2.3.4:80"
|
|
|
+ case "UDP":
|
|
|
+ s.Protocol = syscall.IPPROTO_UDP
|
|
|
+ s.Port = 53
|
|
|
+ s.Address = net.ParseIP("2.3.4.5")
|
|
|
+ serviceAddress = "2.3.4.5:53"
|
|
|
+ }
|
|
|
+
|
|
|
+ err := i.NewService(&s)
|
|
|
+ assert.NoError(t, err)
|
|
|
+ checkService(t, true, protocol, RoundRobin, serviceAddress)
|
|
|
+
|
|
|
+ s.SchedName = ""
|
|
|
+ for j, fwdMethod := range fwdMethods {
|
|
|
+ d1 := Destination{
|
|
|
+ AddressFamily: nl.FAMILY_V4,
|
|
|
+ Address: net.ParseIP("10.1.1.2"),
|
|
|
+ Port: 5000,
|
|
|
+ Weight: 1,
|
|
|
+ ConnectionFlags: fwdMethod,
|
|
|
+ }
|
|
|
+
|
|
|
+ realAddress := "10.1.1.2:5000"
|
|
|
+ err := i.NewDestination(&s, &d1)
|
|
|
+ assert.NoError(t, err)
|
|
|
+ checkDestination(t, true, protocol, serviceAddress, realAddress, fwdMethodStrings[j])
|
|
|
+ d2 := Destination{
|
|
|
+ AddressFamily: nl.FAMILY_V4,
|
|
|
+ Address: net.ParseIP("10.1.1.3"),
|
|
|
+ Port: 5000,
|
|
|
+ Weight: 1,
|
|
|
+ ConnectionFlags: fwdMethod,
|
|
|
+ }
|
|
|
+
|
|
|
+ realAddress = "10.1.1.3:5000"
|
|
|
+ err = i.NewDestination(&s, &d2)
|
|
|
+ assert.NoError(t, err)
|
|
|
+ checkDestination(t, true, protocol, serviceAddress, realAddress, fwdMethodStrings[j])
|
|
|
+
|
|
|
+ d3 := Destination{
|
|
|
+ AddressFamily: nl.FAMILY_V4,
|
|
|
+ Address: net.ParseIP("10.1.1.4"),
|
|
|
+ Port: 5000,
|
|
|
+ Weight: 1,
|
|
|
+ ConnectionFlags: fwdMethod,
|
|
|
+ }
|
|
|
+
|
|
|
+ realAddress = "10.1.1.4:5000"
|
|
|
+ err = i.NewDestination(&s, &d3)
|
|
|
+ assert.NoError(t, err)
|
|
|
+ checkDestination(t, true, protocol, serviceAddress, realAddress, fwdMethodStrings[j])
|
|
|
+
|
|
|
+ for m, updateFwdMethod := range fwdMethods {
|
|
|
+ if updateFwdMethod == fwdMethod {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ d1.ConnectionFlags = updateFwdMethod
|
|
|
+ realAddress = "10.1.1.2:5000"
|
|
|
+ err = i.UpdateDestination(&s, &d1)
|
|
|
+ assert.NoError(t, err)
|
|
|
+ checkDestination(t, true, protocol, serviceAddress, realAddress, fwdMethodStrings[m])
|
|
|
+
|
|
|
+ d2.ConnectionFlags = updateFwdMethod
|
|
|
+ realAddress = "10.1.1.3:5000"
|
|
|
+ err = i.UpdateDestination(&s, &d2)
|
|
|
+ assert.NoError(t, err)
|
|
|
+ checkDestination(t, true, protocol, serviceAddress, realAddress, fwdMethodStrings[m])
|
|
|
+
|
|
|
+ d3.ConnectionFlags = updateFwdMethod
|
|
|
+ realAddress = "10.1.1.4:5000"
|
|
|
+ err = i.UpdateDestination(&s, &d3)
|
|
|
+ assert.NoError(t, err)
|
|
|
+ checkDestination(t, true, protocol, serviceAddress, realAddress, fwdMethodStrings[m])
|
|
|
+ }
|
|
|
+
|
|
|
+ err = i.DelDestination(&s, &d1)
|
|
|
+ assert.NoError(t, err)
|
|
|
+ err = i.DelDestination(&s, &d2)
|
|
|
+ assert.NoError(t, err)
|
|
|
+ err = i.DelDestination(&s, &d3)
|
|
|
+ assert.NoError(t, err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|