refactor hostport logic
This commit is contained in:
parent
4ed86f5d46
commit
aabdaa984f
@ -12,6 +12,8 @@ go_library(
|
|||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = [
|
srcs = [
|
||||||
"fake_iptables.go",
|
"fake_iptables.go",
|
||||||
|
"hostport.go",
|
||||||
|
"hostport_manager.go",
|
||||||
"hostport_syncer.go",
|
"hostport_syncer.go",
|
||||||
],
|
],
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
@ -22,17 +24,23 @@ go_library(
|
|||||||
"//pkg/util/exec:go_default_library",
|
"//pkg/util/exec:go_default_library",
|
||||||
"//pkg/util/iptables:go_default_library",
|
"//pkg/util/iptables:go_default_library",
|
||||||
"//vendor:github.com/golang/glog",
|
"//vendor:github.com/golang/glog",
|
||||||
|
"//vendor:k8s.io/apimachinery/pkg/util/errors",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
go_test(
|
go_test(
|
||||||
name = "go_default_test",
|
name = "go_default_test",
|
||||||
srcs = ["hostport_syncer_test.go"],
|
srcs = [
|
||||||
|
"hostport_manager_test.go",
|
||||||
|
"hostport_syncer_test.go",
|
||||||
|
"hostport_test.go",
|
||||||
|
],
|
||||||
library = ":go_default_library",
|
library = ":go_default_library",
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
deps = [
|
deps = [
|
||||||
"//pkg/api/v1:go_default_library",
|
"//pkg/api/v1:go_default_library",
|
||||||
"//pkg/util/iptables:go_default_library",
|
"//pkg/util/iptables:go_default_library",
|
||||||
|
"//vendor:github.com/stretchr/testify/assert",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
171
pkg/kubelet/network/hostport/hostport.go
Normal file
171
pkg/kubelet/network/hostport/hostport.go
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
/*
|
||||||
|
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 hostport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/api/v1"
|
||||||
|
utiliptables "k8s.io/kubernetes/pkg/util/iptables"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// the hostport chain
|
||||||
|
kubeHostportsChain utiliptables.Chain = "KUBE-HOSTPORTS"
|
||||||
|
// prefix for hostport chains
|
||||||
|
kubeHostportChainPrefix string = "KUBE-HP-"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PortMapping represents a network port in a container
|
||||||
|
type PortMapping struct {
|
||||||
|
Name string
|
||||||
|
HostPort int32
|
||||||
|
ContainerPort int32
|
||||||
|
Protocol v1.Protocol
|
||||||
|
HostIP string
|
||||||
|
}
|
||||||
|
|
||||||
|
// PodPortMapping represents a pod's network state and associated container port mappings
|
||||||
|
type PodPortMapping struct {
|
||||||
|
Namespace string
|
||||||
|
Name string
|
||||||
|
PortMappings []*PortMapping
|
||||||
|
HostNetwork bool
|
||||||
|
IP net.IP
|
||||||
|
}
|
||||||
|
|
||||||
|
type hostport struct {
|
||||||
|
port int32
|
||||||
|
protocol string
|
||||||
|
}
|
||||||
|
|
||||||
|
type hostportOpener func(*hostport) (closeable, error)
|
||||||
|
|
||||||
|
type closeable interface {
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func openLocalPort(hp *hostport) (closeable, error) {
|
||||||
|
// For ports on node IPs, open the actual port and hold it, even though we
|
||||||
|
// use iptables to redirect traffic.
|
||||||
|
// This ensures a) that it's safe to use that port and b) that (a) stays
|
||||||
|
// true. The risk is that some process on the node (e.g. sshd or kubelet)
|
||||||
|
// is using a port and we give that same port out to a Service. That would
|
||||||
|
// be bad because iptables would silently claim the traffic but the process
|
||||||
|
// would never know.
|
||||||
|
// NOTE: We should not need to have a real listen()ing socket - bind()
|
||||||
|
// should be enough, but I can't figure out a way to e2e test without
|
||||||
|
// it. Tools like 'ss' and 'netstat' do not show sockets that are
|
||||||
|
// bind()ed but not listen()ed, and at least the default debian netcat
|
||||||
|
// has no way to avoid about 10 seconds of retries.
|
||||||
|
var socket closeable
|
||||||
|
switch hp.protocol {
|
||||||
|
case "tcp":
|
||||||
|
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", hp.port))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
socket = listener
|
||||||
|
case "udp":
|
||||||
|
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", hp.port))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conn, err := net.ListenUDP("udp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
socket = conn
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown protocol %q", hp.protocol)
|
||||||
|
}
|
||||||
|
glog.V(3).Infof("Opened local port %s", hp.String())
|
||||||
|
return socket, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// openHostports opens all given hostports using the given hostportOpener
|
||||||
|
// If encounter any error, clean up and return the error
|
||||||
|
// If all ports are opened successfully, return the hostport and socket mapping
|
||||||
|
// TODO: move openHostports and closeHostports into a common struct
|
||||||
|
func openHostports(portOpener hostportOpener, podPortMapping *PodPortMapping) (map[hostport]closeable, error) {
|
||||||
|
var retErr error
|
||||||
|
ports := make(map[hostport]closeable)
|
||||||
|
for _, pm := range podPortMapping.PortMappings {
|
||||||
|
if pm.HostPort <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
hp := portMappingToHostport(pm)
|
||||||
|
socket, err := portOpener(&hp)
|
||||||
|
if err != nil {
|
||||||
|
retErr = fmt.Errorf("cannot open hostport %d for pod %s: %v", pm.HostPort, getPodFullName(podPortMapping), err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ports[hp] = socket
|
||||||
|
}
|
||||||
|
|
||||||
|
// If encounter any error, close all hostports that just got opened.
|
||||||
|
if retErr != nil {
|
||||||
|
for hp, socket := range ports {
|
||||||
|
if err := socket.Close(); err != nil {
|
||||||
|
glog.Errorf("Cannot clean up hostport %d for pod %s: %v", hp.port, getPodFullName(podPortMapping), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, retErr
|
||||||
|
}
|
||||||
|
return ports, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// portMappingToHostport creates hostport structure based on input portmapping
|
||||||
|
func portMappingToHostport(portMapping *PortMapping) hostport {
|
||||||
|
return hostport{
|
||||||
|
port: portMapping.HostPort,
|
||||||
|
protocol: strings.ToLower(string(portMapping.Protocol)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensureKubeHostportChains ensures the KUBE-HOSTPORTS chain is setup correctly
|
||||||
|
func ensureKubeHostportChains(iptables utiliptables.Interface, natInterfaceName string) error {
|
||||||
|
glog.V(4).Info("Ensuring kubelet hostport chains")
|
||||||
|
// Ensure kubeHostportChain
|
||||||
|
if _, err := iptables.EnsureChain(utiliptables.TableNAT, kubeHostportsChain); err != nil {
|
||||||
|
return fmt.Errorf("Failed to ensure that %s chain %s exists: %v", utiliptables.TableNAT, kubeHostportsChain, err)
|
||||||
|
}
|
||||||
|
tableChainsNeedJumpServices := []struct {
|
||||||
|
table utiliptables.Table
|
||||||
|
chain utiliptables.Chain
|
||||||
|
}{
|
||||||
|
{utiliptables.TableNAT, utiliptables.ChainOutput},
|
||||||
|
{utiliptables.TableNAT, utiliptables.ChainPrerouting},
|
||||||
|
}
|
||||||
|
args := []string{"-m", "comment", "--comment", "kube hostport portals",
|
||||||
|
"-m", "addrtype", "--dst-type", "LOCAL",
|
||||||
|
"-j", string(kubeHostportsChain)}
|
||||||
|
for _, tc := range tableChainsNeedJumpServices {
|
||||||
|
if _, err := iptables.EnsureRule(utiliptables.Prepend, tc.table, tc.chain, args...); err != nil {
|
||||||
|
return fmt.Errorf("Failed to ensure that %s chain %s jumps to %s: %v", tc.table, tc.chain, kubeHostportsChain, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Need to SNAT traffic from localhost
|
||||||
|
args = []string{"-m", "comment", "--comment", "SNAT for localhost access to hostports", "-o", natInterfaceName, "-s", "127.0.0.0/8", "-j", "MASQUERADE"}
|
||||||
|
if _, err := iptables.EnsureRule(utiliptables.Append, utiliptables.TableNAT, utiliptables.ChainPostrouting, args...); err != nil {
|
||||||
|
return fmt.Errorf("Failed to ensure that %s chain %s jumps to MASQUERADE: %v", utiliptables.TableNAT, utiliptables.ChainPostrouting, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -21,26 +21,17 @@ import (
|
|||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/base32"
|
"encoding/base32"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api/v1"
|
|
||||||
iptablesproxy "k8s.io/kubernetes/pkg/proxy/iptables"
|
iptablesproxy "k8s.io/kubernetes/pkg/proxy/iptables"
|
||||||
utildbus "k8s.io/kubernetes/pkg/util/dbus"
|
utildbus "k8s.io/kubernetes/pkg/util/dbus"
|
||||||
utilexec "k8s.io/kubernetes/pkg/util/exec"
|
utilexec "k8s.io/kubernetes/pkg/util/exec"
|
||||||
utiliptables "k8s.io/kubernetes/pkg/util/iptables"
|
utiliptables "k8s.io/kubernetes/pkg/util/iptables"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
// the hostport chain
|
|
||||||
kubeHostportsChain utiliptables.Chain = "KUBE-HOSTPORTS"
|
|
||||||
// prefix for hostport chains
|
|
||||||
kubeHostportChainPrefix string = "KUBE-HP-"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HostportSyncer takes a list of PodPortMappings and implements hostport all at once
|
// HostportSyncer takes a list of PodPortMappings and implements hostport all at once
|
||||||
type HostportSyncer interface {
|
type HostportSyncer interface {
|
||||||
// SyncHostports gathers all hostports on node and setup iptables rules to enable them.
|
// SyncHostports gathers all hostports on node and setup iptables rules to enable them.
|
||||||
@ -52,26 +43,6 @@ type HostportSyncer interface {
|
|||||||
OpenPodHostportsAndSync(newPortMapping *PodPortMapping, natInterfaceName string, activePodPortMappings []*PodPortMapping) error
|
OpenPodHostportsAndSync(newPortMapping *PodPortMapping, natInterfaceName string, activePodPortMappings []*PodPortMapping) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// PortMapping represents a network port in a container
|
|
||||||
type PortMapping struct {
|
|
||||||
Name string
|
|
||||||
HostPort int32
|
|
||||||
ContainerPort int32
|
|
||||||
Protocol v1.Protocol
|
|
||||||
HostIP string
|
|
||||||
}
|
|
||||||
|
|
||||||
// PodPortMapping represents a pod's network state and associated container port mappings
|
|
||||||
type PodPortMapping struct {
|
|
||||||
Namespace string
|
|
||||||
Name string
|
|
||||||
PortMappings []*PortMapping
|
|
||||||
HostNetwork bool
|
|
||||||
IP net.IP
|
|
||||||
}
|
|
||||||
|
|
||||||
type hostportOpener func(*hostport) (closeable, error)
|
|
||||||
|
|
||||||
type hostportSyncer struct {
|
type hostportSyncer struct {
|
||||||
hostPortMap map[hostport]closeable
|
hostPortMap map[hostport]closeable
|
||||||
iptables utiliptables.Interface
|
iptables utiliptables.Interface
|
||||||
@ -87,15 +58,6 @@ func NewHostportSyncer() HostportSyncer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type closeable interface {
|
|
||||||
Close() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type hostport struct {
|
|
||||||
port int32
|
|
||||||
protocol string
|
|
||||||
}
|
|
||||||
|
|
||||||
type targetPod struct {
|
type targetPod struct {
|
||||||
podFullName string
|
podFullName string
|
||||||
podIP string
|
podIP string
|
||||||
@ -120,7 +82,7 @@ func (h *hostportSyncer) openHostports(podHostportMapping *PodPortMapping) error
|
|||||||
}
|
}
|
||||||
socket, err := h.portOpener(&hp)
|
socket, err := h.portOpener(&hp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
retErr = fmt.Errorf("Cannot open hostport %d for pod %s: %v", port.HostPort, getPodFullName(podHostportMapping), err)
|
retErr = fmt.Errorf("cannot open hostport %d for pod %s: %v", port.HostPort, getPodFullName(podHostportMapping), err)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
ports[hp] = socket
|
ports[hp] = socket
|
||||||
@ -222,31 +184,8 @@ func (h *hostportSyncer) SyncHostports(natInterfaceName string, activePodPortMap
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
glog.V(4).Info("Ensuring kubelet hostport chains")
|
// Ensure KUBE-HOSTPORTS chains
|
||||||
// Ensure kubeHostportChain
|
ensureKubeHostportChains(h.iptables, natInterfaceName)
|
||||||
if _, err := h.iptables.EnsureChain(utiliptables.TableNAT, kubeHostportsChain); err != nil {
|
|
||||||
return fmt.Errorf("Failed to ensure that %s chain %s exists: %v", utiliptables.TableNAT, kubeHostportsChain, err)
|
|
||||||
}
|
|
||||||
tableChainsNeedJumpServices := []struct {
|
|
||||||
table utiliptables.Table
|
|
||||||
chain utiliptables.Chain
|
|
||||||
}{
|
|
||||||
{utiliptables.TableNAT, utiliptables.ChainOutput},
|
|
||||||
{utiliptables.TableNAT, utiliptables.ChainPrerouting},
|
|
||||||
}
|
|
||||||
args := []string{"-m", "comment", "--comment", "kube hostport portals",
|
|
||||||
"-m", "addrtype", "--dst-type", "LOCAL",
|
|
||||||
"-j", string(kubeHostportsChain)}
|
|
||||||
for _, tc := range tableChainsNeedJumpServices {
|
|
||||||
if _, err := h.iptables.EnsureRule(utiliptables.Prepend, tc.table, tc.chain, args...); err != nil {
|
|
||||||
return fmt.Errorf("Failed to ensure that %s chain %s jumps to %s: %v", tc.table, tc.chain, kubeHostportsChain, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Need to SNAT traffic from localhost
|
|
||||||
args = []string{"-m", "comment", "--comment", "SNAT for localhost access to hostports", "-o", natInterfaceName, "-s", "127.0.0.0/8", "-j", "MASQUERADE"}
|
|
||||||
if _, err := h.iptables.EnsureRule(utiliptables.Append, utiliptables.TableNAT, utiliptables.ChainPostrouting, args...); err != nil {
|
|
||||||
return fmt.Errorf("Failed to ensure that %s chain %s jumps to MASQUERADE: %v", utiliptables.TableNAT, utiliptables.ChainPostrouting, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get iptables-save output so we can check for existing chains and rules.
|
// Get iptables-save output so we can check for existing chains and rules.
|
||||||
// This will be a map of chain name to chain with rules as stored in iptables-save/iptables-restore
|
// This will be a map of chain name to chain with rules as stored in iptables-save/iptables-restore
|
||||||
@ -341,44 +280,6 @@ func (h *hostportSyncer) SyncHostports(natInterfaceName string, activePodPortMap
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func openLocalPort(hp *hostport) (closeable, error) {
|
|
||||||
// For ports on node IPs, open the actual port and hold it, even though we
|
|
||||||
// use iptables to redirect traffic.
|
|
||||||
// This ensures a) that it's safe to use that port and b) that (a) stays
|
|
||||||
// true. The risk is that some process on the node (e.g. sshd or kubelet)
|
|
||||||
// is using a port and we give that same port out to a Service. That would
|
|
||||||
// be bad because iptables would silently claim the traffic but the process
|
|
||||||
// would never know.
|
|
||||||
// NOTE: We should not need to have a real listen()ing socket - bind()
|
|
||||||
// should be enough, but I can't figure out a way to e2e test without
|
|
||||||
// it. Tools like 'ss' and 'netstat' do not show sockets that are
|
|
||||||
// bind()ed but not listen()ed, and at least the default debian netcat
|
|
||||||
// has no way to avoid about 10 seconds of retries.
|
|
||||||
var socket closeable
|
|
||||||
switch hp.protocol {
|
|
||||||
case "tcp":
|
|
||||||
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", hp.port))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
socket = listener
|
|
||||||
case "udp":
|
|
||||||
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", hp.port))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
conn, err := net.ListenUDP("udp", addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
socket = conn
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unknown protocol %q", hp.protocol)
|
|
||||||
}
|
|
||||||
glog.V(3).Infof("Opened local port %s", hp.String())
|
|
||||||
return socket, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// cleanupHostportMap closes obsolete hostports
|
// cleanupHostportMap closes obsolete hostports
|
||||||
func (h *hostportSyncer) cleanupHostportMap(containerPortMap map[*PortMapping]targetPod) {
|
func (h *hostportSyncer) cleanupHostportMap(containerPortMap map[*PortMapping]targetPod) {
|
||||||
// compute hostports that are supposed to be open
|
// compute hostports that are supposed to be open
|
||||||
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||||||
package hostport
|
package hostport
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
@ -27,24 +26,6 @@ import (
|
|||||||
utiliptables "k8s.io/kubernetes/pkg/util/iptables"
|
utiliptables "k8s.io/kubernetes/pkg/util/iptables"
|
||||||
)
|
)
|
||||||
|
|
||||||
type fakeSocket struct {
|
|
||||||
port int32
|
|
||||||
protocol string
|
|
||||||
closed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fakeSocket) Close() error {
|
|
||||||
if f.closed {
|
|
||||||
return fmt.Errorf("Socket %q.%s already closed!", f.port, f.protocol)
|
|
||||||
}
|
|
||||||
f.closed = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func openFakeSocket(hp *hostport) (closeable, error) {
|
|
||||||
return &fakeSocket{hp.port, hp.protocol, false}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type ruleMatch struct {
|
type ruleMatch struct {
|
||||||
hostport int
|
hostport int
|
||||||
chain string
|
chain string
|
||||||
@ -53,11 +34,12 @@ type ruleMatch struct {
|
|||||||
|
|
||||||
func TestOpenPodHostports(t *testing.T) {
|
func TestOpenPodHostports(t *testing.T) {
|
||||||
fakeIPTables := NewFakeIPTables()
|
fakeIPTables := NewFakeIPTables()
|
||||||
|
fakeOpener := NewFakeSocketManager()
|
||||||
|
|
||||||
h := &hostportSyncer{
|
h := &hostportSyncer{
|
||||||
hostPortMap: make(map[hostport]closeable),
|
hostPortMap: make(map[hostport]closeable),
|
||||||
iptables: fakeIPTables,
|
iptables: fakeIPTables,
|
||||||
portOpener: openFakeSocket,
|
portOpener: fakeOpener.openFakeSocket,
|
||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
152
pkg/kubelet/network/hostport/hostport_test.go
Normal file
152
pkg/kubelet/network/hostport/hostport_test.go
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
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 hostport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"k8s.io/kubernetes/pkg/api/v1"
|
||||||
|
utiliptables "k8s.io/kubernetes/pkg/util/iptables"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeSocket struct {
|
||||||
|
port int32
|
||||||
|
protocol string
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeSocket) Close() error {
|
||||||
|
if f.closed {
|
||||||
|
return fmt.Errorf("Socket %q.%s already closed!", f.port, f.protocol)
|
||||||
|
}
|
||||||
|
f.closed = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFakeSocketManager() *fakeSocketManager {
|
||||||
|
return &fakeSocketManager{mem: make(map[hostport]*fakeSocket)}
|
||||||
|
}
|
||||||
|
|
||||||
|
type fakeSocketManager struct {
|
||||||
|
mem map[hostport]*fakeSocket
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeSocketManager) openFakeSocket(hp *hostport) (closeable, error) {
|
||||||
|
if socket, ok := f.mem[*hp]; ok && !socket.closed {
|
||||||
|
return nil, fmt.Errorf("hostport is occupied")
|
||||||
|
}
|
||||||
|
fs := &fakeSocket{hp.port, hp.protocol, false}
|
||||||
|
f.mem[*hp] = fs
|
||||||
|
return fs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOpenHostports(t *testing.T) {
|
||||||
|
opener := NewFakeSocketManager()
|
||||||
|
testCases := []struct {
|
||||||
|
podPortMapping *PodPortMapping
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
&PodPortMapping{
|
||||||
|
Namespace: "ns1",
|
||||||
|
Name: "n0",
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&PodPortMapping{
|
||||||
|
Namespace: "ns1",
|
||||||
|
Name: "n1",
|
||||||
|
PortMappings: []*PortMapping{
|
||||||
|
{HostPort: 80, Protocol: v1.Protocol("TCP")},
|
||||||
|
{HostPort: 8080, Protocol: v1.Protocol("TCP")},
|
||||||
|
{HostPort: 443, Protocol: v1.Protocol("TCP")},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&PodPortMapping{
|
||||||
|
Namespace: "ns1",
|
||||||
|
Name: "n2",
|
||||||
|
PortMappings: []*PortMapping{
|
||||||
|
{HostPort: 80, Protocol: v1.Protocol("TCP")},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&PodPortMapping{
|
||||||
|
Namespace: "ns1",
|
||||||
|
Name: "n3",
|
||||||
|
PortMappings: []*PortMapping{
|
||||||
|
{HostPort: 8081, Protocol: v1.Protocol("TCP")},
|
||||||
|
{HostPort: 8080, Protocol: v1.Protocol("TCP")},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&PodPortMapping{
|
||||||
|
Namespace: "ns1",
|
||||||
|
Name: "n3",
|
||||||
|
PortMappings: []*PortMapping{
|
||||||
|
{HostPort: 8081, Protocol: v1.Protocol("TCP")},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
mapping, err := openHostports(opener.openFakeSocket, tc.podPortMapping)
|
||||||
|
if tc.expectError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, len(mapping), len(tc.podPortMapping.PortMappings))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnsureKubeHostportChains(t *testing.T) {
|
||||||
|
interfaceName := "cbr0"
|
||||||
|
builtinChains := []string{"PREROUTING", "OUTPUT"}
|
||||||
|
jumpRule := "-m comment --comment \"kube hostport portals\" -m addrtype --dst-type LOCAL -j KUBE-HOSTPORTS"
|
||||||
|
masqRule := "-m comment --comment \"SNAT for localhost access to hostports\" -o cbr0 -s 127.0.0.0/8 -j MASQUERADE"
|
||||||
|
|
||||||
|
fakeIPTables := NewFakeIPTables()
|
||||||
|
assert.NoError(t, ensureKubeHostportChains(fakeIPTables, interfaceName))
|
||||||
|
|
||||||
|
_, _, err := fakeIPTables.getChain(utiliptables.TableNAT, utiliptables.Chain("KUBE-HOSTPORTS"))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
_, chain, err := fakeIPTables.getChain(utiliptables.TableNAT, utiliptables.ChainPostrouting)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, len(chain.rules), 1)
|
||||||
|
assert.Contains(t, chain.rules[0], masqRule)
|
||||||
|
|
||||||
|
for _, chainName := range builtinChains {
|
||||||
|
_, chain, err := fakeIPTables.getChain(utiliptables.TableNAT, utiliptables.Chain(chainName))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, len(chain.rules), 1)
|
||||||
|
assert.Contains(t, chain.rules[0], jumpRule)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user