Skip to content

Commit 8048468

Browse files
committed
Added a basic api interface for events
1 parent ae9c4cd commit 8048468

File tree

5 files changed

+242
-2
lines changed

5 files changed

+242
-2
lines changed

api/handler.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,29 @@ package api
1717

1818
import (
1919
"encoding/json"
20+
"errors"
2021
"fmt"
2122
"io"
2223
"net/http"
24+
"net/url"
2325
"path"
2426
"regexp"
2527
"sort"
28+
"strconv"
2629
"strings"
2730
"time"
2831

2932
"github.com/golang/glog"
33+
"github.com/google/cadvisor/events"
3034
"github.com/google/cadvisor/info"
3135
"github.com/google/cadvisor/manager"
3236
)
3337

38+
type urlData struct {
39+
boolVal bool
40+
intVal int
41+
}
42+
3443
const (
3544
apiResource = "/api/"
3645
)
@@ -125,6 +134,7 @@ func writeResult(res interface{}, w http.ResponseWriter) error {
125134
w.Header().Set("Content-Type", "application/json")
126135
w.Write(out)
127136
return nil
137+
128138
}
129139

130140
func getContainerInfoRequest(body io.ReadCloser) (*info.ContainerInfoRequest, error) {
@@ -142,6 +152,57 @@ func getContainerInfoRequest(body io.ReadCloser) (*info.ContainerInfoRequest, er
142152
return &query, nil
143153
}
144154

155+
func getEventRequest(request []string) (*events.Request, bool, error) {
156+
if len(request) <= 0 || len(request[0]) == 0 {
157+
return events.NewAllEventsRequest(), true, nil
158+
} else if len(request) > 1 {
159+
return nil, false, errors.New("Should have received one url rawQuery but got multiple")
160+
}
161+
rawQuery := strings.Trim(request[0], "/ ")
162+
163+
query := events.NewRequest()
164+
getEventsFromAllTime := false
165+
166+
urlMap, err := url.ParseQuery(rawQuery)
167+
if err != nil {
168+
glog.Errorf("url parse error of %v is %v", rawQuery, err)
169+
return nil, false, err
170+
}
171+
172+
valueMap := make(map[string]urlData)
173+
for key, val := range urlMap {
174+
// if a variable is assigned twice, ex. &TypeOom=true&TypeOom=false
175+
// then only the first will be taken
176+
newInt, err := strconv.ParseInt(val[0], 0, 0)
177+
if err == nil {
178+
valueMap[key] = urlData{intVal: int(newInt)}
179+
continue
180+
}
181+
newBool, err := strconv.ParseBool(val[0])
182+
if err == nil {
183+
valueMap[key] = urlData{boolVal: newBool}
184+
}
185+
}
186+
187+
getEventsFromAllTime = valueMap["allTime"].boolVal
188+
query.IncludeSubcontainers = valueMap["IncludeSubcontainers"].boolVal
189+
query.EventType[events.TypeOom] = valueMap["TypeOom"].boolVal
190+
query.EventType[events.TypeContainerCreation] = valueMap["TypeContainerCreation"].boolVal
191+
query.EventType[events.TypeContainerDeletion] = valueMap["TypeContainerDeletion"].boolVal
192+
query.MaxEventsReturned = valueMap["MaxEventsReturned"].intVal
193+
194+
if mapVal, ok := valueMap["StartTime"]; ok {
195+
query.StartTime = time.Now().Add(time.Duration(-1 * mapVal.intVal))
196+
}
197+
if mapVal, ok := valueMap["EndTime"]; ok {
198+
query.EndTime = time.Now().Add(time.Duration(-1 * mapVal.intVal))
199+
}
200+
201+
glog.V(2).Infof("%v was returned in api/handler.go:getEventRequest from the url rawQuery %v",
202+
query, rawQuery)
203+
return query, getEventsFromAllTime, nil
204+
}
205+
145206
func getContainerName(request []string) string {
146207
return path.Join("/", strings.Join(request, "/"))
147208
}

api/versions.go

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"net/http"
2020

2121
"github.com/golang/glog"
22+
"github.com/google/cadvisor/events"
2223
"github.com/google/cadvisor/info"
2324
"github.com/google/cadvisor/manager"
2425
)
@@ -30,6 +31,7 @@ const (
3031
dockerApi = "docker"
3132
summaryApi = "summary"
3233
specApi = "spec"
34+
eventsApi = "events"
3335
)
3436

3537
// Interface for a cAdvisor API version
@@ -50,7 +52,9 @@ func getApiVersions() []ApiVersion {
5052
v1_1 := newVersion1_1(v1_0)
5153
v1_2 := newVersion1_2(v1_1)
5254
v2_0 := newVersion2_0(v1_2)
53-
return []ApiVersion{v1_0, v1_1, v1_2, v2_0}
55+
v1_3 := newVersion1_3(v1_2)
56+
return []ApiVersion{v1_0, v1_1, v1_2, v2_0, v1_3}
57+
5458
}
5559

5660
// API v1.0
@@ -270,3 +274,63 @@ func (self *version2_0) HandleRequest(requestType string, request []string, m ma
270274
return self.baseVersion.HandleRequest(requestType, request, m, w, r)
271275
}
272276
}
277+
278+
// API v1.3
279+
280+
type version1_3 struct {
281+
baseVersion *version1_2
282+
}
283+
284+
// v1.3 builds on v1.2.
285+
func newVersion1_3(v *version1_2) *version1_3 {
286+
return &version1_3{
287+
baseVersion: v,
288+
}
289+
}
290+
291+
func (self *version1_3) Version() string {
292+
return "v1.3"
293+
}
294+
295+
func (self *version1_3) SupportedRequestTypes() []string {
296+
return append(self.baseVersion.SupportedRequestTypes(), eventsApi)
297+
}
298+
299+
func (self *version1_3) HandleRequest(requestType string, request []string, m manager.Manager, w http.ResponseWriter, r *http.Request) error {
300+
switch {
301+
case requestType == eventsApi:
302+
query, eventsFromAllTime, err := getEventRequest(request)
303+
if err != nil {
304+
return err
305+
}
306+
glog.V(2).Infof("Api - Events(%v)", query)
307+
308+
if eventsFromAllTime {
309+
allEvents, err := m.GetPastEvents(query)
310+
if err != nil {
311+
return err
312+
}
313+
return writeResult(allEvents, w)
314+
} else {
315+
// every time URL is entered to watch, a channel is created here
316+
eventChannel := make(chan *events.Event)
317+
err = m.WatchForEvents(query, eventChannel)
318+
319+
go func() {
320+
defer close(eventChannel)
321+
currentEventSet := make(events.EventSlice, 0)
322+
for ev := range eventChannel {
323+
// todo: implement write-as-received writeResult method
324+
currentEventSet = append(currentEventSet, ev)
325+
err = writeResult(currentEventSet, w)
326+
if err != nil {
327+
glog.Errorf("Error writing result %v was %v", currentEventSet, err)
328+
}
329+
}
330+
}()
331+
}
332+
return nil
333+
default:
334+
return self.baseVersion.HandleRequest(requestType, request, m, w, r)
335+
}
336+
}

api/versions_test.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Copyright 2015 Google Inc. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package api
16+
17+
import (
18+
"reflect"
19+
"testing"
20+
21+
"github.com/google/cadvisor/events"
22+
)
23+
24+
func TestGetEventRequestBasicRequest(t *testing.T) {
25+
var request = []string{"allTime=true&TypeOom=true&MaxEventsReturned=10"}
26+
expectedQuery := &events.Request{
27+
EventType: map[events.EventType]bool{
28+
events.TypeOom: true,
29+
events.TypeContainerCreation: false,
30+
events.TypeContainerDeletion: false,
31+
},
32+
MaxEventsReturned: 10,
33+
}
34+
receivedQuery, shouldGetAllEvents, err := getEventRequest(request)
35+
if !reflect.DeepEqual(expectedQuery, receivedQuery) {
36+
t.Errorf("expected %v but received %v", expectedQuery, receivedQuery)
37+
}
38+
if !shouldGetAllEvents {
39+
t.Errorf("expected to call GetEvents rather than WatchEvents")
40+
}
41+
if err != nil {
42+
t.Errorf("got error when parsing event request url: %v", err)
43+
}
44+
}
45+
46+
func TestGetEventEmptyRequest(t *testing.T) {
47+
var request = []string{}
48+
expectedQuery := events.NewAllEventsRequest()
49+
receivedQuery, shouldGetAllEvents, err := getEventRequest(request)
50+
if !reflect.DeepEqual(expectedQuery, receivedQuery) {
51+
t.Errorf("expected %v but received %v", expectedQuery, receivedQuery)
52+
}
53+
if !shouldGetAllEvents {
54+
t.Errorf("expected to call GetEvents rather than WatchEvents")
55+
}
56+
if err != nil {
57+
t.Errorf("got error when parsing event request url: %v", err)
58+
}
59+
}
60+
61+
func TestGetEventEmptyStringRequest(t *testing.T) {
62+
var request = []string{""}
63+
expectedQuery := events.NewAllEventsRequest()
64+
receivedQuery, shouldGetAllEvents, err := getEventRequest(request)
65+
if !reflect.DeepEqual(expectedQuery, receivedQuery) {
66+
t.Errorf("expected %v but received %v", expectedQuery, receivedQuery)
67+
}
68+
if !shouldGetAllEvents {
69+
t.Errorf("expected to call GetEvents rather than WatchEvents")
70+
}
71+
if err != nil {
72+
t.Errorf("got error when parsing event request url: %v", err)
73+
}
74+
}
75+
76+
func TestGetEventRequestDoubleArgument(t *testing.T) {
77+
var request = []string{"allTime=true&TypeOom=true&TypeOom=false"}
78+
expectedQuery := &events.Request{
79+
EventType: map[events.EventType]bool{
80+
events.TypeOom: true,
81+
events.TypeContainerCreation: false,
82+
events.TypeContainerDeletion: false,
83+
},
84+
}
85+
receivedQuery, shouldGetAllEvents, err := getEventRequest(request)
86+
if !reflect.DeepEqual(expectedQuery, receivedQuery) {
87+
t.Errorf("expected %v but received %v", expectedQuery, receivedQuery)
88+
}
89+
if !shouldGetAllEvents {
90+
t.Errorf("expected to call GetEvents rather than WatchEvents")
91+
}
92+
if err != nil {
93+
t.Errorf("got error when parsing event request url: %v", err)
94+
}
95+
}

events/handler.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ type Request struct {
103103
// allows the caller to put a limit on how many
104104
// events they receive. If there are more events than MaxEventsReturned
105105
// then the most chronologically recent events in the time period
106-
// specified are returned
106+
// specified are returned. Must be >= 1
107107
MaxEventsReturned int
108108
// the absolute container name for which the event occurred
109109
ContainerName string
@@ -143,6 +143,18 @@ func NewRequest() *Request {
143143
}
144144
}
145145

146+
// returns a pointer to an initialized Request object
147+
func NewAllEventsRequest() *Request {
148+
return &Request{
149+
EventType: map[EventType]bool{
150+
TypeOom: true,
151+
TypeContainerDeletion: true,
152+
TypeContainerCreation: true,
153+
},
154+
IncludeSubcontainers: true,
155+
}
156+
}
157+
146158
// returns a pointer to an initialized watch object
147159
func newWatch(request *Request, outChannel chan *Event) *watch {
148160
return &watch{

manager/manager.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ type Manager interface {
7474

7575
// Get events streamed through passedChannel that fit the request.
7676
WatchForEvents(request *events.Request, passedChannel chan *events.Event) error
77+
78+
// Get past events that have been detected and that fit the request.
79+
GetPastEvents(request *events.Request) (events.EventSlice, error)
7780
}
7881

7982
// New takes a memory storage and returns a new manager.
@@ -668,3 +671,8 @@ func (self *manager) watchForNewOoms() error {
668671
func (self *manager) WatchForEvents(request *events.Request, passedChannel chan *events.Event) error {
669672
return self.eventHandler.WatchEvents(passedChannel, request)
670673
}
674+
675+
// can be called by the api which will return all events satisfying the request
676+
func (self *manager) GetPastEvents(request *events.Request) (events.EventSlice, error) {
677+
return self.eventHandler.GetEvents(request)
678+
}

0 commit comments

Comments
 (0)