diff --git a/pkg/client/containerinfo.go b/pkg/client/containerinfo.go new file mode 100644 index 00000000000..9e41dc5cf44 --- /dev/null +++ b/pkg/client/containerinfo.go @@ -0,0 +1,91 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "strconv" + + "github.com/google/cadvisor/info" +) + +type ContainerInfoGetter interface { + GetContainerInfo(host, podID, containerID string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) + GetMachineInfo(host string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) +} + +type HTTPContainerInfoGetter struct { + Client *http.Client + Port int +} + +func (self *HTTPContainerInfoGetter) getContainerInfo(host, path string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) { + var body io.Reader + if req != nil { + content, err := json.Marshal(req) + if err != nil { + return nil, err + } + body = bytes.NewBuffer(content) + } + + request, err := http.NewRequest( + "GET", + fmt.Sprintf("http://%v/stats/%v", + net.JoinHostPort(host, strconv.Itoa(self.Port)), + path, + ), + body, + ) + 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 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) + if err != nil { + return nil, err + } + return &cinfo, nil +} + +func (self *HTTPContainerInfoGetter) GetContainerInfo(host, podID, containerID string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) { + return self.getContainerInfo( + host, + fmt.Sprintf("%v/%v", podID, containerID), + req, + ) +} + +func (self *HTTPContainerInfoGetter) GetMachineInfo(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 new file mode 100644 index 00000000000..74551fdb8ac --- /dev/null +++ b/pkg/client/containerinfo_test.go @@ -0,0 +1,171 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package client + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "net/url" + "path" + "reflect" + "strconv" + "strings" + "testing" + "time" + + "github.com/google/cadvisor/info" + itest "github.com/google/cadvisor/info/test" +) + +func testHTTPContainerInfoGetter( + req *info.ContainerInfoRequest, + cinfo *info.ContainerInfo, + podID string, + containerID string, + status int, + t *testing.T, +) { + expectedPath := "/stats" + if len(podID) > 0 && len(containerID) > 0 { + expectedPath = path.Join(expectedPath, podID, containerID) + } + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if status != 0 { + w.WriteHeader(status) + } + if strings.TrimRight(r.URL.Path, "/") != strings.TrimRight(expectedPath, "/") { + t.Fatalf("Received request to an invalid path. Should be %v. got %v", + expectedPath, r.URL.Path) + } + + decoder := json.NewDecoder(r.Body) + var receivedReq info.ContainerInfoRequest + err := decoder.Decode(&receivedReq) + if err != nil { + t.Fatal(err) + } + // Note: This will not make a deep copy of req. + // So changing req after Get*Info would be a race. + expectedReq := req + // Fill any empty fields with default value + expectedReq = expectedReq.FillDefaults() + if !reflect.DeepEqual(expectedReq, &receivedReq) { + t.Errorf("received wrong request") + } + encoder := json.NewEncoder(w) + err = encoder.Encode(cinfo) + 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, + } + + var receivedContainerInfo *info.ContainerInfo + if len(podID) > 0 && len(containerID) > 0 { + receivedContainerInfo, err = containerInfoGetter.GetContainerInfo(parts[0], podID, containerID, req) + } else { + receivedContainerInfo, err = containerInfoGetter.GetMachineInfo(parts[0], req) + } + if status == 0 || status == http.StatusOK { + if err != nil { + t.Errorf("received unexpected error: %v", err) + } + + if !receivedContainerInfo.Eq(cinfo) { + t.Error("received unexpected container info") + } + } else { + if err == nil { + t.Error("did not receive expected error.") + } + } +} + +func TestHTTPContainerInfoGetterGetContainerInfoSuccessfully(t *testing.T) { + req := &info.ContainerInfoRequest{ + NumStats: 10, + NumSamples: 10, + } + req = req.FillDefaults() + cinfo := itest.GenerateRandomContainerInfo( + "dockerIDWhichWillNotBeChecked", // docker ID + 2, // Number of cores + req, + 1*time.Second, + ) + testHTTPContainerInfoGetter(req, cinfo, "somePodID", "containerNameInK8S", 0, t) +} + +func TestHTTPContainerInfoGetterGetMachineInfoSuccessfully(t *testing.T) { + req := &info.ContainerInfoRequest{ + NumStats: 10, + NumSamples: 10, + } + req = req.FillDefaults() + cinfo := itest.GenerateRandomContainerInfo( + "dockerIDWhichWillNotBeChecked", // docker ID + 2, // Number of cores + req, + 1*time.Second, + ) + testHTTPContainerInfoGetter(req, cinfo, "", "", 0, t) +} + +func TestHTTPContainerInfoGetterGetContainerInfoWithError(t *testing.T) { + req := &info.ContainerInfoRequest{ + NumStats: 10, + NumSamples: 10, + } + req = req.FillDefaults() + cinfo := itest.GenerateRandomContainerInfo( + "dockerIDWhichWillNotBeChecked", // docker ID + 2, // Number of cores + req, + 1*time.Second, + ) + testHTTPContainerInfoGetter(req, cinfo, "somePodID", "containerNameInK8S", http.StatusNotFound, t) +} + +func TestHTTPContainerInfoGetterGetMachineInfoWithError(t *testing.T) { + req := &info.ContainerInfoRequest{ + NumStats: 10, + NumSamples: 10, + } + req = req.FillDefaults() + cinfo := itest.GenerateRandomContainerInfo( + "dockerIDWhichWillNotBeChecked", // docker ID + 2, // Number of cores + req, + 1*time.Second, + ) + testHTTPContainerInfoGetter(req, cinfo, "", "", http.StatusNotFound, t) +} diff --git a/third_party/src/github.com/google/cadvisor/CHANGELOG.md b/third_party/src/github.com/google/cadvisor/CHANGELOG.md index 32d8b2ec2e0..423ee7823df 100644 --- a/third_party/src/github.com/google/cadvisor/CHANGELOG.md +++ b/third_party/src/github.com/google/cadvisor/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.1.3 (2014-07-14) +- Add support for systemd systems. +- Fixes for UI with InfluxDB storage driver. + ## 0.1.2 (2014-07-10) - Added Storage Driver concept (flag: storage_driver), default is the in-memory driver - Implemented InfluxDB storage driver diff --git a/third_party/src/github.com/google/cadvisor/client/client_test.go b/third_party/src/github.com/google/cadvisor/client/client_test.go index a5b7f69113a..323bbf8d0e4 100644 --- a/third_party/src/github.com/google/cadvisor/client/client_test.go +++ b/third_party/src/github.com/google/cadvisor/client/client_test.go @@ -110,32 +110,7 @@ func TestGetContainerInfo(t *testing.T) { t.Fatal(err) } - // We cannot use DeepEqual() to compare them directly, - // because json en/decoded time may have precision issues. - if !reflect.DeepEqual(returned.ContainerReference, cinfo.ContainerReference) { - t.Errorf("received unexpected container ref") - } - if !reflect.DeepEqual(returned.Subcontainers, cinfo.Subcontainers) { - t.Errorf("received unexpected subcontainers") - } - if !reflect.DeepEqual(returned.Spec, cinfo.Spec) { - t.Errorf("received unexpected spec") - } - if !reflect.DeepEqual(returned.StatsPercentiles, cinfo.StatsPercentiles) { - t.Errorf("received unexpected spec") - } - - for i, expectedStats := range cinfo.Stats { - returnedStats := returned.Stats[i] - if !expectedStats.Eq(returnedStats) { - t.Errorf("received unexpected stats") - } - } - - for i, expectedSample := range cinfo.Samples { - returnedSample := returned.Samples[i] - if !expectedSample.Eq(returnedSample) { - t.Errorf("received unexpected sample") - } + if !returned.Eq(cinfo) { + t.Error("received unexpected ContainerInfo") } } diff --git a/third_party/src/github.com/google/cadvisor/container/docker/handler.go b/third_party/src/github.com/google/cadvisor/container/docker/handler.go index f6456a4cf2e..fd170d67eec 100644 --- a/third_party/src/github.com/google/cadvisor/container/docker/handler.go +++ b/third_party/src/github.com/google/cadvisor/container/docker/handler.go @@ -21,12 +21,14 @@ import ( "math" "os" "path" + "path/filepath" "strings" "time" "github.com/docker/libcontainer" "github.com/docker/libcontainer/cgroups" "github.com/docker/libcontainer/cgroups/fs" + "github.com/docker/libcontainer/cgroups/systemd" "github.com/fsouza/go-dockerclient" "github.com/google/cadvisor/container" "github.com/google/cadvisor/info" @@ -98,8 +100,11 @@ func (self *dockerContainerHandler) splitName() (string, string, error) { if nestedLevels > 0 { // we are running inside a docker container upperLevel := strings.Repeat("../../", nestedLevels) - //parent = strings.Join([]string{parent, upperLevel}, "/") - parent = fmt.Sprintf("%v%v", upperLevel, parent) + parent = filepath.Join(upperLevel, parent) + } + // Strip the last "/" + if parent[len(parent)-1] == '/' { + parent = parent[:len(parent)-1] } return parent, id, nil } @@ -237,7 +242,15 @@ func (self *dockerContainerHandler) GetStats() (stats *info.ContainerStats, err Parent: parent, Name: id, } - s, err := fs.GetStats(cg) + + // TODO(vmarmol): Use libcontainer's Stats() in the new API when that is ready. + // Use systemd paths if systemd is being used. + var s *cgroups.Stats + if systemd.UseSystemd() { + s, err = systemd.GetStats(cg) + } else { + s, err = fs.GetStats(cg) + } if err != nil { return } diff --git a/third_party/src/github.com/google/cadvisor/deploy/docker-only/Dockerfile b/third_party/src/github.com/google/cadvisor/deploy/docker-only/Dockerfile index cede96956fc..a3b18888d88 100644 --- a/third_party/src/github.com/google/cadvisor/deploy/docker-only/Dockerfile +++ b/third_party/src/github.com/google/cadvisor/deploy/docker-only/Dockerfile @@ -2,7 +2,7 @@ FROM busybox:ubuntu-14.04 MAINTAINER kyurtsever@google.com dengnan@google.com vmarmol@google.com jason@swindle.me # Get cAdvisor binaries. -ADD http://storage.googleapis.com/cadvisor-bin/cadvisor /usr/bin/cadvisor +ADD http://storage.googleapis.com/cadvisor-bin/cadvisor-0.1.3 /usr/bin/cadvisor RUN chmod +x /usr/bin/cadvisor EXPOSE 8080 diff --git a/third_party/src/github.com/google/cadvisor/deploy/lmctfy-docker/Dockerfile b/third_party/src/github.com/google/cadvisor/deploy/lmctfy-docker/Dockerfile index 38f03fcc63e..6060cff0824 100644 --- a/third_party/src/github.com/google/cadvisor/deploy/lmctfy-docker/Dockerfile +++ b/third_party/src/github.com/google/cadvisor/deploy/lmctfy-docker/Dockerfile @@ -7,7 +7,7 @@ RUN apt-get update && apt-get install -y -q --no-install-recommends pkg-config l # Get the lcmtfy and cAdvisor binaries. ADD http://storage.googleapis.com/cadvisor-bin/lmctfy/lmctfy /usr/bin/lmctfy ADD http://storage.googleapis.com/cadvisor-bin/lmctfy/libre2.so.0.0.0 /usr/lib/libre2.so.0 -ADD http://storage.googleapis.com/cadvisor-bin/cadvisor-0.1.2 /usr/bin/cadvisor +ADD http://storage.googleapis.com/cadvisor-bin/cadvisor-0.1.3 /usr/bin/cadvisor RUN chmod +x /usr/bin/lmctfy && chmod +x /usr/bin/cadvisor EXPOSE 8080 diff --git a/third_party/src/github.com/google/cadvisor/info/container.go b/third_party/src/github.com/google/cadvisor/info/container.go index cd6a717502e..57ba814173c 100644 --- a/third_party/src/github.com/google/cadvisor/info/container.go +++ b/third_party/src/github.com/google/cadvisor/info/container.go @@ -110,6 +110,52 @@ type ContainerInfo struct { StatsPercentiles *ContainerStatsPercentiles `json:"stats_summary,omitempty"` } +// ContainerInfo may be (un)marshaled by json or other en/decoder. In that +// case, the Timestamp field in each stats/sample may not be precisely +// en/decoded. This will lead to small but acceptable differences between a +// ContainerInfo and its encode-then-decode version. Eq() is used to compare +// two ContainerInfo accepting small difference (<10ms) of Time fields. +func (self *ContainerInfo) Eq(b *ContainerInfo) bool { + + // If both self and b are nil, then Eq() returns true + if self == nil { + return b == nil + } + if b == nil { + return self == nil + } + + // For fields other than time.Time, we will compare them precisely. + // This would require that any slice should have same order. + if !reflect.DeepEqual(self.ContainerReference, b.ContainerReference) { + return false + } + if !reflect.DeepEqual(self.Subcontainers, b.Subcontainers) { + return false + } + if !reflect.DeepEqual(self.Spec, b.Spec) { + return false + } + if !reflect.DeepEqual(self.StatsPercentiles, b.StatsPercentiles) { + return false + } + + for i, expectedStats := range b.Stats { + selfStats := self.Stats[i] + if !expectedStats.Eq(selfStats) { + return false + } + } + + for i, expectedSample := range b.Samples { + selfSample := self.Samples[i] + if !expectedSample.Eq(selfSample) { + return false + } + } + return true +} + func (self *ContainerInfo) StatsAfter(ref time.Time) []*ContainerStats { n := len(self.Stats) + 1 for i, s := range self.Stats { diff --git a/third_party/src/github.com/google/cadvisor/info/version.go b/third_party/src/github.com/google/cadvisor/info/version.go index 96d233c9d24..5440de4ff1b 100644 --- a/third_party/src/github.com/google/cadvisor/info/version.go +++ b/third_party/src/github.com/google/cadvisor/info/version.go @@ -15,4 +15,4 @@ package info // Version of cAdvisor. -const VERSION = "0.1.2" +const VERSION = "0.1.3"