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).
This commit is contained in:
Rohit Jnagal 2015-03-18 05:34:18 +00:00
parent d8b9a50994
commit 6db3717426
4 changed files with 188 additions and 140 deletions

View File

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

View File

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

View File

@ -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))
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
}
// 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) 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
}
return cont.DerivedStats()
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 containersMap, nil
}
func (self *manager) GetFsInfo(label string) ([]v2.FsInfo, error) {

View File

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