Add API version v1.2 with /docker endpoint.

The /docker endpoint lists all Docker containers under one unified
namespace.
This commit is contained in:
Victor Marmol 2014-10-20 22:08:33 -07:00
parent 42e9e50b03
commit 8aa05b0c39
4 changed files with 149 additions and 47 deletions

View File

@ -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

View File

@ -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
}

View File

@ -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"
}

View File

@ -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.