Merge branch 'master' into fix/golint2
Conflicts: pkg/api/types.go pkg/health/health_check.go pkg/kubelet/kubelet.go pkg/kubelet/kubelet_server.go pkg/kubelet/kubelet_server_test.go pkg/kubelet/kubelet_test.go
This commit is contained in:
@@ -127,7 +127,7 @@ echo " This might loop forever if there was some uncaught error during start"
|
|||||||
echo " up."
|
echo " up."
|
||||||
echo
|
echo
|
||||||
|
|
||||||
until $(curl --insecure --user ${user}:${passwd} --max-time 1 \
|
until $(curl --insecure --user ${user}:${passwd} --max-time 5 \
|
||||||
--fail --output /dev/null --silent https://${KUBE_MASTER_IP}/api/v1beta1/pods); do
|
--fail --output /dev/null --silent https://${KUBE_MASTER_IP}/api/v1beta1/pods); do
|
||||||
printf "."
|
printf "."
|
||||||
sleep 2
|
sleep 2
|
||||||
|
@@ -2,7 +2,7 @@ version: v1beta2
|
|||||||
id: cadvisor-agent
|
id: cadvisor-agent
|
||||||
containers:
|
containers:
|
||||||
- name: cadvisor
|
- name: cadvisor
|
||||||
image: google/cadvisor
|
image: google/cadvisor:latest
|
||||||
ports:
|
ports:
|
||||||
- name: http
|
- name: http
|
||||||
containerPort: 8080
|
containerPort: 8080
|
||||||
|
@@ -147,20 +147,6 @@ type Container struct {
|
|||||||
LivenessProbe *LivenessProbe `yaml:"livenessProbe,omitempty" json:"livenessProbe,omitempty"`
|
LivenessProbe *LivenessProbe `yaml:"livenessProbe,omitempty" json:"livenessProbe,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Percentile represents a pair which contains a percentage from 0 to 100 and
|
|
||||||
// its corresponding value.
|
|
||||||
type Percentile struct {
|
|
||||||
Percentage int `json:"percentage,omitempty"`
|
|
||||||
Value uint64 `json:"value,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContainerStats represents statistical information of a container
|
|
||||||
type ContainerStats struct {
|
|
||||||
CPUUsagePercentiles []Percentile `json:"cpu_usage_percentiles,omitempty"`
|
|
||||||
MemoryUsagePercentiles []Percentile `json:"memory_usage_percentiles,omitempty"`
|
|
||||||
MaxMemoryUsage uint64 `json:"max_memory_usage,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Event is the representation of an event logged to etcd backends
|
// Event is the representation of an event logged to etcd backends
|
||||||
type Event struct {
|
type Event struct {
|
||||||
Event string `json:"event,omitempty"`
|
Event string `json:"event,omitempty"`
|
||||||
|
@@ -20,12 +20,14 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"path"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/httplog"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||||
@@ -83,25 +85,37 @@ func MakeAsync(fn WorkFunc) <-chan interface{} {
|
|||||||
//
|
//
|
||||||
// TODO: consider migrating this to go-restful which is a more full-featured version of the same thing.
|
// TODO: consider migrating this to go-restful which is a more full-featured version of the same thing.
|
||||||
type APIServer struct {
|
type APIServer struct {
|
||||||
prefix string
|
prefix string
|
||||||
storage map[string]RESTStorage
|
storage map[string]RESTStorage
|
||||||
ops *Operations
|
ops *Operations
|
||||||
logserver http.Handler
|
mux *http.ServeMux
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new APIServer object.
|
// New creates a new APIServer object.
|
||||||
// 'storage' contains a map of handlers.
|
// 'storage' contains a map of handlers.
|
||||||
// 'prefix' is the hosting path prefix.
|
// 'prefix' is the hosting path prefix.
|
||||||
func New(storage map[string]RESTStorage, prefix string) *APIServer {
|
func New(storage map[string]RESTStorage, prefix string) *APIServer {
|
||||||
return &APIServer{
|
s := &APIServer{
|
||||||
storage: storage,
|
storage: storage,
|
||||||
prefix: prefix,
|
prefix: strings.TrimRight(prefix, "/"),
|
||||||
ops: NewOperations(),
|
ops: NewOperations(),
|
||||||
logserver: http.StripPrefix("/logs/", http.FileServer(http.Dir("/var/log/"))),
|
mux: http.NewServeMux(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.mux.Handle("/logs/", http.StripPrefix("/logs/", http.FileServer(http.Dir("/var/log/"))))
|
||||||
|
s.mux.HandleFunc(s.prefix+"/", s.ServeREST)
|
||||||
|
healthz.InstallHandler(s.mux)
|
||||||
|
s.mux.HandleFunc("/index.html", s.handleIndex)
|
||||||
|
|
||||||
|
// Handle both operations and operations/* with the same handler
|
||||||
|
opPrefix := path.Join(s.prefix, "operations")
|
||||||
|
s.mux.HandleFunc(opPrefix, s.handleOperationRequest)
|
||||||
|
s.mux.HandleFunc(opPrefix+"/", s.handleOperationRequest)
|
||||||
|
|
||||||
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *APIServer) handleIndex(w http.ResponseWriter) {
|
func (server *APIServer) handleIndex(w http.ResponseWriter, req *http.Request) {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
// TODO: serve this out of a file?
|
// TODO: serve this out of a file?
|
||||||
data := "<html><body>Welcome to Kubernetes</body></html>"
|
data := "<html><body>Welcome to Kubernetes</body></html>"
|
||||||
@@ -117,47 +131,37 @@ func (server *APIServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
glog.Infof("APIServer panic'd on %v %v: %#v\n%s\n", req.Method, req.RequestURI, x, debug.Stack())
|
glog.Infof("APIServer panic'd on %v %v: %#v\n%s\n", req.Method, req.RequestURI, x, debug.Stack())
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
defer MakeLogged(req, &w).StacktraceWhen(
|
defer httplog.MakeLogged(req, &w).StacktraceWhen(
|
||||||
StatusIsNot(
|
httplog.StatusIsNot(
|
||||||
http.StatusOK,
|
http.StatusOK,
|
||||||
http.StatusAccepted,
|
http.StatusAccepted,
|
||||||
http.StatusConflict,
|
http.StatusConflict,
|
||||||
),
|
),
|
||||||
).Log()
|
).Log()
|
||||||
url, err := url.ParseRequestURI(req.RequestURI)
|
|
||||||
if err != nil {
|
// Dispatch via our mux.
|
||||||
server.error(err, w)
|
server.mux.ServeHTTP(w, req)
|
||||||
return
|
}
|
||||||
}
|
|
||||||
if url.Path == "/index.html" || url.Path == "/" || url.Path == "" {
|
// ServeREST handles requests to all our RESTStorage objects.
|
||||||
server.handleIndex(w)
|
func (server *APIServer) ServeREST(w http.ResponseWriter, req *http.Request) {
|
||||||
return
|
if !strings.HasPrefix(req.URL.Path, server.prefix) {
|
||||||
}
|
|
||||||
if strings.HasPrefix(url.Path, "/logs/") {
|
|
||||||
server.logserver.ServeHTTP(w, req)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !strings.HasPrefix(url.Path, server.prefix) {
|
|
||||||
server.notFound(req, w)
|
server.notFound(req, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
requestParts := strings.Split(url.Path[len(server.prefix):], "/")[1:]
|
requestParts := strings.Split(req.URL.Path[len(server.prefix):], "/")[1:]
|
||||||
if len(requestParts) < 1 {
|
if len(requestParts) < 1 {
|
||||||
server.notFound(req, w)
|
server.notFound(req, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if requestParts[0] == "operations" {
|
|
||||||
server.handleOperationRequest(requestParts[1:], w, req)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
storage := server.storage[requestParts[0]]
|
storage := server.storage[requestParts[0]]
|
||||||
if storage == nil {
|
if storage == nil {
|
||||||
LogOf(w).Addf("'%v' has no storage object", requestParts[0])
|
httplog.LogOf(w).Addf("'%v' has no storage object", requestParts[0])
|
||||||
server.notFound(req, w)
|
server.notFound(req, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
server.handleREST(requestParts, url, req, w, storage)
|
server.handleREST(requestParts, req, w, storage)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *APIServer) notFound(req *http.Request, w http.ResponseWriter) {
|
func (server *APIServer) notFound(req *http.Request, w http.ResponseWriter) {
|
||||||
@@ -197,7 +201,7 @@ func (server *APIServer) finishReq(out <-chan interface{}, sync bool, timeout ti
|
|||||||
status := http.StatusOK
|
status := http.StatusOK
|
||||||
switch stat := obj.(type) {
|
switch stat := obj.(type) {
|
||||||
case api.Status:
|
case api.Status:
|
||||||
LogOf(w).Addf("programmer error: use *api.Status as a result, not api.Status.")
|
httplog.LogOf(w).Addf("programmer error: use *api.Status as a result, not api.Status.")
|
||||||
if stat.Code != 0 {
|
if stat.Code != 0 {
|
||||||
status = stat.Code
|
status = stat.Code
|
||||||
}
|
}
|
||||||
@@ -236,14 +240,14 @@ func parseTimeout(str string) time.Duration {
|
|||||||
// sync=[false|true] Synchronous request (only applies to create, update, delete operations)
|
// sync=[false|true] Synchronous request (only applies to create, update, delete operations)
|
||||||
// timeout=<duration> Timeout for synchronous requests, only applies if sync=true
|
// timeout=<duration> Timeout for synchronous requests, only applies if sync=true
|
||||||
// labels=<label-selector> Used for filtering list operations
|
// labels=<label-selector> Used for filtering list operations
|
||||||
func (server *APIServer) handleREST(parts []string, requestURL *url.URL, req *http.Request, w http.ResponseWriter, storage RESTStorage) {
|
func (server *APIServer) handleREST(parts []string, req *http.Request, w http.ResponseWriter, storage RESTStorage) {
|
||||||
sync := requestURL.Query().Get("sync") == "true"
|
sync := req.URL.Query().Get("sync") == "true"
|
||||||
timeout := parseTimeout(requestURL.Query().Get("timeout"))
|
timeout := parseTimeout(req.URL.Query().Get("timeout"))
|
||||||
switch req.Method {
|
switch req.Method {
|
||||||
case "GET":
|
case "GET":
|
||||||
switch len(parts) {
|
switch len(parts) {
|
||||||
case 1:
|
case 1:
|
||||||
selector, err := labels.ParseSelector(requestURL.Query().Get("labels"))
|
selector, err := labels.ParseSelector(req.URL.Query().Get("labels"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
server.error(err, w)
|
server.error(err, w)
|
||||||
return
|
return
|
||||||
@@ -325,7 +329,18 @@ func (server *APIServer) handleREST(parts []string, requestURL *url.URL, req *ht
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *APIServer) handleOperationRequest(parts []string, w http.ResponseWriter, req *http.Request) {
|
func (server *APIServer) handleOperationRequest(w http.ResponseWriter, req *http.Request) {
|
||||||
|
opPrefix := path.Join(server.prefix, "operations")
|
||||||
|
if !strings.HasPrefix(req.URL.Path, opPrefix) {
|
||||||
|
server.notFound(req, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
trimmed := strings.TrimLeft(req.URL.Path[len(opPrefix):], "/")
|
||||||
|
parts := strings.Split(trimmed, "/")
|
||||||
|
if len(parts) > 1 {
|
||||||
|
server.notFound(req, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
if req.Method != "GET" {
|
if req.Method != "GET" {
|
||||||
server.notFound(req, w)
|
server.notFound(req, w)
|
||||||
}
|
}
|
||||||
|
@@ -409,3 +409,37 @@ func TestSyncCreateTimeout(t *testing.T) {
|
|||||||
t.Errorf("Unexpected status: %d, Expected: %d, %#v", response.StatusCode, 202, response)
|
t.Errorf("Unexpected status: %d, Expected: %d, %#v", response.StatusCode, 202, response)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOpGet(t *testing.T) {
|
||||||
|
simpleStorage := &SimpleRESTStorage{}
|
||||||
|
handler := New(map[string]RESTStorage{
|
||||||
|
"foo": simpleStorage,
|
||||||
|
}, "/prefix/version")
|
||||||
|
server := httptest.NewServer(handler)
|
||||||
|
client := http.Client{}
|
||||||
|
|
||||||
|
simple := Simple{Name: "foo"}
|
||||||
|
data, _ := api.Encode(simple)
|
||||||
|
request, err := http.NewRequest("POST", server.URL+"/prefix/version/foo", bytes.NewBuffer(data))
|
||||||
|
expectNoError(t, err)
|
||||||
|
response, err := client.Do(request)
|
||||||
|
expectNoError(t, err)
|
||||||
|
if response.StatusCode != http.StatusAccepted {
|
||||||
|
t.Errorf("Unexpected response %#v", response)
|
||||||
|
}
|
||||||
|
|
||||||
|
var itemOut api.Status
|
||||||
|
body, err := extractBody(response, &itemOut)
|
||||||
|
expectNoError(t, err)
|
||||||
|
if itemOut.Status != api.StatusWorking || itemOut.Details == "" {
|
||||||
|
t.Errorf("Unexpected status: %#v (%s)", itemOut, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
req2, err := http.NewRequest("GET", server.URL+"/prefix/version/operations/"+itemOut.Details, nil)
|
||||||
|
expectNoError(t, err)
|
||||||
|
_, err = client.Do(req2)
|
||||||
|
expectNoError(t, err)
|
||||||
|
if response.StatusCode != http.StatusAccepted {
|
||||||
|
t.Errorf("Unexpected response %#v", response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -34,7 +34,7 @@ type Operation struct {
|
|||||||
awaiting <-chan interface{}
|
awaiting <-chan interface{}
|
||||||
finished *time.Time
|
finished *time.Time
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
notify chan bool
|
notify chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Operations tracks all the ongoing operations.
|
// Operations tracks all the ongoing operations.
|
||||||
@@ -62,7 +62,7 @@ func (ops *Operations) NewOperation(from <-chan interface{}) *Operation {
|
|||||||
op := &Operation{
|
op := &Operation{
|
||||||
ID: strconv.FormatInt(id, 10),
|
ID: strconv.FormatInt(id, 10),
|
||||||
awaiting: from,
|
awaiting: from,
|
||||||
notify: make(chan bool, 1),
|
notify: make(chan struct{}),
|
||||||
}
|
}
|
||||||
go op.wait()
|
go op.wait()
|
||||||
go ops.insert(op)
|
go ops.insert(op)
|
||||||
@@ -116,7 +116,7 @@ func (ops *Operations) expire(maxAge time.Duration) {
|
|||||||
|
|
||||||
// Waits forever for the operation to complete; call via go when
|
// Waits forever for the operation to complete; call via go when
|
||||||
// the operation is created. Sets op.finished when the operation
|
// the operation is created. Sets op.finished when the operation
|
||||||
// does complete, and sends on the notify channel, in case there
|
// does complete, and closes the notify channel, in case there
|
||||||
// are any WaitFor() calls in progress.
|
// are any WaitFor() calls in progress.
|
||||||
// Does not keep op locked while waiting.
|
// Does not keep op locked while waiting.
|
||||||
func (op *Operation) wait() {
|
func (op *Operation) wait() {
|
||||||
@@ -128,7 +128,7 @@ func (op *Operation) wait() {
|
|||||||
op.result = result
|
op.result = result
|
||||||
finished := time.Now()
|
finished := time.Now()
|
||||||
op.finished = &finished
|
op.finished = &finished
|
||||||
op.notify <- true
|
close(op.notify)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WaitFor waits for the specified duration, or until the operation finishes,
|
// WaitFor waits for the specified duration, or until the operation finishes,
|
||||||
@@ -137,9 +137,6 @@ func (op *Operation) WaitFor(timeout time.Duration) {
|
|||||||
select {
|
select {
|
||||||
case <-time.After(timeout):
|
case <-time.After(timeout):
|
||||||
case <-op.notify:
|
case <-op.notify:
|
||||||
// Re-send on this channel in case there are others
|
|
||||||
// waiting for notification.
|
|
||||||
op.notify <- true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
18
pkg/health/doc.go
Normal file
18
pkg/health/doc.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
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 health contains utilities for health checking, as well as health status information.
|
||||||
|
package health
|
51
pkg/health/health.go
Normal file
51
pkg/health/health.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
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 health
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Status int
|
||||||
|
|
||||||
|
const (
|
||||||
|
Healthy Status = iota
|
||||||
|
Unhealthy
|
||||||
|
Unknown
|
||||||
|
)
|
||||||
|
|
||||||
|
type HTTPGetInterface interface {
|
||||||
|
Get(url string) (*http.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Check(url string, client HTTPGetInterface) (Status, error) {
|
||||||
|
res, err := client.Get(url)
|
||||||
|
if res.Body != nil {
|
||||||
|
defer res.Body.Close()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return Unknown, err
|
||||||
|
}
|
||||||
|
if res.StatusCode >= http.StatusOK && res.StatusCode < http.StatusBadRequest {
|
||||||
|
return Healthy, nil
|
||||||
|
} else {
|
||||||
|
glog.V(1).Infof("Health check failed for %s, Response: %v", url, *res)
|
||||||
|
return Unhealthy, nil
|
||||||
|
}
|
||||||
|
}
|
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package kubelet
|
package health
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -25,23 +25,8 @@ import (
|
|||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HealthCheckStatus is an enum type which describes a status of health check.
|
|
||||||
type HealthCheckStatus int
|
|
||||||
|
|
||||||
// These are the valid values of HealthCheckStatus.
|
|
||||||
const (
|
|
||||||
CheckHealthy HealthCheckStatus = 0
|
|
||||||
CheckUnhealthy HealthCheckStatus = 1
|
|
||||||
CheckUnknown HealthCheckStatus = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
// HealthChecker hides implementation details of checking health of containers.
|
|
||||||
type HealthChecker interface {
|
type HealthChecker interface {
|
||||||
HealthCheck(container api.Container) (HealthCheckStatus, error)
|
HealthCheck(container api.Container) (Status, error)
|
||||||
}
|
|
||||||
|
|
||||||
type httpDoInterface interface {
|
|
||||||
Get(string) (*http.Response, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeHealthChecker creates a new HealthChecker.
|
// MakeHealthChecker creates a new HealthChecker.
|
||||||
@@ -60,20 +45,18 @@ type MuxHealthChecker struct {
|
|||||||
checkers map[string]HealthChecker
|
checkers map[string]HealthChecker
|
||||||
}
|
}
|
||||||
|
|
||||||
// HealthCheck delegates the health-checking of the container to one of the bundled implementations.
|
func (m *MuxHealthChecker) HealthCheck(container api.Container) (Status, error) {
|
||||||
// It chooses an implementation according to container.LivenessProbe.Type.
|
|
||||||
func (m *MuxHealthChecker) HealthCheck(container api.Container) (HealthCheckStatus, error) {
|
|
||||||
checker, ok := m.checkers[container.LivenessProbe.Type]
|
checker, ok := m.checkers[container.LivenessProbe.Type]
|
||||||
if !ok || checker == nil {
|
if !ok || checker == nil {
|
||||||
glog.Warningf("Failed to find health checker for %s %s", container.Name, container.LivenessProbe.Type)
|
glog.Warningf("Failed to find health checker for %s %s", container.Name, container.LivenessProbe.Type)
|
||||||
return CheckUnknown, nil
|
return Unknown, nil
|
||||||
}
|
}
|
||||||
return checker.HealthCheck(container)
|
return checker.HealthCheck(container)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPHealthChecker is an implementation of HealthChecker which checks container health by sending HTTP Get requests.
|
// HTTPHealthChecker is an implementation of HealthChecker which checks container health by sending HTTP Get requests.
|
||||||
type HTTPHealthChecker struct {
|
type HTTPHealthChecker struct {
|
||||||
client httpDoInterface
|
client HTTPGetInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HTTPHealthChecker) findPort(container api.Container, portName string) int64 {
|
func (h *HTTPHealthChecker) findPort(container api.Container, portName string) int64 {
|
||||||
@@ -86,18 +69,17 @@ func (h *HTTPHealthChecker) findPort(container api.Container, portName string) i
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
// HealthCheck checks if the container is healthy by trying sending HTTP Get requests to the container.
|
func (h *HTTPHealthChecker) HealthCheck(container api.Container) (Status, error) {
|
||||||
func (h *HTTPHealthChecker) HealthCheck(container api.Container) (HealthCheckStatus, error) {
|
|
||||||
params := container.LivenessProbe.HTTPGet
|
params := container.LivenessProbe.HTTPGet
|
||||||
if params == nil {
|
if params == nil {
|
||||||
return CheckUnknown, fmt.Errorf("Error, no HTTP parameters specified: %v", container)
|
return Unknown, fmt.Errorf("Error, no HTTP parameters specified: %v", container)
|
||||||
}
|
}
|
||||||
port := h.findPort(container, params.Port)
|
port := h.findPort(container, params.Port)
|
||||||
if port == -1 {
|
if port == -1 {
|
||||||
var err error
|
var err error
|
||||||
port, err = strconv.ParseInt(params.Port, 10, 0)
|
port, err = strconv.ParseInt(params.Port, 10, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return CheckUnknown, err
|
return Unknown, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var host string
|
var host string
|
||||||
@@ -107,17 +89,5 @@ func (h *HTTPHealthChecker) HealthCheck(container api.Container) (HealthCheckSta
|
|||||||
host = "localhost"
|
host = "localhost"
|
||||||
}
|
}
|
||||||
url := fmt.Sprintf("http://%s:%d%s", host, port, params.Path)
|
url := fmt.Sprintf("http://%s:%d%s", host, port, params.Path)
|
||||||
res, err := h.client.Get(url)
|
return Check(url, h.client)
|
||||||
if res != nil && res.Body != nil {
|
|
||||||
defer res.Body.Close()
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
// At this point, if it fails, its either a policy (unlikely) or HTTP protocol (likely) error.
|
|
||||||
return CheckUnhealthy, nil
|
|
||||||
}
|
|
||||||
if res.StatusCode == http.StatusOK {
|
|
||||||
return CheckHealthy, nil
|
|
||||||
}
|
|
||||||
glog.V(1).Infof("Health check failed for %v, Response: %v", container, *res)
|
|
||||||
return CheckUnhealthy, nil
|
|
||||||
}
|
}
|
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package kubelet
|
package health
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -56,7 +56,7 @@ func TestHttpHealth(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ok, err := check.HealthCheck(container)
|
ok, err := check.HealthCheck(container)
|
||||||
if ok != CheckHealthy {
|
if ok != Healthy {
|
||||||
t.Error("Unexpected unhealthy")
|
t.Error("Unexpected unhealthy")
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
@@ -29,3 +29,7 @@ func handleHealthz(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write([]byte("ok"))
|
w.Write([]byte("ok"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func InstallHandler(mux *http.ServeMux) {
|
||||||
|
mux.HandleFunc("/healthz", handleHealthz)
|
||||||
|
}
|
||||||
|
19
pkg/httplog/doc.go
Normal file
19
pkg/httplog/doc.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
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 httplog contains a helper object and functions to maintain a log
|
||||||
|
// along with an http response.
|
||||||
|
package httplog
|
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package apiserver
|
package httplog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -25,6 +25,18 @@ import (
|
|||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Handler wraps all HTTP calls to delegate with nice logging.
|
||||||
|
// delegate may use LogOf(w).Addf(...) to write additional info to
|
||||||
|
// the per-request log message.
|
||||||
|
//
|
||||||
|
// Intended to wrap calls to your ServeMux.
|
||||||
|
func Handler(delegate http.Handler, pred StacktracePred) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
defer MakeLogged(req, &w).StacktraceWhen(pred).Log()
|
||||||
|
delegate.ServeHTTP(w, req)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// StacktracePred returns true if a stacktrace should be logged for this status
|
// StacktracePred returns true if a stacktrace should be logged for this status
|
||||||
type StacktracePred func(httpStatus int) (logStacktrace bool)
|
type StacktracePred func(httpStatus int) (logStacktrace bool)
|
||||||
|
|
||||||
@@ -44,7 +56,7 @@ type respLogger struct {
|
|||||||
|
|
||||||
// DefaultStacktracePred is the default implementation of StacktracePred.
|
// DefaultStacktracePred is the default implementation of StacktracePred.
|
||||||
func DefaultStacktracePred(status int) bool {
|
func DefaultStacktracePred(status int) bool {
|
||||||
return status != http.StatusOK && status != http.StatusAccepted
|
return status < http.StatusOK || status >= http.StatusBadRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeLogged turns a normal response writer into a logged response writer.
|
// MakeLogged turns a normal response writer into a logged response writer.
|
||||||
@@ -60,6 +72,10 @@ func DefaultStacktracePred(status int) bool {
|
|||||||
//
|
//
|
||||||
// Use LogOf(w).Addf(...) to log something along with the response result.
|
// Use LogOf(w).Addf(...) to log something along with the response result.
|
||||||
func MakeLogged(req *http.Request, w *http.ResponseWriter) *respLogger {
|
func MakeLogged(req *http.Request, w *http.ResponseWriter) *respLogger {
|
||||||
|
if _, ok := (*w).(*respLogger); ok {
|
||||||
|
// Don't double-wrap!
|
||||||
|
panic("multiple MakeLogged calls!")
|
||||||
|
}
|
||||||
rl := &respLogger{
|
rl := &respLogger{
|
||||||
startTime: time.Now(),
|
startTime: time.Now(),
|
||||||
req: req,
|
req: req,
|
138
pkg/kubelet/docker.go
Normal file
138
pkg/kubelet/docker.go
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
/*
|
||||||
|
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 kubelet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
"github.com/fsouza/go-dockerclient"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DockerContainerData is the structured representation of the JSON object returned by Docker inspect
|
||||||
|
type DockerContainerData struct {
|
||||||
|
state struct {
|
||||||
|
Running bool
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DockerInterface is an abstract interface for testability. It abstracts the interface of docker.Client.
|
||||||
|
type DockerInterface interface {
|
||||||
|
ListContainers(options docker.ListContainersOptions) ([]docker.APIContainers, error)
|
||||||
|
InspectContainer(id string) (*docker.Container, error)
|
||||||
|
CreateContainer(docker.CreateContainerOptions) (*docker.Container, error)
|
||||||
|
StartContainer(id string, hostConfig *docker.HostConfig) error
|
||||||
|
StopContainer(id string, timeout uint) error
|
||||||
|
PullImage(opts docker.PullImageOptions, auth docker.AuthConfiguration) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// DockerID is an ID of docker container. It is a type to make it clear when we're working with docker container Ids
|
||||||
|
type DockerID string
|
||||||
|
|
||||||
|
// DockerPuller is an abstract interface for testability. It abstracts image pull operations.
|
||||||
|
type DockerPuller interface {
|
||||||
|
Pull(image string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// dockerPuller is the default implementation of DockerPuller.
|
||||||
|
type dockerPuller struct {
|
||||||
|
client DockerInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDockerPuller creates a new instance of the default implementation of DockerPuller.
|
||||||
|
func NewDockerPuller(client DockerInterface) DockerPuller {
|
||||||
|
return dockerPuller{
|
||||||
|
client: client,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p dockerPuller) Pull(image string) error {
|
||||||
|
image, tag := parseImageName(image)
|
||||||
|
opts := docker.PullImageOptions{
|
||||||
|
Repository: image,
|
||||||
|
Tag: tag,
|
||||||
|
}
|
||||||
|
return p.client.PullImage(opts, docker.AuthConfiguration{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts "-" to "_-_" and "_" to "___" so that we can use "--" to meaningfully separate parts of a docker name.
|
||||||
|
func escapeDash(in string) (out string) {
|
||||||
|
out = strings.Replace(in, "_", "___", -1)
|
||||||
|
out = strings.Replace(out, "-", "_-_", -1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverses the transformation of escapeDash.
|
||||||
|
func unescapeDash(in string) (out string) {
|
||||||
|
out = strings.Replace(in, "_-_", "-", -1)
|
||||||
|
out = strings.Replace(out, "___", "_", -1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const containerNamePrefix = "k8s"
|
||||||
|
|
||||||
|
// Creates a name which can be reversed to identify both manifest id and container name.
|
||||||
|
func buildDockerName(manifest *api.ContainerManifest, container *api.Container) string {
|
||||||
|
// Note, manifest.ID could be blank.
|
||||||
|
return fmt.Sprintf("%s--%s--%s--%08x", containerNamePrefix, escapeDash(container.Name), escapeDash(manifest.ID), rand.Uint32())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upacks a container name, returning the manifest id and container name we would have used to
|
||||||
|
// construct the docker name. If the docker name isn't one we created, we may return empty strings.
|
||||||
|
func parseDockerName(name string) (manifestID, containerName string) {
|
||||||
|
// For some reason docker appears to be appending '/' to names.
|
||||||
|
// If its there, strip it.
|
||||||
|
if name[0] == '/' {
|
||||||
|
name = name[1:]
|
||||||
|
}
|
||||||
|
parts := strings.Split(name, "--")
|
||||||
|
if len(parts) == 0 || parts[0] != containerNamePrefix {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(parts) > 1 {
|
||||||
|
containerName = unescapeDash(parts[1])
|
||||||
|
}
|
||||||
|
if len(parts) > 2 {
|
||||||
|
manifestID = unescapeDash(parts[2])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses image name including an tag and returns image name and tag
|
||||||
|
// TODO: Future Docker versions can parse the tag on daemon side, see:
|
||||||
|
// https://github.com/dotcloud/docker/issues/6876
|
||||||
|
// So this can be deprecated at some point.
|
||||||
|
func parseImageName(image string) (string, string) {
|
||||||
|
tag := ""
|
||||||
|
parts := strings.SplitN(image, "/", 2)
|
||||||
|
repo := ""
|
||||||
|
if len(parts) == 2 {
|
||||||
|
repo = parts[0]
|
||||||
|
image = parts[1]
|
||||||
|
}
|
||||||
|
parts = strings.SplitN(image, ":", 2)
|
||||||
|
if len(parts) == 2 {
|
||||||
|
image = parts[0]
|
||||||
|
tag = parts[1]
|
||||||
|
}
|
||||||
|
if repo != "" {
|
||||||
|
image = fmt.Sprintf("%s/%s", repo, image)
|
||||||
|
}
|
||||||
|
return image, tag
|
||||||
|
}
|
@@ -21,7 +21,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/rand"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -34,6 +33,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/health"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||||
"github.com/coreos/go-etcd/etcd"
|
"github.com/coreos/go-etcd/etcd"
|
||||||
@@ -46,34 +46,14 @@ import (
|
|||||||
|
|
||||||
const defaultChanSize = 1024
|
const defaultChanSize = 1024
|
||||||
|
|
||||||
// DockerContainerData is the structured representation of the JSON object returned by Docker inspect
|
// taken from lmctfy https://github.com/google/lmctfy/blob/master/lmctfy/controllers/cpu_controller.cc
|
||||||
type DockerContainerData struct {
|
const minShares = 2
|
||||||
state struct {
|
const sharesPerCpu = 1024
|
||||||
Running bool
|
const milliCpuToCpu = 1000
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DockerInterface is an abstract interface for testability. It abstracts the interface of docker.Client.
|
|
||||||
type DockerInterface interface {
|
|
||||||
ListContainers(options docker.ListContainersOptions) ([]docker.APIContainers, error)
|
|
||||||
InspectContainer(id string) (*docker.Container, error)
|
|
||||||
CreateContainer(docker.CreateContainerOptions) (*docker.Container, error)
|
|
||||||
StartContainer(id string, hostConfig *docker.HostConfig) error
|
|
||||||
StopContainer(id string, timeout uint) error
|
|
||||||
PullImage(opts docker.PullImageOptions, auth docker.AuthConfiguration) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// DockerID is an ID of docker container. It is a type to make it clear when we're working with docker container Ids
|
|
||||||
type DockerID string
|
|
||||||
|
|
||||||
// DockerPuller is an abstract interface for testability. It abstracts image pull operations.
|
|
||||||
type DockerPuller interface {
|
|
||||||
Pull(image string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// CadvisorInterface is an abstract interface for testability. It abstracts the interface of "github.com/google/cadvisor/client".Client.
|
// CadvisorInterface is an abstract interface for testability. It abstracts the interface of "github.com/google/cadvisor/client".Client.
|
||||||
type CadvisorInterface interface {
|
type CadvisorInterface interface {
|
||||||
ContainerInfo(name string) (*info.ContainerInfo, error)
|
ContainerInfo(name string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error)
|
||||||
MachineInfo() (*info.MachineInfo, error)
|
MachineInfo() (*info.MachineInfo, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +73,7 @@ type Kubelet struct {
|
|||||||
SyncFrequency time.Duration
|
SyncFrequency time.Duration
|
||||||
HTTPCheckFrequency time.Duration
|
HTTPCheckFrequency time.Duration
|
||||||
pullLock sync.Mutex
|
pullLock sync.Mutex
|
||||||
HealthChecker HealthChecker
|
HealthChecker health.HealthChecker
|
||||||
}
|
}
|
||||||
|
|
||||||
type manifestUpdate struct {
|
type manifestUpdate struct {
|
||||||
@@ -119,7 +99,7 @@ func (kl *Kubelet) RunKubelet(dockerEndpoint, configPath, manifestURL, etcdServe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if kl.DockerPuller == nil {
|
if kl.DockerPuller == nil {
|
||||||
kl.DockerPuller = kl.MakeDockerPuller()
|
kl.DockerPuller = NewDockerPuller(kl.DockerClient)
|
||||||
}
|
}
|
||||||
updateChannel := make(chan manifestUpdate)
|
updateChannel := make(chan manifestUpdate)
|
||||||
if configPath != "" {
|
if configPath != "" {
|
||||||
@@ -158,7 +138,7 @@ func (kl *Kubelet) RunKubelet(dockerEndpoint, configPath, manifestURL, etcdServe
|
|||||||
}
|
}
|
||||||
go util.Forever(func() { s.ListenAndServe() }, 0)
|
go util.Forever(func() { s.ListenAndServe() }, 0)
|
||||||
}
|
}
|
||||||
kl.HealthChecker = MakeHealthChecker()
|
kl.HealthChecker = health.MakeHealthChecker()
|
||||||
kl.syncLoop(updateChannel, kl)
|
kl.syncLoop(updateChannel, kl)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,70 +216,6 @@ func (kl *Kubelet) getContainer(ID DockerID) (*docker.APIContainers, error) {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeDockerPuller creates a new instance of the default implementation of DockerPuller.
|
|
||||||
func (kl *Kubelet) MakeDockerPuller() DockerPuller {
|
|
||||||
return dockerPuller{
|
|
||||||
client: kl.DockerClient,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// dockerPuller is the default implementation of DockerPuller.
|
|
||||||
type dockerPuller struct {
|
|
||||||
client DockerInterface
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p dockerPuller) Pull(image string) error {
|
|
||||||
image, tag := parseImageName(image)
|
|
||||||
opts := docker.PullImageOptions{
|
|
||||||
Repository: image,
|
|
||||||
Tag: tag,
|
|
||||||
}
|
|
||||||
return p.client.PullImage(opts, docker.AuthConfiguration{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Converts "-" to "_-_" and "_" to "___" so that we can use "--" to meaningfully separate parts of a docker name.
|
|
||||||
func escapeDash(in string) (out string) {
|
|
||||||
out = strings.Replace(in, "_", "___", -1)
|
|
||||||
out = strings.Replace(out, "-", "_-_", -1)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reverses the transformation of escapeDash.
|
|
||||||
func unescapeDash(in string) (out string) {
|
|
||||||
out = strings.Replace(in, "_-_", "-", -1)
|
|
||||||
out = strings.Replace(out, "___", "_", -1)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const containerNamePrefix = "k8s"
|
|
||||||
|
|
||||||
// Creates a name which can be reversed to identify both manifest id and container name.
|
|
||||||
func buildDockerName(manifest *api.ContainerManifest, container *api.Container) string {
|
|
||||||
// Note, manifest.ID could be blank.
|
|
||||||
return fmt.Sprintf("%s--%s--%s--%08x", containerNamePrefix, escapeDash(container.Name), escapeDash(manifest.ID), rand.Uint32())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upacks a container name, returning the manifest id and container name we would have used to
|
|
||||||
// construct the docker name. If the docker name isn't one we created, we may return empty strings.
|
|
||||||
func parseDockerName(name string) (manifestID, containerName string) {
|
|
||||||
// For some reason docker appears to be appending '/' to names.
|
|
||||||
// If its there, strip it.
|
|
||||||
if name[0] == '/' {
|
|
||||||
name = name[1:]
|
|
||||||
}
|
|
||||||
parts := strings.Split(name, "--")
|
|
||||||
if len(parts) == 0 || parts[0] != containerNamePrefix {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(parts) > 1 {
|
|
||||||
containerName = unescapeDash(parts[1])
|
|
||||||
}
|
|
||||||
if len(parts) > 2 {
|
|
||||||
manifestID = unescapeDash(parts[2])
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeEnvironmentVariables(container *api.Container) []string {
|
func makeEnvironmentVariables(container *api.Container) []string {
|
||||||
var result []string
|
var result []string
|
||||||
for _, value := range container.Env {
|
for _, value := range container.Env {
|
||||||
@@ -358,27 +274,13 @@ func makePortsAndBindings(container *api.Container) (map[docker.Port]struct{}, m
|
|||||||
return exposedPorts, portBindings
|
return exposedPorts, portBindings
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parses image name including an tag and returns image name and tag
|
func milliCpuToShares(milliCpu int) int {
|
||||||
// TODO: Future Docker versions can parse the tag on daemon side, see:
|
// Conceptually (milliCpu / milliCpuToCpu) * sharesPerCpu, but factored to improve rounding.
|
||||||
// https://github.com/dotcloud/docker/issues/6876
|
shares := (milliCpu * sharesPerCpu) / milliCpuToCpu
|
||||||
// So this can be deprecated at some point.
|
if shares < minShares {
|
||||||
func parseImageName(image string) (string, string) {
|
return minShares
|
||||||
tag := ""
|
|
||||||
parts := strings.SplitN(image, "/", 2)
|
|
||||||
repo := ""
|
|
||||||
if len(parts) == 2 {
|
|
||||||
repo = parts[0]
|
|
||||||
image = parts[1]
|
|
||||||
}
|
}
|
||||||
parts = strings.SplitN(image, ":", 2)
|
return shares
|
||||||
if len(parts) == 2 {
|
|
||||||
image = parts[0]
|
|
||||||
tag = parts[1]
|
|
||||||
}
|
|
||||||
if repo != "" {
|
|
||||||
image = fmt.Sprintf("%s/%s", repo, image)
|
|
||||||
}
|
|
||||||
return image, tag
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run a single container from a manifest. Returns the docker container ID
|
// Run a single container from a manifest. Returns the docker container ID
|
||||||
@@ -390,13 +292,15 @@ func (kl *Kubelet) runContainer(manifest *api.ContainerManifest, container *api.
|
|||||||
opts := docker.CreateContainerOptions{
|
opts := docker.CreateContainerOptions{
|
||||||
Name: buildDockerName(manifest, container),
|
Name: buildDockerName(manifest, container),
|
||||||
Config: &docker.Config{
|
Config: &docker.Config{
|
||||||
|
Cmd: container.Command,
|
||||||
|
Env: envVariables,
|
||||||
|
ExposedPorts: exposedPorts,
|
||||||
Hostname: container.Name,
|
Hostname: container.Name,
|
||||||
Image: container.Image,
|
Image: container.Image,
|
||||||
ExposedPorts: exposedPorts,
|
Memory: int64(container.Memory),
|
||||||
Env: envVariables,
|
CpuShares: int64(milliCpuToShares(container.CPU)),
|
||||||
Volumes: volumes,
|
Volumes: volumes,
|
||||||
WorkingDir: container.WorkingDir,
|
WorkingDir: container.WorkingDir,
|
||||||
Cmd: container.Command,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
dockerContainer, err := kl.DockerClient.CreateContainer(opts)
|
dockerContainer, err := kl.DockerClient.CreateContainer(opts)
|
||||||
@@ -735,7 +639,7 @@ func (kl *Kubelet) syncManifest(manifest *api.ContainerManifest, keepChannel cha
|
|||||||
glog.V(1).Infof("health check errored: %v", err)
|
glog.V(1).Infof("health check errored: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if healthy != CheckHealthy {
|
if healthy != health.Healthy {
|
||||||
glog.V(1).Infof("manifest %s container %s is unhealthy.", manifest.ID, container.Name)
|
glog.V(1).Infof("manifest %s container %s is unhealthy.", manifest.ID, container.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.V(1).Infof("Failed to get container info %v, for %s", err, containerID)
|
glog.V(1).Infof("Failed to get container info %v, for %s", err, containerID)
|
||||||
@@ -939,44 +843,29 @@ func (kl *Kubelet) getDockerIDFromPodIDAndContainerName(podID, containerName str
|
|||||||
return "", errors.New("couldn't find container")
|
return "", errors.New("couldn't find container")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getCadvisorContainerInfoRequest(req *info.ContainerInfoRequest) *info.ContainerInfoRequest {
|
||||||
|
ret := &info.ContainerInfoRequest{
|
||||||
|
NumStats: req.NumStats,
|
||||||
|
CpuUsagePercentiles: req.CpuUsagePercentiles,
|
||||||
|
MemoryUsagePercentages: req.MemoryUsagePercentages,
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
// This method takes a container's absolute path and returns the stats for the
|
// This method takes a container's absolute path and returns the stats for the
|
||||||
// container. The container's absolute path refers to its hierarchy in the
|
// container. The container's absolute path refers to its hierarchy in the
|
||||||
// cgroup file system. e.g. The root container, which represents the whole
|
// cgroup file system. e.g. The root container, which represents the whole
|
||||||
// machine, has path "/"; all docker containers have path "/docker/<docker id>"
|
// machine, has path "/"; all docker containers have path "/docker/<docker id>"
|
||||||
func (kl *Kubelet) statsFromContainerPath(containerPath string) (*api.ContainerStats, error) {
|
func (kl *Kubelet) statsFromContainerPath(containerPath string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
|
||||||
info, err := kl.CadvisorClient.ContainerInfo(containerPath)
|
cinfo, err := kl.CadvisorClient.ContainerInfo(containerPath, getCadvisorContainerInfoRequest(req))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// When the stats data for the container is not available yet.
|
return cinfo, nil
|
||||||
if info.StatsPercentiles == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ret := new(api.ContainerStats)
|
|
||||||
ret.MaxMemoryUsage = info.StatsPercentiles.MaxMemoryUsage
|
|
||||||
if len(info.StatsPercentiles.CpuUsagePercentiles) > 0 {
|
|
||||||
percentiles := make([]api.Percentile, len(info.StatsPercentiles.CpuUsagePercentiles))
|
|
||||||
for i, p := range info.StatsPercentiles.CpuUsagePercentiles {
|
|
||||||
percentiles[i].Percentage = p.Percentage
|
|
||||||
percentiles[i].Value = p.Value
|
|
||||||
}
|
|
||||||
ret.CPUUsagePercentiles = percentiles
|
|
||||||
}
|
|
||||||
if len(info.StatsPercentiles.MemoryUsagePercentiles) > 0 {
|
|
||||||
percentiles := make([]api.Percentile, len(info.StatsPercentiles.MemoryUsagePercentiles))
|
|
||||||
for i, p := range info.StatsPercentiles.MemoryUsagePercentiles {
|
|
||||||
percentiles[i].Percentage = p.Percentage
|
|
||||||
percentiles[i].Value = p.Value
|
|
||||||
}
|
|
||||||
ret.MemoryUsagePercentiles = percentiles
|
|
||||||
}
|
|
||||||
return ret, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetContainerStats returns stats (from Cadvisor) for a container.
|
// GetContainerStats returns stats (from Cadvisor) for a container.
|
||||||
func (kl *Kubelet) GetContainerStats(podID, containerName string) (*api.ContainerStats, error) {
|
func (kl *Kubelet) GetContainerInfo(podID, containerName string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
|
||||||
if kl.CadvisorClient == nil {
|
if kl.CadvisorClient == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@@ -984,24 +873,24 @@ func (kl *Kubelet) GetContainerStats(podID, containerName string) (*api.Containe
|
|||||||
if err != nil || len(dockerID) == 0 {
|
if err != nil || len(dockerID) == 0 {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return kl.statsFromContainerPath(fmt.Sprintf("/docker/%s", string(dockerID)))
|
return kl.statsFromContainerPath(fmt.Sprintf("/docker/%s", string(dockerID)), req)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMachineStats returns stats (from Cadvisor) of current machine.
|
// GetMachineStats returns stats (from Cadvisor) of current machine.
|
||||||
func (kl *Kubelet) GetMachineStats() (*api.ContainerStats, error) {
|
func (kl *Kubelet) GetMachineStats(req *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
|
||||||
return kl.statsFromContainerPath("/")
|
return kl.statsFromContainerPath("/", req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (kl *Kubelet) healthy(container api.Container, dockerContainer *docker.APIContainers) (HealthCheckStatus, 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 {
|
||||||
return CheckHealthy, nil
|
return health.Healthy, nil
|
||||||
}
|
}
|
||||||
if time.Now().Unix()-dockerContainer.Created < container.LivenessProbe.InitialDelaySeconds {
|
if time.Now().Unix()-dockerContainer.Created < container.LivenessProbe.InitialDelaySeconds {
|
||||||
return CheckHealthy, nil
|
return health.Healthy, nil
|
||||||
}
|
}
|
||||||
if kl.HealthChecker == nil {
|
if kl.HealthChecker == nil {
|
||||||
return CheckHealthy, nil
|
return health.Healthy, nil
|
||||||
}
|
}
|
||||||
return kl.HealthChecker.HealthCheck(container)
|
return kl.HealthChecker.HealthCheck(container)
|
||||||
}
|
}
|
||||||
|
@@ -20,6 +20,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -27,7 +28,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/httplog"
|
||||||
|
"github.com/google/cadvisor/info"
|
||||||
"gopkg.in/v1/yaml"
|
"gopkg.in/v1/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -41,8 +43,8 @@ type Server struct {
|
|||||||
// kubeletInterface contains all the kubelet methods required by the server.
|
// kubeletInterface contains all the kubelet methods required by the server.
|
||||||
// For testablitiy.
|
// For testablitiy.
|
||||||
type kubeletInterface interface {
|
type kubeletInterface interface {
|
||||||
GetContainerStats(podID, containerName string) (*api.ContainerStats, error)
|
GetContainerInfo(podID, containerName string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error)
|
||||||
GetMachineStats() (*api.ContainerStats, error)
|
GetMachineStats(req *info.ContainerInfoRequest) (*info.ContainerInfo, error)
|
||||||
GetPodInfo(name string) (api.PodInfo, error)
|
GetPodInfo(name string) (api.PodInfo, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,13 +53,14 @@ func (s *Server) error(w http.ResponseWriter, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
defer apiserver.MakeLogged(req, &w).Log()
|
defer httplog.MakeLogged(req, &w).Log()
|
||||||
|
|
||||||
u, err := url.ParseRequestURI(req.RequestURI)
|
u, err := url.ParseRequestURI(req.RequestURI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.error(w, err)
|
s.error(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// TODO: use an http.ServeMux instead of a switch.
|
||||||
switch {
|
switch {
|
||||||
case u.Path == "/container" || u.Path == "/containers":
|
case u.Path == "/container" || u.Path == "/containers":
|
||||||
defer req.Body.Close()
|
defer req.Body.Close()
|
||||||
@@ -113,18 +116,25 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
func (s *Server) serveStats(w http.ResponseWriter, req *http.Request) {
|
func (s *Server) serveStats(w http.ResponseWriter, req *http.Request) {
|
||||||
// /stats/<podid>/<containerName>
|
// /stats/<podid>/<containerName>
|
||||||
components := strings.Split(strings.TrimPrefix(path.Clean(req.URL.Path), "/"), "/")
|
components := strings.Split(strings.TrimPrefix(path.Clean(req.URL.Path), "/"), "/")
|
||||||
var stats *api.ContainerStats
|
var stats *info.ContainerInfo
|
||||||
var err error
|
var err error
|
||||||
|
var query info.ContainerInfoRequest
|
||||||
|
decoder := json.NewDecoder(req.Body)
|
||||||
|
err = decoder.Decode(&query)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
s.error(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
switch len(components) {
|
switch len(components) {
|
||||||
case 1:
|
case 1:
|
||||||
// Machine stats
|
// Machine stats
|
||||||
stats, err = s.Kubelet.GetMachineStats()
|
stats, err = s.Kubelet.GetMachineStats(&query)
|
||||||
case 2:
|
case 2:
|
||||||
// pod stats
|
// pod stats
|
||||||
// TODO(monnand) Implement this
|
// TODO(monnand) Implement this
|
||||||
errors.New("pod level status currently unimplemented")
|
errors.New("pod level status currently unimplemented")
|
||||||
case 3:
|
case 3:
|
||||||
stats, err = s.Kubelet.GetContainerStats(components[1], components[2])
|
stats, err = s.Kubelet.GetContainerInfo(components[1], components[2], &query)
|
||||||
default:
|
default:
|
||||||
http.Error(w, "unknown resource.", http.StatusNotFound)
|
http.Error(w, "unknown resource.", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
|
@@ -29,24 +29,25 @@ import (
|
|||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"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"
|
||||||
|
"github.com/google/cadvisor/info"
|
||||||
)
|
)
|
||||||
|
|
||||||
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) (*api.ContainerStats, error)
|
containerStatsFunc func(podID, containerName string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error)
|
||||||
machineStatsFunc func() (*api.ContainerStats, error)
|
machineStatsFunc func(query *info.ContainerInfoRequest) (*info.ContainerInfo, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fk *fakeKubelet) GetPodInfo(name string) (api.PodInfo, error) {
|
func (fk *fakeKubelet) GetPodInfo(name string) (api.PodInfo, error) {
|
||||||
return fk.infoFunc(name)
|
return fk.infoFunc(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fk *fakeKubelet) GetContainerStats(podID, containerName string) (*api.ContainerStats, error) {
|
func (fk *fakeKubelet) GetContainerInfo(podID, containerName string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
|
||||||
return fk.containerStatsFunc(podID, containerName)
|
return fk.containerStatsFunc(podID, containerName, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fk *fakeKubelet) GetMachineStats() (*api.ContainerStats, error) {
|
func (fk *fakeKubelet) GetMachineStats(req *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
|
||||||
return fk.machineStatsFunc()
|
return fk.machineStatsFunc(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
type serverTestFramework struct {
|
type serverTestFramework struct {
|
||||||
@@ -148,22 +149,24 @@ func TestPodInfo(t *testing.T) {
|
|||||||
|
|
||||||
func TestContainerStats(t *testing.T) {
|
func TestContainerStats(t *testing.T) {
|
||||||
fw := makeServerTest()
|
fw := makeServerTest()
|
||||||
expectedStats := &api.ContainerStats{
|
expectedStats := &info.ContainerInfo{
|
||||||
MaxMemoryUsage: 1024001,
|
StatsPercentiles: &info.ContainerStatsPercentiles{
|
||||||
CPUUsagePercentiles: []api.Percentile{
|
MaxMemoryUsage: 1024001,
|
||||||
{50, 150},
|
CpuUsagePercentiles: []info.Percentile{
|
||||||
{80, 180},
|
{50, 150},
|
||||||
{90, 190},
|
{80, 180},
|
||||||
},
|
{90, 190},
|
||||||
MemoryUsagePercentiles: []api.Percentile{
|
},
|
||||||
{50, 150},
|
MemoryUsagePercentiles: []info.Percentile{
|
||||||
{80, 180},
|
{50, 150},
|
||||||
{90, 190},
|
{80, 180},
|
||||||
|
{90, 190},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
expectedPodID := "somepod"
|
expectedPodID := "somepod"
|
||||||
expectedContainerName := "goodcontainer"
|
expectedContainerName := "goodcontainer"
|
||||||
fw.fakeKubelet.containerStatsFunc = func(podID, containerName string) (*api.ContainerStats, error) {
|
fw.fakeKubelet.containerStatsFunc = 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)
|
||||||
}
|
}
|
||||||
@@ -175,7 +178,7 @@ 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 api.ContainerStats
|
var receivedStats info.ContainerInfo
|
||||||
decoder := json.NewDecoder(resp.Body)
|
decoder := json.NewDecoder(resp.Body)
|
||||||
err = decoder.Decode(&receivedStats)
|
err = decoder.Decode(&receivedStats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -188,20 +191,22 @@ func TestContainerStats(t *testing.T) {
|
|||||||
|
|
||||||
func TestMachineStats(t *testing.T) {
|
func TestMachineStats(t *testing.T) {
|
||||||
fw := makeServerTest()
|
fw := makeServerTest()
|
||||||
expectedStats := &api.ContainerStats{
|
expectedStats := &info.ContainerInfo{
|
||||||
MaxMemoryUsage: 1024001,
|
StatsPercentiles: &info.ContainerStatsPercentiles{
|
||||||
CPUUsagePercentiles: []api.Percentile{
|
MaxMemoryUsage: 1024001,
|
||||||
{50, 150},
|
CpuUsagePercentiles: []info.Percentile{
|
||||||
{80, 180},
|
{50, 150},
|
||||||
{90, 190},
|
{80, 180},
|
||||||
},
|
{90, 190},
|
||||||
MemoryUsagePercentiles: []api.Percentile{
|
},
|
||||||
{50, 150},
|
MemoryUsagePercentiles: []info.Percentile{
|
||||||
{80, 180},
|
{50, 150},
|
||||||
{90, 190},
|
{80, 180},
|
||||||
|
{90, 190},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
fw.fakeKubelet.machineStatsFunc = func() (*api.ContainerStats, error) {
|
fw.fakeKubelet.machineStatsFunc = func(req *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
|
||||||
return expectedStats, nil
|
return expectedStats, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,7 +215,7 @@ 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 api.ContainerStats
|
var receivedStats info.ContainerInfo
|
||||||
decoder := json.NewDecoder(resp.Body)
|
decoder := json.NewDecoder(resp.Body)
|
||||||
err = decoder.Decode(&receivedStats)
|
err = decoder.Decode(&receivedStats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -26,6 +26,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/health"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||||
"github.com/coreos/go-etcd/etcd"
|
"github.com/coreos/go-etcd/etcd"
|
||||||
@@ -324,9 +325,7 @@ func TestGetKubeletStateFromEtcdNotFound(t *testing.T) {
|
|||||||
reader := startReading(channel)
|
reader := startReading(channel)
|
||||||
fakeClient.Data["/registry/hosts/machine/kubelet"] = tools.EtcdResponseWithError{
|
fakeClient.Data["/registry/hosts/machine/kubelet"] = tools.EtcdResponseWithError{
|
||||||
R: &etcd.Response{},
|
R: &etcd.Response{},
|
||||||
E: &etcd.EtcdError{
|
E: tools.EtcdErrorNotFound,
|
||||||
ErrorCode: 100,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
err := kubelet.getKubeletStateFromEtcd("/registry/hosts/machine/kubelet", channel)
|
err := kubelet.getKubeletStateFromEtcd("/registry/hosts/machine/kubelet", channel)
|
||||||
expectNoError(t, err)
|
expectNoError(t, err)
|
||||||
@@ -424,8 +423,8 @@ func TestSyncManifestsDeletes(t *testing.T) {
|
|||||||
|
|
||||||
type FalseHealthChecker struct{}
|
type FalseHealthChecker struct{}
|
||||||
|
|
||||||
func (f *FalseHealthChecker) HealthCheck(container api.Container) (HealthCheckStatus, error) {
|
func (f *FalseHealthChecker) HealthCheck(container api.Container) (health.Status, error) {
|
||||||
return CheckUnhealthy, nil
|
return health.Unhealthy, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSyncManifestsUnhealthy(t *testing.T) {
|
func TestSyncManifestsUnhealthy(t *testing.T) {
|
||||||
@@ -913,8 +912,8 @@ type mockCadvisorClient struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ContainerInfo is a mock implementation of CadvisorInterface.ContainerInfo.
|
// ContainerInfo is a mock implementation of CadvisorInterface.ContainerInfo.
|
||||||
func (c *mockCadvisorClient) ContainerInfo(name string) (*info.ContainerInfo, error) {
|
func (c *mockCadvisorClient) ContainerInfo(name string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
|
||||||
args := c.Called(name)
|
args := c.Called(name, req)
|
||||||
return args.Get(0).(*info.ContainerInfo), args.Error(1)
|
return args.Get(0).(*info.ContainerInfo), args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -926,7 +925,7 @@ func (c *mockCadvisorClient) MachineInfo() (*info.MachineInfo, error) {
|
|||||||
|
|
||||||
func areSamePercentiles(
|
func areSamePercentiles(
|
||||||
cadvisorPercentiles []info.Percentile,
|
cadvisorPercentiles []info.Percentile,
|
||||||
kubePercentiles []api.Percentile,
|
kubePercentiles []info.Percentile,
|
||||||
t *testing.T,
|
t *testing.T,
|
||||||
) {
|
) {
|
||||||
if len(cadvisorPercentiles) != len(kubePercentiles) {
|
if len(cadvisorPercentiles) != len(kubePercentiles) {
|
||||||
@@ -966,7 +965,7 @@ func TestGetContainerStats(t *testing.T) {
|
|||||||
{80, 180},
|
{80, 180},
|
||||||
{90, 190},
|
{90, 190},
|
||||||
},
|
},
|
||||||
CPUUsagePercentiles: []info.Percentile{
|
CpuUsagePercentiles: []info.Percentile{
|
||||||
{51, 101},
|
{51, 101},
|
||||||
{81, 181},
|
{81, 181},
|
||||||
{91, 191},
|
{91, 191},
|
||||||
@@ -975,7 +974,9 @@ func TestGetContainerStats(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mockCadvisor := &mockCadvisorClient{}
|
mockCadvisor := &mockCadvisorClient{}
|
||||||
mockCadvisor.On("ContainerInfo", containerPath).Return(containerInfo, nil)
|
req := &info.ContainerInfoRequest{}
|
||||||
|
cadvisorReq := getCadvisorContainerInfoRequest(req)
|
||||||
|
mockCadvisor.On("ContainerInfo", containerPath, cadvisorReq).Return(containerInfo, nil)
|
||||||
|
|
||||||
kubelet, _, fakeDocker := makeTestKubelet(t)
|
kubelet, _, fakeDocker := makeTestKubelet(t)
|
||||||
kubelet.CadvisorClient = mockCadvisor
|
kubelet.CadvisorClient = mockCadvisor
|
||||||
@@ -988,15 +989,15 @@ func TestGetContainerStats(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
stats, err := kubelet.GetContainerStats("qux", "foo")
|
stats, err := kubelet.GetContainerInfo("qux", "foo", req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpected error: %v", err)
|
t.Errorf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
if stats.MaxMemoryUsage != containerInfo.StatsPercentiles.MaxMemoryUsage {
|
if stats.StatsPercentiles.MaxMemoryUsage != containerInfo.StatsPercentiles.MaxMemoryUsage {
|
||||||
t.Errorf("wrong max memory usage")
|
t.Errorf("wrong max memory usage")
|
||||||
}
|
}
|
||||||
areSamePercentiles(containerInfo.StatsPercentiles.CpuUsagePercentiles, stats.CPUUsagePercentiles, t)
|
areSamePercentiles(containerInfo.StatsPercentiles.CpuUsagePercentiles, stats.StatsPercentiles.CpuUsagePercentiles, t)
|
||||||
areSamePercentiles(containerInfo.StatsPercentiles.MemoryUsagePercentiles, stats.MemoryUsagePercentiles, t)
|
areSamePercentiles(containerInfo.StatsPercentiles.MemoryUsagePercentiles, stats.StatsPercentiles.MemoryUsagePercentiles, t)
|
||||||
mockCadvisor.AssertExpectations(t)
|
mockCadvisor.AssertExpectations(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1020,7 +1021,9 @@ func TestGetMachineStats(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mockCadvisor := &mockCadvisorClient{}
|
mockCadvisor := &mockCadvisorClient{}
|
||||||
mockCadvisor.On("ContainerInfo", containerPath).Return(containerInfo, nil)
|
req := &info.ContainerInfoRequest{}
|
||||||
|
cadvisorReq := getCadvisorContainerInfoRequest(req)
|
||||||
|
mockCadvisor.On("ContainerInfo", containerPath, cadvisorReq).Return(containerInfo, nil)
|
||||||
|
|
||||||
kubelet := Kubelet{
|
kubelet := Kubelet{
|
||||||
DockerClient: &fakeDocker,
|
DockerClient: &fakeDocker,
|
||||||
@@ -1029,15 +1032,15 @@ 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()
|
stats, err := kubelet.GetMachineStats(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpected error: %v", err)
|
t.Errorf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
if stats.MaxMemoryUsage != containerInfo.StatsPercentiles.MaxMemoryUsage {
|
if stats.StatsPercentiles.MaxMemoryUsage != containerInfo.StatsPercentiles.MaxMemoryUsage {
|
||||||
t.Errorf("wrong max memory usage")
|
t.Errorf("wrong max memory usage")
|
||||||
}
|
}
|
||||||
areSamePercentiles(containerInfo.StatsPercentiles.CpuUsagePercentiles, stats.CPUUsagePercentiles, t)
|
areSamePercentiles(containerInfo.StatsPercentiles.CpuUsagePercentiles, stats.StatsPercentiles.CpuUsagePercentiles, t)
|
||||||
areSamePercentiles(containerInfo.StatsPercentiles.MemoryUsagePercentiles, stats.MemoryUsagePercentiles, t)
|
areSamePercentiles(containerInfo.StatsPercentiles.MemoryUsagePercentiles, stats.StatsPercentiles.MemoryUsagePercentiles, t)
|
||||||
mockCadvisor.AssertExpectations(t)
|
mockCadvisor.AssertExpectations(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1052,19 +1055,19 @@ func TestGetContainerStatsWithoutCadvisor(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
stats, _ := kubelet.GetContainerStats("qux", "foo")
|
stats, _ := kubelet.GetContainerInfo("qux", "foo", nil)
|
||||||
// When there's no cAdvisor, the stats should be either nil or empty
|
// When there's no cAdvisor, the stats should be either nil or empty
|
||||||
if stats == nil {
|
if stats == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if stats.MaxMemoryUsage != 0 {
|
if stats.StatsPercentiles.MaxMemoryUsage != 0 {
|
||||||
t.Errorf("MaxMemoryUsage is %v even if there's no cadvisor", stats.MaxMemoryUsage)
|
t.Errorf("MaxMemoryUsage is %v even if there's no cadvisor", stats.StatsPercentiles.MaxMemoryUsage)
|
||||||
}
|
}
|
||||||
if len(stats.CPUUsagePercentiles) > 0 {
|
if len(stats.StatsPercentiles.CpuUsagePercentiles) > 0 {
|
||||||
t.Errorf("Cpu usage percentiles is not empty (%+v) even if there's no cadvisor", stats.CPUUsagePercentiles)
|
t.Errorf("Cpu usage percentiles is not empty (%+v) even if there's no cadvisor", stats.StatsPercentiles.CpuUsagePercentiles)
|
||||||
}
|
}
|
||||||
if len(stats.MemoryUsagePercentiles) > 0 {
|
if len(stats.StatsPercentiles.MemoryUsagePercentiles) > 0 {
|
||||||
t.Errorf("Memory usage percentiles is not empty (%+v) even if there's no cadvisor", stats.MemoryUsagePercentiles)
|
t.Errorf("Memory usage percentiles is not empty (%+v) even if there's no cadvisor", stats.StatsPercentiles.MemoryUsagePercentiles)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1074,8 +1077,10 @@ func TestGetContainerStatsWhenCadvisorFailed(t *testing.T) {
|
|||||||
|
|
||||||
containerInfo := &info.ContainerInfo{}
|
containerInfo := &info.ContainerInfo{}
|
||||||
mockCadvisor := &mockCadvisorClient{}
|
mockCadvisor := &mockCadvisorClient{}
|
||||||
|
req := &info.ContainerInfoRequest{}
|
||||||
|
cadvisorReq := getCadvisorContainerInfoRequest(req)
|
||||||
expectedErr := fmt.Errorf("some error")
|
expectedErr := fmt.Errorf("some error")
|
||||||
mockCadvisor.On("ContainerInfo", containerPath).Return(containerInfo, expectedErr)
|
mockCadvisor.On("ContainerInfo", containerPath, cadvisorReq).Return(containerInfo, expectedErr)
|
||||||
|
|
||||||
kubelet, _, fakeDocker := makeTestKubelet(t)
|
kubelet, _, fakeDocker := makeTestKubelet(t)
|
||||||
kubelet.CadvisorClient = mockCadvisor
|
kubelet.CadvisorClient = mockCadvisor
|
||||||
@@ -1088,7 +1093,7 @@ func TestGetContainerStatsWhenCadvisorFailed(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
stats, err := kubelet.GetContainerStats("qux", "foo")
|
stats, err := kubelet.GetContainerInfo("qux", "foo", req)
|
||||||
if stats != nil {
|
if stats != nil {
|
||||||
t.Errorf("non-nil stats on error")
|
t.Errorf("non-nil stats on error")
|
||||||
}
|
}
|
||||||
@@ -1109,7 +1114,7 @@ func TestGetContainerStatsOnNonExistContainer(t *testing.T) {
|
|||||||
kubelet.CadvisorClient = mockCadvisor
|
kubelet.CadvisorClient = mockCadvisor
|
||||||
fakeDocker.containerList = []docker.APIContainers{}
|
fakeDocker.containerList = []docker.APIContainers{}
|
||||||
|
|
||||||
stats, _ := kubelet.GetContainerStats("qux", "foo")
|
stats, _ := kubelet.GetContainerInfo("qux", "foo", nil)
|
||||||
if stats != nil {
|
if stats != nil {
|
||||||
t.Errorf("non-nil stats on non exist container")
|
t.Errorf("non-nil stats on non exist container")
|
||||||
}
|
}
|
||||||
|
@@ -23,14 +23,15 @@ import (
|
|||||||
|
|
||||||
// Labels allows you to present labels independently from their storage.
|
// Labels allows you to present labels independently from their storage.
|
||||||
type Labels interface {
|
type Labels interface {
|
||||||
|
// Get returns the value for the provided label.
|
||||||
Get(label string) (value string)
|
Get(label string) (value string)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A map of label:value. Implements Labels.
|
// Set is a map of label:value. It implements Labels.
|
||||||
type Set map[string]string
|
type Set map[string]string
|
||||||
|
|
||||||
// All labels listed as a human readable string. Conveniently, exactly the format
|
// String returns all labels listed as a human readable string.
|
||||||
// that ParseSelector takes.
|
// Conveniently, exactly the format that ParseSelector takes.
|
||||||
func (ls Set) String() string {
|
func (ls Set) String() string {
|
||||||
selector := make([]string, 0, len(ls))
|
selector := make([]string, 0, len(ls))
|
||||||
for key, value := range ls {
|
for key, value := range ls {
|
||||||
@@ -41,12 +42,12 @@ func (ls Set) String() string {
|
|||||||
return strings.Join(selector, ",")
|
return strings.Join(selector, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement Labels interface.
|
// Get returns the value in the map for the provided label.
|
||||||
func (ls Set) Get(label string) string {
|
func (ls Set) Get(label string) string {
|
||||||
return ls[label]
|
return ls[label]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convenience function: convert these labels to a selector.
|
// AsSelector converts labels into a selectors.
|
||||||
func (ls Set) AsSelector() Selector {
|
func (ls Set) AsSelector() Selector {
|
||||||
return SelectorFromSet(ls)
|
return SelectorFromSet(ls)
|
||||||
}
|
}
|
||||||
|
@@ -22,12 +22,12 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Represents a selector.
|
// Selector represents a label selector.
|
||||||
type Selector interface {
|
type Selector interface {
|
||||||
// Returns true if this selector matches the given set of labels.
|
// Matches returns true if this selector matches the given set of labels.
|
||||||
Matches(Labels) bool
|
Matches(Labels) bool
|
||||||
|
|
||||||
// Prints a human readable version of this selector.
|
// String returns a human readable string that represents this selector.
|
||||||
String() string
|
String() string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ func try(selectorPiece, op string) (lhs, rhs string, ok bool) {
|
|||||||
return "", "", false
|
return "", "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Given a Set, return a Selector which will match exactly that Set.
|
// SelectorFromSet returns a Selector which will match exactly the given Set.
|
||||||
func SelectorFromSet(ls Set) Selector {
|
func SelectorFromSet(ls Set) Selector {
|
||||||
items := make([]Selector, 0, len(ls))
|
items := make([]Selector, 0, len(ls))
|
||||||
for label, value := range ls {
|
for label, value := range ls {
|
||||||
@@ -99,7 +99,8 @@ func SelectorFromSet(ls Set) Selector {
|
|||||||
return andTerm(items)
|
return andTerm(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Takes a string repsenting a selector and returns an object suitable for matching, or an error.
|
// ParseSelector takes a string repsenting a selector and returns an
|
||||||
|
// object suitable for matching, or an error.
|
||||||
func ParseSelector(selector string) (Selector, error) {
|
func ParseSelector(selector string) (Selector, error) {
|
||||||
parts := strings.Split(selector, ",")
|
parts := strings.Split(selector, ",")
|
||||||
sort.StringSlice(parts).Sort()
|
sort.StringSlice(parts).Sort()
|
||||||
|
@@ -43,7 +43,7 @@ type Master struct {
|
|||||||
storage map[string]apiserver.RESTStorage
|
storage map[string]apiserver.RESTStorage
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a memory (not etcd) backed apiserver.
|
// NewMemoryServer returns a new instance of Master backed with memory (not etcd).
|
||||||
func NewMemoryServer(minions []string, podInfoGetter client.PodInfoGetter, cloud cloudprovider.Interface) *Master {
|
func NewMemoryServer(minions []string, podInfoGetter client.PodInfoGetter, cloud cloudprovider.Interface) *Master {
|
||||||
m := &Master{
|
m := &Master{
|
||||||
podRegistry: registry.MakeMemoryRegistry(),
|
podRegistry: registry.MakeMemoryRegistry(),
|
||||||
@@ -55,7 +55,7 @@ func NewMemoryServer(minions []string, podInfoGetter client.PodInfoGetter, cloud
|
|||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a new apiserver.
|
// New returns a new instance of Master connected to the given etcdServer.
|
||||||
func New(etcdServers, minions []string, podInfoGetter client.PodInfoGetter, cloud cloudprovider.Interface, minionRegexp string) *Master {
|
func New(etcdServers, minions []string, podInfoGetter client.PodInfoGetter, cloud cloudprovider.Interface, minionRegexp string) *Master {
|
||||||
etcdClient := etcd.NewClient(etcdServers)
|
etcdClient := etcd.NewClient(etcdServers)
|
||||||
minionRegistry := minionRegistryMaker(minions, cloud, minionRegexp)
|
minionRegistry := minionRegistryMaker(minions, cloud, minionRegexp)
|
||||||
@@ -84,7 +84,7 @@ func (m *Master) init(cloud cloudprovider.Interface, podInfoGetter client.PodInf
|
|||||||
m.random = rand.New(rand.NewSource(int64(time.Now().Nanosecond())))
|
m.random = rand.New(rand.NewSource(int64(time.Now().Nanosecond())))
|
||||||
podCache := NewPodCache(podInfoGetter, m.podRegistry, time.Second*30)
|
podCache := NewPodCache(podInfoGetter, m.podRegistry, time.Second*30)
|
||||||
go podCache.Loop()
|
go podCache.Loop()
|
||||||
s := scheduler.MakeRandomFitScheduler(m.podRegistry, m.random)
|
s := scheduler.NewRandomFitScheduler(m.podRegistry, m.random)
|
||||||
m.storage = map[string]apiserver.RESTStorage{
|
m.storage = map[string]apiserver.RESTStorage{
|
||||||
"pods": registry.MakePodRegistryStorage(m.podRegistry, podInfoGetter, s, m.minionRegistry, cloud, podCache),
|
"pods": registry.MakePodRegistryStorage(m.podRegistry, podInfoGetter, s, m.minionRegistry, cloud, podCache),
|
||||||
"replicationControllers": registry.NewControllerRegistryStorage(m.controllerRegistry, m.podRegistry),
|
"replicationControllers": registry.NewControllerRegistryStorage(m.controllerRegistry, m.podRegistry),
|
||||||
@@ -94,7 +94,7 @@ func (m *Master) init(cloud cloudprovider.Interface, podInfoGetter client.PodInf
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Runs master. Never returns.
|
// Run begins serving the Kubernetes API. It never returns.
|
||||||
func (m *Master) Run(myAddress, apiPrefix string) error {
|
func (m *Master) Run(myAddress, apiPrefix string) error {
|
||||||
endpoints := registry.MakeEndpointController(m.serviceRegistry, m.podRegistry)
|
endpoints := registry.MakeEndpointController(m.serviceRegistry, m.podRegistry)
|
||||||
go util.Forever(func() { endpoints.SyncServiceEndpoints() }, time.Second*10)
|
go util.Forever(func() { endpoints.SyncServiceEndpoints() }, time.Second*10)
|
||||||
@@ -109,8 +109,9 @@ func (m *Master) Run(myAddress, apiPrefix string) error {
|
|||||||
return s.ListenAndServe()
|
return s.ListenAndServe()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Instead of calling Run, call ConstructHandler to get a handler for your own
|
// ConstructHandler returns an http.Handler which serves the Kubernetes API.
|
||||||
// server. Intended for testing. Only call once.
|
// Instead of calling Run, you can call this function to get a handler for your own server.
|
||||||
|
// It is intended for testing. Only call once.
|
||||||
func (m *Master) ConstructHandler(apiPrefix string) http.Handler {
|
func (m *Master) ConstructHandler(apiPrefix string) http.Handler {
|
||||||
endpoints := registry.MakeEndpointController(m.serviceRegistry, m.podRegistry)
|
endpoints := registry.MakeEndpointController(m.serviceRegistry, m.podRegistry)
|
||||||
go util.Forever(func() { endpoints.SyncServiceEndpoints() }, time.Second*10)
|
go util.Forever(func() { endpoints.SyncServiceEndpoints() }, time.Second*10)
|
||||||
|
@@ -40,6 +40,7 @@ type PodCache struct {
|
|||||||
podLock sync.Mutex
|
podLock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewPodCache returns a new PodCache which watches container information registered in the given PodRegistry.
|
||||||
func NewPodCache(info client.PodInfoGetter, 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,
|
||||||
@@ -49,7 +50,7 @@ func NewPodCache(info client.PodInfoGetter, pods registry.PodRegistry, period ti
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements the PodInfoGetter interface.
|
// GetPodInfo Implements the PodInfoGetter.GetPodInfo.
|
||||||
// The returned value should be treated as read-only.
|
// The returned value should be treated as read-only.
|
||||||
func (p *PodCache) GetPodInfo(host, podID string) (api.PodInfo, error) {
|
func (p *PodCache) GetPodInfo(host, podID string) (api.PodInfo, error) {
|
||||||
p.podLock.Lock()
|
p.podLock.Lock()
|
||||||
@@ -57,9 +58,8 @@ func (p *PodCache) GetPodInfo(host, podID string) (api.PodInfo, error) {
|
|||||||
value, ok := p.podInfo[podID]
|
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 {
|
|
||||||
return value, nil
|
|
||||||
}
|
}
|
||||||
|
return value, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PodCache) updatePodInfo(host, id string) error {
|
func (p *PodCache) updatePodInfo(host, id string) error {
|
||||||
@@ -73,7 +73,7 @@ func (p *PodCache) updatePodInfo(host, id string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update information about all containers. Either called by Loop() below, or one-off.
|
// UpdateAllContainers updates information about all containers. Either called by Loop() below, or one-off.
|
||||||
func (p *PodCache) UpdateAllContainers() {
|
func (p *PodCache) UpdateAllContainers() {
|
||||||
pods, err := p.pods.ListPods(labels.Everything())
|
pods, err := p.pods.ListPods(labels.Everything())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -88,7 +88,8 @@ func (p *PodCache) UpdateAllContainers() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loop runs forever, it is expected to be placed in a go routine.
|
// Loop begins watching updates of container information.
|
||||||
|
// It runs forever, and is expected to be placed in a go routine.
|
||||||
func (p *PodCache) Loop() {
|
func (p *PodCache) Loop() {
|
||||||
util.Forever(func() { p.UpdateAllContainers() }, p.period)
|
util.Forever(func() { p.UpdateAllContainers() }, p.period)
|
||||||
}
|
}
|
||||||
|
@@ -24,40 +24,44 @@ import (
|
|||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Operation is a type of operation of services or endpoints.
|
||||||
type Operation int
|
type Operation int
|
||||||
|
|
||||||
|
// These are the available operation types.
|
||||||
const (
|
const (
|
||||||
SET Operation = iota
|
SET Operation = iota
|
||||||
ADD
|
ADD
|
||||||
REMOVE
|
REMOVE
|
||||||
)
|
)
|
||||||
|
|
||||||
// Defines an operation sent on the channel. You can add or remove single services by
|
// ServiceUpdate describes an operation of services, sent on the channel.
|
||||||
// sending an array of size one and Op == ADD|REMOVE. For setting the state of the system
|
// You can add or remove single services by sending an array of size one and Op == ADD|REMOVE.
|
||||||
// to a given state for this source configuration, set Services as desired and Op to SET,
|
// For setting the state of the system to a given state for this source configuration, set Services as desired and Op to SET,
|
||||||
// which will reset the system state to that specified in this operation for this source
|
// which will reset the system state to that specified in this operation for this source channel.
|
||||||
// channel. To remove all services, set Services to empty array and Op to SET
|
// To remove all services, set Services to empty array and Op to SET
|
||||||
type ServiceUpdate struct {
|
type ServiceUpdate struct {
|
||||||
Services []api.Service
|
Services []api.Service
|
||||||
Op Operation
|
Op Operation
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defines an operation sent on the channel. You can add or remove single endpoints by
|
// EndpointsUpdate describes an operation of endpoints, sent on the channel.
|
||||||
// sending an array of size one and Op == ADD|REMOVE. For setting the state of the system
|
// You can add or remove single endpoints by sending an array of size one and Op == ADD|REMOVE.
|
||||||
// to a given state for this source configuration, set Endpoints as desired and Op to SET,
|
// For setting the state of the system to a given state for this source configuration, set Endpoints as desired and Op to SET,
|
||||||
// which will reset the system state to that specified in this operation for this source
|
// which will reset the system state to that specified in this operation for this source channel.
|
||||||
// channel. To remove all endpoints, set Endpoints to empty array and Op to SET
|
// To remove all endpoints, set Endpoints to empty array and Op to SET
|
||||||
type EndpointsUpdate struct {
|
type EndpointsUpdate struct {
|
||||||
Endpoints []api.Endpoints
|
Endpoints []api.Endpoints
|
||||||
Op Operation
|
Op Operation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ServiceConfigHandler is an abstract interface of objects which receive update notifications for the set of services.
|
||||||
type ServiceConfigHandler interface {
|
type ServiceConfigHandler interface {
|
||||||
// Sent when a configuration has been changed by one of the sources. This is the
|
// OnUpdate gets called when a configuration has been changed by one of the sources.
|
||||||
// union of all the configuration sources.
|
// This is the union of all the configuration sources.
|
||||||
OnUpdate(services []api.Service)
|
OnUpdate(services []api.Service)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EndpointsConfigHandler is an abstract interface of objects which receive update notifications for the set of endpoints.
|
||||||
type EndpointsConfigHandler interface {
|
type EndpointsConfigHandler interface {
|
||||||
// OnUpdate gets called when endpoints configuration is changed for a given
|
// OnUpdate gets called when endpoints configuration is changed for a given
|
||||||
// service on any of the configuration sources. An example is when a new
|
// service on any of the configuration sources. An example is when a new
|
||||||
@@ -65,6 +69,8 @@ type EndpointsConfigHandler interface {
|
|||||||
OnUpdate(endpoints []api.Endpoints)
|
OnUpdate(endpoints []api.Endpoints)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ServiceConfig tracks a set of service configurations and their endpoint configurations.
|
||||||
|
// It accepts "set", "add" and "remove" operations of services and endpoints via channels, and invokes registered handlers on change.
|
||||||
type ServiceConfig struct {
|
type ServiceConfig struct {
|
||||||
// Configuration sources and their lock.
|
// Configuration sources and their lock.
|
||||||
configSourceLock sync.RWMutex
|
configSourceLock sync.RWMutex
|
||||||
@@ -94,6 +100,8 @@ type ServiceConfig struct {
|
|||||||
endpointsNotifyChannel chan string
|
endpointsNotifyChannel chan string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewServiceConfig creates a new ServiceConfig.
|
||||||
|
// It immediately runs the created ServiceConfig.
|
||||||
func NewServiceConfig() *ServiceConfig {
|
func NewServiceConfig() *ServiceConfig {
|
||||||
config := &ServiceConfig{
|
config := &ServiceConfig{
|
||||||
serviceConfigSources: make(map[string]chan ServiceUpdate),
|
serviceConfigSources: make(map[string]chan ServiceUpdate),
|
||||||
@@ -109,22 +117,26 @@ func NewServiceConfig() *ServiceConfig {
|
|||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run begins a loop to accept new service configurations and new endpoint configurations.
|
||||||
|
// It never returns.
|
||||||
func (impl *ServiceConfig) Run() {
|
func (impl *ServiceConfig) Run() {
|
||||||
glog.Infof("Starting the config Run loop")
|
glog.Infof("Starting the config Run loop")
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case source := <-impl.serviceNotifyChannel:
|
case source := <-impl.serviceNotifyChannel:
|
||||||
glog.Infof("Got new service configuration from source %s", source)
|
glog.Infof("Got new service configuration from source %s", source)
|
||||||
impl.NotifyServiceUpdate()
|
impl.notifyServiceUpdate()
|
||||||
case source := <-impl.endpointsNotifyChannel:
|
case source := <-impl.endpointsNotifyChannel:
|
||||||
glog.Infof("Got new endpoint configuration from source %s", source)
|
glog.Infof("Got new endpoint configuration from source %s", source)
|
||||||
impl.NotifyEndpointsUpdate()
|
impl.notifyEndpointsUpdate()
|
||||||
case <-time.After(1 * time.Second):
|
case <-time.After(1 * time.Second):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (impl *ServiceConfig) ServiceChannelListener(source string, listenChannel chan ServiceUpdate) {
|
// serviceChannelListener begins a loop to handle incoming ServiceUpdate notifications from the channel.
|
||||||
|
// It never returns.
|
||||||
|
func (impl *ServiceConfig) serviceChannelListener(source string, listenChannel chan ServiceUpdate) {
|
||||||
// Represents the current services configuration for this channel.
|
// Represents the current services configuration for this channel.
|
||||||
serviceMap := make(map[string]api.Service)
|
serviceMap := make(map[string]api.Service)
|
||||||
for {
|
for {
|
||||||
@@ -160,7 +172,9 @@ func (impl *ServiceConfig) ServiceChannelListener(source string, listenChannel c
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (impl *ServiceConfig) EndpointsChannelListener(source string, listenChannel chan EndpointsUpdate) {
|
// endpointsChannelListener begins a loop to handle incoming EndpointsUpdate notifications from the channel.
|
||||||
|
// It never returns.
|
||||||
|
func (impl *ServiceConfig) endpointsChannelListener(source string, listenChannel chan EndpointsUpdate) {
|
||||||
endpointMap := make(map[string]api.Endpoints)
|
endpointMap := make(map[string]api.Endpoints)
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@@ -214,11 +228,11 @@ func (impl *ServiceConfig) GetServiceConfigurationChannel(source string) chan Se
|
|||||||
}
|
}
|
||||||
newChannel := make(chan ServiceUpdate)
|
newChannel := make(chan ServiceUpdate)
|
||||||
impl.serviceConfigSources[source] = newChannel
|
impl.serviceConfigSources[source] = newChannel
|
||||||
go impl.ServiceChannelListener(source, newChannel)
|
go impl.serviceChannelListener(source, newChannel)
|
||||||
return newChannel
|
return newChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetEndpointConfigurationChannel returns a channel where a configuration source
|
// GetEndpointsConfigurationChannel returns a channel where a configuration source
|
||||||
// can send updates of new endpoint configurations. Multiple calls with the same
|
// can send updates of new endpoint configurations. Multiple calls with the same
|
||||||
// source will return the same channel. This allows change and state based sources
|
// source will return the same channel. This allows change and state based sources
|
||||||
// to use the same channel. Difference source names however will be treated as a
|
// to use the same channel. Difference source names however will be treated as a
|
||||||
@@ -235,11 +249,11 @@ func (impl *ServiceConfig) GetEndpointsConfigurationChannel(source string) chan
|
|||||||
}
|
}
|
||||||
newChannel := make(chan EndpointsUpdate)
|
newChannel := make(chan EndpointsUpdate)
|
||||||
impl.endpointsConfigSources[source] = newChannel
|
impl.endpointsConfigSources[source] = newChannel
|
||||||
go impl.EndpointsChannelListener(source, newChannel)
|
go impl.endpointsChannelListener(source, newChannel)
|
||||||
return newChannel
|
return newChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register ServiceConfigHandler to receive updates of changes to services.
|
// RegisterServiceHandler registers the ServiceConfigHandler to receive updates of changes to services.
|
||||||
func (impl *ServiceConfig) RegisterServiceHandler(handler ServiceConfigHandler) {
|
func (impl *ServiceConfig) RegisterServiceHandler(handler ServiceConfigHandler) {
|
||||||
impl.handlerLock.Lock()
|
impl.handlerLock.Lock()
|
||||||
defer impl.handlerLock.Unlock()
|
defer impl.handlerLock.Unlock()
|
||||||
@@ -255,7 +269,7 @@ func (impl *ServiceConfig) RegisterServiceHandler(handler ServiceConfigHandler)
|
|||||||
panic("Only up to 10 service handlers supported for now")
|
panic("Only up to 10 service handlers supported for now")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register ServiceConfigHandler to receive updates of changes to services.
|
// RegisterEndpointsHandler registers the EndpointsConfigHandler to receive updates of changes to services.
|
||||||
func (impl *ServiceConfig) RegisterEndpointsHandler(handler EndpointsConfigHandler) {
|
func (impl *ServiceConfig) RegisterEndpointsHandler(handler EndpointsConfigHandler) {
|
||||||
impl.handlerLock.Lock()
|
impl.handlerLock.Lock()
|
||||||
defer impl.handlerLock.Unlock()
|
defer impl.handlerLock.Unlock()
|
||||||
@@ -271,8 +285,9 @@ func (impl *ServiceConfig) RegisterEndpointsHandler(handler EndpointsConfigHandl
|
|||||||
panic("Only up to 10 endpoint handlers supported for now")
|
panic("Only up to 10 endpoint handlers supported for now")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (impl *ServiceConfig) NotifyServiceUpdate() {
|
// notifyServiceUpdate calls the registered ServiceConfigHandlers with the current states of services.
|
||||||
services := make([]api.Service, 0)
|
func (impl *ServiceConfig) notifyServiceUpdate() {
|
||||||
|
services := []api.Service{}
|
||||||
impl.configLock.RLock()
|
impl.configLock.RLock()
|
||||||
for _, sourceServices := range impl.serviceConfig {
|
for _, sourceServices := range impl.serviceConfig {
|
||||||
for _, value := range sourceServices {
|
for _, value := range sourceServices {
|
||||||
@@ -291,8 +306,9 @@ func (impl *ServiceConfig) NotifyServiceUpdate() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (impl *ServiceConfig) NotifyEndpointsUpdate() {
|
// notifyEndpointsUpdate calls the registered EndpointsConfigHandlers with the current states of endpoints.
|
||||||
endpoints := make([]api.Endpoints, 0)
|
func (impl *ServiceConfig) notifyEndpointsUpdate() {
|
||||||
|
endpoints := []api.Endpoints{}
|
||||||
impl.configLock.RLock()
|
impl.configLock.RLock()
|
||||||
for _, sourceEndpoints := range impl.endpointConfig {
|
for _, sourceEndpoints := range impl.endpointConfig {
|
||||||
for _, value := range sourceEndpoints {
|
for _, value := range sourceEndpoints {
|
||||||
|
@@ -15,7 +15,7 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Watches etcd and gets the full configuration on preset intervals.
|
// Watches etcd and gets the full configuration on preset intervals.
|
||||||
// Expects the list of exposed services to live under:
|
// It expects the list of exposed services to live under:
|
||||||
// registry/services
|
// registry/services
|
||||||
// which in etcd is exposed like so:
|
// which in etcd is exposed like so:
|
||||||
// http://<etcd server>/v2/keys/registry/services
|
// http://<etcd server>/v2/keys/registry/services
|
||||||
@@ -30,7 +30,7 @@ limitations under the License.
|
|||||||
// '[ { "machine": <host>, "name": <name", "port": <port> },
|
// '[ { "machine": <host>, "name": <name", "port": <port> },
|
||||||
// { "machine": <host2>, "name": <name2", "port": <port2> }
|
// { "machine": <host2>, "name": <name2", "port": <port2> }
|
||||||
// ]',
|
// ]',
|
||||||
//
|
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -44,14 +44,17 @@ import (
|
|||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
)
|
)
|
||||||
|
|
||||||
const RegistryRoot = "registry/services"
|
// registryRoot is the key prefix for service configs in etcd.
|
||||||
|
const registryRoot = "registry/services"
|
||||||
|
|
||||||
|
// ConfigSourceEtcd communicates with a etcd via the client, and sends the change notification of services and endpoints to the specified channels.
|
||||||
type ConfigSourceEtcd struct {
|
type ConfigSourceEtcd struct {
|
||||||
client *etcd.Client
|
client *etcd.Client
|
||||||
serviceChannel chan ServiceUpdate
|
serviceChannel chan ServiceUpdate
|
||||||
endpointsChannel chan EndpointsUpdate
|
endpointsChannel chan EndpointsUpdate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewConfigSourceEtcd creates a new ConfigSourceEtcd and immediately runs the created ConfigSourceEtcd in a goroutine.
|
||||||
func NewConfigSourceEtcd(client *etcd.Client, serviceChannel chan ServiceUpdate, endpointsChannel chan EndpointsUpdate) ConfigSourceEtcd {
|
func NewConfigSourceEtcd(client *etcd.Client, serviceChannel chan ServiceUpdate, endpointsChannel chan EndpointsUpdate) ConfigSourceEtcd {
|
||||||
config := ConfigSourceEtcd{
|
config := ConfigSourceEtcd{
|
||||||
client: client,
|
client: client,
|
||||||
@@ -62,13 +65,14 @@ func NewConfigSourceEtcd(client *etcd.Client, serviceChannel chan ServiceUpdate,
|
|||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run begins watching for new services and their endpoints on etcd.
|
||||||
func (impl ConfigSourceEtcd) Run() {
|
func (impl ConfigSourceEtcd) Run() {
|
||||||
// Initially, just wait for the etcd to come up before doing anything more complicated.
|
// Initially, just wait for the etcd to come up before doing anything more complicated.
|
||||||
var services []api.Service
|
var services []api.Service
|
||||||
var endpoints []api.Endpoints
|
var endpoints []api.Endpoints
|
||||||
var err error
|
var err error
|
||||||
for {
|
for {
|
||||||
services, endpoints, err = impl.GetServices()
|
services, endpoints, err = impl.getServices()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -87,10 +91,10 @@ func (impl ConfigSourceEtcd) Run() {
|
|||||||
|
|
||||||
// Ok, so we got something back from etcd. Let's set up a watch for new services, and
|
// Ok, so we got something back from etcd. Let's set up a watch for new services, and
|
||||||
// their endpoints
|
// their endpoints
|
||||||
go impl.WatchForChanges()
|
go impl.watchForChanges()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
services, endpoints, err = impl.GetServices()
|
services, endpoints, err = impl.getServices()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Errorf("ConfigSourceEtcd: Failed to get services: %v", err)
|
glog.Errorf("ConfigSourceEtcd: Failed to get services: %v", err)
|
||||||
} else {
|
} else {
|
||||||
@@ -107,12 +111,12 @@ func (impl ConfigSourceEtcd) Run() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finds the list of services and their endpoints from etcd.
|
// getServices finds the list of services and their endpoints from etcd.
|
||||||
// This operation is akin to a set a known good at regular intervals.
|
// This operation is akin to a set a known good at regular intervals.
|
||||||
func (impl ConfigSourceEtcd) GetServices() ([]api.Service, []api.Endpoints, error) {
|
func (impl ConfigSourceEtcd) getServices() ([]api.Service, []api.Endpoints, error) {
|
||||||
response, err := impl.client.Get(RegistryRoot+"/specs", true, false)
|
response, err := impl.client.Get(registryRoot+"/specs", true, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Errorf("Failed to get the key %s: %v", RegistryRoot, err)
|
glog.Errorf("Failed to get the key %s: %v", registryRoot, err)
|
||||||
return make([]api.Service, 0), make([]api.Endpoints, 0), err
|
return make([]api.Service, 0), make([]api.Endpoints, 0), err
|
||||||
}
|
}
|
||||||
if response.Node.Dir == true {
|
if response.Node.Dir == true {
|
||||||
@@ -129,7 +133,7 @@ func (impl ConfigSourceEtcd) GetServices() ([]api.Service, []api.Endpoints, erro
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
retServices[i] = svc
|
retServices[i] = svc
|
||||||
endpoints, err := impl.GetEndpoints(svc.ID)
|
endpoints, err := impl.getEndpoints(svc.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Errorf("Couldn't get endpoints for %s : %v skipping", svc.ID, err)
|
glog.Errorf("Couldn't get endpoints for %s : %v skipping", svc.ID, err)
|
||||||
}
|
}
|
||||||
@@ -138,23 +142,23 @@ func (impl ConfigSourceEtcd) GetServices() ([]api.Service, []api.Endpoints, erro
|
|||||||
}
|
}
|
||||||
return retServices, retEndpoints, err
|
return retServices, retEndpoints, err
|
||||||
}
|
}
|
||||||
return nil, nil, fmt.Errorf("did not get the root of the registry %s", RegistryRoot)
|
return nil, nil, fmt.Errorf("did not get the root of the registry %s", registryRoot)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (impl ConfigSourceEtcd) GetEndpoints(service string) (api.Endpoints, error) {
|
// getEndpoints finds the list of endpoints of the service from etcd.
|
||||||
key := fmt.Sprintf(RegistryRoot + "/endpoints/" + service)
|
func (impl ConfigSourceEtcd) getEndpoints(service string) (api.Endpoints, error) {
|
||||||
|
key := fmt.Sprintf(registryRoot + "/endpoints/" + service)
|
||||||
response, err := impl.client.Get(key, true, false)
|
response, err := impl.client.Get(key, true, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Errorf("Failed to get the key: %s %v", key, err)
|
glog.Errorf("Failed to get the key: %s %v", key, err)
|
||||||
return api.Endpoints{}, err
|
return api.Endpoints{}, err
|
||||||
}
|
}
|
||||||
// Parse all the endpoint specifications in this value.
|
// Parse all the endpoint specifications in this value.
|
||||||
return ParseEndpoints(response.Node.Value)
|
return parseEndpoints(response.Node.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EtcdResponseToServiceAndLocalport takes an etcd response and pulls it apart to find
|
// etcdResponseToService takes an etcd response and pulls it apart to find service.
|
||||||
// service
|
func etcdResponseToService(response *etcd.Response) (*api.Service, error) {
|
||||||
func EtcdResponseToService(response *etcd.Response) (*api.Service, error) {
|
|
||||||
if response.Node == nil {
|
if response.Node == nil {
|
||||||
return nil, fmt.Errorf("invalid response from etcd: %#v", response)
|
return nil, fmt.Errorf("invalid response from etcd: %#v", response)
|
||||||
}
|
}
|
||||||
@@ -166,31 +170,31 @@ func EtcdResponseToService(response *etcd.Response) (*api.Service, error) {
|
|||||||
return &svc, err
|
return &svc, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseEndpoints(jsonString string) (api.Endpoints, error) {
|
func parseEndpoints(jsonString string) (api.Endpoints, error) {
|
||||||
var e api.Endpoints
|
var e api.Endpoints
|
||||||
err := json.Unmarshal([]byte(jsonString), &e)
|
err := json.Unmarshal([]byte(jsonString), &e)
|
||||||
return e, err
|
return e, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (impl ConfigSourceEtcd) WatchForChanges() {
|
func (impl ConfigSourceEtcd) watchForChanges() {
|
||||||
glog.Info("Setting up a watch for new services")
|
glog.Info("Setting up a watch for new services")
|
||||||
watchChannel := make(chan *etcd.Response)
|
watchChannel := make(chan *etcd.Response)
|
||||||
go impl.client.Watch("/registry/services/", 0, true, watchChannel, nil)
|
go impl.client.Watch("/registry/services/", 0, true, watchChannel, nil)
|
||||||
for {
|
for {
|
||||||
watchResponse := <-watchChannel
|
watchResponse := <-watchChannel
|
||||||
impl.ProcessChange(watchResponse)
|
impl.processChange(watchResponse)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (impl ConfigSourceEtcd) ProcessChange(response *etcd.Response) {
|
func (impl ConfigSourceEtcd) processChange(response *etcd.Response) {
|
||||||
glog.Infof("Processing a change in service configuration... %s", *response)
|
glog.Infof("Processing a change in service configuration... %s", *response)
|
||||||
|
|
||||||
// If it's a new service being added (signified by a localport being added)
|
// If it's a new service being added (signified by a localport being added)
|
||||||
// then process it as such
|
// then process it as such
|
||||||
if strings.Contains(response.Node.Key, "/endpoints/") {
|
if strings.Contains(response.Node.Key, "/endpoints/") {
|
||||||
impl.ProcessEndpointResponse(response)
|
impl.processEndpointResponse(response)
|
||||||
} else if response.Action == "set" {
|
} else if response.Action == "set" {
|
||||||
service, err := EtcdResponseToService(response)
|
service, err := etcdResponseToService(response)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Errorf("Failed to parse %s Port: %s", response, err)
|
glog.Errorf("Failed to parse %s Port: %s", response, err)
|
||||||
return
|
return
|
||||||
@@ -208,13 +212,12 @@ func (impl ConfigSourceEtcd) ProcessChange(response *etcd.Response) {
|
|||||||
serviceUpdate := ServiceUpdate{Op: REMOVE, Services: []api.Service{{JSONBase: api.JSONBase{ID: parts[3]}}}}
|
serviceUpdate := ServiceUpdate{Op: REMOVE, Services: []api.Service{{JSONBase: api.JSONBase{ID: parts[3]}}}}
|
||||||
impl.serviceChannel <- serviceUpdate
|
impl.serviceChannel <- serviceUpdate
|
||||||
return
|
return
|
||||||
} else {
|
|
||||||
glog.Infof("Unknown service delete: %#v", parts)
|
|
||||||
}
|
}
|
||||||
|
glog.Infof("Unknown service delete: %#v", parts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (impl ConfigSourceEtcd) ProcessEndpointResponse(response *etcd.Response) {
|
func (impl ConfigSourceEtcd) processEndpointResponse(response *etcd.Response) {
|
||||||
glog.Infof("Processing a change in endpoint configuration... %s", *response)
|
glog.Infof("Processing a change in endpoint configuration... %s", *response)
|
||||||
var endpoints api.Endpoints
|
var endpoints api.Endpoints
|
||||||
err := json.Unmarshal([]byte(response.Node.Value), &endpoints)
|
err := json.Unmarshal([]byte(response.Node.Value), &endpoints)
|
||||||
|
@@ -26,15 +26,15 @@ import (
|
|||||||
|
|
||||||
const TomcatContainerEtcdKey = "/registry/services/tomcat/endpoints/tomcat-3bd5af34"
|
const TomcatContainerEtcdKey = "/registry/services/tomcat/endpoints/tomcat-3bd5af34"
|
||||||
const TomcatService = "tomcat"
|
const TomcatService = "tomcat"
|
||||||
const TomcatContainerId = "tomcat-3bd5af34"
|
const TomcatContainerID = "tomcat-3bd5af34"
|
||||||
|
|
||||||
func ValidateJsonParsing(t *testing.T, jsonString string, expectedEndpoints api.Endpoints, expectError bool) {
|
func validateJSONParsing(t *testing.T, jsonString string, expectedEndpoints api.Endpoints, expectError bool) {
|
||||||
endpoints, err := ParseEndpoints(jsonString)
|
endpoints, err := parseEndpoints(jsonString)
|
||||||
if err == nil && expectError {
|
if err == nil && expectError {
|
||||||
t.Errorf("ValidateJsonParsing did not get expected error when parsing %s", jsonString)
|
t.Errorf("validateJSONParsing did not get expected error when parsing %s", jsonString)
|
||||||
}
|
}
|
||||||
if err != nil && !expectError {
|
if err != nil && !expectError {
|
||||||
t.Errorf("ValidateJsonParsing got unexpected error %+v when parsing %s", err, jsonString)
|
t.Errorf("validateJSONParsing got unexpected error %+v when parsing %s", err, jsonString)
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(expectedEndpoints, endpoints) {
|
if !reflect.DeepEqual(expectedEndpoints, endpoints) {
|
||||||
t.Errorf("Didn't get expected endpoints %+v got: %+v", expectedEndpoints, endpoints)
|
t.Errorf("Didn't get expected endpoints %+v got: %+v", expectedEndpoints, endpoints)
|
||||||
@@ -42,7 +42,7 @@ func ValidateJsonParsing(t *testing.T, jsonString string, expectedEndpoints api.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestParseJsonEndpoints(t *testing.T) {
|
func TestParseJsonEndpoints(t *testing.T) {
|
||||||
ValidateJsonParsing(t, "", api.Endpoints{}, true)
|
validateJSONParsing(t, "", api.Endpoints{}, true)
|
||||||
endpoints := api.Endpoints{
|
endpoints := api.Endpoints{
|
||||||
Name: "foo",
|
Name: "foo",
|
||||||
Endpoints: []string{"foo", "bar", "baz"},
|
Endpoints: []string{"foo", "bar", "baz"},
|
||||||
@@ -51,6 +51,6 @@ func TestParseJsonEndpoints(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Unexpected error: %#v", err)
|
t.Errorf("Unexpected error: %#v", err)
|
||||||
}
|
}
|
||||||
ValidateJsonParsing(t, string(data), endpoints, false)
|
validateJSONParsing(t, string(data), endpoints, false)
|
||||||
// ValidateJsonParsing(t, "[{\"port\":8000,\"name\":\"mysql\",\"machine\":\"foo\"},{\"port\":9000,\"name\":\"mysql\",\"machine\":\"bar\"}]", []string{"foo:8000", "bar:9000"}, false)
|
// validateJSONParsing(t, "[{\"port\":8000,\"name\":\"mysql\",\"machine\":\"foo\"},{\"port\":9000,\"name\":\"mysql\",\"machine\":\"bar\"}]", []string{"foo:8000", "bar:9000"}, false)
|
||||||
}
|
}
|
||||||
|
@@ -28,6 +28,7 @@ limitations under the License.
|
|||||||
// }
|
// }
|
||||||
//]
|
//]
|
||||||
//}
|
//}
|
||||||
|
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -41,22 +42,23 @@ import (
|
|||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: kill this struct.
|
// serviceConfig is a deserialized form of the config file format which ConfigSourceFile accepts.
|
||||||
type ServiceJSON struct {
|
type serviceConfig struct {
|
||||||
Name string
|
Services []struct {
|
||||||
Port int
|
Name string `json: "name"`
|
||||||
Endpoints []string
|
Port int `json: "port"`
|
||||||
}
|
Endpoints []string `json: "endpoints"`
|
||||||
type ConfigFile struct {
|
} `json: "service"`
|
||||||
Services []ServiceJSON
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ConfigSourceFile periodically reads service configurations in JSON from a file, and sends the services and endpoints defined in the file to the specified channels.
|
||||||
type ConfigSourceFile struct {
|
type ConfigSourceFile struct {
|
||||||
serviceChannel chan ServiceUpdate
|
serviceChannel chan ServiceUpdate
|
||||||
endpointsChannel chan EndpointsUpdate
|
endpointsChannel chan EndpointsUpdate
|
||||||
filename string
|
filename string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewConfigSourceFile creates a new ConfigSourceFile and let it immediately runs the created ConfigSourceFile in a goroutine.
|
||||||
func NewConfigSourceFile(filename string, serviceChannel chan ServiceUpdate, endpointsChannel chan EndpointsUpdate) ConfigSourceFile {
|
func NewConfigSourceFile(filename string, serviceChannel chan ServiceUpdate, endpointsChannel chan EndpointsUpdate) ConfigSourceFile {
|
||||||
config := ConfigSourceFile{
|
config := ConfigSourceFile{
|
||||||
filename: filename,
|
filename: filename,
|
||||||
@@ -67,6 +69,7 @@ func NewConfigSourceFile(filename string, serviceChannel chan ServiceUpdate, end
|
|||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run begins watching the config file.
|
||||||
func (impl ConfigSourceFile) Run() {
|
func (impl ConfigSourceFile) Run() {
|
||||||
glog.Infof("Watching file %s", impl.filename)
|
glog.Infof("Watching file %s", impl.filename)
|
||||||
var lastData []byte
|
var lastData []byte
|
||||||
@@ -85,7 +88,7 @@ func (impl ConfigSourceFile) Run() {
|
|||||||
}
|
}
|
||||||
lastData = data
|
lastData = data
|
||||||
|
|
||||||
config := new(ConfigFile)
|
config := &serviceConfig{}
|
||||||
if err = json.Unmarshal(data, config); err != nil {
|
if err = json.Unmarshal(data, config); err != nil {
|
||||||
glog.Errorf("Couldn't unmarshal configuration from file : %s %v", data, err)
|
glog.Errorf("Couldn't unmarshal configuration from file : %s %v", data, err)
|
||||||
continue
|
continue
|
||||||
|
@@ -14,5 +14,5 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Package proxy implements the layer-3 network proxy
|
// Package proxy implements the layer-3 network proxy.
|
||||||
package proxy
|
package proxy
|
||||||
|
@@ -22,6 +22,8 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// LoadBalancer represents a load balancer that decides where to route
|
||||||
|
// the incoming services for a particular service to.
|
||||||
type LoadBalancer interface {
|
type LoadBalancer interface {
|
||||||
// LoadBalance takes an incoming request and figures out where to route it to.
|
// LoadBalance takes an incoming request and figures out where to route it to.
|
||||||
// Determination is based on destination service (for example, 'mysql') as
|
// Determination is based on destination service (for example, 'mysql') as
|
||||||
|
@@ -33,11 +33,12 @@ type Proxier struct {
|
|||||||
serviceMap map[string]int
|
serviceMap map[string]int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewProxier returns a newly created and correctly initialized instance of Proxier.
|
||||||
func NewProxier(loadBalancer LoadBalancer) *Proxier {
|
func NewProxier(loadBalancer LoadBalancer) *Proxier {
|
||||||
return &Proxier{loadBalancer: loadBalancer, serviceMap: make(map[string]int)}
|
return &Proxier{loadBalancer: loadBalancer, serviceMap: make(map[string]int)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func CopyBytes(in, out *net.TCPConn) {
|
func copyBytes(in, out *net.TCPConn) {
|
||||||
glog.Infof("Copying from %v <-> %v <-> %v <-> %v",
|
glog.Infof("Copying from %v <-> %v <-> %v <-> %v",
|
||||||
in.RemoteAddr(), in.LocalAddr(), out.LocalAddr(), out.RemoteAddr())
|
in.RemoteAddr(), in.LocalAddr(), out.LocalAddr(), out.RemoteAddr())
|
||||||
_, err := io.Copy(in, out)
|
_, err := io.Copy(in, out)
|
||||||
@@ -49,14 +50,17 @@ func CopyBytes(in, out *net.TCPConn) {
|
|||||||
out.CloseWrite()
|
out.CloseWrite()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a bidirectional byte shuffler. Copies bytes to/from each connection.
|
// proxyConnection creates a bidirectional byte shuffler.
|
||||||
func ProxyConnection(in, out *net.TCPConn) {
|
// It copies bytes to/from each connection.
|
||||||
|
func proxyConnection(in, out *net.TCPConn) {
|
||||||
glog.Infof("Creating proxy between %v <-> %v <-> %v <-> %v",
|
glog.Infof("Creating proxy between %v <-> %v <-> %v <-> %v",
|
||||||
in.RemoteAddr(), in.LocalAddr(), out.LocalAddr(), out.RemoteAddr())
|
in.RemoteAddr(), in.LocalAddr(), out.LocalAddr(), out.RemoteAddr())
|
||||||
go CopyBytes(in, out)
|
go copyBytes(in, out)
|
||||||
go CopyBytes(out, in)
|
go copyBytes(out, in)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AcceptHandler begins accepting incoming connections from listener and proxying the connections to the load-balanced endpoints.
|
||||||
|
// It never returns.
|
||||||
func (proxier Proxier) AcceptHandler(service string, listener net.Listener) {
|
func (proxier Proxier) AcceptHandler(service string, listener net.Listener) {
|
||||||
for {
|
for {
|
||||||
inConn, err := listener.Accept()
|
inConn, err := listener.Accept()
|
||||||
@@ -83,12 +87,12 @@ func (proxier Proxier) AcceptHandler(service string, listener net.Listener) {
|
|||||||
inConn.Close()
|
inConn.Close()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ProxyConnection(inConn.(*net.TCPConn), outConn.(*net.TCPConn))
|
proxyConnection(inConn.(*net.TCPConn), outConn.(*net.TCPConn))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddService starts listening for a new service on a given port.
|
// addService starts listening for a new service on a given port.
|
||||||
func (proxier Proxier) AddService(service string, port int) error {
|
func (proxier Proxier) addService(service string, port int) error {
|
||||||
// Make sure we can start listening on the port before saying all's well.
|
// Make sure we can start listening on the port before saying all's well.
|
||||||
l, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
l, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -117,18 +121,21 @@ func (proxier Proxier) addServiceCommon(service string, l net.Listener) {
|
|||||||
go proxier.AcceptHandler(service, l)
|
go proxier.AcceptHandler(service, l)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnUpdate recieves update notices for the updated services and start listening newly added services.
|
||||||
|
// It implements "github.com/GoogleCloudPlatform/kubernetes/pkg/proxy/config".ServiceConfigHandler.OnUpdate.
|
||||||
func (proxier Proxier) OnUpdate(services []api.Service) {
|
func (proxier Proxier) OnUpdate(services []api.Service) {
|
||||||
glog.Infof("Received update notice: %+v", services)
|
glog.Infof("Received update notice: %+v", services)
|
||||||
for _, service := range services {
|
for _, service := range services {
|
||||||
port, exists := proxier.serviceMap[service.ID]
|
port, exists := proxier.serviceMap[service.ID]
|
||||||
if !exists || port != service.Port {
|
if exists && port == service.Port {
|
||||||
glog.Infof("Adding a new service %s on port %d", service.ID, service.Port)
|
continue
|
||||||
err := proxier.AddService(service.ID, service.Port)
|
|
||||||
if err == nil {
|
|
||||||
proxier.serviceMap[service.ID] = service.Port
|
|
||||||
} else {
|
|
||||||
glog.Infof("Failed to start listening for %s on %d", service.ID, service.Port)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
glog.Infof("Adding a new service %s on port %d", service.ID, service.Port)
|
||||||
|
err := proxier.addService(service.ID, service.Port)
|
||||||
|
if err != nil {
|
||||||
|
glog.Infof("Failed to start listening for %s on %d", service.ID, service.Port)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
proxier.serviceMap[service.ID] = service.Port
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -29,22 +29,25 @@ import (
|
|||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// LoadBalancerRR is a round-robin load balancer. It implements LoadBalancer.
|
||||||
type LoadBalancerRR struct {
|
type LoadBalancerRR struct {
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
endpointsMap map[string][]string
|
endpointsMap map[string][]string
|
||||||
rrIndex map[string]int
|
rrIndex map[string]int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewLoadBalancerRR returns a newly created and correctly initialized instance of LoadBalancerRR.
|
||||||
func NewLoadBalancerRR() *LoadBalancerRR {
|
func NewLoadBalancerRR() *LoadBalancerRR {
|
||||||
return &LoadBalancerRR{endpointsMap: make(map[string][]string), rrIndex: make(map[string]int)}
|
return &LoadBalancerRR{endpointsMap: make(map[string][]string), rrIndex: make(map[string]int)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadBalance selects an endpoint of the service by round-robin algorithm.
|
||||||
func (impl LoadBalancerRR) LoadBalance(service string, srcAddr net.Addr) (string, error) {
|
func (impl LoadBalancerRR) LoadBalance(service string, srcAddr net.Addr) (string, error) {
|
||||||
impl.lock.RLock()
|
impl.lock.RLock()
|
||||||
endpoints, exists := impl.endpointsMap[service]
|
endpoints, exists := impl.endpointsMap[service]
|
||||||
index := impl.rrIndex[service]
|
index := impl.rrIndex[service]
|
||||||
impl.lock.RUnlock()
|
impl.lock.RUnlock()
|
||||||
if exists == false {
|
if !exists {
|
||||||
return "", errors.New("no service entry for:" + service)
|
return "", errors.New("no service entry for:" + service)
|
||||||
}
|
}
|
||||||
if len(endpoints) == 0 {
|
if len(endpoints) == 0 {
|
||||||
@@ -55,7 +58,7 @@ func (impl LoadBalancerRR) LoadBalance(service string, srcAddr net.Addr) (string
|
|||||||
return endpoint, nil
|
return endpoint, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (impl LoadBalancerRR) IsValid(spec string) bool {
|
func (impl LoadBalancerRR) isValid(spec string) bool {
|
||||||
_, port, err := net.SplitHostPort(spec)
|
_, port, err := net.SplitHostPort(spec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
@@ -67,16 +70,19 @@ func (impl LoadBalancerRR) IsValid(spec string) bool {
|
|||||||
return value > 0
|
return value > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (impl LoadBalancerRR) FilterValidEndpoints(endpoints []string) []string {
|
func (impl LoadBalancerRR) filterValidEndpoints(endpoints []string) []string {
|
||||||
var result []string
|
var result []string
|
||||||
for _, spec := range endpoints {
|
for _, spec := range endpoints {
|
||||||
if impl.IsValid(spec) {
|
if impl.isValid(spec) {
|
||||||
result = append(result, spec)
|
result = append(result, spec)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnUpdate updates the registered endpoints with the new
|
||||||
|
// endpoint information, removes the registered endpoints
|
||||||
|
// no longer present in the provided endpoints.
|
||||||
func (impl LoadBalancerRR) OnUpdate(endpoints []api.Endpoints) {
|
func (impl LoadBalancerRR) OnUpdate(endpoints []api.Endpoints) {
|
||||||
tmp := make(map[string]bool)
|
tmp := make(map[string]bool)
|
||||||
impl.lock.Lock()
|
impl.lock.Lock()
|
||||||
@@ -84,7 +90,7 @@ func (impl LoadBalancerRR) OnUpdate(endpoints []api.Endpoints) {
|
|||||||
// First update / add all new endpoints for services.
|
// First update / add all new endpoints for services.
|
||||||
for _, value := range endpoints {
|
for _, value := range endpoints {
|
||||||
existingEndpoints, exists := impl.endpointsMap[value.Name]
|
existingEndpoints, exists := impl.endpointsMap[value.Name]
|
||||||
validEndpoints := impl.FilterValidEndpoints(value.Endpoints)
|
validEndpoints := impl.filterValidEndpoints(value.Endpoints)
|
||||||
if !exists || !reflect.DeepEqual(existingEndpoints, validEndpoints) {
|
if !exists || !reflect.DeepEqual(existingEndpoints, validEndpoints) {
|
||||||
glog.Infof("LoadBalancerRR: Setting endpoints for %s to %+v", value.Name, value.Endpoints)
|
glog.Infof("LoadBalancerRR: Setting endpoints for %s to %+v", value.Name, value.Endpoints)
|
||||||
impl.endpointsMap[value.Name] = validEndpoints
|
impl.endpointsMap[value.Name] = validEndpoints
|
||||||
|
@@ -24,16 +24,16 @@ import (
|
|||||||
|
|
||||||
func TestLoadBalanceValidateWorks(t *testing.T) {
|
func TestLoadBalanceValidateWorks(t *testing.T) {
|
||||||
loadBalancer := NewLoadBalancerRR()
|
loadBalancer := NewLoadBalancerRR()
|
||||||
if loadBalancer.IsValid("") {
|
if loadBalancer.isValid("") {
|
||||||
t.Errorf("Didn't fail for empty string")
|
t.Errorf("Didn't fail for empty string")
|
||||||
}
|
}
|
||||||
if loadBalancer.IsValid("foobar") {
|
if loadBalancer.isValid("foobar") {
|
||||||
t.Errorf("Didn't fail with no port")
|
t.Errorf("Didn't fail with no port")
|
||||||
}
|
}
|
||||||
if loadBalancer.IsValid("foobar:-1") {
|
if loadBalancer.isValid("foobar:-1") {
|
||||||
t.Errorf("Didn't fail with a negative port")
|
t.Errorf("Didn't fail with a negative port")
|
||||||
}
|
}
|
||||||
if !loadBalancer.IsValid("foobar:8080") {
|
if !loadBalancer.isValid("foobar:8080") {
|
||||||
t.Errorf("Failed a valid config.")
|
t.Errorf("Failed a valid config.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,7 +41,7 @@ func TestLoadBalanceValidateWorks(t *testing.T) {
|
|||||||
func TestLoadBalanceFilterWorks(t *testing.T) {
|
func TestLoadBalanceFilterWorks(t *testing.T) {
|
||||||
loadBalancer := NewLoadBalancerRR()
|
loadBalancer := NewLoadBalancerRR()
|
||||||
endpoints := []string{"foobar:1", "foobar:2", "foobar:-1", "foobar:3", "foobar:-2"}
|
endpoints := []string{"foobar:1", "foobar:2", "foobar:-1", "foobar:3", "foobar:-2"}
|
||||||
filtered := loadBalancer.FilterValidEndpoints(endpoints)
|
filtered := loadBalancer.filterValidEndpoints(endpoints)
|
||||||
|
|
||||||
if len(filtered) != 3 {
|
if len(filtered) != 3 {
|
||||||
t.Errorf("Failed to filter to the correct size")
|
t.Errorf("Failed to filter to the correct size")
|
||||||
@@ -59,7 +59,7 @@ func TestLoadBalanceFilterWorks(t *testing.T) {
|
|||||||
|
|
||||||
func TestLoadBalanceFailsWithNoEndpoints(t *testing.T) {
|
func TestLoadBalanceFailsWithNoEndpoints(t *testing.T) {
|
||||||
loadBalancer := NewLoadBalancerRR()
|
loadBalancer := NewLoadBalancerRR()
|
||||||
endpoints := make([]api.Endpoints, 0)
|
var endpoints []api.Endpoints
|
||||||
loadBalancer.OnUpdate(endpoints)
|
loadBalancer.OnUpdate(endpoints)
|
||||||
endpoint, err := loadBalancer.LoadBalance("foo", nil)
|
endpoint, err := loadBalancer.LoadBalance("foo", nil)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@@ -53,9 +53,7 @@ func TestEtcdGetPodNotFound(t *testing.T) {
|
|||||||
R: &etcd.Response{
|
R: &etcd.Response{
|
||||||
Node: nil,
|
Node: nil,
|
||||||
},
|
},
|
||||||
E: &etcd.EtcdError{
|
E: tools.EtcdErrorNotFound,
|
||||||
ErrorCode: 100,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
registry := MakeTestEtcdRegistry(fakeClient, []string{"machine"})
|
registry := MakeTestEtcdRegistry(fakeClient, []string{"machine"})
|
||||||
_, err := registry.GetPod("foo")
|
_, err := registry.GetPod("foo")
|
||||||
@@ -70,7 +68,7 @@ func TestEtcdCreatePod(t *testing.T) {
|
|||||||
R: &etcd.Response{
|
R: &etcd.Response{
|
||||||
Node: nil,
|
Node: nil,
|
||||||
},
|
},
|
||||||
E: &etcd.EtcdError{ErrorCode: 100},
|
E: tools.EtcdErrorNotFound,
|
||||||
}
|
}
|
||||||
fakeClient.Set("/registry/hosts/machine/kubelet", util.MakeJSONString([]api.ContainerManifest{}), 0)
|
fakeClient.Set("/registry/hosts/machine/kubelet", util.MakeJSONString([]api.ContainerManifest{}), 0)
|
||||||
registry := MakeTestEtcdRegistry(fakeClient, []string{"machine"})
|
registry := MakeTestEtcdRegistry(fakeClient, []string{"machine"})
|
||||||
@@ -133,13 +131,13 @@ func TestEtcdCreatePodWithContainersError(t *testing.T) {
|
|||||||
R: &etcd.Response{
|
R: &etcd.Response{
|
||||||
Node: nil,
|
Node: nil,
|
||||||
},
|
},
|
||||||
E: &etcd.EtcdError{ErrorCode: 100},
|
E: tools.EtcdErrorNotFound,
|
||||||
}
|
}
|
||||||
fakeClient.Data["/registry/hosts/machine/kubelet"] = tools.EtcdResponseWithError{
|
fakeClient.Data["/registry/hosts/machine/kubelet"] = tools.EtcdResponseWithError{
|
||||||
R: &etcd.Response{
|
R: &etcd.Response{
|
||||||
Node: nil,
|
Node: nil,
|
||||||
},
|
},
|
||||||
E: &etcd.EtcdError{ErrorCode: 200},
|
E: tools.EtcdErrorValueRequired,
|
||||||
}
|
}
|
||||||
registry := MakeTestEtcdRegistry(fakeClient, []string{"machine"})
|
registry := MakeTestEtcdRegistry(fakeClient, []string{"machine"})
|
||||||
err := registry.CreatePod("machine", api.Pod{
|
err := registry.CreatePod("machine", api.Pod{
|
||||||
@@ -154,7 +152,7 @@ func TestEtcdCreatePodWithContainersError(t *testing.T) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("Unexpected non-error")
|
t.Error("Unexpected non-error")
|
||||||
}
|
}
|
||||||
if err != nil && err.(*etcd.EtcdError).ErrorCode != 100 {
|
if !tools.IsEtcdNotFound(err) {
|
||||||
t.Errorf("Unexpected error: %#v", err)
|
t.Errorf("Unexpected error: %#v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -165,13 +163,13 @@ func TestEtcdCreatePodWithContainersNotFound(t *testing.T) {
|
|||||||
R: &etcd.Response{
|
R: &etcd.Response{
|
||||||
Node: nil,
|
Node: nil,
|
||||||
},
|
},
|
||||||
E: &etcd.EtcdError{ErrorCode: 100},
|
E: tools.EtcdErrorNotFound,
|
||||||
}
|
}
|
||||||
fakeClient.Data["/registry/hosts/machine/kubelet"] = tools.EtcdResponseWithError{
|
fakeClient.Data["/registry/hosts/machine/kubelet"] = tools.EtcdResponseWithError{
|
||||||
R: &etcd.Response{
|
R: &etcd.Response{
|
||||||
Node: nil,
|
Node: nil,
|
||||||
},
|
},
|
||||||
E: &etcd.EtcdError{ErrorCode: 100},
|
E: tools.EtcdErrorNotFound,
|
||||||
}
|
}
|
||||||
registry := MakeTestEtcdRegistry(fakeClient, []string{"machine"})
|
registry := MakeTestEtcdRegistry(fakeClient, []string{"machine"})
|
||||||
err := registry.CreatePod("machine", api.Pod{
|
err := registry.CreatePod("machine", api.Pod{
|
||||||
@@ -213,7 +211,7 @@ func TestEtcdCreatePodWithExistingContainers(t *testing.T) {
|
|||||||
R: &etcd.Response{
|
R: &etcd.Response{
|
||||||
Node: nil,
|
Node: nil,
|
||||||
},
|
},
|
||||||
E: &etcd.EtcdError{ErrorCode: 100},
|
E: tools.EtcdErrorNotFound,
|
||||||
}
|
}
|
||||||
fakeClient.Set("/registry/hosts/machine/kubelet", util.MakeJSONString([]api.ContainerManifest{
|
fakeClient.Set("/registry/hosts/machine/kubelet", util.MakeJSONString([]api.ContainerManifest{
|
||||||
{
|
{
|
||||||
@@ -329,7 +327,7 @@ func TestEtcdListPodsNotFound(t *testing.T) {
|
|||||||
key := "/registry/hosts/machine/pods"
|
key := "/registry/hosts/machine/pods"
|
||||||
fakeClient.Data[key] = tools.EtcdResponseWithError{
|
fakeClient.Data[key] = tools.EtcdResponseWithError{
|
||||||
R: &etcd.Response{},
|
R: &etcd.Response{},
|
||||||
E: &etcd.EtcdError{ErrorCode: 100},
|
E: tools.EtcdErrorNotFound,
|
||||||
}
|
}
|
||||||
registry := MakeTestEtcdRegistry(fakeClient, []string{"machine"})
|
registry := MakeTestEtcdRegistry(fakeClient, []string{"machine"})
|
||||||
pods, err := registry.ListPods(labels.Everything())
|
pods, err := registry.ListPods(labels.Everything())
|
||||||
@@ -374,7 +372,7 @@ func TestEtcdListControllersNotFound(t *testing.T) {
|
|||||||
key := "/registry/controllers"
|
key := "/registry/controllers"
|
||||||
fakeClient.Data[key] = tools.EtcdResponseWithError{
|
fakeClient.Data[key] = tools.EtcdResponseWithError{
|
||||||
R: &etcd.Response{},
|
R: &etcd.Response{},
|
||||||
E: &etcd.EtcdError{ErrorCode: 100},
|
E: tools.EtcdErrorNotFound,
|
||||||
}
|
}
|
||||||
registry := MakeTestEtcdRegistry(fakeClient, []string{"machine"})
|
registry := MakeTestEtcdRegistry(fakeClient, []string{"machine"})
|
||||||
controllers, err := registry.ListControllers()
|
controllers, err := registry.ListControllers()
|
||||||
@@ -389,7 +387,7 @@ func TestEtcdListServicesNotFound(t *testing.T) {
|
|||||||
key := "/registry/services/specs"
|
key := "/registry/services/specs"
|
||||||
fakeClient.Data[key] = tools.EtcdResponseWithError{
|
fakeClient.Data[key] = tools.EtcdResponseWithError{
|
||||||
R: &etcd.Response{},
|
R: &etcd.Response{},
|
||||||
E: &etcd.EtcdError{ErrorCode: 100},
|
E: tools.EtcdErrorNotFound,
|
||||||
}
|
}
|
||||||
registry := MakeTestEtcdRegistry(fakeClient, []string{"machine"})
|
registry := MakeTestEtcdRegistry(fakeClient, []string{"machine"})
|
||||||
services, err := registry.ListServices()
|
services, err := registry.ListServices()
|
||||||
@@ -442,9 +440,7 @@ func TestEtcdGetControllerNotFound(t *testing.T) {
|
|||||||
R: &etcd.Response{
|
R: &etcd.Response{
|
||||||
Node: nil,
|
Node: nil,
|
||||||
},
|
},
|
||||||
E: &etcd.EtcdError{
|
E: tools.EtcdErrorNotFound,
|
||||||
ErrorCode: 100,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
registry := MakeTestEtcdRegistry(fakeClient, []string{"machine"})
|
registry := MakeTestEtcdRegistry(fakeClient, []string{"machine"})
|
||||||
ctrl, err := registry.GetController("foo")
|
ctrl, err := registry.GetController("foo")
|
||||||
@@ -538,7 +534,7 @@ func TestEtcdCreateService(t *testing.T) {
|
|||||||
R: &etcd.Response{
|
R: &etcd.Response{
|
||||||
Node: nil,
|
Node: nil,
|
||||||
},
|
},
|
||||||
E: &etcd.EtcdError{ErrorCode: 100},
|
E: tools.EtcdErrorNotFound,
|
||||||
}
|
}
|
||||||
registry := MakeTestEtcdRegistry(fakeClient, []string{"machine"})
|
registry := MakeTestEtcdRegistry(fakeClient, []string{"machine"})
|
||||||
err := registry.CreateService(api.Service{
|
err := registry.CreateService(api.Service{
|
||||||
@@ -572,9 +568,7 @@ func TestEtcdGetServiceNotFound(t *testing.T) {
|
|||||||
R: &etcd.Response{
|
R: &etcd.Response{
|
||||||
Node: nil,
|
Node: nil,
|
||||||
},
|
},
|
||||||
E: &etcd.EtcdError{
|
E: tools.EtcdErrorNotFound,
|
||||||
ErrorCode: 100,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
registry := MakeTestEtcdRegistry(fakeClient, []string{"machine"})
|
registry := MakeTestEtcdRegistry(fakeClient, []string{"machine"})
|
||||||
_, err := registry.GetService("foo")
|
_, err := registry.GetService("foo")
|
||||||
|
86
pkg/registry/healthy_minion_registry.go
Normal file
86
pkg/registry/healthy_minion_registry.go
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
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 registry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/health"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HealthyMinionRegistry struct {
|
||||||
|
delegate MinionRegistry
|
||||||
|
client health.HTTPGetInterface
|
||||||
|
port int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHealthyMinionRegistry(delegate MinionRegistry, client *http.Client) MinionRegistry {
|
||||||
|
return &HealthyMinionRegistry{
|
||||||
|
delegate: delegate,
|
||||||
|
client: client,
|
||||||
|
port: 10250,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HealthyMinionRegistry) makeMinionURL(minion string) string {
|
||||||
|
return fmt.Sprintf("http://%s:%d/healthz", minion, h.port)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HealthyMinionRegistry) List() (currentMinions []string, err error) {
|
||||||
|
var result []string
|
||||||
|
list, err := h.delegate.List()
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
for _, minion := range list {
|
||||||
|
status, err := health.Check(h.makeMinionURL(minion), h.client)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
if status == health.Healthy {
|
||||||
|
result = append(result, minion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HealthyMinionRegistry) Insert(minion string) error {
|
||||||
|
return h.delegate.Insert(minion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HealthyMinionRegistry) Delete(minion string) error {
|
||||||
|
return h.delegate.Delete(minion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HealthyMinionRegistry) Contains(minion string) (bool, error) {
|
||||||
|
contains, err := h.delegate.Contains(minion)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if !contains {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
status, err := health.Check(h.makeMinionURL(minion), h.client)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if status == health.Unhealthy {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
96
pkg/registry/healthy_minion_registry_test.go
Normal file
96
pkg/registry/healthy_minion_registry_test.go
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
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 registry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type alwaysYes struct{}
|
||||||
|
|
||||||
|
func (alwaysYes) Get(url string) (*http.Response, error) {
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBasicDelegation(t *testing.T) {
|
||||||
|
mockMinionRegistry := MockMinionRegistry{
|
||||||
|
minions: []string{"m1", "m2", "m3"},
|
||||||
|
}
|
||||||
|
healthy := HealthyMinionRegistry{
|
||||||
|
delegate: &mockMinionRegistry,
|
||||||
|
client: alwaysYes{},
|
||||||
|
}
|
||||||
|
|
||||||
|
list, err := healthy.List()
|
||||||
|
expectNoError(t, err)
|
||||||
|
if !reflect.DeepEqual(list, mockMinionRegistry.minions) {
|
||||||
|
t.Errorf("Expected %v, Got %v", mockMinionRegistry.minions, list)
|
||||||
|
}
|
||||||
|
err = healthy.Insert("foo")
|
||||||
|
expectNoError(t, err)
|
||||||
|
|
||||||
|
ok, err := healthy.Contains("m1")
|
||||||
|
expectNoError(t, err)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("Unexpected absence of 'm1'")
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, err = healthy.Contains("m5")
|
||||||
|
expectNoError(t, err)
|
||||||
|
if ok {
|
||||||
|
t.Errorf("Unexpected presence of 'm5'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type notMinion struct {
|
||||||
|
minion string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *notMinion) Get(url string) (*http.Response, error) {
|
||||||
|
if url != "http://"+n.minion+":10250/healthz" {
|
||||||
|
return &http.Response{StatusCode: http.StatusOK}, nil
|
||||||
|
} else {
|
||||||
|
return &http.Response{StatusCode: http.StatusInternalServerError}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFiltering(t *testing.T) {
|
||||||
|
mockMinionRegistry := MockMinionRegistry{
|
||||||
|
minions: []string{"m1", "m2", "m3"},
|
||||||
|
}
|
||||||
|
healthy := HealthyMinionRegistry{
|
||||||
|
delegate: &mockMinionRegistry,
|
||||||
|
client: ¬Minion{minion: "m1"},
|
||||||
|
port: 10250,
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := []string{"m2", "m3"}
|
||||||
|
list, err := healthy.List()
|
||||||
|
expectNoError(t, err)
|
||||||
|
if !reflect.DeepEqual(list, expected) {
|
||||||
|
t.Errorf("Expected %v, Got %v", expected, list)
|
||||||
|
}
|
||||||
|
ok, err := healthy.Contains("m1")
|
||||||
|
expectNoError(t, err)
|
||||||
|
if ok {
|
||||||
|
t.Errorf("Unexpected presence of 'm1'")
|
||||||
|
}
|
||||||
|
}
|
@@ -75,3 +75,53 @@ func (registry *MockPodRegistry) DeletePod(podId string) error {
|
|||||||
defer registry.Unlock()
|
defer registry.Unlock()
|
||||||
return registry.err
|
return registry.err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MockMinionRegistry struct {
|
||||||
|
err error
|
||||||
|
minion string
|
||||||
|
minions []string
|
||||||
|
sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeMockMinionRegistry(minions []string) *MockMinionRegistry {
|
||||||
|
return &MockMinionRegistry{
|
||||||
|
minions: minions,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (registry *MockMinionRegistry) List() ([]string, error) {
|
||||||
|
registry.Lock()
|
||||||
|
defer registry.Unlock()
|
||||||
|
return registry.minions, registry.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (registry *MockMinionRegistry) Insert(minion string) error {
|
||||||
|
registry.Lock()
|
||||||
|
defer registry.Unlock()
|
||||||
|
registry.minion = minion
|
||||||
|
return registry.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (registry *MockMinionRegistry) Contains(minion string) (bool, error) {
|
||||||
|
registry.Lock()
|
||||||
|
defer registry.Unlock()
|
||||||
|
for _, name := range registry.minions {
|
||||||
|
if name == minion {
|
||||||
|
return true, registry.err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, registry.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (registry *MockMinionRegistry) Delete(minion string) error {
|
||||||
|
registry.Lock()
|
||||||
|
defer registry.Unlock()
|
||||||
|
var newList []string
|
||||||
|
for _, name := range registry.minions {
|
||||||
|
if name != minion {
|
||||||
|
newList = append(newList, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
registry.minions = newList
|
||||||
|
return registry.err
|
||||||
|
}
|
||||||
|
@@ -32,7 +32,7 @@ type RandomFitScheduler struct {
|
|||||||
randomLock sync.Mutex
|
randomLock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakeRandomFitScheduler(podLister PodLister, random *rand.Rand) Scheduler {
|
func NewRandomFitScheduler(podLister PodLister, random *rand.Rand) Scheduler {
|
||||||
return &RandomFitScheduler{
|
return &RandomFitScheduler{
|
||||||
podLister: podLister,
|
podLister: podLister,
|
||||||
random: random,
|
random: random,
|
||||||
|
@@ -28,7 +28,7 @@ func TestRandomFitSchedulerNothingScheduled(t *testing.T) {
|
|||||||
r := rand.New(rand.NewSource(0))
|
r := rand.New(rand.NewSource(0))
|
||||||
st := schedulerTester{
|
st := schedulerTester{
|
||||||
t: t,
|
t: t,
|
||||||
scheduler: MakeRandomFitScheduler(&fakeRegistry, r),
|
scheduler: NewRandomFitScheduler(&fakeRegistry, r),
|
||||||
minionLister: FakeMinionLister{"m1", "m2", "m3"},
|
minionLister: FakeMinionLister{"m1", "m2", "m3"},
|
||||||
}
|
}
|
||||||
st.expectSchedule(api.Pod{}, "m3")
|
st.expectSchedule(api.Pod{}, "m3")
|
||||||
@@ -41,7 +41,7 @@ func TestRandomFitSchedulerFirstScheduled(t *testing.T) {
|
|||||||
r := rand.New(rand.NewSource(0))
|
r := rand.New(rand.NewSource(0))
|
||||||
st := schedulerTester{
|
st := schedulerTester{
|
||||||
t: t,
|
t: t,
|
||||||
scheduler: MakeRandomFitScheduler(fakeRegistry, r),
|
scheduler: NewRandomFitScheduler(fakeRegistry, r),
|
||||||
minionLister: FakeMinionLister{"m1", "m2", "m3"},
|
minionLister: FakeMinionLister{"m1", "m2", "m3"},
|
||||||
}
|
}
|
||||||
st.expectSchedule(makePod("", 8080), "m3")
|
st.expectSchedule(makePod("", 8080), "m3")
|
||||||
@@ -56,7 +56,7 @@ func TestRandomFitSchedulerFirstScheduledComplicated(t *testing.T) {
|
|||||||
r := rand.New(rand.NewSource(0))
|
r := rand.New(rand.NewSource(0))
|
||||||
st := schedulerTester{
|
st := schedulerTester{
|
||||||
t: t,
|
t: t,
|
||||||
scheduler: MakeRandomFitScheduler(fakeRegistry, r),
|
scheduler: NewRandomFitScheduler(fakeRegistry, r),
|
||||||
minionLister: FakeMinionLister{"m1", "m2", "m3"},
|
minionLister: FakeMinionLister{"m1", "m2", "m3"},
|
||||||
}
|
}
|
||||||
st.expectSchedule(makePod("", 8080, 8081), "m3")
|
st.expectSchedule(makePod("", 8080, 8081), "m3")
|
||||||
@@ -71,7 +71,7 @@ func TestRandomFitSchedulerFirstScheduledImpossible(t *testing.T) {
|
|||||||
r := rand.New(rand.NewSource(0))
|
r := rand.New(rand.NewSource(0))
|
||||||
st := schedulerTester{
|
st := schedulerTester{
|
||||||
t: t,
|
t: t,
|
||||||
scheduler: MakeRandomFitScheduler(fakeRegistry, r),
|
scheduler: NewRandomFitScheduler(fakeRegistry, r),
|
||||||
minionLister: FakeMinionLister{"m1", "m2", "m3"},
|
minionLister: FakeMinionLister{"m1", "m2", "m3"},
|
||||||
}
|
}
|
||||||
st.expectFailure(makePod("", 8080, 8081))
|
st.expectFailure(makePod("", 8080, 8081))
|
||||||
|
@@ -25,6 +25,16 @@ import (
|
|||||||
"github.com/coreos/go-etcd/etcd"
|
"github.com/coreos/go-etcd/etcd"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
EtcdErrorCodeNotFound = 100
|
||||||
|
EtcdErrorCodeValueRequired = 200
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
EtcdErrorNotFound = &etcd.EtcdError{ErrorCode: EtcdErrorCodeNotFound}
|
||||||
|
EtcdErrorValueRequired = &etcd.EtcdError{ErrorCode: EtcdErrorCodeValueRequired}
|
||||||
|
)
|
||||||
|
|
||||||
// EtcdClient is an injectable interface for testing.
|
// EtcdClient is an injectable interface for testing.
|
||||||
type EtcdClient interface {
|
type EtcdClient interface {
|
||||||
AddChild(key, data string, ttl uint64) (*etcd.Response, error)
|
AddChild(key, data string, ttl uint64) (*etcd.Response, error)
|
||||||
|
@@ -36,7 +36,7 @@ func TestIsNotFoundErr(t *testing.T) {
|
|||||||
t.Errorf("Expected %#v to return %v, but it did not", err, isNotFound)
|
t.Errorf("Expected %#v to return %v, but it did not", err, isNotFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try(&etcd.EtcdError{ErrorCode: 100}, true)
|
try(EtcdErrorNotFound, true)
|
||||||
try(&etcd.EtcdError{ErrorCode: 101}, false)
|
try(&etcd.EtcdError{ErrorCode: 101}, false)
|
||||||
try(nil, false)
|
try(nil, false)
|
||||||
try(fmt.Errorf("some other kind of error"), false)
|
try(fmt.Errorf("some other kind of error"), false)
|
||||||
|
@@ -74,7 +74,7 @@ func (f *FakeEtcdClient) Get(key string, sort, recursive bool) (*etcd.Response,
|
|||||||
result := f.Data[key]
|
result := f.Data[key]
|
||||||
if result.R == nil {
|
if result.R == nil {
|
||||||
f.t.Errorf("Unexpected get for %s", key)
|
f.t.Errorf("Unexpected get for %s", key)
|
||||||
return &etcd.Response{}, &etcd.EtcdError{ErrorCode: 100} // Key not found
|
return &etcd.Response{}, EtcdErrorNotFound
|
||||||
}
|
}
|
||||||
f.t.Logf("returning %v: %v %#v", key, result.R, result.E)
|
f.t.Logf("returning %v: %v %#v", key, result.R, result.E)
|
||||||
return result.R, result.E
|
return result.R, result.E
|
||||||
@@ -105,7 +105,7 @@ func (f *FakeEtcdClient) Delete(key string, recursive bool) (*etcd.Response, err
|
|||||||
R: &etcd.Response{
|
R: &etcd.Response{
|
||||||
Node: nil,
|
Node: nil,
|
||||||
},
|
},
|
||||||
E: &etcd.EtcdError{ErrorCode: 100},
|
E: EtcdErrorNotFound,
|
||||||
}
|
}
|
||||||
|
|
||||||
f.DeletedKeys = append(f.DeletedKeys, key)
|
f.DeletedKeys = append(f.DeletedKeys, key)
|
||||||
|
3
third_party/deps.sh
vendored
3
third_party/deps.sh
vendored
@@ -5,6 +5,8 @@ TOP_PACKAGES="
|
|||||||
code.google.com/p/goauth2/compute/serviceaccount
|
code.google.com/p/goauth2/compute/serviceaccount
|
||||||
code.google.com/p/goauth2/oauth
|
code.google.com/p/goauth2/oauth
|
||||||
code.google.com/p/google-api-go-client/compute/v1
|
code.google.com/p/google-api-go-client/compute/v1
|
||||||
|
github.com/google/cadvisor/info
|
||||||
|
github.com/google/cadvisor/client
|
||||||
"
|
"
|
||||||
|
|
||||||
DEP_PACKAGES="
|
DEP_PACKAGES="
|
||||||
@@ -13,7 +15,6 @@ DEP_PACKAGES="
|
|||||||
code.google.com/p/google-api-go-client/googleapi
|
code.google.com/p/google-api-go-client/googleapi
|
||||||
github.com/coreos/go-log/log
|
github.com/coreos/go-log/log
|
||||||
github.com/coreos/go-systemd/journal
|
github.com/coreos/go-systemd/journal
|
||||||
github.com/google/cadvisor/info
|
|
||||||
"
|
"
|
||||||
|
|
||||||
PACKAGES="$TOP_PACKAGES $DEP_PACKAGES"
|
PACKAGES="$TOP_PACKAGES $DEP_PACKAGES"
|
||||||
|
1
third_party/src/github.com/google/cadvisor/.gitignore
vendored
Normal file
1
third_party/src/github.com/google/cadvisor/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.swp
|
@@ -1,13 +1,12 @@
|
|||||||
language: go
|
language: go
|
||||||
go:
|
go:
|
||||||
- 1.2
|
- 1.3
|
||||||
before_script:
|
before_script:
|
||||||
- go get github.com/stretchr/testify/mock
|
- go get github.com/stretchr/testify/mock
|
||||||
- go get github.com/kr/pretty
|
- go get github.com/kr/pretty
|
||||||
|
- wget http://s3.amazonaws.com/influxdb/influxdb_latest_amd64.deb
|
||||||
|
- sudo dpkg -i influxdb_latest_amd64.deb
|
||||||
|
- sudo service influxdb start
|
||||||
script:
|
script:
|
||||||
- go test -v -race github.com/google/cadvisor/container
|
- go test -v -race github.com/google/cadvisor/...
|
||||||
- go test -v github.com/google/cadvisor/info
|
|
||||||
- go test -v github.com/google/cadvisor/client
|
|
||||||
- go test -v github.com/google/cadvisor/sampling
|
|
||||||
- go test -v github.com/google/cadvisor/storage/memory
|
|
||||||
- go build github.com/google/cadvisor
|
- go build github.com/google/cadvisor
|
||||||
|
20
third_party/src/github.com/google/cadvisor/CHANGELOG.md
vendored
Normal file
20
third_party/src/github.com/google/cadvisor/CHANGELOG.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 0.1.2 (2014-07-10)
|
||||||
|
- Added Storage Driver concept (flag: storage_driver), default is the in-memory driver
|
||||||
|
- Implemented InfluxDB storage driver
|
||||||
|
- Support in REST API for specifying number of stats to return
|
||||||
|
- Allow running without lmctfy (flag: allow_lmctfy)
|
||||||
|
- Bugfixes
|
||||||
|
|
||||||
|
## 0.1.0 (2014-06-14)
|
||||||
|
- Support for container aliases
|
||||||
|
- Sampling historical usage and exporting that in the REST API
|
||||||
|
- Bugfixes for UI
|
||||||
|
|
||||||
|
## 0.0.0 (2014-06-10)
|
||||||
|
- Initial version of cAdvisor
|
||||||
|
- Web UI with auto-updating stats
|
||||||
|
- v1.0 REST API with container and machine information
|
||||||
|
- Support for Docker containers
|
||||||
|
- Support for lmctfy containers
|
@@ -6,6 +6,10 @@
|
|||||||
|
|
||||||
# Please keep the list sorted by first name.
|
# Please keep the list sorted by first name.
|
||||||
|
|
||||||
|
Jason Swindle <jason@swindle.me>
|
||||||
|
Johan Euphrosine <proppy@google.com>
|
||||||
Kamil Yurtsever <kyurtsever@google.com>
|
Kamil Yurtsever <kyurtsever@google.com>
|
||||||
Nan Deng <dengnan@google.com>
|
Nan Deng <dengnan@google.com>
|
||||||
|
Rohit Jnagal <jnagal@google.com>
|
||||||
Victor Marmol <vmarmol@google.com>
|
Victor Marmol <vmarmol@google.com>
|
||||||
|
Zohaib Maya <zohaib@google.com>
|
||||||
|
32
third_party/src/github.com/google/cadvisor/advice/interference/detector.go
vendored
Normal file
32
third_party/src/github.com/google/cadvisor/advice/interference/detector.go
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// 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 inference
|
||||||
|
|
||||||
|
import "github.com/google/cadvisor/info"
|
||||||
|
|
||||||
|
// InterferenceDectector detects if there's a container which
|
||||||
|
// interferences with a set of containers. The detector tracks
|
||||||
|
// a set of containers and find the victims and antagonist.
|
||||||
|
type InterferenceDetector interface {
|
||||||
|
// Tracks the behavior of the container.
|
||||||
|
AddContainer(ref info.ContainerReference)
|
||||||
|
|
||||||
|
// Returns a list of possible interferences. The upper layer may take action
|
||||||
|
// based on the interference.
|
||||||
|
Detect() ([]*info.Interference, error)
|
||||||
|
|
||||||
|
// The name of the detector.
|
||||||
|
Name() string
|
||||||
|
}
|
@@ -19,12 +19,13 @@ package api
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/cadvisor/info"
|
||||||
"github.com/google/cadvisor/manager"
|
"github.com/google/cadvisor/manager"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -34,9 +35,11 @@ const (
|
|||||||
MachineApi = "machine"
|
MachineApi = "machine"
|
||||||
)
|
)
|
||||||
|
|
||||||
func HandleRequest(m manager.Manager, w http.ResponseWriter, u *url.URL) error {
|
func HandleRequest(m manager.Manager, w http.ResponseWriter, r *http.Request) error {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
|
u := r.URL
|
||||||
|
|
||||||
// Get API request type.
|
// Get API request type.
|
||||||
requestType := u.Path[len(ApiResource):]
|
requestType := u.Path[len(ApiResource):]
|
||||||
i := strings.Index(requestType, "/")
|
i := strings.Index(requestType, "/")
|
||||||
@@ -46,7 +49,8 @@ func HandleRequest(m manager.Manager, w http.ResponseWriter, u *url.URL) error {
|
|||||||
requestType = requestType[:i]
|
requestType = requestType[:i]
|
||||||
}
|
}
|
||||||
|
|
||||||
if requestType == MachineApi {
|
switch {
|
||||||
|
case requestType == MachineApi:
|
||||||
log.Printf("Api - Machine")
|
log.Printf("Api - Machine")
|
||||||
|
|
||||||
// Get the MachineInfo
|
// Get the MachineInfo
|
||||||
@@ -60,14 +64,20 @@ func HandleRequest(m manager.Manager, w http.ResponseWriter, u *url.URL) error {
|
|||||||
fmt.Fprintf(w, "Failed to marshall MachineInfo with error: %s", err)
|
fmt.Fprintf(w, "Failed to marshall MachineInfo with error: %s", err)
|
||||||
}
|
}
|
||||||
w.Write(out)
|
w.Write(out)
|
||||||
} else if requestType == ContainersApi {
|
case requestType == ContainersApi:
|
||||||
// The container name is the path after the requestType
|
// The container name is the path after the requestType
|
||||||
containerName := requestArgs
|
containerName := requestArgs
|
||||||
|
|
||||||
log.Printf("Api - Container(%s)", containerName)
|
log.Printf("Api - Container(%s)", containerName)
|
||||||
|
|
||||||
|
var query info.ContainerInfoRequest
|
||||||
|
decoder := json.NewDecoder(r.Body)
|
||||||
|
err := decoder.Decode(&query)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return fmt.Errorf("unable to decode the json value: ", err)
|
||||||
|
}
|
||||||
// Get the container.
|
// Get the container.
|
||||||
cont, err := m.GetContainerInfo(containerName)
|
cont, err := m.GetContainerInfo(containerName, &query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(w, "Failed to get container \"%s\" with error: %s", containerName, err)
|
fmt.Fprintf(w, "Failed to get container \"%s\" with error: %s", containerName, err)
|
||||||
return err
|
return err
|
||||||
@@ -79,7 +89,7 @@ func HandleRequest(m manager.Manager, w http.ResponseWriter, u *url.URL) error {
|
|||||||
fmt.Fprintf(w, "Failed to marshall container %q with error: %s", containerName, err)
|
fmt.Fprintf(w, "Failed to marshall container %q with error: %s", containerName, err)
|
||||||
}
|
}
|
||||||
w.Write(out)
|
w.Write(out)
|
||||||
} else {
|
default:
|
||||||
return fmt.Errorf("unknown API request type %q", requestType)
|
return fmt.Errorf("unknown API request type %q", requestType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -27,32 +27,46 @@ import (
|
|||||||
"github.com/google/cadvisor/manager"
|
"github.com/google/cadvisor/manager"
|
||||||
"github.com/google/cadvisor/pages"
|
"github.com/google/cadvisor/pages"
|
||||||
"github.com/google/cadvisor/pages/static"
|
"github.com/google/cadvisor/pages/static"
|
||||||
"github.com/google/cadvisor/storage/memory"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var argPort = flag.Int("port", 8080, "port to listen")
|
var argPort = flag.Int("port", 8080, "port to listen")
|
||||||
var argSampleSize = flag.Int("samples", 1024, "number of samples we want to keep")
|
var argAllowLmctfy = flag.Bool("allow_lmctfy", true, "whether to allow lmctfy as a container handler")
|
||||||
var argHistoryDuration = flag.Int("history_duration", 60, "number of seconds of container history to keep")
|
|
||||||
|
var argDbDriver = flag.String("storage_driver", "memory", "storage driver to use. Options are: memory (default) and influxdb")
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
storage := memory.New(*argSampleSize, *argHistoryDuration)
|
storageDriver, err := NewStorageDriver(*argDbDriver)
|
||||||
// TODO(monnand): Add stats writer for manager
|
if err != nil {
|
||||||
containerManager, err := manager.New(storage)
|
log.Fatalf("Failed to connect to database: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
containerManager, err := manager.New(storageDriver)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to create a Container Manager: %s", err)
|
log.Fatalf("Failed to create a Container Manager: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := lmctfy.Register("/"); err != nil {
|
// Register lmctfy for the root if allowed and available.
|
||||||
log.Printf("lmctfy registration failed: %v.", err)
|
registeredRoot := false
|
||||||
log.Print("Running in docker only mode.")
|
if *argAllowLmctfy {
|
||||||
if err := docker.Register(containerManager, "/"); err != nil {
|
if err := lmctfy.Register("/"); err != nil {
|
||||||
log.Printf("Docker registration failed: %v.", err)
|
log.Printf("lmctfy registration failed: %v.", err)
|
||||||
log.Fatalf("Unable to continue without docker or lmctfy.")
|
log.Print("Running in docker only mode.")
|
||||||
|
} else {
|
||||||
|
registeredRoot = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Register Docker for root if we were unable to register lmctfy.
|
||||||
|
if !registeredRoot {
|
||||||
|
if err := docker.Register(containerManager, "/"); err != nil {
|
||||||
|
log.Printf("Docker registration failed: %v.", err)
|
||||||
|
log.Fatalf("Unable to continue without root handler.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register Docker for all Docker containers.
|
||||||
if err := docker.Register(containerManager, "/docker"); err != nil {
|
if err := docker.Register(containerManager, "/docker"); err != nil {
|
||||||
// Ignore this error because we should work with lmctfy only
|
// Ignore this error because we should work with lmctfy only
|
||||||
log.Printf("Docker registration failed: %v.", err)
|
log.Printf("Docker registration failed: %v.", err)
|
||||||
@@ -69,7 +83,7 @@ func main() {
|
|||||||
|
|
||||||
// Handler for the API.
|
// Handler for the API.
|
||||||
http.HandleFunc(api.ApiResource, func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc(api.ApiResource, func(w http.ResponseWriter, r *http.Request) {
|
||||||
err := api.HandleRequest(containerManager, w, r.URL)
|
err := api.HandleRequest(containerManager, w, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(w, "%s", err)
|
fmt.Fprintf(w, "%s", err)
|
||||||
}
|
}
|
||||||
|
@@ -15,6 +15,7 @@
|
|||||||
package cadvisor
|
package cadvisor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@@ -49,7 +50,7 @@ func (self *Client) machineInfoUrl() string {
|
|||||||
func (self *Client) MachineInfo() (minfo *info.MachineInfo, err error) {
|
func (self *Client) MachineInfo() (minfo *info.MachineInfo, err error) {
|
||||||
u := self.machineInfoUrl()
|
u := self.machineInfoUrl()
|
||||||
ret := new(info.MachineInfo)
|
ret := new(info.MachineInfo)
|
||||||
err = self.httpGetJsonData(ret, u, "machine info")
|
err = self.httpGetJsonData(ret, nil, u, "machine info")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -64,8 +65,19 @@ func (self *Client) containerInfoUrl(name string) string {
|
|||||||
return strings.Join([]string{self.baseUrl, "containers", name}, "/")
|
return strings.Join([]string{self.baseUrl, "containers", name}, "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Client) httpGetJsonData(data interface{}, url, infoName string) error {
|
func (self *Client) httpGetJsonData(data, postData interface{}, url, infoName string) error {
|
||||||
resp, err := http.Get(url)
|
var resp *http.Response
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if postData != nil {
|
||||||
|
data, err := json.Marshal(postData)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to marshal data: %v", err)
|
||||||
|
}
|
||||||
|
resp, err = http.Post(url, "application/json", bytes.NewBuffer(data))
|
||||||
|
} else {
|
||||||
|
resp, err = http.Get(url)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("unable to get %v: %v", infoName, err)
|
err = fmt.Errorf("unable to get %v: %v", infoName, err)
|
||||||
return err
|
return err
|
||||||
@@ -84,10 +96,12 @@ func (self *Client) httpGetJsonData(data interface{}, url, infoName string) erro
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Client) ContainerInfo(name string) (cinfo *info.ContainerInfo, err error) {
|
func (self *Client) ContainerInfo(
|
||||||
|
name string,
|
||||||
|
query *info.ContainerInfoRequest) (cinfo *info.ContainerInfo, err error) {
|
||||||
u := self.containerInfoUrl(name)
|
u := self.containerInfoUrl(name)
|
||||||
ret := new(info.ContainerInfo)
|
ret := new(info.ContainerInfo)
|
||||||
err = self.httpGetJsonData(ret, u, fmt.Sprintf("container info for %v", name))
|
err = self.httpGetJsonData(ret, query, u, fmt.Sprintf("container info for %v", name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@@ -21,33 +21,42 @@ import (
|
|||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/google/cadvisor/info"
|
"github.com/google/cadvisor/info"
|
||||||
|
itest "github.com/google/cadvisor/info/test"
|
||||||
|
"github.com/kr/pretty"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testGetJsonData(
|
func testGetJsonData(
|
||||||
strRep string,
|
expected interface{},
|
||||||
emptyData interface{},
|
|
||||||
f func() (interface{}, error),
|
f func() (interface{}, error),
|
||||||
) error {
|
) error {
|
||||||
err := json.Unmarshal([]byte(strRep), emptyData)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid json input: %v", err)
|
|
||||||
}
|
|
||||||
reply, err := f()
|
reply, err := f()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to retrieve data: %v", err)
|
return fmt.Errorf("unable to retrieve data: %v", err)
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(reply, emptyData) {
|
if !reflect.DeepEqual(reply, expected) {
|
||||||
return fmt.Errorf("retrieved wrong data: %+v != %+v", reply, emptyData)
|
return pretty.Errorf("retrieved wrong data: %# v != %# v", reply, expected)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func cadvisorTestClient(path, reply string) (*Client, *httptest.Server, error) {
|
func cadvisorTestClient(path string, expectedPostObj, expectedPostObjEmpty, replyObj interface{}, t *testing.T) (*Client, *httptest.Server, error) {
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path == path {
|
if r.URL.Path == path {
|
||||||
fmt.Fprint(w, reply)
|
if expectedPostObj != nil {
|
||||||
|
decoder := json.NewDecoder(r.Body)
|
||||||
|
err := decoder.Decode(expectedPostObjEmpty)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Recieved invalid object: %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(expectedPostObj, expectedPostObjEmpty) {
|
||||||
|
t.Errorf("Recieved unexpected object: %+v", expectedPostObjEmpty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
encoder := json.NewEncoder(w)
|
||||||
|
encoder.Encode(replyObj)
|
||||||
} else if r.URL.Path == "/api/v1.0/machine" {
|
} else if r.URL.Path == "/api/v1.0/machine" {
|
||||||
fmt.Fprint(w, `{"num_cores":8,"memory_capacity":31625871360}`)
|
fmt.Fprint(w, `{"num_cores":8,"memory_capacity":31625871360}`)
|
||||||
} else {
|
} else {
|
||||||
@@ -64,693 +73,69 @@ func cadvisorTestClient(path, reply string) (*Client, *httptest.Server, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGetMachineinfo(t *testing.T) {
|
func TestGetMachineinfo(t *testing.T) {
|
||||||
respStr := `{"num_cores":8,"memory_capacity":31625871360}`
|
minfo := &info.MachineInfo{
|
||||||
client, server, err := cadvisorTestClient("/api/v1.0/machine", respStr)
|
NumCores: 8,
|
||||||
|
MemoryCapacity: 31625871360,
|
||||||
|
}
|
||||||
|
client, server, err := cadvisorTestClient("/api/v1.0/machine", nil, nil, minfo, t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to get a client %v", err)
|
t.Fatalf("unable to get a client %v", err)
|
||||||
}
|
}
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
err = testGetJsonData(respStr, &info.MachineInfo{}, func() (interface{}, error) {
|
returned, err := client.MachineInfo()
|
||||||
return client.MachineInfo()
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
if !reflect.DeepEqual(returned, minfo) {
|
||||||
|
t.Fatalf("received unexpected machine info")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetContainerInfo(t *testing.T) {
|
func TestGetContainerInfo(t *testing.T) {
|
||||||
respStr := `
|
query := &info.ContainerInfoRequest{
|
||||||
{
|
NumStats: 3,
|
||||||
"name": "%v",
|
NumSamples: 2,
|
||||||
"spec": {
|
CpuUsagePercentiles: []int{10, 50, 90},
|
||||||
"cpu": {
|
MemoryUsagePercentages: []int{10, 80, 90},
|
||||||
"limit": 18446744073709551000,
|
}
|
||||||
"max_limit": 0,
|
|
||||||
"mask": {
|
|
||||||
"data": [
|
|
||||||
18446744073709551000
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"limit": 18446744073709551000,
|
|
||||||
"swap_limit": 18446744073709551000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"stats": [
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:26.434981825Z",
|
|
||||||
"cpu": {
|
|
||||||
"usage": {
|
|
||||||
"total": 56896502,
|
|
||||||
"per_cpu": [
|
|
||||||
20479682,
|
|
||||||
13579420,
|
|
||||||
6025040,
|
|
||||||
2255123,
|
|
||||||
3635661,
|
|
||||||
2489368,
|
|
||||||
5158288,
|
|
||||||
3273920
|
|
||||||
],
|
|
||||||
"user": 10000000,
|
|
||||||
"system": 30000000
|
|
||||||
},
|
|
||||||
"load": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616,
|
|
||||||
"container_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
},
|
|
||||||
"hierarchical_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:27.538394608Z",
|
|
||||||
"cpu": {
|
|
||||||
"usage": {
|
|
||||||
"total": 56896502,
|
|
||||||
"per_cpu": [
|
|
||||||
20479682,
|
|
||||||
13579420,
|
|
||||||
6025040,
|
|
||||||
2255123,
|
|
||||||
3635661,
|
|
||||||
2489368,
|
|
||||||
5158288,
|
|
||||||
3273920
|
|
||||||
],
|
|
||||||
"user": 10000000,
|
|
||||||
"system": 30000000
|
|
||||||
},
|
|
||||||
"load": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616,
|
|
||||||
"container_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
},
|
|
||||||
"hierarchical_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:28.640302072Z",
|
|
||||||
"cpu": {
|
|
||||||
"usage": {
|
|
||||||
"total": 56896502,
|
|
||||||
"per_cpu": [
|
|
||||||
20479682,
|
|
||||||
13579420,
|
|
||||||
6025040,
|
|
||||||
2255123,
|
|
||||||
3635661,
|
|
||||||
2489368,
|
|
||||||
5158288,
|
|
||||||
3273920
|
|
||||||
],
|
|
||||||
"user": 10000000,
|
|
||||||
"system": 30000000
|
|
||||||
},
|
|
||||||
"load": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616,
|
|
||||||
"container_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
},
|
|
||||||
"hierarchical_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:29.74247308Z",
|
|
||||||
"cpu": {
|
|
||||||
"usage": {
|
|
||||||
"total": 56896502,
|
|
||||||
"per_cpu": [
|
|
||||||
20479682,
|
|
||||||
13579420,
|
|
||||||
6025040,
|
|
||||||
2255123,
|
|
||||||
3635661,
|
|
||||||
2489368,
|
|
||||||
5158288,
|
|
||||||
3273920
|
|
||||||
],
|
|
||||||
"user": 10000000,
|
|
||||||
"system": 30000000
|
|
||||||
},
|
|
||||||
"load": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616,
|
|
||||||
"container_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
},
|
|
||||||
"hierarchical_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:30.844494537Z",
|
|
||||||
"cpu": {
|
|
||||||
"usage": {
|
|
||||||
"total": 56896502,
|
|
||||||
"per_cpu": [
|
|
||||||
20479682,
|
|
||||||
13579420,
|
|
||||||
6025040,
|
|
||||||
2255123,
|
|
||||||
3635661,
|
|
||||||
2489368,
|
|
||||||
5158288,
|
|
||||||
3273920
|
|
||||||
],
|
|
||||||
"user": 10000000,
|
|
||||||
"system": 30000000
|
|
||||||
},
|
|
||||||
"load": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616,
|
|
||||||
"container_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
},
|
|
||||||
"hierarchical_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:31.946757066Z",
|
|
||||||
"cpu": {
|
|
||||||
"usage": {
|
|
||||||
"total": 56896502,
|
|
||||||
"per_cpu": [
|
|
||||||
20479682,
|
|
||||||
13579420,
|
|
||||||
6025040,
|
|
||||||
2255123,
|
|
||||||
3635661,
|
|
||||||
2489368,
|
|
||||||
5158288,
|
|
||||||
3273920
|
|
||||||
],
|
|
||||||
"user": 10000000,
|
|
||||||
"system": 30000000
|
|
||||||
},
|
|
||||||
"load": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616,
|
|
||||||
"container_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
},
|
|
||||||
"hierarchical_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:33.050214062Z",
|
|
||||||
"cpu": {
|
|
||||||
"usage": {
|
|
||||||
"total": 56896502,
|
|
||||||
"per_cpu": [
|
|
||||||
20479682,
|
|
||||||
13579420,
|
|
||||||
6025040,
|
|
||||||
2255123,
|
|
||||||
3635661,
|
|
||||||
2489368,
|
|
||||||
5158288,
|
|
||||||
3273920
|
|
||||||
],
|
|
||||||
"user": 10000000,
|
|
||||||
"system": 30000000
|
|
||||||
},
|
|
||||||
"load": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616,
|
|
||||||
"container_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
},
|
|
||||||
"hierarchical_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:34.15222186Z",
|
|
||||||
"cpu": {
|
|
||||||
"usage": {
|
|
||||||
"total": 56896502,
|
|
||||||
"per_cpu": [
|
|
||||||
20479682,
|
|
||||||
13579420,
|
|
||||||
6025040,
|
|
||||||
2255123,
|
|
||||||
3635661,
|
|
||||||
2489368,
|
|
||||||
5158288,
|
|
||||||
3273920
|
|
||||||
],
|
|
||||||
"user": 10000000,
|
|
||||||
"system": 30000000
|
|
||||||
},
|
|
||||||
"load": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616,
|
|
||||||
"container_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
},
|
|
||||||
"hierarchical_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:35.25417391Z",
|
|
||||||
"cpu": {
|
|
||||||
"usage": {
|
|
||||||
"total": 56896502,
|
|
||||||
"per_cpu": [
|
|
||||||
20479682,
|
|
||||||
13579420,
|
|
||||||
6025040,
|
|
||||||
2255123,
|
|
||||||
3635661,
|
|
||||||
2489368,
|
|
||||||
5158288,
|
|
||||||
3273920
|
|
||||||
],
|
|
||||||
"user": 10000000,
|
|
||||||
"system": 30000000
|
|
||||||
},
|
|
||||||
"load": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616,
|
|
||||||
"container_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
},
|
|
||||||
"hierarchical_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:36.355902169Z",
|
|
||||||
"cpu": {
|
|
||||||
"usage": {
|
|
||||||
"total": 56896502,
|
|
||||||
"per_cpu": [
|
|
||||||
20479682,
|
|
||||||
13579420,
|
|
||||||
6025040,
|
|
||||||
2255123,
|
|
||||||
3635661,
|
|
||||||
2489368,
|
|
||||||
5158288,
|
|
||||||
3273920
|
|
||||||
],
|
|
||||||
"user": 10000000,
|
|
||||||
"system": 30000000
|
|
||||||
},
|
|
||||||
"load": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616,
|
|
||||||
"container_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
},
|
|
||||||
"hierarchical_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:37.457585928Z",
|
|
||||||
"cpu": {
|
|
||||||
"usage": {
|
|
||||||
"total": 56896502,
|
|
||||||
"per_cpu": [
|
|
||||||
20479682,
|
|
||||||
13579420,
|
|
||||||
6025040,
|
|
||||||
2255123,
|
|
||||||
3635661,
|
|
||||||
2489368,
|
|
||||||
5158288,
|
|
||||||
3273920
|
|
||||||
],
|
|
||||||
"user": 10000000,
|
|
||||||
"system": 30000000
|
|
||||||
},
|
|
||||||
"load": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616,
|
|
||||||
"container_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
},
|
|
||||||
"hierarchical_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:38.559417379Z",
|
|
||||||
"cpu": {
|
|
||||||
"usage": {
|
|
||||||
"total": 56896502,
|
|
||||||
"per_cpu": [
|
|
||||||
20479682,
|
|
||||||
13579420,
|
|
||||||
6025040,
|
|
||||||
2255123,
|
|
||||||
3635661,
|
|
||||||
2489368,
|
|
||||||
5158288,
|
|
||||||
3273920
|
|
||||||
],
|
|
||||||
"user": 10000000,
|
|
||||||
"system": 30000000
|
|
||||||
},
|
|
||||||
"load": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616,
|
|
||||||
"container_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
},
|
|
||||||
"hierarchical_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:39.662978029Z",
|
|
||||||
"cpu": {
|
|
||||||
"usage": {
|
|
||||||
"total": 56896502,
|
|
||||||
"per_cpu": [
|
|
||||||
20479682,
|
|
||||||
13579420,
|
|
||||||
6025040,
|
|
||||||
2255123,
|
|
||||||
3635661,
|
|
||||||
2489368,
|
|
||||||
5158288,
|
|
||||||
3273920
|
|
||||||
],
|
|
||||||
"user": 10000000,
|
|
||||||
"system": 30000000
|
|
||||||
},
|
|
||||||
"load": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616,
|
|
||||||
"container_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
},
|
|
||||||
"hierarchical_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:40.764671232Z",
|
|
||||||
"cpu": {
|
|
||||||
"usage": {
|
|
||||||
"total": 56896502,
|
|
||||||
"per_cpu": [
|
|
||||||
20479682,
|
|
||||||
13579420,
|
|
||||||
6025040,
|
|
||||||
2255123,
|
|
||||||
3635661,
|
|
||||||
2489368,
|
|
||||||
5158288,
|
|
||||||
3273920
|
|
||||||
],
|
|
||||||
"user": 10000000,
|
|
||||||
"system": 30000000
|
|
||||||
},
|
|
||||||
"load": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616,
|
|
||||||
"container_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
},
|
|
||||||
"hierarchical_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:41.866456459Z",
|
|
||||||
"cpu": {
|
|
||||||
"usage": {
|
|
||||||
"total": 56896502,
|
|
||||||
"per_cpu": [
|
|
||||||
20479682,
|
|
||||||
13579420,
|
|
||||||
6025040,
|
|
||||||
2255123,
|
|
||||||
3635661,
|
|
||||||
2489368,
|
|
||||||
5158288,
|
|
||||||
3273920
|
|
||||||
],
|
|
||||||
"user": 10000000,
|
|
||||||
"system": 30000000
|
|
||||||
},
|
|
||||||
"load": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616,
|
|
||||||
"container_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
},
|
|
||||||
"hierarchical_data": {
|
|
||||||
"pgfault": 2279
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stats_summary": {
|
|
||||||
"max_memory_usage": 495616,
|
|
||||||
"samples": [
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:27.538394608Z",
|
|
||||||
"duration": 1103412783,
|
|
||||||
"cpu": {
|
|
||||||
"usage": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:28.640302072Z",
|
|
||||||
"duration": 1101907464,
|
|
||||||
"cpu": {
|
|
||||||
"usage": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:29.74247308Z",
|
|
||||||
"duration": 1102171008,
|
|
||||||
"cpu": {
|
|
||||||
"usage": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:30.844494537Z",
|
|
||||||
"duration": 1102021457,
|
|
||||||
"cpu": {
|
|
||||||
"usage": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:31.946757066Z",
|
|
||||||
"duration": 1102262529,
|
|
||||||
"cpu": {
|
|
||||||
"usage": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:33.050214062Z",
|
|
||||||
"duration": 1103456996,
|
|
||||||
"cpu": {
|
|
||||||
"usage": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:34.15222186Z",
|
|
||||||
"duration": 1102007798,
|
|
||||||
"cpu": {
|
|
||||||
"usage": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:35.25417391Z",
|
|
||||||
"duration": 1101952050,
|
|
||||||
"cpu": {
|
|
||||||
"usage": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:36.355902169Z",
|
|
||||||
"duration": 1101728259,
|
|
||||||
"cpu": {
|
|
||||||
"usage": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:37.457585928Z",
|
|
||||||
"duration": 1101683759,
|
|
||||||
"cpu": {
|
|
||||||
"usage": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:38.559417379Z",
|
|
||||||
"duration": 1101831451,
|
|
||||||
"cpu": {
|
|
||||||
"usage": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:39.662978029Z",
|
|
||||||
"duration": 1103560650,
|
|
||||||
"cpu": {
|
|
||||||
"usage": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:40.764671232Z",
|
|
||||||
"duration": 1101693203,
|
|
||||||
"cpu": {
|
|
||||||
"usage": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2014-06-13T01:03:41.866456459Z",
|
|
||||||
"duration": 1101785227,
|
|
||||||
"cpu": {
|
|
||||||
"usage": 0
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"usage": 495616
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"memory_usage_percentiles": [
|
|
||||||
{
|
|
||||||
"percentage": 50,
|
|
||||||
"value": 495616
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"percentage": 80,
|
|
||||||
"value": 495616
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"percentage": 90,
|
|
||||||
"value": 495616
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"percentage": 95,
|
|
||||||
"value": 495616
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"percentage": 99,
|
|
||||||
"value": 495616
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"cpu_usage_percentiles": [
|
|
||||||
{
|
|
||||||
"percentage": 50,
|
|
||||||
"value": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"percentage": 80,
|
|
||||||
"value": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"percentage": 90,
|
|
||||||
"value": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"percentage": 95,
|
|
||||||
"value": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"percentage": 99,
|
|
||||||
"value": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
containerName := "/some/container"
|
containerName := "/some/container"
|
||||||
respStr = fmt.Sprintf(respStr, containerName)
|
cinfo := itest.GenerateRandomContainerInfo(containerName, 4, query, 1*time.Second)
|
||||||
client, server, err := cadvisorTestClient(fmt.Sprintf("/api/v1.0/containers%v", containerName), respStr)
|
client, server, err := cadvisorTestClient(fmt.Sprintf("/api/v1.0/containers%v", containerName), query, &info.ContainerInfoRequest{}, cinfo, t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to get a client %v", err)
|
t.Fatalf("unable to get a client %v", err)
|
||||||
}
|
}
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
err = testGetJsonData(respStr, &info.ContainerInfo{}, func() (interface{}, error) {
|
returned, err := client.ContainerInfo(containerName, query)
|
||||||
return client.ContainerInfo(containerName)
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -118,7 +118,7 @@ func (self *dockerContainerHandler) isDockerContainer() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO(vmarmol): Switch to getting this from libcontainer once we have a solid API.
|
// TODO(vmarmol): Switch to getting this from libcontainer once we have a solid API.
|
||||||
func readLibcontainerSpec(id string) (spec *libcontainer.Container, err error) {
|
func readLibcontainerSpec(id string) (spec *libcontainer.Config, err error) {
|
||||||
dir := "/var/lib/docker/execdriver/native"
|
dir := "/var/lib/docker/execdriver/native"
|
||||||
configPath := path.Join(dir, id, "container.json")
|
configPath := path.Join(dir, id, "container.json")
|
||||||
f, err := os.Open(configPath)
|
f, err := os.Open(configPath)
|
||||||
@@ -127,7 +127,7 @@ func readLibcontainerSpec(id string) (spec *libcontainer.Container, err error) {
|
|||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
d := json.NewDecoder(f)
|
d := json.NewDecoder(f)
|
||||||
ret := new(libcontainer.Container)
|
ret := new(libcontainer.Config)
|
||||||
err = d.Decode(ret)
|
err = d.Decode(ret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -136,7 +136,7 @@ func readLibcontainerSpec(id string) (spec *libcontainer.Container, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func libcontainerConfigToContainerSpec(config *libcontainer.Container, mi *info.MachineInfo) *info.ContainerSpec {
|
func libcontainerConfigToContainerSpec(config *libcontainer.Config, mi *info.MachineInfo) *info.ContainerSpec {
|
||||||
spec := new(info.ContainerSpec)
|
spec := new(info.ContainerSpec)
|
||||||
spec.Memory = new(info.MemorySpec)
|
spec.Memory = new(info.MemorySpec)
|
||||||
spec.Memory.Limit = math.MaxUint64
|
spec.Memory.Limit = math.MaxUint64
|
||||||
@@ -209,6 +209,12 @@ func libcontainerToContainerStats(s *cgroups.Stats, mi *info.MachineInfo) *info.
|
|||||||
ret.Memory.ContainerData.Pgmajfault = v
|
ret.Memory.ContainerData.Pgmajfault = v
|
||||||
ret.Memory.HierarchicalData.Pgmajfault = v
|
ret.Memory.HierarchicalData.Pgmajfault = v
|
||||||
}
|
}
|
||||||
|
if v, ok := s.MemoryStats.Stats["total_inactive_anon"]; ok {
|
||||||
|
ret.Memory.WorkingSet = ret.Memory.Usage - v
|
||||||
|
if v, ok := s.MemoryStats.Stats["total_active_file"]; ok {
|
||||||
|
ret.Memory.WorkingSet -= v
|
||||||
|
}
|
||||||
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
88
third_party/src/github.com/google/cadvisor/container/test/mock.go
vendored
Normal file
88
third_party/src/github.com/google/cadvisor/container/test/mock.go
vendored
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
// 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 test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/cadvisor/container"
|
||||||
|
"github.com/google/cadvisor/info"
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This struct mocks a container handler.
|
||||||
|
type MockContainerHandler struct {
|
||||||
|
mock.Mock
|
||||||
|
Name string
|
||||||
|
Aliases []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// If self.Name is not empty, then ContainerReference() will return self.Name and self.Aliases.
|
||||||
|
// Otherwise, it will use the value provided by .On().Return().
|
||||||
|
func (self *MockContainerHandler) ContainerReference() (info.ContainerReference, error) {
|
||||||
|
if len(self.Name) > 0 {
|
||||||
|
var aliases []string
|
||||||
|
if len(self.Aliases) > 0 {
|
||||||
|
aliases = make([]string, len(self.Aliases))
|
||||||
|
copy(aliases, self.Aliases)
|
||||||
|
}
|
||||||
|
return info.ContainerReference{
|
||||||
|
Name: self.Name,
|
||||||
|
Aliases: aliases,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
args := self.Called()
|
||||||
|
return args.Get(0).(info.ContainerReference), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *MockContainerHandler) GetSpec() (*info.ContainerSpec, error) {
|
||||||
|
args := self.Called()
|
||||||
|
return args.Get(0).(*info.ContainerSpec), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *MockContainerHandler) GetStats() (*info.ContainerStats, error) {
|
||||||
|
args := self.Called()
|
||||||
|
return args.Get(0).(*info.ContainerStats), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *MockContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) {
|
||||||
|
args := self.Called(listType)
|
||||||
|
return args.Get(0).([]info.ContainerReference), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *MockContainerHandler) ListThreads(listType container.ListType) ([]int, error) {
|
||||||
|
args := self.Called(listType)
|
||||||
|
return args.Get(0).([]int), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *MockContainerHandler) ListProcesses(listType container.ListType) ([]int, error) {
|
||||||
|
args := self.Called(listType)
|
||||||
|
return args.Get(0).([]int), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
type FactoryForMockContainerHandler struct {
|
||||||
|
Name string
|
||||||
|
PrepareContainerHandlerFunc func(name string, handler *MockContainerHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *FactoryForMockContainerHandler) String() string {
|
||||||
|
return self.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *FactoryForMockContainerHandler) NewContainerHandler(name string) (container.ContainerHandler, error) {
|
||||||
|
handler := &MockContainerHandler{}
|
||||||
|
if self.PrepareContainerHandlerFunc != nil {
|
||||||
|
self.PrepareContainerHandlerFunc(name, handler)
|
||||||
|
}
|
||||||
|
return handler, nil
|
||||||
|
}
|
@@ -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.
|
# 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/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/lmctfy/libre2.so.0.0.0 /usr/lib/libre2.so.0
|
||||||
ADD http://storage.googleapis.com/cadvisor-bin/cadvisor /usr/bin/cadvisor
|
ADD http://storage.googleapis.com/cadvisor-bin/cadvisor-0.1.2 /usr/bin/cadvisor
|
||||||
RUN chmod +x /usr/bin/lmctfy && chmod +x /usr/bin/cadvisor
|
RUN chmod +x /usr/bin/lmctfy && chmod +x /usr/bin/cadvisor
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
34
third_party/src/github.com/google/cadvisor/info/advice.go
vendored
Normal file
34
third_party/src/github.com/google/cadvisor/info/advice.go
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
// 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 info
|
||||||
|
|
||||||
|
// This struct describes one type of relationship between containers: One
|
||||||
|
// container, antagonist, interferes the performance of other
|
||||||
|
// containers, victims.
|
||||||
|
type Interference struct {
|
||||||
|
// Absolute name of the antagonist container name. This field
|
||||||
|
// should not be empty.
|
||||||
|
Antagonist string `json:"antagonist"`
|
||||||
|
|
||||||
|
// The absolute path of the victims. This field should not be empty.
|
||||||
|
Victims []string `json:"victims"`
|
||||||
|
|
||||||
|
// The name of the detector used to detect this antagonism. This field
|
||||||
|
// should not be empty
|
||||||
|
Detector string `json:"detector"`
|
||||||
|
|
||||||
|
// Human readable description of this interference
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
}
|
@@ -16,6 +16,7 @@ package info
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -57,6 +58,40 @@ type ContainerReference struct {
|
|||||||
Aliases []string `json:"aliases,omitempty"`
|
Aliases []string `json:"aliases,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ContainerInfoQuery is used when users check a container info from the REST api.
|
||||||
|
// It specifies how much data users want to get about a container
|
||||||
|
type ContainerInfoRequest struct {
|
||||||
|
// Max number of stats to return.
|
||||||
|
NumStats int `json:"num_stats,omitempty"`
|
||||||
|
// Max number of samples to return.
|
||||||
|
NumSamples int `json:"num_samples,omitempty"`
|
||||||
|
|
||||||
|
// Different percentiles of CPU usage within a period. The values must be within [0, 100]
|
||||||
|
CpuUsagePercentiles []int `json:"cpu_usage_percentiles,omitempty"`
|
||||||
|
// Different percentiles of memory usage within a period. The values must be within [0, 100]
|
||||||
|
MemoryUsagePercentages []int `json:"memory_usage_percentiles,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *ContainerInfoRequest) FillDefaults() *ContainerInfoRequest {
|
||||||
|
ret := self
|
||||||
|
if ret == nil {
|
||||||
|
ret = new(ContainerInfoRequest)
|
||||||
|
}
|
||||||
|
if ret.NumStats <= 0 {
|
||||||
|
ret.NumStats = 1024
|
||||||
|
}
|
||||||
|
if ret.NumSamples <= 0 {
|
||||||
|
ret.NumSamples = 1024
|
||||||
|
}
|
||||||
|
if len(ret.CpuUsagePercentiles) == 0 {
|
||||||
|
ret.CpuUsagePercentiles = []int{50, 80, 90, 99}
|
||||||
|
}
|
||||||
|
if len(ret.MemoryUsagePercentages) == 0 {
|
||||||
|
ret.MemoryUsagePercentages = []int{50, 80, 90, 99}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
type ContainerInfo struct {
|
type ContainerInfo struct {
|
||||||
ContainerReference
|
ContainerReference
|
||||||
|
|
||||||
@@ -119,7 +154,7 @@ type CpuStats struct {
|
|||||||
|
|
||||||
// Per CPU/core usage of the container.
|
// Per CPU/core usage of the container.
|
||||||
// Unit: nanoseconds.
|
// Unit: nanoseconds.
|
||||||
PerCpu []uint64 `json:"per_cpu,omitempty"`
|
PerCpu []uint64 `json:"per_cpu_usage,omitempty"`
|
||||||
|
|
||||||
// Time spent in user space.
|
// Time spent in user space.
|
||||||
// Unit: nanoseconds
|
// Unit: nanoseconds
|
||||||
@@ -165,6 +200,42 @@ type ContainerStats struct {
|
|||||||
Memory *MemoryStats `json:"memory,omitempty"`
|
Memory *MemoryStats `json:"memory,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Makes a deep copy of the ContainerStats and returns a pointer to the new
|
||||||
|
// copy. Copy() will allocate a new ContainerStats object if dst is nil.
|
||||||
|
func (self *ContainerStats) Copy(dst *ContainerStats) *ContainerStats {
|
||||||
|
if dst == nil {
|
||||||
|
dst = new(ContainerStats)
|
||||||
|
}
|
||||||
|
dst.Timestamp = self.Timestamp
|
||||||
|
if self.Cpu != nil {
|
||||||
|
if dst.Cpu == nil {
|
||||||
|
dst.Cpu = new(CpuStats)
|
||||||
|
}
|
||||||
|
// To make a deep copy of a slice, we need to copy every value
|
||||||
|
// in the slice. To make less memory allocation, we would like
|
||||||
|
// to reuse the slice in dst if possible.
|
||||||
|
percpu := dst.Cpu.Usage.PerCpu
|
||||||
|
if len(percpu) != len(self.Cpu.Usage.PerCpu) {
|
||||||
|
percpu = make([]uint64, len(self.Cpu.Usage.PerCpu))
|
||||||
|
}
|
||||||
|
dst.Cpu.Usage = self.Cpu.Usage
|
||||||
|
dst.Cpu.Load = self.Cpu.Load
|
||||||
|
copy(percpu, self.Cpu.Usage.PerCpu)
|
||||||
|
dst.Cpu.Usage.PerCpu = percpu
|
||||||
|
} else {
|
||||||
|
dst.Cpu = nil
|
||||||
|
}
|
||||||
|
if self.Memory != nil {
|
||||||
|
if dst.Memory == nil {
|
||||||
|
dst.Memory = new(MemoryStats)
|
||||||
|
}
|
||||||
|
*dst.Memory = *self.Memory
|
||||||
|
} else {
|
||||||
|
dst.Memory = nil
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
type ContainerStatsSample struct {
|
type ContainerStatsSample struct {
|
||||||
// Timetamp of the end of the sample period
|
// Timetamp of the end of the sample period
|
||||||
Timestamp time.Time `json:"timestamp"`
|
Timestamp time.Time `json:"timestamp"`
|
||||||
@@ -173,6 +244,9 @@ type ContainerStatsSample struct {
|
|||||||
Cpu struct {
|
Cpu struct {
|
||||||
// number of nanoseconds of CPU time used by the container
|
// number of nanoseconds of CPU time used by the container
|
||||||
Usage uint64 `json:"usage"`
|
Usage uint64 `json:"usage"`
|
||||||
|
|
||||||
|
// Per-core usage of the container. (unit: nanoseconds)
|
||||||
|
PerCpuUsage []uint64 `json:"per_cpu_usage,omitempty"`
|
||||||
} `json:"cpu"`
|
} `json:"cpu"`
|
||||||
Memory struct {
|
Memory struct {
|
||||||
// Units: Bytes.
|
// Units: Bytes.
|
||||||
@@ -180,6 +254,67 @@ type ContainerStatsSample struct {
|
|||||||
} `json:"memory"`
|
} `json:"memory"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func timeEq(t1, t2 time.Time, tolerance time.Duration) bool {
|
||||||
|
// t1 should not be later than t2
|
||||||
|
if t1.After(t2) {
|
||||||
|
t1, t2 = t2, t1
|
||||||
|
}
|
||||||
|
diff := t2.Sub(t1)
|
||||||
|
if diff <= tolerance {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func durationEq(a, b time.Duration, tolerance time.Duration) bool {
|
||||||
|
if a > b {
|
||||||
|
a, b = b, a
|
||||||
|
}
|
||||||
|
diff := a - b
|
||||||
|
if diff <= tolerance {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// 10ms, i.e. 0.01s
|
||||||
|
timePrecision time.Duration = 10 * time.Millisecond
|
||||||
|
)
|
||||||
|
|
||||||
|
// This function is useful because we do not require precise time
|
||||||
|
// representation.
|
||||||
|
func (a *ContainerStats) Eq(b *ContainerStats) bool {
|
||||||
|
if !timeEq(a.Timestamp, b.Timestamp, timePrecision) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(a.Cpu, b.Cpu) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(a.Memory, b.Memory) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is useful because we do not require precise time
|
||||||
|
// representation.
|
||||||
|
func (a *ContainerStatsSample) Eq(b *ContainerStatsSample) bool {
|
||||||
|
if !timeEq(a.Timestamp, b.Timestamp, timePrecision) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !durationEq(a.Duration, b.Duration, timePrecision) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(a.Cpu, b.Cpu) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(a.Memory, b.Memory) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
type Percentile struct {
|
type Percentile struct {
|
||||||
Percentage int `json:"percentage"`
|
Percentage int `json:"percentage"`
|
||||||
Value uint64 `json:"value"`
|
Value uint64 `json:"value"`
|
||||||
@@ -211,9 +346,28 @@ func NewSample(prev, current *ContainerStats) (*ContainerStatsSample, error) {
|
|||||||
if current.Cpu.Usage.Total < prev.Cpu.Usage.Total {
|
if current.Cpu.Usage.Total < prev.Cpu.Usage.Total {
|
||||||
return nil, fmt.Errorf("current CPU usage is less than prev CPU usage (cumulative).")
|
return nil, fmt.Errorf("current CPU usage is less than prev CPU usage (cumulative).")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var percpu []uint64
|
||||||
|
|
||||||
|
if len(current.Cpu.Usage.PerCpu) > 0 {
|
||||||
|
curNumCpus := len(current.Cpu.Usage.PerCpu)
|
||||||
|
percpu = make([]uint64, curNumCpus)
|
||||||
|
|
||||||
|
for i, currUsage := range current.Cpu.Usage.PerCpu {
|
||||||
|
var prevUsage uint64 = 0
|
||||||
|
if i < len(prev.Cpu.Usage.PerCpu) {
|
||||||
|
prevUsage = prev.Cpu.Usage.PerCpu[i]
|
||||||
|
}
|
||||||
|
if currUsage < prevUsage {
|
||||||
|
return nil, fmt.Errorf("current per-core CPU usage is less than prev per-core CPU usage (cumulative).")
|
||||||
|
}
|
||||||
|
percpu[i] = currUsage - prevUsage
|
||||||
|
}
|
||||||
|
}
|
||||||
sample := new(ContainerStatsSample)
|
sample := new(ContainerStatsSample)
|
||||||
// Calculate the diff to get the CPU usage within the time interval.
|
// Calculate the diff to get the CPU usage within the time interval.
|
||||||
sample.Cpu.Usage = current.Cpu.Usage.Total - prev.Cpu.Usage.Total
|
sample.Cpu.Usage = current.Cpu.Usage.Total - prev.Cpu.Usage.Total
|
||||||
|
sample.Cpu.PerCpuUsage = percpu
|
||||||
// Memory usage is current memory usage
|
// Memory usage is current memory usage
|
||||||
sample.Memory.Usage = current.Memory.Usage
|
sample.Memory.Usage = current.Memory.Usage
|
||||||
sample.Timestamp = current.Timestamp
|
sample.Timestamp = current.Timestamp
|
||||||
|
@@ -15,6 +15,7 @@
|
|||||||
package info
|
package info
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -230,3 +231,68 @@ func TestAddSampleWrongCpuUsage(t *testing.T) {
|
|||||||
t.Errorf("generated an unexpected sample: %+v", sample)
|
t.Errorf("generated an unexpected sample: %+v", sample)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAddSampleHotPluggingCpu(t *testing.T) {
|
||||||
|
cpuPrevUsage := uint64(10)
|
||||||
|
cpuCurrentUsage := uint64(15)
|
||||||
|
memCurrentUsage := uint64(200)
|
||||||
|
prevTime := time.Now()
|
||||||
|
|
||||||
|
prev := createStats(cpuPrevUsage, memCurrentUsage, prevTime)
|
||||||
|
current := createStats(cpuCurrentUsage, memCurrentUsage, prevTime.Add(1*time.Second))
|
||||||
|
current.Cpu.Usage.PerCpu = append(current.Cpu.Usage.PerCpu, 10)
|
||||||
|
|
||||||
|
sample, err := NewSample(prev, current)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("should be able to generate a sample. but received error: %v", err)
|
||||||
|
}
|
||||||
|
if len(sample.Cpu.PerCpuUsage) != 2 {
|
||||||
|
t.Fatalf("Should have 2 cores.")
|
||||||
|
}
|
||||||
|
if sample.Cpu.PerCpuUsage[0] != cpuCurrentUsage-cpuPrevUsage {
|
||||||
|
t.Errorf("First cpu usage is %v. should be %v", sample.Cpu.PerCpuUsage[0], cpuCurrentUsage-cpuPrevUsage)
|
||||||
|
}
|
||||||
|
if sample.Cpu.PerCpuUsage[1] != 10 {
|
||||||
|
t.Errorf("Second cpu usage is %v. should be 10", sample.Cpu.PerCpuUsage[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddSampleHotUnpluggingCpu(t *testing.T) {
|
||||||
|
cpuPrevUsage := uint64(10)
|
||||||
|
cpuCurrentUsage := uint64(15)
|
||||||
|
memCurrentUsage := uint64(200)
|
||||||
|
prevTime := time.Now()
|
||||||
|
|
||||||
|
prev := createStats(cpuPrevUsage, memCurrentUsage, prevTime)
|
||||||
|
current := createStats(cpuCurrentUsage, memCurrentUsage, prevTime.Add(1*time.Second))
|
||||||
|
prev.Cpu.Usage.PerCpu = append(prev.Cpu.Usage.PerCpu, 10)
|
||||||
|
|
||||||
|
sample, err := NewSample(prev, current)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("should be able to generate a sample. but received error: %v", err)
|
||||||
|
}
|
||||||
|
if len(sample.Cpu.PerCpuUsage) != 1 {
|
||||||
|
t.Fatalf("Should have 1 cores.")
|
||||||
|
}
|
||||||
|
if sample.Cpu.PerCpuUsage[0] != cpuCurrentUsage-cpuPrevUsage {
|
||||||
|
t.Errorf("First cpu usage is %v. should be %v", sample.Cpu.PerCpuUsage[0], cpuCurrentUsage-cpuPrevUsage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContainerStatsCopy(t *testing.T) {
|
||||||
|
stats := createStats(100, 101, time.Now())
|
||||||
|
shadowStats := stats.Copy(nil)
|
||||||
|
if !reflect.DeepEqual(stats, shadowStats) {
|
||||||
|
t.Errorf("Copy() returned different object")
|
||||||
|
}
|
||||||
|
stats.Cpu.Usage.PerCpu[0] = shadowStats.Cpu.Usage.PerCpu[0] + 1
|
||||||
|
stats.Cpu.Load = shadowStats.Cpu.Load + 1
|
||||||
|
stats.Memory.Usage = shadowStats.Memory.Usage + 1
|
||||||
|
if reflect.DeepEqual(stats, shadowStats) {
|
||||||
|
t.Errorf("Copy() did not deeply copy the object")
|
||||||
|
}
|
||||||
|
stats = shadowStats.Copy(stats)
|
||||||
|
if !reflect.DeepEqual(stats, shadowStats) {
|
||||||
|
t.Errorf("Copy() returned different object")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
127
third_party/src/github.com/google/cadvisor/info/test/datagen.go
vendored
Normal file
127
third_party/src/github.com/google/cadvisor/info/test/datagen.go
vendored
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
// 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 test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/cadvisor/info"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GenerateRandomStats(numStats, numCores int, duration time.Duration) []*info.ContainerStats {
|
||||||
|
ret := make([]*info.ContainerStats, numStats)
|
||||||
|
perCoreUsages := make([]uint64, numCores)
|
||||||
|
currentTime := time.Now()
|
||||||
|
for i := range perCoreUsages {
|
||||||
|
perCoreUsages[i] = uint64(rand.Int63n(1000))
|
||||||
|
}
|
||||||
|
for i := 0; i < numStats; i++ {
|
||||||
|
stats := new(info.ContainerStats)
|
||||||
|
stats.Cpu = new(info.CpuStats)
|
||||||
|
stats.Memory = new(info.MemoryStats)
|
||||||
|
stats.Timestamp = currentTime
|
||||||
|
currentTime = currentTime.Add(duration)
|
||||||
|
|
||||||
|
percore := make([]uint64, numCores)
|
||||||
|
for i := range perCoreUsages {
|
||||||
|
perCoreUsages[i] += uint64(rand.Int63n(1000))
|
||||||
|
percore[i] = perCoreUsages[i]
|
||||||
|
stats.Cpu.Usage.Total += percore[i]
|
||||||
|
}
|
||||||
|
stats.Cpu.Usage.PerCpu = percore
|
||||||
|
stats.Cpu.Usage.User = stats.Cpu.Usage.Total
|
||||||
|
stats.Cpu.Usage.System = 0
|
||||||
|
stats.Memory.Usage = uint64(rand.Int63n(4096))
|
||||||
|
ret[i] = stats
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateRandomContainerSpec(numCores int) *info.ContainerSpec {
|
||||||
|
ret := &info.ContainerSpec{
|
||||||
|
Cpu: &info.CpuSpec{},
|
||||||
|
Memory: &info.MemorySpec{},
|
||||||
|
}
|
||||||
|
ret.Cpu.Limit = uint64(1000 + rand.Int63n(2000))
|
||||||
|
ret.Cpu.MaxLimit = uint64(1000 + rand.Int63n(2000))
|
||||||
|
n := (numCores + 63) / 64
|
||||||
|
ret.Cpu.Mask.Data = make([]uint64, n)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
ret.Cpu.Mask.Data[i] = math.MaxUint64
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.Memory.Limit = uint64(4096 + rand.Int63n(4096))
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateRandomContainerInfo(containerName string, numCores int, query *info.ContainerInfoRequest, duration time.Duration) *info.ContainerInfo {
|
||||||
|
stats := GenerateRandomStats(query.NumStats, numCores, duration)
|
||||||
|
samples, _ := NewSamplesFromStats(stats...)
|
||||||
|
if len(samples) > query.NumSamples {
|
||||||
|
samples = samples[:query.NumSamples]
|
||||||
|
}
|
||||||
|
cpuPercentiles := make([]info.Percentile, 0, len(query.CpuUsagePercentiles))
|
||||||
|
|
||||||
|
// TODO(monnand): This will generate percentiles where 50%tile data may
|
||||||
|
// be larger than 90%tile data.
|
||||||
|
for _, p := range query.CpuUsagePercentiles {
|
||||||
|
percentile := info.Percentile{p, uint64(rand.Int63n(1000))}
|
||||||
|
cpuPercentiles = append(cpuPercentiles, percentile)
|
||||||
|
}
|
||||||
|
memPercentiles := make([]info.Percentile, 0, len(query.MemoryUsagePercentages))
|
||||||
|
for _, p := range query.MemoryUsagePercentages {
|
||||||
|
percentile := info.Percentile{p, uint64(rand.Int63n(1000))}
|
||||||
|
memPercentiles = append(memPercentiles, percentile)
|
||||||
|
}
|
||||||
|
|
||||||
|
percentiles := &info.ContainerStatsPercentiles{
|
||||||
|
MaxMemoryUsage: uint64(rand.Int63n(4096)),
|
||||||
|
MemoryUsagePercentiles: memPercentiles,
|
||||||
|
CpuUsagePercentiles: cpuPercentiles,
|
||||||
|
}
|
||||||
|
|
||||||
|
spec := GenerateRandomContainerSpec(numCores)
|
||||||
|
|
||||||
|
ret := &info.ContainerInfo{
|
||||||
|
ContainerReference: info.ContainerReference{
|
||||||
|
Name: containerName,
|
||||||
|
},
|
||||||
|
Spec: spec,
|
||||||
|
StatsPercentiles: percentiles,
|
||||||
|
Samples: samples,
|
||||||
|
Stats: stats,
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSamplesFromStats(stats ...*info.ContainerStats) ([]*info.ContainerStatsSample, error) {
|
||||||
|
if len(stats) < 2 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
samples := make([]*info.ContainerStatsSample, 0, len(stats)-1)
|
||||||
|
for i, s := range stats[1:] {
|
||||||
|
prev := stats[i]
|
||||||
|
sample, err := info.NewSample(prev, s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to generate sample from %+v and %+v: %v",
|
||||||
|
prev, s, err)
|
||||||
|
}
|
||||||
|
samples = append(samples, sample)
|
||||||
|
}
|
||||||
|
return samples, nil
|
||||||
|
}
|
@@ -15,4 +15,4 @@
|
|||||||
package info
|
package info
|
||||||
|
|
||||||
// Version of cAdvisor.
|
// Version of cAdvisor.
|
||||||
const VERSION = "0.1.0"
|
const VERSION = "0.1.2"
|
||||||
|
237
third_party/src/github.com/google/cadvisor/manager/container_test.go
vendored
Normal file
237
third_party/src/github.com/google/cadvisor/manager/container_test.go
vendored
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
// Per-container manager.
|
||||||
|
|
||||||
|
package manager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/cadvisor/container"
|
||||||
|
ctest "github.com/google/cadvisor/container/test"
|
||||||
|
"github.com/google/cadvisor/info"
|
||||||
|
itest "github.com/google/cadvisor/info/test"
|
||||||
|
"github.com/google/cadvisor/storage"
|
||||||
|
stest "github.com/google/cadvisor/storage/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func createContainerDataAndSetHandler(
|
||||||
|
driver storage.StorageDriver,
|
||||||
|
f func(*ctest.MockContainerHandler),
|
||||||
|
t *testing.T,
|
||||||
|
) *containerData {
|
||||||
|
factory := &ctest.FactoryForMockContainerHandler{
|
||||||
|
Name: "factoryForMockContainer",
|
||||||
|
PrepareContainerHandlerFunc: func(name string, handler *ctest.MockContainerHandler) {
|
||||||
|
handler.Name = name
|
||||||
|
f(handler)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
container.RegisterContainerHandlerFactory("/", factory)
|
||||||
|
|
||||||
|
if driver == nil {
|
||||||
|
driver = &stest.MockStorageDriver{}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, err := NewContainerData("/container", driver)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContainerUpdateSubcontainers(t *testing.T) {
|
||||||
|
var handler *ctest.MockContainerHandler
|
||||||
|
subcontainers := []info.ContainerReference{
|
||||||
|
{Name: "/container/ee0103"},
|
||||||
|
{Name: "/container/abcd"},
|
||||||
|
{Name: "/container/something"},
|
||||||
|
}
|
||||||
|
cd := createContainerDataAndSetHandler(
|
||||||
|
nil,
|
||||||
|
func(h *ctest.MockContainerHandler) {
|
||||||
|
h.On("ListContainers", container.LIST_SELF).Return(
|
||||||
|
subcontainers,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
handler = h
|
||||||
|
},
|
||||||
|
t,
|
||||||
|
)
|
||||||
|
|
||||||
|
err := cd.updateSubcontainers()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cd.info.Subcontainers) != len(subcontainers) {
|
||||||
|
t.Errorf("Received %v subcontainers, should be %v", len(cd.info.Subcontainers), len(subcontainers))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sub := range cd.info.Subcontainers {
|
||||||
|
found := false
|
||||||
|
for _, sub2 := range subcontainers {
|
||||||
|
if sub.Name == sub2.Name {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Errorf("Received unknown sub container %v", sub)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.AssertExpectations(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContainerUpdateSubcontainersWithError(t *testing.T) {
|
||||||
|
var handler *ctest.MockContainerHandler
|
||||||
|
cd := createContainerDataAndSetHandler(
|
||||||
|
nil,
|
||||||
|
func(h *ctest.MockContainerHandler) {
|
||||||
|
h.On("ListContainers", container.LIST_SELF).Return(
|
||||||
|
[]info.ContainerReference{},
|
||||||
|
fmt.Errorf("some error"),
|
||||||
|
)
|
||||||
|
handler = h
|
||||||
|
},
|
||||||
|
t,
|
||||||
|
)
|
||||||
|
|
||||||
|
err := cd.updateSubcontainers()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("updateSubcontainers should return error")
|
||||||
|
}
|
||||||
|
if len(cd.info.Subcontainers) != 0 {
|
||||||
|
t.Errorf("Received %v subcontainers, should be 0", len(cd.info.Subcontainers))
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.AssertExpectations(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContainerUpdateStats(t *testing.T) {
|
||||||
|
var handler *ctest.MockContainerHandler
|
||||||
|
var ref info.ContainerReference
|
||||||
|
|
||||||
|
driver := &stest.MockStorageDriver{}
|
||||||
|
|
||||||
|
statsList := itest.GenerateRandomStats(1, 4, 1*time.Second)
|
||||||
|
stats := statsList[0]
|
||||||
|
|
||||||
|
cd := createContainerDataAndSetHandler(
|
||||||
|
driver,
|
||||||
|
func(h *ctest.MockContainerHandler) {
|
||||||
|
h.On("GetStats").Return(
|
||||||
|
stats,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
handler = h
|
||||||
|
ref.Name = h.Name
|
||||||
|
},
|
||||||
|
t,
|
||||||
|
)
|
||||||
|
|
||||||
|
driver.On("AddStats", ref, stats).Return(nil)
|
||||||
|
|
||||||
|
err := cd.updateStats()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.AssertExpectations(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContainerUpdateSpec(t *testing.T) {
|
||||||
|
var handler *ctest.MockContainerHandler
|
||||||
|
spec := itest.GenerateRandomContainerSpec(4)
|
||||||
|
cd := createContainerDataAndSetHandler(
|
||||||
|
nil,
|
||||||
|
func(h *ctest.MockContainerHandler) {
|
||||||
|
h.On("GetSpec").Return(
|
||||||
|
spec,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
handler = h
|
||||||
|
},
|
||||||
|
t,
|
||||||
|
)
|
||||||
|
|
||||||
|
err := cd.updateSpec()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.AssertExpectations(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContainerGetInfo(t *testing.T) {
|
||||||
|
var handler *ctest.MockContainerHandler
|
||||||
|
spec := itest.GenerateRandomContainerSpec(4)
|
||||||
|
subcontainers := []info.ContainerReference{
|
||||||
|
{Name: "/container/ee0103"},
|
||||||
|
{Name: "/container/abcd"},
|
||||||
|
{Name: "/container/something"},
|
||||||
|
}
|
||||||
|
aliases := []string{"a1", "a2"}
|
||||||
|
cd := createContainerDataAndSetHandler(
|
||||||
|
nil,
|
||||||
|
func(h *ctest.MockContainerHandler) {
|
||||||
|
h.On("GetSpec").Return(
|
||||||
|
spec,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
h.On("ListContainers", container.LIST_SELF).Return(
|
||||||
|
subcontainers,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
h.Aliases = aliases
|
||||||
|
handler = h
|
||||||
|
},
|
||||||
|
t,
|
||||||
|
)
|
||||||
|
|
||||||
|
info, err := cd.GetInfo()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.AssertExpectations(t)
|
||||||
|
|
||||||
|
if len(info.Subcontainers) != len(subcontainers) {
|
||||||
|
t.Errorf("Received %v subcontainers, should be %v", len(info.Subcontainers), len(subcontainers))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sub := range info.Subcontainers {
|
||||||
|
found := false
|
||||||
|
for _, sub2 := range subcontainers {
|
||||||
|
if sub.Name == sub2.Name {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Errorf("Received unknown sub container %v", sub)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(spec, info.Spec) {
|
||||||
|
t.Errorf("received wrong container spec")
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.Name != handler.Name {
|
||||||
|
t.Errorf("received wrong container name: received %v; should be %v", info.Name, handler.Name)
|
||||||
|
}
|
||||||
|
}
|
@@ -30,7 +30,7 @@ type Manager interface {
|
|||||||
Start() error
|
Start() error
|
||||||
|
|
||||||
// Get information about a container.
|
// Get information about a container.
|
||||||
GetContainerInfo(containerName string) (*info.ContainerInfo, error)
|
GetContainerInfo(containerName string, query *info.ContainerInfoRequest) (*info.ContainerInfo, error)
|
||||||
|
|
||||||
// Get information about the machine.
|
// Get information about the machine.
|
||||||
GetMachineInfo() (*info.MachineInfo, error)
|
GetMachineInfo() (*info.MachineInfo, error)
|
||||||
@@ -106,8 +106,8 @@ func (m *manager) Start() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get a container by name.
|
// Get a container by name.
|
||||||
func (m *manager) GetContainerInfo(containerName string) (*info.ContainerInfo, error) {
|
func (m *manager) GetContainerInfo(containerName string, query *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
|
||||||
log.Printf("Get(%s)", containerName)
|
log.Printf("Get(%s); %+v", containerName, query)
|
||||||
var cont *containerData
|
var cont *containerData
|
||||||
var ok bool
|
var ok bool
|
||||||
func() {
|
func() {
|
||||||
@@ -130,21 +130,21 @@ func (m *manager) GetContainerInfo(containerName string) (*info.ContainerInfo, e
|
|||||||
var percentiles *info.ContainerStatsPercentiles
|
var percentiles *info.ContainerStatsPercentiles
|
||||||
var samples []*info.ContainerStatsSample
|
var samples []*info.ContainerStatsSample
|
||||||
var stats []*info.ContainerStats
|
var stats []*info.ContainerStats
|
||||||
// TODO(monnand): These numbers should not be hard coded
|
query = query.FillDefaults()
|
||||||
percentiles, err = m.storageDriver.Percentiles(
|
percentiles, err = m.storageDriver.Percentiles(
|
||||||
cinfo.Name,
|
cinfo.Name,
|
||||||
[]int{50, 80, 90, 99},
|
query.CpuUsagePercentiles,
|
||||||
[]int{50, 80, 90, 99},
|
query.MemoryUsagePercentages,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
samples, err = m.storageDriver.Samples(cinfo.Name, 1024)
|
samples, err = m.storageDriver.Samples(cinfo.Name, query.NumSamples)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
stats, err = m.storageDriver.RecentStats(cinfo.Name, 1024)
|
stats, err = m.storageDriver.RecentStats(cinfo.Name, query.NumStats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
253
third_party/src/github.com/google/cadvisor/manager/manager_test.go
vendored
Normal file
253
third_party/src/github.com/google/cadvisor/manager/manager_test.go
vendored
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
// Per-container manager.
|
||||||
|
|
||||||
|
package manager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/cadvisor/container"
|
||||||
|
ctest "github.com/google/cadvisor/container/test"
|
||||||
|
"github.com/google/cadvisor/info"
|
||||||
|
itest "github.com/google/cadvisor/info/test"
|
||||||
|
stest "github.com/google/cadvisor/storage/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func createManagerAndAddContainers(
|
||||||
|
driver *stest.MockStorageDriver,
|
||||||
|
containers []string,
|
||||||
|
f func(*ctest.MockContainerHandler),
|
||||||
|
t *testing.T,
|
||||||
|
) *manager {
|
||||||
|
if driver == nil {
|
||||||
|
driver = &stest.MockStorageDriver{}
|
||||||
|
}
|
||||||
|
factory := &ctest.FactoryForMockContainerHandler{
|
||||||
|
Name: "factoryForManager",
|
||||||
|
PrepareContainerHandlerFunc: func(name string, handler *ctest.MockContainerHandler) {
|
||||||
|
handler.Name = name
|
||||||
|
found := false
|
||||||
|
for _, c := range containers {
|
||||||
|
if c == name {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Errorf("Asked to create a container with name %v, which is unknown.", name)
|
||||||
|
}
|
||||||
|
f(handler)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
container.RegisterContainerHandlerFactory("/", factory)
|
||||||
|
mif, err := New(driver)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if ret, ok := mif.(*manager); ok {
|
||||||
|
for _, container := range containers {
|
||||||
|
ret.containers[container], err = NewContainerData(container, driver)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
t.Fatal("Wrong type")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetContainerInfo(t *testing.T) {
|
||||||
|
containers := []string{
|
||||||
|
"/c1",
|
||||||
|
"/c2",
|
||||||
|
}
|
||||||
|
|
||||||
|
query := &info.ContainerInfoRequest{
|
||||||
|
NumStats: 256,
|
||||||
|
NumSamples: 128,
|
||||||
|
CpuUsagePercentiles: []int{10, 50, 90},
|
||||||
|
MemoryUsagePercentages: []int{10, 80, 90},
|
||||||
|
}
|
||||||
|
|
||||||
|
infosMap := make(map[string]*info.ContainerInfo, len(containers))
|
||||||
|
handlerMap := make(map[string]*ctest.MockContainerHandler, len(containers))
|
||||||
|
|
||||||
|
for _, container := range containers {
|
||||||
|
infosMap[container] = itest.GenerateRandomContainerInfo(container, 4, query, 1*time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
driver := &stest.MockStorageDriver{}
|
||||||
|
m := createManagerAndAddContainers(
|
||||||
|
driver,
|
||||||
|
containers,
|
||||||
|
func(h *ctest.MockContainerHandler) {
|
||||||
|
cinfo := infosMap[h.Name]
|
||||||
|
stats := cinfo.Stats
|
||||||
|
samples := cinfo.Samples
|
||||||
|
percentiles := cinfo.StatsPercentiles
|
||||||
|
spec := cinfo.Spec
|
||||||
|
driver.On(
|
||||||
|
"Percentiles",
|
||||||
|
h.Name,
|
||||||
|
query.CpuUsagePercentiles,
|
||||||
|
query.MemoryUsagePercentages,
|
||||||
|
).Return(
|
||||||
|
percentiles,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
driver.On(
|
||||||
|
"Samples",
|
||||||
|
h.Name,
|
||||||
|
query.NumSamples,
|
||||||
|
).Return(
|
||||||
|
samples,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
driver.On(
|
||||||
|
"RecentStats",
|
||||||
|
h.Name,
|
||||||
|
query.NumStats,
|
||||||
|
).Return(
|
||||||
|
stats,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
h.On("ListContainers", container.LIST_SELF).Return(
|
||||||
|
[]info.ContainerReference(nil),
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
h.On("GetSpec").Return(
|
||||||
|
spec,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
handlerMap[h.Name] = h
|
||||||
|
},
|
||||||
|
t,
|
||||||
|
)
|
||||||
|
|
||||||
|
returnedInfos := make(map[string]*info.ContainerInfo, len(containers))
|
||||||
|
|
||||||
|
for _, container := range containers {
|
||||||
|
cinfo, err := m.GetContainerInfo(container, query)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to get info for container %v: %v", container, err)
|
||||||
|
}
|
||||||
|
returnedInfos[container] = cinfo
|
||||||
|
}
|
||||||
|
|
||||||
|
for container, handler := range handlerMap {
|
||||||
|
handler.AssertExpectations(t)
|
||||||
|
returned := returnedInfos[container]
|
||||||
|
expected := infosMap[container]
|
||||||
|
if !reflect.DeepEqual(returned, expected) {
|
||||||
|
t.Errorf("returned unexpected info for container %v; returned %+v; expected %+v", container, returned, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetContainerInfoWithDefaultValue(t *testing.T) {
|
||||||
|
containers := []string{
|
||||||
|
"/c1",
|
||||||
|
"/c2",
|
||||||
|
}
|
||||||
|
|
||||||
|
var query *info.ContainerInfoRequest
|
||||||
|
query = query.FillDefaults()
|
||||||
|
|
||||||
|
infosMap := make(map[string]*info.ContainerInfo, len(containers))
|
||||||
|
handlerMap := make(map[string]*ctest.MockContainerHandler, len(containers))
|
||||||
|
|
||||||
|
for _, container := range containers {
|
||||||
|
infosMap[container] = itest.GenerateRandomContainerInfo(container, 4, query, 1*time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
driver := &stest.MockStorageDriver{}
|
||||||
|
m := createManagerAndAddContainers(
|
||||||
|
driver,
|
||||||
|
containers,
|
||||||
|
func(h *ctest.MockContainerHandler) {
|
||||||
|
cinfo := infosMap[h.Name]
|
||||||
|
stats := cinfo.Stats
|
||||||
|
samples := cinfo.Samples
|
||||||
|
percentiles := cinfo.StatsPercentiles
|
||||||
|
spec := cinfo.Spec
|
||||||
|
driver.On(
|
||||||
|
"Percentiles",
|
||||||
|
h.Name,
|
||||||
|
query.CpuUsagePercentiles,
|
||||||
|
query.MemoryUsagePercentages,
|
||||||
|
).Return(
|
||||||
|
percentiles,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
driver.On(
|
||||||
|
"Samples",
|
||||||
|
h.Name,
|
||||||
|
query.NumSamples,
|
||||||
|
).Return(
|
||||||
|
samples,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
driver.On(
|
||||||
|
"RecentStats",
|
||||||
|
h.Name,
|
||||||
|
query.NumStats,
|
||||||
|
).Return(
|
||||||
|
stats,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
h.On("ListContainers", container.LIST_SELF).Return(
|
||||||
|
[]info.ContainerReference(nil),
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
h.On("GetSpec").Return(
|
||||||
|
spec,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
handlerMap[h.Name] = h
|
||||||
|
},
|
||||||
|
t,
|
||||||
|
)
|
||||||
|
|
||||||
|
returnedInfos := make(map[string]*info.ContainerInfo, len(containers))
|
||||||
|
|
||||||
|
for _, container := range containers {
|
||||||
|
// nil should give us default values
|
||||||
|
cinfo, err := m.GetContainerInfo(container, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to get info for container %v: %v", container, err)
|
||||||
|
}
|
||||||
|
returnedInfos[container] = cinfo
|
||||||
|
}
|
||||||
|
|
||||||
|
for container, handler := range handlerMap {
|
||||||
|
handler.AssertExpectations(t)
|
||||||
|
returned := returnedInfos[container]
|
||||||
|
expected := infosMap[container]
|
||||||
|
if !reflect.DeepEqual(returned, expected) {
|
||||||
|
t.Errorf("returned unexpected info for container %v; returned %+v; expected %+v", container, returned, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -162,7 +162,11 @@ func ServerContainersPage(m manager.Manager, w http.ResponseWriter, u *url.URL)
|
|||||||
containerName := u.Path[len(ContainersPage)-1:]
|
containerName := u.Path[len(ContainersPage)-1:]
|
||||||
|
|
||||||
// Get the container.
|
// Get the container.
|
||||||
cont, err := m.GetContainerInfo(containerName)
|
reqParams := info.ContainerInfoRequest{
|
||||||
|
NumStats: 60,
|
||||||
|
NumSamples: 60,
|
||||||
|
}
|
||||||
|
cont, err := m.GetContainerInfo(containerName, &reqParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to get container \"%s\" with error: %s", containerName, err)
|
return fmt.Errorf("Failed to get container \"%s\" with error: %s", containerName, err)
|
||||||
}
|
}
|
||||||
|
@@ -128,7 +128,7 @@ function drawCpuPerCoreUsage(elementId, machineInfo, stats) {
|
|||||||
elements.push(cur.timestamp);
|
elements.push(cur.timestamp);
|
||||||
for (var j = 0; j < machineInfo.num_cores; j++) {
|
for (var j = 0; j < machineInfo.num_cores; j++) {
|
||||||
// TODO(vmarmol): This assumes we sample every second, use the timestamps.
|
// TODO(vmarmol): This assumes we sample every second, use the timestamps.
|
||||||
elements.push((cur.cpu.usage.per_cpu[j] - prev.cpu.usage.per_cpu[j]) / 1000000000);
|
elements.push((cur.cpu.usage.per_cpu_usage[j] - prev.cpu.usage.per_cpu_usage[j]) / 1000000000);
|
||||||
}
|
}
|
||||||
data.push(elements);
|
data.push(elements);
|
||||||
}
|
}
|
||||||
|
498
third_party/src/github.com/google/cadvisor/storage/influxdb/influxdb.go
vendored
Normal file
498
third_party/src/github.com/google/cadvisor/storage/influxdb/influxdb.go
vendored
Normal file
@@ -0,0 +1,498 @@
|
|||||||
|
// 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 influxdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/cadvisor/info"
|
||||||
|
"github.com/google/cadvisor/storage"
|
||||||
|
"github.com/influxdb/influxdb-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
type influxdbStorage struct {
|
||||||
|
client *influxdb.Client
|
||||||
|
prevStats *info.ContainerStats
|
||||||
|
machineName string
|
||||||
|
tableName string
|
||||||
|
windowLen time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
colTimestamp string = "timestamp"
|
||||||
|
colMachineName string = "machine"
|
||||||
|
colContainerName string = "container_name"
|
||||||
|
colCpuCumulativeUsage string = "cpu_cumulative_usage"
|
||||||
|
// Cumulative Cpu Usage in system mode
|
||||||
|
colCpuCumulativeUsageSystem string = "cpu_cumulative_usage_system"
|
||||||
|
// Cumulative Cpu Usage in user mode
|
||||||
|
colCpuCumulativeUsageUser string = "cpu_cumulative_usage_user"
|
||||||
|
// Memory Usage
|
||||||
|
colMemoryUsage string = "memory_usage"
|
||||||
|
// Working set size
|
||||||
|
colMemoryWorkingSet string = "memory_working_set"
|
||||||
|
// container page fault
|
||||||
|
colMemoryContainerPgfault string = "memory_container_pgfault"
|
||||||
|
// container major page fault
|
||||||
|
colMemoryContainerPgmajfault string = "memory_container_pgmajfault"
|
||||||
|
// hierarchical page fault
|
||||||
|
colMemoryHierarchicalPgfault string = "memory_hierarchical_pgfault"
|
||||||
|
// hierarchical major page fault
|
||||||
|
colMemoryHierarchicalPgmajfault string = "memory_hierarchical_pgmajfault"
|
||||||
|
// Cumulative per core usage
|
||||||
|
colPerCoreCumulativeUsagePrefix string = "per_core_cumulative_usage_core_"
|
||||||
|
// Optional: sample duration. Unit: Nanosecond.
|
||||||
|
colSampleDuration string = "sample_duration"
|
||||||
|
// Optional: Instant cpu usage
|
||||||
|
colCpuInstantUsage string = "cpu_instant_usage"
|
||||||
|
// Optional: Instant per core usage
|
||||||
|
colPerCoreInstantUsagePrefix string = "per_core_instant_usage_core_"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (self *influxdbStorage) containerStatsToValues(
|
||||||
|
ref info.ContainerReference,
|
||||||
|
stats *info.ContainerStats,
|
||||||
|
) (columns []string, values []interface{}) {
|
||||||
|
|
||||||
|
// Timestamp
|
||||||
|
columns = append(columns, colTimestamp)
|
||||||
|
values = append(values, stats.Timestamp.Format(time.RFC3339Nano))
|
||||||
|
|
||||||
|
// Machine name
|
||||||
|
columns = append(columns, colMachineName)
|
||||||
|
values = append(values, self.machineName)
|
||||||
|
|
||||||
|
// Container name
|
||||||
|
columns = append(columns, colContainerName)
|
||||||
|
values = append(values, ref.Name)
|
||||||
|
|
||||||
|
// Cumulative Cpu Usage
|
||||||
|
columns = append(columns, colCpuCumulativeUsage)
|
||||||
|
values = append(values, stats.Cpu.Usage.Total)
|
||||||
|
|
||||||
|
// Cumulative Cpu Usage in system mode
|
||||||
|
columns = append(columns, colCpuCumulativeUsageSystem)
|
||||||
|
values = append(values, stats.Cpu.Usage.System)
|
||||||
|
|
||||||
|
// Cumulative Cpu Usage in user mode
|
||||||
|
columns = append(columns, colCpuCumulativeUsageUser)
|
||||||
|
values = append(values, stats.Cpu.Usage.User)
|
||||||
|
|
||||||
|
// Memory Usage
|
||||||
|
columns = append(columns, colMemoryUsage)
|
||||||
|
values = append(values, stats.Memory.Usage)
|
||||||
|
|
||||||
|
// Working set size
|
||||||
|
columns = append(columns, colMemoryWorkingSet)
|
||||||
|
values = append(values, stats.Memory.WorkingSet)
|
||||||
|
|
||||||
|
// container page fault
|
||||||
|
columns = append(columns, colMemoryContainerPgfault)
|
||||||
|
values = append(values, stats.Memory.ContainerData.Pgfault)
|
||||||
|
|
||||||
|
// container major page fault
|
||||||
|
columns = append(columns, colMemoryContainerPgmajfault)
|
||||||
|
values = append(values, stats.Memory.ContainerData.Pgmajfault)
|
||||||
|
|
||||||
|
// hierarchical page fault
|
||||||
|
columns = append(columns, colMemoryHierarchicalPgfault)
|
||||||
|
values = append(values, stats.Memory.HierarchicalData.Pgfault)
|
||||||
|
|
||||||
|
// hierarchical major page fault
|
||||||
|
columns = append(columns, colMemoryHierarchicalPgmajfault)
|
||||||
|
values = append(values, stats.Memory.HierarchicalData.Pgmajfault)
|
||||||
|
|
||||||
|
// per cpu cumulative usage
|
||||||
|
for i, u := range stats.Cpu.Usage.PerCpu {
|
||||||
|
columns = append(columns, fmt.Sprintf("%v%v", colPerCoreCumulativeUsagePrefix, i))
|
||||||
|
values = append(values, u)
|
||||||
|
}
|
||||||
|
|
||||||
|
sample, err := info.NewSample(self.prevStats, stats)
|
||||||
|
if err != nil || sample == nil {
|
||||||
|
return columns, values
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional: sample duration. Unit: Nanosecond.
|
||||||
|
columns = append(columns, colSampleDuration)
|
||||||
|
values = append(values, sample.Duration.String())
|
||||||
|
|
||||||
|
// Optional: Instant cpu usage
|
||||||
|
columns = append(columns, colCpuInstantUsage)
|
||||||
|
values = append(values, sample.Cpu.Usage)
|
||||||
|
|
||||||
|
// Optional: Instant per core usage
|
||||||
|
for i, u := range sample.Cpu.PerCpuUsage {
|
||||||
|
columns = append(columns, fmt.Sprintf("%v%v", colPerCoreInstantUsagePrefix, i))
|
||||||
|
values = append(values, u)
|
||||||
|
}
|
||||||
|
|
||||||
|
return columns, values
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertToUint64(v interface{}) (uint64, error) {
|
||||||
|
if v == nil {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
switch x := v.(type) {
|
||||||
|
case uint64:
|
||||||
|
return x, nil
|
||||||
|
case int:
|
||||||
|
if x < 0 {
|
||||||
|
return 0, fmt.Errorf("negative value: %v", x)
|
||||||
|
}
|
||||||
|
return uint64(x), nil
|
||||||
|
case int32:
|
||||||
|
if x < 0 {
|
||||||
|
return 0, fmt.Errorf("negative value: %v", x)
|
||||||
|
}
|
||||||
|
return uint64(x), nil
|
||||||
|
case int64:
|
||||||
|
if x < 0 {
|
||||||
|
return 0, fmt.Errorf("negative value: %v", x)
|
||||||
|
}
|
||||||
|
return uint64(x), nil
|
||||||
|
case float64:
|
||||||
|
if x < 0 {
|
||||||
|
return 0, fmt.Errorf("negative value: %v", x)
|
||||||
|
}
|
||||||
|
return uint64(x), nil
|
||||||
|
case uint32:
|
||||||
|
return uint64(x), nil
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("Unknown type")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *influxdbStorage) valuesToContainerStats(columns []string, values []interface{}) (*info.ContainerStats, error) {
|
||||||
|
stats := &info.ContainerStats{
|
||||||
|
Cpu: &info.CpuStats{},
|
||||||
|
Memory: &info.MemoryStats{},
|
||||||
|
}
|
||||||
|
perCoreUsage := make(map[int]uint64, 32)
|
||||||
|
var err error
|
||||||
|
for i, col := range columns {
|
||||||
|
v := values[i]
|
||||||
|
switch {
|
||||||
|
case col == colTimestamp:
|
||||||
|
if str, ok := v.(string); ok {
|
||||||
|
stats.Timestamp, err = time.Parse(time.RFC3339Nano, str)
|
||||||
|
}
|
||||||
|
case col == colMachineName:
|
||||||
|
if m, ok := v.(string); ok {
|
||||||
|
if m != self.machineName {
|
||||||
|
return nil, fmt.Errorf("different machine")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("machine name field is not a string: %v", v)
|
||||||
|
}
|
||||||
|
// Cumulative Cpu Usage
|
||||||
|
case col == colCpuCumulativeUsage:
|
||||||
|
stats.Cpu.Usage.Total, err = convertToUint64(v)
|
||||||
|
// Cumulative Cpu used by the system
|
||||||
|
case col == colCpuCumulativeUsageSystem:
|
||||||
|
stats.Cpu.Usage.System, err = convertToUint64(v)
|
||||||
|
// Cumulative Cpu Usage in user mode
|
||||||
|
case col == colCpuCumulativeUsageUser:
|
||||||
|
stats.Cpu.Usage.User, err = convertToUint64(v)
|
||||||
|
// Memory Usage
|
||||||
|
case col == colMemoryUsage:
|
||||||
|
stats.Memory.Usage, err = convertToUint64(v)
|
||||||
|
// Working set size
|
||||||
|
case col == colMemoryWorkingSet:
|
||||||
|
stats.Memory.WorkingSet, err = convertToUint64(v)
|
||||||
|
// container page fault
|
||||||
|
case col == colMemoryContainerPgfault:
|
||||||
|
stats.Memory.ContainerData.Pgfault, err = convertToUint64(v)
|
||||||
|
// container major page fault
|
||||||
|
case col == colMemoryContainerPgmajfault:
|
||||||
|
stats.Memory.ContainerData.Pgmajfault, err = convertToUint64(v)
|
||||||
|
// hierarchical page fault
|
||||||
|
case col == colMemoryHierarchicalPgfault:
|
||||||
|
stats.Memory.HierarchicalData.Pgfault, err = convertToUint64(v)
|
||||||
|
// hierarchical major page fault
|
||||||
|
case col == colMemoryHierarchicalPgmajfault:
|
||||||
|
stats.Memory.HierarchicalData.Pgmajfault, err = convertToUint64(v)
|
||||||
|
case strings.HasPrefix(col, colPerCoreCumulativeUsagePrefix):
|
||||||
|
idxStr := col[len(colPerCoreCumulativeUsagePrefix):]
|
||||||
|
idx, err := strconv.Atoi(idxStr)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
perCoreUsage[idx], err = convertToUint64(v)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("column %v has invalid value %v: %v", col, v, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stats.Cpu.Usage.PerCpu = make([]uint64, len(perCoreUsage))
|
||||||
|
for idx, usage := range perCoreUsage {
|
||||||
|
stats.Cpu.Usage.PerCpu[idx] = usage
|
||||||
|
}
|
||||||
|
return stats, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *influxdbStorage) valuesToContainerSample(columns []string, values []interface{}) (*info.ContainerStatsSample, error) {
|
||||||
|
sample := &info.ContainerStatsSample{}
|
||||||
|
perCoreUsage := make(map[int]uint64, 32)
|
||||||
|
var err error
|
||||||
|
for i, col := range columns {
|
||||||
|
v := values[i]
|
||||||
|
switch {
|
||||||
|
case col == colTimestamp:
|
||||||
|
if str, ok := v.(string); ok {
|
||||||
|
sample.Timestamp, err = time.Parse(time.RFC3339Nano, str)
|
||||||
|
}
|
||||||
|
case col == colMachineName:
|
||||||
|
if m, ok := v.(string); ok {
|
||||||
|
if m != self.machineName {
|
||||||
|
return nil, fmt.Errorf("different machine")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("machine name field is not a string: %v", v)
|
||||||
|
}
|
||||||
|
// Memory Usage
|
||||||
|
case col == colMemoryUsage:
|
||||||
|
sample.Memory.Usage, err = convertToUint64(v)
|
||||||
|
// sample duration. Unit: Nanosecond.
|
||||||
|
case col == colSampleDuration:
|
||||||
|
if v == nil {
|
||||||
|
// this record does not have sample_duration, so it's the first stats.
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
sample.Duration, err = time.ParseDuration(v.(string))
|
||||||
|
// Instant cpu usage
|
||||||
|
case col == colCpuInstantUsage:
|
||||||
|
sample.Cpu.Usage, err = convertToUint64(v)
|
||||||
|
case strings.HasPrefix(col, colPerCoreInstantUsagePrefix):
|
||||||
|
idxStr := col[len(colPerCoreInstantUsagePrefix):]
|
||||||
|
idx, err := strconv.Atoi(idxStr)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
perCoreUsage[idx], err = convertToUint64(v)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("column %v has invalid value %v: %v", col, v, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sample.Cpu.PerCpuUsage = make([]uint64, len(perCoreUsage))
|
||||||
|
for idx, usage := range perCoreUsage {
|
||||||
|
sample.Cpu.PerCpuUsage[idx] = usage
|
||||||
|
}
|
||||||
|
if sample.Duration.Nanoseconds() == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return sample, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *influxdbStorage) AddStats(ref info.ContainerReference, stats *info.ContainerStats) error {
|
||||||
|
series := &influxdb.Series{
|
||||||
|
Name: self.tableName,
|
||||||
|
// There's only one point for each stats
|
||||||
|
Points: make([][]interface{}, 1),
|
||||||
|
}
|
||||||
|
if stats == nil || stats.Cpu == nil || stats.Memory == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
series.Columns, series.Points[0] = self.containerStatsToValues(ref, stats)
|
||||||
|
|
||||||
|
self.prevStats = stats.Copy(self.prevStats)
|
||||||
|
err := self.client.WriteSeries([]*influxdb.Series{series})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *influxdbStorage) RecentStats(containerName string, numStats int) ([]*info.ContainerStats, error) {
|
||||||
|
// TODO(dengnan): select only columns that we need
|
||||||
|
// TODO(dengnan): escape names
|
||||||
|
query := fmt.Sprintf("select * from %v where %v='%v' and %v='%v'", self.tableName, colContainerName, containerName, colMachineName, self.machineName)
|
||||||
|
if numStats > 0 {
|
||||||
|
query = fmt.Sprintf("%v limit %v", query, numStats)
|
||||||
|
}
|
||||||
|
series, err := self.client.Query(query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
statsList := make([]*info.ContainerStats, 0, len(series))
|
||||||
|
// By default, influxDB returns data in time descending order.
|
||||||
|
// RecentStats() requires stats in time increasing order,
|
||||||
|
// so we need to go through from the last one to the first one.
|
||||||
|
for i := len(series) - 1; i >= 0; i-- {
|
||||||
|
s := series[i]
|
||||||
|
for j := len(s.Points) - 1; j >= 0; j-- {
|
||||||
|
values := s.Points[j]
|
||||||
|
stats, err := self.valuesToContainerStats(s.Columns, values)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if stats == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
statsList = append(statsList, stats)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return statsList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *influxdbStorage) Samples(containerName string, numSamples int) ([]*info.ContainerStatsSample, error) {
|
||||||
|
// TODO(dengnan): select only columns that we need
|
||||||
|
// TODO(dengnan): escape names
|
||||||
|
query := fmt.Sprintf("select * from %v where %v='%v' and %v='%v'", self.tableName, colContainerName, containerName, colMachineName, self.machineName)
|
||||||
|
if numSamples > 0 {
|
||||||
|
query = fmt.Sprintf("%v limit %v", query, numSamples)
|
||||||
|
}
|
||||||
|
series, err := self.client.Query(query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sampleList := make([]*info.ContainerStatsSample, 0, len(series))
|
||||||
|
for i := len(series) - 1; i >= 0; i-- {
|
||||||
|
s := series[i]
|
||||||
|
for j := len(s.Points) - 1; j >= 0; j-- {
|
||||||
|
values := s.Points[j]
|
||||||
|
sample, err := self.valuesToContainerSample(s.Columns, values)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if sample == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sampleList = append(sampleList, sample)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sampleList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *influxdbStorage) Close() error {
|
||||||
|
self.client = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *influxdbStorage) Percentiles(
|
||||||
|
containerName string,
|
||||||
|
cpuUsagePercentiles []int,
|
||||||
|
memUsagePercentiles []int,
|
||||||
|
) (*info.ContainerStatsPercentiles, error) {
|
||||||
|
selectedCol := make([]string, 0, len(cpuUsagePercentiles)+len(memUsagePercentiles)+1)
|
||||||
|
|
||||||
|
selectedCol = append(selectedCol, fmt.Sprintf("max(%v)", colMemoryUsage))
|
||||||
|
for _, p := range cpuUsagePercentiles {
|
||||||
|
selectedCol = append(selectedCol, fmt.Sprintf("percentile(%v, %v)", colCpuInstantUsage, p))
|
||||||
|
}
|
||||||
|
for _, p := range memUsagePercentiles {
|
||||||
|
selectedCol = append(selectedCol, fmt.Sprintf("percentile(%v, %v)", colMemoryUsage, p))
|
||||||
|
}
|
||||||
|
|
||||||
|
query := fmt.Sprintf("select %v from %v where %v='%v' and %v='%v' and time > now() - %v",
|
||||||
|
strings.Join(selectedCol, ","),
|
||||||
|
self.tableName,
|
||||||
|
colContainerName,
|
||||||
|
containerName,
|
||||||
|
colMachineName,
|
||||||
|
self.machineName,
|
||||||
|
fmt.Sprintf("%vs", self.windowLen.Seconds()),
|
||||||
|
)
|
||||||
|
series, err := self.client.Query(query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(series) != 1 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if len(series[0].Points) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
point := series[0].Points[0]
|
||||||
|
|
||||||
|
ret := new(info.ContainerStatsPercentiles)
|
||||||
|
ret.MaxMemoryUsage, err = convertToUint64(point[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid max memory usage: %v", err)
|
||||||
|
}
|
||||||
|
retrievedCpuPercentiles := point[2 : 2+len(cpuUsagePercentiles)]
|
||||||
|
for i, p := range cpuUsagePercentiles {
|
||||||
|
v, err := convertToUint64(retrievedCpuPercentiles[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid cpu usage: %v", err)
|
||||||
|
}
|
||||||
|
ret.CpuUsagePercentiles = append(
|
||||||
|
ret.CpuUsagePercentiles,
|
||||||
|
info.Percentile{
|
||||||
|
Percentage: p,
|
||||||
|
Value: v,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
retrievedMemoryPercentiles := point[2+len(cpuUsagePercentiles):]
|
||||||
|
for i, p := range memUsagePercentiles {
|
||||||
|
v, err := convertToUint64(retrievedMemoryPercentiles[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid memory usage: %v", err)
|
||||||
|
}
|
||||||
|
ret.MemoryUsagePercentiles = append(
|
||||||
|
ret.MemoryUsagePercentiles,
|
||||||
|
info.Percentile{
|
||||||
|
Percentage: p,
|
||||||
|
Value: v,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// machineName: A unique identifier to identify the host that current cAdvisor
|
||||||
|
// instance is running on.
|
||||||
|
// influxdbHost: The host which runs influxdb.
|
||||||
|
// percentilesDuration: Time window which will be considered when calls Percentiles()
|
||||||
|
func New(machineName,
|
||||||
|
tablename,
|
||||||
|
database,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
influxdbHost string,
|
||||||
|
isSecure bool,
|
||||||
|
percentilesDuration time.Duration,
|
||||||
|
) (storage.StorageDriver, error) {
|
||||||
|
config := &influxdb.ClientConfig{
|
||||||
|
Host: influxdbHost,
|
||||||
|
Username: username,
|
||||||
|
Password: password,
|
||||||
|
Database: database,
|
||||||
|
IsSecure: isSecure,
|
||||||
|
}
|
||||||
|
client, err := influxdb.NewClient(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// TODO(monnand): With go 1.3, we cannot compress data now.
|
||||||
|
client.DisableCompression()
|
||||||
|
if percentilesDuration.Seconds() < 1.0 {
|
||||||
|
percentilesDuration = 5 * time.Minute
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := &influxdbStorage{
|
||||||
|
client: client,
|
||||||
|
windowLen: percentilesDuration,
|
||||||
|
machineName: machineName,
|
||||||
|
tableName: tablename,
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
136
third_party/src/github.com/google/cadvisor/storage/influxdb/influxdb_test.go
vendored
Normal file
136
third_party/src/github.com/google/cadvisor/storage/influxdb/influxdb_test.go
vendored
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
// 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 influxdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/cadvisor/storage"
|
||||||
|
"github.com/google/cadvisor/storage/test"
|
||||||
|
"github.com/influxdb/influxdb-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func runStorageTest(f func(storage.StorageDriver, *testing.T), t *testing.T) {
|
||||||
|
machineName := "machineA"
|
||||||
|
tablename := "t"
|
||||||
|
database := "cadvisor"
|
||||||
|
username := "root"
|
||||||
|
password := "root"
|
||||||
|
hostname := "localhost:8086"
|
||||||
|
percentilesDuration := 10 * time.Minute
|
||||||
|
rootConfig := &influxdb.ClientConfig{
|
||||||
|
Host: hostname,
|
||||||
|
Username: username,
|
||||||
|
Password: password,
|
||||||
|
IsSecure: false,
|
||||||
|
}
|
||||||
|
rootClient, err := influxdb.NewClient(rootConfig)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// create the data base first.
|
||||||
|
rootClient.CreateDatabase(database)
|
||||||
|
config := &influxdb.ClientConfig{
|
||||||
|
Host: hostname,
|
||||||
|
Username: username,
|
||||||
|
Password: password,
|
||||||
|
Database: database,
|
||||||
|
IsSecure: false,
|
||||||
|
}
|
||||||
|
client, err := influxdb.NewClient(config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
client.DisableCompression()
|
||||||
|
deleteAll := fmt.Sprintf("drop series %v", tablename)
|
||||||
|
_, err = client.Query(deleteAll)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// delete all data by the end of the call
|
||||||
|
defer client.Query(deleteAll)
|
||||||
|
|
||||||
|
driver, err := New(machineName,
|
||||||
|
tablename,
|
||||||
|
database,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
hostname,
|
||||||
|
false,
|
||||||
|
percentilesDuration)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// generate another container's data on same machine.
|
||||||
|
test.StorageDriverFillRandomStatsFunc("containerOnSameMachine", 100, driver, t)
|
||||||
|
|
||||||
|
// generate another container's data on another machine.
|
||||||
|
driverForAnotherMachine, err := New("machineB",
|
||||||
|
tablename,
|
||||||
|
database,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
hostname,
|
||||||
|
false,
|
||||||
|
percentilesDuration)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer driverForAnotherMachine.Close()
|
||||||
|
test.StorageDriverFillRandomStatsFunc("containerOnAnotherMachine", 100, driverForAnotherMachine, t)
|
||||||
|
f(driver, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSampleCpuUsage(t *testing.T) {
|
||||||
|
runStorageTest(test.StorageDriverTestSampleCpuUsage, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRetrievePartialRecentStats(t *testing.T) {
|
||||||
|
runStorageTest(test.StorageDriverTestRetrievePartialRecentStats, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSamplesWithoutSample(t *testing.T) {
|
||||||
|
runStorageTest(test.StorageDriverTestSamplesWithoutSample, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRetrieveAllRecentStats(t *testing.T) {
|
||||||
|
runStorageTest(test.StorageDriverTestRetrieveAllRecentStats, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoRecentStats(t *testing.T) {
|
||||||
|
runStorageTest(test.StorageDriverTestNoRecentStats, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoSamples(t *testing.T) {
|
||||||
|
runStorageTest(test.StorageDriverTestNoSamples, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPercentiles(t *testing.T) {
|
||||||
|
runStorageTest(test.StorageDriverTestPercentiles, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMaxMemoryUsage(t *testing.T) {
|
||||||
|
runStorageTest(test.StorageDriverTestMaxMemoryUsage, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPercentilesWithoutSample(t *testing.T) {
|
||||||
|
runStorageTest(test.StorageDriverTestPercentilesWithoutSample, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPercentilesWithoutStats(t *testing.T) {
|
||||||
|
runStorageTest(test.StorageDriverTestPercentilesWithoutStats, t)
|
||||||
|
}
|
@@ -41,20 +41,7 @@ func (self *containerStorage) updatePrevStats(stats *info.ContainerStats) {
|
|||||||
self.prevStats = nil
|
self.prevStats = nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if self.prevStats == nil {
|
self.prevStats = stats.Copy(self.prevStats)
|
||||||
self.prevStats = &info.ContainerStats{
|
|
||||||
Cpu: &info.CpuStats{},
|
|
||||||
Memory: &info.MemoryStats{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// make a deep copy.
|
|
||||||
self.prevStats.Timestamp = stats.Timestamp
|
|
||||||
*self.prevStats.Cpu = *stats.Cpu
|
|
||||||
self.prevStats.Cpu.Usage.PerCpu = make([]uint64, len(stats.Cpu.Usage.PerCpu))
|
|
||||||
for i, perCpu := range stats.Cpu.Usage.PerCpu {
|
|
||||||
self.prevStats.Cpu.Usage.PerCpu[i] = perCpu
|
|
||||||
}
|
|
||||||
*self.prevStats.Memory = *stats.Memory
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *containerStorage) AddStats(stats *info.ContainerStats) error {
|
func (self *containerStorage) AddStats(stats *info.ContainerStats) error {
|
||||||
@@ -75,9 +62,9 @@ func (self *containerStorage) AddStats(stats *info.ContainerStats) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if self.recentStats.Len() >= self.maxNumStats {
|
if self.recentStats.Len() >= self.maxNumStats {
|
||||||
self.recentStats.Remove(self.recentStats.Front())
|
self.recentStats.Remove(self.recentStats.Back())
|
||||||
}
|
}
|
||||||
self.recentStats.PushBack(stats)
|
self.recentStats.PushFront(stats)
|
||||||
self.updatePrevStats(stats)
|
self.updatePrevStats(stats)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -88,18 +75,25 @@ func (self *containerStorage) RecentStats(numStats int) ([]*info.ContainerStats,
|
|||||||
if self.recentStats.Len() < numStats || numStats < 0 {
|
if self.recentStats.Len() < numStats || numStats < 0 {
|
||||||
numStats = self.recentStats.Len()
|
numStats = self.recentStats.Len()
|
||||||
}
|
}
|
||||||
ret := make([]*info.ContainerStats, 0, numStats)
|
|
||||||
|
// Stats in the recentStats list are stored in reverse chronological
|
||||||
|
// order, i.e. most recent stats is in the front.
|
||||||
|
// numStats will always <= recentStats.Len() so that there will be
|
||||||
|
// always at least numStats available stats to retrieve. We traverse
|
||||||
|
// the recentStats list from its head and fill the ret slice in
|
||||||
|
// reverse order so that the returned slice will be in chronological
|
||||||
|
// order. The order of the returned slice is not specified by the
|
||||||
|
// StorageDriver interface, so it is not necessary for other storage
|
||||||
|
// drivers to return the slice in the same order.
|
||||||
|
ret := make([]*info.ContainerStats, numStats)
|
||||||
e := self.recentStats.Front()
|
e := self.recentStats.Front()
|
||||||
for i := 0; i < numStats; i++ {
|
for i := numStats - 1; i >= 0; i-- {
|
||||||
data, ok := e.Value.(*info.ContainerStats)
|
data, ok := e.Value.(*info.ContainerStats)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("The %vth element is not a ContainerStats", i)
|
return nil, fmt.Errorf("The %vth element is not a ContainerStats", i)
|
||||||
}
|
}
|
||||||
ret = append(ret, data)
|
ret[i] = data
|
||||||
e = e.Next()
|
e = e.Next()
|
||||||
if e == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
@@ -162,23 +156,34 @@ type InMemoryStorage struct {
|
|||||||
func (self *InMemoryStorage) AddStats(ref info.ContainerReference, stats *info.ContainerStats) error {
|
func (self *InMemoryStorage) AddStats(ref info.ContainerReference, stats *info.ContainerStats) error {
|
||||||
var cstore *containerStorage
|
var cstore *containerStorage
|
||||||
var ok bool
|
var ok bool
|
||||||
self.lock.Lock()
|
|
||||||
if cstore, ok = self.containerStorageMap[ref.Name]; !ok {
|
func() {
|
||||||
cstore = newContainerStore(ref, self.maxNumSamples, self.maxNumStats)
|
self.lock.Lock()
|
||||||
self.containerStorageMap[ref.Name] = cstore
|
defer self.lock.Unlock()
|
||||||
}
|
if cstore, ok = self.containerStorageMap[ref.Name]; !ok {
|
||||||
self.lock.Unlock()
|
cstore = newContainerStore(ref, self.maxNumSamples, self.maxNumStats)
|
||||||
|
self.containerStorageMap[ref.Name] = cstore
|
||||||
|
}
|
||||||
|
}()
|
||||||
return cstore.AddStats(stats)
|
return cstore.AddStats(stats)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *InMemoryStorage) Samples(name string, numSamples int) ([]*info.ContainerStatsSample, error) {
|
func (self *InMemoryStorage) Samples(name string, numSamples int) ([]*info.ContainerStatsSample, error) {
|
||||||
var cstore *containerStorage
|
var cstore *containerStorage
|
||||||
var ok bool
|
var ok bool
|
||||||
self.lock.RLock()
|
|
||||||
if cstore, ok = self.containerStorageMap[name]; !ok {
|
err := func() error {
|
||||||
return nil, fmt.Errorf("unable to find data for container %v", name)
|
self.lock.RLock()
|
||||||
|
defer self.lock.RUnlock()
|
||||||
|
if cstore, ok = self.containerStorageMap[name]; !ok {
|
||||||
|
return fmt.Errorf("unable to find data for container %v", name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
self.lock.RUnlock()
|
|
||||||
|
|
||||||
return cstore.Samples(numSamples)
|
return cstore.Samples(numSamples)
|
||||||
}
|
}
|
||||||
@@ -186,11 +191,17 @@ func (self *InMemoryStorage) Samples(name string, numSamples int) ([]*info.Conta
|
|||||||
func (self *InMemoryStorage) RecentStats(name string, numStats int) ([]*info.ContainerStats, error) {
|
func (self *InMemoryStorage) RecentStats(name string, numStats int) ([]*info.ContainerStats, error) {
|
||||||
var cstore *containerStorage
|
var cstore *containerStorage
|
||||||
var ok bool
|
var ok bool
|
||||||
self.lock.RLock()
|
err := func() error {
|
||||||
if cstore, ok = self.containerStorageMap[name]; !ok {
|
self.lock.RLock()
|
||||||
return nil, fmt.Errorf("unable to find data for container %v", name)
|
defer self.lock.RUnlock()
|
||||||
|
if cstore, ok = self.containerStorageMap[name]; !ok {
|
||||||
|
return fmt.Errorf("unable to find data for container %v", name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
self.lock.RUnlock()
|
|
||||||
|
|
||||||
return cstore.RecentStats(numStats)
|
return cstore.RecentStats(numStats)
|
||||||
}
|
}
|
||||||
@@ -198,11 +209,17 @@ func (self *InMemoryStorage) RecentStats(name string, numStats int) ([]*info.Con
|
|||||||
func (self *InMemoryStorage) Percentiles(name string, cpuPercentiles, memPercentiles []int) (*info.ContainerStatsPercentiles, error) {
|
func (self *InMemoryStorage) Percentiles(name string, cpuPercentiles, memPercentiles []int) (*info.ContainerStatsPercentiles, error) {
|
||||||
var cstore *containerStorage
|
var cstore *containerStorage
|
||||||
var ok bool
|
var ok bool
|
||||||
self.lock.RLock()
|
err := func() error {
|
||||||
if cstore, ok = self.containerStorageMap[name]; !ok {
|
self.lock.RLock()
|
||||||
return nil, fmt.Errorf("unable to find data for container %v", name)
|
defer self.lock.RUnlock()
|
||||||
|
if cstore, ok = self.containerStorageMap[name]; !ok {
|
||||||
|
return fmt.Errorf("unable to find data for container %v", name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
self.lock.RUnlock()
|
|
||||||
|
|
||||||
return cstore.Percentiles(cpuPercentiles, memPercentiles)
|
return cstore.Percentiles(cpuPercentiles, memPercentiles)
|
||||||
}
|
}
|
||||||
|
@@ -44,6 +44,32 @@ func TestSamplesWithoutSample(t *testing.T) {
|
|||||||
runStorageTest(test.StorageDriverTestSamplesWithoutSample, t)
|
runStorageTest(test.StorageDriverTestSamplesWithoutSample, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPercentilessWithoutSample(t *testing.T) {
|
func TestPercentilesWithoutSample(t *testing.T) {
|
||||||
runStorageTest(test.StorageDriverTestPercentilesWithoutSample, t)
|
runStorageTest(test.StorageDriverTestPercentilesWithoutSample, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPercentiles(t *testing.T) {
|
||||||
|
N := 100
|
||||||
|
driver := New(N, N)
|
||||||
|
test.StorageDriverTestPercentiles(driver, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRetrievePartialRecentStats(t *testing.T) {
|
||||||
|
runStorageTest(test.StorageDriverTestRetrievePartialRecentStats, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRetrieveAllRecentStats(t *testing.T) {
|
||||||
|
runStorageTest(test.StorageDriverTestRetrieveAllRecentStats, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoRecentStats(t *testing.T) {
|
||||||
|
runStorageTest(test.StorageDriverTestNoRecentStats, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoSamples(t *testing.T) {
|
||||||
|
runStorageTest(test.StorageDriverTestNoSamples, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPercentilesWithoutStats(t *testing.T) {
|
||||||
|
runStorageTest(test.StorageDriverTestPercentilesWithoutStats, t)
|
||||||
|
}
|
||||||
|
@@ -21,7 +21,9 @@ type StorageDriver interface {
|
|||||||
|
|
||||||
// Read most recent stats. numStats indicates max number of stats
|
// Read most recent stats. numStats indicates max number of stats
|
||||||
// returned. The returned stats must be consecutive observed stats. If
|
// returned. The returned stats must be consecutive observed stats. If
|
||||||
// numStats < 0, then return all stats stored in the storage.
|
// numStats < 0, then return all stats stored in the storage. The
|
||||||
|
// returned stats should be sorted in time increasing order, i.e. Most
|
||||||
|
// recent stats should be the last.
|
||||||
RecentStats(containerName string, numStats int) ([]*info.ContainerStats, error)
|
RecentStats(containerName string, numStats int) ([]*info.ContainerStats, error)
|
||||||
|
|
||||||
// Read the specified percentiles of CPU and memory usage of the container.
|
// Read the specified percentiles of CPU and memory usage of the container.
|
||||||
@@ -31,7 +33,7 @@ type StorageDriver interface {
|
|||||||
// Returns samples of the container stats. If numSamples < 0, then
|
// Returns samples of the container stats. If numSamples < 0, then
|
||||||
// the number of returned samples is implementation defined. Otherwise, the driver
|
// the number of returned samples is implementation defined. Otherwise, the driver
|
||||||
// should return at most numSamples samples.
|
// should return at most numSamples samples.
|
||||||
Samples(containername string, numSamples int) ([]*info.ContainerStatsSample, error)
|
Samples(containerName string, numSamples int) ([]*info.ContainerStatsSample, error)
|
||||||
|
|
||||||
// Close will clear the state of the storage driver. The elements
|
// Close will clear the state of the storage driver. The elements
|
||||||
// stored in the underlying storage may or may not be deleted depending
|
// stored in the underlying storage may or may not be deleted depending
|
||||||
|
57
third_party/src/github.com/google/cadvisor/storage/test/mock.go
vendored
Normal file
57
third_party/src/github.com/google/cadvisor/storage/test/mock.go
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
// 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 test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/cadvisor/info"
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MockStorageDriver struct {
|
||||||
|
mock.Mock
|
||||||
|
MockCloseMethod bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *MockStorageDriver) AddStats(ref info.ContainerReference, stats *info.ContainerStats) error {
|
||||||
|
args := self.Called(ref, stats)
|
||||||
|
return args.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *MockStorageDriver) RecentStats(containerName string, numStats int) ([]*info.ContainerStats, error) {
|
||||||
|
args := self.Called(containerName, numStats)
|
||||||
|
return args.Get(0).([]*info.ContainerStats), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *MockStorageDriver) Percentiles(
|
||||||
|
containerName string,
|
||||||
|
cpuUsagePercentiles []int,
|
||||||
|
memUsagePercentiles []int,
|
||||||
|
) (*info.ContainerStatsPercentiles, error) {
|
||||||
|
args := self.Called(containerName, cpuUsagePercentiles, memUsagePercentiles)
|
||||||
|
return args.Get(0).(*info.ContainerStatsPercentiles), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *MockStorageDriver) Samples(containerName string, numSamples int) ([]*info.ContainerStatsSample, error) {
|
||||||
|
args := self.Called(containerName, numSamples)
|
||||||
|
return args.Get(0).([]*info.ContainerStatsSample), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *MockStorageDriver) Close() error {
|
||||||
|
if self.MockCloseMethod {
|
||||||
|
args := self.Called()
|
||||||
|
return args.Error(0)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@@ -16,6 +16,7 @@ package test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -43,6 +44,7 @@ func buildTrace(cpu, mem []uint64, duration time.Duration) []*info.ContainerStat
|
|||||||
stats.Cpu.Usage.Total = cpuTotalUsage
|
stats.Cpu.Usage.Total = cpuTotalUsage
|
||||||
stats.Cpu.Usage.User = stats.Cpu.Usage.Total
|
stats.Cpu.Usage.User = stats.Cpu.Usage.Total
|
||||||
stats.Cpu.Usage.System = 0
|
stats.Cpu.Usage.System = 0
|
||||||
|
stats.Cpu.Usage.PerCpu = []uint64{cpuTotalUsage}
|
||||||
|
|
||||||
stats.Memory.Usage = mem[i]
|
stats.Memory.Usage = mem[i]
|
||||||
|
|
||||||
@@ -51,10 +53,133 @@ func buildTrace(cpu, mem []uint64, duration time.Duration) []*info.ContainerStat
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// The underlying driver must be able to hold more than 10 samples.
|
func timeEq(t1, t2 time.Time, tolerance time.Duration) bool {
|
||||||
|
// t1 should not be later than t2
|
||||||
|
if t1.After(t2) {
|
||||||
|
t1, t2 = t2, t1
|
||||||
|
}
|
||||||
|
diff := t2.Sub(t1)
|
||||||
|
if diff <= tolerance {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func durationEq(a, b time.Duration, tolerance time.Duration) bool {
|
||||||
|
if a > b {
|
||||||
|
a, b = b, a
|
||||||
|
}
|
||||||
|
diff := a - b
|
||||||
|
if diff <= tolerance {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// 10ms, i.e. 0.01s
|
||||||
|
timePrecision time.Duration = 10 * time.Millisecond
|
||||||
|
)
|
||||||
|
|
||||||
|
// This function is useful because we do not require precise time
|
||||||
|
// representation.
|
||||||
|
func statsEq(a, b *info.ContainerStats) bool {
|
||||||
|
if !timeEq(a.Timestamp, b.Timestamp, timePrecision) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(a.Cpu, b.Cpu) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(a.Memory, b.Memory) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is useful because we do not require precise time
|
||||||
|
// representation.
|
||||||
|
func sampleEq(a, b *info.ContainerStatsSample) bool {
|
||||||
|
if !timeEq(a.Timestamp, b.Timestamp, timePrecision) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !durationEq(a.Duration, b.Duration, timePrecision) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(a.Cpu, b.Cpu) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(a.Memory, b.Memory) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func samplesInTrace(samples []*info.ContainerStatsSample, cpuTrace, memTrace []uint64, samplePeriod time.Duration, t *testing.T) {
|
||||||
|
for _, sample := range samples {
|
||||||
|
if sample.Duration != samplePeriod {
|
||||||
|
t.Errorf("sample duration is %v, not %v", sample.Duration, samplePeriod)
|
||||||
|
}
|
||||||
|
cpuUsage := sample.Cpu.Usage
|
||||||
|
memUsage := sample.Memory.Usage
|
||||||
|
found := false
|
||||||
|
for _, u := range cpuTrace {
|
||||||
|
if u == cpuUsage {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Errorf("unable to find cpu usage %v", cpuUsage)
|
||||||
|
}
|
||||||
|
found = false
|
||||||
|
for _, u := range memTrace {
|
||||||
|
if u == memUsage {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Errorf("unable to find mem usage %v", memUsage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function will generate random stats and write
|
||||||
|
// them into the storage. The function will not close the driver
|
||||||
|
func StorageDriverFillRandomStatsFunc(
|
||||||
|
containerName string,
|
||||||
|
N int,
|
||||||
|
driver storage.StorageDriver,
|
||||||
|
t *testing.T,
|
||||||
|
) {
|
||||||
|
cpuTrace := make([]uint64, 0, N)
|
||||||
|
memTrace := make([]uint64, 0, N)
|
||||||
|
|
||||||
|
// We need N+1 observations to get N samples
|
||||||
|
for i := 0; i < N+1; i++ {
|
||||||
|
cpuTrace = append(cpuTrace, uint64(rand.Intn(1000)))
|
||||||
|
memTrace = append(memTrace, uint64(rand.Intn(1000)))
|
||||||
|
}
|
||||||
|
|
||||||
|
samplePeriod := 1 * time.Second
|
||||||
|
|
||||||
|
ref := info.ContainerReference{
|
||||||
|
Name: containerName,
|
||||||
|
}
|
||||||
|
|
||||||
|
trace := buildTrace(cpuTrace, memTrace, samplePeriod)
|
||||||
|
|
||||||
|
for _, stats := range trace {
|
||||||
|
err := driver.AddStats(ref, stats)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to add stats: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func StorageDriverTestSampleCpuUsage(driver storage.StorageDriver, t *testing.T) {
|
func StorageDriverTestSampleCpuUsage(driver storage.StorageDriver, t *testing.T) {
|
||||||
defer driver.Close()
|
defer driver.Close()
|
||||||
N := 10
|
N := 100
|
||||||
cpuTrace := make([]uint64, 0, N)
|
cpuTrace := make([]uint64, 0, N)
|
||||||
memTrace := make([]uint64, 0, N)
|
memTrace := make([]uint64, 0, N)
|
||||||
|
|
||||||
@@ -73,28 +198,37 @@ func StorageDriverTestSampleCpuUsage(driver storage.StorageDriver, t *testing.T)
|
|||||||
trace := buildTrace(cpuTrace, memTrace, samplePeriod)
|
trace := buildTrace(cpuTrace, memTrace, samplePeriod)
|
||||||
|
|
||||||
for _, stats := range trace {
|
for _, stats := range trace {
|
||||||
driver.AddStats(ref, stats)
|
err := driver.AddStats(ref, stats)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to add stats: %v", err)
|
||||||
|
}
|
||||||
|
// set the trace to something else. The stats stored in the
|
||||||
|
// storage should not be affected.
|
||||||
|
stats.Cpu.Usage.Total = 0
|
||||||
|
stats.Cpu.Usage.System = 0
|
||||||
|
stats.Cpu.Usage.User = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
samples, err := driver.Samples(ref.Name, N)
|
samples, err := driver.Samples(ref.Name, N)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unable to sample stats: %v", err)
|
t.Errorf("unable to sample stats: %v", err)
|
||||||
}
|
}
|
||||||
for _, sample := range samples {
|
if len(samples) == 0 {
|
||||||
if sample.Duration != samplePeriod {
|
t.Fatal("should at least store one sample")
|
||||||
t.Errorf("sample duration is %v, not %v", sample.Duration, samplePeriod)
|
|
||||||
}
|
|
||||||
cpuUsage := sample.Cpu.Usage
|
|
||||||
found := false
|
|
||||||
for _, u := range cpuTrace {
|
|
||||||
if u == cpuUsage {
|
|
||||||
found = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
t.Errorf("unable to find cpu usage %v", cpuUsage)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
samplesInTrace(samples, cpuTrace, memTrace, samplePeriod, t)
|
||||||
|
|
||||||
|
samples, err = driver.Samples(ref.Name, -1)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unable to sample stats: %v", err)
|
||||||
|
}
|
||||||
|
samplesInTrace(samples, cpuTrace, memTrace, samplePeriod, t)
|
||||||
|
|
||||||
|
samples, err = driver.Samples(ref.Name, N-5)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unable to sample stats: %v", err)
|
||||||
|
}
|
||||||
|
samplesInTrace(samples, cpuTrace, memTrace, samplePeriod, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func StorageDriverTestMaxMemoryUsage(driver storage.StorageDriver, t *testing.T) {
|
func StorageDriverTestMaxMemoryUsage(driver storage.StorageDriver, t *testing.T) {
|
||||||
@@ -114,7 +248,16 @@ func StorageDriverTestMaxMemoryUsage(driver storage.StorageDriver, t *testing.T)
|
|||||||
trace := buildTrace(cpuTrace, memTrace, 1*time.Second)
|
trace := buildTrace(cpuTrace, memTrace, 1*time.Second)
|
||||||
|
|
||||||
for _, stats := range trace {
|
for _, stats := range trace {
|
||||||
driver.AddStats(ref, stats)
|
err := driver.AddStats(ref, stats)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to add stats: %v", err)
|
||||||
|
}
|
||||||
|
// set the trace to something else. The stats stored in the
|
||||||
|
// storage should not be affected.
|
||||||
|
stats.Cpu.Usage.Total = 0
|
||||||
|
stats.Cpu.Usage.System = 0
|
||||||
|
stats.Cpu.Usage.User = 0
|
||||||
|
stats.Memory.Usage = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
percentiles, err := driver.Percentiles(ref.Name, []int{50}, []int{50})
|
percentiles, err := driver.Percentiles(ref.Name, []int{50}, []int{50})
|
||||||
@@ -168,3 +311,174 @@ func StorageDriverTestPercentilesWithoutSample(driver storage.StorageDriver, t *
|
|||||||
t.Errorf("There should be no percentiles")
|
t.Errorf("There should be no percentiles")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func StorageDriverTestPercentiles(driver storage.StorageDriver, t *testing.T) {
|
||||||
|
defer driver.Close()
|
||||||
|
N := 100
|
||||||
|
cpuTrace := make([]uint64, N)
|
||||||
|
memTrace := make([]uint64, N)
|
||||||
|
for i := 1; i < N+1; i++ {
|
||||||
|
cpuTrace[i-1] = uint64(i)
|
||||||
|
memTrace[i-1] = uint64(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
trace := buildTrace(cpuTrace, memTrace, 1*time.Second)
|
||||||
|
|
||||||
|
ref := info.ContainerReference{
|
||||||
|
Name: "container",
|
||||||
|
}
|
||||||
|
for _, stats := range trace {
|
||||||
|
driver.AddStats(ref, stats)
|
||||||
|
}
|
||||||
|
percentages := []int{
|
||||||
|
80,
|
||||||
|
90,
|
||||||
|
50,
|
||||||
|
}
|
||||||
|
percentiles, err := driver.Percentiles(ref.Name, percentages, percentages)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, x := range percentiles.CpuUsagePercentiles {
|
||||||
|
for _, y := range percentiles.CpuUsagePercentiles {
|
||||||
|
// lower percentage, smaller value
|
||||||
|
if x.Percentage < y.Percentage && x.Value > y.Value {
|
||||||
|
t.Errorf("%v percent is %v; while %v percent is %v",
|
||||||
|
x.Percentage, x.Value, y.Percentage, y.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, x := range percentiles.MemoryUsagePercentiles {
|
||||||
|
for _, y := range percentiles.MemoryUsagePercentiles {
|
||||||
|
if x.Percentage < y.Percentage && x.Value > y.Value {
|
||||||
|
t.Errorf("%v percent is %v; while %v percent is %v",
|
||||||
|
x.Percentage, x.Value, y.Percentage, y.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func StorageDriverTestRetrievePartialRecentStats(driver storage.StorageDriver, t *testing.T) {
|
||||||
|
defer driver.Close()
|
||||||
|
N := 100
|
||||||
|
memTrace := make([]uint64, N)
|
||||||
|
cpuTrace := make([]uint64, N)
|
||||||
|
for i := 0; i < N; i++ {
|
||||||
|
memTrace[i] = uint64(i + 1)
|
||||||
|
cpuTrace[i] = uint64(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
ref := info.ContainerReference{
|
||||||
|
Name: "container",
|
||||||
|
}
|
||||||
|
|
||||||
|
trace := buildTrace(cpuTrace, memTrace, 1*time.Second)
|
||||||
|
|
||||||
|
for _, stats := range trace {
|
||||||
|
driver.AddStats(ref, stats)
|
||||||
|
}
|
||||||
|
|
||||||
|
recentStats, err := driver.RecentStats(ref.Name, 10)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(recentStats) == 0 {
|
||||||
|
t.Fatal("should at least store one stats")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(recentStats) > 10 {
|
||||||
|
t.Fatalf("returned %v stats, not 10.", len(recentStats))
|
||||||
|
}
|
||||||
|
|
||||||
|
actualRecentStats := trace[len(trace)-len(recentStats):]
|
||||||
|
|
||||||
|
// The returned stats should be sorted in time increasing order
|
||||||
|
for i, s := range actualRecentStats {
|
||||||
|
r := recentStats[i]
|
||||||
|
if !statsEq(s, r) {
|
||||||
|
t.Errorf("unexpected stats %+v with memory usage %v", r, r.Memory.Usage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func StorageDriverTestRetrieveAllRecentStats(driver storage.StorageDriver, t *testing.T) {
|
||||||
|
defer driver.Close()
|
||||||
|
N := 100
|
||||||
|
memTrace := make([]uint64, N)
|
||||||
|
cpuTrace := make([]uint64, N)
|
||||||
|
for i := 0; i < N; i++ {
|
||||||
|
memTrace[i] = uint64(i + 1)
|
||||||
|
cpuTrace[i] = uint64(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
ref := info.ContainerReference{
|
||||||
|
Name: "container",
|
||||||
|
}
|
||||||
|
|
||||||
|
trace := buildTrace(cpuTrace, memTrace, 1*time.Second)
|
||||||
|
|
||||||
|
for _, stats := range trace {
|
||||||
|
driver.AddStats(ref, stats)
|
||||||
|
}
|
||||||
|
|
||||||
|
recentStats, err := driver.RecentStats(ref.Name, -1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(recentStats) == 0 {
|
||||||
|
t.Fatal("should at least store one stats")
|
||||||
|
}
|
||||||
|
if len(recentStats) > N {
|
||||||
|
t.Fatalf("returned %v stats, not 100.", len(recentStats))
|
||||||
|
}
|
||||||
|
|
||||||
|
actualRecentStats := trace[len(trace)-len(recentStats):]
|
||||||
|
|
||||||
|
// The returned stats should be sorted in time increasing order
|
||||||
|
for i, s := range actualRecentStats {
|
||||||
|
r := recentStats[i]
|
||||||
|
if !statsEq(s, r) {
|
||||||
|
t.Errorf("unexpected stats %+v with memory usage %v", r, r.Memory.Usage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func StorageDriverTestNoRecentStats(driver storage.StorageDriver, t *testing.T) {
|
||||||
|
defer driver.Close()
|
||||||
|
nonExistContainer := "somerandomecontainer"
|
||||||
|
stats, _ := driver.RecentStats(nonExistContainer, -1)
|
||||||
|
if len(stats) > 0 {
|
||||||
|
t.Errorf("RecentStats() returns %v stats on non exist container", len(stats))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func StorageDriverTestNoSamples(driver storage.StorageDriver, t *testing.T) {
|
||||||
|
defer driver.Close()
|
||||||
|
nonExistContainer := "somerandomecontainer"
|
||||||
|
samples, _ := driver.Samples(nonExistContainer, -1)
|
||||||
|
if len(samples) > 0 {
|
||||||
|
t.Errorf("Samples() returns %v samples on non exist container", len(samples))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func StorageDriverTestPercentilesWithoutStats(driver storage.StorageDriver, t *testing.T) {
|
||||||
|
defer driver.Close()
|
||||||
|
nonExistContainer := "somerandomecontainer"
|
||||||
|
percentiles, _ := driver.Percentiles(nonExistContainer, []int{50, 80}, []int{50, 80})
|
||||||
|
if percentiles == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if percentiles.MaxMemoryUsage != 0 {
|
||||||
|
t.Errorf("Percentiles() reports max memory usage > 0 when there's no stats.")
|
||||||
|
}
|
||||||
|
for _, p := range percentiles.CpuUsagePercentiles {
|
||||||
|
if p.Value != 0 {
|
||||||
|
t.Errorf("Percentiles() reports cpu usage is %v when there's no stats.", p.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, p := range percentiles.MemoryUsagePercentiles {
|
||||||
|
if p.Value != 0 {
|
||||||
|
t.Errorf("Percentiles() reports memory usage is %v when there's no stats.", p.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
71
third_party/src/github.com/google/cadvisor/storagedriver.go
vendored
Normal file
71
third_party/src/github.com/google/cadvisor/storagedriver.go
vendored
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
// 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/cadvisor/storage"
|
||||||
|
"github.com/google/cadvisor/storage/influxdb"
|
||||||
|
"github.com/google/cadvisor/storage/memory"
|
||||||
|
)
|
||||||
|
|
||||||
|
var argSampleSize = flag.Int("samples", 1024, "number of samples we want to keep")
|
||||||
|
var argHistoryDuration = flag.Int("history_duration", 60, "number of seconds of container history to keep")
|
||||||
|
var argDbUsername = flag.String("storage_driver_user", "root", "database username")
|
||||||
|
var argDbPassword = flag.String("storage_driver_password", "root", "database password")
|
||||||
|
var argDbHost = flag.String("storage_driver_host", "localhost:8086", "database host:port")
|
||||||
|
var argDbName = flag.String("storage_driver_name", "cadvisor", "database name")
|
||||||
|
var argDbIsSecure = flag.Bool("storage_driver_secure", false, "use secure connection with database")
|
||||||
|
|
||||||
|
func NewStorageDriver(driverName string) (storage.StorageDriver, error) {
|
||||||
|
var storageDriver storage.StorageDriver
|
||||||
|
var err error
|
||||||
|
switch driverName {
|
||||||
|
case "":
|
||||||
|
// empty string by default is the in memory store
|
||||||
|
fallthrough
|
||||||
|
case "memory":
|
||||||
|
storageDriver = memory.New(*argSampleSize, *argHistoryDuration)
|
||||||
|
return storageDriver, nil
|
||||||
|
case "influxdb":
|
||||||
|
var hostname string
|
||||||
|
hostname, err = os.Hostname()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
storageDriver, err = influxdb.New(
|
||||||
|
hostname,
|
||||||
|
"cadvisorTable",
|
||||||
|
*argDbName,
|
||||||
|
*argDbUsername,
|
||||||
|
*argDbPassword,
|
||||||
|
*argDbHost,
|
||||||
|
*argDbIsSecure,
|
||||||
|
// TODO(monnand): One hour? Or user-defined?
|
||||||
|
1*time.Hour,
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("Unknown database driver: %v", *argDbDriver)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return storageDriver, nil
|
||||||
|
}
|
Reference in New Issue
Block a user