diff --git a/pkg/api/types.go b/pkg/api/types.go index 365c0c94a44..88827128df4 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -54,7 +54,11 @@ type ContainerManifest struct { Version string `yaml:"version" json:"version"` // Required: This must be a DNS_SUBDOMAIN. // TODO: ID on Manifest is deprecated and will be removed in the future. - ID string `yaml:"id" json:"id"` + ID string `yaml:"id" json:"id"` + // TODO: UUID on Manifest is deprecated in the future once we are done + // with the API refactoring. It is required for now to determine the instance + // of a Pod. + UUID string `yaml:"uuid,omitempty" json:"uuid,omitempty"` Volumes []Volume `yaml:"volumes" json:"volumes"` Containers []Container `yaml:"containers" json:"containers"` } diff --git a/pkg/api/v1beta1/types.go b/pkg/api/v1beta1/types.go index 74d576d8049..bc2a9c34fe7 100644 --- a/pkg/api/v1beta1/types.go +++ b/pkg/api/v1beta1/types.go @@ -54,7 +54,11 @@ type ContainerManifest struct { Version string `yaml:"version" json:"version"` // Required: This must be a DNS_SUBDOMAIN. // TODO: ID on Manifest is deprecated and will be removed in the future. - ID string `yaml:"id" json:"id"` + ID string `yaml:"id" json:"id"` + // TODO: UUID on Manifext is deprecated in the future once we are done + // with the API refactory. It is required for now to determine the instance + // of a Pod. + UUID string `yaml:"uuid,omitempty" json:"uuid,omitempty"` Volumes []Volume `yaml:"volumes" json:"volumes"` Containers []Container `yaml:"containers" json:"containers"` } diff --git a/pkg/health/exec.go b/pkg/health/exec.go index 1651f69e5da..efcfec91507 100644 --- a/pkg/health/exec.go +++ b/pkg/health/exec.go @@ -27,7 +27,7 @@ import ( const defaultHealthyRegex = "^OK$" type CommandRunner interface { - RunInContainer(podFullName, containerName string, cmd []string) ([]byte, error) + RunInContainer(podFullName, uuid, containerName string, cmd []string) ([]byte, error) } type ExecHealthChecker struct { @@ -47,7 +47,7 @@ func (e *ExecHealthChecker) HealthCheck(podFullName string, currentState api.Pod if container.LivenessProbe.Exec == nil { return Unknown, fmt.Errorf("Missing exec parameters") } - data, err := e.runner.RunInContainer(podFullName, container.Name, container.LivenessProbe.Exec.Command) + data, err := e.runner.RunInContainer(podFullName, currentState.Manifest.UUID, container.Name, container.LivenessProbe.Exec.Command) glog.V(1).Infof("container %s failed health check: %s", podFullName, string(data)) if err != nil { if IsExitError(err) { diff --git a/pkg/health/exec_test.go b/pkg/health/exec_test.go index 0782b8afd44..44727779a96 100644 --- a/pkg/health/exec_test.go +++ b/pkg/health/exec_test.go @@ -31,7 +31,7 @@ type FakeExec struct { err error } -func (f *FakeExec) RunInContainer(podFullName, container string, cmd []string) ([]byte, error) { +func (f *FakeExec) RunInContainer(podFullName, uuid, container string, cmd []string) ([]byte, error) { f.cmd = cmd return f.out, f.err } diff --git a/pkg/kubelet/config/etcd.go b/pkg/kubelet/config/etcd.go index 07cb87b5c8a..e0d0c785d73 100644 --- a/pkg/kubelet/config/etcd.go +++ b/pkg/kubelet/config/etcd.go @@ -99,7 +99,9 @@ func eventToPods(ev watch.Event) ([]kubelet.Pod, error) { if name == "" { name = fmt.Sprintf("%d", i+1) } - pods = append(pods, kubelet.Pod{Name: name, Manifest: manifest}) + pods = append(pods, kubelet.Pod{ + Name: name, + Manifest: manifest}) } return pods, nil diff --git a/pkg/kubelet/docker.go b/pkg/kubelet/docker.go index eef646c6f66..46cdb788752 100644 --- a/pkg/kubelet/docker.go +++ b/pkg/kubelet/docker.go @@ -109,21 +109,24 @@ func (p dockerPuller) Pull(image string) error { // DockerContainers is a map of containers type DockerContainers map[DockerID]*docker.APIContainers -func (c DockerContainers) FindPodContainer(podFullName, containerName string) (*docker.APIContainers, bool, uint64) { +func (c DockerContainers) FindPodContainer(podFullName, uuid, containerName string) (*docker.APIContainers, bool, uint64) { for _, dockerContainer := range c { - dockerManifestID, dockerContainerName, hash := parseDockerName(dockerContainer.Names[0]) - if dockerManifestID == podFullName && dockerContainerName == containerName { + dockerManifestID, dockerUUID, dockerContainerName, hash := parseDockerName(dockerContainer.Names[0]) + if dockerManifestID == podFullName && + (uuid == "" || dockerUUID == uuid) && + dockerContainerName == containerName { return dockerContainer, true, hash } } return nil, false, 0 } +// Note, this might return containers belong to a different Pod instance with the same name func (c DockerContainers) FindContainersByPodFullName(podFullName string) map[string]*docker.APIContainers { containers := make(map[string]*docker.APIContainers) for _, dockerContainer := range c { - dockerManifestID, dockerContainerName, _ := parseDockerName(dockerContainer.Names[0]) + dockerManifestID, _, dockerContainerName, _ := parseDockerName(dockerContainer.Names[0]) if dockerManifestID == podFullName { containers[dockerContainerName] = dockerContainer } @@ -154,7 +157,7 @@ func getKubeletDockerContainers(client DockerInterface) (DockerContainers, error var ErrNoContainersInPod = errors.New("no containers exist for this pod") // GetDockerPodInfo returns docker info for all containers in the pod/manifest. -func getDockerPodInfo(client DockerInterface, podFullName string) (api.PodInfo, error) { +func getDockerPodInfo(client DockerInterface, podFullName, uuid string) (api.PodInfo, error) { info := api.PodInfo{} containers, err := client.ListContainers(docker.ListContainersOptions{}) @@ -163,10 +166,13 @@ func getDockerPodInfo(client DockerInterface, podFullName string) (api.PodInfo, } for _, value := range containers { - dockerManifestID, dockerContainerName, _ := parseDockerName(value.Names[0]) + dockerManifestID, dockerUUID, dockerContainerName, _ := parseDockerName(value.Names[0]) if dockerManifestID != podFullName { continue } + if uuid != "" && dockerUUID != uuid { + continue + } inspectResult, err := client.InspectContainer(value.ID) if err != nil { return nil, err @@ -211,12 +217,25 @@ func hashContainer(container *api.Container) uint64 { func buildDockerName(pod *Pod, container *api.Container) string { containerName := escapeDash(container.Name) + "." + strconv.FormatUint(hashContainer(container), 16) // Note, manifest.ID could be blank. - return fmt.Sprintf("%s--%s--%s--%08x", containerNamePrefix, containerName, escapeDash(GetPodFullName(pod)), rand.Uint32()) + if len(pod.Manifest.UUID) == 0 { + return fmt.Sprintf("%s--%s--%s--%08x", + containerNamePrefix, + containerName, + escapeDash(GetPodFullName(pod)), + rand.Uint32()) + } else { + return fmt.Sprintf("%s--%s--%s--%s--%08x", + containerNamePrefix, + containerName, + escapeDash(GetPodFullName(pod)), + escapeDash(pod.Manifest.UUID), + rand.Uint32()) + } } // Upacks a container name, returning the pod full name and container name we would have used to // construct the docker name. If the docker name isn't one we created, we may return empty strings. -func parseDockerName(name string) (podFullName, containerName string, hash uint64) { +func parseDockerName(name string) (podFullName, uuid, containerName string, hash uint64) { // For some reason docker appears to be appending '/' to names. // If it's there, strip it. if name[0] == '/' { @@ -240,11 +259,14 @@ func parseDockerName(name string) (podFullName, containerName string, hash uint6 if len(parts) > 2 { podFullName = unescapeDash(parts[2]) } + if len(parts) > 4 { + uuid = unescapeDash(parts[3]) + } return } // Parses image name including a tag and returns image name and tag. -// TODO: Future Docker versions can parse the tag on daemon side, see: +// TODO: Future Docker versions can parse the tag on daemon side, see // https://github.com/dotcloud/docker/issues/6876 // So this can be deprecated at some point. func parseImageName(image string) (string, string) { diff --git a/pkg/kubelet/handlers.go b/pkg/kubelet/handlers.go index 8ae6f7e0771..bf207c07f90 100644 --- a/pkg/kubelet/handlers.go +++ b/pkg/kubelet/handlers.go @@ -30,8 +30,8 @@ type execActionHandler struct { kubelet *Kubelet } -func (e *execActionHandler) Run(podFullName string, container *api.Container, handler *api.Handler) error { - _, err := e.kubelet.RunInContainer(podFullName, container.Name, handler.Exec.Command) +func (e *execActionHandler) Run(podFullName, uuid string, container *api.Container, handler *api.Handler) error { + _, err := e.kubelet.RunInContainer(podFullName, uuid, container.Name, handler.Exec.Command) return err } @@ -65,11 +65,11 @@ func ResolvePort(portReference util.IntOrString, container *api.Container) (int, return -1, fmt.Errorf("couldn't find port: %v in %v", portReference, container) } -func (h *httpActionHandler) Run(podFullName string, container *api.Container, handler *api.Handler) error { +func (h *httpActionHandler) Run(podFullName, uuid string, container *api.Container, handler *api.Handler) error { host := handler.HTTPGet.Host if len(host) == 0 { var info api.PodInfo - info, err := h.kubelet.GetPodInfo(podFullName) + info, err := h.kubelet.GetPodInfo(podFullName, uuid) if err != nil { glog.Errorf("unable to get pod info, event handlers may be invalid.") return err diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index b6f0c5ad9fc..9ff13f3ada2 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -292,7 +292,7 @@ func (kl *Kubelet) mountExternalVolumes(manifest *api.ContainerManifest) (volume // A basic interface that knows how to execute handlers type actionHandler interface { - Run(podFullName string, container *api.Container, handler *api.Handler) error + Run(podFullName, uuid string, container *api.Container, handler *api.Handler) error } func (kl *Kubelet) newActionHandler(handler *api.Handler) actionHandler { @@ -307,12 +307,12 @@ func (kl *Kubelet) newActionHandler(handler *api.Handler) actionHandler { } } -func (kl *Kubelet) runHandler(podFullName string, container *api.Container, handler *api.Handler) error { +func (kl *Kubelet) runHandler(podFullName, uuid string, container *api.Container, handler *api.Handler) error { actionHandler := kl.newActionHandler(handler) if actionHandler == nil { return fmt.Errorf("invalid handler") } - return actionHandler.Run(podFullName, container, handler) + return actionHandler.Run(podFullName, uuid, container, handler) } // Run a single container from a pod. Returns the docker container ID @@ -344,7 +344,7 @@ func (kl *Kubelet) runContainer(pod *Pod, container *api.Container, podVolumes v NetworkMode: netMode, }) if err == nil && container.Lifecycle != nil && container.Lifecycle.PostStart != nil { - handlerErr := kl.runHandler(GetPodFullName(pod), container, container.Lifecycle.PostStart) + handlerErr := kl.runHandler(GetPodFullName(pod), pod.Manifest.UUID, container, container.Lifecycle.PostStart) if handlerErr != nil { kl.killContainerByID(dockerContainer.ID, "") return DockerID(""), fmt.Errorf("failed to call event handler: %v", handlerErr) @@ -364,12 +364,13 @@ func (kl *Kubelet) killContainerByID(ID, name string) error { if len(name) == 0 { return err } - podFullName, containerName, _ := parseDockerName(name) + podFullName, uuid, containerName, _ := parseDockerName(name) kl.LogEvent(&api.Event{ Event: "STOP", Manifest: &api.ContainerManifest{ //TODO: This should be reported using either the apiserver schema or the kubelet schema - ID: podFullName, + ID: podFullName, + UUID: uuid, }, Container: &api.Container{ Name: containerName, @@ -408,7 +409,7 @@ func (kl *Kubelet) deleteAllContainers(pod *Pod, podFullName string, dockerConta errs := make(chan error, len(pod.Manifest.Containers)) wg := sync.WaitGroup{} for _, container := range pod.Manifest.Containers { - if dockerContainer, found, _ := dockerContainers.FindPodContainer(podFullName, container.Name); found { + if dockerContainer, found, _ := dockerContainers.FindPodContainer(podFullName, pod.Manifest.UUID, container.Name); found { count++ wg.Add(1) go func() { @@ -437,12 +438,13 @@ type empty struct{} func (kl *Kubelet) syncPod(pod *Pod, dockerContainers DockerContainers) error { podFullName := GetPodFullName(pod) + uuid := pod.Manifest.UUID containersToKeep := make(map[DockerID]empty) killedContainers := make(map[DockerID]empty) // Make sure we have a network container var netID DockerID - if networkDockerContainer, found, _ := dockerContainers.FindPodContainer(podFullName, networkContainerName); found { + if networkDockerContainer, found, _ := dockerContainers.FindPodContainer(podFullName, uuid, networkContainerName); found { netID = DockerID(networkDockerContainer.ID) } else { glog.Infof("Network container doesn't exist, creating") @@ -473,10 +475,11 @@ func (kl *Kubelet) syncPod(pod *Pod, dockerContainers DockerContainers) error { return err } - podState := api.PodState{} - info, err := kl.GetPodInfo(podFullName) + podState := api.PodState{Manifest: api.ContainerManifest{UUID: uuid}} + info, err := kl.GetPodInfo(podFullName, uuid) if err != nil { - glog.Errorf("Unable to get pod info, health checks may be invalid.") + glog.Errorf("Unable to get pod with name %s and uuid %s info, health checks may be invalid.", + podFullName, uuid) } netInfo, found := info[networkContainerName] if found && netInfo.NetworkSettings != nil { @@ -485,9 +488,8 @@ func (kl *Kubelet) syncPod(pod *Pod, dockerContainers DockerContainers) error { for _, container := range pod.Manifest.Containers { expectedHash := hashContainer(&container) - if dockerContainer, found, hash := dockerContainers.FindPodContainer(podFullName, container.Name); found { + if dockerContainer, found, hash := dockerContainers.FindPodContainer(podFullName, uuid, container.Name); found { containerID := DockerID(dockerContainer.ID) - glog.Infof("pod %s container %s exists as %v", podFullName, container.Name, containerID) glog.V(1).Infof("pod %s container %s exists as %v", podFullName, container.Name, containerID) // look for changes in the container. @@ -530,8 +532,8 @@ func (kl *Kubelet) syncPod(pod *Pod, dockerContainers DockerContainers) error { // Kill any containers in this pod which were not identified above (guards against duplicates). for id, container := range dockerContainers { - curPodFullName, _, _ := parseDockerName(container.Names[0]) - if curPodFullName == podFullName { + curPodFullName, curUUID, _, _ := parseDockerName(container.Names[0]) + if curPodFullName == podFullName && curUUID == uuid { // Don't kill containers we want to keep or those we already killed. _, keep := containersToKeep[id] _, killed := killedContainers[id] @@ -549,6 +551,7 @@ func (kl *Kubelet) syncPod(pod *Pod, dockerContainers DockerContainers) error { type podContainer struct { podFullName string + uuid string containerName string } @@ -601,11 +604,12 @@ func (kl *Kubelet) SyncPods(pods []Pod) error { for i := range pods { pod := &pods[i] podFullName := GetPodFullName(pod) + uuid := pod.Manifest.UUID // Add all containers (including net) to the map. - desiredContainers[podContainer{podFullName, networkContainerName}] = empty{} + desiredContainers[podContainer{podFullName, uuid, networkContainerName}] = empty{} for _, cont := range pod.Manifest.Containers { - desiredContainers[podContainer{podFullName, cont.Name}] = empty{} + desiredContainers[podContainer{podFullName, uuid, cont.Name}] = empty{} } // Run the sync in an async manifest worker. @@ -625,8 +629,8 @@ func (kl *Kubelet) SyncPods(pods []Pod) error { } for _, container := range existingContainers { // Don't kill containers that are in the desired pods. - podFullName, containerName, _ := parseDockerName(container.Names[0]) - if _, ok := desiredContainers[podContainer{podFullName, containerName}]; !ok { + podFullName, uuid, containerName, _ := parseDockerName(container.Names[0]) + if _, ok := desiredContainers[podContainer{podFullName, uuid, containerName}]; !ok { err = kl.killContainer(container) if err != nil { glog.Errorf("Error killing container: %v", err) @@ -716,12 +720,12 @@ func (kl *Kubelet) statsFromContainerPath(containerPath string, req *info.Contai } // GetPodInfo returns information from Docker about the containers in a pod -func (kl *Kubelet) GetPodInfo(podFullName string) (api.PodInfo, error) { - return getDockerPodInfo(kl.dockerClient, podFullName) +func (kl *Kubelet) GetPodInfo(podFullName, uuid string) (api.PodInfo, error) { + return getDockerPodInfo(kl.dockerClient, podFullName, uuid) } // GetContainerInfo returns stats (from Cadvisor) for a container. -func (kl *Kubelet) GetContainerInfo(podFullName, containerName string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) { +func (kl *Kubelet) GetContainerInfo(podFullName, uuid, containerName string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) { if kl.cadvisorClient == nil { return nil, nil } @@ -729,7 +733,7 @@ func (kl *Kubelet) GetContainerInfo(podFullName, containerName string, req *info if err != nil { return nil, err } - dockerContainer, found, _ := dockerContainers.FindPodContainer(podFullName, containerName) + dockerContainer, found, _ := dockerContainers.FindPodContainer(podFullName, uuid, containerName) if !found { return nil, errors.New("couldn't find container") } @@ -766,7 +770,7 @@ func (kl *Kubelet) ServeLogs(w http.ResponseWriter, req *http.Request) { } // Run a command in a container, returns the combined stdout, stderr as an array of bytes -func (kl *Kubelet) RunInContainer(podFullName, container string, cmd []string) ([]byte, error) { +func (kl *Kubelet) RunInContainer(podFullName, uuid, container string, cmd []string) ([]byte, error) { if kl.runner == nil { return nil, fmt.Errorf("no runner specified.") } @@ -774,7 +778,7 @@ func (kl *Kubelet) RunInContainer(podFullName, container string, cmd []string) ( if err != nil { return nil, err } - dockerContainer, found, _ := dockerContainers.FindPodContainer(podFullName, container) + dockerContainer, found, _ := dockerContainers.FindPodContainer(podFullName, uuid, container) if !found { return nil, fmt.Errorf("container not found (%s)", container) } diff --git a/pkg/kubelet/kubelet_test.go b/pkg/kubelet/kubelet_test.go index 92a4eac5274..bc9dbee5997 100644 --- a/pkg/kubelet/kubelet_test.go +++ b/pkg/kubelet/kubelet_test.go @@ -84,7 +84,7 @@ func verifyPackUnpack(t *testing.T, podNamespace, podName, containerName string) container, ) podFullName := fmt.Sprintf("%s.%s", podName, podNamespace) - returnedPodFullName, returnedContainerName, hash := parseDockerName(name) + returnedPodFullName, _, returnedContainerName, hash := parseDockerName(name) if podFullName != returnedPodFullName || containerName != returnedContainerName || computedHash != hash { t.Errorf("For (%s, %s, %d), unpacked (%s, %s, %d)", podFullName, containerName, computedHash, returnedPodFullName, returnedContainerName, hash) } @@ -108,7 +108,7 @@ func TestContainerManifestNaming(t *testing.T) { name := fmt.Sprintf("k8s--%s--%s.%s--12345", container.Name, pod.Name, pod.Namespace) podFullName := fmt.Sprintf("%s.%s", pod.Name, pod.Namespace) - returnedPodFullName, returnedContainerName, hash := parseDockerName(name) + returnedPodFullName, _, returnedContainerName, hash := parseDockerName(name) if returnedPodFullName != podFullName || returnedContainerName != container.Name || hash != 0 { t.Errorf("unexpected parse: %s %s %d", returnedPodFullName, returnedContainerName, hash) } @@ -139,13 +139,13 @@ func TestGetContainerID(t *testing.T) { t.Errorf("Expected %#v, Got %#v", fakeDocker.containerList, dockerContainers) } verifyCalls(t, fakeDocker, []string{"list"}) - dockerContainer, found, _ := dockerContainers.FindPodContainer("qux", "foo") + dockerContainer, found, _ := dockerContainers.FindPodContainer("qux", "", "foo") if dockerContainer == nil || !found { t.Errorf("Failed to find container %#v", dockerContainer) } fakeDocker.clearCalls() - dockerContainer, found, _ = dockerContainers.FindPodContainer("foobar", "foo") + dockerContainer, found, _ = dockerContainers.FindPodContainer("foobar", "", "foo") verifyCalls(t, fakeDocker, []string{}) if dockerContainer != nil || found { t.Errorf("Should not have found container %#v", dockerContainer) @@ -943,7 +943,7 @@ func TestGetContainerInfo(t *testing.T) { }, } - stats, err := kubelet.GetContainerInfo("qux", "foo", req) + stats, err := kubelet.GetContainerInfo("qux", "", "foo", req) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -1009,11 +1009,11 @@ func TestGetContainerInfoWithoutCadvisor(t *testing.T) { ID: "foobar", // pod id: qux // container id: foo - Names: []string{"/k8s--foo--qux--1234"}, + Names: []string{"/k8s--foo--qux--uuid--1234"}, }, } - stats, _ := kubelet.GetContainerInfo("qux", "foo", nil) + stats, _ := kubelet.GetContainerInfo("qux", "uuid", "foo", nil) // When there's no cAdvisor, the stats should be either nil or empty if stats == nil { return @@ -1047,11 +1047,11 @@ func TestGetContainerInfoWhenCadvisorFailed(t *testing.T) { ID: containerID, // pod id: qux // container id: foo - Names: []string{"/k8s--foo--qux--1234"}, + Names: []string{"/k8s--foo--qux--uuid--1234"}, }, } - stats, err := kubelet.GetContainerInfo("qux", "foo", req) + stats, err := kubelet.GetContainerInfo("qux", "uuid", "foo", req) if stats != nil { t.Errorf("non-nil stats on error") } @@ -1072,7 +1072,7 @@ func TestGetContainerInfoOnNonExistContainer(t *testing.T) { kubelet.cadvisorClient = mockCadvisor fakeDocker.containerList = []docker.APIContainers{} - stats, _ := kubelet.GetContainerInfo("qux", "foo", nil) + stats, _ := kubelet.GetContainerInfo("qux", "", "foo", nil) if stats != nil { t.Errorf("non-nil stats on non exist container") } @@ -1102,6 +1102,7 @@ func TestRunInContainerNoSuchPod(t *testing.T) { containerName := "containerFoo" output, err := kubelet.RunInContainer( GetPodFullName(&Pod{Name: podName, Namespace: podNamespace}), + "", containerName, []string{"ls"}) if output != nil { @@ -1132,6 +1133,7 @@ func TestRunInContainer(t *testing.T) { cmd := []string{"ls"} _, err := kubelet.RunInContainer( GetPodFullName(&Pod{Name: podName, Namespace: podNamespace}), + "", containerName, cmd) if fakeCommandRunner.ID != containerID { @@ -1209,7 +1211,7 @@ func TestRunHandlerExec(t *testing.T) { }, }, } - err := kubelet.runHandler(podName+"."+podNamespace, &container, container.Lifecycle.PostStart) + err := kubelet.runHandler(podName+"."+podNamespace, "", &container, container.Lifecycle.PostStart) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -1251,7 +1253,7 @@ func TestRunHandlerHttp(t *testing.T) { }, }, } - err := kubelet.runHandler(podName+"."+podNamespace, &container, container.Lifecycle.PostStart) + err := kubelet.runHandler(podName+"."+podNamespace, "", &container, container.Lifecycle.PostStart) if err != nil { t.Errorf("unexpected error: %v", err) } diff --git a/pkg/kubelet/server.go b/pkg/kubelet/server.go index c7edb5d6fae..5c0c9a80afc 100644 --- a/pkg/kubelet/server.go +++ b/pkg/kubelet/server.go @@ -62,11 +62,11 @@ func ListenAndServeKubeletServer(host HostInterface, updates chan<- interface{}, // HostInterface contains all the kubelet methods required by the server. // For testablitiy. type HostInterface interface { - GetContainerInfo(podFullName, containerName string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) + GetContainerInfo(podFullName, uuid, containerName string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) GetRootInfo(req *info.ContainerInfoRequest) (*info.ContainerInfo, error) GetMachineInfo() (*info.MachineInfo, error) - GetPodInfo(name string) (api.PodInfo, error) - RunInContainer(name, container string, cmd []string) ([]byte, error) + GetPodInfo(name, uuid string) (api.PodInfo, error) + RunInContainer(name, uuid, container string, cmd []string) ([]byte, error) ServeLogs(w http.ResponseWriter, req *http.Request) } @@ -150,6 +150,7 @@ func (s *Server) handlePodInfo(w http.ResponseWriter, req *http.Request) { return } podID := u.Query().Get("podID") + podUUID := u.Query().Get("UUID") if len(podID) == 0 { w.WriteHeader(http.StatusBadRequest) http.Error(w, "Missing 'podID=' query entry.", http.StatusBadRequest) @@ -157,7 +158,7 @@ func (s *Server) handlePodInfo(w http.ResponseWriter, req *http.Request) { } // TODO: backwards compatibility with existing API, needs API change podFullName := GetPodFullName(&Pod{Name: podID, Namespace: "etcd"}) - info, err := s.host.GetPodInfo(podFullName) + info, err := s.host.GetPodInfo(podFullName, podUUID) if err == ErrNoContainersInPod { http.Error(w, "Pod does not exist", http.StatusNotFound) return @@ -211,15 +212,21 @@ func (s *Server) handleRun(w http.ResponseWriter, req *http.Request) { return } parts := strings.Split(u.Path, "/") - if len(parts) != 4 { + var podID, uuid, container string + if len(parts) == 4 { + podID = parts[2] + container = parts[3] + } else if len(parts) == 5 { + podID = parts[2] + uuid = parts[3] + container = parts[4] + } else { http.Error(w, "Unexpected path for command running", http.StatusBadRequest) return } - podID := parts[2] - container := parts[3] podFullName := GetPodFullName(&Pod{Name: podID, Namespace: "etcd"}) command := strings.Split(u.Query().Get("cmd"), " ") - data, err := s.host.RunInContainer(podFullName, container, command) + data, err := s.host.RunInContainer(podFullName, uuid, container, command) if err != nil { s.error(w, err) return @@ -241,7 +248,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) { // serveStats implements stats logic. func (s *Server) serveStats(w http.ResponseWriter, req *http.Request) { - // /stats// + // /stats// or /stats/// components := strings.Split(strings.TrimPrefix(path.Clean(req.URL.Path), "/"), "/") var stats *info.ContainerInfo var err error @@ -260,7 +267,10 @@ func (s *Server) serveStats(w http.ResponseWriter, req *http.Request) { // TODO(monnand) Implement this errors.New("pod level status currently unimplemented") case 3: - stats, err = s.host.GetContainerInfo(components[1], components[2], &query) + // Backward compatibility without uuid information + stats, err = s.host.GetContainerInfo(components[1], "", components[2], &query) + case 4: + stats, err = s.host.GetContainerInfo(components[1], components[2], components[2], &query) default: http.Error(w, "unknown resource.", http.StatusNotFound) return diff --git a/pkg/kubelet/server_test.go b/pkg/kubelet/server_test.go index 2745679d8a4..a47fb2d1770 100644 --- a/pkg/kubelet/server_test.go +++ b/pkg/kubelet/server_test.go @@ -40,14 +40,14 @@ type fakeKubelet struct { rootInfoFunc func(query *info.ContainerInfoRequest) (*info.ContainerInfo, error) machineInfoFunc func() (*info.MachineInfo, error) logFunc func(w http.ResponseWriter, req *http.Request) - runFunc func(podFullName, containerName string, cmd []string) ([]byte, error) + runFunc func(podFullName, uuid, containerName string, cmd []string) ([]byte, error) } -func (fk *fakeKubelet) GetPodInfo(name string) (api.PodInfo, error) { +func (fk *fakeKubelet) GetPodInfo(name, uuid string) (api.PodInfo, error) { return fk.infoFunc(name) } -func (fk *fakeKubelet) GetContainerInfo(podFullName, containerName string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) { +func (fk *fakeKubelet) GetContainerInfo(podFullName, uuid, containerName string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) { return fk.containerInfoFunc(podFullName, containerName, req) } @@ -63,8 +63,8 @@ func (fk *fakeKubelet) ServeLogs(w http.ResponseWriter, req *http.Request) { fk.logFunc(w, req) } -func (fk *fakeKubelet) RunInContainer(podFullName, containerName string, cmd []string) ([]byte, error) { - return fk.runFunc(podFullName, containerName, cmd) +func (fk *fakeKubelet) RunInContainer(podFullName, uuid, containerName string, cmd []string) ([]byte, error) { + return fk.runFunc(podFullName, uuid, containerName, cmd) } type serverTestFramework struct { @@ -301,7 +301,7 @@ func TestServeRunInContainer(t *testing.T) { expectedPodName := podName + ".etcd" expectedContainerName := "baz" expectedCommand := "ls -a" - fw.fakeKubelet.runFunc = func(podFullName, containerName string, cmd []string) ([]byte, error) { + fw.fakeKubelet.runFunc = func(podFullName, uuid, containerName string, cmd []string) ([]byte, error) { if podFullName != expectedPodName { t.Errorf("expected %s, got %s", expectedPodName, podFullName) } @@ -332,3 +332,46 @@ func TestServeRunInContainer(t *testing.T) { t.Errorf("expected %s, got %s", output, result) } } + +func TestServeRunInContainerWithUUID(t *testing.T) { + fw := newServerTest() + output := "foo bar" + podName := "foo" + expectedPodName := podName + ".etcd" + expectedUuid := "7e00838d_-_3523_-_11e4_-_8421_-_42010af0a720" + expectedContainerName := "baz" + expectedCommand := "ls -a" + fw.fakeKubelet.runFunc = func(podFullName, uuid, containerName string, cmd []string) ([]byte, error) { + if podFullName != expectedPodName { + t.Errorf("expected %s, got %s", expectedPodName, podFullName) + } + if uuid != expectedUuid { + t.Errorf("expected %s, got %s", expectedUuid, uuid) + } + if containerName != expectedContainerName { + t.Errorf("expected %s, got %s", expectedContainerName, containerName) + } + if strings.Join(cmd, " ") != expectedCommand { + t.Errorf("expected: %s, got %v", expectedCommand, cmd) + } + + return []byte(output), nil + } + + resp, err := http.Get(fw.testHTTPServer.URL + "/run/" + podName + "/" + expectedUuid + "/" + expectedContainerName + "?cmd=ls%20-a") + + if err != nil { + t.Fatalf("Got error GETing: %v", err) + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + // copying the response body did not work + t.Errorf("Cannot copy resp: %#v", err) + } + result := string(body) + if result != output { + t.Errorf("expected %s, got %s", output, result) + } +} diff --git a/pkg/registry/pod/storage.go b/pkg/registry/pod/storage.go index 3dcd0aebf29..f0158b95122 100644 --- a/pkg/registry/pod/storage.go +++ b/pkg/registry/pod/storage.go @@ -67,8 +67,9 @@ func NewRegistryStorage(config *RegistryStorageConfig) apiserver.RESTStorage { func (rs *RegistryStorage) Create(obj runtime.Object) (<-chan runtime.Object, error) { pod := obj.(*api.Pod) + pod.DesiredState.Manifest.UUID = uuid.NewUUID().String() if len(pod.ID) == 0 { - pod.ID = uuid.NewUUID().String() + pod.ID = pod.DesiredState.Manifest.UUID } pod.DesiredState.Manifest.ID = pod.ID if errs := validation.ValidatePod(pod); len(errs) > 0 { diff --git a/pkg/registry/pod/storage_test.go b/pkg/registry/pod/storage_test.go index 37bb8dab7ce..78fbf37a5da 100644 --- a/pkg/registry/pod/storage_test.go +++ b/pkg/registry/pod/storage_test.go @@ -99,6 +99,29 @@ func TestCreatePodSetsIds(t *testing.T) { } } +func TestCreatePodSetsUUIDs(t *testing.T) { + podRegistry := registrytest.NewPodRegistry(nil) + podRegistry.Err = fmt.Errorf("test error") + storage := RegistryStorage{ + registry: podRegistry, + } + desiredState := api.PodState{ + Manifest: api.ContainerManifest{ + Version: "v1beta1", + }, + } + pod := &api.Pod{DesiredState: desiredState} + ch, err := storage.Create(pod) + if err != nil { + t.Errorf("Expected %#v, Got %#v", nil, err) + } + expectApiStatusError(t, ch, podRegistry.Err.Error()) + + if len(podRegistry.Pod.DesiredState.Manifest.UUID) == 0 { + t.Errorf("Expected pod UUID to be set, Got %#v", pod) + } +} + func TestListPodsError(t *testing.T) { podRegistry := registrytest.NewPodRegistry(nil) podRegistry.Err = fmt.Errorf("test error")