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:
parent
d2397e5ab7
commit
41e27e40ef
@ -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
|
||||||
|
}
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user