diff --git a/pkg/kubelet/docker.go b/pkg/kubelet/docker.go index cf152712f12..b7b9217e449 100644 --- a/pkg/kubelet/docker.go +++ b/pkg/kubelet/docker.go @@ -20,6 +20,7 @@ import ( "errors" "fmt" "math/rand" + "os/exec" "strings" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" @@ -63,6 +64,30 @@ func NewDockerPuller(client DockerInterface) DockerPuller { } } +type dockerContainerCommandRunner struct{} + +func (d *dockerContainerCommandRunner) getRunInContainerCommand(containerID string, cmd []string) (*exec.Cmd, error) { + args := append([]string{"exec"}, cmd...) + command := exec.Command("/usr/sbin/nsinit", args...) + command.Dir = fmt.Sprintf("/var/lib/docker/execdriver/native/%s", containerID) + return command, nil +} + +// RunInContainer uses nsinit to run the command inside the container identified by containerID +func (d *dockerContainerCommandRunner) RunInContainer(containerID string, cmd []string) ([]byte, error) { + c, err := d.getRunInContainerCommand(containerID, cmd) + if err != nil { + return nil, err + } + return c.CombinedOutput() +} + +// NewDockerContainerCommandRunner creates a ContainerCommandRunner which uses nsinit to run a command +// inside a container. +func NewDockerContainerCommandRunner() ContainerCommandRunner { + return &dockerContainerCommandRunner{} +} + func (p dockerPuller) Pull(image string) error { image, tag := parseImageName(image) diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index 3cf1e356e5a..a19b17ec8c2 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -86,6 +86,10 @@ func NewIntegrationTestKubelet(hn string, dc DockerInterface) *Kubelet { } } +type ContainerCommandRunner interface { + RunInContainer(containerID string, cmd []string) ([]byte, error) +} + // Kubelet is the main kubelet implementation. type Kubelet struct { hostname string @@ -103,6 +107,8 @@ type Kubelet struct { dockerPuller DockerPuller // Optional, defaults to /logs/ from /var/log logServer http.Handler + // Optional, defaults to simple Docker implementation + runner ContainerCommandRunner } // Run starts the kubelet reacting to config updates @@ -659,3 +665,20 @@ func (kl *Kubelet) ServeLogs(w http.ResponseWriter, req *http.Request) { // TODO: whitelist logs we are willing to serve kl.logServer.ServeHTTP(w, req) } + +// Run a command in a container, returns the combined stdout, stderr as an array of bytes +func (kl *Kubelet) RunInContainer(pod *Pod, container string, cmd []string) ([]byte, error) { + if kl.runner == nil { + return nil, fmt.Errorf("no runner specified.") + } + podFullName := GetPodFullName(pod) + dockerContainers, err := getKubeletDockerContainers(kl.dockerClient) + if err != nil { + return nil, err + } + dockerContainer, found := dockerContainers.FindPodContainer(podFullName, container) + if !found { + return nil, fmt.Errorf("container not found (%s)", container) + } + return kl.runner.RunInContainer(dockerContainer.ID, cmd) +} diff --git a/pkg/kubelet/kubelet_test.go b/pkg/kubelet/kubelet_test.go index a6ea820f1ae..7360985b6a0 100644 --- a/pkg/kubelet/kubelet_test.go +++ b/pkg/kubelet/kubelet_test.go @@ -843,6 +843,86 @@ func TestGetContainerInfoOnNonExistContainer(t *testing.T) { mockCadvisor.AssertExpectations(t) } +type fakeContainerCommandRunner struct { + Cmd []string + ID string + E error +} + +func (f *fakeContainerCommandRunner) RunInContainer(id string, cmd []string) ([]byte, error) { + f.Cmd = cmd + f.ID = id + return []byte{}, f.E +} + +func TestRunInContainerNoSuchPod(t *testing.T) { + fakeCommandRunner := fakeContainerCommandRunner{} + kubelet, _, fakeDocker := makeTestKubelet(t) + fakeDocker.containerList = []docker.APIContainers{} + kubelet.runner = &fakeCommandRunner + + podName := "podFoo" + podNamespace := "etcd" + containerName := "containerFoo" + output, err := kubelet.RunInContainer( + &Pod{Name: podName, Namespace: podNamespace}, + containerName, + []string{"ls"}) + if output != nil { + t.Errorf("unexpected non-nil command: %v", output) + } + if err == nil { + t.Error("unexpected non-error") + } +} + +func TestRunInContainer(t *testing.T) { + fakeCommandRunner := fakeContainerCommandRunner{} + kubelet, _, fakeDocker := makeTestKubelet(t) + kubelet.runner = &fakeCommandRunner + + containerID := "abc1234" + podName := "podFoo" + podNamespace := "etcd" + containerName := "containerFoo" + + fakeDocker.containerList = []docker.APIContainers{ + { + ID: containerID, + Names: []string{"/k8s--" + containerName + "--" + podName + "." + podNamespace + "--1234"}, + }, + } + + cmd := []string{"ls"} + _, err := kubelet.RunInContainer( + &Pod{Name: podName, Namespace: podNamespace}, + containerName, + cmd) + if fakeCommandRunner.ID != containerID { + t.Errorf("unexected ID: %s", fakeCommandRunner.ID) + } + if !reflect.DeepEqual(fakeCommandRunner.Cmd, cmd) { + t.Errorf("unexpected commnd: %s", fakeCommandRunner.Cmd) + } + if err != nil { + t.Errorf("unexpected error: %v", err) + } +} + +func TestDockerContainerCommand(t *testing.T) { + runner := dockerContainerCommandRunner{} + containerID := "1234" + command := []string{"ls"} + cmd, _ := runner.getRunInContainerCommand(containerID, command) + if cmd.Dir != "/var/lib/docker/execdriver/native/"+containerID { + t.Errorf("unexpected command CWD: %s", cmd.Dir) + } + if !reflect.DeepEqual(cmd.Args, []string{"/usr/sbin/nsinit", "exec", "ls"}) { + t.Errorf("unexpectd command args: %s", cmd.Args) + } + +} + var parseImageNameTests = []struct { imageName string name string