Skip to content

Commit

Permalink
Adding v1.1 remote API.
Browse files Browse the repository at this point in the history
Main addition is the "subcontainers" resource which provides
ContainerInfo for all subcontainers (recursively and including
self) of the specified container.
  • Loading branch information
vmarmol committed Aug 1, 2014
1 parent d2397e5 commit 41e27e4
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 39 deletions.
72 changes: 57 additions & 15 deletions api/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 query request.
query, err := getContainerInfoRequest(r.Body)
if err != nil {
return err
}

// Get the container.
cont, err := m.GetContainerInfo(containerName, &query)
cont, err := m.GetContainerInfo(containerName, query)
if err != nil {
fmt.Fprintf(w, "Failed to get container \"%s\" with error: %s", containerName, err)
return err
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:
Expand All @@ -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
}
64 changes: 53 additions & 11 deletions manager/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ package manager
import (
"fmt"
"log"
"path"
"strings"
"sync"
"time"

Expand All @@ -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)

Expand Down Expand Up @@ -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 {
Expand All @@ -130,20 +138,20 @@ 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,
)
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
}
Expand All @@ -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
Expand Down Expand Up @@ -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()
Expand Down
70 changes: 57 additions & 13 deletions manager/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down Expand Up @@ -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 {
Expand All @@ -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)
}
}
}

0 comments on commit 41e27e4

Please sign in to comment.