Skip to content

Commit

Permalink
Merge pull request google#1084 from vishh/docker-v1.10
Browse files Browse the repository at this point in the history
Track fs usage for docker versions >= 1.10
  • Loading branch information
vishh committed Feb 4, 2016
2 parents c924829 + f0fd525 commit 05068c1
Show file tree
Hide file tree
Showing 10 changed files with 236 additions and 23 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ test:
@echo ">> running tests"
@$(GO) test -short -race $(pkgs)

test-integration: test
test-integration: build test
@./build/integration.sh

format:
Expand Down
13 changes: 10 additions & 3 deletions api/versions.go
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ func (self *version2_1) Version() string {
}

func (self *version2_1) SupportedRequestTypes() []string {
return self.baseVersion.SupportedRequestTypes()
return append([]string{machineStatsApi}, self.baseVersion.SupportedRequestTypes()...)
}

func (self *version2_1) HandleRequest(requestType string, request []string, m manager.Manager, w http.ResponseWriter, r *http.Request) error {
Expand All @@ -492,9 +492,16 @@ func (self *version2_1) HandleRequest(requestType string, request []string, m ma
if err != nil {
return err
}
contStats := make(map[string][]*v2.ContainerStats, len(conts))
contStats := make(map[string]v2.ContainerInfo, len(conts))
for name, cont := range conts {
contStats[name] = v2.ContainerStatsFromV1(&cont.Spec, cont.Stats)
if name == "/" {
// Root cgroup stats should be exposed as machine stats
continue
}
contStats[name] = v2.ContainerInfo{
Spec: v2.ContainerSpecFromV1(&cont.Spec, cont.Aliases, cont.Namespace),
Stats: v2.ContainerStatsFromV1(&cont.Spec, cont.Stats),
}
}
return writeResult(contStats, w)
default:
Expand Down
37 changes: 30 additions & 7 deletions client/v2/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"path"
"strconv"
"strings"

v1 "github.com/google/cadvisor/info/v1"
Expand Down Expand Up @@ -85,6 +87,23 @@ func (self *Client) Attributes() (attr *v2.Attributes, err error) {
return
}

// Stats returns stats for the requested container.
func (self *Client) Stats(name string, request *v2.RequestOptions) (map[string]v2.ContainerInfo, error) {
u := self.statsUrl(name)
ret := make(map[string]v2.ContainerInfo)
data := url.Values{
"type": []string{request.IdType},
"count": []string{strconv.Itoa(request.Count)},
"recursive": []string{strconv.FormatBool(request.Recursive)},
}

u = fmt.Sprintf("%s?%s", u, data.Encode())
if err := self.httpGetJsonData(&ret, nil, u, "stats"); err != nil {
return nil, err
}
return ret, nil
}

func (self *Client) machineInfoUrl() string {
return self.baseUrl + path.Join("machine")
}
Expand All @@ -101,7 +120,11 @@ func (self *Client) attributesUrl() string {
return self.baseUrl + path.Join("attributes")
}

func (self *Client) httpGetResponse(postData interface{}, url, infoName string) ([]byte, error) {
func (self *Client) statsUrl(name string) string {
return path.Join(self.baseUrl, "stats", name)
}

func (self *Client) httpGetResponse(postData interface{}, urlPath, infoName string) ([]byte, error) {
var resp *http.Response
var err error

Expand All @@ -110,24 +133,24 @@ func (self *Client) httpGetResponse(postData interface{}, url, infoName string)
if marshalErr != nil {
return nil, fmt.Errorf("unable to marshal data: %v", marshalErr)
}
resp, err = http.Post(url, "application/json", bytes.NewBuffer(data))
resp, err = http.Post(urlPath, "application/json", bytes.NewBuffer(data))
} else {
resp, err = http.Get(url)
resp, err = http.Get(urlPath)
}
if err != nil {
return nil, fmt.Errorf("unable to post %q to %q: %v", infoName, url, err)
return nil, fmt.Errorf("unable to post %q to %q: %v", infoName, urlPath, err)
}
if resp == nil {
return nil, fmt.Errorf("received empty response for %q from %q", infoName, url)
return nil, fmt.Errorf("received empty response for %q from %q", infoName, urlPath)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
err = fmt.Errorf("unable to read all %q from %q: %v", infoName, url, err)
err = fmt.Errorf("unable to read all %q from %q: %v", infoName, urlPath, err)
return nil, err
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("request %q failed with error: %q", url, strings.TrimSpace(string(body)))
return nil, fmt.Errorf("request %q failed with error: %q", urlPath, strings.TrimSpace(string(body)))
}
return body, nil
}
Expand Down
20 changes: 13 additions & 7 deletions container/docker/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ type dockerFactory struct {

// Information about mounted filesystems.
fsInfo fs.FsInfo

dockerVersion []int
}

func (self *dockerFactory) String() string {
Expand All @@ -140,6 +142,7 @@ func (self *dockerFactory) NewContainerHandler(name string, inHostNamespace bool
&self.cgroupSubsystems,
inHostNamespace,
metadataEnvs,
self.dockerVersion,
)
return
}
Expand Down Expand Up @@ -253,20 +256,21 @@ func Register(factory info.MachineInfoFactory, fsInfo fs.FsInfo) error {
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")
version, err := parseDockerVersion(version_string)
dockerVersion, err = parseDockerVersion(version_string)
if err != nil {
return fmt.Errorf("couldn't parse docker version: %v", err)
}
for index, number := range version {
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, version, version_string)
return fmt.Errorf("cAdvisor requires docker version %v or above but we have found version %v reported as \"%v\"", expected_version, dockerVersion, version_string)
}
}
}
Expand All @@ -289,7 +293,8 @@ func Register(factory info.MachineInfoFactory, fsInfo fs.FsInfo) error {

storageDir, err := getStorageDir(information)
if err != nil {
return err
glog.V(2).Infof("failed to detect storage directory from docker. Defaulting to using the value in --docker_root: %q", err, *dockerRootDir)
storageDir = path.Join(*dockerRootDir, sd)
}
cgroupSubsystems, err := libcontainer.GetCgroupSubsystems()
if err != nil {
Expand All @@ -298,12 +303,13 @@ func Register(factory info.MachineInfoFactory, fsInfo fs.FsInfo) error {

glog.Infof("Registering Docker factory")
f := &dockerFactory{
machineInfoFactory: factory,
cgroupSubsystems: cgroupSubsystems,
client: client,
dockerVersion: dockerVersion,
fsInfo: fsInfo,
machineInfoFactory: factory,
storageDriver: storageDriver(sd),
storageDriverDir: storageDir,
cgroupSubsystems: cgroupSubsystems,
fsInfo: fsInfo,
}
container.RegisterContainerHandlerFactory(f)
return nil
Expand Down
29 changes: 27 additions & 2 deletions container/docker/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package docker

import (
"fmt"
"io/ioutil"
"math"
"path"
"strings"
Expand Down Expand Up @@ -82,6 +83,24 @@ type dockerContainerHandler struct {
fsHandler fsHandler
}

func getRwLayerID(containerID, storageDriverDir string, dockerVersion []int) (string, error) {
const (
// Docker version >=1.10.0 have a randomized ID for the root fs of a container.
randomizedRWLayerMinorVersion = 10
// Directory where the file containinig the randomized ID of the root fs of a container is stored in versions >= 1.10.0
rwLayerIDDir = "../image/aufs/layerdb/mounts/"
rwLayerIDFile = "mount-id"
)
if (dockerVersion[0] <= 1) && (dockerVersion[1] < randomizedRWLayerMinorVersion) {
return containerID, nil
}
bytes, err := ioutil.ReadFile(path.Join(storageDriverDir, rwLayerIDDir, containerID, rwLayerIDFile))
if err != nil {
return "", fmt.Errorf("failed to identify the read-write layer ID for container %q. - %v", containerID, err)
}
return string(bytes), err
}

func newDockerContainerHandler(
client *docker.Client,
name string,
Expand All @@ -92,6 +111,7 @@ func newDockerContainerHandler(
cgroupSubsystems *containerlibcontainer.CgroupSubsystems,
inHostNamespace bool,
metadataEnvs []string,
dockerVersion []int,
) (container.ContainerHandler, error) {
// Create the cgroup paths.
cgroupPaths := make(map[string]string, len(cgroupSubsystems.MountPoints))
Expand All @@ -116,12 +136,17 @@ func newDockerContainerHandler(

// Add the Containers dir where the log files are stored.
otherStorageDir := path.Join(path.Dir(storageDriverDir), pathToContainersDir, id)

rwLayerID, err := getRwLayerID(id, storageDriverDir, dockerVersion)
if err != nil {
return nil, err
}
var rootfsStorageDir string
switch storageDriver {
case aufsStorageDriver:
rootfsStorageDir = path.Join(storageDriverDir, aufsRWLayer, id)
rootfsStorageDir = path.Join(storageDriverDir, aufsRWLayer, rwLayerID)
case overlayStorageDriver:
rootfsStorageDir = path.Join(storageDriverDir, id)
rootfsStorageDir = path.Join(storageDriverDir, rwLayerID)
}

handler := &dockerContainerHandler{
Expand Down
50 changes: 50 additions & 0 deletions container/docker/handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// 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.

// Handler for Docker containers.
package docker

import (
"io/ioutil"
"os"
"path"
"testing"

"github.com/stretchr/testify/assert"
)

func TestStorageDirDetectionWithOldVersions(t *testing.T) {
as := assert.New(t)
rwLayer, err := getRwLayerID("abcd", "/", []int{1, 9, 0})
as.Nil(err)
as.Equal(rwLayer, "abcd")
}

func TestStorageDirDetectionWithNewVersions(t *testing.T) {
as := assert.New(t)
testDir, err := ioutil.TempDir("", "")
as.Nil(err)
containerID := "abcd"
randomizedID := "xyz"
randomIDPath := path.Join(testDir, "image/aufs/layerdb/mounts/", containerID)
as.Nil(os.MkdirAll(randomIDPath, os.ModePerm))
as.Nil(ioutil.WriteFile(path.Join(randomIDPath, "mount-id"), []byte(randomizedID), os.ModePerm))
rwLayer, err := getRwLayerID(containerID, path.Join(testDir, "aufs"), []int{1, 10, 0})
as.Nil(err)
as.Equal(rwLayer, randomizedID)
rwLayer, err = getRwLayerID(containerID, path.Join(testDir, "aufs"), []int{1, 10, 0})
as.Nil(err)
as.Equal(rwLayer, randomizedID)

}
2 changes: 1 addition & 1 deletion info/v2/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ func ContainerStatsFromV1(spec *v1.ContainerSpec, stats []*v1.ContainerStats) []
}
} else if len(val.Filesystem) > 1 {
// Cannot handle multiple devices per container.
glog.Errorf("failed to handle multiple devices for container. Skipping Filesystem stats")
glog.V(2).Infof("failed to handle multiple devices for container. Skipping Filesystem stats")
}
}
if spec.HasDiskIo {
Expand Down
48 changes: 48 additions & 0 deletions integration/framework/framework.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@ func New(t *testing.T) Framework {
return fm
}

const (
Aufs string = "aufs"
Overlay string = "overlay"
DeviceMapper string = "devicemapper"
Unknown string = ""
)

type DockerActions interface {
// Run the no-op pause Docker container and return its ID.
RunPause() string
Expand All @@ -113,6 +120,9 @@ type DockerActions interface {
// -> docker run busybox ping www.google.com
Run(args DockerRunArgs, cmd ...string) string
RunStress(args DockerRunArgs, cmd ...string) string

Version() []string
StorageDriver() string
}

type ShellActions interface {
Expand Down Expand Up @@ -255,6 +265,44 @@ func (self dockerActions) Run(args DockerRunArgs, cmd ...string) string {
return containerId
}

func (self dockerActions) Version() []string {
dockerCommand := []string{"docker", "version", "-f", "'{{.Server.Version}}'"}
output, _ := self.fm.Shell().Run("sudo", dockerCommand...)
output = strings.TrimSpace(output)
ret := strings.Split(output, ".")
if len(ret) != 3 {
self.fm.T().Fatalf("invalid version %v", output)
}
return ret
}

func (self dockerActions) StorageDriver() string {
dockerCommand := []string{"docker", "info"}
output, _ := self.fm.Shell().Run("sudo", dockerCommand...)
if len(output) < 1 {
self.fm.T().Fatalf("failed to find docker storage driver - %v", output)
}
for _, line := range strings.Split(output, "\n") {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "Storage Driver: ") {
idx := strings.LastIndex(line, ": ") + 2
driver := line[idx:]
switch driver {
case Aufs:
return Aufs
case Overlay:
return Overlay
case DeviceMapper:
return DeviceMapper
default:
return Unknown
}
}
}
self.fm.T().Fatalf("failed to find docker storage driver from info - %v", output)
return Unknown
}

func (self dockerActions) RunStress(args DockerRunArgs, cmd ...string) string {
dockerCommand := append(append(append(append([]string{"docker", "run", "-m=4M", "-d", "-t", "-i"}, args.Args...), args.Image), args.InnerArgs...), cmd...)

Expand Down
2 changes: 1 addition & 1 deletion integration/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ func PushAndRunTests(host, testDir string) error {
if err != nil {
return fmt.Errorf("error reading local log file: %v", err)
}
glog.Errorf("%v", string(logs))
glog.Errorf("----------------------\nLogs from Host: %q\n%v\n", host, string(logs))
err = fmt.Errorf("error on host %s: %v\n%+v", host, err, attributes)
}
return err
Expand Down
Loading

0 comments on commit 05068c1

Please sign in to comment.