Change kublet to serve podInfo instead of containerInfo. Plumb through system.

This commit is contained in:
Daniel Smith 2014-07-01 15:35:56 -07:00
parent 049bc6b6d4
commit 11d6451d2a
10 changed files with 108 additions and 101 deletions

View File

@ -157,6 +157,9 @@ const (
PodStopped PodStatus = "Stopped" 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) // PodState is the state of a pod, used as either input (desired state) or output (current state)
type PodState struct { type PodState struct {
Manifest ContainerManifest `json:"manifest,omitempty" yaml:"manifest,omitempty"` 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 // of `docker inspect`. This output format is *not* final and should not be relied
// upon. // upon.
// TODO: Make real decisions about what our info should look like. // 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 { type PodList struct {

View File

@ -24,31 +24,30 @@ import (
"net/http" "net/http"
"strconv" "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. // Injectable for easy testing.
type ContainerInfo interface { type PodInfoGetter interface {
// GetContainerInfo returns information about container 'name' on 'host' // GetPodInfo returns information about all containers which are part
// Returns a json-formatted []byte (which can be unmarshalled into a // Returns an api.PodInfo, or an error if one occurs.
// map[string]interface{}) or an error if one occurs. GetPodInfo(host, podID string) (api.PodInfo, error)
GetContainerInfo(host, name string) (*docker.Container, error)
} }
// The default implementation, accesses the kubelet over HTTP // The default implementation, accesses the kubelet over HTTP
type HTTPContainerInfo struct { type HTTPPodInfoGetter struct {
Client *http.Client Client *http.Client
Port uint 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( request, err := http.NewRequest(
"GET", "GET",
fmt.Sprintf( fmt.Sprintf(
"http://%s/containerInfo?container=%s", "http://%s/podInfo?podID=%s",
net.JoinHostPort(host, strconv.FormatUint(uint64(c.Port), 10)), net.JoinHostPort(host, strconv.FormatUint(uint64(c.Port), 10)),
name), podID),
nil) nil)
if err != nil { if err != nil {
return nil, err return nil, err
@ -63,20 +62,20 @@ func (c *HTTPContainerInfo) GetContainerInfo(host, name string) (*docker.Contain
return nil, err return nil, err
} }
// Check that this data can be unmarshalled // Check that this data can be unmarshalled
var container docker.Container info := api.PodInfo{}
err = json.Unmarshal(body, &container) err = json.Unmarshal(body, &info)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &container, nil return info, nil
} }
// Useful for testing. // Useful for testing.
type FakeContainerInfo struct { type FakePodInfoGetter struct {
data *docker.Container data api.PodInfo
err error 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 return c.data, c.err
} }

View File

@ -25,6 +25,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/fsouza/go-dockerclient" "github.com/fsouza/go-dockerclient"
) )
@ -36,8 +37,10 @@ func expectNoError(t *testing.T, err error) {
} }
} }
func TestHTTPContainerInfo(t *testing.T) { func TestHTTPPodInfoGetter(t *testing.T) {
expectObj := &docker.Container{ID: "myID"} expectObj := api.PodInfo{
"myID": docker.Container{ID: "myID"},
}
body, err := json.Marshal(expectObj) body, err := json.Marshal(expectObj)
expectNoError(t, err) expectNoError(t, err)
fakeHandler := util.FakeHandler{ fakeHandler := util.FakeHandler{
@ -52,15 +55,15 @@ func TestHTTPContainerInfo(t *testing.T) {
port, err := strconv.Atoi(parts[1]) port, err := strconv.Atoi(parts[1])
expectNoError(t, err) expectNoError(t, err)
containerInfo := &HTTPContainerInfo{ podInfoGetter := &HTTPPodInfoGetter{
Client: http.DefaultClient, Client: http.DefaultClient,
Port: uint(port), Port: uint(port),
} }
gotObj, err := containerInfo.GetContainerInfo(parts[0], "foo") gotObj, err := podInfoGetter.GetPodInfo(parts[0], "foo")
expectNoError(t, err) expectNoError(t, err)
// reflect.DeepEqual(expectObj, gotObj) doesn't handle blank times well // 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) t.Errorf("Unexpected response. Expected: %#v, received %#v", expectObj, gotObj)
} }
} }

View File

@ -783,16 +783,26 @@ func (kl *Kubelet) getContainerIdFromName(name string) (DockerId, bool, error) {
return "", false, nil return "", false, nil
} }
// Returns docker info for a container // Returns docker info for all containers in the pod/manifest
func (kl *Kubelet) GetContainerInfo(name string) (*docker.Container, error) { func (kl *Kubelet) GetPodInfo(podID string) (api.PodInfo, error) {
dockerId, found, err := kl.getContainerIdFromName(name) info := api.PodInfo{}
if err != nil || !found {
return nil, err containerList, err := kl.DockerClient.ListContainers(docker.ListContainersOptions{})
}
info, err := kl.DockerClient.InspectContainer(string(dockerId))
if err != nil { if err != nil {
return nil, err 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 return info, nil
} }

View File

@ -24,7 +24,6 @@ import (
"net/url" "net/url"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/fsouza/go-dockerclient"
"gopkg.in/v1/yaml" "gopkg.in/v1/yaml"
) )
@ -37,7 +36,7 @@ type KubeletServer struct {
// For testablitiy. // For testablitiy.
type kubeletInterface interface { type kubeletInterface interface {
GetContainerStats(name string) (*api.ContainerStats, error) 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) { 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.WriteHeader(http.StatusOK)
w.Header().Add("Content-type", "application/json") w.Header().Add("Content-type", "application/json")
w.Write(data) w.Write(data)
case u.Path == "/containerInfo": case u.Path == "/podInfo":
// NOTE: The master appears to pass a Pod.ID podID := u.Query().Get("podID")
// The server appears to pass a Pod.ID if len(podID) == 0 {
container := u.Query().Get("container")
if len(container) == 0 {
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
fmt.Fprint(w, "Missing container selector arg.") fmt.Fprint(w, "Missing 'podID=' query entry.")
return return
} }
info, err := s.Kubelet.GetContainerInfo(container) info, err := s.Kubelet.GetPodInfo(podID)
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "Internal Error: %v", err) fmt.Fprintf(w, "Internal Error: %v", err)

View File

@ -32,11 +32,11 @@ import (
) )
type fakeKubelet struct { type fakeKubelet struct {
infoFunc func(name string) (*docker.Container, error) infoFunc func(name string) (api.PodInfo, error)
statsFunc func(name string) (*api.ContainerStats, 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) 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() fw := makeServerTest()
expected := &docker.Container{ID: "myContainerID"} expected := api.PodInfo{"goodpod": docker.Container{ID: "myContainerID"}}
fw.fakeKubelet.infoFunc = func(name string) (*docker.Container, error) { fw.fakeKubelet.infoFunc = func(name string) (api.PodInfo, error) {
if name == "goodcontainer" { if name == "goodpod" {
return expected, nil 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 { if err != nil {
t.Errorf("Got error GETing: %v", err) t.Errorf("Got error GETing: %v", err)
} }

View File

@ -80,7 +80,7 @@ func New(etcdServers, minions []string, cloud cloudprovider.Interface, minionReg
} }
func (m *Master) init(cloud cloudprovider.Interface) { func (m *Master) init(cloud cloudprovider.Interface) {
containerInfo := &client.HTTPContainerInfo{ containerInfo := &client.HTTPPodInfoGetter{
Client: http.DefaultClient, Client: http.DefaultClient,
Port: 10250, Port: 10250,
} }

View File

@ -21,53 +21,55 @@ import (
"sync" "sync"
"time" "time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/fsouza/go-dockerclient"
"github.com/golang/glog" "github.com/golang/glog"
) )
// PodCache contains both a cache of container information, as well as the mechanism for keeping // PodCache contains both a cache of container information, as well as the mechanism for keeping
// that cache up to date. // that cache up to date.
type PodCache struct { type PodCache struct {
containerInfo client.ContainerInfo containerInfo client.PodInfoGetter
pods registry.PodRegistry pods registry.PodRegistry
podInfo map[string]docker.Container // This is a map of pod id to a map of container name to the
period time.Duration podInfo map[string]api.PodInfo
podLock sync.Mutex 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{ return &PodCache{
containerInfo: info, containerInfo: info,
pods: pods, pods: pods,
podInfo: map[string]docker.Container{}, podInfo: map[string]api.PodInfo{},
period: period, period: period,
} }
} }
// Implements the ContainerInfo interface. // Implements the PodInfoGetter interface.
func (p *PodCache) GetContainerInfo(host, id string) (*docker.Container, error) { // The returned value should be treated as read-only.
func (p *PodCache) GetPodInfo(host, podID string) (api.PodInfo, error) {
p.podLock.Lock() p.podLock.Lock()
defer p.podLock.Unlock() defer p.podLock.Unlock()
value, ok := p.podInfo[id] value, ok := p.podInfo[podID]
if !ok { if !ok {
return nil, errors.New("No cached pod info") return nil, errors.New("No cached pod info")
} else { } else {
return &value, nil return value, nil
} }
} }
func (p *PodCache) updateContainerInfo(host, id string) error { func (p *PodCache) updatePodInfo(host, id string) error {
info, err := p.containerInfo.GetContainerInfo(host, id) info, err := p.containerInfo.GetPodInfo(host, id)
if err != nil { if err != nil {
return err return err
} }
p.podLock.Lock() p.podLock.Lock()
defer p.podLock.Unlock() defer p.podLock.Unlock()
p.podInfo[id] = *info p.podInfo[id] = info
return nil return nil
} }
@ -79,7 +81,7 @@ func (p *PodCache) UpdateAllContainers() {
return return
} }
for _, pod := range pods { for _, pod := range pods {
err := p.updateContainerInfo(pod.CurrentState.Host, pod.ID) err := p.updatePodInfo(pod.CurrentState.Host, pod.ID)
if err != nil { if err != nil {
glog.Errorf("Error synchronizing container: %#v", err) glog.Errorf("Error synchronizing container: %#v", err)
} }

View File

@ -26,14 +26,14 @@ import (
"github.com/fsouza/go-dockerclient" "github.com/fsouza/go-dockerclient"
) )
type FakeContainerInfo struct { type FakePodInfoGetter struct {
host string host string
id string id string
data *docker.Container data api.PodInfo
err error 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.host = host
f.id = id f.id = id
return f.data, f.err return f.data, f.err
@ -42,14 +42,14 @@ func (f *FakeContainerInfo) GetContainerInfo(host, id string) (*docker.Container
func TestPodCacheGet(t *testing.T) { func TestPodCacheGet(t *testing.T) {
cache := NewPodCache(nil, nil, time.Second*1) cache := NewPodCache(nil, nil, time.Second*1)
expected := docker.Container{ID: "foo"} expected := api.PodInfo{"foo": docker.Container{ID: "foo"}}
cache.podInfo["foo"] = expected cache.podInfo["foo"] = expected
info, err := cache.GetContainerInfo("host", "foo") info, err := cache.GetPodInfo("host", "foo")
if err != nil { if err != nil {
t.Errorf("Unexpected error: %#v", err) 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) t.Errorf("Unexpected mismatch. Expected: %#v, Got: #%v", &expected, info)
} }
} }
@ -57,7 +57,7 @@ func TestPodCacheGet(t *testing.T) {
func TestPodCacheGetMissing(t *testing.T) { func TestPodCacheGetMissing(t *testing.T) {
cache := NewPodCache(nil, nil, time.Second*1) cache := NewPodCache(nil, nil, time.Second*1)
info, err := cache.GetContainerInfo("host", "foo") info, err := cache.GetPodInfo("host", "foo")
if err == nil { if err == nil {
t.Errorf("Unexpected non-error: %#v", err) t.Errorf("Unexpected non-error: %#v", err)
} }
@ -66,24 +66,24 @@ func TestPodCacheGetMissing(t *testing.T) {
} }
} }
func TestPodGetContainerInfo(t *testing.T) { func TestPodGetPodInfoGetter(t *testing.T) {
expected := docker.Container{ID: "foo"} expected := api.PodInfo{"foo": docker.Container{ID: "foo"}}
fake := FakeContainerInfo{ fake := FakePodInfoGetter{
data: &expected, data: expected,
} }
cache := NewPodCache(&fake, nil, time.Second*1) cache := NewPodCache(&fake, nil, time.Second*1)
cache.updateContainerInfo("host", "foo") cache.updatePodInfo("host", "foo")
if fake.host != "host" || fake.id != "foo" { if fake.host != "host" || fake.id != "foo" {
t.Errorf("Unexpected access: %#v", fake) t.Errorf("Unexpected access: %#v", fake)
} }
info, err := cache.GetContainerInfo("host", "foo") info, err := cache.GetPodInfo("host", "foo")
if err != nil { if err != nil {
t.Errorf("Unexpected error: %#v", err) 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) t.Errorf("Unexpected mismatch. Expected: %#v, Got: #%v", &expected, info)
} }
} }
@ -99,9 +99,9 @@ func TestPodUpdateAllContainers(t *testing.T) {
pods := []api.Pod{pod} pods := []api.Pod{pod}
mockRegistry := registry.MakeMockPodRegistry(pods) mockRegistry := registry.MakeMockPodRegistry(pods)
expected := docker.Container{ID: "foo"} expected := api.PodInfo{"foo": docker.Container{ID: "foo"}}
fake := FakeContainerInfo{ fake := FakePodInfoGetter{
data: &expected, data: expected,
} }
cache := NewPodCache(&fake, mockRegistry, time.Second*1) cache := NewPodCache(&fake, mockRegistry, time.Second*1)
@ -111,11 +111,11 @@ func TestPodUpdateAllContainers(t *testing.T) {
t.Errorf("Unexpected access: %#v", fake) t.Errorf("Unexpected access: %#v", fake)
} }
info, err := cache.GetContainerInfo("machine", "foo") info, err := cache.GetPodInfo("machine", "foo")
if err != nil { if err != nil {
t.Errorf("Unexpected error: %#v", err) 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) t.Errorf("Unexpected mismatch. Expected: %#v, Got: #%v", &expected, info)
} }
} }

View File

@ -27,15 +27,14 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/scheduler" "github.com/GoogleCloudPlatform/kubernetes/pkg/scheduler"
"github.com/fsouza/go-dockerclient"
"github.com/golang/glog" "github.com/golang/glog"
) )
// PodRegistryStorage implements the RESTStorage interface in terms of a PodRegistry // PodRegistryStorage implements the RESTStorage interface in terms of a PodRegistry
type PodRegistryStorage struct { type PodRegistryStorage struct {
registry PodRegistry registry PodRegistry
containerInfo client.ContainerInfo podInfoGetter client.PodInfoGetter
podCache client.ContainerInfo podCache client.PodInfoGetter
scheduler scheduler.Scheduler scheduler scheduler.Scheduler
minionLister scheduler.MinionLister minionLister scheduler.MinionLister
cloud cloudprovider.Interface cloud cloudprovider.Interface
@ -45,20 +44,20 @@ type PodRegistryStorage struct {
// MakePodRegistryStorage makes a RESTStorage object for a pod registry. // MakePodRegistryStorage makes a RESTStorage object for a pod registry.
// Parameters: // Parameters:
// registry: The pod registry // registry: The pod registry
// containerInfo: Source of fresh container info // podInfoGetter: Source of fresh container info
// scheduler: The scheduler for assigning pods to machines // scheduler: The scheduler for assigning pods to machines
// minionLister: Object which can list available minions for the scheduler // minionLister: Object which can list available minions for the scheduler
// cloud: Interface to a cloud provider (may be null) // cloud: Interface to a cloud provider (may be null)
// podCache: Source of cached container info // podCache: Source of cached container info
func MakePodRegistryStorage(registry PodRegistry, func MakePodRegistryStorage(registry PodRegistry,
containerInfo client.ContainerInfo, podInfoGetter client.PodInfoGetter,
scheduler scheduler.Scheduler, scheduler scheduler.Scheduler,
minionLister scheduler.MinionLister, minionLister scheduler.MinionLister,
cloud cloudprovider.Interface, cloud cloudprovider.Interface,
podCache client.ContainerInfo) apiserver.RESTStorage { podCache client.PodInfoGetter) apiserver.RESTStorage {
return &PodRegistryStorage{ return &PodRegistryStorage{
registry: registry, registry: registry,
containerInfo: containerInfo, podInfoGetter: podInfoGetter,
scheduler: scheduler, scheduler: scheduler,
minionLister: minionLister, minionLister: minionLister,
cloud: cloud, cloud: cloud,
@ -84,18 +83,12 @@ func (storage *PodRegistryStorage) fillPodInfo(pod *api.Pod) {
// Get cached info for the list currently. // Get cached info for the list currently.
// TODO: Optionally use fresh info // TODO: Optionally use fresh info
if storage.podCache != nil { if storage.podCache != nil {
pod.CurrentState.Info = map[string]docker.Container{} info, err := storage.podCache.GetPodInfo(pod.CurrentState.Host, pod.ID)
infoMap := pod.CurrentState.Info if err != nil {
glog.Errorf("Error getting container info: %#v", err)
for _, container := range pod.DesiredState.Manifest.Containers { return
// 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
} }
pod.CurrentState.Info = info
} }
} }
@ -158,7 +151,7 @@ func (storage *PodRegistryStorage) Get(id string) (interface{}, error) {
if pod == nil { if pod == nil {
return pod, nil return pod, nil
} }
if storage.containerInfo != nil { if storage.podCache != nil || storage.podInfoGetter != nil {
storage.fillPodInfo(pod) storage.fillPodInfo(pod)
pod.CurrentState.Status = makePodStatus(pod) pod.CurrentState.Status = makePodStatus(pod)
} }