Skip to content

Commit e25510b

Browse files
committed
Added a basic api interface for events
1 parent e7a3518 commit e25510b

File tree

5 files changed

+265
-6
lines changed

5 files changed

+265
-6
lines changed

api/handler.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,20 @@ 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
)
@@ -125,6 +129,7 @@ func writeResult(res interface{}, w http.ResponseWriter) error {
125129
w.Header().Set("Content-Type", "application/json")
126130
w.Write(out)
127131
return nil
132+
128133
}
129134

130135
func getContainerInfoRequest(body io.ReadCloser) (*info.ContainerInfoRequest, error) {
@@ -142,6 +147,84 @@ func getContainerInfoRequest(body io.ReadCloser) (*info.ContainerInfoRequest, er
142147
return &query, nil
143148
}
144149

150+
// request is the rawQuery portion of the url input by the user. It should have
151+
// length 1, otherwise the url was ill formatted (backslashes must have
152+
// been inserted in the argument section of the url) and function will return an
153+
// error. The user can set any or none of the following arguments in any order
154+
// with any twice defined arguments being assigned the first value.
155+
// If the value type for the argument is wrong the field will be assumed to be
156+
// unassigned
157+
// bools: all_time, subcontainers, oom_events, creation_events, deletion_events
158+
// ints: max_events, start_time (unix timestamp), end_time (unix timestamp)
159+
// example request: ["oom_events=true&max_events=10"]
160+
func getEventRequest(request []string) (*events.Request, bool, error) {
161+
if len(request) <= 0 || len(request[0]) == 0 {
162+
return events.NewAllEventsRequest(), true, nil
163+
} else if len(request) > 1 {
164+
return nil, false, errors.New(
165+
"should have received one url rawQuery but got multiple")
166+
}
167+
rawQuery := strings.Trim(request[0], "/ ")
168+
169+
query := events.NewRequest()
170+
getEventsFromAllTime := false
171+
172+
urlMap, err := url.ParseQuery(rawQuery)
173+
if err != nil {
174+
glog.Errorf("url parse error of %v is %v", rawQuery, err)
175+
return nil, false, err
176+
}
177+
178+
for key, val := range urlMap {
179+
if key == "all_time" {
180+
newBool, err := strconv.ParseBool(val[0])
181+
if err == nil {
182+
getEventsFromAllTime = newBool
183+
}
184+
} else if key == "subcontainers" {
185+
newBool, err := strconv.ParseBool(val[0])
186+
if err == nil {
187+
query.IncludeSubcontainers = newBool
188+
}
189+
} else if key == "oom_events" {
190+
newBool, err := strconv.ParseBool(val[0])
191+
if err == nil {
192+
query.EventType[events.TypeOom] = newBool
193+
}
194+
} else if key == "creation_events" {
195+
newBool, err := strconv.ParseBool(val[0])
196+
if err == nil {
197+
query.EventType[events.TypeContainerCreation] = newBool
198+
}
199+
} else if key == "deletion_events" {
200+
newBool, err := strconv.ParseBool(val[0])
201+
if err == nil {
202+
query.EventType[events.TypeContainerDeletion] = newBool
203+
}
204+
} else if key == "max_events" {
205+
newInt, err := strconv.Atoi(val[0])
206+
if err == nil {
207+
query.MaxEventsReturned = int(newInt)
208+
}
209+
} else if key == "start_time" {
210+
newTime, err := time.Parse(time.RFC3339, val[0])
211+
if err == nil {
212+
query.StartTime = newTime
213+
}
214+
} else if key == "end_time" {
215+
newTime, err := time.Parse(time.RFC3339, val[0])
216+
if err == nil {
217+
query.EndTime = newTime
218+
}
219+
}
220+
}
221+
222+
glog.V(2).Infof(
223+
"%v was returned in api/handler.go:getEventRequest from the url rawQuery %v",
224+
query, rawQuery)
225+
return query, getEventsFromAllTime, nil
226+
}
227+
145228
func getContainerName(request []string) string {
146229
return path.Join("/", strings.Join(request, "/"))
147230
}

api/versions.go

Lines changed: 70 additions & 5 deletions
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
@@ -49,8 +51,11 @@ func getApiVersions() []ApiVersion {
4951
v1_0 := &version1_0{}
5052
v1_1 := newVersion1_1(v1_0)
5153
v1_2 := newVersion1_2(v1_1)
52-
v2_0 := newVersion2_0(v1_2)
53-
return []ApiVersion{v1_0, v1_1, v1_2, v2_0}
54+
v1_3 := newVersion1_3(v1_2)
55+
v2_0 := newVersion2_0(v1_3)
56+
57+
return []ApiVersion{v1_0, v1_1, v1_2, v1_3, v2_0}
58+
5459
}
5560

5661
// API v1.0
@@ -227,12 +232,72 @@ func (self *version1_2) HandleRequest(requestType string, request []string, m ma
227232
}
228233
}
229234

230-
// v2.0 builds on v1.2
231-
type version2_0 struct {
235+
// API v1.3
236+
237+
type version1_3 struct {
232238
baseVersion *version1_2
233239
}
234240

235-
func newVersion2_0(v *version1_2) *version2_0 {
241+
// v1.3 builds on v1.2.
242+
func newVersion1_3(v *version1_2) *version1_3 {
243+
return &version1_3{
244+
baseVersion: v,
245+
}
246+
}
247+
248+
func (self *version1_3) Version() string {
249+
return "v1.3"
250+
}
251+
252+
func (self *version1_3) SupportedRequestTypes() []string {
253+
return append(self.baseVersion.SupportedRequestTypes(), eventsApi)
254+
}
255+
256+
func (self *version1_3) HandleRequest(requestType string, request []string, m manager.Manager, w http.ResponseWriter, r *http.Request) error {
257+
switch requestType {
258+
case eventsApi:
259+
query, eventsFromAllTime, err := getEventRequest(request)
260+
if err != nil {
261+
return err
262+
}
263+
glog.V(2).Infof("Api - Events(%v)", query)
264+
265+
if eventsFromAllTime {
266+
allEvents, err := m.GetPastEvents(query)
267+
if err != nil {
268+
return err
269+
}
270+
return writeResult(allEvents, w)
271+
} else {
272+
// every time URL is entered to watch, a channel is created here
273+
eventChannel := make(chan *events.Event, 10)
274+
err = m.WatchForEvents(query, eventChannel)
275+
276+
defer close(eventChannel)
277+
currentEventSet := make(events.EventSlice, 0)
278+
for ev := range eventChannel {
279+
// todo: implement write-as-received writeResult method
280+
currentEventSet = append(currentEventSet, ev)
281+
err = writeResult(currentEventSet, w)
282+
if err != nil {
283+
return err
284+
}
285+
}
286+
}
287+
return nil
288+
default:
289+
return self.baseVersion.HandleRequest(requestType, request, m, w, r)
290+
}
291+
}
292+
293+
// API v2.0
294+
295+
// v2.0 builds on v1.3
296+
type version2_0 struct {
297+
baseVersion *version1_3
298+
}
299+
300+
func newVersion2_0(v *version1_3) *version2_0 {
236301
return &version2_0{
237302
baseVersion: v,
238303
}

api/versions_test.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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{"all_time=true&oom_events=true&max_events=10"}
26+
expectedQuery := &events.Request{
27+
EventType: map[events.EventType]bool{
28+
events.TypeOom: true,
29+
},
30+
MaxEventsReturned: 10,
31+
}
32+
receivedQuery, shouldGetAllEvents, err := getEventRequest(request)
33+
if !reflect.DeepEqual(expectedQuery, receivedQuery) {
34+
t.Errorf("expected %v but received %v", expectedQuery, receivedQuery)
35+
}
36+
if !shouldGetAllEvents {
37+
t.Errorf("expected to call GetEvents rather than WatchEvents")
38+
}
39+
if err != nil {
40+
t.Errorf("got error when parsing event request url: %v", err)
41+
}
42+
}
43+
44+
func TestGetEventEmptyRequest(t *testing.T) {
45+
var request = []string{}
46+
expectedQuery := events.NewAllEventsRequest()
47+
receivedQuery, shouldGetAllEvents, err := getEventRequest(request)
48+
if !reflect.DeepEqual(expectedQuery, receivedQuery) {
49+
t.Errorf("expected %v but received %v", expectedQuery, receivedQuery)
50+
}
51+
if !shouldGetAllEvents {
52+
t.Errorf("expected to call GetEvents rather than WatchEvents")
53+
}
54+
if err != nil {
55+
t.Errorf("got error when parsing event request url: %v", err)
56+
}
57+
}
58+
59+
func TestGetEventEmptyStringRequest(t *testing.T) {
60+
var request = []string{""}
61+
expectedQuery := events.NewAllEventsRequest()
62+
receivedQuery, shouldGetAllEvents, err := getEventRequest(request)
63+
if !reflect.DeepEqual(expectedQuery, receivedQuery) {
64+
t.Errorf("expected %v but received %v", expectedQuery, receivedQuery)
65+
}
66+
if !shouldGetAllEvents {
67+
t.Errorf("expected to call GetEvents rather than WatchEvents")
68+
}
69+
if err != nil {
70+
t.Errorf("got error when parsing event request url: %v", err)
71+
}
72+
}
73+
74+
func TestGetEventRequestDoubleArgument(t *testing.T) {
75+
var request = []string{"all_time=true&oom_events=true&oom_events=false"}
76+
expectedQuery := &events.Request{
77+
EventType: map[events.EventType]bool{
78+
events.TypeOom: true,
79+
},
80+
}
81+
receivedQuery, shouldGetAllEvents, err := getEventRequest(request)
82+
if !reflect.DeepEqual(expectedQuery, receivedQuery) {
83+
t.Errorf("expected %v but received %v", expectedQuery, receivedQuery)
84+
}
85+
if !shouldGetAllEvents {
86+
t.Errorf("expected to call GetEvents rather than WatchEvents")
87+
}
88+
if err != nil {
89+
t.Errorf("got error when parsing event request url: %v", err)
90+
}
91+
}

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)