Merge pull request #491 from monnand/cadvisor-update-3

Retrieve machine spec from cAdvisor
This commit is contained in:
Daniel Smith 2014-07-17 16:01:52 -07:00
commit f6bbcff93a
6 changed files with 158 additions and 47 deletions

View File

@ -29,8 +29,12 @@ import (
) )
type ContainerInfoGetter interface { type ContainerInfoGetter interface {
// GetContainerInfo returns information about a container.
GetContainerInfo(host, podID, containerID string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) GetContainerInfo(host, podID, containerID string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error)
GetMachineInfo(host string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) // GetRootInfo returns information about the root container on a machine.
GetRootInfo(host string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error)
// GetMachineInfo returns the machine's information like number of cores, memory capacity.
GetMachineInfo(host string) (*info.MachineInfo, error)
} }
type HTTPContainerInfoGetter struct { type HTTPContainerInfoGetter struct {
@ -38,6 +42,35 @@ type HTTPContainerInfoGetter struct {
Port int Port int
} }
func (self *HTTPContainerInfoGetter) GetMachineInfo(host string) (*info.MachineInfo, error) {
request, err := http.NewRequest(
"GET",
fmt.Sprintf("http://%v/spec",
net.JoinHostPort(host, strconv.Itoa(self.Port)),
),
nil,
)
if err != nil {
return nil, err
}
response, err := self.Client.Do(request)
if err != nil {
return nil, err
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return nil, fmt.Errorf("trying to get machine spec from %v; received status %v",
host, response.Status)
}
var minfo info.MachineInfo
err = json.NewDecoder(response.Body).Decode(&minfo)
if err != nil {
return nil, err
}
return &minfo, nil
}
func (self *HTTPContainerInfoGetter) getContainerInfo(host, path string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) { func (self *HTTPContainerInfoGetter) getContainerInfo(host, path string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
var body io.Reader var body io.Reader
if req != nil { if req != nil {
@ -69,9 +102,8 @@ func (self *HTTPContainerInfoGetter) getContainerInfo(host, path string, req *in
return nil, fmt.Errorf("trying to get info for %v from %v; received status %v", return nil, fmt.Errorf("trying to get info for %v from %v; received status %v",
path, host, response.Status) path, host, response.Status)
} }
decoder := json.NewDecoder(response.Body)
var cinfo info.ContainerInfo var cinfo info.ContainerInfo
err = decoder.Decode(&cinfo) err = json.NewDecoder(response.Body).Decode(&cinfo)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -86,6 +118,6 @@ func (self *HTTPContainerInfoGetter) GetContainerInfo(host, podID, containerID s
) )
} }
func (self *HTTPContainerInfoGetter) GetMachineInfo(host string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) { func (self *HTTPContainerInfoGetter) GetRootInfo(host string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
return self.getContainerInfo(host, "", req) return self.getContainerInfo(host, "", req)
} }

View File

@ -53,9 +53,8 @@ func testHTTPContainerInfoGetter(
expectedPath, r.URL.Path) expectedPath, r.URL.Path)
} }
decoder := json.NewDecoder(r.Body)
var receivedReq info.ContainerInfoRequest var receivedReq info.ContainerInfoRequest
err := decoder.Decode(&receivedReq) err := json.NewDecoder(r.Body).Decode(&receivedReq)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -67,8 +66,7 @@ func testHTTPContainerInfoGetter(
if !reflect.DeepEqual(expectedReq, &receivedReq) { if !reflect.DeepEqual(expectedReq, &receivedReq) {
t.Errorf("received wrong request") t.Errorf("received wrong request")
} }
encoder := json.NewEncoder(w) err = json.NewEncoder(w).Encode(cinfo)
err = encoder.Encode(cinfo)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -93,7 +91,7 @@ func testHTTPContainerInfoGetter(
if len(podID) > 0 && len(containerID) > 0 { if len(podID) > 0 && len(containerID) > 0 {
receivedContainerInfo, err = containerInfoGetter.GetContainerInfo(parts[0], podID, containerID, req) receivedContainerInfo, err = containerInfoGetter.GetContainerInfo(parts[0], podID, containerID, req)
} else { } else {
receivedContainerInfo, err = containerInfoGetter.GetMachineInfo(parts[0], req) receivedContainerInfo, err = containerInfoGetter.GetRootInfo(parts[0], req)
} }
if status == 0 || status == http.StatusOK { if status == 0 || status == http.StatusOK {
if err != nil { if err != nil {
@ -125,7 +123,7 @@ func TestHTTPContainerInfoGetterGetContainerInfoSuccessfully(t *testing.T) {
testHTTPContainerInfoGetter(req, cinfo, "somePodID", "containerNameInK8S", 0, t) testHTTPContainerInfoGetter(req, cinfo, "somePodID", "containerNameInK8S", 0, t)
} }
func TestHTTPContainerInfoGetterGetMachineInfoSuccessfully(t *testing.T) { func TestHTTPContainerInfoGetterGetRootInfoSuccessfully(t *testing.T) {
req := &info.ContainerInfoRequest{ req := &info.ContainerInfoRequest{
NumStats: 10, NumStats: 10,
NumSamples: 10, NumSamples: 10,
@ -155,7 +153,7 @@ func TestHTTPContainerInfoGetterGetContainerInfoWithError(t *testing.T) {
testHTTPContainerInfoGetter(req, cinfo, "somePodID", "containerNameInK8S", http.StatusNotFound, t) testHTTPContainerInfoGetter(req, cinfo, "somePodID", "containerNameInK8S", http.StatusNotFound, t)
} }
func TestHTTPContainerInfoGetterGetMachineInfoWithError(t *testing.T) { func TestHTTPContainerInfoGetterGetRootInfoWithError(t *testing.T) {
req := &info.ContainerInfoRequest{ req := &info.ContainerInfoRequest{
NumStats: 10, NumStats: 10,
NumSamples: 10, NumSamples: 10,
@ -169,3 +167,39 @@ func TestHTTPContainerInfoGetterGetMachineInfoWithError(t *testing.T) {
) )
testHTTPContainerInfoGetter(req, cinfo, "", "", http.StatusNotFound, t) testHTTPContainerInfoGetter(req, cinfo, "", "", http.StatusNotFound, t)
} }
func TestHTTPGetMachineInfo(t *testing.T) {
mspec := &info.MachineInfo{
NumCores: 4,
MemoryCapacity: 2048,
}
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err := json.NewEncoder(w).Encode(mspec)
if err != nil {
t.Fatal(err)
}
}))
hostURL, err := url.Parse(ts.URL)
if err != nil {
t.Fatal(err)
}
parts := strings.Split(hostURL.Host, ":")
port, err := strconv.Atoi(parts[1])
if err != nil {
t.Fatal(err)
}
containerInfoGetter := &HTTPContainerInfoGetter{
Client: http.DefaultClient,
Port: port,
}
received, err := containerInfoGetter.GetMachineInfo(parts[0])
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(received, mspec) {
t.Errorf("received wrong machine spec")
}
}

View File

@ -769,11 +769,15 @@ func (kl *Kubelet) GetContainerInfo(manifestID, containerName string, req *info.
return kl.statsFromContainerPath(fmt.Sprintf("/docker/%s", dockerContainer.ID), req) return kl.statsFromContainerPath(fmt.Sprintf("/docker/%s", dockerContainer.ID), req)
} }
// GetMachineStats returns stats (from Cadvisor) of current machine. // GetRootInfo returns stats (from Cadvisor) of current machine (root container).
func (kl *Kubelet) GetMachineStats(req *info.ContainerInfoRequest) (*info.ContainerInfo, error) { func (kl *Kubelet) GetRootInfo(req *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
return kl.statsFromContainerPath("/", req) return kl.statsFromContainerPath("/", req)
} }
func (kl *Kubelet) GetMachineInfo() (*info.MachineInfo, error) {
return kl.CadvisorClient.MachineInfo()
}
func (kl *Kubelet) healthy(container api.Container, dockerContainer *docker.APIContainers) (health.Status, error) { func (kl *Kubelet) healthy(container api.Container, dockerContainer *docker.APIContainers) (health.Status, error) {
// Give the container 60 seconds to start up. // Give the container 60 seconds to start up.
if container.LivenessProbe == nil { if container.LivenessProbe == nil {

View File

@ -948,7 +948,7 @@ func areSamePercentiles(
} }
} }
func TestGetContainerStats(t *testing.T) { func TestGetContainerInfo(t *testing.T) {
containerID := "ab2cdf" containerID := "ab2cdf"
containerPath := fmt.Sprintf("/docker/%v", containerID) containerPath := fmt.Sprintf("/docker/%v", containerID)
containerInfo := &info.ContainerInfo{ containerInfo := &info.ContainerInfo{
@ -1001,7 +1001,7 @@ func TestGetContainerStats(t *testing.T) {
mockCadvisor.AssertExpectations(t) mockCadvisor.AssertExpectations(t)
} }
func TestGetMachineStats(t *testing.T) { func TestGetRooInfo(t *testing.T) {
containerPath := "/" containerPath := "/"
containerInfo := &info.ContainerInfo{ containerInfo := &info.ContainerInfo{
ContainerReference: info.ContainerReference{ ContainerReference: info.ContainerReference{
@ -1032,7 +1032,7 @@ func TestGetMachineStats(t *testing.T) {
} }
// If the container name is an empty string, then it means the root container. // If the container name is an empty string, then it means the root container.
stats, err := kubelet.GetMachineStats(req) stats, err := kubelet.GetRootInfo(req)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
@ -1044,7 +1044,7 @@ func TestGetMachineStats(t *testing.T) {
mockCadvisor.AssertExpectations(t) mockCadvisor.AssertExpectations(t)
} }
func TestGetContainerStatsWithoutCadvisor(t *testing.T) { func TestGetContainerInfoWithoutCadvisor(t *testing.T) {
kubelet, _, fakeDocker := makeTestKubelet(t) kubelet, _, fakeDocker := makeTestKubelet(t)
fakeDocker.containerList = []docker.APIContainers{ fakeDocker.containerList = []docker.APIContainers{
{ {
@ -1071,7 +1071,7 @@ func TestGetContainerStatsWithoutCadvisor(t *testing.T) {
} }
} }
func TestGetContainerStatsWhenCadvisorFailed(t *testing.T) { func TestGetContainerInfoWhenCadvisorFailed(t *testing.T) {
containerID := "ab2cdf" containerID := "ab2cdf"
containerPath := fmt.Sprintf("/docker/%v", containerID) containerPath := fmt.Sprintf("/docker/%v", containerID)
@ -1107,7 +1107,7 @@ func TestGetContainerStatsWhenCadvisorFailed(t *testing.T) {
mockCadvisor.AssertExpectations(t) mockCadvisor.AssertExpectations(t)
} }
func TestGetContainerStatsOnNonExistContainer(t *testing.T) { func TestGetContainerInfoOnNonExistContainer(t *testing.T) {
mockCadvisor := &mockCadvisorClient{} mockCadvisor := &mockCadvisorClient{}
kubelet, _, fakeDocker := makeTestKubelet(t) kubelet, _, fakeDocker := makeTestKubelet(t)

View File

@ -44,7 +44,8 @@ type Server struct {
// For testablitiy. // For testablitiy.
type kubeletInterface interface { type kubeletInterface interface {
GetContainerInfo(podID, containerName string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) GetContainerInfo(podID, containerName string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error)
GetMachineStats(req *info.ContainerInfoRequest) (*info.ContainerInfo, error) GetRootInfo(req *info.ContainerInfoRequest) (*info.ContainerInfo, error)
GetMachineInfo() (*info.MachineInfo, error)
GetPodInfo(name string) (api.PodInfo, error) GetPodInfo(name string) (api.PodInfo, error)
} }
@ -108,6 +109,19 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
w.Write(data) w.Write(data)
case strings.HasPrefix(u.Path, "/stats"): case strings.HasPrefix(u.Path, "/stats"):
s.serveStats(w, req) s.serveStats(w, req)
case strings.HasPrefix(u.Path, "/spec"):
info, err := s.Kubelet.GetMachineInfo()
if err != nil {
s.error(w, err)
return
}
data, err := json.Marshal(info)
if err != nil {
s.error(w, err)
return
}
w.Header().Add("Content-type", "application/json")
w.Write(data)
default: default:
s.DelegateHandler.ServeHTTP(w, req) s.DelegateHandler.ServeHTTP(w, req)
} }
@ -119,8 +133,7 @@ func (s *Server) serveStats(w http.ResponseWriter, req *http.Request) {
var stats *info.ContainerInfo var stats *info.ContainerInfo
var err error var err error
var query info.ContainerInfoRequest var query info.ContainerInfoRequest
decoder := json.NewDecoder(req.Body) err = json.NewDecoder(req.Body).Decode(&query)
err = decoder.Decode(&query)
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
s.error(w, err) s.error(w, err)
return return
@ -128,7 +141,7 @@ func (s *Server) serveStats(w http.ResponseWriter, req *http.Request) {
switch len(components) { switch len(components) {
case 1: case 1:
// Machine stats // Machine stats
stats, err = s.Kubelet.GetMachineStats(&query) stats, err = s.Kubelet.GetRootInfo(&query)
case 2: case 2:
// pod stats // pod stats
// TODO(monnand) Implement this // TODO(monnand) Implement this

View File

@ -33,9 +33,10 @@ import (
) )
type fakeKubelet struct { type fakeKubelet struct {
infoFunc func(name string) (api.PodInfo, error) infoFunc func(name string) (api.PodInfo, error)
containerStatsFunc func(podID, containerName string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) containerInfoFunc func(podID, containerName string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error)
machineStatsFunc func(query *info.ContainerInfoRequest) (*info.ContainerInfo, error) rootInfoFunc func(query *info.ContainerInfoRequest) (*info.ContainerInfo, error)
machineInfoFunc func() (*info.MachineInfo, error)
} }
func (fk *fakeKubelet) GetPodInfo(name string) (api.PodInfo, error) { func (fk *fakeKubelet) GetPodInfo(name string) (api.PodInfo, error) {
@ -43,11 +44,15 @@ func (fk *fakeKubelet) GetPodInfo(name string) (api.PodInfo, error) {
} }
func (fk *fakeKubelet) GetContainerInfo(podID, containerName string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) { func (fk *fakeKubelet) GetContainerInfo(podID, containerName string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
return fk.containerStatsFunc(podID, containerName, req) return fk.containerInfoFunc(podID, containerName, req)
} }
func (fk *fakeKubelet) GetMachineStats(req *info.ContainerInfoRequest) (*info.ContainerInfo, error) { func (fk *fakeKubelet) GetRootInfo(req *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
return fk.machineStatsFunc(req) return fk.rootInfoFunc(req)
}
func (fk *fakeKubelet) GetMachineInfo() (*info.MachineInfo, error) {
return fk.machineInfoFunc()
} }
type serverTestFramework struct { type serverTestFramework struct {
@ -147,9 +152,9 @@ func TestPodInfo(t *testing.T) {
} }
} }
func TestContainerStats(t *testing.T) { func TestContainerInfo(t *testing.T) {
fw := makeServerTest() fw := makeServerTest()
expectedStats := &info.ContainerInfo{ expectedInfo := &info.ContainerInfo{
StatsPercentiles: &info.ContainerStatsPercentiles{ StatsPercentiles: &info.ContainerStatsPercentiles{
MaxMemoryUsage: 1024001, MaxMemoryUsage: 1024001,
CpuUsagePercentiles: []info.Percentile{ CpuUsagePercentiles: []info.Percentile{
@ -166,11 +171,11 @@ func TestContainerStats(t *testing.T) {
} }
expectedPodID := "somepod" expectedPodID := "somepod"
expectedContainerName := "goodcontainer" expectedContainerName := "goodcontainer"
fw.fakeKubelet.containerStatsFunc = func(podID, containerName string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) { fw.fakeKubelet.containerInfoFunc = func(podID, containerName string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
if podID != expectedPodID || containerName != expectedContainerName { if podID != expectedPodID || containerName != expectedContainerName {
return nil, fmt.Errorf("bad podID or containerName: podID=%v; containerName=%v", podID, containerName) return nil, fmt.Errorf("bad podID or containerName: podID=%v; containerName=%v", podID, containerName)
} }
return expectedStats, nil return expectedInfo, nil
} }
resp, err := http.Get(fw.testHTTPServer.URL + fmt.Sprintf("/stats/%v/%v", expectedPodID, expectedContainerName)) resp, err := http.Get(fw.testHTTPServer.URL + fmt.Sprintf("/stats/%v/%v", expectedPodID, expectedContainerName))
@ -178,20 +183,19 @@ func TestContainerStats(t *testing.T) {
t.Fatalf("Got error GETing: %v", err) t.Fatalf("Got error GETing: %v", err)
} }
defer resp.Body.Close() defer resp.Body.Close()
var receivedStats info.ContainerInfo var receivedInfo info.ContainerInfo
decoder := json.NewDecoder(resp.Body) err = json.NewDecoder(resp.Body).Decode(&receivedInfo)
err = decoder.Decode(&receivedStats)
if err != nil { if err != nil {
t.Fatalf("received invalid json data: %v", err) t.Fatalf("received invalid json data: %v", err)
} }
if !reflect.DeepEqual(&receivedStats, expectedStats) { if !reflect.DeepEqual(&receivedInfo, expectedInfo) {
t.Errorf("received wrong data: %#v", receivedStats) t.Errorf("received wrong data: %#v", receivedInfo)
} }
} }
func TestMachineStats(t *testing.T) { func TestRootInfo(t *testing.T) {
fw := makeServerTest() fw := makeServerTest()
expectedStats := &info.ContainerInfo{ expectedInfo := &info.ContainerInfo{
StatsPercentiles: &info.ContainerStatsPercentiles{ StatsPercentiles: &info.ContainerStatsPercentiles{
MaxMemoryUsage: 1024001, MaxMemoryUsage: 1024001,
CpuUsagePercentiles: []info.Percentile{ CpuUsagePercentiles: []info.Percentile{
@ -206,8 +210,8 @@ func TestMachineStats(t *testing.T) {
}, },
}, },
} }
fw.fakeKubelet.machineStatsFunc = func(req *info.ContainerInfoRequest) (*info.ContainerInfo, error) { fw.fakeKubelet.rootInfoFunc = func(req *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
return expectedStats, nil return expectedInfo, nil
} }
resp, err := http.Get(fw.testHTTPServer.URL + "/stats") resp, err := http.Get(fw.testHTTPServer.URL + "/stats")
@ -215,13 +219,37 @@ func TestMachineStats(t *testing.T) {
t.Fatalf("Got error GETing: %v", err) t.Fatalf("Got error GETing: %v", err)
} }
defer resp.Body.Close() defer resp.Body.Close()
var receivedStats info.ContainerInfo var receivedInfo info.ContainerInfo
decoder := json.NewDecoder(resp.Body) err = json.NewDecoder(resp.Body).Decode(&receivedInfo)
err = decoder.Decode(&receivedStats)
if err != nil { if err != nil {
t.Fatalf("received invalid json data: %v", err) t.Fatalf("received invalid json data: %v", err)
} }
if !reflect.DeepEqual(&receivedStats, expectedStats) { if !reflect.DeepEqual(&receivedInfo, expectedInfo) {
t.Errorf("received wrong data: %#v", receivedStats) t.Errorf("received wrong data: %#v", receivedInfo)
}
}
func TestMachineInfo(t *testing.T) {
fw := makeServerTest()
expectedInfo := &info.MachineInfo{
NumCores: 4,
MemoryCapacity: 1024,
}
fw.fakeKubelet.machineInfoFunc = func() (*info.MachineInfo, error) {
return expectedInfo, nil
}
resp, err := http.Get(fw.testHTTPServer.URL + "/spec")
if err != nil {
t.Fatalf("Got error GETing: %v", err)
}
defer resp.Body.Close()
var receivedInfo info.MachineInfo
err = json.NewDecoder(resp.Body).Decode(&receivedInfo)
if err != nil {
t.Fatalf("received invalid json data: %v", err)
}
if !reflect.DeepEqual(&receivedInfo, expectedInfo) {
t.Errorf("received wrong data: %#v", receivedInfo)
} }
} }