Commit 00f8ae35 authored by choury's avatar choury

fix duplicate unbind action

parent 6e2249b7
...@@ -13,6 +13,7 @@ go_test( ...@@ -13,6 +13,7 @@ go_test(
deps = [ deps = [
"//pkg/api:go_default_library", "//pkg/api:go_default_library",
"//pkg/proxy:go_default_library", "//pkg/proxy:go_default_library",
"//pkg/proxy/ipvs/testing:go_default_library",
"//pkg/proxy/util:go_default_library", "//pkg/proxy/util:go_default_library",
"//pkg/util/iptables:go_default_library", "//pkg/util/iptables:go_default_library",
"//pkg/util/iptables/testing:go_default_library", "//pkg/util/iptables/testing:go_default_library",
...@@ -30,7 +31,16 @@ go_test( ...@@ -30,7 +31,16 @@ go_test(
go_library( go_library(
name = "go_default_library", name = "go_default_library",
srcs = ["proxier.go"], srcs = [
"netlink.go",
"netlink_unsupported.go",
"proxier.go",
] + select({
"@io_bazel_rules_go//go/platform:linux_amd64": [
"netlink_linux.go",
],
"//conditions:default": [],
}),
deps = [ deps = [
"//pkg/api:go_default_library", "//pkg/api:go_default_library",
"//pkg/api/helper:go_default_library", "//pkg/api/helper:go_default_library",
...@@ -52,7 +62,12 @@ go_library( ...@@ -52,7 +62,12 @@ go_library(
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//vendor/k8s.io/client-go/tools/record:go_default_library", "//vendor/k8s.io/client-go/tools/record:go_default_library",
"//vendor/k8s.io/utils/exec:go_default_library", "//vendor/k8s.io/utils/exec:go_default_library",
], ] + select({
"@io_bazel_rules_go//go/platform:linux_amd64": [
"//vendor/github.com/vishvananda/netlink:go_default_library",
],
"//conditions:default": [],
}),
) )
filegroup( filegroup(
...@@ -64,6 +79,9 @@ filegroup( ...@@ -64,6 +79,9 @@ filegroup(
filegroup( filegroup(
name = "all-srcs", name = "all-srcs",
srcs = [":package-srcs"], srcs = [
":package-srcs",
"//pkg/proxy/ipvs/testing:all-srcs",
],
tags = ["automanaged"], tags = ["automanaged"],
) )
/*
Copyright 2017 The Kubernetes Authors.
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 ipvs
// NetLinkHandle for revoke netlink interface
type NetLinkHandle interface {
// EnsureAddressBind checks if address is bound to the interface and, if not, binds it. If the address is already bound, return true.
EnsureAddressBind(address, devName string) (exist bool, err error)
// UnbindAddress unbind address from the interface
UnbindAddress(address, devName string) error
}
// +build linux
/*
Copyright 2017 The Kubernetes Authors.
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 ipvs
import (
"fmt"
"net"
"syscall"
"github.com/vishvananda/netlink"
)
type netlinkHandle struct {
netlink.Handle
}
// NewNetLinkHandle will crate a new netlinkHandle
func NewNetLinkHandle() NetLinkHandle {
return &netlinkHandle{netlink.Handle{}}
}
// EnsureAddressBind checks if address is bound to the interface and, if not, binds it. If the address is already bound, return true.
func (h *netlinkHandle) EnsureAddressBind(address, devName string) (exist bool, err error) {
dev, err := h.LinkByName(devName)
if err != nil {
return false, fmt.Errorf("error get interface: %s, err: %v", devName, err)
}
addr := net.ParseIP(address)
if addr == nil {
return false, fmt.Errorf("error parse ip address: %s", address)
}
if err := h.AddrAdd(dev, &netlink.Addr{IPNet: netlink.NewIPNet(addr)}); err != nil {
// "EEXIST" will be returned if the address is already bound to device
if err == syscall.Errno(syscall.EEXIST) {
return true, nil
}
return false, fmt.Errorf("error bind address: %s to interface: %s, err: %v", address, devName, err)
}
return false, nil
}
// UnbindAddress unbind address from the interface
func (h *netlinkHandle) UnbindAddress(address, devName string) error {
dev, err := h.LinkByName(devName)
if err != nil {
return fmt.Errorf("error get interface: %s, err: %v", devName, err)
}
addr := net.ParseIP(address)
if addr == nil {
return fmt.Errorf("error parse ip address: %s", address)
}
if err := h.AddrDel(dev, &netlink.Addr{IPNet: netlink.NewIPNet(addr)}); err != nil {
return fmt.Errorf("error unbind address: %s from interface: %s, err: %v", address, devName, err)
}
return nil
}
// +build !linux
/*
Copyright 2017 The Kubernetes Authors.
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 ipvs
import (
"fmt"
)
type emptyHandle struct {
}
// NewNetLinkHandle will create an EmptyHandle
func NewNetLinkHandle() NetLinkHandle {
return &emptyHandle{}
}
// EnsureAddressBind checks if address is bound to the interface and, if not, binds it. If the address is already bound, return true.
func (h *emptyHandle) EnsureAddressBind(address, devName string) (exist bool, err error) {
return false, fmt.Errorf("netlink not supported for this platform")
}
// UnbindAddress unbind address from the interface
func (h *emptyHandle) UnbindAddress(address, devName string) error {
return fmt.Errorf("netlink not supported for this platform")
}
...@@ -134,6 +134,8 @@ type Proxier struct { ...@@ -134,6 +134,8 @@ type Proxier struct {
iptablesData *bytes.Buffer iptablesData *bytes.Buffer
natChains *bytes.Buffer natChains *bytes.Buffer
natRules *bytes.Buffer natRules *bytes.Buffer
// Added as a member to the struct to allow injection for testing.
netlinkHandle NetLinkHandle
} }
// IPGetter helps get node network interface IP // IPGetter helps get node network interface IP
...@@ -270,6 +272,7 @@ func NewProxier(ipt utiliptables.Interface, ipvs utilipvs.Interface, ...@@ -270,6 +272,7 @@ func NewProxier(ipt utiliptables.Interface, ipvs utilipvs.Interface,
iptablesData: bytes.NewBuffer(nil), iptablesData: bytes.NewBuffer(nil),
natChains: bytes.NewBuffer(nil), natChains: bytes.NewBuffer(nil),
natRules: bytes.NewBuffer(nil), natRules: bytes.NewBuffer(nil),
netlinkHandle: NewNetLinkHandle(),
} }
burstSyncs := 2 burstSyncs := 2
glog.V(3).Infof("minSyncPeriod: %v, syncPeriod: %v, burstSyncs: %d", minSyncPeriod, syncPeriod, burstSyncs) glog.V(3).Infof("minSyncPeriod: %v, syncPeriod: %v, burstSyncs: %d", minSyncPeriod, syncPeriod, burstSyncs)
...@@ -1295,7 +1298,7 @@ func (proxier *Proxier) syncService(svcName string, vs *utilipvs.VirtualServer, ...@@ -1295,7 +1298,7 @@ func (proxier *Proxier) syncService(svcName string, vs *utilipvs.VirtualServer,
// bind service address to dummy interface even if service not changed, // bind service address to dummy interface even if service not changed,
// in case that service IP was removed by other processes // in case that service IP was removed by other processes
if bindAddr { if bindAddr {
_, err := proxier.ipvs.EnsureVirtualServerAddressBind(vs, DefaultDummyDevice) _, err := proxier.netlinkHandle.EnsureAddressBind(vs.Address.String(), DefaultDummyDevice)
if err != nil { if err != nil {
glog.Errorf("Failed to bind service address to dummy device %q: %v", svcName, err) glog.Errorf("Failed to bind service address to dummy device %q: %v", svcName, err)
return err return err
...@@ -1384,6 +1387,7 @@ func (proxier *Proxier) syncEndpoint(svcPortName proxy.ServicePortName, onlyNode ...@@ -1384,6 +1387,7 @@ func (proxier *Proxier) syncEndpoint(svcPortName proxy.ServicePortName, onlyNode
} }
func (proxier *Proxier) cleanLegacyService(atciveServices map[string]bool, currentServices map[string]*utilipvs.VirtualServer) { func (proxier *Proxier) cleanLegacyService(atciveServices map[string]bool, currentServices map[string]*utilipvs.VirtualServer) {
unbindIPAddr := sets.NewString()
for cS := range currentServices { for cS := range currentServices {
if !atciveServices[cS] { if !atciveServices[cS] {
svc := currentServices[cS] svc := currentServices[cS]
...@@ -1391,10 +1395,14 @@ func (proxier *Proxier) cleanLegacyService(atciveServices map[string]bool, curre ...@@ -1391,10 +1395,14 @@ func (proxier *Proxier) cleanLegacyService(atciveServices map[string]bool, curre
if err != nil { if err != nil {
glog.Errorf("Failed to delete service, error: %v", err) glog.Errorf("Failed to delete service, error: %v", err)
} }
err = proxier.ipvs.UnbindVirtualServerAddress(svc, DefaultDummyDevice) unbindIPAddr.Insert(svc.Address.String())
if err != nil { }
glog.Errorf("Failed to unbind service from dummy interface, error: %v", err) }
}
for _, addr := range unbindIPAddr.UnsortedList() {
err := proxier.netlinkHandle.UnbindAddress(addr, DefaultDummyDevice)
if err != nil {
glog.Errorf("Failed to unbind service from dummy interface, error: %v", err)
} }
} }
} }
......
...@@ -31,6 +31,7 @@ import ( ...@@ -31,6 +31,7 @@ import (
fakeexec "k8s.io/utils/exec/testing" fakeexec "k8s.io/utils/exec/testing"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
netlinktest "k8s.io/kubernetes/pkg/proxy/ipvs/testing"
proxyutil "k8s.io/kubernetes/pkg/proxy/util" proxyutil "k8s.io/kubernetes/pkg/proxy/util"
utiliptables "k8s.io/kubernetes/pkg/util/iptables" utiliptables "k8s.io/kubernetes/pkg/util/iptables"
iptablestest "k8s.io/kubernetes/pkg/util/iptables/testing" iptablestest "k8s.io/kubernetes/pkg/util/iptables/testing"
...@@ -114,6 +115,7 @@ func NewFakeProxier(ipt utiliptables.Interface, ipvs utilipvs.Interface, nodeIPs ...@@ -114,6 +115,7 @@ func NewFakeProxier(ipt utiliptables.Interface, ipvs utilipvs.Interface, nodeIPs
iptablesData: bytes.NewBuffer(nil), iptablesData: bytes.NewBuffer(nil),
natChains: bytes.NewBuffer(nil), natChains: bytes.NewBuffer(nil),
natRules: bytes.NewBuffer(nil), natRules: bytes.NewBuffer(nil),
netlinkHandle: netlinktest.NewFakeNetlinkHandle(),
} }
} }
......
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = select({
"@io_bazel_rules_go//go/platform:linux_amd64": [
"fake.go",
],
"//conditions:default": [],
}),
tags = ["automanaged"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
// +build linux
/*
Copyright 2017 The Kubernetes Authors.
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 testing
//FakeNetlinkHandle mock implementation of proxy NetlinkHandle
type FakeNetlinkHandle struct {
}
//NewFakeNetlinkHandle will create a new FakeNetlinkHandle
func NewFakeNetlinkHandle() *FakeNetlinkHandle {
return &FakeNetlinkHandle{}
}
//EnsureAddressBind is a mock implementation
func (h *FakeNetlinkHandle) EnsureAddressBind(address, devName string) (exist bool, err error) {
return false, nil
}
//UnbindAddress is a mock implementation
func (h *FakeNetlinkHandle) UnbindAddress(address, devName string) error {
return nil
}
...@@ -20,9 +20,6 @@ go_test( ...@@ -20,9 +20,6 @@ go_test(
deps = select({ deps = select({
"@io_bazel_rules_go//go/platform:linux_amd64": [ "@io_bazel_rules_go//go/platform:linux_amd64": [
"//vendor/github.com/docker/libnetwork/ipvs:go_default_library", "//vendor/github.com/docker/libnetwork/ipvs:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/utils/exec:go_default_library",
"//vendor/k8s.io/utils/exec/testing:go_default_library",
], ],
"//conditions:default": [], "//conditions:default": [],
}), }),
......
...@@ -25,10 +25,6 @@ import ( ...@@ -25,10 +25,6 @@ import (
type Interface interface { type Interface interface {
// Flush clears all virtual servers in system. return occurred error immediately. // Flush clears all virtual servers in system. return occurred error immediately.
Flush() error Flush() error
// EnsureVirtualServerAddressBind checks if virtual server's address is bound to dummy interface and, if not, binds it. If the address is already bound, return true.
EnsureVirtualServerAddressBind(serv *VirtualServer, dev string) (exist bool, err error)
// UnbindVirtualServerAddress checks if virtual server's address is bound to dummy interface and, if so, unbinds it.
UnbindVirtualServerAddress(serv *VirtualServer, dev string) error
// AddVirtualServer creates the specified virtual server. // AddVirtualServer creates the specified virtual server.
AddVirtualServer(*VirtualServer) error AddVirtualServer(*VirtualServer) error
// UpdateVirtualServer updates an already existing virtual server. If the virtual server does not exist, return error. // UpdateVirtualServer updates an already existing virtual server. If the virtual server does not exist, return error.
......
...@@ -30,8 +30,6 @@ import ( ...@@ -30,8 +30,6 @@ import (
utilexec "k8s.io/utils/exec" utilexec "k8s.io/utils/exec"
) )
const cmdIP = "ip"
// runner implements Interface. // runner implements Interface.
type runner struct { type runner struct {
exec utilexec.Interface exec utilexec.Interface
...@@ -51,34 +49,6 @@ func New(exec utilexec.Interface) Interface { ...@@ -51,34 +49,6 @@ func New(exec utilexec.Interface) Interface {
} }
} }
// EnsureVirtualServerAddressBind is part of Interface.
func (runner *runner) EnsureVirtualServerAddressBind(vs *VirtualServer, dummyDev string) (exist bool, err error) {
addr := vs.Address.String()
args := []string{"addr", "add", addr, "dev", dummyDev}
out, err := runner.exec.Command(cmdIP, args...).CombinedOutput()
if err != nil {
// "exit status 2" will be returned if the address is already bound to dummy device
if ee, ok := err.(utilexec.ExitError); ok {
if ee.Exited() && ee.ExitStatus() == 2 {
return true, nil
}
}
return false, fmt.Errorf("error bind address: %s to dummy interface: %s, err: %v: %s", vs.Address.String(), dummyDev, err, out)
}
return false, nil
}
// UnbindVirtualServerAddress is part of Interface.
func (runner *runner) UnbindVirtualServerAddress(vs *VirtualServer, dummyDev string) error {
addr := vs.Address.String()
args := []string{"addr", "del", addr, "dev", dummyDev}
out, err := runner.exec.Command(cmdIP, args...).CombinedOutput()
if err != nil {
return fmt.Errorf("error unbind address: %s from dummy interface: %s, err: %v: %s", vs.Address.String(), dummyDev, err, out)
}
return nil
}
// AddVirtualServer is part of Interface. // AddVirtualServer is part of Interface.
func (runner *runner) AddVirtualServer(vs *VirtualServer) error { func (runner *runner) AddVirtualServer(vs *VirtualServer) error {
eSvc, err := toBackendService(vs) eSvc, err := toBackendService(vs)
......
...@@ -25,129 +25,9 @@ import ( ...@@ -25,129 +25,9 @@ import (
"syscall" "syscall"
"testing" "testing"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/utils/exec"
fakeexec "k8s.io/utils/exec/testing"
"github.com/docker/libnetwork/ipvs" "github.com/docker/libnetwork/ipvs"
) )
const dummyDevice = "kube-ipvs0"
func TestEnsureVirtualServerAddressBind(t *testing.T) {
tests := []VirtualServer{
{
Address: net.ParseIP("10.20.30.40"),
Port: uint16(1234),
Protocol: string("TCP"),
},
{
Address: net.ParseIP("2012::beef"),
Port: uint16(5678),
Protocol: string("UDP"),
},
}
for i := range tests {
vs := &tests[i]
fcmd := fakeexec.FakeCmd{
CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
// Success.
func() ([]byte, error) { return []byte{}, nil },
// Exists.
func() ([]byte, error) { return nil, &fakeexec.FakeExitError{Status: 2} },
},
}
fexec := fakeexec.FakeExec{
CommandScript: []fakeexec.FakeCommandAction{
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
},
}
runner := New(&fexec)
// Success.
exists, err := runner.EnsureVirtualServerAddressBind(vs, dummyDevice)
if err != nil {
t.Errorf("expected success, got %v", err)
}
if exists {
t.Errorf("expected exists = false")
}
if fcmd.CombinedOutputCalls != 1 {
t.Errorf("expected 1 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls)
}
IP := tests[i].Address.String()
if !sets.NewString(fcmd.CombinedOutputLog[0]...).HasAll("ip", "addr", "add", IP, "dev", dummyDevice) {
t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[0])
}
// Exists.
exists, err = runner.EnsureVirtualServerAddressBind(vs, dummyDevice)
if err != nil {
t.Errorf("expected success, got %v", err)
}
if !exists {
t.Errorf("expected exists = true")
}
}
}
func TestUnbindVirtualServerAddress(t *testing.T) {
tests := []VirtualServer{
{
Address: net.ParseIP("2012::beef"),
Port: uint16(5678),
Protocol: string("UDP"),
},
{
Address: net.ParseIP("10.20.30.40"),
Port: uint16(1234),
Protocol: string("TCP"),
},
}
for i := range tests {
vs := &tests[i]
fcmd := fakeexec.FakeCmd{
CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
// Success.
func() ([]byte, error) {
return []byte{}, nil
},
// Failure.
func() ([]byte, error) {
return nil, &fakeexec.FakeExitError{Status: 2}
},
},
}
fexec := fakeexec.FakeExec{
CommandScript: []fakeexec.FakeCommandAction{
func(cmd string, args ...string) exec.Cmd {
return fakeexec.InitFakeCmd(&fcmd, cmd, args...)
},
func(cmd string, args ...string) exec.Cmd {
return fakeexec.InitFakeCmd(&fcmd, cmd, args...)
},
},
}
runner := New(&fexec)
// Success.
err := runner.UnbindVirtualServerAddress(vs, dummyDevice)
if err != nil {
t.Errorf("expected success, got %v", err)
}
if fcmd.CombinedOutputCalls != 1 {
t.Errorf("expected 1 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls)
}
IP := tests[i].Address.String()
if !sets.NewString(fcmd.CombinedOutputLog[0]...).HasAll("ip", "addr", "del", IP, "dev", dummyDevice) {
t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[0])
}
// Failure.
err = runner.UnbindVirtualServerAddress(vs, dummyDevice)
if err == nil {
t.Errorf("expected failure")
}
}
}
func Test_toVirtualServer(t *testing.T) { func Test_toVirtualServer(t *testing.T) {
Tests := []struct { Tests := []struct {
ipvsService ipvs.Service ipvsService ipvs.Service
......
...@@ -36,14 +36,6 @@ func (runner *runner) Flush() error { ...@@ -36,14 +36,6 @@ func (runner *runner) Flush() error {
return fmt.Errorf("IPVS not supported for this platform") return fmt.Errorf("IPVS not supported for this platform")
} }
func (runner *runner) EnsureVirtualServerAddressBind(*VirtualServer, string) (bool, error) {
return false, fmt.Errorf("IPVS not supported for this platform")
}
func (runner *runner) UnbindVirtualServerAddress(*VirtualServer, string) error {
return fmt.Errorf("IPVS not supported for this platform")
}
func (runner *runner) AddVirtualServer(*VirtualServer) error { func (runner *runner) AddVirtualServer(*VirtualServer) error {
return fmt.Errorf("IPVS not supported for this platform") return fmt.Errorf("IPVS not supported for this platform")
} }
......
...@@ -55,16 +55,6 @@ func toServiceKey(serv *utilipvs.VirtualServer) serviceKey { ...@@ -55,16 +55,6 @@ func toServiceKey(serv *utilipvs.VirtualServer) serviceKey {
} }
} }
//EnsureVirtualServerAddressBind is an empty implementation
func (*FakeIPVS) EnsureVirtualServerAddressBind(serv *utilipvs.VirtualServer, dev string) (exist bool, err error) {
return true, nil
}
//UnbindVirtualServerAddress is an empty implementation
func (*FakeIPVS) UnbindVirtualServerAddress(serv *utilipvs.VirtualServer, dev string) error {
return nil
}
//AddVirtualServer is a fake implementation, it simply adds the VirtualServer into the cache store. //AddVirtualServer is a fake implementation, it simply adds the VirtualServer into the cache store.
func (f *FakeIPVS) AddVirtualServer(serv *utilipvs.VirtualServer) error { func (f *FakeIPVS) AddVirtualServer(serv *utilipvs.VirtualServer) error {
if serv == nil { if serv == nil {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment