From 8aa05b0c39ce1a579d0adfa9ad98da3c00f78ac8 Mon Sep 17 00:00:00 2001 From: Victor Marmol Date: Mon, 20 Oct 2014 22:08:33 -0700 Subject: [PATCH] Add API version v1.2 with /docker endpoint. The /docker endpoint lists all Docker containers under one unified namespace. --- api/handler.go | 61 ++++++++++++++++++++++++-------- container/docker/factory.go | 57 ++++++++++++++++++++---------- container/docker/handler.go | 9 ++--- manager/manager.go | 69 ++++++++++++++++++++++++++++++++----- 4 files changed, 149 insertions(+), 47 deletions(-) diff --git a/api/handler.go b/api/handler.go index ff06819f..c155c110 100644 --- a/api/handler.go +++ b/api/handler.go @@ -34,14 +34,17 @@ const ( containersApi = "containers" subcontainersApi = "subcontainers" machineApi = "machine" + dockerApi = "docker" version1_0 = "v1.0" version1_1 = "v1.1" + version1_2 = "v1.2" ) var supportedApiVersions map[string]struct{} = map[string]struct{}{ version1_0: {}, version1_1: {}, + version1_2: {}, } func RegisterHandlers(m manager.Manager) error { @@ -78,6 +81,9 @@ func handleRequest(m manager.Manager, w http.ResponseWriter, r *http.Request) er requestArgs = requestElements[4:] } + // The container name is the path after the requestType. + containerName := path.Join("/", strings.Join(requestArgs, "/")) + // Check elements. if len(emptyElement) != 0 { return fmt.Errorf("unexpected API request format %q", request) @@ -99,15 +105,11 @@ func handleRequest(m manager.Manager, w http.ResponseWriter, r *http.Request) er return err } - out, err := json.Marshal(machineInfo) + err = writeResult(machineInfo, w) if err != nil { - fmt.Fprintf(w, "Failed to marshall MachineInfo with error: %s", err) + return err } - w.Write(out) case requestType == containersApi: - // The container name is the path after the requestType. - containerName := path.Join("/", strings.Join(requestArgs, "/")) - glog.V(2).Infof("Api - Container(%s)", containerName) // Get the query request. @@ -123,19 +125,15 @@ func handleRequest(m manager.Manager, w http.ResponseWriter, r *http.Request) er } // Only output the container as JSON. - out, err := json.Marshal(cont) + err = writeResult(cont, w) if err != nil { - return fmt.Errorf("failed to marshall container %q with error: %s", containerName, err) + return err } - w.Write(out) case requestType == subcontainersApi: if version == version1_0 { return fmt.Errorf("request type of %q not supported in API version %q", requestType, version) } - // The container name is the path after the requestType. - containerName := path.Join("/", strings.Join(requestArgs, "/")) - glog.V(2).Infof("Api - Subcontainers(%s)", containerName) // Get the query request. @@ -151,11 +149,34 @@ func handleRequest(m manager.Manager, w http.ResponseWriter, r *http.Request) er } // Only output the containers as JSON. - out, err := json.Marshal(containers) + err = writeResult(containers, w) if err != nil { - return fmt.Errorf("failed to marshall container %q with error: %s", containerName, err) + return err + } + case requestType == dockerApi: + if version == version1_0 || version == version1_1 { + return fmt.Errorf("request type of %q not supported in API version %q", requestType, version) + } + + glog.V(2).Infof("Api - Docker(%s)", containerName) + + // Get the query request. + query, err := getContainerInfoRequest(r.Body) + if err != nil { + return err + } + + // Get the Docker containers. + containers, err := m.DockerContainersInfo(containerName, query) + if err != nil { + return fmt.Errorf("failed to get docker containers for %q with error: %s", containerName, err) + } + + // Only output the containers as JSON. + err = writeResult(containers, w) + if err != nil { + return err } - w.Write(out) default: return fmt.Errorf("unknown API request type %q", requestType) } @@ -164,6 +185,16 @@ func handleRequest(m manager.Manager, w http.ResponseWriter, r *http.Request) er return nil } +func writeResult(res interface{}, w http.ResponseWriter) error { + out, err := json.Marshal(res) + if err != nil { + return fmt.Errorf("failed to marshall response %+v with error: %s", res, err) + } + + w.Write(out) + return nil +} + func getContainerInfoRequest(body io.ReadCloser) (*info.ContainerInfoRequest, error) { var query info.ContainerInfoRequest diff --git a/container/docker/factory.go b/container/docker/factory.go index dc916885..65a3a7a4 100644 --- a/container/docker/factory.go +++ b/container/docker/factory.go @@ -17,6 +17,7 @@ package docker import ( "flag" "fmt" + "path" "regexp" "strconv" "strings" @@ -33,12 +34,19 @@ var ArgDockerEndpoint = flag.String("docker", "unix:///var/run/docker.sock", "do // Basepath to all container specific information that libcontainer stores. var dockerRootDir = flag.String("docker_root", "/var/lib/docker", "Absolute path to the Docker state root directory (default: /var/lib/docker)") +// Whether the system is using Systemd. +var useSystemd bool + +func init() { + useSystemd = systemd.UseSystemd() + if useSystemd { + glog.Infof("System is using systemd") + } +} + type dockerFactory struct { machineInfoFactory info.MachineInfoFactory - // Whether this system is using systemd. - useSystemd bool - // Whether docker is running with AUFS storage driver. usesAufsDriver bool @@ -58,31 +66,48 @@ func (self *dockerFactory) NewContainerHandler(name string) (handler container.C client, name, self.machineInfoFactory, - self.useSystemd, *dockerRootDir, self.usesAufsDriver, ) return } +// Returns whether the specified full container name corresponds to a Docker container. +func IsDockerContainerName(name string) bool { + if useSystemd { + // In systemd systems the containers are: /system.slice/docker-{ID} + return strings.HasPrefix(name, "/system.slice/docker-") + } else { + return strings.HasPrefix(name, "/docker/") + } +} + +// Returns a list of possible full container names for the specified Docker container name. +func FullDockerContainerNames(dockerName string) []string { + names := make([]string, 0, 2) + + // Add the full container name. + if useSystemd { + names = append(names, path.Join("/system.slice", fmt.Sprintf("docker-%s.scope", dockerName))) + } else { + names = append(names, path.Join("/docker", dockerName)) + } + + // Add the Docker alias name. + return append(names, path.Join("/docker", dockerName)) +} + // Docker handles all containers under /docker func (self *dockerFactory) CanHandle(name string) (bool, error) { - // In systemd systems the containers are: /system.slice/docker-{ID} - if self.useSystemd { - if !strings.HasPrefix(name, "/system.slice/docker-") { - return false, nil - } - } else if name == "/" { - return false, nil - } else if name == "/docker" { + if name == "/docker" { // We need the docker driver to handle /docker. Otherwise the aggregation at the API level will break. return true, nil - } else if !strings.HasPrefix(name, "/docker/") { + } else if !IsDockerContainerName(name) { return false, nil } // Check if the container is known to docker and it is active. - id := containerNameToDockerId(name, self.useSystemd) + id := containerNameToDockerId(name) // We assume that if Inspect fails then the container is not known to docker. ctnr, err := self.client.InspectContainer(id) @@ -162,13 +187,9 @@ func Register(factory info.MachineInfoFactory) error { f := &dockerFactory{ machineInfoFactory: factory, - useSystemd: systemd.UseSystemd(), client: client, usesAufsDriver: usesAufsDriver, } - if f.useSystemd { - glog.Infof("System is using systemd") - } container.RegisterContainerHandlerFactory(f) return nil } diff --git a/container/docker/handler.go b/container/docker/handler.go index b7208e83..df3d1c8f 100644 --- a/container/docker/handler.go +++ b/container/docker/handler.go @@ -51,7 +51,6 @@ type dockerContainerHandler struct { id string aliases []string machineInfoFactory info.MachineInfoFactory - useSystemd bool libcontainerStateDir string cgroup cgroups.Cgroup usesAufsDriver bool @@ -63,7 +62,6 @@ func newDockerContainerHandler( client *docker.Client, name string, machineInfoFactory info.MachineInfoFactory, - useSystemd bool, dockerRootDir string, usesAufsDriver bool, ) (container.ContainerHandler, error) { @@ -75,7 +73,6 @@ func newDockerContainerHandler( client: client, name: name, machineInfoFactory: machineInfoFactory, - useSystemd: useSystemd, libcontainerStateDir: path.Join(dockerRootDir, pathToLibcontainerState), cgroup: cgroups.Cgroup{ Parent: "/", @@ -88,7 +85,7 @@ func newDockerContainerHandler( if handler.isDockerRoot() { return handler, nil } - id := containerNameToDockerId(name, useSystemd) + id := containerNameToDockerId(name) handler.id = id ctnr, err := client.InspectContainer(id) // We assume that if Inspect fails then the container is not known to docker. @@ -99,7 +96,7 @@ func newDockerContainerHandler( return handler, nil } -func containerNameToDockerId(name string, useSystemd bool) string { +func containerNameToDockerId(name string) string { id := path.Base(name) // Turn systemd cgroup name into Docker ID. @@ -320,7 +317,7 @@ func (self *dockerContainerHandler) ListContainers(listType container.ListType) // On non-systemd systems Docker containers are under /docker. containerPrefix := "/docker" - if self.useSystemd { + if useSystemd { containerPrefix = "/system.slice" } diff --git a/manager/manager.go b/manager/manager.go index 6a07864d..e7f80271 100644 --- a/manager/manager.go +++ b/manager/manager.go @@ -19,6 +19,7 @@ import ( "flag" "fmt" "path" + "regexp" "strings" "sync" "time" @@ -26,6 +27,7 @@ import ( "github.com/docker/libcontainer/cgroups" "github.com/golang/glog" "github.com/google/cadvisor/container" + "github.com/google/cadvisor/container/docker" "github.com/google/cadvisor/info" "github.com/google/cadvisor/storage" ) @@ -48,6 +50,10 @@ type Manager interface { // Get information about all subcontainers of the specified container (includes self). SubcontainersInfo(containerName string, query *info.ContainerInfoRequest) ([]*info.ContainerInfo, error) + // Get information for the specified Docker container (or "/" for all Docker containers). + // Full container names here are interpreted within the Docker namespace (e.g.: /test -> top-level Docker container named 'test'). + DockerContainersInfo(containerName string, query *info.ContainerInfoRequest) ([]*info.ContainerInfo, error) + // Get information about the machine. GetMachineInfo() (*info.MachineInfo, error) @@ -94,13 +100,14 @@ func New(driver storage.StorageDriver) (Manager, error) { } type manager struct { - containers map[string]*containerData - containersLock sync.RWMutex - storageDriver storage.StorageDriver - machineInfo info.MachineInfo - versionInfo info.VersionInfo - quitChannels []chan error - cadvisorContainer string + containers map[string]*containerData + containersLock sync.RWMutex + storageDriver storage.StorageDriver + machineInfo info.MachineInfo + versionInfo info.VersionInfo + quitChannels []chan error + cadvisorContainer string + dockerContainersRegexp *regexp.Regexp } // Start the container manager. @@ -249,8 +256,54 @@ func (self *manager) SubcontainersInfo(containerName string, query *info.Contain } } }() + + return self.containerDataSliceToContainerInfoSlice(containers, query) +} + +func (self *manager) DockerContainersInfo(containerName string, query *info.ContainerInfoRequest) ([]*info.ContainerInfo, error) { + var containers []*containerData + err := func() error { + self.containersLock.RLock() + defer self.containersLock.RUnlock() + containers = make([]*containerData, 0, len(self.containers)) + + if containerName == "/" { + // Get all the Docker containers. + for i := range self.containers { + if docker.IsDockerContainerName(self.containers[i].info.Name) { + containers = append(containers, self.containers[i]) + } + } + } else { + // Strip "/" + containerName = strings.Trim(containerName, "/") + + // Get the specified container (check all possible Docker names). + possibleNames := docker.FullDockerContainerNames(containerName) + found := false + for _, fullName := range possibleNames { + cont, ok := self.containers[fullName] + if ok { + containers = append(containers, cont) + break + } + } + if !found { + return fmt.Errorf("unable to find Docker container %q with full names %v", containerName, possibleNames) + } + } + return nil + }() + if err != nil { + return nil, err + } + + return self.containerDataSliceToContainerInfoSlice(containers, query) +} + +func (self *manager) containerDataSliceToContainerInfoSlice(containers []*containerData, query *info.ContainerInfoRequest) ([]*info.ContainerInfo, error) { if len(containers) == 0 { - return nil, fmt.Errorf("unknown container %q", containerName) + return nil, fmt.Errorf("no containers found") } // Get the info for each container.