diff --git a/pkg/api/types.go b/pkg/api/types.go index b6d504a0935..9e11e52714a 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -157,6 +157,9 @@ const ( PodStopped PodStatus = "Stopped" ) +// PodInfo contains one entry for every container with available info. +type PodInfo map[string]docker.Container + // PodState is the state of a pod, used as either input (desired state) or output (current state) type PodState struct { Manifest ContainerManifest `json:"manifest,omitempty" yaml:"manifest,omitempty"` @@ -169,7 +172,7 @@ type PodState struct { // of `docker inspect`. This output format is *not* final and should not be relied // upon. // TODO: Make real decisions about what our info should look like. - Info map[string]docker.Container `json:"info,omitempty" yaml:"info,omitempty"` + Info PodInfo `json:"info,omitempty" yaml:"info,omitempty"` } type PodList struct { diff --git a/pkg/client/container_info.go b/pkg/client/podinfo.go similarity index 60% rename from pkg/client/container_info.go rename to pkg/client/podinfo.go index c4ec0c031a2..e216662f5fe 100644 --- a/pkg/client/container_info.go +++ b/pkg/client/podinfo.go @@ -24,31 +24,30 @@ import ( "net/http" "strconv" - "github.com/fsouza/go-dockerclient" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" ) -// ContainerInfo is an interface for things that can get information about a container. +// PodInfoGetter is an interface for things that can get information about a pod's containers. // Injectable for easy testing. -type ContainerInfo interface { - // GetContainerInfo returns information about container 'name' on 'host' - // Returns a json-formatted []byte (which can be unmarshalled into a - // map[string]interface{}) or an error if one occurs. - GetContainerInfo(host, name string) (*docker.Container, error) +type PodInfoGetter interface { + // GetPodInfo returns information about all containers which are part + // Returns an api.PodInfo, or an error if one occurs. + GetPodInfo(host, podID string) (api.PodInfo, error) } // The default implementation, accesses the kubelet over HTTP -type HTTPContainerInfo struct { +type HTTPPodInfoGetter struct { Client *http.Client Port uint } -func (c *HTTPContainerInfo) GetContainerInfo(host, name string) (*docker.Container, error) { +func (c *HTTPPodInfoGetter) GetPodInfo(host, podID string) (api.PodInfo, error) { request, err := http.NewRequest( "GET", fmt.Sprintf( - "http://%s/containerInfo?container=%s", + "http://%s/podInfo?podID=%s", net.JoinHostPort(host, strconv.FormatUint(uint64(c.Port), 10)), - name), + podID), nil) if err != nil { return nil, err @@ -63,20 +62,20 @@ func (c *HTTPContainerInfo) GetContainerInfo(host, name string) (*docker.Contain return nil, err } // Check that this data can be unmarshalled - var container docker.Container - err = json.Unmarshal(body, &container) + info := api.PodInfo{} + err = json.Unmarshal(body, &info) if err != nil { return nil, err } - return &container, nil + return info, nil } // Useful for testing. -type FakeContainerInfo struct { - data *docker.Container +type FakePodInfoGetter struct { + data api.PodInfo err error } -func (c *FakeContainerInfo) GetContainerInfo(host, name string) (*docker.Container, error) { +func (c *FakePodInfoGetter) GetPodInfo(host, podID string) (api.PodInfo, error) { return c.data, c.err } diff --git a/pkg/client/container_info_test.go b/pkg/client/podinfo_test.go similarity index 82% rename from pkg/client/container_info_test.go rename to pkg/client/podinfo_test.go index 5d04c361ad6..7748b7b3996 100644 --- a/pkg/client/container_info_test.go +++ b/pkg/client/podinfo_test.go @@ -25,6 +25,7 @@ import ( "strings" "testing" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/fsouza/go-dockerclient" ) @@ -36,8 +37,10 @@ func expectNoError(t *testing.T, err error) { } } -func TestHTTPContainerInfo(t *testing.T) { - expectObj := &docker.Container{ID: "myID"} +func TestHTTPPodInfoGetter(t *testing.T) { + expectObj := api.PodInfo{ + "myID": docker.Container{ID: "myID"}, + } body, err := json.Marshal(expectObj) expectNoError(t, err) fakeHandler := util.FakeHandler{ @@ -52,15 +55,15 @@ func TestHTTPContainerInfo(t *testing.T) { port, err := strconv.Atoi(parts[1]) expectNoError(t, err) - containerInfo := &HTTPContainerInfo{ + podInfoGetter := &HTTPPodInfoGetter{ Client: http.DefaultClient, Port: uint(port), } - gotObj, err := containerInfo.GetContainerInfo(parts[0], "foo") + gotObj, err := podInfoGetter.GetPodInfo(parts[0], "foo") expectNoError(t, err) // reflect.DeepEqual(expectObj, gotObj) doesn't handle blank times well - if expectObj.ID != gotObj.ID { + if len(gotObj) != len(expectObj) || expectObj["myID"].ID != gotObj["myID"].ID { t.Errorf("Unexpected response. Expected: %#v, received %#v", expectObj, gotObj) } } diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index 79a4d48a6cf..eac9c225241 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -783,16 +783,26 @@ func (kl *Kubelet) getContainerIdFromName(name string) (DockerId, bool, error) { return "", false, nil } -// Returns docker info for a container -func (kl *Kubelet) GetContainerInfo(name string) (*docker.Container, error) { - dockerId, found, err := kl.getContainerIdFromName(name) - if err != nil || !found { - return nil, err - } - info, err := kl.DockerClient.InspectContainer(string(dockerId)) +// Returns docker info for all containers in the pod/manifest +func (kl *Kubelet) GetPodInfo(podID string) (api.PodInfo, error) { + info := api.PodInfo{} + + containerList, err := kl.DockerClient.ListContainers(docker.ListContainersOptions{}) if err != nil { return nil, err } + + for _, value := range containerList { + manifestID, containerName := parseDockerName(value.Names[0]) + if manifestID != podID { + continue + } + inspectResult, err := kl.DockerClient.InspectContainer(value.ID) + if err != nil { + return nil, err + } + info[containerName] = *inspectResult + } return info, nil } diff --git a/pkg/kubelet/kubelet_server.go b/pkg/kubelet/kubelet_server.go index 965cda1d0e1..b2ab0b8a362 100644 --- a/pkg/kubelet/kubelet_server.go +++ b/pkg/kubelet/kubelet_server.go @@ -24,7 +24,6 @@ import ( "net/url" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/fsouza/go-dockerclient" "gopkg.in/v1/yaml" ) @@ -37,7 +36,7 @@ type KubeletServer struct { // For testablitiy. type kubeletInterface interface { GetContainerStats(name string) (*api.ContainerStats, error) - GetContainerInfo(name string) (*docker.Container, error) + GetPodInfo(name string) (api.PodInfo, error) } func (s *KubeletServer) error(w http.ResponseWriter, err error) { @@ -105,16 +104,14 @@ func (s *KubeletServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { w.WriteHeader(http.StatusOK) w.Header().Add("Content-type", "application/json") w.Write(data) - case u.Path == "/containerInfo": - // NOTE: The master appears to pass a Pod.ID - // The server appears to pass a Pod.ID - container := u.Query().Get("container") - if len(container) == 0 { + case u.Path == "/podInfo": + podID := u.Query().Get("podID") + if len(podID) == 0 { w.WriteHeader(http.StatusBadRequest) - fmt.Fprint(w, "Missing container selector arg.") + fmt.Fprint(w, "Missing 'podID=' query entry.") return } - info, err := s.Kubelet.GetContainerInfo(container) + info, err := s.Kubelet.GetPodInfo(podID) if err != nil { w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, "Internal Error: %v", err) diff --git a/pkg/kubelet/kubelet_server_test.go b/pkg/kubelet/kubelet_server_test.go index c63df291cf5..6b2b14f86f0 100644 --- a/pkg/kubelet/kubelet_server_test.go +++ b/pkg/kubelet/kubelet_server_test.go @@ -32,11 +32,11 @@ import ( ) type fakeKubelet struct { - infoFunc func(name string) (*docker.Container, error) + infoFunc func(name string) (api.PodInfo, error) statsFunc func(name string) (*api.ContainerStats, error) } -func (fk *fakeKubelet) GetContainerInfo(name string) (*docker.Container, error) { +func (fk *fakeKubelet) GetPodInfo(name string) (api.PodInfo, error) { return fk.infoFunc(name) } @@ -115,16 +115,16 @@ func TestContainers(t *testing.T) { } } -func TestContainerInfo(t *testing.T) { +func TestPodInfo(t *testing.T) { fw := makeServerTest() - expected := &docker.Container{ID: "myContainerID"} - fw.fakeKubelet.infoFunc = func(name string) (*docker.Container, error) { - if name == "goodcontainer" { + expected := api.PodInfo{"goodpod": docker.Container{ID: "myContainerID"}} + fw.fakeKubelet.infoFunc = func(name string) (api.PodInfo, error) { + if name == "goodpod" { return expected, nil } - return nil, fmt.Errorf("bad container") + return nil, fmt.Errorf("bad pod") } - resp, err := http.Get(fw.testHttpServer.URL + "/containerInfo?container=goodcontainer") + resp, err := http.Get(fw.testHttpServer.URL + "/podInfo?podID=goodpod") if err != nil { t.Errorf("Got error GETing: %v", err) } diff --git a/pkg/master/master.go b/pkg/master/master.go index 1c60eaba5a3..70dd71cec07 100644 --- a/pkg/master/master.go +++ b/pkg/master/master.go @@ -80,7 +80,7 @@ func New(etcdServers, minions []string, cloud cloudprovider.Interface, minionReg } func (m *Master) init(cloud cloudprovider.Interface) { - containerInfo := &client.HTTPContainerInfo{ + containerInfo := &client.HTTPPodInfoGetter{ Client: http.DefaultClient, Port: 10250, } diff --git a/pkg/master/pod_cache.go b/pkg/master/pod_cache.go index d3b20601b4b..d075e6cde57 100644 --- a/pkg/master/pod_cache.go +++ b/pkg/master/pod_cache.go @@ -21,53 +21,55 @@ import ( "sync" "time" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" - "github.com/fsouza/go-dockerclient" "github.com/golang/glog" ) // PodCache contains both a cache of container information, as well as the mechanism for keeping // that cache up to date. type PodCache struct { - containerInfo client.ContainerInfo + containerInfo client.PodInfoGetter pods registry.PodRegistry - podInfo map[string]docker.Container - period time.Duration - podLock sync.Mutex + // This is a map of pod id to a map of container name to the + podInfo map[string]api.PodInfo + period time.Duration + podLock sync.Mutex } -func NewPodCache(info client.ContainerInfo, pods registry.PodRegistry, period time.Duration) *PodCache { +func NewPodCache(info client.PodInfoGetter, pods registry.PodRegistry, period time.Duration) *PodCache { return &PodCache{ containerInfo: info, pods: pods, - podInfo: map[string]docker.Container{}, + podInfo: map[string]api.PodInfo{}, period: period, } } -// Implements the ContainerInfo interface. -func (p *PodCache) GetContainerInfo(host, id string) (*docker.Container, error) { +// Implements the PodInfoGetter interface. +// The returned value should be treated as read-only. +func (p *PodCache) GetPodInfo(host, podID string) (api.PodInfo, error) { p.podLock.Lock() defer p.podLock.Unlock() - value, ok := p.podInfo[id] + value, ok := p.podInfo[podID] if !ok { return nil, errors.New("No cached pod info") } else { - return &value, nil + return value, nil } } -func (p *PodCache) updateContainerInfo(host, id string) error { - info, err := p.containerInfo.GetContainerInfo(host, id) +func (p *PodCache) updatePodInfo(host, id string) error { + info, err := p.containerInfo.GetPodInfo(host, id) if err != nil { return err } p.podLock.Lock() defer p.podLock.Unlock() - p.podInfo[id] = *info + p.podInfo[id] = info return nil } @@ -79,7 +81,7 @@ func (p *PodCache) UpdateAllContainers() { return } for _, pod := range pods { - err := p.updateContainerInfo(pod.CurrentState.Host, pod.ID) + err := p.updatePodInfo(pod.CurrentState.Host, pod.ID) if err != nil { glog.Errorf("Error synchronizing container: %#v", err) } diff --git a/pkg/master/pod_cache_test.go b/pkg/master/pod_cache_test.go index 7465041b5e9..f2eb86c1d8e 100644 --- a/pkg/master/pod_cache_test.go +++ b/pkg/master/pod_cache_test.go @@ -26,14 +26,14 @@ import ( "github.com/fsouza/go-dockerclient" ) -type FakeContainerInfo struct { +type FakePodInfoGetter struct { host string id string - data *docker.Container + data api.PodInfo err error } -func (f *FakeContainerInfo) GetContainerInfo(host, id string) (*docker.Container, error) { +func (f *FakePodInfoGetter) GetPodInfo(host, id string) (api.PodInfo, error) { f.host = host f.id = id return f.data, f.err @@ -42,14 +42,14 @@ func (f *FakeContainerInfo) GetContainerInfo(host, id string) (*docker.Container func TestPodCacheGet(t *testing.T) { cache := NewPodCache(nil, nil, time.Second*1) - expected := docker.Container{ID: "foo"} + expected := api.PodInfo{"foo": docker.Container{ID: "foo"}} cache.podInfo["foo"] = expected - info, err := cache.GetContainerInfo("host", "foo") + info, err := cache.GetPodInfo("host", "foo") if err != nil { t.Errorf("Unexpected error: %#v", err) } - if !reflect.DeepEqual(info, &expected) { + if !reflect.DeepEqual(info, expected) { t.Errorf("Unexpected mismatch. Expected: %#v, Got: #%v", &expected, info) } } @@ -57,7 +57,7 @@ func TestPodCacheGet(t *testing.T) { func TestPodCacheGetMissing(t *testing.T) { cache := NewPodCache(nil, nil, time.Second*1) - info, err := cache.GetContainerInfo("host", "foo") + info, err := cache.GetPodInfo("host", "foo") if err == nil { t.Errorf("Unexpected non-error: %#v", err) } @@ -66,24 +66,24 @@ func TestPodCacheGetMissing(t *testing.T) { } } -func TestPodGetContainerInfo(t *testing.T) { - expected := docker.Container{ID: "foo"} - fake := FakeContainerInfo{ - data: &expected, +func TestPodGetPodInfoGetter(t *testing.T) { + expected := api.PodInfo{"foo": docker.Container{ID: "foo"}} + fake := FakePodInfoGetter{ + data: expected, } cache := NewPodCache(&fake, nil, time.Second*1) - cache.updateContainerInfo("host", "foo") + cache.updatePodInfo("host", "foo") if fake.host != "host" || fake.id != "foo" { t.Errorf("Unexpected access: %#v", fake) } - info, err := cache.GetContainerInfo("host", "foo") + info, err := cache.GetPodInfo("host", "foo") if err != nil { t.Errorf("Unexpected error: %#v", err) } - if !reflect.DeepEqual(info, &expected) { + if !reflect.DeepEqual(info, expected) { t.Errorf("Unexpected mismatch. Expected: %#v, Got: #%v", &expected, info) } } @@ -99,9 +99,9 @@ func TestPodUpdateAllContainers(t *testing.T) { pods := []api.Pod{pod} mockRegistry := registry.MakeMockPodRegistry(pods) - expected := docker.Container{ID: "foo"} - fake := FakeContainerInfo{ - data: &expected, + expected := api.PodInfo{"foo": docker.Container{ID: "foo"}} + fake := FakePodInfoGetter{ + data: expected, } cache := NewPodCache(&fake, mockRegistry, time.Second*1) @@ -111,11 +111,11 @@ func TestPodUpdateAllContainers(t *testing.T) { t.Errorf("Unexpected access: %#v", fake) } - info, err := cache.GetContainerInfo("machine", "foo") + info, err := cache.GetPodInfo("machine", "foo") if err != nil { t.Errorf("Unexpected error: %#v", err) } - if !reflect.DeepEqual(info, &expected) { + if !reflect.DeepEqual(info, expected) { t.Errorf("Unexpected mismatch. Expected: %#v, Got: #%v", &expected, info) } } diff --git a/pkg/registry/pod_registry.go b/pkg/registry/pod_registry.go index 495cee38719..bc6415bc294 100644 --- a/pkg/registry/pod_registry.go +++ b/pkg/registry/pod_registry.go @@ -27,15 +27,14 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/scheduler" - "github.com/fsouza/go-dockerclient" "github.com/golang/glog" ) // PodRegistryStorage implements the RESTStorage interface in terms of a PodRegistry type PodRegistryStorage struct { registry PodRegistry - containerInfo client.ContainerInfo - podCache client.ContainerInfo + podInfoGetter client.PodInfoGetter + podCache client.PodInfoGetter scheduler scheduler.Scheduler minionLister scheduler.MinionLister cloud cloudprovider.Interface @@ -45,20 +44,20 @@ type PodRegistryStorage struct { // MakePodRegistryStorage makes a RESTStorage object for a pod registry. // Parameters: // registry: The pod registry -// containerInfo: Source of fresh container info +// podInfoGetter: Source of fresh container info // scheduler: The scheduler for assigning pods to machines // minionLister: Object which can list available minions for the scheduler // cloud: Interface to a cloud provider (may be null) // podCache: Source of cached container info func MakePodRegistryStorage(registry PodRegistry, - containerInfo client.ContainerInfo, + podInfoGetter client.PodInfoGetter, scheduler scheduler.Scheduler, minionLister scheduler.MinionLister, cloud cloudprovider.Interface, - podCache client.ContainerInfo) apiserver.RESTStorage { + podCache client.PodInfoGetter) apiserver.RESTStorage { return &PodRegistryStorage{ registry: registry, - containerInfo: containerInfo, + podInfoGetter: podInfoGetter, scheduler: scheduler, minionLister: minionLister, cloud: cloud, @@ -84,18 +83,12 @@ func (storage *PodRegistryStorage) fillPodInfo(pod *api.Pod) { // Get cached info for the list currently. // TODO: Optionally use fresh info if storage.podCache != nil { - pod.CurrentState.Info = map[string]docker.Container{} - infoMap := pod.CurrentState.Info - - for _, container := range pod.DesiredState.Manifest.Containers { - // TODO: clearly need to pass both pod ID and container name here. - info, err := storage.podCache.GetContainerInfo(pod.CurrentState.Host, pod.ID) - if err != nil { - glog.Errorf("Error getting container info: %#v", err) - continue - } - infoMap[container.Name] = *info + info, err := storage.podCache.GetPodInfo(pod.CurrentState.Host, pod.ID) + if err != nil { + glog.Errorf("Error getting container info: %#v", err) + return } + pod.CurrentState.Info = info } } @@ -158,7 +151,7 @@ func (storage *PodRegistryStorage) Get(id string) (interface{}, error) { if pod == nil { return pod, nil } - if storage.containerInfo != nil { + if storage.podCache != nil || storage.podInfoGetter != nil { storage.fillPodInfo(pod) pod.CurrentState.Status = makePodStatus(pod) }