mirror of https://github.com/k3s-io/k3s
add a client to get container info from kubelet
parent
c7d31fabbc
commit
6878f105c0
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -15,4 +15,4 @@
|
|||
package info
|
||||
|
||||
// Version of cAdvisor.
|
||||
const VERSION = "0.1.2"
|
||||
const VERSION = "0.1.3"
|
||||
|
|
Loading…
Reference in New Issue