Allow clients to determine the difference between create or update on PUT

PUT allows an object to be created (http 201).  This allows REST code to
indicate an object has been created and clients to react to it.

APIServer now deals with <-chan RESTResult instead of <-chan runtime.Object,
allowing more data to be passed through.
This commit is contained in:
Clayton Coleman 2014-10-24 13:16:02 -04:00
parent 2bbd11eda6
commit d5ee171410
25 changed files with 297 additions and 87 deletions

View File

@ -103,7 +103,7 @@ func (storage *SimpleRESTStorage) Get(ctx api.Context, id string) (runtime.Objec
return api.Scheme.CopyOrDie(&storage.item), storage.errors["get"] return api.Scheme.CopyOrDie(&storage.item), storage.errors["get"]
} }
func (storage *SimpleRESTStorage) Delete(ctx api.Context, id string) (<-chan runtime.Object, error) { func (storage *SimpleRESTStorage) Delete(ctx api.Context, id string) (<-chan RESTResult, error) {
storage.deleted = id storage.deleted = id
if err := storage.errors["delete"]; err != nil { if err := storage.errors["delete"]; err != nil {
return nil, err return nil, err
@ -120,7 +120,7 @@ func (storage *SimpleRESTStorage) New() runtime.Object {
return &Simple{} return &Simple{}
} }
func (storage *SimpleRESTStorage) Create(ctx api.Context, obj runtime.Object) (<-chan runtime.Object, error) { func (storage *SimpleRESTStorage) Create(ctx api.Context, obj runtime.Object) (<-chan RESTResult, error) {
storage.created = obj.(*Simple) storage.created = obj.(*Simple)
if err := storage.errors["create"]; err != nil { if err := storage.errors["create"]; err != nil {
return nil, err return nil, err
@ -133,7 +133,7 @@ func (storage *SimpleRESTStorage) Create(ctx api.Context, obj runtime.Object) (<
}), nil }), nil
} }
func (storage *SimpleRESTStorage) Update(ctx api.Context, obj runtime.Object) (<-chan runtime.Object, error) { func (storage *SimpleRESTStorage) Update(ctx api.Context, obj runtime.Object) (<-chan RESTResult, error) {
storage.updated = obj.(*Simple) storage.updated = obj.(*Simple)
if err := storage.errors["update"]; err != nil { if err := storage.errors["update"]; err != nil {
return nil, err return nil, err

View File

@ -28,13 +28,38 @@ type WorkFunc func() (result runtime.Object, err error)
// MakeAsync takes a function and executes it, delivering the result in the way required // MakeAsync takes a function and executes it, delivering the result in the way required
// by RESTStorage's Update, Delete, and Create methods. // by RESTStorage's Update, Delete, and Create methods.
func MakeAsync(fn WorkFunc) <-chan runtime.Object { func MakeAsync(fn WorkFunc) <-chan RESTResult {
channel := make(chan runtime.Object) channel := make(chan RESTResult)
go func() { go func() {
defer util.HandleCrash() defer util.HandleCrash()
obj, err := fn() obj, err := fn()
if err != nil { if err != nil {
channel <- errToAPIStatus(err) channel <- RESTResult{Object: errToAPIStatus(err)}
} else {
channel <- RESTResult{Object: obj}
}
// 'close' is used to signal that no further values will
// be written to the channel. Not strictly necessary, but
// also won't hurt.
close(channel)
}()
return channel
}
// WorkFunc is used to perform any time consuming work for an api call, after
// the input has been validated. Pass one of these to MakeAsync to create an
// appropriate return value for the Update, Delete, and Create methods.
type WorkResultFunc func() (result RESTResult, err error)
// MakeAsync takes a function and executes it, delivering the result in the way required
// by RESTStorage's Update, Delete, and Create methods.
func MakeAsyncResult(fn WorkResultFunc) <-chan RESTResult {
channel := make(chan RESTResult)
go func() {
defer util.HandleCrash()
obj, err := fn()
if err != nil {
channel <- RESTResult{Object: errToAPIStatus(err)}
} else { } else {
channel <- obj channel <- obj
} }

View File

@ -65,6 +65,7 @@ func RecoverPanics(handler http.Handler) http.Handler {
defer httplog.NewLogged(req, &w).StacktraceWhen( defer httplog.NewLogged(req, &w).StacktraceWhen(
httplog.StatusIsNot( httplog.StatusIsNot(
http.StatusOK, http.StatusOK,
http.StatusCreated,
http.StatusAccepted, http.StatusAccepted,
http.StatusMovedPermanently, http.StatusMovedPermanently,
http.StatusTemporaryRedirect, http.StatusTemporaryRedirect,

View File

@ -41,10 +41,27 @@ type RESTStorage interface {
// Delete finds a resource in the storage and deletes it. // Delete finds a resource in the storage and deletes it.
// Although it can return an arbitrary error value, IsNotFound(err) is true for the // Although it can return an arbitrary error value, IsNotFound(err) is true for the
// returned error value err when the specified resource is not found. // returned error value err when the specified resource is not found.
Delete(ctx api.Context, id string) (<-chan runtime.Object, error) Delete(ctx api.Context, id string) (<-chan RESTResult, error)
Create(ctx api.Context, obj runtime.Object) (<-chan runtime.Object, error) // Create creates a new version of a resource.
Update(ctx api.Context, obj runtime.Object) (<-chan runtime.Object, error) Create(ctx api.Context, obj runtime.Object) (<-chan RESTResult, error)
// Update finds a resource in the storage and updates it. Some implementations
// may allow updates creates the object - they should set the Created flag of
// the returned RESTResultto true. In the event of an asynchronous error returned
// via an api.Status object, the Created flag is ignored.
Update(ctx api.Context, obj runtime.Object) (<-chan RESTResult, error)
}
// RESTResult indicates the result of a REST transformation.
type RESTResult struct {
// The result of this operation. May be nil if the operation has no meaningful
// result (like Delete)
runtime.Object
// May be set true to indicate that the Update operation resulted in the object
// being created.
Created bool
} }
// ResourceWatcher should be implemented by all RESTStorage objects that // ResourceWatcher should be implemented by all RESTStorage objects that

View File

@ -53,7 +53,8 @@ func (h *OperationHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
obj, complete := op.StatusOrResult() result, complete := op.StatusOrResult()
obj := result.Object
if complete { if complete {
writeJSON(http.StatusOK, h.codec, obj, w) writeJSON(http.StatusOK, h.codec, obj, w)
} else { } else {
@ -64,9 +65,9 @@ func (h *OperationHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// Operation represents an ongoing action which the server is performing. // Operation represents an ongoing action which the server is performing.
type Operation struct { type Operation struct {
ID string ID string
result runtime.Object result RESTResult
onReceive func(runtime.Object) onReceive func(RESTResult)
awaiting <-chan runtime.Object awaiting <-chan RESTResult
finished *time.Time finished *time.Time
lock sync.Mutex lock sync.Mutex
notify chan struct{} notify chan struct{}
@ -93,7 +94,7 @@ func NewOperations() *Operations {
// NewOperation adds a new operation. It is lock-free. 'onReceive' will be called // NewOperation adds a new operation. It is lock-free. 'onReceive' will be called
// with the value read from 'from', when it is read. // with the value read from 'from', when it is read.
func (ops *Operations) NewOperation(from <-chan runtime.Object, onReceive func(runtime.Object)) *Operation { func (ops *Operations) NewOperation(from <-chan RESTResult, onReceive func(RESTResult)) *Operation {
id := atomic.AddInt64(&ops.lastID, 1) id := atomic.AddInt64(&ops.lastID, 1)
op := &Operation{ op := &Operation{
ID: strconv.FormatInt(id, 10), ID: strconv.FormatInt(id, 10),
@ -192,16 +193,16 @@ func (op *Operation) expired(limitTime time.Time) bool {
// StatusOrResult returns status information or the result of the operation if it is complete, // StatusOrResult returns status information or the result of the operation if it is complete,
// with a bool indicating true in the latter case. // with a bool indicating true in the latter case.
func (op *Operation) StatusOrResult() (description runtime.Object, finished bool) { func (op *Operation) StatusOrResult() (description RESTResult, finished bool) {
op.lock.Lock() op.lock.Lock()
defer op.lock.Unlock() defer op.lock.Unlock()
if op.finished == nil { if op.finished == nil {
return &api.Status{ return RESTResult{Object: &api.Status{
Status: api.StatusWorking, Status: api.StatusWorking,
Reason: api.StatusReasonWorking, Reason: api.StatusReasonWorking,
Details: &api.StatusDetails{ID: op.ID, Kind: "operation"}, Details: &api.StatusDetails{ID: op.ID, Kind: "operation"},
}, false }}, false
} }
return op.result, true return op.result, true
} }

View File

@ -34,16 +34,16 @@ import (
func TestOperation(t *testing.T) { func TestOperation(t *testing.T) {
ops := NewOperations() ops := NewOperations()
c := make(chan runtime.Object) c := make(chan RESTResult)
called := make(chan struct{}) called := make(chan struct{})
op := ops.NewOperation(c, func(runtime.Object) { go close(called) }) op := ops.NewOperation(c, func(RESTResult) { go close(called) })
// Allow context switch, so that op's ID can get added to the map and Get will work. // Allow context switch, so that op's ID can get added to the map and Get will work.
// This is just so we can test Get. Ordinary users have no need to call Get immediately // This is just so we can test Get. Ordinary users have no need to call Get immediately
// after calling NewOperation, because it returns the operation directly. // after calling NewOperation, because it returns the operation directly.
time.Sleep(time.Millisecond) time.Sleep(time.Millisecond)
go func() { go func() {
time.Sleep(500 * time.Millisecond) time.Sleep(500 * time.Millisecond)
c <- &Simple{ObjectMeta: api.ObjectMeta{Name: "All done"}} c <- RESTResult{Object: &Simple{ObjectMeta: api.ObjectMeta{Name: "All done"}}}
}() }()
if op.expired(time.Now().Add(-time.Minute)) { if op.expired(time.Now().Add(-time.Minute)) {
@ -96,7 +96,7 @@ func TestOperation(t *testing.T) {
t.Errorf("expire failed to remove the operation %#v", ops) t.Errorf("expire failed to remove the operation %#v", ops)
} }
if op.result.(*Simple).Name != "All done" { if op.result.Object.(*Simple).Name != "All done" {
t.Errorf("Got unexpected result: %#v", op.result) t.Errorf("Got unexpected result: %#v", op.result)
} }
} }

View File

@ -29,6 +29,7 @@ import (
"github.com/golang/glog" "github.com/golang/glog"
) )
// RESTHandler implements HTTP verbs on a set of RESTful resources identified by name.
type RESTHandler struct { type RESTHandler struct {
storage map[string]RESTStorage storage map[string]RESTStorage
codec runtime.Codec codec runtime.Codec
@ -78,9 +79,9 @@ func (h *RESTHandler) setSelfLinkAddName(obj runtime.Object, req *http.Request)
} }
// curry adapts either of the self link setting functions into a function appropriate for operation's hook. // curry adapts either of the self link setting functions into a function appropriate for operation's hook.
func curry(f func(runtime.Object, *http.Request) error, req *http.Request) func(runtime.Object) { func curry(f func(runtime.Object, *http.Request) error, req *http.Request) func(RESTResult) {
return func(obj runtime.Object) { return func(obj RESTResult) {
if err := f(obj, req); err != nil { if err := f(obj.Object, req); err != nil {
glog.Errorf("unable to set self link for %#v: %v", obj, err) glog.Errorf("unable to set self link for %#v: %v", obj, err)
} }
} }
@ -217,7 +218,7 @@ func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w htt
} }
// createOperation creates an operation to process a channel response. // createOperation creates an operation to process a channel response.
func (h *RESTHandler) createOperation(out <-chan runtime.Object, sync bool, timeout time.Duration, onReceive func(runtime.Object)) *Operation { func (h *RESTHandler) createOperation(out <-chan RESTResult, sync bool, timeout time.Duration, onReceive func(RESTResult)) *Operation {
op := h.ops.NewOperation(out, onReceive) op := h.ops.NewOperation(out, onReceive)
if sync { if sync {
op.WaitFor(timeout) op.WaitFor(timeout)
@ -230,9 +231,13 @@ func (h *RESTHandler) createOperation(out <-chan runtime.Object, sync bool, time
// finishReq finishes up a request, waiting until the operation finishes or, after a timeout, creating an // finishReq finishes up a request, waiting until the operation finishes or, after a timeout, creating an
// Operation to receive the result and returning its ID down the writer. // Operation to receive the result and returning its ID down the writer.
func (h *RESTHandler) finishReq(op *Operation, req *http.Request, w http.ResponseWriter) { func (h *RESTHandler) finishReq(op *Operation, req *http.Request, w http.ResponseWriter) {
obj, complete := op.StatusOrResult() result, complete := op.StatusOrResult()
obj := result.Object
if complete { if complete {
status := http.StatusOK status := http.StatusOK
if result.Created {
status = http.StatusCreated
}
switch stat := obj.(type) { switch stat := obj.(type) {
case *api.Status: case *api.Status:
if stat.Code != 0 { if stat.Code != 0 {

View File

@ -0,0 +1,69 @@
/*
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 apiserver
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
)
func TestFinishReq(t *testing.T) {
handler := &RESTHandler{codec: api.Codec}
op := &Operation{finished: &time.Time{}, result: RESTResult{Object: &api.Status{Code: http.StatusNotFound}}}
resp := httptest.NewRecorder()
handler.finishReq(op, nil, resp)
status := &api.Status{}
if err := json.Unmarshal([]byte(resp.Body.String()), status); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp.Code != http.StatusNotFound || status.Code != http.StatusNotFound {
t.Errorf("unexpected status: %#v", status)
}
}
func TestFinishReqUnwrap(t *testing.T) {
handler := &RESTHandler{codec: api.Codec}
op := &Operation{finished: &time.Time{}, result: RESTResult{Created: true, Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}}}
resp := httptest.NewRecorder()
handler.finishReq(op, nil, resp)
obj := &api.Pod{}
if err := json.Unmarshal([]byte(resp.Body.String()), obj); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp.Code != http.StatusCreated || obj.Name != "foo" {
t.Errorf("unexpected object: %#v", obj)
}
}
func TestFinishReqUnwrapStatus(t *testing.T) {
handler := &RESTHandler{codec: api.Codec}
op := &Operation{finished: &time.Time{}, result: RESTResult{Created: true, Object: &api.Status{Code: http.StatusNotFound}}}
resp := httptest.NewRecorder()
handler.finishReq(op, nil, resp)
obj := &api.Status{}
if err := json.Unmarshal([]byte(resp.Body.String()), obj); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp.Code != http.StatusNotFound || obj.Code != http.StatusNotFound {
t.Errorf("unexpected object: %#v", obj)
}
}

View File

@ -57,6 +57,7 @@ type testClient struct {
Request testRequest Request testRequest
Response Response Response Response
Error bool Error bool
Created bool
server *httptest.Server server *httptest.Server
handler *util.FakeHandler handler *util.FakeHandler
// For query args, an optional function to validate the contents // For query args, an optional function to validate the contents

View File

@ -272,7 +272,7 @@ func (r *Request) Do() Result {
if err != nil { if err != nil {
return Result{err: err} return Result{err: err}
} }
respBody, err := r.c.doRequest(req) respBody, created, err := r.c.doRequest(req)
if err != nil { if err != nil {
if s, ok := err.(APIStatus); ok { if s, ok := err.(APIStatus); ok {
status := s.Status() status := s.Status()
@ -292,14 +292,16 @@ func (r *Request) Do() Result {
} }
} }
} }
return Result{respBody, err, r.c.Codec} return Result{respBody, created, err, r.c.Codec}
} }
} }
// Result contains the result of calling Request.Do(). // Result contains the result of calling Request.Do().
type Result struct { type Result struct {
body []byte body []byte
created bool
err error err error
codec runtime.Codec codec runtime.Codec
} }
@ -324,6 +326,13 @@ func (r Result) Into(obj runtime.Object) error {
return r.codec.DecodeInto(r.body, obj) return r.codec.DecodeInto(r.body, obj)
} }
// WasCreated updates the provided bool pointer to whether the server returned
// 201 created or a different response.
func (r Result) WasCreated(wasCreated *bool) Result {
*wasCreated = r.created
return r
}
// Error returns the error executing the request, nil if no error occurred. // Error returns the error executing the request, nil if no error occurred.
func (r Result) Error() error { func (r Result) Error() error {
return r.err return r.err

View File

@ -168,13 +168,14 @@ func TestDoRequestNewWayFile(t *testing.T) {
} }
testServer := httptest.NewServer(&fakeHandler) testServer := httptest.NewServer(&fakeHandler)
c := NewOrDie(&Config{Host: testServer.URL, Version: "v1beta1", Username: "user", Password: "pass"}) c := NewOrDie(&Config{Host: testServer.URL, Version: "v1beta1", Username: "user", Password: "pass"})
wasCreated := true
obj, err := c.Verb("POST"). obj, err := c.Verb("POST").
Path("foo/bar"). Path("foo/bar").
Path("baz"). Path("baz").
ParseSelectorParam("labels", "name=foo"). ParseSelectorParam("labels", "name=foo").
Timeout(time.Second). Timeout(time.Second).
Body(file.Name()). Body(file.Name()).
Do().Get() Do().WasCreated(&wasCreated).Get()
if err != nil { if err != nil {
t.Errorf("Unexpected error: %v %#v", err, err) t.Errorf("Unexpected error: %v %#v", err, err)
return return
@ -184,6 +185,9 @@ func TestDoRequestNewWayFile(t *testing.T) {
} else if !reflect.DeepEqual(obj, expectedObj) { } else if !reflect.DeepEqual(obj, expectedObj) {
t.Errorf("Expected: %#v, got %#v", expectedObj, obj) t.Errorf("Expected: %#v, got %#v", expectedObj, obj)
} }
if wasCreated {
t.Errorf("expected object was not created")
}
tmpStr := string(reqBodyExpected) tmpStr := string(reqBodyExpected)
fakeHandler.ValidateRequest(t, "/api/v1beta1/foo/bar/baz?labels=name%3Dfoo", "POST", &tmpStr) fakeHandler.ValidateRequest(t, "/api/v1beta1/foo/bar/baz?labels=name%3Dfoo", "POST", &tmpStr)
if fakeHandler.RequestReceived.Header["Authorization"] == nil { if fakeHandler.RequestReceived.Header["Authorization"] == nil {
@ -191,6 +195,50 @@ func TestDoRequestNewWayFile(t *testing.T) {
} }
} }
func TestWasCreated(t *testing.T) {
reqObj := &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}
reqBodyExpected, err := v1beta1.Codec.Encode(reqObj)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
expectedObj := &api.Service{Port: 12345}
expectedBody, _ := v1beta1.Codec.Encode(expectedObj)
fakeHandler := util.FakeHandler{
StatusCode: 201,
ResponseBody: string(expectedBody),
T: t,
}
testServer := httptest.NewServer(&fakeHandler)
c := NewOrDie(&Config{Host: testServer.URL, Version: "v1beta1", Username: "user", Password: "pass"})
wasCreated := false
obj, err := c.Verb("PUT").
Path("foo/bar").
Path("baz").
ParseSelectorParam("labels", "name=foo").
Timeout(time.Second).
Body(reqBodyExpected).
Do().WasCreated(&wasCreated).Get()
if err != nil {
t.Errorf("Unexpected error: %v %#v", err, err)
return
}
if obj == nil {
t.Error("nil obj")
} else if !reflect.DeepEqual(obj, expectedObj) {
t.Errorf("Expected: %#v, got %#v", expectedObj, obj)
}
if !wasCreated {
t.Errorf("Expected object was created")
}
tmpStr := string(reqBodyExpected)
fakeHandler.ValidateRequest(t, "/api/v1beta1/foo/bar/baz?labels=name%3Dfoo", "PUT", &tmpStr)
if fakeHandler.RequestReceived.Header["Authorization"] == nil {
t.Errorf("Request is missing authorization header: %#v", *fakeHandler.RequestReceived)
}
}
func TestVerbs(t *testing.T) { func TestVerbs(t *testing.T) {
c := NewOrDie(&Config{}) c := NewOrDie(&Config{})
if r := c.Post(); r.verb != "POST" { if r := c.Post(); r.verb != "POST" {

View File

@ -76,7 +76,7 @@ func NewRESTClient(baseURL *url.URL, c runtime.Codec) *RESTClient {
} }
// doRequest executes a request against a server // doRequest executes a request against a server
func (c *RESTClient) doRequest(request *http.Request) ([]byte, error) { func (c *RESTClient) doRequest(request *http.Request) ([]byte, bool, error) {
client := c.Client client := c.Client
if client == nil { if client == nil {
client = http.DefaultClient client = http.DefaultClient
@ -84,12 +84,12 @@ func (c *RESTClient) doRequest(request *http.Request) ([]byte, error) {
response, err := client.Do(request) response, err := client.Do(request)
if err != nil { if err != nil {
return nil, err return nil, false, err
} }
defer response.Body.Close() defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body) body, err := ioutil.ReadAll(response.Body)
if err != nil { if err != nil {
return body, err return body, false, err
} }
// Did the server give us a status response? // Did the server give us a status response?
@ -102,19 +102,20 @@ func (c *RESTClient) doRequest(request *http.Request) ([]byte, error) {
switch { switch {
case response.StatusCode < http.StatusOK || response.StatusCode > http.StatusPartialContent: case response.StatusCode < http.StatusOK || response.StatusCode > http.StatusPartialContent:
if !isStatusResponse { if !isStatusResponse {
return nil, fmt.Errorf("request [%#v] failed (%d) %s: %s", request, response.StatusCode, response.Status, string(body)) return nil, false, fmt.Errorf("request [%#v] failed (%d) %s: %s", request, response.StatusCode, response.Status, string(body))
} }
return nil, errors.FromObject(&status) return nil, false, errors.FromObject(&status)
} }
// If the server gave us a status back, look at what it was. // If the server gave us a status back, look at what it was.
if isStatusResponse && status.Status != api.StatusSuccess { if isStatusResponse && status.Status != api.StatusSuccess {
// "Working" requests need to be handled specially. // "Working" requests need to be handled specially.
// "Failed" requests are clearly just an error and it makes sense to return them as such. // "Failed" requests are clearly just an error and it makes sense to return them as such.
return nil, errors.FromObject(&status) return nil, false, errors.FromObject(&status)
} }
return body, err created := response.StatusCode == http.StatusCreated
return body, created, err
} }
// Verb begins a request with a verb (GET, POST, PUT, DELETE). // Verb begins a request with a verb (GET, POST, PUT, DELETE).

View File

@ -105,6 +105,7 @@ func TestDoRequest(t *testing.T) {
uri, _ := url.Parse("http://localhost") uri, _ := url.Parse("http://localhost")
testClients := []testClient{ testClients := []testClient{
{Request: testRequest{Method: "GET", Path: "good"}, Response: Response{StatusCode: 200}}, {Request: testRequest{Method: "GET", Path: "good"}, Response: Response{StatusCode: 200}},
{Request: testRequest{Method: "GET", Path: "good"}, Response: Response{StatusCode: 201}, Created: true},
{Request: testRequest{Method: "GET", Path: "bad%ZZ"}, Error: true}, {Request: testRequest{Method: "GET", Path: "bad%ZZ"}, Error: true},
{Request: testRequest{Method: "GET", Path: "error"}, Response: Response{StatusCode: 500}, Error: true}, {Request: testRequest{Method: "GET", Path: "error"}, Response: Response{StatusCode: 500}, Error: true},
{Request: testRequest{Method: "POST", Path: "faildecode"}, Response: Response{StatusCode: 200, RawBody: &invalid}}, {Request: testRequest{Method: "POST", Path: "faildecode"}, Response: Response{StatusCode: 200, RawBody: &invalid}},
@ -120,7 +121,10 @@ func TestDoRequest(t *testing.T) {
Header: make(http.Header), Header: make(http.Header),
URL: &prefix, URL: &prefix,
} }
response, err := client.doRequest(request) response, created, err := client.doRequest(request)
if c.Created != created {
t.Errorf("expected created %f, got %f", c.Created, created)
}
//t.Logf("dorequest: %#v\n%#v\n%v", request.URL, response, err) //t.Logf("dorequest: %#v\n%#v\n%v", request.URL, response, err)
c.ValidateRaw(t, response, err) c.ValidateRaw(t, response, err)
} }
@ -160,18 +164,16 @@ func TestDoRequestAccepted(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
body, err := c.doRequest(request) body, _, err := c.doRequest(request)
if fakeHandler.RequestReceived.Header["Authorization"] == nil { if fakeHandler.RequestReceived.Header["Authorization"] == nil {
t.Errorf("Request is missing authorization header: %#v", *request) t.Errorf("Request is missing authorization header: %#v", *request)
} }
if err == nil { if err == nil {
t.Error("Unexpected non-error") t.Fatalf("Unexpected non-error")
return
} }
se, ok := err.(APIStatus) se, ok := err.(APIStatus)
if !ok { if !ok {
t.Errorf("Unexpected kind of error: %#v", err) t.Fatalf("Unexpected kind of error: %#v", err)
return
} }
if !reflect.DeepEqual(se.Status(), *status) { if !reflect.DeepEqual(se.Status(), *status) {
t.Errorf("Unexpected status: %#v %#v", se.Status(), status) t.Errorf("Unexpected status: %#v %#v", se.Status(), status)
@ -196,7 +198,7 @@ func TestDoRequestAcceptedSuccess(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
body, err := c.doRequest(request) body, _, err := c.doRequest(request)
if fakeHandler.RequestReceived.Header["Authorization"] == nil { if fakeHandler.RequestReceived.Header["Authorization"] == nil {
t.Errorf("Request is missing authorization header: %#v", *request) t.Errorf("Request is missing authorization header: %#v", *request)
} }
@ -227,7 +229,7 @@ func TestDoRequestFailed(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
body, err := c.doRequest(request) body, _, err := c.doRequest(request)
if err == nil || body != nil { if err == nil || body != nil {
t.Errorf("unexpected non-error: %#v", body) t.Errorf("unexpected non-error: %#v", body)
} }
@ -240,3 +242,34 @@ func TestDoRequestFailed(t *testing.T) {
t.Errorf("Unexpected mis-match. Expected %#v. Saw %#v", status, actual) t.Errorf("Unexpected mis-match. Expected %#v. Saw %#v", status, actual)
} }
} }
func TestDoRequestCreated(t *testing.T) {
status := &api.Status{Status: api.StatusSuccess}
expectedBody, _ := latest.Codec.Encode(status)
fakeHandler := util.FakeHandler{
StatusCode: 201,
ResponseBody: string(expectedBody),
T: t,
}
testServer := httptest.NewServer(&fakeHandler)
request, _ := http.NewRequest("GET", testServer.URL+"/foo/bar", nil)
c, err := RESTClientFor(&Config{Host: testServer.URL, Username: "user", Password: "pass"})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
body, created, err := c.doRequest(request)
if err != nil {
t.Errorf("Unexpected error %#v", err)
}
if !created {
t.Errorf("Expected object to be created")
}
statusOut, err := latest.Codec.Decode(body)
if err != nil {
t.Errorf("Unexpected error %#v", err)
}
if !reflect.DeepEqual(status, statusOut) {
t.Errorf("Unexpected mis-match. Expected %#v. Saw %#v", status, statusOut)
}
fakeHandler.ValidateRequest(t, "/foo/bar", "GET", nil)
}

View File

@ -51,7 +51,7 @@ func (*REST) Get(ctx api.Context, id string) (runtime.Object, error) {
} }
// Delete returns an error because bindings are write-only objects. // Delete returns an error because bindings are write-only objects.
func (*REST) Delete(ctx api.Context, id string) (<-chan runtime.Object, error) { func (*REST) Delete(ctx api.Context, id string) (<-chan apiserver.RESTResult, error) {
return nil, errors.NewNotFound("binding", id) return nil, errors.NewNotFound("binding", id)
} }
@ -61,7 +61,7 @@ func (*REST) New() runtime.Object {
} }
// Create attempts to make the assignment indicated by the binding it recieves. // Create attempts to make the assignment indicated by the binding it recieves.
func (b *REST) Create(ctx api.Context, obj runtime.Object) (<-chan runtime.Object, error) { func (b *REST) Create(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
binding, ok := obj.(*api.Binding) binding, ok := obj.(*api.Binding)
if !ok { if !ok {
return nil, fmt.Errorf("incorrect type: %#v", obj) return nil, fmt.Errorf("incorrect type: %#v", obj)
@ -75,6 +75,6 @@ func (b *REST) Create(ctx api.Context, obj runtime.Object) (<-chan runtime.Objec
} }
// Update returns an error-- this object may not be updated. // Update returns an error-- this object may not be updated.
func (b *REST) Update(ctx api.Context, obj runtime.Object) (<-chan runtime.Object, error) { func (b *REST) Update(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
return nil, fmt.Errorf("Bindings may not be changed.") return nil, fmt.Errorf("Bindings may not be changed.")
} }

View File

@ -111,7 +111,7 @@ func TestRESTPost(t *testing.T) {
Message: item.err.Error(), Message: item.err.Error(),
} }
} }
if e, a := expect, <-resultChan; !reflect.DeepEqual(e, a) { if e, a := expect, (<-resultChan).Object; !reflect.DeepEqual(e, a) {
t.Errorf("%v: expected %#v, got %#v", i, e, a) t.Errorf("%v: expected %#v, got %#v", i, e, a)
} }
} }

View File

@ -54,7 +54,7 @@ func NewREST(registry Registry, podLister PodLister) *REST {
} }
// Create registers the given ReplicationController. // Create registers the given ReplicationController.
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan runtime.Object, error) { func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
controller, ok := obj.(*api.ReplicationController) controller, ok := obj.(*api.ReplicationController)
if !ok { if !ok {
return nil, fmt.Errorf("not a replication controller: %#v", obj) return nil, fmt.Errorf("not a replication controller: %#v", obj)
@ -84,7 +84,7 @@ func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan runtime.Obje
} }
// Delete asynchronously deletes the ReplicationController specified by its id. // Delete asynchronously deletes the ReplicationController specified by its id.
func (rs *REST) Delete(ctx api.Context, id string) (<-chan runtime.Object, error) { func (rs *REST) Delete(ctx api.Context, id string) (<-chan apiserver.RESTResult, error) {
return apiserver.MakeAsync(func() (runtime.Object, error) { return apiserver.MakeAsync(func() (runtime.Object, error) {
return &api.Status{Status: api.StatusSuccess}, rs.registry.DeleteController(ctx, id) return &api.Status{Status: api.StatusSuccess}, rs.registry.DeleteController(ctx, id)
}), nil }), nil
@ -127,7 +127,7 @@ func (*REST) New() runtime.Object {
// Update replaces a given ReplicationController instance with an existing // Update replaces a given ReplicationController instance with an existing
// instance in storage.registry. // instance in storage.registry.
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan runtime.Object, error) { func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
controller, ok := obj.(*api.ReplicationController) controller, ok := obj.(*api.ReplicationController)
if !ok { if !ok {
return nil, fmt.Errorf("not a replication controller: %#v", obj) return nil, fmt.Errorf("not a replication controller: %#v", obj)

View File

@ -60,7 +60,7 @@ func (rs *REST) Watch(ctx api.Context, label, field labels.Selector, resourceVer
} }
// Create satisfies the RESTStorage interface. // Create satisfies the RESTStorage interface.
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan runtime.Object, error) { func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
endpoints, ok := obj.(*api.Endpoints) endpoints, ok := obj.(*api.Endpoints)
if !ok { if !ok {
return nil, fmt.Errorf("not an endpoints: %#v", obj) return nil, fmt.Errorf("not an endpoints: %#v", obj)
@ -79,7 +79,7 @@ func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan runtime.Obje
} }
// Update satisfies the RESTStorage interface. // Update satisfies the RESTStorage interface.
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan runtime.Object, error) { func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
endpoints, ok := obj.(*api.Endpoints) endpoints, ok := obj.(*api.Endpoints)
if !ok { if !ok {
return nil, fmt.Errorf("not an endpoints: %#v", obj) return nil, fmt.Errorf("not an endpoints: %#v", obj)
@ -94,7 +94,7 @@ func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan runtime.Obje
} }
// Delete satisfies the RESTStorage interface but is unimplemented. // Delete satisfies the RESTStorage interface but is unimplemented.
func (rs *REST) Delete(ctx api.Context, id string) (<-chan runtime.Object, error) { func (rs *REST) Delete(ctx api.Context, id string) (<-chan apiserver.RESTResult, error) {
return nil, errors.New("unimplemented") return nil, errors.New("unimplemented")
} }

View File

@ -41,7 +41,7 @@ func NewREST(registry generic.Registry) *REST {
} }
} }
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan runtime.Object, error) { func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
event, ok := obj.(*api.Event) event, ok := obj.(*api.Event)
if !ok { if !ok {
return nil, fmt.Errorf("invalid object type") return nil, fmt.Errorf("invalid object type")
@ -57,7 +57,7 @@ func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan runtime.Obje
}), nil }), nil
} }
func (rs *REST) Delete(ctx api.Context, id string) (<-chan runtime.Object, error) { func (rs *REST) Delete(ctx api.Context, id string) (<-chan apiserver.RESTResult, error) {
obj, err := rs.registry.Get(ctx, id) obj, err := rs.registry.Get(ctx, id)
if err != nil { if err != nil {
return nil, err return nil, err
@ -116,6 +116,6 @@ func (*REST) New() runtime.Object {
} }
// Update returns an error: Events are not mutable. // Update returns an error: Events are not mutable.
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan runtime.Object, error) { func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
return nil, fmt.Errorf("not allowed: 'Event' objects are not mutable") return nil, fmt.Errorf("not allowed: 'Event' objects are not mutable")
} }

View File

@ -47,7 +47,7 @@ func TestRESTCreate(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Unexpected error %v", err) t.Fatalf("Unexpected error %v", err)
} }
if e, a := eventA, <-c; !reflect.DeepEqual(e, a) { if e, a := eventA, (<-c).Object; !reflect.DeepEqual(e, a) {
t.Errorf("diff: %s", util.ObjectDiff(e, a)) t.Errorf("diff: %s", util.ObjectDiff(e, a))
} }
} }
@ -67,7 +67,7 @@ func TestRESTDelete(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Unexpected error %v", err) t.Fatalf("Unexpected error %v", err)
} }
if stat := (<-c).(*api.Status); stat.Status != api.StatusSuccess { if stat := (<-c).Object.(*api.Status); stat.Status != api.StatusSuccess {
t.Errorf("unexpected status: %v", stat) t.Errorf("unexpected status: %v", stat)
} }
} }

View File

@ -45,7 +45,7 @@ func NewREST(m Registry) *REST {
var ErrDoesNotExist = errors.New("The requested resource does not exist.") var ErrDoesNotExist = errors.New("The requested resource does not exist.")
var ErrNotHealty = errors.New("The requested minion is not healthy.") var ErrNotHealty = errors.New("The requested minion is not healthy.")
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan runtime.Object, error) { func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
minion, ok := obj.(*api.Minion) minion, ok := obj.(*api.Minion)
if !ok { if !ok {
return nil, fmt.Errorf("not a minion: %#v", obj) return nil, fmt.Errorf("not a minion: %#v", obj)
@ -72,7 +72,7 @@ func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan runtime.Obje
}), nil }), nil
} }
func (rs *REST) Delete(ctx api.Context, id string) (<-chan runtime.Object, error) { func (rs *REST) Delete(ctx api.Context, id string) (<-chan apiserver.RESTResult, error) {
minion, err := rs.registry.GetMinion(ctx, id) minion, err := rs.registry.GetMinion(ctx, id)
if minion == nil { if minion == nil {
return nil, ErrDoesNotExist return nil, ErrDoesNotExist
@ -101,7 +101,7 @@ func (rs *REST) New() runtime.Object {
return &api.Minion{} return &api.Minion{}
} }
func (rs *REST) Update(ctx api.Context, minion runtime.Object) (<-chan runtime.Object, error) { func (rs *REST) Update(ctx api.Context, minion runtime.Object) (<-chan apiserver.RESTResult, error) {
return nil, fmt.Errorf("Minions can only be created (inserted) and deleted.") return nil, fmt.Errorf("Minions can only be created (inserted) and deleted.")
} }

View File

@ -42,7 +42,7 @@ func TestMinionREST(t *testing.T) {
t.Errorf("insert failed") t.Errorf("insert failed")
} }
obj := <-c obj := <-c
if m, ok := obj.(*api.Minion); !ok || m.Name != "baz" { if m, ok := obj.Object.(*api.Minion); !ok || m.Name != "baz" {
t.Errorf("insert return value was weird: %#v", obj) t.Errorf("insert return value was weird: %#v", obj)
} }
if obj, err := ms.Get(ctx, "baz"); err != nil || obj.(*api.Minion).Name != "baz" { if obj, err := ms.Get(ctx, "baz"); err != nil || obj.(*api.Minion).Name != "baz" {
@ -54,7 +54,7 @@ func TestMinionREST(t *testing.T) {
t.Errorf("delete failed") t.Errorf("delete failed")
} }
obj = <-c obj = <-c
if s, ok := obj.(*api.Status); !ok || s.Status != api.StatusSuccess { if s, ok := obj.Object.(*api.Status); !ok || s.Status != api.StatusSuccess {
t.Errorf("delete return value was weird: %#v", obj) t.Errorf("delete return value was weird: %#v", obj)
} }
if _, err := ms.Get(ctx, "bar"); err != ErrDoesNotExist { if _, err := ms.Get(ctx, "bar"); err != ErrDoesNotExist {

View File

@ -88,7 +88,7 @@ func NewREST(config *RESTConfig) *REST {
} }
} }
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan runtime.Object, error) { func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
pod := obj.(*api.Pod) pod := obj.(*api.Pod)
if !api.ValidNamespace(ctx, &pod.ObjectMeta) { if !api.ValidNamespace(ctx, &pod.ObjectMeta) {
return nil, errors.NewConflict("pod", pod.Namespace, fmt.Errorf("Pod.Namespace does not match the provided context")) return nil, errors.NewConflict("pod", pod.Namespace, fmt.Errorf("Pod.Namespace does not match the provided context"))
@ -111,7 +111,7 @@ func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan runtime.Obje
}), nil }), nil
} }
func (rs *REST) Delete(ctx api.Context, id string) (<-chan runtime.Object, error) { func (rs *REST) Delete(ctx api.Context, id string) (<-chan apiserver.RESTResult, error) {
return apiserver.MakeAsync(func() (runtime.Object, error) { return apiserver.MakeAsync(func() (runtime.Object, error) {
return &api.Status{Status: api.StatusSuccess}, rs.registry.DeletePod(ctx, id) return &api.Status{Status: api.StatusSuccess}, rs.registry.DeletePod(ctx, id)
}), nil }), nil
@ -184,7 +184,7 @@ func (*REST) New() runtime.Object {
return &api.Pod{} return &api.Pod{}
} }
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan runtime.Object, error) { func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
pod := obj.(*api.Pod) pod := obj.(*api.Pod)
if !api.ValidNamespace(ctx, &pod.ObjectMeta) { if !api.ValidNamespace(ctx, &pod.ObjectMeta) {
return nil, errors.NewConflict("pod", pod.Namespace, fmt.Errorf("Pod.Namespace does not match the provided context")) return nil, errors.NewConflict("pod", pod.Namespace, fmt.Errorf("Pod.Namespace does not match the provided context"))

View File

@ -26,17 +26,17 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/fake" "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/fake"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
) )
func expectApiStatusError(t *testing.T, ch <-chan runtime.Object, msg string) { func expectApiStatusError(t *testing.T, ch <-chan apiserver.RESTResult, msg string) {
out := <-ch out := <-ch
status, ok := out.(*api.Status) status, ok := out.Object.(*api.Status)
if !ok { if !ok {
t.Errorf("Expected an api.Status object, was %#v", out) t.Errorf("Expected an api.Status object, was %#v", out)
return return
@ -46,9 +46,9 @@ func expectApiStatusError(t *testing.T, ch <-chan runtime.Object, msg string) {
} }
} }
func expectPod(t *testing.T, ch <-chan runtime.Object) (*api.Pod, bool) { func expectPod(t *testing.T, ch <-chan apiserver.RESTResult) (*api.Pod, bool) {
out := <-ch out := <-ch
pod, ok := out.(*api.Pod) pod, ok := out.Object.(*api.Pod)
if !ok || pod == nil { if !ok || pod == nil {
t.Errorf("Expected an api.Pod object, was %#v", out) t.Errorf("Expected an api.Pod object, was %#v", out)
return nil, false return nil, false

View File

@ -79,7 +79,7 @@ func reloadIPsFromStorage(ipa *ipAllocator, registry Registry) {
} }
} }
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan runtime.Object, error) { func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
service := obj.(*api.Service) service := obj.(*api.Service)
if !api.ValidNamespace(ctx, &service.ObjectMeta) { if !api.ValidNamespace(ctx, &service.ObjectMeta) {
return nil, errors.NewConflict("service", service.Namespace, fmt.Errorf("Service.Namespace does not match the provided context")) return nil, errors.NewConflict("service", service.Namespace, fmt.Errorf("Service.Namespace does not match the provided context"))
@ -144,7 +144,7 @@ func hostsFromMinionList(list *api.MinionList) []string {
return result return result
} }
func (rs *REST) Delete(ctx api.Context, id string) (<-chan runtime.Object, error) { func (rs *REST) Delete(ctx api.Context, id string) (<-chan apiserver.RESTResult, error) {
service, err := rs.registry.GetService(ctx, id) service, err := rs.registry.GetService(ctx, id)
if err != nil { if err != nil {
return nil, err return nil, err
@ -211,7 +211,7 @@ func GetServiceEnvironmentVariables(ctx api.Context, registry Registry, machine
return result, nil return result, nil
} }
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan runtime.Object, error) { func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
service := obj.(*api.Service) service := obj.(*api.Service)
if !api.ValidNamespace(ctx, &service.ObjectMeta) { if !api.ValidNamespace(ctx, &service.ObjectMeta) {
return nil, errors.NewConflict("service", service.Namespace, fmt.Errorf("Service.Namespace does not match the provided context")) return nil, errors.NewConflict("service", service.Namespace, fmt.Errorf("Service.Namespace does not match the provided context"))

View File

@ -52,7 +52,7 @@ func TestServiceRegistryCreate(t *testing.T) {
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
c, _ := storage.Create(ctx, svc) c, _ := storage.Create(ctx, svc)
created_svc := <-c created_svc := <-c
created_service := created_svc.(*api.Service) created_service := created_svc.Object.(*api.Service)
if created_service.Name != "foo" { if created_service.Name != "foo" {
t.Errorf("Expected foo, but got %v", created_service.Name) t.Errorf("Expected foo, but got %v", created_service.Name)
} }
@ -125,7 +125,7 @@ func TestServiceRegistryUpdate(t *testing.T) {
t.Errorf("Expected no error") t.Errorf("Expected no error")
} }
updated_svc := <-c updated_svc := <-c
updated_service := updated_svc.(*api.Service) updated_service := updated_svc.Object.(*api.Service)
if updated_service.Name != "foo" { if updated_service.Name != "foo" {
t.Errorf("Expected foo, but got %v", updated_service.Name) t.Errorf("Expected foo, but got %v", updated_service.Name)
} }
@ -423,7 +423,7 @@ func TestServiceRegistryIPAllocation(t *testing.T) {
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
c1, _ := rest.Create(ctx, svc1) c1, _ := rest.Create(ctx, svc1)
created_svc1 := <-c1 created_svc1 := <-c1
created_service_1 := created_svc1.(*api.Service) created_service_1 := created_svc1.Object.(*api.Service)
if created_service_1.Name != "foo" { if created_service_1.Name != "foo" {
t.Errorf("Expected foo, but got %v", created_service_1.Name) t.Errorf("Expected foo, but got %v", created_service_1.Name)
} }
@ -439,7 +439,7 @@ func TestServiceRegistryIPAllocation(t *testing.T) {
ctx = api.NewDefaultContext() ctx = api.NewDefaultContext()
c2, _ := rest.Create(ctx, svc2) c2, _ := rest.Create(ctx, svc2)
created_svc2 := <-c2 created_svc2 := <-c2
created_service_2 := created_svc2.(*api.Service) created_service_2 := created_svc2.Object.(*api.Service)
if created_service_2.Name != "bar" { if created_service_2.Name != "bar" {
t.Errorf("Expected bar, but got %v", created_service_2.Name) t.Errorf("Expected bar, but got %v", created_service_2.Name)
} }
@ -462,7 +462,7 @@ func TestServiceRegistryIPReallocation(t *testing.T) {
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
c1, _ := rest.Create(ctx, svc1) c1, _ := rest.Create(ctx, svc1)
created_svc1 := <-c1 created_svc1 := <-c1
created_service_1 := created_svc1.(*api.Service) created_service_1 := created_svc1.Object.(*api.Service)
if created_service_1.Name != "foo" { if created_service_1.Name != "foo" {
t.Errorf("Expected foo, but got %v", created_service_1.Name) t.Errorf("Expected foo, but got %v", created_service_1.Name)
} }
@ -481,7 +481,7 @@ func TestServiceRegistryIPReallocation(t *testing.T) {
ctx = api.NewDefaultContext() ctx = api.NewDefaultContext()
c2, _ := rest.Create(ctx, svc2) c2, _ := rest.Create(ctx, svc2)
created_svc2 := <-c2 created_svc2 := <-c2
created_service_2 := created_svc2.(*api.Service) created_service_2 := created_svc2.Object.(*api.Service)
if created_service_2.Name != "bar" { if created_service_2.Name != "bar" {
t.Errorf("Expected bar, but got %v", created_service_2.Name) t.Errorf("Expected bar, but got %v", created_service_2.Name)
} }
@ -504,7 +504,7 @@ func TestServiceRegistryIPUpdate(t *testing.T) {
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
c, _ := rest.Create(ctx, svc) c, _ := rest.Create(ctx, svc)
created_svc := <-c created_svc := <-c
created_service := created_svc.(*api.Service) created_service := created_svc.Object.(*api.Service)
if created_service.Port != 6502 { if created_service.Port != 6502 {
t.Errorf("Expected port 6502, but got %v", created_service.Port) t.Errorf("Expected port 6502, but got %v", created_service.Port)
} }
@ -523,7 +523,7 @@ func TestServiceRegistryIPUpdate(t *testing.T) {
c, _ = rest.Update(ctx, update) c, _ = rest.Update(ctx, update)
updated_svc := <-c updated_svc := <-c
updated_service := updated_svc.(*api.Service) updated_service := updated_svc.Object.(*api.Service)
if updated_service.Port != 6503 { if updated_service.Port != 6503 {
t.Errorf("Expected port 6503, but got %v", updated_service.Port) t.Errorf("Expected port 6503, but got %v", updated_service.Port)
} }
@ -550,7 +550,7 @@ func TestServiceRegistryIPExternalLoadBalancer(t *testing.T) {
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
c, _ := rest.Create(ctx, svc) c, _ := rest.Create(ctx, svc)
created_svc := <-c created_svc := <-c
created_service := created_svc.(*api.Service) created_service := created_svc.Object.(*api.Service)
if created_service.Port != 6502 { if created_service.Port != 6502 {
t.Errorf("Expected port 6502, but got %v", created_service.Port) t.Errorf("Expected port 6502, but got %v", created_service.Port)
} }
@ -594,7 +594,7 @@ func TestServiceRegistryIPReloadFromStorage(t *testing.T) {
} }
c, _ = rest2.Create(ctx, svc) c, _ = rest2.Create(ctx, svc)
created_svc := <-c created_svc := <-c
created_service := created_svc.(*api.Service) created_service := created_svc.Object.(*api.Service)
if created_service.PortalIP != "1.2.3.3" { if created_service.PortalIP != "1.2.3.3" {
t.Errorf("Unexpected PortalIP: %s", created_service.PortalIP) t.Errorf("Unexpected PortalIP: %s", created_service.PortalIP)
} }