Merge pull request #147 from vmarmol/subcontainers2
Adding v1.1 remote API.
This commit is contained in:
commit
01969c5cdf
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user