From 6db37174260caab8446ab85f3e97e5b09d381fdc Mon Sep 17 00:00:00 2001 From: Rohit Jnagal Date: Wed, 18 Mar 2015 05:34:18 +0000 Subject: [PATCH] Add recursive and type options (raw/docker) to spec and summary endpoints. spec, stats, and summary now all support the same options (except count, which is only parsed for stats). --- api/versions.go | 95 +++++------------- info/v2/container.go | 7 +- manager/manager.go | 211 +++++++++++++++++++++++++++------------- manager/manager_mock.go | 17 ++-- 4 files changed, 189 insertions(+), 141 deletions(-) diff --git a/api/versions.go b/api/versions.go index 6e0d6247c3..545aae7176 100644 --- a/api/versions.go +++ b/api/versions.go @@ -18,7 +18,6 @@ import ( "fmt" "net/http" "strconv" - "strings" "github.com/golang/glog" "github.com/google/cadvisor/events" @@ -39,8 +38,6 @@ const ( storageApi = "storage" attributesApi = "attributes" versionApi = "version" - typeName = "name" - typeDocker = "docker" ) // Interface for a cAdvisor API version @@ -310,6 +307,10 @@ func (self *version2_0) SupportedRequestTypes() []string { } func (self *version2_0) HandleRequest(requestType string, request []string, m manager.Manager, w http.ResponseWriter, r *http.Request) error { + opt, err := getRequestOptions(r) + if err != nil { + return err + } switch requestType { case versionApi: glog.V(2).Infof("Api - Version") @@ -342,77 +343,33 @@ func (self *version2_0) HandleRequest(requestType string, request []string, m ma return writeResult(machineInfo, w) case summaryApi: containerName := getContainerName(request) - glog.V(2).Infof("Api - Summary(%v)", containerName) + glog.V(2).Infof("Api - Summary for container %q, options %+v", containerName, opt) - stats, err := m.GetContainerDerivedStats(containerName) + stats, err := m.GetDerivedStats(containerName, opt) if err != nil { return err } - return writeResult(stats, w) case statsApi: name := getContainerName(request) - sr, err := getStatsRequest(name, r) + glog.V(2).Infof("Api - Stats: Looking for stats for container %q, options %+v", name, opt) + conts, err := m.GetRequestedContainersInfo(name, opt) if err != nil { return err } - glog.V(2).Infof("Api - Stats: Looking for stats for container %q, options %+v", name, sr) - query := info.ContainerInfoRequest{ - NumStats: sr.Count, - } - switch sr.IdType { - case typeName: - contStats := make(map[string][]v2.ContainerStats, 0) - if sr.Recursive == false { - cont, err := m.GetContainerInfo(name, &query) - if err != nil { - return fmt.Errorf("failed to get container %q: %v", name, err) - } - contStats[name] = convertStats(cont) - } else { - containers, err := m.SubcontainersInfo(name, &query) - if err != nil { - return fmt.Errorf("failed to get subcontainers for container %q with error: %s", name, err) - } - for _, cont := range containers { - contStats[cont.Name] = convertStats(cont) - } - } - return writeResult(contStats, w) - case typeDocker: - contStats := make(map[string][]v2.ContainerStats, 0) - if name == "/" { - // special case: get all docker containers. - if sr.Recursive == false { - return fmt.Errorf("unknown Docker container %q", name) - } - containers, err := m.AllDockerContainers(&query) - if err != nil { - return fmt.Errorf("failed to get all docker containers: %v", err) - } - for name, cont := range containers { - contStats[name] = convertStats(&cont) - } - } else { - name = strings.TrimPrefix(name, "/") - cont, err := m.DockerContainer(name, &query) - if err != nil { - return fmt.Errorf("failed to get Docker container %q with error: %v", name, err) - } - contStats[cont.Name] = convertStats(&cont) - } - return writeResult(contStats, w) - default: - return fmt.Errorf("unknown id type %q for container name %q", sr.IdType, name) + contStats := make(map[string][]v2.ContainerStats, 0) + for name, cont := range conts { + contStats[name] = convertStats(cont) } + return writeResult(contStats, w) case specApi: containerName := getContainerName(request) - glog.V(2).Infof("Api - Spec(%v)", containerName) - spec, err := m.GetContainerSpec(containerName) + glog.V(2).Infof("Api - Spec for container %q, options %+v", containerName, opt) + specs, err := m.GetContainerSpec(containerName, opt) if err != nil { return err } - return writeResult(spec, w) + return writeResult(specs, w) case storageApi: var err error fi := []v2.FsInfo{} @@ -469,35 +426,35 @@ func convertStats(cont *info.ContainerInfo) []v2.ContainerStats { return stats } -func getStatsRequest(id string, r *http.Request) (v2.StatsRequest, error) { +func getRequestOptions(r *http.Request) (v2.RequestOptions, error) { supportedTypes := map[string]bool{ - typeName: true, - typeDocker: true, + v2.TypeName: true, + v2.TypeDocker: true, } // fill in the defaults. - sr := v2.StatsRequest{ - IdType: typeName, + opt := v2.RequestOptions{ + IdType: v2.TypeName, Count: 64, Recursive: false, } idType := r.URL.Query().Get("type") if len(idType) != 0 { if !supportedTypes[idType] { - return sr, fmt.Errorf("unknown 'type' %q for container name %q", idType, id) + return opt, fmt.Errorf("unknown 'type' %q", idType) } - sr.IdType = idType + opt.IdType = idType } count := r.URL.Query().Get("count") if len(count) != 0 { n, err := strconv.ParseUint(count, 10, 32) if err != nil { - return sr, fmt.Errorf("failed to parse 'count' option: %v", count) + return opt, fmt.Errorf("failed to parse 'count' option: %v", count) } - sr.Count = int(n) + opt.Count = int(n) } recursive := r.URL.Query().Get("recursive") if recursive == "true" { - sr.Recursive = true + opt.Recursive = true } - return sr, nil + return opt, nil } diff --git a/info/v2/container.go b/info/v2/container.go index 03a806cc01..43d883f310 100644 --- a/info/v2/container.go +++ b/info/v2/container.go @@ -22,6 +22,11 @@ import ( "github.com/google/cadvisor/info/v1" ) +const ( + TypeName = "name" + TypeDocker = "docker" +) + type CpuSpec struct { // Requested cpu shares. Default is 1024. Limit uint64 `json:"limit"` @@ -150,7 +155,7 @@ type FsInfo struct { Labels []string `json:"labels"` } -type StatsRequest struct { +type RequestOptions struct { // Type of container identifier specified - "name", "dockerid", dockeralias" IdType string `json:"type"` // Number of stats to return diff --git a/manager/manager.go b/manager/manager.go index 112a20d060..77b3231757 100644 --- a/manager/manager.go +++ b/manager/manager.go @@ -63,11 +63,14 @@ type Manager interface { // Gets information about a specific Docker container. The specified name is within the Docker namespace. DockerContainer(dockerName string, query *info.ContainerInfoRequest) (info.ContainerInfo, error) - // Gets spec for a container. - GetContainerSpec(containerName string) (v2.ContainerSpec, error) + // Gets spec for all containers based on request options. + GetContainerSpec(containerName string, options v2.RequestOptions) (map[string]v2.ContainerSpec, error) - // Get derived stats for a container. - GetContainerDerivedStats(containerName string) (v2.DerivedStats, error) + // Gets summary stats for all containers based on request options. + GetDerivedStats(containerName string, options v2.RequestOptions) (map[string]v2.DerivedStats, error) + + // Get info for all requested containers based on the request options. + GetRequestedContainersInfo(containerName string, options v2.RequestOptions) (map[string]*info.ContainerInfo, error) // Get information about the machine. GetMachineInfo() (*info.MachineInfo, error) @@ -297,17 +300,37 @@ func (self *manager) getContainerData(containerName string) (*containerData, err return cont, nil } -func (self *manager) GetContainerSpec(containerName string) (v2.ContainerSpec, error) { - cont, err := self.getContainerData(containerName) +func (self *manager) GetDerivedStats(containerName string, options v2.RequestOptions) (map[string]v2.DerivedStats, error) { + conts, err := self.getRequestedContainers(containerName, options) if err != nil { - return v2.ContainerSpec{}, err + return nil, err } - cinfo, err := cont.GetInfo() + stats := make(map[string]v2.DerivedStats) + for name, cont := range conts { + d, err := cont.DerivedStats() + if err != nil { + return nil, err + } + stats[name] = d + } + return stats, nil +} + +func (self *manager) GetContainerSpec(containerName string, options v2.RequestOptions) (map[string]v2.ContainerSpec, error) { + conts, err := self.getRequestedContainers(containerName, options) if err != nil { - return v2.ContainerSpec{}, err + return nil, err } - spec := self.getV2Spec(cinfo) - return spec, nil + specs := make(map[string]v2.ContainerSpec) + for name, cont := range conts { + cinfo, err := cont.GetInfo() + if err != nil { + return nil, err + } + spec := self.getV2Spec(cinfo) + specs[name] = spec + } + return specs, nil } // Get V2 container spec from v1 container info. @@ -377,22 +400,34 @@ func (self *manager) containerDataToContainerInfo(cont *containerData, query *in return ret, nil } -func (self *manager) SubcontainersInfo(containerName string, query *info.ContainerInfoRequest) ([]*info.ContainerInfo, error) { - var containersMap map[string]*containerData - func() { - self.containersLock.RLock() - defer self.containersLock.RUnlock() - containersMap = make(map[string]*containerData, len(self.containers)) - - // Get all the unique subcontainers of the specified container - matchedName := path.Join(containerName, "/") - for i := range self.containers { - name := self.containers[i].info.Name - if name == containerName || strings.HasPrefix(name, matchedName) { - containersMap[self.containers[i].info.Name] = self.containers[i] - } +func (self *manager) getContainer(containerName string) (*containerData, error) { + self.containersLock.RLock() + defer self.containersLock.RUnlock() + cont, ok := self.containers[namespacedContainerName{Name: containerName}] + if !ok { + return nil, fmt.Errorf("unknown container %q", containerName) + } + return cont, nil +} + +func (self *manager) getSubcontainers(containerName string) map[string]*containerData { + self.containersLock.RLock() + defer self.containersLock.RUnlock() + containersMap := make(map[string]*containerData, len(self.containers)) + + // Get all the unique subcontainers of the specified container + matchedName := path.Join(containerName, "/") + for i := range self.containers { + name := self.containers[i].info.Name + if name == containerName || strings.HasPrefix(name, matchedName) { + containersMap[self.containers[i].info.Name] = self.containers[i] } - }() + } + return containersMap +} + +func (self *manager) SubcontainersInfo(containerName string, query *info.ContainerInfoRequest) ([]*info.ContainerInfo, error) { + containersMap := self.getSubcontainers(containerName) containers := make([]*containerData, 0, len(containersMap)) for _, cont := range containersMap { @@ -401,20 +436,22 @@ func (self *manager) SubcontainersInfo(containerName string, query *info.Contain return self.containerDataSliceToContainerInfoSlice(containers, query) } -func (self *manager) AllDockerContainers(query *info.ContainerInfoRequest) (map[string]info.ContainerInfo, error) { - var containers map[string]*containerData - func() { - self.containersLock.RLock() - defer self.containersLock.RUnlock() - containers = make(map[string]*containerData, len(self.containers)) +func (self *manager) getAllDockerContainers() map[string]*containerData { + self.containersLock.RLock() + defer self.containersLock.RUnlock() + containers := make(map[string]*containerData, len(self.containers)) - // Get containers in the Docker namespace. - for name, cont := range self.containers { - if name.Namespace == docker.DockerNamespace { - containers[cont.info.Name] = cont - } + // Get containers in the Docker namespace. + for name, cont := range self.containers { + if name.Namespace == docker.DockerNamespace { + containers[cont.info.Name] = cont } - }() + } + return containers +} + +func (self *manager) AllDockerContainers(query *info.ContainerInfoRequest) (map[string]info.ContainerInfo, error) { + containers := self.getAllDockerContainers() output := make(map[string]info.ContainerInfo, len(containers)) for name, cont := range containers { @@ -427,23 +464,25 @@ func (self *manager) AllDockerContainers(query *info.ContainerInfoRequest) (map[ return output, nil } -func (self *manager) DockerContainer(containerName string, query *info.ContainerInfoRequest) (info.ContainerInfo, error) { - var container *containerData = nil - func() { - self.containersLock.RLock() - defer self.containersLock.RUnlock() +func (self *manager) getDockerContainer(containerName string) (*containerData, error) { + self.containersLock.RLock() + defer self.containersLock.RUnlock() - // Check for the container in the Docker container namespace. - cont, ok := self.containers[namespacedContainerName{ - Namespace: docker.DockerNamespace, - Name: containerName, - }] - if ok { - container = cont - } - }() - if container == nil { - return info.ContainerInfo{}, fmt.Errorf("unable to find Docker container %q", containerName) + // Check for the container in the Docker container namespace. + cont, ok := self.containers[namespacedContainerName{ + Namespace: docker.DockerNamespace, + Name: containerName, + }] + if !ok { + return nil, fmt.Errorf("unable to find Docker container %q", containerName) + } + return cont, nil +} + +func (self *manager) DockerContainer(containerName string, query *info.ContainerInfoRequest) (info.ContainerInfo, error) { + container, err := self.getDockerContainer(containerName) + if err != nil { + return info.ContainerInfo{}, err } inf, err := self.containerDataToContainerInfo(container, query) @@ -472,18 +511,60 @@ func (self *manager) containerDataSliceToContainerInfoSlice(containers []*contai return output, nil } -func (self *manager) GetContainerDerivedStats(containerName string) (v2.DerivedStats, error) { - var ok bool - var cont *containerData - func() { - self.containersLock.RLock() - defer self.containersLock.RUnlock() - cont, ok = self.containers[namespacedContainerName{Name: containerName}] - }() - if !ok { - return v2.DerivedStats{}, fmt.Errorf("unknown container %q", containerName) +func (self *manager) GetRequestedContainersInfo(containerName string, options v2.RequestOptions) (map[string]*info.ContainerInfo, error) { + containers, err := self.getRequestedContainers(containerName, options) + if err != nil { + return nil, err + } + containersMap := make(map[string]*info.ContainerInfo) + query := info.ContainerInfoRequest{ + NumStats: options.Count, + } + for name, data := range containers { + info, err := self.containerDataToContainerInfo(data, &query) + if err != nil { + // Skip containers with errors, we try to degrade gracefully. + continue + } + containersMap[name] = info + } + return containersMap, nil +} + +func (self *manager) getRequestedContainers(containerName string, options v2.RequestOptions) (map[string]*containerData, error) { + containersMap := make(map[string]*containerData) + switch options.IdType { + case v2.TypeName: + if options.Recursive == false { + cont, err := self.getContainer(containerName) + if err != nil { + return containersMap, err + } + containersMap[cont.info.Name] = cont + } else { + containersMap = self.getSubcontainers(containerName) + if len(containersMap) == 0 { + return containersMap, fmt.Errorf("unknown container: %q", containerName) + } + } + case v2.TypeDocker: + if options.Recursive == false { + containerName = strings.TrimPrefix(containerName, "/") + cont, err := self.getDockerContainer(containerName) + if err != nil { + return containersMap, err + } + containersMap[cont.info.Name] = cont + } else { + if containerName != "/" { + return containersMap, fmt.Errorf("invalid request for docker container %q with subcontainers", containerName) + } + containersMap = self.getAllDockerContainers() + } + default: + return containersMap, fmt.Errorf("invalid request type %q", options.IdType) } - return cont.DerivedStats() + return containersMap, nil } func (self *manager) GetFsInfo(label string) ([]v2.FsInfo, error) { diff --git a/manager/manager_mock.go b/manager/manager_mock.go index a14619e318..d732da0946 100644 --- a/manager/manager_mock.go +++ b/manager/manager_mock.go @@ -55,14 +55,19 @@ func (c *ManagerMock) DockerContainer(name string, query *info.ContainerInfoRequ return args.Get(0).(info.ContainerInfo), args.Error(1) } -func (c *ManagerMock) GetContainerSpec(containerName string) (info.ContainerSpec, error) { - args := c.Called(containerName) - return args.Get(0).(info.ContainerSpec), args.Error(1) +func (c *ManagerMock) GetContainerSpec(containerName string, options v2.RequestOptions) (map[string]v2.ContainerSpec, error) { + args := c.Called(containerName, options) + return args.Get(0).(map[string]v2.ContainerSpec), args.Error(1) } -func (c *ManagerMock) GetContainerDerivedStats(containerName string) (v2.DerivedStats, error) { - args := c.Called(containerName) - return args.Get(0).(v2.DerivedStats), args.Error(1) +func (c *ManagerMock) GetDerivedStats(containerName string, options v2.RequestOptions) (map[string]v2.DerivedStats, error) { + args := c.Called(containerName, options) + return args.Get(0).(map[string]v2.DerivedStats), args.Error(1) +} + +func (c *ManagerMock) GetRequestedContainersInfo(containerName string, options v2.RequestOptions) (map[string]*info.ContainerInfo, error) { + args := c.Called(containerName, options) + return args.Get(0).(map[string]*info.ContainerInfo), args.Error(1) } func (c *ManagerMock) WatchForEvents(queryuest *events.Request, passedChannel chan *events.Event) error {