From c4eacd3b76b006c3478c790d2c1d6fadd6305945 Mon Sep 17 00:00:00 2001 From: "Tim St. Clair" Date: Fri, 15 Apr 2016 11:46:21 -0700 Subject: [PATCH] Update cadvisor godeps --- Godeps/Godeps.json | 72 ++-- .../{raw => common}/container_hints.go | 10 +- .../container/{docker => common}/fsHandler.go | 20 +- .../cadvisor/container/common/helpers.go | 203 ++++++++++ .../{raw => common}/inotify_watcher.go | 2 +- .../test_resources/container_hints.json | 0 .../cadvisor/container/docker/factory.go | 41 +- .../cadvisor/container/docker/handler.go | 134 +++---- .../container/libcontainer/compatibility.go | 367 ------------------ .../google/cadvisor/container/raw/factory.go | 20 +- .../google/cadvisor/container/raw/handler.go | 182 +-------- .../google/cadvisor/container/rkt/client.go | 76 ++++ .../google/cadvisor/container/rkt/factory.go | 104 +++++ .../google/cadvisor/container/rkt/handler.go | 327 ++++++++++++++++ .../google/cadvisor/container/rkt/helpers.go | 93 +++++ .../cadvisor/container/systemd/factory.go | 57 +++ .../src/github.com/google/cadvisor/fs/fs.go | 129 +++--- .../google/cadvisor/manager/manager.go | 94 +++-- .../cadvisor/utils/cpuload/netlink/netlink.go | 7 +- .../cadvisor/utils/cpuload/netlink/reader.go | 3 +- .../google/cadvisor/validate/validate.go | 33 +- .../google/cadvisor/version/VERSION | 2 +- 22 files changed, 1134 insertions(+), 842 deletions(-) rename Godeps/_workspace/src/github.com/google/cadvisor/container/{raw => common}/container_hints.go (88%) rename Godeps/_workspace/src/github.com/google/cadvisor/container/{docker => common}/fsHandler.go (87%) create mode 100644 Godeps/_workspace/src/github.com/google/cadvisor/container/common/helpers.go rename Godeps/_workspace/src/github.com/google/cadvisor/container/{raw => common}/inotify_watcher.go (99%) rename Godeps/_workspace/src/github.com/google/cadvisor/container/{raw => common}/test_resources/container_hints.json (100%) delete mode 100644 Godeps/_workspace/src/github.com/google/cadvisor/container/libcontainer/compatibility.go create mode 100644 Godeps/_workspace/src/github.com/google/cadvisor/container/rkt/client.go create mode 100644 Godeps/_workspace/src/github.com/google/cadvisor/container/rkt/factory.go create mode 100644 Godeps/_workspace/src/github.com/google/cadvisor/container/rkt/handler.go create mode 100644 Godeps/_workspace/src/github.com/google/cadvisor/container/rkt/helpers.go create mode 100644 Godeps/_workspace/src/github.com/google/cadvisor/container/systemd/factory.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 0fb79085af2..3dc6c7d7bed 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -663,93 +663,93 @@ }, { "ImportPath": "github.com/google/cadvisor/api", - "Comment": "v0.22.2", - "Rev": "546a3771589bdb356777c646c6eca24914fdd48b" + "Comment": "v0.23.0", + "Rev": "750f18e5eac3f6193b354fc14c03d92d4318a0ec" }, { "ImportPath": "github.com/google/cadvisor/cache/memory", - "Comment": "v0.22.2", - "Rev": "546a3771589bdb356777c646c6eca24914fdd48b" + "Comment": "v0.23.0", + "Rev": "750f18e5eac3f6193b354fc14c03d92d4318a0ec" }, { "ImportPath": "github.com/google/cadvisor/collector", - "Comment": "v0.22.2", - "Rev": "546a3771589bdb356777c646c6eca24914fdd48b" + "Comment": "v0.23.0", + "Rev": "750f18e5eac3f6193b354fc14c03d92d4318a0ec" }, { "ImportPath": "github.com/google/cadvisor/container", - "Comment": "v0.22.2", - "Rev": "546a3771589bdb356777c646c6eca24914fdd48b" + "Comment": "v0.23.0", + "Rev": "750f18e5eac3f6193b354fc14c03d92d4318a0ec" }, { "ImportPath": "github.com/google/cadvisor/events", - "Comment": "v0.22.2", - "Rev": "546a3771589bdb356777c646c6eca24914fdd48b" + "Comment": "v0.23.0", + "Rev": "750f18e5eac3f6193b354fc14c03d92d4318a0ec" }, { "ImportPath": "github.com/google/cadvisor/fs", - "Comment": "v0.22.2", - "Rev": "546a3771589bdb356777c646c6eca24914fdd48b" + "Comment": "v0.23.0", + "Rev": "750f18e5eac3f6193b354fc14c03d92d4318a0ec" }, { "ImportPath": "github.com/google/cadvisor/healthz", - "Comment": "v0.22.2", - "Rev": "546a3771589bdb356777c646c6eca24914fdd48b" + "Comment": "v0.23.0", + "Rev": "750f18e5eac3f6193b354fc14c03d92d4318a0ec" }, { "ImportPath": "github.com/google/cadvisor/http", - "Comment": "v0.22.2", - "Rev": "546a3771589bdb356777c646c6eca24914fdd48b" + "Comment": "v0.23.0", + "Rev": "750f18e5eac3f6193b354fc14c03d92d4318a0ec" }, { "ImportPath": "github.com/google/cadvisor/info/v1", - "Comment": "v0.22.2", - "Rev": "546a3771589bdb356777c646c6eca24914fdd48b" + "Comment": "v0.23.0", + "Rev": "750f18e5eac3f6193b354fc14c03d92d4318a0ec" }, { "ImportPath": "github.com/google/cadvisor/info/v2", - "Comment": "v0.22.2", - "Rev": "546a3771589bdb356777c646c6eca24914fdd48b" + "Comment": "v0.23.0", + "Rev": "750f18e5eac3f6193b354fc14c03d92d4318a0ec" }, { "ImportPath": "github.com/google/cadvisor/manager", - "Comment": "v0.22.2", - "Rev": "546a3771589bdb356777c646c6eca24914fdd48b" + "Comment": "v0.23.0", + "Rev": "750f18e5eac3f6193b354fc14c03d92d4318a0ec" }, { "ImportPath": "github.com/google/cadvisor/metrics", - "Comment": "v0.22.2", - "Rev": "546a3771589bdb356777c646c6eca24914fdd48b" + "Comment": "v0.23.0", + "Rev": "750f18e5eac3f6193b354fc14c03d92d4318a0ec" }, { "ImportPath": "github.com/google/cadvisor/pages", - "Comment": "v0.22.2", - "Rev": "546a3771589bdb356777c646c6eca24914fdd48b" + "Comment": "v0.23.0", + "Rev": "750f18e5eac3f6193b354fc14c03d92d4318a0ec" }, { "ImportPath": "github.com/google/cadvisor/storage", - "Comment": "v0.22.2", - "Rev": "546a3771589bdb356777c646c6eca24914fdd48b" + "Comment": "v0.23.0", + "Rev": "750f18e5eac3f6193b354fc14c03d92d4318a0ec" }, { "ImportPath": "github.com/google/cadvisor/summary", - "Comment": "v0.22.2", - "Rev": "546a3771589bdb356777c646c6eca24914fdd48b" + "Comment": "v0.23.0", + "Rev": "750f18e5eac3f6193b354fc14c03d92d4318a0ec" }, { "ImportPath": "github.com/google/cadvisor/utils", - "Comment": "v0.22.2", - "Rev": "546a3771589bdb356777c646c6eca24914fdd48b" + "Comment": "v0.23.0", + "Rev": "750f18e5eac3f6193b354fc14c03d92d4318a0ec" }, { "ImportPath": "github.com/google/cadvisor/validate", - "Comment": "v0.22.2", - "Rev": "546a3771589bdb356777c646c6eca24914fdd48b" + "Comment": "v0.23.0", + "Rev": "750f18e5eac3f6193b354fc14c03d92d4318a0ec" }, { "ImportPath": "github.com/google/cadvisor/version", - "Comment": "v0.22.2", - "Rev": "546a3771589bdb356777c646c6eca24914fdd48b" + "Comment": "v0.23.0", + "Rev": "750f18e5eac3f6193b354fc14c03d92d4318a0ec" }, { "ImportPath": "github.com/google/gofuzz", diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/container/raw/container_hints.go b/Godeps/_workspace/src/github.com/google/cadvisor/container/common/container_hints.go similarity index 88% rename from Godeps/_workspace/src/github.com/google/cadvisor/container/raw/container_hints.go rename to Godeps/_workspace/src/github.com/google/cadvisor/container/common/container_hints.go index e1d90233e83..08e32832bc3 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/container/raw/container_hints.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/container/common/container_hints.go @@ -16,7 +16,7 @@ // an array of ContainerHint structs, each with a container's id and networkInterface // This allows collecting stats about network interfaces configured outside docker // and lxc -package raw +package common import ( "encoding/json" @@ -25,7 +25,7 @@ import ( "os" ) -var argContainerHints = flag.String("container_hints", "/etc/cadvisor/container_hints.json", "location of the container hints file") +var ArgContainerHints = flag.String("container_hints", "/etc/cadvisor/container_hints.json", "location of the container hints file") type containerHints struct { AllHosts []containerHint `json:"all_hosts,omitempty"` @@ -34,10 +34,10 @@ type containerHints struct { type containerHint struct { FullName string `json:"full_path,omitempty"` NetworkInterface *networkInterface `json:"network_interface,omitempty"` - Mounts []mount `json:"mounts,omitempty"` + Mounts []Mount `json:"mounts,omitempty"` } -type mount struct { +type Mount struct { HostDir string `json:"host_dir,omitempty"` ContainerDir string `json:"container_dir,omitempty"` } @@ -47,7 +47,7 @@ type networkInterface struct { VethChild string `json:"veth_child,omitempty"` } -func getContainerHintsFromFile(containerHintsFile string) (containerHints, error) { +func GetContainerHintsFromFile(containerHintsFile string) (containerHints, error) { dat, err := ioutil.ReadFile(containerHintsFile) if os.IsNotExist(err) { return containerHints{}, nil diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/container/docker/fsHandler.go b/Godeps/_workspace/src/github.com/google/cadvisor/container/common/fsHandler.go similarity index 87% rename from Godeps/_workspace/src/github.com/google/cadvisor/container/docker/fsHandler.go rename to Godeps/_workspace/src/github.com/google/cadvisor/container/common/fsHandler.go index c5b406a424e..ec2e76affe4 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/container/docker/fsHandler.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/container/common/fsHandler.go @@ -13,7 +13,7 @@ // limitations under the License. // Handler for Docker containers. -package docker +package common import ( "sync" @@ -24,10 +24,10 @@ import ( "github.com/golang/glog" ) -type fsHandler interface { - start() - usage() (uint64, uint64) - stop() +type FsHandler interface { + Start() + Usage() (baseUsageBytes uint64, totalUsageBytes uint64) + Stop() } type realFsHandler struct { @@ -50,9 +50,9 @@ const ( maxDuBackoffFactor = 20 ) -var _ fsHandler = &realFsHandler{} +var _ FsHandler = &realFsHandler{} -func newFsHandler(period time.Duration, rootfs, extraDir string, fsInfo fs.FsInfo) fsHandler { +func NewFsHandler(period time.Duration, rootfs, extraDir string, fsInfo fs.FsInfo) FsHandler { return &realFsHandler{ lastUpdate: time.Time{}, usageBytes: 0, @@ -119,15 +119,15 @@ func (fh *realFsHandler) trackUsage() { } } -func (fh *realFsHandler) start() { +func (fh *realFsHandler) Start() { go fh.trackUsage() } -func (fh *realFsHandler) stop() { +func (fh *realFsHandler) Stop() { close(fh.stopChan) } -func (fh *realFsHandler) usage() (baseUsageBytes, totalUsageBytes uint64) { +func (fh *realFsHandler) Usage() (baseUsageBytes, totalUsageBytes uint64) { fh.RLock() defer fh.RUnlock() return fh.baseUsageBytes, fh.usageBytes diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/container/common/helpers.go b/Godeps/_workspace/src/github.com/google/cadvisor/container/common/helpers.go new file mode 100644 index 00000000000..ac00b2ca45b --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/cadvisor/container/common/helpers.go @@ -0,0 +1,203 @@ +// Copyright 2016 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 common + +import ( + "fmt" + "io/ioutil" + "os" + "path" + "strconv" + "strings" + "time" + + info "github.com/google/cadvisor/info/v1" + "github.com/google/cadvisor/utils" + + "github.com/golang/glog" +) + +func DebugInfo(watches map[string][]string) map[string][]string { + out := make(map[string][]string) + + lines := make([]string, 0, len(watches)) + for containerName, cgroupWatches := range watches { + lines = append(lines, fmt.Sprintf("%s:", containerName)) + for _, cg := range cgroupWatches { + lines = append(lines, fmt.Sprintf("\t%s", cg)) + } + } + out["Inotify watches"] = lines + + return out +} + +func GetSpec(cgroupPaths map[string]string, machineInfoFactory info.MachineInfoFactory, hasNetwork, hasFilesystem bool) (info.ContainerSpec, error) { + var spec info.ContainerSpec + + // Assume unified hierarchy containers. + // Get the lowest creation time from all hierarchies as the container creation time. + now := time.Now() + lowestTime := now + for _, cgroupPath := range cgroupPaths { + // The modified time of the cgroup directory changes whenever a subcontainer is created. + // eg. /docker will have creation time matching the creation of latest docker container. + // Use clone_children as a workaround as it isn't usually modified. It is only likely changed + // immediately after creating a container. + cgroupPath = path.Join(cgroupPath, "cgroup.clone_children") + fi, err := os.Stat(cgroupPath) + if err == nil && fi.ModTime().Before(lowestTime) { + lowestTime = fi.ModTime() + } + } + if lowestTime != now { + spec.CreationTime = lowestTime + } + + // Get machine info. + mi, err := machineInfoFactory.GetMachineInfo() + if err != nil { + return spec, err + } + + // CPU. + cpuRoot, ok := cgroupPaths["cpu"] + if ok { + if utils.FileExists(cpuRoot) { + spec.HasCpu = true + spec.Cpu.Limit = readUInt64(cpuRoot, "cpu.shares") + spec.Cpu.Period = readUInt64(cpuRoot, "cpu.cfs_period_us") + quota := readString(cpuRoot, "cpu.cfs_quota_us") + + if quota != "" && quota != "-1" { + val, err := strconv.ParseUint(quota, 10, 64) + if err != nil { + glog.Errorf("GetSpec: Failed to parse CPUQuota from %q: %s", path.Join(cpuRoot, "cpu.cfs_quota_us"), err) + } + spec.Cpu.Quota = val + } + } + } + + // Cpu Mask. + // This will fail for non-unified hierarchies. We'll return the whole machine mask in that case. + cpusetRoot, ok := cgroupPaths["cpuset"] + if ok { + if utils.FileExists(cpusetRoot) { + spec.HasCpu = true + mask := readString(cpusetRoot, "cpuset.cpus") + spec.Cpu.Mask = utils.FixCpuMask(mask, mi.NumCores) + } + } + + // Memory + memoryRoot, ok := cgroupPaths["memory"] + if ok { + if utils.FileExists(memoryRoot) { + spec.HasMemory = true + spec.Memory.Limit = readUInt64(memoryRoot, "memory.limit_in_bytes") + spec.Memory.SwapLimit = readUInt64(memoryRoot, "memory.memsw.limit_in_bytes") + } + } + + spec.HasNetwork = hasNetwork + spec.HasFilesystem = hasFilesystem + + if blkioRoot, ok := cgroupPaths["blkio"]; ok && utils.FileExists(blkioRoot) { + spec.HasDiskIo = true + } + + return spec, nil +} + +func readString(dirpath string, file string) string { + cgroupFile := path.Join(dirpath, file) + + // Ignore non-existent files + if !utils.FileExists(cgroupFile) { + return "" + } + + // Read + out, err := ioutil.ReadFile(cgroupFile) + if err != nil { + glog.Errorf("readString: Failed to read %q: %s", cgroupFile, err) + return "" + } + return strings.TrimSpace(string(out)) +} + +func readUInt64(dirpath string, file string) uint64 { + out := readString(dirpath, file) + if out == "" { + return 0 + } + + val, err := strconv.ParseUint(out, 10, 64) + if err != nil { + glog.Errorf("readUInt64: Failed to parse int %q from file %q: %s", out, path.Join(dirpath, file), err) + return 0 + } + + return val +} + +// Lists all directories under "path" and outputs the results as children of "parent". +func ListDirectories(dirpath string, parent string, recursive bool, output map[string]struct{}) error { + // Ignore if this hierarchy does not exist. + if !utils.FileExists(dirpath) { + return nil + } + + entries, err := ioutil.ReadDir(dirpath) + if err != nil { + return err + } + for _, entry := range entries { + // We only grab directories. + if entry.IsDir() { + name := path.Join(parent, entry.Name()) + output[name] = struct{}{} + + // List subcontainers if asked to. + if recursive { + err := ListDirectories(path.Join(dirpath, entry.Name()), name, true, output) + if err != nil { + return err + } + } + } + } + return nil +} + +func MakeCgroupPaths(mountPoints map[string]string, name string) map[string]string { + cgroupPaths := make(map[string]string, len(mountPoints)) + for key, val := range mountPoints { + cgroupPaths[key] = path.Join(val, name) + } + + return cgroupPaths +} + +func CgroupExists(cgroupPaths map[string]string) bool { + // If any cgroup exists, the container is still alive. + for _, cgroupPath := range cgroupPaths { + if utils.FileExists(cgroupPath) { + return true + } + } + return false +} diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/container/raw/inotify_watcher.go b/Godeps/_workspace/src/github.com/google/cadvisor/container/common/inotify_watcher.go similarity index 99% rename from Godeps/_workspace/src/github.com/google/cadvisor/container/raw/inotify_watcher.go rename to Godeps/_workspace/src/github.com/google/cadvisor/container/common/inotify_watcher.go index 8f1e5c9887e..16e2f2c9b0f 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/container/raw/inotify_watcher.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/container/common/inotify_watcher.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package raw +package common import ( "sync" diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/container/raw/test_resources/container_hints.json b/Godeps/_workspace/src/github.com/google/cadvisor/container/common/test_resources/container_hints.json similarity index 100% rename from Godeps/_workspace/src/github.com/google/cadvisor/container/raw/test_resources/container_hints.json rename to Godeps/_workspace/src/github.com/google/cadvisor/container/common/test_resources/container_hints.json diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/container/docker/factory.go b/Godeps/_workspace/src/github.com/google/cadvisor/container/docker/factory.go index 02d67495457..35bd15ac852 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/container/docker/factory.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/container/docker/factory.go @@ -27,7 +27,7 @@ import ( "github.com/google/cadvisor/fs" info "github.com/google/cadvisor/info/v1" - "github.com/fsouza/go-dockerclient" + docker "github.com/fsouza/go-dockerclient" "github.com/golang/glog" ) @@ -163,6 +163,7 @@ var ( version_re = regexp.MustCompile(version_regexp_string) ) +// TODO: switch to a semantic versioning library. func parseDockerVersion(full_version_string string) ([]int, error) { matches := version_re.FindAllStringSubmatch(full_version_string, -1) if len(matches) != 1 { @@ -186,42 +187,16 @@ func Register(factory info.MachineInfoFactory, fsInfo fs.FsInfo, ignoreMetrics c if err != nil { return fmt.Errorf("unable to communicate with docker daemon: %v", err) } - var dockerVersion []int - if version, err := client.Version(); err != nil { - return fmt.Errorf("unable to communicate with docker daemon: %v", err) - } else { - expected_version := []int{1, 0, 0} - version_string := version.Get("Version") - dockerVersion, err = parseDockerVersion(version_string) - if err != nil { - return fmt.Errorf("couldn't parse docker version: %v", err) - } - for index, number := range dockerVersion { - if number > expected_version[index] { - break - } else if number < expected_version[index] { - return fmt.Errorf("cAdvisor requires docker version %v or above but we have found version %v reported as \"%v\"", expected_version, dockerVersion, version_string) - } - } - } - information, err := client.Info() + dockerInfo, err := ValidateInfo() if err != nil { - return fmt.Errorf("failed to detect Docker info: %v", err) + return fmt.Errorf("failed to validate Docker info: %v", err) } - // Check that the libcontainer execdriver is used. - execDriver := information.Get("ExecutionDriver") - if !strings.HasPrefix(execDriver, "native") { - return fmt.Errorf("docker found, but not using native exec driver") - } + // Version already validated above, assume no error here. + dockerVersion, _ := parseDockerVersion(dockerInfo.ServerVersion) - sd := information.Get("Driver") - if sd == "" { - return fmt.Errorf("failed to find docker storage driver") - } - - storageDir := information.Get("DockerRootDir") + storageDir := dockerInfo.DockerRootDir if storageDir == "" { storageDir = *dockerRootDir } @@ -237,7 +212,7 @@ func Register(factory info.MachineInfoFactory, fsInfo fs.FsInfo, ignoreMetrics c dockerVersion: dockerVersion, fsInfo: fsInfo, machineInfoFactory: factory, - storageDriver: storageDriver(sd), + storageDriver: storageDriver(dockerInfo.Driver), storageDir: storageDir, ignoreMetrics: ignoreMetrics, } diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/container/docker/handler.go b/Godeps/_workspace/src/github.com/google/cadvisor/container/docker/handler.go index bfdbeb2cfba..74c17531e2b 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/container/docker/handler.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/container/docker/handler.go @@ -18,16 +18,15 @@ package docker import ( "fmt" "io/ioutil" - "math" "path" "strings" "time" "github.com/google/cadvisor/container" + "github.com/google/cadvisor/container/common" containerlibcontainer "github.com/google/cadvisor/container/libcontainer" "github.com/google/cadvisor/fs" info "github.com/google/cadvisor/info/v1" - "github.com/google/cadvisor/utils" docker "github.com/fsouza/go-dockerclient" "github.com/opencontainers/runc/libcontainer/cgroups" @@ -80,7 +79,7 @@ type dockerContainerHandler struct { networkMode string // Filesystem handler. - fsHandler fsHandler + fsHandler common.FsHandler ignoreMetrics container.MetricSet } @@ -169,7 +168,7 @@ func newDockerContainerHandler( } if !ignoreMetrics.Has(container.DiskUsageMetrics) { - handler.fsHandler = newFsHandler(time.Minute, rootfsStorageDir, otherStorageDir, fsInfo) + handler.fsHandler = common.NewFsHandler(time.Minute, rootfsStorageDir, otherStorageDir, fsInfo) } // We assume that if Inspect fails then the container is not known to docker. @@ -200,15 +199,14 @@ func newDockerContainerHandler( } func (self *dockerContainerHandler) Start() { - // Start the filesystem handler. if self.fsHandler != nil { - self.fsHandler.start() + self.fsHandler.Start() } } func (self *dockerContainerHandler) Cleanup() { if self.fsHandler != nil { - self.fsHandler.stop() + self.fsHandler.Stop() } } @@ -222,50 +220,6 @@ func (self *dockerContainerHandler) ContainerReference() (info.ContainerReferenc }, nil } -func (self *dockerContainerHandler) readLibcontainerConfig() (*libcontainerconfigs.Config, error) { - config, err := containerlibcontainer.ReadConfig(*dockerRootDir, *dockerRunDir, self.id) - if err != nil { - return nil, fmt.Errorf("failed to read libcontainer config: %v", err) - } - - // Replace cgroup parent and name with our own since we may be running in a different context. - if config.Cgroups == nil { - config.Cgroups = new(libcontainerconfigs.Cgroup) - } - config.Cgroups.Name = self.name - config.Cgroups.Parent = "/" - - return config, nil -} - -func libcontainerConfigToContainerSpec(config *libcontainerconfigs.Config, mi *info.MachineInfo) info.ContainerSpec { - var spec info.ContainerSpec - spec.HasMemory = true - spec.Memory.Limit = math.MaxUint64 - spec.Memory.SwapLimit = math.MaxUint64 - - if config.Cgroups.Resources != nil { - if config.Cgroups.Resources.Memory > 0 { - spec.Memory.Limit = uint64(config.Cgroups.Resources.Memory) - } - if config.Cgroups.Resources.MemorySwap > 0 { - spec.Memory.SwapLimit = uint64(config.Cgroups.Resources.MemorySwap) - } - - // Get CPU info - spec.HasCpu = true - spec.Cpu.Limit = 1024 - if config.Cgroups.Resources.CpuShares != 0 { - spec.Cpu.Limit = uint64(config.Cgroups.Resources.CpuShares) - } - spec.Cpu.Mask = utils.FixCpuMask(config.Cgroups.Resources.CpusetCpus, mi.NumCores) - } - - spec.HasDiskIo = true - - return spec -} - func (self *dockerContainerHandler) needNet() bool { if !self.ignoreMetrics.Has(container.NetworkUsageMetrics) { return !strings.HasPrefix(self.networkMode, "container:") @@ -274,29 +228,12 @@ func (self *dockerContainerHandler) needNet() bool { } func (self *dockerContainerHandler) GetSpec() (info.ContainerSpec, error) { - mi, err := self.machineInfoFactory.GetMachineInfo() - if err != nil { - return info.ContainerSpec{}, err - } - libcontainerConfig, err := self.readLibcontainerConfig() - if err != nil { - return info.ContainerSpec{}, err - } - - spec := libcontainerConfigToContainerSpec(libcontainerConfig, mi) - spec.CreationTime = self.creationTime - - if !self.ignoreMetrics.Has(container.DiskUsageMetrics) { - switch self.storageDriver { - case aufsStorageDriver, overlayStorageDriver, zfsStorageDriver: - spec.HasFilesystem = true - } - } + hasFilesystem := !self.ignoreMetrics.Has(container.DiskUsageMetrics) + spec, err := common.GetSpec(self.cgroupPaths, self.machineInfoFactory, self.needNet(), hasFilesystem) spec.Labels = self.labels spec.Envs = self.envs spec.Image = self.image - spec.HasNetwork = self.needNet() return spec, err } @@ -320,6 +257,7 @@ func (self *dockerContainerHandler) getFsStats(stats *info.ContainerStats) error if err != nil { return err } + var ( limit uint64 fsType string @@ -336,7 +274,7 @@ func (self *dockerContainerHandler) getFsStats(stats *info.ContainerStats) error fsStat := info.FsStats{Device: deviceInfo.Device, Type: fsType, Limit: limit} - fsStat.BaseUsage, fsStat.Usage = self.fsHandler.usage() + fsStat.BaseUsage, fsStat.Usage = self.fsHandler.Usage() stats.Filesystem = append(stats.Filesystem, fsStat) return nil @@ -401,19 +339,19 @@ func (self *dockerContainerHandler) StopWatchingSubcontainers() error { } func (self *dockerContainerHandler) Exists() bool { - return containerlibcontainer.Exists(*dockerRootDir, *dockerRunDir, self.id) + return common.CgroupExists(self.cgroupPaths) } -func DockerInfo() (map[string]string, error) { +func DockerInfo() (docker.DockerInfo, error) { client, err := Client() if err != nil { - return nil, fmt.Errorf("unable to communicate with docker daemon: %v", err) + return docker.DockerInfo{}, fmt.Errorf("unable to communicate with docker daemon: %v", err) } info, err := client.Info() if err != nil { - return nil, err + return docker.DockerInfo{}, err } - return info.Map(), nil + return *info, nil } func DockerImages() ([]docker.APIImages, error) { @@ -427,3 +365,47 @@ func DockerImages() ([]docker.APIImages, error) { } return images, nil } + +// Checks whether the dockerInfo reflects a valid docker setup, and returns it if it does, or an +// error otherwise. +func ValidateInfo() (*docker.DockerInfo, error) { + client, err := Client() + if err != nil { + return nil, fmt.Errorf("unable to communicate with docker daemon: %v", err) + } + + dockerInfo, err := client.Info() + if err != nil { + return nil, fmt.Errorf("failed to detect Docker info: %v", err) + } + + // Fall back to version API if ServerVersion is not set in info. + if dockerInfo.ServerVersion == "" { + version, err := client.Version() + if err != nil { + return nil, fmt.Errorf("unable to get docker version: %v", err) + } + dockerInfo.ServerVersion = version.Get("Version") + } + version, err := parseDockerVersion(dockerInfo.ServerVersion) + if err != nil { + return nil, err + } + + if version[0] < 1 { + return nil, fmt.Errorf("cAdvisor requires docker version %v or above but we have found version %v reported as %q", []int{1, 0, 0}, version, dockerInfo.ServerVersion) + } + + // Check that the libcontainer execdriver is used if the version is < 1.11 + // (execution drivers are no longer supported as of 1.11). + if version[0] <= 1 && version[1] <= 10 && + !strings.HasPrefix(dockerInfo.ExecutionDriver, "native") { + return nil, fmt.Errorf("docker found, but not using native exec driver") + } + + if dockerInfo.Driver == "" { + return nil, fmt.Errorf("failed to find docker storage driver") + } + + return dockerInfo, nil +} diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/container/libcontainer/compatibility.go b/Godeps/_workspace/src/github.com/google/cadvisor/container/libcontainer/compatibility.go deleted file mode 100644 index 7d484170e4b..00000000000 --- a/Godeps/_workspace/src/github.com/google/cadvisor/container/libcontainer/compatibility.go +++ /dev/null @@ -1,367 +0,0 @@ -// Copyright 2015 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 libcontainer - -import ( - "encoding/json" - "io/ioutil" - "path" - - "github.com/google/cadvisor/utils" - - "github.com/opencontainers/runc/libcontainer" - "github.com/opencontainers/runc/libcontainer/configs" -) - -// State represents a running container's state -type preAPIState struct { - // InitPid is the init process id in the parent namespace - InitPid int `json:"init_pid,omitempty"` - - // InitStartTime is the init process start time - InitStartTime string `json:"init_start_time,omitempty"` - - // Network runtime state. - NetworkState preAPINetworkState `json:"network_state,omitempty"` - - // Path to all the cgroups setup for a container. Key is cgroup subsystem name. - CgroupPaths map[string]string `json:"cgroup_paths,omitempty"` -} - -// Struct describing the network specific runtime state that will be maintained by libcontainer for all running containers -// Do not depend on it outside of libcontainer. -type preAPINetworkState struct { - // The name of the veth interface on the Host. - VethHost string `json:"veth_host,omitempty"` - // The name of the veth interface created inside the container for the child. - VethChild string `json:"veth_child,omitempty"` - // Net namespace path. - NsPath string `json:"ns_path,omitempty"` -} - -type preAPIConfig struct { - // Pathname to container's root filesystem - RootFs string `json:"root_fs,omitempty"` - - // Hostname optionally sets the container's hostname if provided - Hostname string `json:"hostname,omitempty"` - - // User will set the uid and gid of the executing process running inside the container - User string `json:"user,omitempty"` - - // WorkingDir will change the processes current working directory inside the container's rootfs - WorkingDir string `json:"working_dir,omitempty"` - - // Env will populate the processes environment with the provided values - // Any values from the parent processes will be cleared before the values - // provided in Env are provided to the process - Env []string `json:"environment,omitempty"` - - // Tty when true will allocate a pty slave on the host for access by the container's process - // and ensure that it is mounted inside the container's rootfs - Tty bool `json:"tty,omitempty"` - - // Namespaces specifies the container's namespaces that it should setup when cloning the init process - // If a namespace is not provided that namespace is shared from the container's parent process - Namespaces []configs.Namespace `json:"namespaces,omitempty"` - - // Capabilities specify the capabilities to keep when executing the process inside the container - // All capbilities not specified will be dropped from the processes capability mask - Capabilities []string `json:"capabilities,omitempty"` - - // Networks specifies the container's network setup to be created - Networks []preAPINetwork `json:"networks,omitempty"` - - // Routes can be specified to create entries in the route table as the container is started - Routes []*configs.Route `json:"routes,omitempty"` - - // Cgroups specifies specific cgroup settings for the various subsystems that the container is - // placed into to limit the resources the container has available - Cgroups *configs.Cgroup `json:"cgroups,omitempty"` - - // AppArmorProfile specifies the profile to apply to the process running in the container and is - // change at the time the process is execed - AppArmorProfile string `json:"apparmor_profile,omitempty"` - - // ProcessLabel specifies the label to apply to the process running in the container. It is - // commonly used by selinux - ProcessLabel string `json:"process_label,omitempty"` - - // RestrictSys will remount /proc/sys, /sys, and mask over sysrq-trigger as well as /proc/irq and - // /proc/bus - RestrictSys bool `json:"restrict_sys,omitempty"` -} - -// Network defines configuration for a container's networking stack -// -// The network configuration can be omited from a container causing the -// container to be setup with the host's networking stack -type preAPINetwork struct { - // Type sets the networks type, commonly veth and loopback - Type string `json:"type,omitempty"` - - // The bridge to use. - Bridge string `json:"bridge,omitempty"` - - // Prefix for the veth interfaces. - VethPrefix string `json:"veth_prefix,omitempty"` - - // MacAddress contains the MAC address to set on the network interface - MacAddress string `json:"mac_address,omitempty"` - - // Address contains the IPv4 and mask to set on the network interface - Address string `json:"address,omitempty"` - - // IPv6Address contains the IPv6 and mask to set on the network interface - IPv6Address string `json:"ipv6_address,omitempty"` - - // Gateway sets the gateway address that is used as the default for the interface - Gateway string `json:"gateway,omitempty"` - - // IPv6Gateway sets the ipv6 gateway address that is used as the default for the interface - IPv6Gateway string `json:"ipv6_gateway,omitempty"` - - // Mtu sets the mtu value for the interface and will be mirrored on both the host and - // container's interfaces if a pair is created, specifically in the case of type veth - // Note: This does not apply to loopback interfaces. - Mtu int `json:"mtu,omitempty"` - - // TxQueueLen sets the tx_queuelen value for the interface and will be mirrored on both the host and - // container's interfaces if a pair is created, specifically in the case of type veth - // Note: This does not apply to loopback interfaces. - TxQueueLen int `json:"txqueuelen,omitempty"` -} - -type v1Cgroup struct { - configs.Cgroup - - // Weight per cgroup per device, can override BlkioWeight. - BlkioWeightDevice string `json:"blkio_weight_device"` - // IO read rate limit per cgroup per device, bytes per second. - BlkioThrottleReadBpsDevice string `json:"blkio_throttle_read_bps_device"` - - // IO write rate limit per cgroup per divice, bytes per second. - BlkioThrottleWriteBpsDevice string `json:"blkio_throttle_write_bps_device"` - - // IO read rate limit per cgroup per device, IO per second. - BlkioThrottleReadIOPSDevice string `json:"blkio_throttle_read_iops_device"` - - // IO write rate limit per cgroup per device, IO per second. - BlkioThrottleWriteIOPSDevice string `json:"blkio_throttle_write_iops_device"` -} - -type v1Config struct { - configs.Config - - // Cgroups specifies specific cgroup settings for the various subsystems that the container is - // placed into to limit the resources the container has available - Cgroup *v1Cgroup `json:"cgroups"` -} - -// State represents a running container's state -type v1State struct { - libcontainer.State - - // Config is the container's configuration. - Config v1Config `json:"config"` -} - -// Relative path to the libcontainer execdriver directory. -const libcontainerExecDriverPath = "execdriver/native" - -// TODO(vmarmol): Deprecate over time as old Dockers are phased out. -func ReadConfig(dockerRoot, dockerRun, containerID string) (*configs.Config, error) { - // Try using the new config if it is available. - configPath := configPath(dockerRun, containerID) - if utils.FileExists(configPath) { - out, err := ioutil.ReadFile(configPath) - if err != nil { - return nil, err - } - - var state libcontainer.State - if err = json.Unmarshal(out, &state); err != nil { - if _, ok := err.(*json.UnmarshalTypeError); ok { - // Since some fields changes in Cgroup struct, it will be failed while unmarshalling to libcontainer.State struct. - // This failure is caused by a change of runc(https://github.com/opencontainers/runc/commit/c6e406af243fab0c9636539c1cb5f4d60fe0787f). - // If we encountered the UnmarshalTypeError, try to unmarshal it again to v1State struct and convert it. - var state v1State - err2 := json.Unmarshal(out, &state) - if err2 != nil { - return nil, err - } - return convertOldConfigToNew(state.Config), nil - } else { - return nil, err - } - } - return &state.Config, nil - } - - // Fallback to reading the old config which is comprised of the state and config files. - oldConfigPath := oldConfigPath(dockerRoot, containerID) - out, err := ioutil.ReadFile(oldConfigPath) - if err != nil { - return nil, err - } - - // Try reading the preAPIConfig. - var config preAPIConfig - err = json.Unmarshal(out, &config) - if err != nil { - // Try to parse the old pre-API config. The main difference is that namespaces used to be a map, now it is a slice of structs. - // The JSON marshaler will use the non-nested field before the nested one. - type oldLibcontainerConfig struct { - preAPIConfig - OldNamespaces map[string]bool `json:"namespaces,omitempty"` - } - var oldConfig oldLibcontainerConfig - err2 := json.Unmarshal(out, &oldConfig) - if err2 != nil { - // Use original error. - return nil, err - } - - // Translate the old pre-API config into the new config. - config = oldConfig.preAPIConfig - for ns := range oldConfig.OldNamespaces { - config.Namespaces = append(config.Namespaces, configs.Namespace{ - Type: configs.NamespaceType(ns), - }) - } - } - - // Read the old state file as well. - state, err := readState(dockerRoot, containerID) - if err != nil { - return nil, err - } - - // Convert preAPIConfig + old state file to Config. - // This only converts some of the fields, the ones we use. - // You may need to add fields if the one you're interested in is not available. - var result configs.Config - result.Cgroups = new(configs.Cgroup) - result.Rootfs = config.RootFs - result.Hostname = config.Hostname - result.Namespaces = config.Namespaces - result.Capabilities = config.Capabilities - for _, net := range config.Networks { - n := &configs.Network{ - Name: state.NetworkState.VethChild, - Bridge: net.Bridge, - MacAddress: net.MacAddress, - Address: net.Address, - Gateway: net.Gateway, - IPv6Address: net.IPv6Address, - IPv6Gateway: net.IPv6Gateway, - HostInterfaceName: state.NetworkState.VethHost, - } - result.Networks = append(result.Networks, n) - } - result.Routes = config.Routes - if config.Cgroups != nil { - result.Cgroups = config.Cgroups - } - - return &result, nil -} - -func convertOldConfigToNew(config v1Config) *configs.Config { - var ( - result configs.Config - old *v1Cgroup = config.Cgroup - ) - result.Rootfs = config.Config.Rootfs - result.Hostname = config.Config.Hostname - result.Namespaces = config.Config.Namespaces - result.Capabilities = config.Config.Capabilities - result.Networks = config.Config.Networks - result.Routes = config.Config.Routes - - var newCgroup = &configs.Cgroup{ - Name: old.Name, - Parent: old.Parent, - Resources: &configs.Resources{ - AllowAllDevices: old.Resources.AllowAllDevices, - AllowedDevices: old.Resources.AllowedDevices, - DeniedDevices: old.Resources.DeniedDevices, - Memory: old.Resources.Memory, - MemoryReservation: old.Resources.MemoryReservation, - MemorySwap: old.Resources.MemorySwap, - KernelMemory: old.Resources.KernelMemory, - CpuShares: old.Resources.CpuShares, - CpuQuota: old.Resources.CpuQuota, - CpuPeriod: old.Resources.CpuPeriod, - CpuRtRuntime: old.Resources.CpuRtRuntime, - CpuRtPeriod: old.Resources.CpuRtPeriod, - CpusetCpus: old.Resources.CpusetCpus, - CpusetMems: old.Resources.CpusetMems, - BlkioWeight: old.Resources.BlkioWeight, - BlkioLeafWeight: old.Resources.BlkioLeafWeight, - Freezer: old.Resources.Freezer, - HugetlbLimit: old.Resources.HugetlbLimit, - OomKillDisable: old.Resources.OomKillDisable, - MemorySwappiness: old.Resources.MemorySwappiness, - NetPrioIfpriomap: old.Resources.NetPrioIfpriomap, - NetClsClassid: old.Resources.NetClsClassid, - }, - } - - result.Cgroups = newCgroup - - return &result -} - -func readState(dockerRoot, containerID string) (preAPIState, error) { - // pre-API libcontainer changed how its state was stored, try the old way of a "pid" file - statePath := path.Join(dockerRoot, libcontainerExecDriverPath, containerID, "state.json") - if !utils.FileExists(statePath) { - pidPath := path.Join(dockerRoot, libcontainerExecDriverPath, containerID, "pid") - if utils.FileExists(pidPath) { - // We don't need the old state, return an empty state and we'll gracefully degrade. - return preAPIState{}, nil - } - } - out, err := ioutil.ReadFile(statePath) - if err != nil { - return preAPIState{}, err - } - - // Parse the state. - var state preAPIState - err = json.Unmarshal(out, &state) - if err != nil { - return preAPIState{}, err - } - - return state, nil -} - -// Gets the path to the libcontainer configuration. -func configPath(dockerRun, containerID string) string { - return path.Join(dockerRun, libcontainerExecDriverPath, containerID, "state.json") -} - -// Gets the path to the old libcontainer configuration. -func oldConfigPath(dockerRoot, containerID string) string { - return path.Join(dockerRoot, libcontainerExecDriverPath, containerID, "container.json") -} - -// Gets whether the specified container exists. -func Exists(dockerRoot, dockerRun, containerID string) bool { - // New or old config must exist for the container to be considered alive. - return utils.FileExists(configPath(dockerRun, containerID)) || utils.FileExists(oldConfigPath(dockerRoot, containerID)) -} diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/container/raw/factory.go b/Godeps/_workspace/src/github.com/google/cadvisor/container/raw/factory.go index ed2db52c196..e413207ff19 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/container/raw/factory.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/container/raw/factory.go @@ -19,6 +19,7 @@ import ( "fmt" "github.com/google/cadvisor/container" + "github.com/google/cadvisor/container/common" "github.com/google/cadvisor/container/libcontainer" "github.com/google/cadvisor/fs" info "github.com/google/cadvisor/info/v1" @@ -39,7 +40,7 @@ type rawFactory struct { fsInfo fs.FsInfo // Watcher for inotify events. - watcher *InotifyWatcher + watcher *common.InotifyWatcher // List of metrics to be ignored. ignoreMetrics map[container.MetricKind]struct{} @@ -64,20 +65,7 @@ func (self *rawFactory) CanHandleAndAccept(name string) (bool, bool, error) { } func (self *rawFactory) DebugInfo() map[string][]string { - out := make(map[string][]string) - - // Get information about inotify watches. - watches := self.watcher.GetWatches() - lines := make([]string, 0, len(watches)) - for containerName, cgroupWatches := range watches { - lines = append(lines, fmt.Sprintf("%s:", containerName)) - for _, cg := range cgroupWatches { - lines = append(lines, fmt.Sprintf("\t%s", cg)) - } - } - out["Inotify watches"] = lines - - return out + return common.DebugInfo(self.watcher.GetWatches()) } func Register(machineInfoFactory info.MachineInfoFactory, fsInfo fs.FsInfo, ignoreMetrics map[container.MetricKind]struct{}) error { @@ -89,7 +77,7 @@ func Register(machineInfoFactory info.MachineInfoFactory, fsInfo fs.FsInfo, igno return fmt.Errorf("failed to find supported cgroup mounts for the raw factory") } - watcher, err := NewInotifyWatcher() + watcher, err := common.NewInotifyWatcher() if err != nil { return err } diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/container/raw/handler.go b/Godeps/_workspace/src/github.com/google/cadvisor/container/raw/handler.go index 94d3141c770..0e55046edd1 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/container/raw/handler.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/container/raw/handler.go @@ -18,17 +18,14 @@ package raw import ( "fmt" "io/ioutil" - "os" "path" - "strconv" "strings" - "time" "github.com/google/cadvisor/container" + "github.com/google/cadvisor/container/common" "github.com/google/cadvisor/container/libcontainer" "github.com/google/cadvisor/fs" info "github.com/google/cadvisor/info/v1" - "github.com/google/cadvisor/utils" "github.com/google/cadvisor/utils/machine" "github.com/golang/glog" @@ -45,7 +42,7 @@ type rawContainerHandler struct { machineInfoFactory info.MachineInfoFactory // Inotify event watcher. - watcher *InotifyWatcher + watcher *common.InotifyWatcher // Signal for watcher thread to stop. stopWatcher chan error @@ -58,7 +55,7 @@ type rawContainerHandler struct { cgroupManager cgroups.Manager fsInfo fs.FsInfo - externalMounts []mount + externalMounts []common.Mount rootFs string @@ -72,14 +69,10 @@ func isRootCgroup(name string) bool { return name == "/" } -func newRawContainerHandler(name string, cgroupSubsystems *libcontainer.CgroupSubsystems, machineInfoFactory info.MachineInfoFactory, fsInfo fs.FsInfo, watcher *InotifyWatcher, rootFs string, ignoreMetrics container.MetricSet) (container.ContainerHandler, error) { - // Create the cgroup paths. - cgroupPaths := make(map[string]string, len(cgroupSubsystems.MountPoints)) - for key, val := range cgroupSubsystems.MountPoints { - cgroupPaths[key] = path.Join(val, name) - } +func newRawContainerHandler(name string, cgroupSubsystems *libcontainer.CgroupSubsystems, machineInfoFactory info.MachineInfoFactory, fsInfo fs.FsInfo, watcher *common.InotifyWatcher, rootFs string, ignoreMetrics container.MetricSet) (container.ContainerHandler, error) { + cgroupPaths := common.MakeCgroupPaths(cgroupSubsystems.MountPoints, name) - cHints, err := getContainerHintsFromFile(*argContainerHints) + cHints, err := common.GetContainerHintsFromFile(*common.ArgContainerHints) if err != nil { return nil, err } @@ -92,7 +85,7 @@ func newRawContainerHandler(name string, cgroupSubsystems *libcontainer.CgroupSu Paths: cgroupPaths, } - var externalMounts []mount + var externalMounts []common.Mount for _, container := range cHints.AllHosts { if name == container.FullName { externalMounts = container.Mounts @@ -128,38 +121,6 @@ func (self *rawContainerHandler) ContainerReference() (info.ContainerReference, }, nil } -func readString(dirpath string, file string) string { - cgroupFile := path.Join(dirpath, file) - - // Ignore non-existent files - if !utils.FileExists(cgroupFile) { - return "" - } - - // Read - out, err := ioutil.ReadFile(cgroupFile) - if err != nil { - glog.Errorf("raw driver: Failed to read %q: %s", cgroupFile, err) - return "" - } - return strings.TrimSpace(string(out)) -} - -func readUInt64(dirpath string, file string) uint64 { - out := readString(dirpath, file) - if out == "" { - return 0 - } - - val, err := strconv.ParseUint(out, 10, 64) - if err != nil { - glog.Errorf("raw driver: Failed to parse int %q from file %q: %s", out, path.Join(dirpath, file), err) - return 0 - } - - return val -} - func (self *rawContainerHandler) GetRootNetworkDevices() ([]info.NetInfo, error) { nd := []info.NetInfo{} if isRootCgroup(self.name) { @@ -179,66 +140,21 @@ func (self *rawContainerHandler) Start() {} func (self *rawContainerHandler) Cleanup() {} func (self *rawContainerHandler) GetSpec() (info.ContainerSpec, error) { - var spec info.ContainerSpec - - // The raw driver assumes unified hierarchy containers. - - // Get the lowest creation time from all hierarchies as the container creation time. - now := time.Now() - lowestTime := now - for _, cgroupPath := range self.cgroupPaths { - // The modified time of the cgroup directory changes whenever a subcontainer is created. - // eg. /docker will have creation time matching the creation of latest docker container. - // Use clone_children as a workaround as it isn't usually modified. It is only likely changed - // immediately after creating a container. - cgroupPath = path.Join(cgroupPath, "cgroup.clone_children") - fi, err := os.Stat(cgroupPath) - if err == nil && fi.ModTime().Before(lowestTime) { - lowestTime = fi.ModTime() - } - } - if lowestTime != now { - spec.CreationTime = lowestTime - } - - // Get machine info. - mi, err := self.machineInfoFactory.GetMachineInfo() + const hasNetwork = false + hasFilesystem := isRootCgroup(self.name) || len(self.externalMounts) > 0 + spec, err := common.GetSpec(self.cgroupPaths, self.machineInfoFactory, hasNetwork, hasFilesystem) if err != nil { return spec, err } - // CPU. - cpuRoot, ok := self.cgroupPaths["cpu"] - if ok { - if utils.FileExists(cpuRoot) { - spec.HasCpu = true - spec.Cpu.Limit = readUInt64(cpuRoot, "cpu.shares") - spec.Cpu.Period = readUInt64(cpuRoot, "cpu.cfs_period_us") - quota := readString(cpuRoot, "cpu.cfs_quota_us") - - if quota != "" && quota != "-1" { - val, err := strconv.ParseUint(quota, 10, 64) - if err != nil { - glog.Errorf("raw driver: Failed to parse CPUQuota from %q: %s", path.Join(cpuRoot, "cpu.cfs_quota_us"), err) - } - spec.Cpu.Quota = val - } + if isRootCgroup(self.name) { + // Check physical network devices for root container. + nd, err := self.GetRootNetworkDevices() + if err != nil { + return spec, err } - } + spec.HasNetwork = spec.HasNetwork || len(nd) != 0 - // Cpu Mask. - // This will fail for non-unified hierarchies. We'll return the whole machine mask in that case. - cpusetRoot, ok := self.cgroupPaths["cpuset"] - if ok { - if utils.FileExists(cpusetRoot) { - spec.HasCpu = true - mask := readString(cpusetRoot, "cpuset.cpus") - spec.Cpu.Mask = utils.FixCpuMask(mask, mi.NumCores) - } - } - - // Memory - if self.name == "/" { // Get memory and swap limits of the running machine memLimit, err := machine.GetMachineMemoryCapacity() if err != nil { @@ -256,35 +172,8 @@ func (self *rawContainerHandler) GetSpec() (info.ContainerSpec, error) { } else { spec.Memory.SwapLimit = uint64(swapLimit) } - } else { - memoryRoot, ok := self.cgroupPaths["memory"] - if ok { - if utils.FileExists(memoryRoot) { - spec.HasMemory = true - spec.Memory.Limit = readUInt64(memoryRoot, "memory.limit_in_bytes") - spec.Memory.SwapLimit = readUInt64(memoryRoot, "memory.memsw.limit_in_bytes") - } - } } - // Fs. - if self.name == "/" || self.externalMounts != nil { - spec.HasFilesystem = true - } - - // DiskIo. - if blkioRoot, ok := self.cgroupPaths["blkio"]; ok && utils.FileExists(blkioRoot) { - spec.HasDiskIo = true - } - - // Check physical network devices for root container. - nd, err := self.GetRootNetworkDevices() - if err != nil { - return spec, err - } - if len(nd) != 0 { - spec.HasNetwork = true - } return spec, nil } @@ -379,39 +268,10 @@ func (self *rawContainerHandler) GetContainerLabels() map[string]string { return map[string]string{} } -// Lists all directories under "path" and outputs the results as children of "parent". -func listDirectories(dirpath string, parent string, recursive bool, output map[string]struct{}) error { - // Ignore if this hierarchy does not exist. - if !utils.FileExists(dirpath) { - return nil - } - - entries, err := ioutil.ReadDir(dirpath) - if err != nil { - return err - } - for _, entry := range entries { - // We only grab directories. - if entry.IsDir() { - name := path.Join(parent, entry.Name()) - output[name] = struct{}{} - - // List subcontainers if asked to. - if recursive { - err := listDirectories(path.Join(dirpath, entry.Name()), name, true, output) - if err != nil { - return err - } - } - } - } - return nil -} - func (self *rawContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) { containers := make(map[string]struct{}) for _, cgroupPath := range self.cgroupPaths { - err := listDirectories(cgroupPath, self.name, listType == container.ListRecursive, containers) + err := common.ListDirectories(cgroupPath, self.name, listType == container.ListRecursive, containers) if err != nil { return nil, err } @@ -583,11 +443,5 @@ func (self *rawContainerHandler) StopWatchingSubcontainers() error { } func (self *rawContainerHandler) Exists() bool { - // If any cgroup exists, the container is still alive. - for _, cgroupPath := range self.cgroupPaths { - if utils.FileExists(cgroupPath) { - return true - } - } - return false + return common.CgroupExists(self.cgroupPaths) } diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/container/rkt/client.go b/Godeps/_workspace/src/github.com/google/cadvisor/container/rkt/client.go new file mode 100644 index 00000000000..ee0167c8d21 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/cadvisor/container/rkt/client.go @@ -0,0 +1,76 @@ +// Copyright 2016 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 rkt + +import ( + "fmt" + "net" + "sync" + "time" + + rktapi "github.com/coreos/rkt/api/v1alpha" + + "golang.org/x/net/context" + "google.golang.org/grpc" +) + +const ( + defaultRktAPIServiceAddr = "localhost:15441" + timeout = 2 * time.Second +) + +var ( + rktClient rktapi.PublicAPIClient + rktClientErr error + once sync.Once +) + +func Client() (rktapi.PublicAPIClient, error) { + once.Do(func() { + conn, err := net.DialTimeout("tcp", defaultRktAPIServiceAddr, timeout) + if err != nil { + rktClient = nil + rktClientErr = fmt.Errorf("rkt: cannot tcp Dial rkt api service: %v", err) + return + } + + conn.Close() + + apisvcConn, err := grpc.Dial(defaultRktAPIServiceAddr, grpc.WithInsecure(), grpc.WithTimeout(timeout)) + if err != nil { + rktClient = nil + rktClientErr = fmt.Errorf("rkt: cannot grpc Dial rkt api service: %v", err) + return + } + + rktClient = rktapi.NewPublicAPIClient(apisvcConn) + }) + + return rktClient, rktClientErr +} + +func RktPath() (string, error) { + client, err := Client() + if err != nil { + return "", err + } + + resp, err := client.GetInfo(context.Background(), &rktapi.GetInfoRequest{}) + if err != nil { + return "", fmt.Errorf("couldn't GetInfo from rkt api service: %v", err) + } + + return resp.Info.GlobalFlags.Dir, nil +} diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/container/rkt/factory.go b/Godeps/_workspace/src/github.com/google/cadvisor/container/rkt/factory.go new file mode 100644 index 00000000000..9f4ca761993 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/cadvisor/container/rkt/factory.go @@ -0,0 +1,104 @@ +// Copyright 2016 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 rkt + +import ( + "fmt" + "strings" + + "github.com/google/cadvisor/container" + "github.com/google/cadvisor/container/libcontainer" + "github.com/google/cadvisor/fs" + info "github.com/google/cadvisor/info/v1" + + "github.com/golang/glog" +) + +const RktNamespace = "rkt" + +type rktFactory struct { + machineInfoFactory info.MachineInfoFactory + + cgroupSubsystems *libcontainer.CgroupSubsystems + + fsInfo fs.FsInfo + + ignoreMetrics container.MetricSet + + rktPath string +} + +func (self *rktFactory) String() string { + return "rkt" +} + +func (self *rktFactory) NewContainerHandler(name string, inHostNamespace bool) (container.ContainerHandler, error) { + client, err := Client() + if err != nil { + return nil, err + } + + rootFs := "/" + if !inHostNamespace { + rootFs = "/rootfs" + } + return newRktContainerHandler(name, client, self.rktPath, self.cgroupSubsystems, self.machineInfoFactory, self.fsInfo, rootFs, self.ignoreMetrics) +} + +func (self *rktFactory) CanHandleAndAccept(name string) (bool, bool, error) { + // will ignore all cgroup names that don't either correspond to the machine.slice that is the pod or the containers that belong to the pod + // only works for machined rkt pods at the moment + + if strings.HasPrefix(name, "/machine.slice/machine-rkt\\x2d") { + accept, err := verifyName(name) + return true, accept, err + } + return false, false, fmt.Errorf("%s not handled by rkt handler", name) +} + +func (self *rktFactory) DebugInfo() map[string][]string { + return map[string][]string{} +} + +func Register(machineInfoFactory info.MachineInfoFactory, fsInfo fs.FsInfo, ignoreMetrics container.MetricSet) error { + _, err := Client() + if err != nil { + return fmt.Errorf("unable to communicate with Rkt api service: %v", err) + } + + rktPath, err := RktPath() + if err != nil { + return fmt.Errorf("unable to get the RktPath variable %v", err) + } + + cgroupSubsystems, err := libcontainer.GetCgroupSubsystems() + if err != nil { + return fmt.Errorf("failed to get cgroup subsystems: %v", err) + } + if len(cgroupSubsystems.Mounts) == 0 { + return fmt.Errorf("failed to find supported cgroup mounts for the raw factory") + } + + glog.Infof("Registering Rkt factory") + factory := &rktFactory{ + machineInfoFactory: machineInfoFactory, + fsInfo: fsInfo, + cgroupSubsystems: &cgroupSubsystems, + ignoreMetrics: ignoreMetrics, + rktPath: rktPath, + } + container.RegisterContainerHandlerFactory(factory) + return nil +} diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/container/rkt/handler.go b/Godeps/_workspace/src/github.com/google/cadvisor/container/rkt/handler.go new file mode 100644 index 00000000000..31ca9707f55 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/cadvisor/container/rkt/handler.go @@ -0,0 +1,327 @@ +// Copyright 2016 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. + +// Handler for "rkt" containers. +package rkt + +import ( + "fmt" + "os" + "path" + "time" + + rktapi "github.com/coreos/rkt/api/v1alpha" + "github.com/google/cadvisor/container" + "github.com/google/cadvisor/container/common" + "github.com/google/cadvisor/container/libcontainer" + "github.com/google/cadvisor/fs" + info "github.com/google/cadvisor/info/v1" + "golang.org/x/net/context" + + "github.com/golang/glog" + "github.com/opencontainers/runc/libcontainer/cgroups" + cgroupfs "github.com/opencontainers/runc/libcontainer/cgroups/fs" + "github.com/opencontainers/runc/libcontainer/configs" +) + +type rktContainerHandler struct { + rktClient rktapi.PublicAPIClient + // Name of the container for this handler. + name string + cgroupSubsystems *libcontainer.CgroupSubsystems + machineInfoFactory info.MachineInfoFactory + + // Absolute path to the cgroup hierarchies of this container. + // (e.g.: "cpu" -> "/sys/fs/cgroup/cpu/test") + cgroupPaths map[string]string + + // Manager of this container's cgroups. + cgroupManager cgroups.Manager + + // Whether this container has network isolation enabled. + hasNetwork bool + + fsInfo fs.FsInfo + + rootFs string + + isPod bool + + aliases []string + + pid int + + rootfsStorageDir string + + labels map[string]string + + // Filesystem handler. + fsHandler common.FsHandler + + ignoreMetrics container.MetricSet + + apiPod *rktapi.Pod +} + +func newRktContainerHandler(name string, rktClient rktapi.PublicAPIClient, rktPath string, cgroupSubsystems *libcontainer.CgroupSubsystems, machineInfoFactory info.MachineInfoFactory, fsInfo fs.FsInfo, rootFs string, ignoreMetrics container.MetricSet) (container.ContainerHandler, error) { + aliases := make([]string, 1) + isPod := false + + apiPod := &rktapi.Pod{} + + parsed, err := parseName(name) + if err != nil { + return nil, fmt.Errorf("this should be impossible!, new handler failing, but factory allowed, name = %s", name) + } + + //rktnetes uses containerID: rkt://fff40827-b994-4e3a-8f88-6427c2c8a5ac:nginx + if parsed.Container == "" { + isPod = true + aliases = append(aliases, "rkt://"+parsed.Pod) + } else { + aliases = append(aliases, "rkt://"+parsed.Pod+":"+parsed.Container) + } + + pid := os.Getpid() + labels := make(map[string]string) + resp, err := rktClient.InspectPod(context.Background(), &rktapi.InspectPodRequest{ + Id: parsed.Pod, + }) + if err != nil { + return nil, err + } else { + var annotations []*rktapi.KeyValue + if parsed.Container == "" { + pid = int(resp.Pod.Pid) + apiPod = resp.Pod + annotations = resp.Pod.Annotations + } else { + var ok bool + if annotations, ok = findAnnotations(resp.Pod.Apps, parsed.Container); !ok { + glog.Warningf("couldn't find application in Pod matching %v", parsed.Container) + } + } + labels = createLabels(annotations) + } + + cgroupPaths := common.MakeCgroupPaths(cgroupSubsystems.MountPoints, name) + + // Generate the equivalent cgroup manager for this container. + cgroupManager := &cgroupfs.Manager{ + Cgroups: &configs.Cgroup{ + Name: name, + }, + Paths: cgroupPaths, + } + + hasNetwork := false + if isPod { + hasNetwork = true + } + + rootfsStorageDir := getRootFs(rktPath, parsed) + + handler := &rktContainerHandler{ + name: name, + rktClient: rktClient, + cgroupSubsystems: cgroupSubsystems, + machineInfoFactory: machineInfoFactory, + cgroupPaths: cgroupPaths, + cgroupManager: cgroupManager, + fsInfo: fsInfo, + hasNetwork: hasNetwork, + rootFs: rootFs, + isPod: isPod, + aliases: aliases, + pid: pid, + labels: labels, + rootfsStorageDir: rootfsStorageDir, + ignoreMetrics: ignoreMetrics, + apiPod: apiPod, + } + + if !ignoreMetrics.Has(container.DiskUsageMetrics) { + handler.fsHandler = common.NewFsHandler(time.Minute, rootfsStorageDir, "", fsInfo) + } + + return handler, nil +} + +func findAnnotations(apps []*rktapi.App, container string) ([]*rktapi.KeyValue, bool) { + for _, app := range apps { + if app.Name == container { + return app.Annotations, true + } + } + return nil, false +} + +func createLabels(annotations []*rktapi.KeyValue) map[string]string { + labels := make(map[string]string) + for _, kv := range annotations { + labels[kv.Key] = kv.Value + } + + return labels +} + +func (handler *rktContainerHandler) ContainerReference() (info.ContainerReference, error) { + return info.ContainerReference{ + Name: handler.name, + Aliases: handler.aliases, + Namespace: RktNamespace, + Labels: handler.labels, + }, nil +} + +func (handler *rktContainerHandler) Start() { + handler.fsHandler.Start() +} + +func (handler *rktContainerHandler) Cleanup() { + handler.fsHandler.Stop() +} + +func (handler *rktContainerHandler) GetSpec() (info.ContainerSpec, error) { + hasNetwork := handler.hasNetwork && !handler.ignoreMetrics.Has(container.NetworkUsageMetrics) + hasFilesystem := !handler.ignoreMetrics.Has(container.DiskUsageMetrics) + return common.GetSpec(handler.cgroupPaths, handler.machineInfoFactory, hasNetwork, hasFilesystem) +} + +func (handler *rktContainerHandler) getFsStats(stats *info.ContainerStats) error { + if handler.ignoreMetrics.Has(container.DiskUsageMetrics) { + return nil + } + + deviceInfo, err := handler.fsInfo.GetDirFsDevice(handler.rootfsStorageDir) + if err != nil { + return err + } + + mi, err := handler.machineInfoFactory.GetMachineInfo() + if err != nil { + return err + } + var limit uint64 = 0 + + // Use capacity as limit. + for _, fs := range mi.Filesystems { + if fs.Device == deviceInfo.Device { + limit = fs.Capacity + break + } + } + + fsStat := info.FsStats{Device: deviceInfo.Device, Limit: limit} + + fsStat.BaseUsage, fsStat.Usage = handler.fsHandler.Usage() + + stats.Filesystem = append(stats.Filesystem, fsStat) + + return nil +} + +func (handler *rktContainerHandler) GetStats() (*info.ContainerStats, error) { + stats, err := libcontainer.GetStats(handler.cgroupManager, handler.rootFs, handler.pid, handler.ignoreMetrics) + if err != nil { + return stats, err + } + + // Get filesystem stats. + err = handler.getFsStats(stats) + if err != nil { + return stats, err + } + + return stats, nil +} + +func (handler *rktContainerHandler) GetCgroupPath(resource string) (string, error) { + path, ok := handler.cgroupPaths[resource] + if !ok { + return "", fmt.Errorf("could not find path for resource %q for container %q\n", resource, handler.name) + } + return path, nil +} + +func (handler *rktContainerHandler) GetContainerLabels() map[string]string { + return handler.labels +} + +func (handler *rktContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) { + containers := make(map[string]struct{}) + + // Rkt containers do not have subcontainers, only the "Pod" does. + if handler.isPod == false { + var ret []info.ContainerReference + return ret, nil + } + + // Turn the system.slice cgroups into the Pod's subcontainers + for _, cgroupPath := range handler.cgroupPaths { + err := common.ListDirectories(path.Join(cgroupPath, "system.slice"), path.Join(handler.name, "system.slice"), listType == container.ListRecursive, containers) + if err != nil { + return nil, err + } + } + + // Create the container references. for the Pod's subcontainers + ret := make([]info.ContainerReference, 0, len(handler.apiPod.Apps)) + for cont := range containers { + aliases := make([]string, 1) + parsed, err := parseName(cont) + if err != nil { + return nil, fmt.Errorf("this should be impossible!, unable to parse rkt subcontainer name = %s", cont) + } + aliases = append(aliases, parsed.Pod+":"+parsed.Container) + + labels := make(map[string]string) + if annotations, ok := findAnnotations(handler.apiPod.Apps, parsed.Container); !ok { + glog.Warningf("couldn't find application in Pod matching %v", parsed.Container) + } else { + labels = createLabels(annotations) + } + + ret = append(ret, info.ContainerReference{ + Name: cont, + Aliases: aliases, + Namespace: RktNamespace, + Labels: labels, + }) + } + + return ret, nil +} + +func (handler *rktContainerHandler) ListThreads(listType container.ListType) ([]int, error) { + // TODO(sjpotter): Implement? Not implemented with docker yet + return nil, nil +} + +func (handler *rktContainerHandler) ListProcesses(listType container.ListType) ([]int, error) { + return libcontainer.GetProcesses(handler.cgroupManager) +} + +func (handler *rktContainerHandler) WatchSubcontainers(events chan container.SubcontainerEvent) error { + return fmt.Errorf("watch is unimplemented in the Rkt container driver") +} + +func (handler *rktContainerHandler) StopWatchingSubcontainers() error { + // No-op for Rkt driver. + return nil +} + +func (handler *rktContainerHandler) Exists() bool { + return common.CgroupExists(handler.cgroupPaths) +} diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/container/rkt/helpers.go b/Godeps/_workspace/src/github.com/google/cadvisor/container/rkt/helpers.go new file mode 100644 index 00000000000..2ea7a20a4d0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/cadvisor/container/rkt/helpers.go @@ -0,0 +1,93 @@ +// Copyright 2016 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 rkt + +import ( + "fmt" + "io/ioutil" + "path" + "strings" + + "github.com/golang/glog" +) + +type parsedName struct { + Pod string + Container string +} + +func verifyName(name string) (bool, error) { + _, err := parseName(name) + return err == nil, err +} + +/* Parse cgroup name into a pod/container name struct + Example cgroup fs name + + pod - /sys/fs/cgroup/cpu/machine.slice/machine-rkt\\x2df556b64a\\x2d17a7\\x2d47d7\\x2d93ec\\x2def2275c3d67e.scope/ + container under pod - /sys/fs/cgroup/cpu/machine.slice/machine-rkt\\x2df556b64a\\x2d17a7\\x2d47d7\\x2d93ec\\x2def2275c3d67e.scope/system.slice/alpine-sh.service +*/ +//TODO{sjpotter}: this currently only recognizes machined started pods, which actually doesn't help with k8s which uses them as systemd services, need a solution for both +func parseName(name string) (*parsedName, error) { + splits := strings.Split(name, "/") + if len(splits) == 3 || len(splits) == 5 { + parsed := &parsedName{} + + if splits[1] == "machine.slice" { + replacer := strings.NewReplacer("machine-rkt\\x2d", "", ".scope", "", "\\x2d", "-") + parsed.Pod = replacer.Replace(splits[2]) + if len(splits) == 3 { + return parsed, nil + } + if splits[3] == "system.slice" { + parsed.Container = strings.Replace(splits[4], ".service", "", -1) + return parsed, nil + } + } + } + + return nil, fmt.Errorf("%s not handled by rkt handler", name) +} + +// Gets a Rkt container's overlay upper dir +func getRootFs(root string, parsed *parsedName) string { + /* Example of where it stores the upper dir key + for container + /var/lib/rkt/pods/run/bc793ec6-c48f-4480-99b5-6bec16d52210/appsinfo/alpine-sh/treeStoreID + for pod + /var/lib/rkt/pods/run/f556b64a-17a7-47d7-93ec-ef2275c3d67e/stage1TreeStoreID + + */ + + var tree string + if parsed.Container == "" { + tree = path.Join(root, "pods/run", parsed.Pod, "stage1TreeStoreID") + } else { + tree = path.Join(root, "pods/run", parsed.Pod, "appsinfo", parsed.Container, "treeStoreID") + } + + bytes, err := ioutil.ReadFile(tree) + if err != nil { + glog.Infof("ReadFile failed, couldn't read %v to get upper dir: %v", tree, err) + return "" + } + + s := string(bytes) + + /* Example of where the upper dir is stored via key read above + /var/lib/rkt/pods/run/bc793ec6-c48f-4480-99b5-6bec16d52210/overlay/deps-sha512-82a099e560a596662b15dec835e9adabab539cad1f41776a30195a01a8f2f22b/ + */ + return path.Join(root, "pods/run", parsed.Pod, "overlay", s) +} diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/container/systemd/factory.go b/Godeps/_workspace/src/github.com/google/cadvisor/container/systemd/factory.go new file mode 100644 index 00000000000..635c9c78527 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/cadvisor/container/systemd/factory.go @@ -0,0 +1,57 @@ +// Copyright 2016 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 systemd + +import ( + "fmt" + "strings" + + "github.com/google/cadvisor/container" + "github.com/google/cadvisor/fs" + info "github.com/google/cadvisor/info/v1" + + "github.com/golang/glog" +) + +type systemdFactory struct{} + +func (f *systemdFactory) String() string { + return "systemd" +} + +func (f *systemdFactory) NewContainerHandler(name string, inHostNamespace bool) (container.ContainerHandler, error) { + return nil, fmt.Errorf("Not yet supported") +} + +func (f *systemdFactory) CanHandleAndAccept(name string) (bool, bool, error) { + // on systemd using devicemapper each mount into the container has an associated cgroup that we ignore. + // for details on .mount units: http://man7.org/linux/man-pages/man5/systemd.mount.5.html + if strings.HasSuffix(name, ".mount") { + return true, false, nil + } + return false, false, fmt.Errorf("%s not handled by systemd handler", name) +} + +func (f *systemdFactory) DebugInfo() map[string][]string { + return map[string][]string{} +} + +// Register registers the systemd container factory. +func Register(machineInfoFactory info.MachineInfoFactory, fsInfo fs.FsInfo, ignoreMetrics container.MetricSet) error { + glog.Infof("Registering systemd factory") + factory := &systemdFactory{} + container.RegisterContainerHandlerFactory(factory) + return nil +} diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/fs/fs.go b/Godeps/_workspace/src/github.com/google/cadvisor/fs/fs.go index d6e9e27a06b..72ae4babec5 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/fs/fs.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/fs/fs.go @@ -19,7 +19,6 @@ package fs import ( "bufio" - "encoding/json" "fmt" "io/ioutil" "os" @@ -40,6 +39,7 @@ import ( const ( LabelSystemRoot = "root" LabelDockerImages = "docker-images" + LabelRktImages = "rkt-images" ) type partition struct { @@ -62,8 +62,14 @@ type RealFsInfo struct { type Context struct { // docker root directory. - DockerRoot string - DockerInfo map[string]string + Docker DockerContext + RktPath string +} + +type DockerContext struct { + Root string + Driver string + DriverStatus map[string]string } func NewFsInfo(context Context) (FsInfo, error) { @@ -76,6 +82,11 @@ func NewFsInfo(context Context) (FsInfo, error) { labels: make(map[string]string, 0), dmsetup: &defaultDmsetupClient{}, } + + fsInfo.addSystemRootLabel(mounts) + fsInfo.addDockerImagesLabel(context, mounts) + fsInfo.addRktImagesLabel(context, mounts) + supportedFsType := map[string]bool{ // all ext systems are checked through prefix. "btrfs": true, @@ -102,12 +113,7 @@ func NewFsInfo(context Context) (FsInfo, error) { } } - // need to call this before the log line below printing out the partitions, as this function may - // add a "partition" for devicemapper to fsInfo.partitions - fsInfo.addDockerImagesLabel(context) - glog.Infof("Filesystem partitions: %+v", fsInfo.partitions) - fsInfo.addSystemRootLabel() return fsInfo, nil } @@ -115,22 +121,17 @@ func NewFsInfo(context Context) (FsInfo, error) { // docker is using devicemapper for its storage driver. If a loopback device is being used, don't // return any information or error, as we want to report based on the actual partition where the // loopback file resides, inside of the loopback file itself. -func (self *RealFsInfo) getDockerDeviceMapperInfo(dockerInfo map[string]string) (string, *partition, error) { - if storageDriver, ok := dockerInfo["Driver"]; ok && storageDriver != DeviceMapper.String() { +func (self *RealFsInfo) getDockerDeviceMapperInfo(context DockerContext) (string, *partition, error) { + if context.Driver != DeviceMapper.String() { return "", nil, nil } - var driverStatus [][]string - if err := json.Unmarshal([]byte(dockerInfo["DriverStatus"]), &driverStatus); err != nil { - return "", nil, err - } - - dataLoopFile := dockerStatusValue(driverStatus, "Data loop file") + dataLoopFile := context.DriverStatus["Data loop file"] if len(dataLoopFile) > 0 { return "", nil, nil } - dev, major, minor, blockSize, err := dockerDMDevice(driverStatus, self.dmsetup) + dev, major, minor, blockSize, err := dockerDMDevice(context.DriverStatus, self.dmsetup) if err != nil { return "", nil, err } @@ -144,19 +145,24 @@ func (self *RealFsInfo) getDockerDeviceMapperInfo(dockerInfo map[string]string) } // addSystemRootLabel attempts to determine which device contains the mount for /. -func (self *RealFsInfo) addSystemRootLabel() { - for src, p := range self.partitions { - if p.mountpoint == "/" { - if _, ok := self.labels[LabelSystemRoot]; !ok { - self.labels[LabelSystemRoot] = src +func (self *RealFsInfo) addSystemRootLabel(mounts []*mount.Info) { + for _, m := range mounts { + if m.Mountpoint == "/" { + self.partitions[m.Source] = partition{ + fsType: m.Fstype, + mountpoint: m.Mountpoint, + major: uint(m.Major), + minor: uint(m.Minor), } + self.labels[LabelSystemRoot] = m.Source + return } } } // addDockerImagesLabel attempts to determine which device contains the mount for docker images. -func (self *RealFsInfo) addDockerImagesLabel(context Context) { - dockerDev, dockerPartition, err := self.getDockerDeviceMapperInfo(context.DockerInfo) +func (self *RealFsInfo) addDockerImagesLabel(context Context, mounts []*mount.Info) { + dockerDev, dockerPartition, err := self.getDockerDeviceMapperInfo(context.Docker) if err != nil { glog.Warningf("Could not get Docker devicemapper device: %v", err) } @@ -164,48 +170,64 @@ func (self *RealFsInfo) addDockerImagesLabel(context Context) { self.partitions[dockerDev] = *dockerPartition self.labels[LabelDockerImages] = dockerDev } else { - dockerPaths := getDockerImagePaths(context) + self.updateContainerImagesPath(LabelDockerImages, mounts, getDockerImagePaths(context)) + } +} - for src, p := range self.partitions { - self.updateDockerImagesPath(src, p.mountpoint, dockerPaths) +func (self *RealFsInfo) addRktImagesLabel(context Context, mounts []*mount.Info) { + if context.RktPath != "" { + rktPath := context.RktPath + rktImagesPaths := map[string]struct{}{ + "/": {}, } + for rktPath != "/" && rktPath != "." { + rktImagesPaths[rktPath] = struct{}{} + rktPath = filepath.Dir(rktPath) + } + self.updateContainerImagesPath(LabelRktImages, mounts, rktImagesPaths) } } // Generate a list of possible mount points for docker image management from the docker root directory. // Right now, we look for each type of supported graph driver directories, but we can do better by parsing // some of the context from `docker info`. -func getDockerImagePaths(context Context) []string { +func getDockerImagePaths(context Context) map[string]struct{} { + dockerImagePaths := map[string]struct{}{ + "/": {}, + } + // TODO(rjnagal): Detect docker root and graphdriver directories from docker info. - dockerRoot := context.DockerRoot - dockerImagePaths := []string{} + dockerRoot := context.Docker.Root for _, dir := range []string{"devicemapper", "btrfs", "aufs", "overlay", "zfs"} { - dockerImagePaths = append(dockerImagePaths, path.Join(dockerRoot, dir)) + dockerImagePaths[path.Join(dockerRoot, dir)] = struct{}{} } for dockerRoot != "/" && dockerRoot != "." { - dockerImagePaths = append(dockerImagePaths, dockerRoot) + dockerImagePaths[dockerRoot] = struct{}{} dockerRoot = filepath.Dir(dockerRoot) } - dockerImagePaths = append(dockerImagePaths, "/") return dockerImagePaths } -// This method compares the mountpoint with possible docker image mount points. If a match is found, -// docker images label is added to the partition. -func (self *RealFsInfo) updateDockerImagesPath(source string, mountpoint string, dockerImagePaths []string) { - for _, v := range dockerImagePaths { - if v == mountpoint { - if i, ok := self.labels[LabelDockerImages]; ok { - // pick the innermost mountpoint. - mnt := self.partitions[i].mountpoint - if len(mnt) < len(mountpoint) { - self.labels[LabelDockerImages] = source - } - } else { - self.labels[LabelDockerImages] = source +// This method compares the mountpoints with possible container image mount points. If a match is found, +// the label is added to the partition. +func (self *RealFsInfo) updateContainerImagesPath(label string, mounts []*mount.Info, containerImagePaths map[string]struct{}) { + var useMount *mount.Info + for _, m := range mounts { + if _, ok := containerImagePaths[m.Mountpoint]; ok { + if useMount == nil || (len(useMount.Mountpoint) < len(m.Mountpoint)) { + useMount = m } } } + if useMount != nil { + self.partitions[useMount.Source] = partition{ + fsType: useMount.Fstype, + mountpoint: useMount.Mountpoint, + major: uint(useMount.Major), + minor: uint(useMount.Minor), + } + self.labels[label] = useMount.Source + } } func (self *RealFsInfo) GetDeviceForLabel(label string) (string, error) { @@ -412,15 +434,6 @@ func getVfsStats(path string) (total uint64, free uint64, avail uint64, inodes u return total, free, avail, inodes, inodesFree, nil } -func dockerStatusValue(status [][]string, target string) string { - for _, v := range status { - if len(v) == 2 && strings.ToLower(v[0]) == strings.ToLower(target) { - return v[1] - } - } - return "" -} - // dmsetupClient knows to to interact with dmsetup to retrieve information about devicemapper. type dmsetupClient interface { table(poolName string) ([]byte, error) @@ -438,9 +451,9 @@ func (*defaultDmsetupClient) table(poolName string) ([]byte, error) { // Devicemapper thin provisioning is detailed at // https://www.kernel.org/doc/Documentation/device-mapper/thin-provisioning.txt -func dockerDMDevice(driverStatus [][]string, dmsetup dmsetupClient) (string, uint, uint, uint, error) { - poolName := dockerStatusValue(driverStatus, "Pool Name") - if len(poolName) == 0 { +func dockerDMDevice(driverStatus map[string]string, dmsetup dmsetupClient) (string, uint, uint, uint, error) { + poolName, ok := driverStatus["Pool Name"] + if !ok || len(poolName) == 0 { return "", 0, 0, 0, fmt.Errorf("Could not get dm pool name") } diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/manager/manager.go b/Godeps/_workspace/src/github.com/google/cadvisor/manager/manager.go index eb8d2cb9cbb..1179303a607 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/manager/manager.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/manager/manager.go @@ -16,7 +16,6 @@ package manager import ( - "encoding/json" "flag" "fmt" "os" @@ -31,6 +30,8 @@ import ( "github.com/google/cadvisor/container" "github.com/google/cadvisor/container/docker" "github.com/google/cadvisor/container/raw" + "github.com/google/cadvisor/container/rkt" + "github.com/google/cadvisor/container/systemd" "github.com/google/cadvisor/events" "github.com/google/cadvisor/fs" info "github.com/google/cadvisor/info/v1" @@ -60,7 +61,7 @@ type Manager interface { // Stops the manager. Stop() error - // Get information about a container. + // information about a container. GetContainerInfo(containerName string, query *info.ContainerInfoRequest) (*info.ContainerInfo, error) // Get V2 information about a container. @@ -131,11 +132,23 @@ func New(memoryCache *memory.InMemoryCache, sysfs sysfs.SysFs, maxHousekeepingIn } glog.Infof("cAdvisor running in container: %q", selfContainer) - dockerInfo, err := docker.DockerInfo() + dockerInfo, err := dockerInfo() if err != nil { glog.Warningf("Unable to connect to Docker: %v", err) } - context := fs.Context{DockerRoot: docker.RootDir(), DockerInfo: dockerInfo} + rktPath, err := rkt.RktPath() + if err != nil { + glog.Warningf("unable to connect to Rkt api service: %v", err) + } + + context := fs.Context{ + Docker: fs.DockerContext{ + Root: docker.RootDir(), + Driver: dockerInfo.Driver, + DriverStatus: dockerInfo.DriverStatus, + }, + RktPath: rktPath, + } fsInfo, err := fs.NewFsInfo(context) if err != nil { return nil, err @@ -206,13 +219,21 @@ type manager struct { // Start the container manager. func (self *manager) Start() error { - // Register Docker container factory. err := docker.Register(self, self.fsInfo, self.ignoreMetrics) if err != nil { glog.Errorf("Docker container factory registration failed: %v.", err) } - // Register the raw driver. + err = rkt.Register(self, self.fsInfo, self.ignoreMetrics) + if err != nil { + glog.Errorf("Registration of the rkt container factory failed: %v", err) + } + + err = systemd.Register(self, self.fsInfo, self.ignoreMetrics) + if err != nil { + glog.Errorf("Registration of the systemd container factory failed: %v", err) + } + err = raw.Register(self, self.fsInfo, self.ignoreMetrics) if err != nil { glog.Errorf("Registration of the raw container factory failed: %v", err) @@ -1150,58 +1171,31 @@ func (m *manager) DockerImages() ([]DockerImage, error) { } func (m *manager) DockerInfo() (DockerStatus, error) { - info, err := docker.DockerInfo() + return dockerInfo() +} + +func dockerInfo() (DockerStatus, error) { + dockerInfo, err := docker.DockerInfo() if err != nil { return DockerStatus{}, err } - versionInfo, err := m.GetVersionInfo() + versionInfo, err := getVersionInfo() if err != nil { return DockerStatus{}, err } out := DockerStatus{} out.Version = versionInfo.DockerVersion - if val, ok := info["KernelVersion"]; ok { - out.KernelVersion = val - } - if val, ok := info["OperatingSystem"]; ok { - out.OS = val - } - if val, ok := info["Name"]; ok { - out.Hostname = val - } - if val, ok := info["DockerRootDir"]; ok { - out.RootDir = val - } - if val, ok := info["Driver"]; ok { - out.Driver = val - } - if val, ok := info["ExecutionDriver"]; ok { - out.ExecDriver = val - } - if val, ok := info["Images"]; ok { - n, err := strconv.Atoi(val) - if err == nil { - out.NumImages = n - } - } - if val, ok := info["Containers"]; ok { - n, err := strconv.Atoi(val) - if err == nil { - out.NumContainers = n - } - } - if val, ok := info["DriverStatus"]; ok { - var driverStatus [][]string - err := json.Unmarshal([]byte(val), &driverStatus) - if err != nil { - return DockerStatus{}, err - } - out.DriverStatus = make(map[string]string) - for _, v := range driverStatus { - if len(v) == 2 { - out.DriverStatus[v[0]] = v[1] - } - } + out.KernelVersion = dockerInfo.KernelVersion + out.OS = dockerInfo.OperatingSystem + out.Hostname = dockerInfo.Name + out.RootDir = dockerInfo.DockerRootDir + out.Driver = dockerInfo.Driver + out.ExecDriver = dockerInfo.ExecutionDriver + out.NumImages = dockerInfo.Images + out.NumContainers = dockerInfo.Containers + out.DriverStatus = make(map[string]string, len(dockerInfo.DriverStatus)) + for _, v := range dockerInfo.DriverStatus { + out.DriverStatus[v[0]] = v[1] } return out, nil } diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/utils/cpuload/netlink/netlink.go b/Godeps/_workspace/src/github.com/google/cadvisor/utils/cpuload/netlink/netlink.go index 7ca05f361eb..e32b3fd9bda 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/utils/cpuload/netlink/netlink.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/utils/cpuload/netlink/netlink.go @@ -18,6 +18,7 @@ import ( "bytes" "encoding/binary" "fmt" + "os" "syscall" info "github.com/google/cadvisor/info/v1" @@ -219,10 +220,10 @@ func verifyHeader(msg syscall.NetlinkMessage) error { // Get load stats for a task group. // id: family id for taskstats. -// fd: fd to path to the cgroup directory under cpu hierarchy. +// cfd: open file to path to the cgroup directory under cpu hierarchy. // conn: open netlink connection used to communicate with kernel. -func getLoadStats(id uint16, fd uintptr, conn *Connection) (info.LoadStats, error) { - msg := prepareCmdMessage(id, fd) +func getLoadStats(id uint16, cfd *os.File, conn *Connection) (info.LoadStats, error) { + msg := prepareCmdMessage(id, cfd.Fd()) err := conn.WriteMessage(msg.toRawMsg()) if err != nil { return info.LoadStats{}, err diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/utils/cpuload/netlink/reader.go b/Godeps/_workspace/src/github.com/google/cadvisor/utils/cpuload/netlink/reader.go index 4e3aa681d59..1f43d755e17 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/utils/cpuload/netlink/reader.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/utils/cpuload/netlink/reader.go @@ -66,11 +66,12 @@ func (self *NetlinkReader) GetCpuLoad(name string, path string) (info.LoadStats, } cfd, err := os.Open(path) + defer cfd.Close() if err != nil { return info.LoadStats{}, fmt.Errorf("failed to open cgroup path %s: %q", path, err) } - stats, err := getLoadStats(self.familyId, cfd.Fd(), self.conn) + stats, err := getLoadStats(self.familyId, cfd, self.conn) if err != nil { return info.LoadStats{}, err } diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/validate/validate.go b/Godeps/_workspace/src/github.com/google/cadvisor/validate/validate.go index 2ccbbb49291..385a798f304 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/validate/validate.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/validate/validate.go @@ -185,28 +185,19 @@ func validateCgroups() (string, string) { } func validateDockerInfo() (string, string) { - client, err := docker.Client() - if err == nil { - info, err := client.Info() - if err == nil { - execDriver := info.Get("ExecutionDriver") - storageDriver := info.Get("Driver") - desc := fmt.Sprintf("Docker exec driver is %s. Storage driver is %s.\n", execDriver, storageDriver) - if strings.Contains(execDriver, "native") { - stateFile := docker.DockerStateDir() - if !utils.FileExists(stateFile) { - desc += fmt.Sprintf("\tDocker container state directory %q is not accessible.\n", stateFile) - return Unsupported, desc - } - desc += fmt.Sprintf("\tDocker container state directory is at %q and is accessible.\n", stateFile) - return Recommended, desc - } else if strings.Contains(execDriver, "lxc") { - return Supported, desc - } - return Unknown, desc - } + info, err := docker.ValidateInfo() + if err != nil { + return Unsupported, fmt.Sprintf("Docker setup is invalid: %v", err) } - return Unknown, "Docker remote API not reachable\n\t" + + desc := fmt.Sprintf("Docker exec driver is %s. Storage driver is %s.\n", info.ExecutionDriver, info.Driver) + stateFile := docker.DockerStateDir() + if !utils.FileExists(stateFile) { + desc += fmt.Sprintf("\tDocker container state directory %q is not accessible.\n", stateFile) + return Unsupported, desc + } + desc += fmt.Sprintf("\tDocker container state directory is at %q and is accessible.\n", stateFile) + return Recommended, desc } func validateCgroupMounts() (string, string) { diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/version/VERSION b/Godeps/_workspace/src/github.com/google/cadvisor/version/VERSION index a7f3fc27a7a..ca222b7cf39 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/version/VERSION +++ b/Godeps/_workspace/src/github.com/google/cadvisor/version/VERSION @@ -1 +1 @@ -0.22.0 \ No newline at end of file +0.23.0