Added a basic api interface for events
This commit is contained in:
parent
e7a3518c96
commit
6e14267c3c
@ -23,10 +23,12 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
"github.com/google/cadvisor/events"
|
||||||
"github.com/google/cadvisor/info"
|
"github.com/google/cadvisor/info"
|
||||||
"github.com/google/cadvisor/manager"
|
"github.com/google/cadvisor/manager"
|
||||||
)
|
)
|
||||||
@ -125,6 +127,7 @@ func writeResult(res interface{}, w http.ResponseWriter) error {
|
|||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.Write(out)
|
w.Write(out)
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getContainerInfoRequest(body io.ReadCloser) (*info.ContainerInfoRequest, error) {
|
func getContainerInfoRequest(body io.ReadCloser) (*info.ContainerInfoRequest, error) {
|
||||||
@ -142,6 +145,74 @@ func getContainerInfoRequest(body io.ReadCloser) (*info.ContainerInfoRequest, er
|
|||||||
return &query, nil
|
return &query, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The user can set any or none of the following arguments in any order
|
||||||
|
// with any twice defined arguments being assigned the first value.
|
||||||
|
// If the value type for the argument is wrong the field will be assumed to be
|
||||||
|
// unassigned
|
||||||
|
// bools: historical, subcontainers, oom_events, creation_events, deletion_events
|
||||||
|
// ints: max_events, start_time (unix timestamp), end_time (unix timestamp)
|
||||||
|
// example r.URL: http://localhost:8080/api/v1.3/events?oom_events=true&historical=true&max_events=10
|
||||||
|
func getEventRequest(r *http.Request) (*events.Request, bool, error) {
|
||||||
|
query := events.NewRequest()
|
||||||
|
getHistoricalEvents := false
|
||||||
|
|
||||||
|
urlMap := r.URL.Query()
|
||||||
|
|
||||||
|
if val, ok := urlMap["historical"]; ok {
|
||||||
|
newBool, err := strconv.ParseBool(val[0])
|
||||||
|
if err == nil {
|
||||||
|
getHistoricalEvents = newBool
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if val, ok := urlMap["subcontainers"]; ok {
|
||||||
|
newBool, err := strconv.ParseBool(val[0])
|
||||||
|
if err == nil {
|
||||||
|
query.IncludeSubcontainers = newBool
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if val, ok := urlMap["oom_events"]; ok {
|
||||||
|
newBool, err := strconv.ParseBool(val[0])
|
||||||
|
if err == nil {
|
||||||
|
query.EventType[events.TypeOom] = newBool
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if val, ok := urlMap["creation_events"]; ok {
|
||||||
|
newBool, err := strconv.ParseBool(val[0])
|
||||||
|
if err == nil {
|
||||||
|
query.EventType[events.TypeContainerCreation] = newBool
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if val, ok := urlMap["deletion_events"]; ok {
|
||||||
|
newBool, err := strconv.ParseBool(val[0])
|
||||||
|
if err == nil {
|
||||||
|
query.EventType[events.TypeContainerDeletion] = newBool
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if val, ok := urlMap["max_events"]; ok {
|
||||||
|
newInt, err := strconv.Atoi(val[0])
|
||||||
|
if err == nil {
|
||||||
|
query.MaxEventsReturned = int(newInt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if val, ok := urlMap["start_time"]; ok {
|
||||||
|
newTime, err := time.Parse(time.RFC3339, val[0])
|
||||||
|
if err == nil {
|
||||||
|
query.StartTime = newTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if val, ok := urlMap["end_time"]; ok {
|
||||||
|
newTime, err := time.Parse(time.RFC3339, val[0])
|
||||||
|
if err == nil {
|
||||||
|
query.EndTime = newTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glog.V(2).Infof(
|
||||||
|
"%v was returned in api/handler.go:getEventRequest from the url rawQuery %v",
|
||||||
|
query, r.URL.RawQuery)
|
||||||
|
return query, getHistoricalEvents, nil
|
||||||
|
}
|
||||||
|
|
||||||
func getContainerName(request []string) string {
|
func getContainerName(request []string) string {
|
||||||
return path.Join("/", strings.Join(request, "/"))
|
return path.Join("/", strings.Join(request, "/"))
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
"github.com/google/cadvisor/events"
|
||||||
"github.com/google/cadvisor/info"
|
"github.com/google/cadvisor/info"
|
||||||
"github.com/google/cadvisor/manager"
|
"github.com/google/cadvisor/manager"
|
||||||
)
|
)
|
||||||
@ -30,6 +31,7 @@ const (
|
|||||||
dockerApi = "docker"
|
dockerApi = "docker"
|
||||||
summaryApi = "summary"
|
summaryApi = "summary"
|
||||||
specApi = "spec"
|
specApi = "spec"
|
||||||
|
eventsApi = "events"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Interface for a cAdvisor API version
|
// Interface for a cAdvisor API version
|
||||||
@ -49,8 +51,11 @@ func getApiVersions() []ApiVersion {
|
|||||||
v1_0 := &version1_0{}
|
v1_0 := &version1_0{}
|
||||||
v1_1 := newVersion1_1(v1_0)
|
v1_1 := newVersion1_1(v1_0)
|
||||||
v1_2 := newVersion1_2(v1_1)
|
v1_2 := newVersion1_2(v1_1)
|
||||||
v2_0 := newVersion2_0(v1_2)
|
v1_3 := newVersion1_3(v1_2)
|
||||||
return []ApiVersion{v1_0, v1_1, v1_2, v2_0}
|
v2_0 := newVersion2_0(v1_3)
|
||||||
|
|
||||||
|
return []ApiVersion{v1_0, v1_1, v1_2, v1_3, v2_0}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// API v1.0
|
// API v1.0
|
||||||
@ -227,12 +232,72 @@ func (self *version1_2) HandleRequest(requestType string, request []string, m ma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// v2.0 builds on v1.2
|
// API v1.3
|
||||||
type version2_0 struct {
|
|
||||||
|
type version1_3 struct {
|
||||||
baseVersion *version1_2
|
baseVersion *version1_2
|
||||||
}
|
}
|
||||||
|
|
||||||
func newVersion2_0(v *version1_2) *version2_0 {
|
// v1.3 builds on v1.2.
|
||||||
|
func newVersion1_3(v *version1_2) *version1_3 {
|
||||||
|
return &version1_3{
|
||||||
|
baseVersion: v,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *version1_3) Version() string {
|
||||||
|
return "v1.3"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *version1_3) SupportedRequestTypes() []string {
|
||||||
|
return append(self.baseVersion.SupportedRequestTypes(), eventsApi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *version1_3) HandleRequest(requestType string, request []string, m manager.Manager, w http.ResponseWriter, r *http.Request) error {
|
||||||
|
switch requestType {
|
||||||
|
case eventsApi:
|
||||||
|
query, eventsFromAllTime, err := getEventRequest(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
glog.V(2).Infof("Api - Events(%v)", query)
|
||||||
|
|
||||||
|
if eventsFromAllTime {
|
||||||
|
allEvents, err := m.GetPastEvents(query)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return writeResult(allEvents, w)
|
||||||
|
} else {
|
||||||
|
// every time URL is entered to watch, a channel is created here
|
||||||
|
eventChannel := make(chan *events.Event, 10)
|
||||||
|
err = m.WatchForEvents(query, eventChannel)
|
||||||
|
|
||||||
|
defer close(eventChannel)
|
||||||
|
currentEventSet := make(events.EventSlice, 0)
|
||||||
|
for ev := range eventChannel {
|
||||||
|
// todo: implement write-as-received writeResult method
|
||||||
|
currentEventSet = append(currentEventSet, ev)
|
||||||
|
err = writeResult(currentEventSet, w)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return self.baseVersion.HandleRequest(requestType, request, m, w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// API v2.0
|
||||||
|
|
||||||
|
// v2.0 builds on v1.3
|
||||||
|
type version2_0 struct {
|
||||||
|
baseVersion *version1_3
|
||||||
|
}
|
||||||
|
|
||||||
|
func newVersion2_0(v *version1_3) *version2_0 {
|
||||||
return &version2_0{
|
return &version2_0{
|
||||||
baseVersion: v,
|
baseVersion: v,
|
||||||
}
|
}
|
||||||
|
81
api/versions_test.go
Normal file
81
api/versions_test.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
// Copyright 2015 Google Inc. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/cadvisor/events"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// returns an http.Request pointer for an input url test string
|
||||||
|
func makeHTTPRequest(requestURL string, t *testing.T) *http.Request {
|
||||||
|
dummyReader, _ := io.Pipe()
|
||||||
|
r, err := http.NewRequest("GET", requestURL, dummyReader)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetEventRequestBasicRequest(t *testing.T) {
|
||||||
|
r := makeHTTPRequest("http://localhost:8080/api/v1.3/events?oom_events=true&historical=true&max_events=10", t)
|
||||||
|
expectedQuery := &events.Request{
|
||||||
|
EventType: map[events.EventType]bool{
|
||||||
|
events.TypeOom: true,
|
||||||
|
},
|
||||||
|
MaxEventsReturned: 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
receivedQuery, getHistoricalEvents, err := getEventRequest(r)
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(expectedQuery, receivedQuery) {
|
||||||
|
t.Errorf("expected %v but received %v", expectedQuery, receivedQuery)
|
||||||
|
}
|
||||||
|
assert.True(t, getHistoricalEvents)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetEventEmptyRequest(t *testing.T) {
|
||||||
|
r := makeHTTPRequest("", t)
|
||||||
|
expectedQuery := events.NewRequest()
|
||||||
|
|
||||||
|
receivedQuery, getHistoricalEvents, err := getEventRequest(r)
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(expectedQuery, receivedQuery) {
|
||||||
|
t.Errorf("expected %v but received %v", expectedQuery, receivedQuery)
|
||||||
|
}
|
||||||
|
assert.False(t, getHistoricalEvents)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetEventRequestDoubleArgument(t *testing.T) {
|
||||||
|
r := makeHTTPRequest("http://localhost:8080/api/v1.3/events?historical=true&oom_events=true&oom_events=false", t)
|
||||||
|
expectedQuery := &events.Request{
|
||||||
|
EventType: map[events.EventType]bool{
|
||||||
|
events.TypeOom: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
receivedQuery, getHistoricalEvents, err := getEventRequest(r)
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(expectedQuery, receivedQuery) {
|
||||||
|
t.Errorf("expected %v but received %v", expectedQuery, receivedQuery)
|
||||||
|
}
|
||||||
|
assert.True(t, getHistoricalEvents)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
@ -103,7 +103,7 @@ type Request struct {
|
|||||||
// allows the caller to put a limit on how many
|
// allows the caller to put a limit on how many
|
||||||
// events they receive. If there are more events than MaxEventsReturned
|
// events they receive. If there are more events than MaxEventsReturned
|
||||||
// then the most chronologically recent events in the time period
|
// then the most chronologically recent events in the time period
|
||||||
// specified are returned
|
// specified are returned. Must be >= 1
|
||||||
MaxEventsReturned int
|
MaxEventsReturned int
|
||||||
// the absolute container name for which the event occurred
|
// the absolute container name for which the event occurred
|
||||||
ContainerName string
|
ContainerName string
|
||||||
|
@ -74,6 +74,9 @@ type Manager interface {
|
|||||||
|
|
||||||
// Get events streamed through passedChannel that fit the request.
|
// Get events streamed through passedChannel that fit the request.
|
||||||
WatchForEvents(request *events.Request, passedChannel chan *events.Event) error
|
WatchForEvents(request *events.Request, passedChannel chan *events.Event) error
|
||||||
|
|
||||||
|
// Get past events that have been detected and that fit the request.
|
||||||
|
GetPastEvents(request *events.Request) (events.EventSlice, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// New takes a memory storage and returns a new manager.
|
// New takes a memory storage and returns a new manager.
|
||||||
@ -474,6 +477,7 @@ func (m *manager) createContainer(containerName string) error {
|
|||||||
|
|
||||||
// Start the container's housekeeping.
|
// Start the container's housekeeping.
|
||||||
cont.Start()
|
cont.Start()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -596,8 +600,8 @@ func (self *manager) watchForNewContainers(quit chan error) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Register for new subcontainers.
|
// Register for new subcontainers.
|
||||||
events := make(chan container.SubcontainerEvent, 16)
|
eventsChannel := make(chan container.SubcontainerEvent, 16)
|
||||||
err := root.handler.WatchSubcontainers(events)
|
err := root.handler.WatchSubcontainers(eventsChannel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -612,7 +616,7 @@ func (self *manager) watchForNewContainers(quit chan error) error {
|
|||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case event := <-events:
|
case event := <-eventsChannel:
|
||||||
switch {
|
switch {
|
||||||
case event.EventType == container.SubcontainerAdd:
|
case event.EventType == container.SubcontainerAdd:
|
||||||
err = self.createContainer(event.Name)
|
err = self.createContainer(event.Name)
|
||||||
@ -668,3 +672,8 @@ func (self *manager) watchForNewOoms() error {
|
|||||||
func (self *manager) WatchForEvents(request *events.Request, passedChannel chan *events.Event) error {
|
func (self *manager) WatchForEvents(request *events.Request, passedChannel chan *events.Event) error {
|
||||||
return self.eventHandler.WatchEvents(passedChannel, request)
|
return self.eventHandler.WatchEvents(passedChannel, request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// can be called by the api which will return all events satisfying the request
|
||||||
|
func (self *manager) GetPastEvents(request *events.Request) (events.EventSlice, error) {
|
||||||
|
return self.eventHandler.GetEvents(request)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user