Adding v1.1 remote API.

Main addition is the "subcontainers" resource which provides
ContainerInfo for all subcontainers (recursively and including
self) of the specified container.
This commit is contained in:
Victor Marmol 2014-08-01 16:14:26 -07:00
parent d2397e5ab7
commit 41e27e40ef
3 changed files with 168 additions and 40 deletions

View File

@ -35,10 +35,14 @@ const (
containersApi = "containers" containersApi = "containers"
subcontainersApi = "subcontainers" subcontainersApi = "subcontainers"
machineApi = "machine" machineApi = "machine"
version1_0 = "v1.0"
version1_1 = "v1.1"
) )
var supportedApiVersions map[string]struct{} = map[string]struct{}{ var supportedApiVersions map[string]struct{} = map[string]struct{}{
"v1.0": struct{}{}, version1_0: struct{}{},
version1_1: struct{}{},
} }
func RegisterHandlers(m manager.Manager) error { func RegisterHandlers(m manager.Manager) error {
@ -107,28 +111,50 @@ func handleRequest(m manager.Manager, w http.ResponseWriter, r *http.Request) er
log.Printf("Api - Container(%s)", containerName) log.Printf("Api - Container(%s)", containerName)
var query info.ContainerInfoRequest // Get the query request.
query, err := getContainerInfoRequest(r.Body)
// If a user does not specify number of stats/samples he wants,
// it's 64 by default
query.NumStats = 64
query.NumSamples = 64
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&query)
if err != nil && err != io.EOF {
return fmt.Errorf("unable to decode the json value: ", err)
}
// Get the container.
cont, err := m.GetContainerInfo(containerName, &query)
if err != nil { if err != nil {
fmt.Fprintf(w, "Failed to get container \"%s\" with error: %s", containerName, err)
return err return err
} }
// Get the container.
cont, err := m.GetContainerInfo(containerName, query)
if err != nil {
return fmt.Errorf("failed to get container %q with error: %s", containerName, err)
}
// Only output the container as JSON. // Only output the container as JSON.
out, err := json.Marshal(cont) out, err := json.Marshal(cont)
if err != nil { if err != nil {
fmt.Fprintf(w, "Failed to marshall container %q with error: %s", containerName, err) return fmt.Errorf("failed to marshall container %q with error: %s", containerName, 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, "/"))
log.Printf("Api - Subcontainers(%s)", containerName)
// Get the query request.
query, err := getContainerInfoRequest(r.Body)
if err != nil {
return err
}
// Get the subcontainers.
containers, err := m.SubcontainersInfo(containerName, query)
if err != nil {
return fmt.Errorf("failed to get subcontainers for container %q with error: %s", containerName, err)
}
// Only output the containers as JSON.
out, err := json.Marshal(containers)
if err != nil {
return fmt.Errorf("failed to marshall container %q with error: %s", containerName, err)
} }
w.Write(out) w.Write(out)
default: default:
@ -138,3 +164,19 @@ func handleRequest(m manager.Manager, w http.ResponseWriter, r *http.Request) er
log.Printf("Request took %s", time.Since(start)) log.Printf("Request took %s", time.Since(start))
return nil return nil
} }
func getContainerInfoRequest(body io.ReadCloser) (*info.ContainerInfoRequest, error) {
var query info.ContainerInfoRequest
// Default stats and samples is 64.
query.NumStats = 64
query.NumSamples = 64
decoder := json.NewDecoder(body)
err := decoder.Decode(&query)
if err != nil && err != io.EOF {
return nil, fmt.Errorf("unable to decode the json value: %s", err)
}
return &query, nil
}

View File

@ -17,6 +17,8 @@ package manager
import ( import (
"fmt" "fmt"
"log" "log"
"path"
"strings"
"sync" "sync"
"time" "time"
@ -32,6 +34,9 @@ type Manager interface {
// Get information about a container. // Get information about a container.
GetContainerInfo(containerName string, query *info.ContainerInfoRequest) (*info.ContainerInfo, error) GetContainerInfo(containerName string, query *info.ContainerInfoRequest) (*info.ContainerInfo, error)
// Get information about all subcontainers of the specified container (includes self).
SubcontainersInfo(containerName string, query *info.ContainerInfoRequest) ([]*info.ContainerInfo, error)
// Get information about the machine. // Get information about the machine.
GetMachineInfo() (*info.MachineInfo, error) GetMachineInfo() (*info.MachineInfo, error)
@ -106,21 +111,24 @@ func (m *manager) Start() error {
} }
// Get a container by name. // Get a container by name.
func (m *manager) GetContainerInfo(containerName string, query *info.ContainerInfoRequest) (*info.ContainerInfo, error) { func (self *manager) GetContainerInfo(containerName string, query *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
log.Printf("Get(%s); %+v", containerName, query)
var cont *containerData var cont *containerData
var ok bool var ok bool
func() { func() {
m.containersLock.RLock() self.containersLock.RLock()
defer m.containersLock.RUnlock() defer self.containersLock.RUnlock()
// Ensure we have the container. // Ensure we have the container.
cont, ok = m.containers[containerName] cont, ok = self.containers[containerName]
}() }()
if !ok { if !ok {
return nil, fmt.Errorf("unknown container \"%s\"", containerName) return nil, fmt.Errorf("unknown container %q", containerName)
} }
return self.containerDataToContainerInfo(cont, query)
}
func (self *manager) containerDataToContainerInfo(cont *containerData, query *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
// Get the info from the container. // Get the info from the container.
cinfo, err := cont.GetInfo() cinfo, err := cont.GetInfo()
if err != nil { if err != nil {
@ -130,7 +138,7 @@ func (m *manager) GetContainerInfo(containerName string, query *info.ContainerIn
var percentiles *info.ContainerStatsPercentiles var percentiles *info.ContainerStatsPercentiles
var samples []*info.ContainerStatsSample var samples []*info.ContainerStatsSample
var stats []*info.ContainerStats var stats []*info.ContainerStats
percentiles, err = m.storageDriver.Percentiles( percentiles, err = self.storageDriver.Percentiles(
cinfo.Name, cinfo.Name,
query.CpuUsagePercentiles, query.CpuUsagePercentiles,
query.MemoryUsagePercentiles, query.MemoryUsagePercentiles,
@ -138,12 +146,12 @@ func (m *manager) GetContainerInfo(containerName string, query *info.ContainerIn
if err != nil { if err != nil {
return nil, err return nil, err
} }
samples, err = m.storageDriver.Samples(cinfo.Name, query.NumSamples) samples, err = self.storageDriver.Samples(cinfo.Name, query.NumSamples)
if err != nil { if err != nil {
return nil, err return nil, err
} }
stats, err = m.storageDriver.RecentStats(cinfo.Name, query.NumStats) stats, err = self.storageDriver.RecentStats(cinfo.Name, query.NumStats)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -165,12 +173,46 @@ func (m *manager) GetContainerInfo(containerName string, query *info.ContainerIn
if ret.Spec.Memory != nil { if ret.Spec.Memory != nil {
// Memory.Limit is 0 means there's no limit // Memory.Limit is 0 means there's no limit
if ret.Spec.Memory.Limit == 0 { if ret.Spec.Memory.Limit == 0 {
ret.Spec.Memory.Limit = uint64(m.machineInfo.MemoryCapacity) ret.Spec.Memory.Limit = uint64(self.machineInfo.MemoryCapacity)
} }
} }
return ret, nil return ret, nil
} }
func (self *manager) SubcontainersInfo(containerName string, query *info.ContainerInfoRequest) ([]*info.ContainerInfo, error) {
var containers []*containerData
func() {
self.containersLock.RLock()
defer self.containersLock.RUnlock()
containers = make([]*containerData, 0, len(self.containers))
// Get all the 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) {
containers = append(containers, self.containers[i])
}
}
}()
if len(containers) == 0 {
return nil, fmt.Errorf("unknown container %q", containerName)
}
// Get the info for each container.
output := make([]*info.ContainerInfo, 0, len(containers))
for i := range containers {
cinfo, err := self.containerDataToContainerInfo(containers[i], query)
if err != nil {
// Skip containers with errors, we try to degrade gracefully.
continue
}
output = append(output, cinfo)
}
return output, nil
}
func (m *manager) GetMachineInfo() (*info.MachineInfo, error) { func (m *manager) GetMachineInfo() (*info.MachineInfo, error) {
// Copy and return the MachineInfo. // Copy and return the MachineInfo.
ret := m.machineInfo ret := m.machineInfo
@ -200,7 +242,7 @@ func (m *manager) createContainer(containerName string) (*containerData, error)
m.containers[alias] = cont m.containers[alias] = cont
} }
}() }()
log.Printf("Added container: %s (aliases: %s)", containerName, cont.info.Aliases) log.Printf("Added container: %q (aliases: %s)", containerName, cont.info.Aliases)
// Start the container's housekeeping. // Start the container's housekeeping.
cont.Start() cont.Start()

View File

@ -71,19 +71,9 @@ func createManagerAndAddContainers(
return nil return nil
} }
func TestGetContainerInfo(t *testing.T) { // Expect a manager with the specified containers and query. Returns the manager, map of ContainerInfo objects,
containers := []string{ // and map of MockContainerHandler objects.}
"/c1", func expectManagerWithContainers(containers []string, query *info.ContainerInfoRequest, t *testing.T) (*manager, map[string]*info.ContainerInfo, map[string]*container.MockContainerHandler) {
"/c2",
}
query := &info.ContainerInfoRequest{
NumStats: 256,
NumSamples: 128,
CpuUsagePercentiles: []int{10, 50, 90},
MemoryUsagePercentiles: []int{10, 80, 90},
}
infosMap := make(map[string]*info.ContainerInfo, len(containers)) infosMap := make(map[string]*info.ContainerInfo, len(containers))
handlerMap := make(map[string]*container.MockContainerHandler, len(containers)) handlerMap := make(map[string]*container.MockContainerHandler, len(containers))
@ -142,6 +132,24 @@ func TestGetContainerInfo(t *testing.T) {
t, t,
) )
return m, infosMap, handlerMap
}
func TestGetContainerInfo(t *testing.T) {
containers := []string{
"/c1",
"/c2",
}
query := &info.ContainerInfoRequest{
NumStats: 256,
NumSamples: 128,
CpuUsagePercentiles: []int{10, 50, 90},
MemoryUsagePercentiles: []int{10, 80, 90},
}
m, infosMap, handlerMap := expectManagerWithContainers(containers, query, t)
returnedInfos := make(map[string]*info.ContainerInfo, len(containers)) returnedInfos := make(map[string]*info.ContainerInfo, len(containers))
for _, container := range containers { for _, container := range containers {
@ -162,3 +170,39 @@ func TestGetContainerInfo(t *testing.T) {
} }
} }
func TestSubcontainersInfo(t *testing.T) {
containers := []string{
"/c1",
"/c2",
}
query := &info.ContainerInfoRequest{
NumStats: 64,
NumSamples: 64,
CpuUsagePercentiles: []int{10, 50, 90},
MemoryUsagePercentiles: []int{10, 80, 90},
}
m, _, _ := expectManagerWithContainers(containers, query, t)
result, err := m.SubcontainersInfo("/", query)
if err != nil {
t.Fatalf("expected to succeed: %s", err)
}
if len(result) != len(containers) {
t.Errorf("expected to received containers: %v, but received: %v", containers, result)
}
for _, res := range result {
found := false
for _, name := range containers {
if res.Name == name {
found = true
break
}
}
if !found {
t.Errorf("unexpected container %q in result, expected one of %v", res.Name, containers)
}
}
}