mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-30 15:05:27 +00:00
Merge pull request #13628 from MikaelCluseau/wip-hairpin
Auto commit by PR queue bot
This commit is contained in:
commit
5ed8400642
@ -130,7 +130,7 @@ func filterHTTPError(err error, image string) error {
|
|||||||
jerr.Code == http.StatusServiceUnavailable ||
|
jerr.Code == http.StatusServiceUnavailable ||
|
||||||
jerr.Code == http.StatusGatewayTimeout) {
|
jerr.Code == http.StatusGatewayTimeout) {
|
||||||
glog.V(2).Infof("Pulling image %q failed: %v", image, err)
|
glog.V(2).Infof("Pulling image %q failed: %v", image, err)
|
||||||
return fmt.Errorf("image pull failed for %s because the registry is temporarily unavailbe.", image)
|
return fmt.Errorf("image pull failed for %s because the registry is temporarily unavailable.", image)
|
||||||
} else {
|
} else {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -255,7 +255,7 @@ func TestPullWithJSONError(t *testing.T) {
|
|||||||
"Bad gateway": {
|
"Bad gateway": {
|
||||||
"ubuntu",
|
"ubuntu",
|
||||||
&jsonmessage.JSONError{Code: 502, Message: "<!doctype html>\n<html class=\"no-js\" lang=\"\">\n <head>\n </head>\n <body>\n <h1>Oops, there was an error!</h1>\n <p>We have been contacted of this error, feel free to check out <a href=\"http://status.docker.com/\">status.docker.com</a>\n to see if there is a bigger issue.</p>\n\n </body>\n</html>"},
|
&jsonmessage.JSONError{Code: 502, Message: "<!doctype html>\n<html class=\"no-js\" lang=\"\">\n <head>\n </head>\n <body>\n <h1>Oops, there was an error!</h1>\n <p>We have been contacted of this error, feel free to check out <a href=\"http://status.docker.com/\">status.docker.com</a>\n to see if there is a bigger issue.</p>\n\n </body>\n</html>"},
|
||||||
"because the registry is temporarily unavailbe",
|
"because the registry is temporarily unavailable",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
|
@ -42,6 +42,7 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/kubelet/lifecycle"
|
"k8s.io/kubernetes/pkg/kubelet/lifecycle"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/metrics"
|
"k8s.io/kubernetes/pkg/kubelet/metrics"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/network"
|
"k8s.io/kubernetes/pkg/kubelet/network"
|
||||||
|
"k8s.io/kubernetes/pkg/kubelet/network/hairpin"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/prober"
|
"k8s.io/kubernetes/pkg/kubelet/prober"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/qos"
|
"k8s.io/kubernetes/pkg/kubelet/qos"
|
||||||
kubeletTypes "k8s.io/kubernetes/pkg/kubelet/types"
|
kubeletTypes "k8s.io/kubernetes/pkg/kubelet/types"
|
||||||
@ -1734,6 +1735,16 @@ func (dm *DockerManager) SyncPod(pod *api.Pod, runningPod kubecontainer.Pod, pod
|
|||||||
glog.Errorf("Failed to create pod infra container: %v; Skipping pod %q", err, podFullName)
|
glog.Errorf("Failed to create pod infra container: %v; Skipping pod %q", err, podFullName)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setup the host interface (FIXME: move to networkPlugin when ready)
|
||||||
|
podInfraContainer, err := dm.client.InspectContainer(string(podInfraContainerID))
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Failed to inspect pod infra container: %v; Skipping pod %q", err, podFullName)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = hairpin.SetUpContainer(podInfraContainer.State.Pid, "eth0"); err != nil {
|
||||||
|
glog.Warningf("Hairpin setup failed for pod %q: %v", podFullName, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start everything
|
// Start everything
|
||||||
|
@ -885,7 +885,7 @@ func TestSyncPodCreateNetAndContainer(t *testing.T) {
|
|||||||
runSyncPod(t, dm, fakeDocker, pod, nil)
|
runSyncPod(t, dm, fakeDocker, pod, nil)
|
||||||
verifyCalls(t, fakeDocker, []string{
|
verifyCalls(t, fakeDocker, []string{
|
||||||
// Create pod infra container.
|
// Create pod infra container.
|
||||||
"create", "start", "inspect_container",
|
"create", "start", "inspect_container", "inspect_container",
|
||||||
// Create container.
|
// Create container.
|
||||||
"create", "start", "inspect_container",
|
"create", "start", "inspect_container",
|
||||||
})
|
})
|
||||||
@ -934,7 +934,7 @@ func TestSyncPodCreatesNetAndContainerPullsImage(t *testing.T) {
|
|||||||
|
|
||||||
verifyCalls(t, fakeDocker, []string{
|
verifyCalls(t, fakeDocker, []string{
|
||||||
// Create pod infra container.
|
// Create pod infra container.
|
||||||
"create", "start", "inspect_container",
|
"create", "start", "inspect_container", "inspect_container",
|
||||||
// Create container.
|
// Create container.
|
||||||
"create", "start", "inspect_container",
|
"create", "start", "inspect_container",
|
||||||
})
|
})
|
||||||
@ -1027,7 +1027,7 @@ func TestSyncPodDeletesWithNoPodInfraContainer(t *testing.T) {
|
|||||||
// Kill the container since pod infra container is not running.
|
// Kill the container since pod infra container is not running.
|
||||||
"stop",
|
"stop",
|
||||||
// Create pod infra container.
|
// Create pod infra container.
|
||||||
"create", "start", "inspect_container",
|
"create", "start", "inspect_container", "inspect_container",
|
||||||
// Create container.
|
// Create container.
|
||||||
"create", "start", "inspect_container",
|
"create", "start", "inspect_container",
|
||||||
})
|
})
|
||||||
@ -2093,7 +2093,7 @@ func TestSyncPodWithTerminationLog(t *testing.T) {
|
|||||||
runSyncPod(t, dm, fakeDocker, pod, nil)
|
runSyncPod(t, dm, fakeDocker, pod, nil)
|
||||||
verifyCalls(t, fakeDocker, []string{
|
verifyCalls(t, fakeDocker, []string{
|
||||||
// Create pod infra container.
|
// Create pod infra container.
|
||||||
"create", "start", "inspect_container",
|
"create", "start", "inspect_container", "inspect_container",
|
||||||
// Create container.
|
// Create container.
|
||||||
"create", "start", "inspect_container",
|
"create", "start", "inspect_container",
|
||||||
})
|
})
|
||||||
@ -2132,7 +2132,7 @@ func TestSyncPodWithHostNetwork(t *testing.T) {
|
|||||||
|
|
||||||
verifyCalls(t, fakeDocker, []string{
|
verifyCalls(t, fakeDocker, []string{
|
||||||
// Create pod infra container.
|
// Create pod infra container.
|
||||||
"create", "start", "inspect_container",
|
"create", "start", "inspect_container", "inspect_container",
|
||||||
// Create container.
|
// Create container.
|
||||||
"create", "start", "inspect_container",
|
"create", "start", "inspect_container",
|
||||||
})
|
})
|
||||||
|
100
pkg/kubelet/network/hairpin/hairpin.go
Normal file
100
pkg/kubelet/network/hairpin/hairpin.go
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
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 hairpin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"path"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"k8s.io/kubernetes/pkg/util/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
sysfsNetPath = "/sys/devices/virtual/net"
|
||||||
|
hairpinModeRelativePath = "brport/hairpin_mode"
|
||||||
|
hairpinEnable = "1"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ethtoolOutputRegex = regexp.MustCompile("peer_ifindex: (\\d+)")
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetUpContainer(containerPid int, containerInterfaceName string) error {
|
||||||
|
e := exec.New()
|
||||||
|
return setUpContainerInternal(e, containerPid, containerInterfaceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setUpContainerInternal(e exec.Interface, containerPid int, containerInterfaceName string) error {
|
||||||
|
hostIfName, err := findPairInterfaceOfContainerInterface(e, containerPid, containerInterfaceName)
|
||||||
|
if err != nil {
|
||||||
|
glog.Infof("Unable to find pair interface, setting up all interfaces: %v", err)
|
||||||
|
return setUpAllInterfaces()
|
||||||
|
}
|
||||||
|
return setUpInterface(hostIfName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func findPairInterfaceOfContainerInterface(e exec.Interface, containerPid int, containerInterfaceName string) (string, error) {
|
||||||
|
nsenterPath, err := e.LookPath("nsenter")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
ethtoolPath, err := e.LookPath("ethtool")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// Get container's interface index
|
||||||
|
output, err := e.Command(nsenterPath, "-t", fmt.Sprintf("%d", containerPid), "-n", "-F", "--", ethtoolPath, "--statistics", containerInterfaceName).CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Unable to query interface %s of container %d: %v", containerInterfaceName, containerPid, err)
|
||||||
|
}
|
||||||
|
// look for peer_ifindex
|
||||||
|
match := ethtoolOutputRegex.FindSubmatch(output)
|
||||||
|
if match == nil {
|
||||||
|
return "", fmt.Errorf("No peer_ifindex in interface statistics for %s of container %d", containerInterfaceName, containerPid)
|
||||||
|
}
|
||||||
|
peerIfIndex, err := strconv.Atoi(string(match[1]))
|
||||||
|
if err != nil { // seems impossible (\d+ not numeric)
|
||||||
|
return "", fmt.Errorf("peer_ifindex wasn't numeric: %s: %v", match[1], err)
|
||||||
|
}
|
||||||
|
iface, err := net.InterfaceByIndex(peerIfIndex)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return iface.Name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setUpAllInterfaces() error {
|
||||||
|
interfaces, err := net.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, netIf := range interfaces {
|
||||||
|
setUpInterface(netIf.Name) // ignore errors
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setUpInterface(ifName string) error {
|
||||||
|
glog.V(3).Infof("Enabling hairpin on interface %s", ifName)
|
||||||
|
hairpinModeFile := path.Join(sysfsNetPath, ifName, hairpinModeRelativePath)
|
||||||
|
return ioutil.WriteFile(hairpinModeFile, []byte(hairpinEnable), 0644)
|
||||||
|
}
|
96
pkg/kubelet/network/hairpin/hairpin_test.go
Normal file
96
pkg/kubelet/network/hairpin/hairpin_test.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
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 hairpin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/util/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFindPairInterfaceOfContainerInterface(t *testing.T) {
|
||||||
|
// there should be at least "lo" on any system
|
||||||
|
interfaces, _ := net.Interfaces()
|
||||||
|
validOutput := fmt.Sprintf("garbage\n peer_ifindex: %d", interfaces[0].Index)
|
||||||
|
invalidOutput := fmt.Sprintf("garbage\n unknown: %d", interfaces[0].Index)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
output string
|
||||||
|
err error
|
||||||
|
expectedName string
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
output: validOutput,
|
||||||
|
expectedName: interfaces[0].Name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
output: invalidOutput,
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
output: validOutput,
|
||||||
|
err: errors.New("error"),
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
fcmd := exec.FakeCmd{
|
||||||
|
CombinedOutputScript: []exec.FakeCombinedOutputAction{
|
||||||
|
func() ([]byte, error) { return []byte(test.output), test.err },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
fexec := exec.FakeExec{
|
||||||
|
CommandScript: []exec.FakeCommandAction{
|
||||||
|
func(cmd string, args ...string) exec.Cmd {
|
||||||
|
return exec.InitFakeCmd(&fcmd, cmd, args...)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
LookPathFunc: func(file string) (string, error) {
|
||||||
|
return fmt.Sprintf("/fake-bin/%s", file), nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
name, err := findPairInterfaceOfContainerInterface(&fexec, 123, "eth0")
|
||||||
|
if test.expectErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("unexpected non-error")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if name != test.expectedName {
|
||||||
|
t.Errorf("unexpected name: %s (expected: %s)", name, test.expectedName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetUpInterface(t *testing.T) {
|
||||||
|
err := setUpInterface("non-existent")
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("unexpected non-error")
|
||||||
|
}
|
||||||
|
hairpinModeFile := fmt.Sprintf("%s/%s/%s", sysfsNetPath, "non-existent", hairpinModeRelativePath)
|
||||||
|
if !strings.Contains(fmt.Sprintf("%v", err), hairpinModeFile) {
|
||||||
|
t.Errorf("should have tried to open %s", hairpinModeFile)
|
||||||
|
}
|
||||||
|
}
|
@ -147,6 +147,13 @@ func TestRunOnce(t *testing.T) {
|
|||||||
State: docker.State{Running: true, Pid: 42},
|
State: docker.State{Running: true, Pid: 42},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "syncPod",
|
||||||
|
container: docker.Container{
|
||||||
|
Config: &docker.Config{Image: "someimage"},
|
||||||
|
State: docker.State{Running: true, Pid: 42},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
t: t,
|
t: t,
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,9 @@ type Interface interface {
|
|||||||
// Command returns a Cmd instance which can be used to run a single command.
|
// Command returns a Cmd instance which can be used to run a single command.
|
||||||
// This follows the pattern of package os/exec.
|
// This follows the pattern of package os/exec.
|
||||||
Command(cmd string, args ...string) Cmd
|
Command(cmd string, args ...string) Cmd
|
||||||
|
|
||||||
|
// LookPath wraps os/exec.LookPath
|
||||||
|
LookPath(file string) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cmd is an interface that presents an API that is very similar to Cmd from os/exec.
|
// Cmd is an interface that presents an API that is very similar to Cmd from os/exec.
|
||||||
@ -62,6 +65,11 @@ func (executor *executor) Command(cmd string, args ...string) Cmd {
|
|||||||
return (*cmdWrapper)(osexec.Command(cmd, args...))
|
return (*cmdWrapper)(osexec.Command(cmd, args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LookPath is part of the Interface interface
|
||||||
|
func (executor *executor) LookPath(file string) (string, error) {
|
||||||
|
return osexec.LookPath(file)
|
||||||
|
}
|
||||||
|
|
||||||
// Wraps exec.Cmd so we can capture errors.
|
// Wraps exec.Cmd so we can capture errors.
|
||||||
type cmdWrapper osexec.Cmd
|
type cmdWrapper osexec.Cmd
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
package exec
|
package exec
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
osexec "os/exec"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -81,3 +82,13 @@ func TestExecutorWithArgs(t *testing.T) {
|
|||||||
t.Errorf("unexpected output: %q", string(out))
|
t.Errorf("unexpected output: %q", string(out))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLookPath(t *testing.T) {
|
||||||
|
ex := New()
|
||||||
|
|
||||||
|
shExpected, _ := osexec.LookPath("sh")
|
||||||
|
sh, _ := ex.LookPath("sh")
|
||||||
|
if sh != shExpected {
|
||||||
|
t.Errorf("unexpected result for LookPath: got %s, expected %s", sh, shExpected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
type FakeExec struct {
|
type FakeExec struct {
|
||||||
CommandScript []FakeCommandAction
|
CommandScript []FakeCommandAction
|
||||||
CommandCalls int
|
CommandCalls int
|
||||||
|
LookPathFunc func(string) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type FakeCommandAction func(cmd string, args ...string) Cmd
|
type FakeCommandAction func(cmd string, args ...string) Cmd
|
||||||
@ -37,6 +38,10 @@ func (fake *FakeExec) Command(cmd string, args ...string) Cmd {
|
|||||||
return fake.CommandScript[i](cmd, args...)
|
return fake.CommandScript[i](cmd, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fake *FakeExec) LookPath(file string) (string, error) {
|
||||||
|
return fake.LookPathFunc(file)
|
||||||
|
}
|
||||||
|
|
||||||
// A simple scripted Cmd type.
|
// A simple scripted Cmd type.
|
||||||
type FakeCmd struct {
|
type FakeCmd struct {
|
||||||
Argv []string
|
Argv []string
|
||||||
|
Loading…
Reference in New Issue
Block a user