Merge pull request #147 from vmarmol/subcontainers2

Adding v1.1 remote API.
This commit is contained in:
monnand 2014-08-04 12:53:08 -04:00
commit 01969c5cdf
3 changed files with 168 additions and 40 deletions

View File

@ -35,10 +35,14 @@ const (
containersApi = "containers"
subcontainersApi = "subcontainers"
machineApi = "machine"
version1_0 = "v1.0"
version1_1 = "v1.1"
)
var supportedApiVersions map[string]struct{} = map[string]struct{}{
"v1.0": struct{}{},
version1_0: struct{}{},
version1_1: struct{}{},
}
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)
var query info.ContainerInfoRequest
// 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)
// Get the query request.
query, err := getContainerInfoRequest(r.Body)
if err != nil {
fmt.Fprintf(w, "Failed to get container \"%s\" with error: %s", containerName, 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.
out, err := json.Marshal(cont)
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)
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))
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 (
"fmt"
"log"
"path"
"strings"
"sync"
"time"
@ -32,6 +34,9 @@ type Manager interface {
// Get information about a container.
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.
GetMachineInfo() (*info.MachineInfo, error)
@ -106,21 +111,24 @@ func (m *manager) Start() error {
}
// Get a container by name.
func (m *manager) GetContainerInfo(containerName string, query *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
log.Printf("Get(%s); %+v", containerName, query)
func (self *manager) GetContainerInfo(containerName string, query *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
var cont *containerData
var ok bool
func() {
m.containersLock.RLock()
defer m.containersLock.RUnlock()
self.containersLock.RLock()
defer self.containersLock.RUnlock()
// Ensure we have the container.
cont, ok = m.containers[containerName]
cont, ok = self.containers[containerName]
}()
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.
cinfo, err := cont.GetInfo()
if err != nil {
@ -130,7 +138,7 @@ func (m *manager) GetContainerInfo(containerName string, query *info.ContainerIn
var percentiles *info.ContainerStatsPercentiles
var samples []*info.ContainerStatsSample
var stats []*info.ContainerStats
percentiles, err = m.storageDriver.Percentiles(
percentiles, err = self.storageDriver.Percentiles(
cinfo.Name,
query.CpuUsagePercentiles,
query.MemoryUsagePercentiles,
@ -138,12 +146,12 @@ func (m *manager) GetContainerInfo(containerName string, query *info.ContainerIn
if err != nil {
return nil, err
}
samples, err = m.storageDriver.Samples(cinfo.Name, query.NumSamples)
samples, err = self.storageDriver.Samples(cinfo.Name, query.NumSamples)
if err != nil {
return nil, err
}
stats, err = m.storageDriver.RecentStats(cinfo.Name, query.NumStats)
stats, err = self.storageDriver.RecentStats(cinfo.Name, query.NumStats)
if err != nil {
return nil, err
}
@ -165,12 +173,46 @@ func (m *manager) GetContainerInfo(containerName string, query *info.ContainerIn
if ret.Spec.Memory != nil {
// Memory.Limit is 0 means there's no limit
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
}
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) {
// Copy and return the MachineInfo.
ret := m.machineInfo
@ -200,7 +242,7 @@ func (m *manager) createContainer(containerName string) (*containerData, error)
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.
cont.Start()

View File

@ -71,19 +71,9 @@ func createManagerAndAddContainers(
return nil
}
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},
}
// Expect a manager with the specified containers and query. Returns the manager, map of ContainerInfo objects,
// and map of MockContainerHandler objects.}
func expectManagerWithContainers(containers []string, query *info.ContainerInfoRequest, t *testing.T) (*manager, map[string]*info.ContainerInfo, map[string]*container.MockContainerHandler) {
infosMap := make(map[string]*info.ContainerInfo, len(containers))
handlerMap := make(map[string]*container.MockContainerHandler, len(containers))
@ -142,6 +132,24 @@ func TestGetContainerInfo(t *testing.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))
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)
}
}
}