diff --git a/pkg/client/client.go b/pkg/client/client.go index 7fd0772ec56..8af1d7e49d8 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -33,7 +33,7 @@ type Interface interface { EndpointsNamespacer VersionInterface MinionsInterface - EventsInterface + EventNamespacer } func (c *Client) ReplicationControllers(namespace string) ReplicationControllerInterface { @@ -44,8 +44,8 @@ func (c *Client) Minions() MinionInterface { return newMinions(c) } -func (c *Client) Events() EventInterface { - return newEvents(c) +func (c *Client) Events(namespace string) EventInterface { + return newEvents(c, namespace) } func (c *Client) Endpoints(namespace string) EndpointsInterface { diff --git a/pkg/client/client_test.go b/pkg/client/client_test.go index 46809e93c17..11d3b3a451e 100644 --- a/pkg/client/client_test.go +++ b/pkg/client/client_test.go @@ -23,6 +23,7 @@ import ( "net/url" "path" "reflect" + "strings" "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" @@ -121,6 +122,7 @@ func (c *testClient) ValidateCommon(t *testing.T, err error) { requestBody := body(c.Request.Body, c.Request.RawBody) actualQuery := c.handler.RequestReceived.URL.Query() + t.Logf("got query: %v", actualQuery) // We check the query manually, so blank it out so that FakeHandler.ValidateRequest // won't check it. c.handler.RequestReceived.URL.RawQuery = "" @@ -128,11 +130,17 @@ func (c *testClient) ValidateCommon(t *testing.T, err error) { for key, values := range c.Request.Query { validator, ok := c.QueryValidator[key] if !ok { - validator = func(a, b string) bool { return a == b } + switch key { + case "labels", "fields": + validator = validateLabels + default: + validator = func(a, b string) bool { return a == b } + } } observed := actualQuery.Get(key) - if !validator(values[0], observed) { - t.Errorf("Unexpected query arg for key: %s. Expected %s, Received %s", key, values[0], observed) + wanted := strings.Join(values, "") + if !validator(wanted, observed) { + t.Errorf("Unexpected query arg for key: %s. Expected %s, Received %s", key, wanted, observed) } } if c.Request.Header != "" { diff --git a/pkg/client/events.go b/pkg/client/events.go index a0123bdb168..a8cd6dc66d1 100644 --- a/pkg/client/events.go +++ b/pkg/client/events.go @@ -17,14 +17,17 @@ limitations under the License. package client import ( + "fmt" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/watch" ) -// Events has methods to work with Event resources -type EventsInterface interface { - Events() EventInterface +// EventNamespacer can return an EventInterface for the given namespace. +type EventNamespacer interface { + Events(namespace string) EventInterface } // EventInterface has methods to work with Event resources @@ -33,32 +36,48 @@ type EventInterface interface { List(label, field labels.Selector) (*api.EventList, error) Get(id string) (*api.Event, error) Watch(label, field labels.Selector, resourceVersion string) (watch.Interface, error) + // Search finds events about the specified object + Search(objOrRef runtime.Object) (*api.EventList, error) } // events implements Events interface type events struct { - r *Client + client *Client + namespace string } -// newEvents returns a events -func newEvents(c *Client) *events { +// newEvents returns a new events object. +func newEvents(c *Client, ns string) *events { return &events{ - r: c, + client: c, + namespace: ns, } } -// Create makes a new event. Returns the copy of the event the server returns, or an error. -func (c *events) Create(event *api.Event) (*api.Event, error) { +// Create makes a new event. Returns the copy of the event the server returns, +// or an error. The namespace to create the event within is deduced from the +// event; it must either match this event client's namespace, or this event +// client must have been created with the "" namespace. +func (e *events) Create(event *api.Event) (*api.Event, error) { + if e.namespace != "" && event.Namespace != e.namespace { + return nil, fmt.Errorf("can't create an event with namespace '%v' in namespace '%v'", event.Namespace, e.namespace) + } result := &api.Event{} - err := c.r.Post().Path("events").Namespace(event.Namespace).Body(event).Do().Into(result) + err := e.client.Post(). + Path("events"). + Namespace(event.Namespace). + Body(event). + Do(). + Into(result) return result, err } // List returns a list of events matching the selectors. -func (c *events) List(label, field labels.Selector) (*api.EventList, error) { +func (e *events) List(label, field labels.Selector) (*api.EventList, error) { result := &api.EventList{} - err := c.r.Get(). + err := e.client.Get(). Path("events"). + Namespace(e.namespace). SelectorParam("labels", label). SelectorParam("fields", field). Do(). @@ -67,19 +86,45 @@ func (c *events) List(label, field labels.Selector) (*api.EventList, error) { } // Get returns the given event, or an error. -func (c *events) Get(id string) (*api.Event, error) { +func (e *events) Get(id string) (*api.Event, error) { result := &api.Event{} - err := c.r.Get().Path("events").Path(id).Do().Into(result) + err := e.client.Get(). + Path("events"). + Path(id). + Namespace(e.namespace). + Do(). + Into(result) return result, err } // Watch starts watching for events matching the given selectors. -func (c *events) Watch(label, field labels.Selector, resourceVersion string) (watch.Interface, error) { - return c.r.Get(). +func (e *events) Watch(label, field labels.Selector, resourceVersion string) (watch.Interface, error) { + return e.client.Get(). Path("watch"). Path("events"). Param("resourceVersion", resourceVersion). + Namespace(e.namespace). SelectorParam("labels", label). SelectorParam("fields", field). Watch() } + +// Search finds events about the specified object. The namespace of the +// object must match this event's client namespace unless the event client +// was made with the "" namespace. +func (e *events) Search(objOrRef runtime.Object) (*api.EventList, error) { + ref, err := api.GetReference(objOrRef) + if err != nil { + return nil, err + } + // TODO: search by UID if it's set + fields := labels.Set{ + "involvedObject.kind": ref.Kind, + "involvedObject.namespace": ref.Namespace, + "involvedObject.name": ref.Name, + }.AsSelector() + if e.namespace != "" && ref.Namespace != e.namespace { + return nil, fmt.Errorf("won't be able to find any events of namespace '%v' in namespace '%v'", ref.Namespace, e.namespace) + } + return e.List(labels.Everything(), fields) +} diff --git a/pkg/client/events_test.go b/pkg/client/events_test.go new file mode 100644 index 00000000000..3dd8cfc4381 --- /dev/null +++ b/pkg/client/events_test.go @@ -0,0 +1,49 @@ +/* +Copyright 2014 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 client + +import ( + "net/url" + "testing" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi" +) + +func TestEventSearch(t *testing.T) { + c := &testClient{ + Request: testRequest{ + Method: "GET", + Path: "/events", + Query: url.Values{ + "fields": []string{"involvedObject.kind=Pod,involvedObject.name=foo,involvedObject.namespace=baz"}, + "labels": []string{}, + }, + }, + Response: Response{StatusCode: 200, Body: &api.EventList{}}, + } + eventList, err := c.Setup().Events("").Search( + &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: "baz", + SelfLink: testapi.SelfLink("pods", ""), + }, + }, + ) + c.Validate(t, eventList, err) +} diff --git a/pkg/client/fake.go b/pkg/client/fake.go index b7a508ad9ea..793c8d216fa 100644 --- a/pkg/client/fake.go +++ b/pkg/client/fake.go @@ -49,7 +49,7 @@ func (c *Fake) Minions() MinionInterface { return &FakeMinions{Fake: c} } -func (c *Fake) Events() EventInterface { +func (c *Fake) Events(namespace string) EventInterface { return &FakeEvents{Fake: c} } diff --git a/pkg/client/fake_events.go b/pkg/client/fake_events.go index 4dc3fca0d16..cdc5a61b356 100644 --- a/pkg/client/fake_events.go +++ b/pkg/client/fake_events.go @@ -19,6 +19,7 @@ package client import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/watch" ) @@ -51,3 +52,9 @@ func (c *FakeEvents) Watch(label, field labels.Selector, resourceVersion string) c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "watch-events", Value: resourceVersion}) return c.Fake.Watch, c.Fake.Err } + +// Search returns a list of events matching the specified object. +func (c *FakeEvents) Search(objOrRef runtime.Object) (*api.EventList, error) { + c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "search-events"}) + return &c.Fake.EventsList, nil +} diff --git a/plugin/cmd/scheduler/scheduler.go b/plugin/cmd/scheduler/scheduler.go index 44c364fc4e8..3b5541af0be 100644 --- a/plugin/cmd/scheduler/scheduler.go +++ b/plugin/cmd/scheduler/scheduler.go @@ -56,7 +56,7 @@ func main() { glog.Fatalf("Invalid API configuration: %v", err) } - record.StartRecording(kubeClient.Events(), "scheduler") + record.StartRecording(kubeClient.Events(""), "scheduler") go http.ListenAndServe(net.JoinHostPort(address.String(), strconv.Itoa(*port)), nil)