Skip to content

Commit 0dfa447

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

File tree

5 files changed

+276
-12
lines changed

5 files changed

+276
-12
lines changed

api/handler.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@ import (
2323
"path"
2424
"regexp"
2525
"sort"
26+
"strconv"
2627
"strings"
2728
"time"
2829

2930
"github.com/golang/glog"
31+
"github.com/google/cadvisor/events"
3032
"github.com/google/cadvisor/info"
3133
"github.com/google/cadvisor/manager"
3234
)
@@ -125,6 +127,7 @@ func writeResult(res interface{}, w http.ResponseWriter) error {
125127
w.Header().Set("Content-Type", "application/json")
126128
w.Write(out)
127129
return nil
130+
128131
}
129132

130133
func getContainerInfoRequest(body io.ReadCloser) (*info.ContainerInfoRequest, error) {
@@ -142,6 +145,74 @@ func getContainerInfoRequest(body io.ReadCloser) (*info.ContainerInfoRequest, er
142145
return &query, nil
143146
}
144147

148+
// The user can set any or none of the following arguments in any order
149+
// with any twice defined arguments being assigned the first value.
150+
// If the value type for the argument is wrong the field will be assumed to be
151+
// unassigned
152+
// bools: historical, subcontainers, oom_events, creation_events, deletion_events
153+
// ints: max_events, start_time (unix timestamp), end_time (unix timestamp)
154+
// example r.URL: http://localhost:8080/api/v1.3/events?oom_events=true&historical=true&max_events=10
155+
func getEventRequest(r *http.Request) (*events.Request, bool, error) {
156+
query := events.NewRequest()
157+
getHistoricalEvents := false
158+
159+
urlMap := r.URL.Query()
160+
161+
if val, ok := urlMap["historical"]; ok {
162+
newBool, err := strconv.ParseBool(val[0])
163+
if err == nil {
164+
getHistoricalEvents = newBool
165+
}
166+
}
167+
if val, ok := urlMap["subcontainers"]; ok {
168+
newBool, err := strconv.ParseBool(val[0])
169+
if err == nil {
170+
query.IncludeSubcontainers = newBool
171+
}
172+
}
173+
if val, ok := urlMap["oom_events"]; ok {
174+
newBool, err := strconv.ParseBool(val[0])
175+
if err == nil {
176+
query.EventType[events.TypeOom] = newBool
177+
}
178+
}
179+
if val, ok := urlMap["creation_events"]; ok {
180+
newBool, err := strconv.ParseBool(val[0])
181+
if err == nil {
182+
query.EventType[events.TypeContainerCreation] = newBool
183+
}
184+
}
185+
if val, ok := urlMap["deletion_events"]; ok {
186+
newBool, err := strconv.ParseBool(val[0])
187+
if err == nil {
188+
query.EventType[events.TypeContainerDeletion] = newBool
189+
}
190+
}
191+
if val, ok := urlMap["max_events"]; ok {
192+
newInt, err := strconv.Atoi(val[0])
193+
if err == nil {
194+
query.MaxEventsReturned = int(newInt)
195+
}
196+
}
197+
if val, ok := urlMap["start_time"]; ok {
198+
newTime, err := time.Parse(time.RFC3339, val[0])
199+
if err == nil {
200+
query.StartTime = newTime
201+
}
202+
}
203+
if val, ok := urlMap["end_time"]; ok {
204+
newTime, err := time.Parse(time.RFC3339, val[0])
205+
if err == nil {
206+
query.EndTime = newTime
207+
}
208+
}
209+
210+
glog.V(2).Infof(
211+
"%v was returned in api/handler.go:getEventRequest from the url rawQuery %v",
212+
query, r.URL.RawQuery)
213+
return query, getHistoricalEvents, nil
214+
}
215+
145216
func getContainerName(request []string) string {
146217
return path.Join("/", strings.Join(request, "/"))
147218
}

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(r)
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: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
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+
"io"
19+
"net/http"
20+
"reflect"
21+
"testing"
22+
23+
"github.com/google/cadvisor/events"
24+
"github.com/stretchr/testify/assert"
25+
)
26+
27+
// returns an http.Request pointer for an input url test string
28+
func makeHTTPRequest(requestURL string, t *testing.T) *http.Request {
29+
dummyReader, _ := io.Pipe()
30+
r, err := http.NewRequest("GET", requestURL, dummyReader)
31+
assert.Nil(t, err)
32+
return r
33+
}
34+
35+
func TestGetEventRequestBasicRequest(t *testing.T) {
36+
r := makeHTTPRequest("http://localhost:8080/api/v1.3/events?oom_events=true&historical=true&max_events=10", t)
37+
expectedQuery := &events.Request{
38+
EventType: map[events.EventType]bool{
39+
events.TypeOom: true,
40+
},
41+
MaxEventsReturned: 10,
42+
}
43+
44+
receivedQuery, getHistoricalEvents, err := getEventRequest(r)
45+
46+
if !reflect.DeepEqual(expectedQuery, receivedQuery) {
47+
t.Errorf("expected %v but received %v", expectedQuery, receivedQuery)
48+
}
49+
assert.True(t, getHistoricalEvents)
50+
assert.Nil(t, err)
51+
}
52+
53+
func TestGetEventEmptyRequest(t *testing.T) {
54+
r := makeHTTPRequest("", t)
55+
expectedQuery := events.NewRequest()
56+
57+
receivedQuery, getHistoricalEvents, err := getEventRequest(r)
58+
59+
if !reflect.DeepEqual(expectedQuery, receivedQuery) {
60+
t.Errorf("expected %v but received %v", expectedQuery, receivedQuery)
61+
}
62+
assert.False(t, getHistoricalEvents)
63+
assert.Nil(t, err)
64+
}
65+
66+
func TestGetEventEmptyStringRequest(t *testing.T) {
67+
r := makeHTTPRequest("/", t)
68+
expectedQuery := events.NewRequest()
69+
70+
receivedQuery, getHistoricalEvents, err := getEventRequest(r)
71+
72+
if !reflect.DeepEqual(expectedQuery, receivedQuery) {
73+
t.Errorf("expected %v but received %v", expectedQuery, receivedQuery)
74+
}
75+
assert.False(t, getHistoricalEvents)
76+
assert.Nil(t, err)
77+
}
78+
79+
func TestGetEventRequestDoubleArgument(t *testing.T) {
80+
r := makeHTTPRequest("http://localhost:8080/api/v1.3/events?historical=true&oom_events=true&oom_events=false", t)
81+
expectedQuery := &events.Request{
82+
EventType: map[events.EventType]bool{
83+
events.TypeOom: true,
84+
},
85+
}
86+
87+
receivedQuery, getHistoricalEvents, err := getEventRequest(r)
88+
89+
if !reflect.DeepEqual(expectedQuery, receivedQuery) {
90+
t.Errorf("expected %v but received %v", expectedQuery, receivedQuery)
91+
}
92+
assert.True(t, getHistoricalEvents)
93+
assert.Nil(t, err)
94+
}

events/handler.go

Lines changed: 1 addition & 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

manager/manager.go

Lines changed: 40 additions & 6 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.
@@ -474,6 +477,19 @@ func (m *manager) createContainer(containerName string) error {
474477

475478
// Start the container's housekeeping.
476479
cont.Start()
480+
481+
newEvent := &events.Event{
482+
EventType: events.TypeContainerCreation,
483+
Timestamp: time.Now(),
484+
ContainerName: containerName,
485+
EventData: cont,
486+
}
487+
err = m.eventHandler.AddEvent(newEvent)
488+
if err != nil {
489+
glog.Errorf("Failed to add event %v, got error: %v", newEvent, err)
490+
return err
491+
}
492+
477493
return nil
478494
}
479495

@@ -505,6 +521,18 @@ func (m *manager) destroyContainer(containerName string) error {
505521
})
506522
}
507523
glog.Infof("Destroyed container: %q (aliases: %v, namespace: %q)", containerName, cont.info.Aliases, cont.info.Namespace)
524+
525+
newEvent := &events.Event{
526+
EventType: events.TypeContainerDeletion,
527+
Timestamp: time.Now(),
528+
ContainerName: containerName,
529+
EventData: cont,
530+
}
531+
err = m.eventHandler.AddEvent(newEvent)
532+
if err != nil {
533+
glog.Errorf("Failed to add event %v, got error: %v", newEvent, err)
534+
return err
535+
}
508536
return nil
509537
}
510538

@@ -596,8 +624,8 @@ func (self *manager) watchForNewContainers(quit chan error) error {
596624
}
597625

598626
// Register for new subcontainers.
599-
events := make(chan container.SubcontainerEvent, 16)
600-
err := root.handler.WatchSubcontainers(events)
627+
eventsChannel := make(chan container.SubcontainerEvent, 16)
628+
err := root.handler.WatchSubcontainers(eventsChannel)
601629
if err != nil {
602630
return err
603631
}
@@ -612,15 +640,16 @@ func (self *manager) watchForNewContainers(quit chan error) error {
612640
go func() {
613641
for {
614642
select {
615-
case event := <-events:
643+
case event := <-eventsChannel:
616644
switch {
617645
case event.EventType == container.SubcontainerAdd:
618646
err = self.createContainer(event.Name)
619647
case event.EventType == container.SubcontainerDelete:
620648
err = self.destroyContainer(event.Name)
621-
}
622-
if err != nil {
623-
glog.Warning("Failed to process watch event: %v", err)
649+
if err != nil {
650+
glog.Warning("Failed to process watch event: %v", err)
651+
}
652+
624653
}
625654
case <-quit:
626655
// Stop processing events if asked to quit.
@@ -668,3 +697,8 @@ func (self *manager) watchForNewOoms() error {
668697
func (self *manager) WatchForEvents(request *events.Request, passedChannel chan *events.Event) error {
669698
return self.eventHandler.WatchEvents(passedChannel, request)
670699
}
700+
701+
// can be called by the api which will return all events satisfying the request
702+
func (self *manager) GetPastEvents(request *events.Request) (events.EventSlice, error) {
703+
return self.eventHandler.GetEvents(request)
704+
}

0 commit comments

Comments
 (0)