diff --git a/pkg/client/containerinfo.go b/pkg/client/containerinfo.go index 9e41dc5cf44..a6dc2f681e5 100644 --- a/pkg/client/containerinfo.go +++ b/pkg/client/containerinfo.go @@ -29,8 +29,12 @@ import ( ) type ContainerInfoGetter interface { + // GetContainerInfo returns information about a container. 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 { @@ -38,6 +42,35 @@ type HTTPContainerInfoGetter struct { 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) { var body io.Reader 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", path, host, response.Status) } - decoder := json.NewDecoder(response.Body) var cinfo info.ContainerInfo - err = decoder.Decode(&cinfo) + err = json.NewDecoder(response.Body).Decode(&cinfo) if err != nil { 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) } diff --git a/pkg/client/containerinfo_test.go b/pkg/client/containerinfo_test.go index 74551fdb8ac..4d4a04543b2 100644 --- a/pkg/client/containerinfo_test.go +++ b/pkg/client/containerinfo_test.go @@ -53,9 +53,8 @@ func testHTTPContainerInfoGetter( expectedPath, r.URL.Path) } - decoder := json.NewDecoder(r.Body) var receivedReq info.ContainerInfoRequest - err := decoder.Decode(&receivedReq) + err := json.NewDecoder(r.Body).Decode(&receivedReq) if err != nil { t.Fatal(err) } @@ -67,8 +66,7 @@ func testHTTPContainerInfoGetter( if !reflect.DeepEqual(expectedReq, &receivedReq) { t.Errorf("received wrong request") } - encoder := json.NewEncoder(w) - err = encoder.Encode(cinfo) + err = json.NewEncoder(w).Encode(cinfo) if err != nil { t.Fatal(err) } @@ -93,7 +91,7 @@ func testHTTPContainerInfoGetter( if len(podID) > 0 && len(containerID) > 0 { receivedContainerInfo, err = containerInfoGetter.GetContainerInfo(parts[0], podID, containerID, req) } else { - receivedContainerInfo, err = containerInfoGetter.GetMachineInfo(parts[0], req) + receivedContainerInfo, err = containerInfoGetter.GetRootInfo(parts[0], req) } if status == 0 || status == http.StatusOK { if err != nil { @@ -125,7 +123,7 @@ func TestHTTPContainerInfoGetterGetContainerInfoSuccessfully(t *testing.T) { testHTTPContainerInfoGetter(req, cinfo, "somePodID", "containerNameInK8S", 0, t) } -func TestHTTPContainerInfoGetterGetMachineInfoSuccessfully(t *testing.T) { +func TestHTTPContainerInfoGetterGetRootInfoSuccessfully(t *testing.T) { req := &info.ContainerInfoRequest{ NumStats: 10, NumSamples: 10, @@ -155,7 +153,7 @@ func TestHTTPContainerInfoGetterGetContainerInfoWithError(t *testing.T) { testHTTPContainerInfoGetter(req, cinfo, "somePodID", "containerNameInK8S", http.StatusNotFound, t) } -func TestHTTPContainerInfoGetterGetMachineInfoWithError(t *testing.T) { +func TestHTTPContainerInfoGetterGetRootInfoWithError(t *testing.T) { req := &info.ContainerInfoRequest{ NumStats: 10, NumSamples: 10, @@ -169,3 +167,39 @@ func TestHTTPContainerInfoGetterGetMachineInfoWithError(t *testing.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") + } +} diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index 6909411121e..d6936d4cc9b 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -769,11 +769,15 @@ func (kl *Kubelet) GetContainerInfo(manifestID, containerName string, req *info. return kl.statsFromContainerPath(fmt.Sprintf("/docker/%s", dockerContainer.ID), req) } -// GetMachineStats returns stats (from Cadvisor) of current machine. -func (kl *Kubelet) GetMachineStats(req *info.ContainerInfoRequest) (*info.ContainerInfo, error) { +// GetRootInfo returns stats (from Cadvisor) of current machine (root container). +func (kl *Kubelet) GetRootInfo(req *info.ContainerInfoRequest) (*info.ContainerInfo, error) { 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) { // Give the container 60 seconds to start up. if container.LivenessProbe == nil { diff --git a/pkg/kubelet/kubelet_test.go b/pkg/kubelet/kubelet_test.go index 1777e6fec4c..f4bb954567d 100644 --- a/pkg/kubelet/kubelet_test.go +++ b/pkg/kubelet/kubelet_test.go @@ -948,7 +948,7 @@ func areSamePercentiles( } } -func TestGetContainerStats(t *testing.T) { +func TestGetContainerInfo(t *testing.T) { containerID := "ab2cdf" containerPath := fmt.Sprintf("/docker/%v", containerID) containerInfo := &info.ContainerInfo{ @@ -1001,7 +1001,7 @@ func TestGetContainerStats(t *testing.T) { mockCadvisor.AssertExpectations(t) } -func TestGetMachineStats(t *testing.T) { +func TestGetRooInfo(t *testing.T) { containerPath := "/" containerInfo := &info.ContainerInfo{ 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. - stats, err := kubelet.GetMachineStats(req) + stats, err := kubelet.GetRootInfo(req) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -1044,7 +1044,7 @@ func TestGetMachineStats(t *testing.T) { mockCadvisor.AssertExpectations(t) } -func TestGetContainerStatsWithoutCadvisor(t *testing.T) { +func TestGetContainerInfoWithoutCadvisor(t *testing.T) { kubelet, _, fakeDocker := makeTestKubelet(t) 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" containerPath := fmt.Sprintf("/docker/%v", containerID) @@ -1107,7 +1107,7 @@ func TestGetContainerStatsWhenCadvisorFailed(t *testing.T) { mockCadvisor.AssertExpectations(t) } -func TestGetContainerStatsOnNonExistContainer(t *testing.T) { +func TestGetContainerInfoOnNonExistContainer(t *testing.T) { mockCadvisor := &mockCadvisorClient{} kubelet, _, fakeDocker := makeTestKubelet(t) diff --git a/pkg/kubelet/server.go b/pkg/kubelet/server.go index 5789df17fc0..9eda58a4066 100644 --- a/pkg/kubelet/server.go +++ b/pkg/kubelet/server.go @@ -44,7 +44,8 @@ type Server struct { // For testablitiy. type kubeletInterface interface { 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) } @@ -108,6 +109,19 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) { w.Write(data) case strings.HasPrefix(u.Path, "/stats"): 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: s.DelegateHandler.ServeHTTP(w, req) } @@ -119,8 +133,7 @@ func (s *Server) serveStats(w http.ResponseWriter, req *http.Request) { var stats *info.ContainerInfo var err error var query info.ContainerInfoRequest - decoder := json.NewDecoder(req.Body) - err = decoder.Decode(&query) + err = json.NewDecoder(req.Body).Decode(&query) if err != nil && err != io.EOF { s.error(w, err) return @@ -128,7 +141,7 @@ func (s *Server) serveStats(w http.ResponseWriter, req *http.Request) { switch len(components) { case 1: // Machine stats - stats, err = s.Kubelet.GetMachineStats(&query) + stats, err = s.Kubelet.GetRootInfo(&query) case 2: // pod stats // TODO(monnand) Implement this diff --git a/pkg/kubelet/server_test.go b/pkg/kubelet/server_test.go index 0db9b2f9a4e..db79d36c392 100644 --- a/pkg/kubelet/server_test.go +++ b/pkg/kubelet/server_test.go @@ -33,9 +33,10 @@ import ( ) type fakeKubelet struct { - infoFunc func(name string) (api.PodInfo, error) - containerStatsFunc func(podID, containerName string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) - machineStatsFunc func(query *info.ContainerInfoRequest) (*info.ContainerInfo, error) + infoFunc func(name string) (api.PodInfo, error) + containerInfoFunc func(podID, containerName string, req *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) { @@ -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) { - return fk.containerStatsFunc(podID, containerName, req) + return fk.containerInfoFunc(podID, containerName, req) } -func (fk *fakeKubelet) GetMachineStats(req *info.ContainerInfoRequest) (*info.ContainerInfo, error) { - return fk.machineStatsFunc(req) +func (fk *fakeKubelet) GetRootInfo(req *info.ContainerInfoRequest) (*info.ContainerInfo, error) { + return fk.rootInfoFunc(req) +} + +func (fk *fakeKubelet) GetMachineInfo() (*info.MachineInfo, error) { + return fk.machineInfoFunc() } type serverTestFramework struct { @@ -147,9 +152,9 @@ func TestPodInfo(t *testing.T) { } } -func TestContainerStats(t *testing.T) { +func TestContainerInfo(t *testing.T) { fw := makeServerTest() - expectedStats := &info.ContainerInfo{ + expectedInfo := &info.ContainerInfo{ StatsPercentiles: &info.ContainerStatsPercentiles{ MaxMemoryUsage: 1024001, CpuUsagePercentiles: []info.Percentile{ @@ -166,11 +171,11 @@ func TestContainerStats(t *testing.T) { } expectedPodID := "somepod" 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 { 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)) @@ -178,20 +183,19 @@ func TestContainerStats(t *testing.T) { t.Fatalf("Got error GETing: %v", err) } defer resp.Body.Close() - var receivedStats info.ContainerInfo - decoder := json.NewDecoder(resp.Body) - err = decoder.Decode(&receivedStats) + var receivedInfo info.ContainerInfo + err = json.NewDecoder(resp.Body).Decode(&receivedInfo) if err != nil { t.Fatalf("received invalid json data: %v", err) } - if !reflect.DeepEqual(&receivedStats, expectedStats) { - t.Errorf("received wrong data: %#v", receivedStats) + if !reflect.DeepEqual(&receivedInfo, expectedInfo) { + t.Errorf("received wrong data: %#v", receivedInfo) } } -func TestMachineStats(t *testing.T) { +func TestRootInfo(t *testing.T) { fw := makeServerTest() - expectedStats := &info.ContainerInfo{ + expectedInfo := &info.ContainerInfo{ StatsPercentiles: &info.ContainerStatsPercentiles{ MaxMemoryUsage: 1024001, CpuUsagePercentiles: []info.Percentile{ @@ -206,8 +210,8 @@ func TestMachineStats(t *testing.T) { }, }, } - fw.fakeKubelet.machineStatsFunc = func(req *info.ContainerInfoRequest) (*info.ContainerInfo, error) { - return expectedStats, nil + fw.fakeKubelet.rootInfoFunc = func(req *info.ContainerInfoRequest) (*info.ContainerInfo, error) { + return expectedInfo, nil } resp, err := http.Get(fw.testHTTPServer.URL + "/stats") @@ -215,13 +219,37 @@ func TestMachineStats(t *testing.T) { t.Fatalf("Got error GETing: %v", err) } defer resp.Body.Close() - var receivedStats info.ContainerInfo - decoder := json.NewDecoder(resp.Body) - err = decoder.Decode(&receivedStats) + var receivedInfo info.ContainerInfo + err = json.NewDecoder(resp.Body).Decode(&receivedInfo) if err != nil { t.Fatalf("received invalid json data: %v", err) } - if !reflect.DeepEqual(&receivedStats, expectedStats) { - t.Errorf("received wrong data: %#v", receivedStats) + if !reflect.DeepEqual(&receivedInfo, expectedInfo) { + 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) } }