Merge pull request #4263 from smarterclayton/simplify_apiserver
Remove layers of indirection between apiinstaller and resthandler
This commit is contained in:
@@ -29,6 +29,12 @@ import (
|
|||||||
const (
|
const (
|
||||||
StatusUnprocessableEntity = 422
|
StatusUnprocessableEntity = 422
|
||||||
StatusTooManyRequests = 429
|
StatusTooManyRequests = 429
|
||||||
|
// HTTP recommendations are for servers to define 5xx error codes
|
||||||
|
// for scenarios not covered by behavior. In this case, TryAgainLater
|
||||||
|
// is an indication that a transient server error has occured and the
|
||||||
|
// client *should* retry, with an optional Retry-After header to specify
|
||||||
|
// the back off window.
|
||||||
|
StatusTryAgainLater = 504
|
||||||
)
|
)
|
||||||
|
|
||||||
// StatusError is an error intended for consumption by a REST API server; it can also be
|
// StatusError is an error intended for consumption by a REST API server; it can also be
|
||||||
@@ -202,6 +208,17 @@ func NewInternalError(err error) error {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewTimeoutError returns an error indicating that a timeout occurred before the request
|
||||||
|
// could be completed. Clients may retry, but the operation may still complete.
|
||||||
|
func NewTimeoutError(message string) error {
|
||||||
|
return &StatusError{api.Status{
|
||||||
|
Status: api.StatusFailure,
|
||||||
|
Code: StatusTryAgainLater,
|
||||||
|
Reason: api.StatusReasonTimeout,
|
||||||
|
Message: fmt.Sprintf("Timeout: %s", message),
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
// IsNotFound returns true if the specified error was created by NewNotFoundErr.
|
// IsNotFound returns true if the specified error was created by NewNotFoundErr.
|
||||||
func IsNotFound(err error) bool {
|
func IsNotFound(err error) bool {
|
||||||
return reasonForError(err) == api.StatusReasonNotFound
|
return reasonForError(err) == api.StatusReasonNotFound
|
||||||
|
@@ -85,12 +85,12 @@ func (t *Tester) TestCreateResetsUserData(valid runtime.Object) {
|
|||||||
objectMeta.UID = "bad-uid"
|
objectMeta.UID = "bad-uid"
|
||||||
objectMeta.CreationTimestamp = now
|
objectMeta.CreationTimestamp = now
|
||||||
|
|
||||||
channel, err := t.storage.(apiserver.RESTCreater).Create(api.NewDefaultContext(), valid)
|
obj, err := t.storage.(apiserver.RESTCreater).Create(api.NewDefaultContext(), valid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
if obj := <-channel; obj.Object == nil {
|
if obj == nil {
|
||||||
t.Fatalf("Unexpected object from channel: %#v", obj)
|
t.Fatalf("Unexpected object from result: %#v", obj)
|
||||||
}
|
}
|
||||||
if objectMeta.UID == "bad-uid" || objectMeta.CreationTimestamp == now {
|
if objectMeta.UID == "bad-uid" || objectMeta.CreationTimestamp == now {
|
||||||
t.Errorf("ObjectMeta did not reset basic fields: %#v", objectMeta)
|
t.Errorf("ObjectMeta did not reset basic fields: %#v", objectMeta)
|
||||||
@@ -111,12 +111,12 @@ func (t *Tester) TestCreateHasMetadata(valid runtime.Object) {
|
|||||||
context = api.NewContext()
|
context = api.NewContext()
|
||||||
}
|
}
|
||||||
|
|
||||||
channel, err := t.storage.(apiserver.RESTCreater).Create(context, valid)
|
obj, err := t.storage.(apiserver.RESTCreater).Create(context, valid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
if obj := <-channel; obj.Object == nil {
|
if obj == nil {
|
||||||
t.Fatalf("Unexpected object from channel: %#v", obj)
|
t.Fatalf("Unexpected object from result: %#v", obj)
|
||||||
}
|
}
|
||||||
if !api.HasObjectMetaSystemFieldValues(objectMeta) {
|
if !api.HasObjectMetaSystemFieldValues(objectMeta) {
|
||||||
t.Errorf("storage did not populate object meta field values")
|
t.Errorf("storage did not populate object meta field values")
|
||||||
@@ -148,12 +148,8 @@ func (t *Tester) TestCreateGeneratesNameReturnsTryAgain(valid runtime.Object) {
|
|||||||
|
|
||||||
objectMeta.GenerateName = "test-"
|
objectMeta.GenerateName = "test-"
|
||||||
t.withStorageError(errors.NewAlreadyExists("kind", "thing"), func() {
|
t.withStorageError(errors.NewAlreadyExists("kind", "thing"), func() {
|
||||||
ch, err := t.storage.(apiserver.RESTCreater).Create(api.NewDefaultContext(), valid)
|
_, err := t.storage.(apiserver.RESTCreater).Create(api.NewDefaultContext(), valid)
|
||||||
if err != nil {
|
if err == nil || !errors.IsTryAgainLater(err) {
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
res := <-ch
|
|
||||||
if err := errors.FromObject(res.Object); err == nil || !errors.IsTryAgainLater(err) {
|
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@@ -17,20 +17,24 @@ limitations under the License.
|
|||||||
package apiserver
|
package apiserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
gpath "path"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
|
|
||||||
"github.com/emicklei/go-restful"
|
"github.com/emicklei/go-restful"
|
||||||
)
|
)
|
||||||
|
|
||||||
type APIInstaller struct {
|
type APIInstaller struct {
|
||||||
prefix string // Path prefix where API resources are to be registered.
|
group *APIGroupVersion
|
||||||
version string // The API version being installed.
|
prefix string // Path prefix where API resources are to be registered.
|
||||||
restHandler *RESTHandler
|
version string // The API version being installed.
|
||||||
mapper meta.RESTMapper
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Struct capturing information about an action ("GET", "POST", "WATCH", PROXY", etc).
|
// Struct capturing information about an action ("GET", "POST", "WATCH", PROXY", etc).
|
||||||
@@ -40,6 +44,9 @@ type action struct {
|
|||||||
Params []*restful.Parameter // List of parameters associated with the action.
|
Params []*restful.Parameter // List of parameters associated with the action.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// errEmptyName is returned when API requests do not fill the name section of the path.
|
||||||
|
var errEmptyName = fmt.Errorf("name must be provided")
|
||||||
|
|
||||||
// Installs handlers for API resources.
|
// Installs handlers for API resources.
|
||||||
func (a *APIInstaller) Install() (ws *restful.WebService, errors []error) {
|
func (a *APIInstaller) Install() (ws *restful.WebService, errors []error) {
|
||||||
errors = make([]error, 0)
|
errors = make([]error, 0)
|
||||||
@@ -49,16 +56,16 @@ func (a *APIInstaller) Install() (ws *restful.WebService, errors []error) {
|
|||||||
|
|
||||||
// Initialize the custom handlers.
|
// Initialize the custom handlers.
|
||||||
watchHandler := (&WatchHandler{
|
watchHandler := (&WatchHandler{
|
||||||
storage: a.restHandler.storage,
|
storage: a.group.storage,
|
||||||
codec: a.restHandler.codec,
|
codec: a.group.codec,
|
||||||
canonicalPrefix: a.restHandler.canonicalPrefix,
|
prefix: a.group.prefix,
|
||||||
selfLinker: a.restHandler.selfLinker,
|
linker: a.group.linker,
|
||||||
apiRequestInfoResolver: a.restHandler.apiRequestInfoResolver,
|
info: a.group.info,
|
||||||
})
|
})
|
||||||
redirectHandler := (&RedirectHandler{a.restHandler.storage, a.restHandler.codec, a.restHandler.apiRequestInfoResolver})
|
redirectHandler := (&RedirectHandler{a.group.storage, a.group.codec, a.group.info})
|
||||||
proxyHandler := (&ProxyHandler{a.prefix + "/proxy/", a.restHandler.storage, a.restHandler.codec, a.restHandler.apiRequestInfoResolver})
|
proxyHandler := (&ProxyHandler{a.prefix + "/proxy/", a.group.storage, a.group.codec, a.group.info})
|
||||||
|
|
||||||
for path, storage := range a.restHandler.storage {
|
for path, storage := range a.group.storage {
|
||||||
if err := a.registerResourceHandlers(path, storage, ws, watchHandler, redirectHandler, proxyHandler); err != nil {
|
if err := a.registerResourceHandlers(path, storage, ws, watchHandler, redirectHandler, proxyHandler); err != nil {
|
||||||
errors = append(errors, err)
|
errors = append(errors, err)
|
||||||
}
|
}
|
||||||
@@ -78,8 +85,11 @@ func (a *APIInstaller) newWebService() *restful.WebService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage, ws *restful.WebService, watchHandler http.Handler, redirectHandler http.Handler, proxyHandler http.Handler) error {
|
func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage, ws *restful.WebService, watchHandler http.Handler, redirectHandler http.Handler, proxyHandler http.Handler) error {
|
||||||
// Handler for standard REST verbs (GET, PUT, POST and DELETE).
|
codec := a.group.codec
|
||||||
restVerbHandler := restfulStripPrefix(a.prefix, a.restHandler)
|
admit := a.group.admit
|
||||||
|
linker := a.group.linker
|
||||||
|
resource := path
|
||||||
|
|
||||||
object := storage.New()
|
object := storage.New()
|
||||||
// TODO: add scheme to APIInstaller rather than using api.Scheme
|
// TODO: add scheme to APIInstaller rather than using api.Scheme
|
||||||
_, kind, err := api.Scheme.ObjectVersionAndKind(object)
|
_, kind, err := api.Scheme.ObjectVersionAndKind(object)
|
||||||
@@ -103,28 +113,31 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
|
|||||||
versionedList = indirectArbitraryPointer(versionedListPtr)
|
versionedList = indirectArbitraryPointer(versionedListPtr)
|
||||||
}
|
}
|
||||||
|
|
||||||
mapping, err := a.mapper.RESTMapping(kind, a.version)
|
mapping, err := a.group.mapper.RESTMapping(kind, a.version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// what verbs are supported by the storage, used to know what verbs we support per path
|
// what verbs are supported by the storage, used to know what verbs we support per path
|
||||||
storageVerbs := map[string]bool{}
|
storageVerbs := map[string]bool{}
|
||||||
if _, ok := storage.(RESTCreater); ok {
|
creater, ok := storage.(RESTCreater)
|
||||||
// Handler for standard REST verbs (GET, PUT, POST and DELETE).
|
if ok {
|
||||||
storageVerbs["RESTCreater"] = true
|
storageVerbs["RESTCreater"] = true
|
||||||
}
|
}
|
||||||
if _, ok := storage.(RESTLister); ok {
|
lister, ok := storage.(RESTLister)
|
||||||
// Handler for standard REST verbs (GET, PUT, POST and DELETE).
|
if ok {
|
||||||
storageVerbs["RESTLister"] = true
|
storageVerbs["RESTLister"] = true
|
||||||
}
|
}
|
||||||
if _, ok := storage.(RESTGetter); ok {
|
getter, ok := storage.(RESTGetter)
|
||||||
|
if ok {
|
||||||
storageVerbs["RESTGetter"] = true
|
storageVerbs["RESTGetter"] = true
|
||||||
}
|
}
|
||||||
if _, ok := storage.(RESTDeleter); ok {
|
deleter, ok := storage.(RESTDeleter)
|
||||||
|
if ok {
|
||||||
storageVerbs["RESTDeleter"] = true
|
storageVerbs["RESTDeleter"] = true
|
||||||
}
|
}
|
||||||
if _, ok := storage.(RESTUpdater); ok {
|
updater, ok := storage.(RESTUpdater)
|
||||||
|
if ok {
|
||||||
storageVerbs["RESTUpdater"] = true
|
storageVerbs["RESTUpdater"] = true
|
||||||
}
|
}
|
||||||
if _, ok := storage.(ResourceWatcher); ok {
|
if _, ok := storage.(ResourceWatcher); ok {
|
||||||
@@ -134,6 +147,14 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
|
|||||||
storageVerbs["Redirector"] = true
|
storageVerbs["Redirector"] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var namespaceFn ResourceNamespaceFunc
|
||||||
|
var nameFn ResourceNameFunc
|
||||||
|
var generateLinkFn linkFunc
|
||||||
|
var objNameFn ObjectNameFunc
|
||||||
|
linkFn := func(req *restful.Request, obj runtime.Object) error {
|
||||||
|
return setSelfLink(obj, req.Request, a.group.linker, generateLinkFn)
|
||||||
|
}
|
||||||
|
|
||||||
allowWatchList := storageVerbs["ResourceWatcher"] && storageVerbs["RESTLister"] // watching on lists is allowed only for kinds that support both watch and list.
|
allowWatchList := storageVerbs["ResourceWatcher"] && storageVerbs["RESTLister"] // watching on lists is allowed only for kinds that support both watch and list.
|
||||||
scope := mapping.Scope
|
scope := mapping.Scope
|
||||||
nameParam := ws.PathParameter("name", "name of the "+kind).DataType("string")
|
nameParam := ws.PathParameter("name", "name of the "+kind).DataType("string")
|
||||||
@@ -141,6 +162,14 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
|
|||||||
actions := []action{}
|
actions := []action{}
|
||||||
// Get the list of actions for the given scope.
|
// Get the list of actions for the given scope.
|
||||||
if scope.Name() != meta.RESTScopeNameNamespace {
|
if scope.Name() != meta.RESTScopeNameNamespace {
|
||||||
|
objNameFn = func(obj runtime.Object) (namespace, name string, err error) {
|
||||||
|
name, err = linker.Name(obj)
|
||||||
|
if len(name) == 0 {
|
||||||
|
err = errEmptyName
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Handler for standard REST verbs (GET, PUT, POST and DELETE).
|
// Handler for standard REST verbs (GET, PUT, POST and DELETE).
|
||||||
actions = appendIf(actions, action{"LIST", path, params}, storageVerbs["RESTLister"])
|
actions = appendIf(actions, action{"LIST", path, params}, storageVerbs["RESTLister"])
|
||||||
actions = appendIf(actions, action{"POST", path, params}, storageVerbs["RESTCreater"])
|
actions = appendIf(actions, action{"POST", path, params}, storageVerbs["RESTCreater"])
|
||||||
@@ -148,6 +177,22 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
|
|||||||
|
|
||||||
itemPath := path + "/{name}"
|
itemPath := path + "/{name}"
|
||||||
nameParams := append(params, nameParam)
|
nameParams := append(params, nameParam)
|
||||||
|
namespaceFn = func(req *restful.Request) (namespace string, err error) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nameFn = func(req *restful.Request) (namespace, name string, err error) {
|
||||||
|
name = req.PathParameter("name")
|
||||||
|
if len(name) == 0 {
|
||||||
|
err = errEmptyName
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
generateLinkFn = func(namespace, name string) (path string, query string) {
|
||||||
|
path = strings.Replace(itemPath, "{name}", name, 1)
|
||||||
|
path = gpath.Join(a.prefix, path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
actions = appendIf(actions, action{"GET", itemPath, nameParams}, storageVerbs["RESTGetter"])
|
actions = appendIf(actions, action{"GET", itemPath, nameParams}, storageVerbs["RESTGetter"])
|
||||||
actions = appendIf(actions, action{"PUT", itemPath, nameParams}, storageVerbs["RESTUpdater"])
|
actions = appendIf(actions, action{"PUT", itemPath, nameParams}, storageVerbs["RESTUpdater"])
|
||||||
actions = appendIf(actions, action{"DELETE", itemPath, nameParams}, storageVerbs["RESTDeleter"])
|
actions = appendIf(actions, action{"DELETE", itemPath, nameParams}, storageVerbs["RESTDeleter"])
|
||||||
@@ -156,18 +201,49 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
|
|||||||
actions = appendIf(actions, action{"PROXY", "/proxy/" + itemPath + "/{path:*}", nameParams}, storageVerbs["Redirector"])
|
actions = appendIf(actions, action{"PROXY", "/proxy/" + itemPath + "/{path:*}", nameParams}, storageVerbs["Redirector"])
|
||||||
actions = appendIf(actions, action{"PROXY", "/proxy/" + itemPath, nameParams}, storageVerbs["Redirector"])
|
actions = appendIf(actions, action{"PROXY", "/proxy/" + itemPath, nameParams}, storageVerbs["Redirector"])
|
||||||
} else {
|
} else {
|
||||||
|
objNameFn = func(obj runtime.Object) (namespace, name string, err error) {
|
||||||
|
if name, err = linker.Name(obj); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
namespace, err = linker.Namespace(obj)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// v1beta3 format with namespace in path
|
// v1beta3 format with namespace in path
|
||||||
if scope.ParamPath() {
|
if scope.ParamPath() {
|
||||||
// Handler for standard REST verbs (GET, PUT, POST and DELETE).
|
// Handler for standard REST verbs (GET, PUT, POST and DELETE).
|
||||||
namespaceParam := ws.PathParameter(scope.ParamName(), scope.ParamDescription()).DataType("string")
|
namespaceParam := ws.PathParameter(scope.ParamName(), scope.ParamDescription()).DataType("string")
|
||||||
namespacedPath := scope.ParamName() + "/{" + scope.ParamName() + "}/" + path
|
namespacedPath := scope.ParamName() + "/{" + scope.ParamName() + "}/" + path
|
||||||
namespaceParams := []*restful.Parameter{namespaceParam}
|
namespaceParams := []*restful.Parameter{namespaceParam}
|
||||||
|
namespaceFn = func(req *restful.Request) (namespace string, err error) {
|
||||||
|
namespace = req.PathParameter(scope.ParamName())
|
||||||
|
if len(namespace) == 0 {
|
||||||
|
namespace = api.NamespaceDefault
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
actions = appendIf(actions, action{"LIST", namespacedPath, namespaceParams}, storageVerbs["RESTLister"])
|
actions = appendIf(actions, action{"LIST", namespacedPath, namespaceParams}, storageVerbs["RESTLister"])
|
||||||
actions = appendIf(actions, action{"POST", namespacedPath, namespaceParams}, storageVerbs["RESTCreater"])
|
actions = appendIf(actions, action{"POST", namespacedPath, namespaceParams}, storageVerbs["RESTCreater"])
|
||||||
actions = appendIf(actions, action{"WATCHLIST", "/watch/" + namespacedPath, namespaceParams}, allowWatchList)
|
actions = appendIf(actions, action{"WATCHLIST", "/watch/" + namespacedPath, namespaceParams}, allowWatchList)
|
||||||
|
|
||||||
itemPath := namespacedPath + "/{name}"
|
itemPath := namespacedPath + "/{name}"
|
||||||
nameParams := append(namespaceParams, nameParam)
|
nameParams := append(namespaceParams, nameParam)
|
||||||
|
nameFn = func(req *restful.Request) (namespace, name string, err error) {
|
||||||
|
namespace, _ = namespaceFn(req)
|
||||||
|
name = req.PathParameter("name")
|
||||||
|
if len(name) == 0 {
|
||||||
|
err = errEmptyName
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
generateLinkFn = func(namespace, name string) (path string, query string) {
|
||||||
|
path = strings.Replace(itemPath, "{name}", name, 1)
|
||||||
|
path = strings.Replace(path, "{"+scope.ParamName()+"}", namespace, 1)
|
||||||
|
path = gpath.Join(a.prefix, path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
actions = appendIf(actions, action{"GET", itemPath, nameParams}, storageVerbs["RESTGetter"])
|
actions = appendIf(actions, action{"GET", itemPath, nameParams}, storageVerbs["RESTGetter"])
|
||||||
actions = appendIf(actions, action{"PUT", itemPath, nameParams}, storageVerbs["RESTUpdater"])
|
actions = appendIf(actions, action{"PUT", itemPath, nameParams}, storageVerbs["RESTUpdater"])
|
||||||
actions = appendIf(actions, action{"DELETE", itemPath, nameParams}, storageVerbs["RESTDeleter"])
|
actions = appendIf(actions, action{"DELETE", itemPath, nameParams}, storageVerbs["RESTDeleter"])
|
||||||
@@ -184,12 +260,39 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
|
|||||||
// v1beta1/v1beta2 format where namespace was a query parameter
|
// v1beta1/v1beta2 format where namespace was a query parameter
|
||||||
namespaceParam := ws.QueryParameter(scope.ParamName(), scope.ParamDescription()).DataType("string")
|
namespaceParam := ws.QueryParameter(scope.ParamName(), scope.ParamDescription()).DataType("string")
|
||||||
namespaceParams := []*restful.Parameter{namespaceParam}
|
namespaceParams := []*restful.Parameter{namespaceParam}
|
||||||
|
namespaceFn = func(req *restful.Request) (namespace string, err error) {
|
||||||
|
namespace = req.QueryParameter(scope.ParamName())
|
||||||
|
if len(namespace) == 0 {
|
||||||
|
namespace = api.NamespaceDefault
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
actions = appendIf(actions, action{"LIST", path, namespaceParams}, storageVerbs["RESTLister"])
|
actions = appendIf(actions, action{"LIST", path, namespaceParams}, storageVerbs["RESTLister"])
|
||||||
actions = appendIf(actions, action{"POST", path, namespaceParams}, storageVerbs["RESTCreater"])
|
actions = appendIf(actions, action{"POST", path, namespaceParams}, storageVerbs["RESTCreater"])
|
||||||
actions = appendIf(actions, action{"WATCHLIST", "/watch/" + path, namespaceParams}, allowWatchList)
|
actions = appendIf(actions, action{"WATCHLIST", "/watch/" + path, namespaceParams}, allowWatchList)
|
||||||
|
|
||||||
itemPath := path + "/{name}"
|
itemPath := path + "/{name}"
|
||||||
nameParams := append(namespaceParams, nameParam)
|
nameParams := append(namespaceParams, nameParam)
|
||||||
|
nameFn = func(req *restful.Request) (namespace, name string, err error) {
|
||||||
|
namespace, _ = namespaceFn(req)
|
||||||
|
name = req.PathParameter("name")
|
||||||
|
if len(name) == 0 {
|
||||||
|
err = errEmptyName
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
generateLinkFn = func(namespace, name string) (path string, query string) {
|
||||||
|
path = strings.Replace(itemPath, "{name}", name, -1)
|
||||||
|
path = gpath.Join(a.prefix, path)
|
||||||
|
if len(namespace) > 0 {
|
||||||
|
values := make(url.Values)
|
||||||
|
values.Set(scope.ParamName(), namespace)
|
||||||
|
query = values.Encode()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
actions = appendIf(actions, action{"GET", itemPath, nameParams}, storageVerbs["RESTGetter"])
|
actions = appendIf(actions, action{"GET", itemPath, nameParams}, storageVerbs["RESTGetter"])
|
||||||
actions = appendIf(actions, action{"PUT", itemPath, nameParams}, storageVerbs["RESTUpdater"])
|
actions = appendIf(actions, action{"PUT", itemPath, nameParams}, storageVerbs["RESTUpdater"])
|
||||||
actions = appendIf(actions, action{"DELETE", itemPath, nameParams}, storageVerbs["RESTDeleter"])
|
actions = appendIf(actions, action{"DELETE", itemPath, nameParams}, storageVerbs["RESTDeleter"])
|
||||||
@@ -218,43 +321,50 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
|
|||||||
// test/integration/auth_test.go is currently the most comprehensive status code test
|
// test/integration/auth_test.go is currently the most comprehensive status code test
|
||||||
|
|
||||||
for _, action := range actions {
|
for _, action := range actions {
|
||||||
|
m := monitorFilter(action.Verb, resource)
|
||||||
switch action.Verb {
|
switch action.Verb {
|
||||||
case "GET": // Get a resource.
|
case "GET": // Get a resource.
|
||||||
route := ws.GET(action.Path).To(restVerbHandler).
|
route := ws.GET(action.Path).To(GetResource(getter, nameFn, linkFn, codec)).
|
||||||
|
Filter(m).
|
||||||
Doc("read the specified " + kind).
|
Doc("read the specified " + kind).
|
||||||
Operation("read" + kind).
|
Operation("read" + kind).
|
||||||
Writes(versionedObject)
|
Writes(versionedObject)
|
||||||
addParams(route, action.Params)
|
addParams(route, action.Params)
|
||||||
ws.Route(route)
|
ws.Route(route)
|
||||||
case "LIST": // List all resources of a kind.
|
case "LIST": // List all resources of a kind.
|
||||||
route := ws.GET(action.Path).To(restVerbHandler).
|
route := ws.GET(action.Path).To(ListResource(lister, namespaceFn, linkFn, codec)).
|
||||||
|
Filter(m).
|
||||||
Doc("list objects of kind " + kind).
|
Doc("list objects of kind " + kind).
|
||||||
Operation("list" + kind).
|
Operation("list" + kind).
|
||||||
Writes(versionedList)
|
Writes(versionedList)
|
||||||
addParams(route, action.Params)
|
addParams(route, action.Params)
|
||||||
ws.Route(route)
|
ws.Route(route)
|
||||||
case "PUT": // Update a resource.
|
case "PUT": // Update a resource.
|
||||||
route := ws.PUT(action.Path).To(restVerbHandler).
|
route := ws.PUT(action.Path).To(UpdateResource(updater, nameFn, objNameFn, linkFn, codec, resource, admit)).
|
||||||
|
Filter(m).
|
||||||
Doc("update the specified " + kind).
|
Doc("update the specified " + kind).
|
||||||
Operation("update" + kind).
|
Operation("update" + kind).
|
||||||
Reads(versionedObject)
|
Reads(versionedObject)
|
||||||
addParams(route, action.Params)
|
addParams(route, action.Params)
|
||||||
ws.Route(route)
|
ws.Route(route)
|
||||||
case "POST": // Create a resource.
|
case "POST": // Create a resource.
|
||||||
route := ws.POST(action.Path).To(restVerbHandler).
|
route := ws.POST(action.Path).To(CreateResource(creater, namespaceFn, linkFn, codec, resource, admit)).
|
||||||
|
Filter(m).
|
||||||
Doc("create a " + kind).
|
Doc("create a " + kind).
|
||||||
Operation("create" + kind).
|
Operation("create" + kind).
|
||||||
Reads(versionedObject)
|
Reads(versionedObject)
|
||||||
addParams(route, action.Params)
|
addParams(route, action.Params)
|
||||||
ws.Route(route)
|
ws.Route(route)
|
||||||
case "DELETE": // Delete a resource.
|
case "DELETE": // Delete a resource.
|
||||||
route := ws.DELETE(action.Path).To(restVerbHandler).
|
route := ws.DELETE(action.Path).To(DeleteResource(deleter, nameFn, linkFn, codec, resource, kind, admit)).
|
||||||
|
Filter(m).
|
||||||
Doc("delete a " + kind).
|
Doc("delete a " + kind).
|
||||||
Operation("delete" + kind)
|
Operation("delete" + kind)
|
||||||
addParams(route, action.Params)
|
addParams(route, action.Params)
|
||||||
ws.Route(route)
|
ws.Route(route)
|
||||||
case "WATCH": // Watch a resource.
|
case "WATCH": // Watch a resource.
|
||||||
route := ws.GET(action.Path).To(restfulStripPrefix(a.prefix+"/watch", watchHandler)).
|
route := ws.GET(action.Path).To(restfulStripPrefix(a.prefix+"/watch", watchHandler)).
|
||||||
|
Filter(m).
|
||||||
Doc("watch a particular " + kind).
|
Doc("watch a particular " + kind).
|
||||||
Operation("watch" + kind).
|
Operation("watch" + kind).
|
||||||
Writes(versionedObject)
|
Writes(versionedObject)
|
||||||
@@ -262,6 +372,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
|
|||||||
ws.Route(route)
|
ws.Route(route)
|
||||||
case "WATCHLIST": // Watch all resources of a kind.
|
case "WATCHLIST": // Watch all resources of a kind.
|
||||||
route := ws.GET(action.Path).To(restfulStripPrefix(a.prefix+"/watch", watchHandler)).
|
route := ws.GET(action.Path).To(restfulStripPrefix(a.prefix+"/watch", watchHandler)).
|
||||||
|
Filter(m).
|
||||||
Doc("watch a list of " + kind).
|
Doc("watch a list of " + kind).
|
||||||
Operation("watch" + kind + "list").
|
Operation("watch" + kind + "list").
|
||||||
Writes(versionedList)
|
Writes(versionedList)
|
||||||
@@ -269,6 +380,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
|
|||||||
ws.Route(route)
|
ws.Route(route)
|
||||||
case "REDIRECT": // Get the redirect URL for a resource.
|
case "REDIRECT": // Get the redirect URL for a resource.
|
||||||
route := ws.GET(action.Path).To(restfulStripPrefix(a.prefix+"/redirect", redirectHandler)).
|
route := ws.GET(action.Path).To(restfulStripPrefix(a.prefix+"/redirect", redirectHandler)).
|
||||||
|
Filter(m).
|
||||||
Doc("redirect GET request to " + kind).
|
Doc("redirect GET request to " + kind).
|
||||||
Operation("redirect" + kind).
|
Operation("redirect" + kind).
|
||||||
Produces("*/*").
|
Produces("*/*").
|
||||||
@@ -277,10 +389,12 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
|
|||||||
ws.Route(route)
|
ws.Route(route)
|
||||||
case "PROXY": // Proxy requests to a resource.
|
case "PROXY": // Proxy requests to a resource.
|
||||||
// Accept all methods as per https://github.com/GoogleCloudPlatform/kubernetes/issues/3996
|
// Accept all methods as per https://github.com/GoogleCloudPlatform/kubernetes/issues/3996
|
||||||
addProxyRoute(ws, "GET", a.prefix, action.Path, proxyHandler, kind, action.Params)
|
addProxyRoute(ws, "GET", a.prefix, action.Path, proxyHandler, kind, resource, action.Params)
|
||||||
addProxyRoute(ws, "PUT", a.prefix, action.Path, proxyHandler, kind, action.Params)
|
addProxyRoute(ws, "PUT", a.prefix, action.Path, proxyHandler, kind, resource, action.Params)
|
||||||
addProxyRoute(ws, "POST", a.prefix, action.Path, proxyHandler, kind, action.Params)
|
addProxyRoute(ws, "POST", a.prefix, action.Path, proxyHandler, kind, resource, action.Params)
|
||||||
addProxyRoute(ws, "DELETE", a.prefix, action.Path, proxyHandler, kind, action.Params)
|
addProxyRoute(ws, "DELETE", a.prefix, action.Path, proxyHandler, kind, resource, action.Params)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unrecognized action verb: %s", action.Verb)
|
||||||
}
|
}
|
||||||
// Note: update GetAttribs() when adding a custom handler.
|
// Note: update GetAttribs() when adding a custom handler.
|
||||||
}
|
}
|
||||||
@@ -306,8 +420,9 @@ func restfulStripPrefix(prefix string, handler http.Handler) restful.RouteFuncti
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func addProxyRoute(ws *restful.WebService, method string, prefix string, path string, proxyHandler http.Handler, kind string, params []*restful.Parameter) {
|
func addProxyRoute(ws *restful.WebService, method string, prefix string, path string, proxyHandler http.Handler, kind, resource string, params []*restful.Parameter) {
|
||||||
proxyRoute := ws.Method(method).Path(path).To(restfulStripPrefix(prefix+"/proxy", proxyHandler)).
|
proxyRoute := ws.Method(method).Path(path).To(restfulStripPrefix(prefix+"/proxy", proxyHandler)).
|
||||||
|
Filter(monitorFilter("PROXY", resource)).
|
||||||
Doc("proxy " + method + " requests to " + kind).
|
Doc("proxy " + method + " requests to " + kind).
|
||||||
Operation("proxy" + method + kind).
|
Operation("proxy" + method + kind).
|
||||||
Produces("*/*").
|
Produces("*/*").
|
||||||
|
@@ -73,6 +73,15 @@ func monitor(handler, verb, resource string, httpCode int, reqStart time.Time) {
|
|||||||
requestLatencies.WithLabelValues(handler, verb).Observe(float64((time.Since(reqStart)) / time.Microsecond))
|
requestLatencies.WithLabelValues(handler, verb).Observe(float64((time.Since(reqStart)) / time.Microsecond))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// monitorFilter creates a filter that reports the metrics for a given resource and action.
|
||||||
|
func monitorFilter(action, resource string) restful.FilterFunction {
|
||||||
|
return func(req *restful.Request, res *restful.Response, chain *restful.FilterChain) {
|
||||||
|
reqStart := time.Now()
|
||||||
|
chain.ProcessFilter(req, res)
|
||||||
|
monitor("rest", action, resource, res.StatusCode(), reqStart)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// mux is an object that can register http handlers.
|
// mux is an object that can register http handlers.
|
||||||
type Mux interface {
|
type Mux interface {
|
||||||
Handle(pattern string, handler http.Handler)
|
Handle(pattern string, handler http.Handler)
|
||||||
@@ -89,9 +98,9 @@ type defaultAPIServer struct {
|
|||||||
// as RESTful resources at prefix, serialized by codec, and also includes the support
|
// as RESTful resources at prefix, serialized by codec, and also includes the support
|
||||||
// http resources.
|
// http resources.
|
||||||
// Note: This method is used only in tests.
|
// Note: This method is used only in tests.
|
||||||
func Handle(storage map[string]RESTStorage, codec runtime.Codec, root string, version string, selfLinker runtime.SelfLinker, admissionControl admission.Interface, mapper meta.RESTMapper) http.Handler {
|
func Handle(storage map[string]RESTStorage, codec runtime.Codec, root string, version string, linker runtime.SelfLinker, admissionControl admission.Interface, mapper meta.RESTMapper) http.Handler {
|
||||||
prefix := root + "/" + version
|
prefix := path.Join(root, version)
|
||||||
group := NewAPIGroupVersion(storage, codec, root, prefix, selfLinker, admissionControl, mapper)
|
group := NewAPIGroupVersion(storage, codec, root, prefix, linker, admissionControl, mapper)
|
||||||
container := restful.NewContainer()
|
container := restful.NewContainer()
|
||||||
container.Router(restful.CurlyRouter{})
|
container.Router(restful.CurlyRouter{})
|
||||||
mux := container.ServeMux
|
mux := container.ServeMux
|
||||||
@@ -102,16 +111,19 @@ func Handle(storage map[string]RESTStorage, codec runtime.Codec, root string, ve
|
|||||||
return &defaultAPIServer{mux, group}
|
return &defaultAPIServer{mux, group}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This is a whole API version right now. Maybe should rename it.
|
// APIGroupVersion is a helper for exposing RESTStorage objects as http.Handlers via go-restful
|
||||||
// APIGroupVersion is a http.Handler that exposes multiple RESTStorage objects
|
|
||||||
// It handles URLs of the form:
|
// It handles URLs of the form:
|
||||||
// /${storage_key}[/${object_name}]
|
// /${storage_key}[/${object_name}]
|
||||||
// Where 'storage_key' points to a RESTStorage object stored in storage.
|
// Where 'storage_key' points to a RESTStorage object stored in storage.
|
||||||
//
|
|
||||||
// TODO: consider migrating this to go-restful which is a more full-featured version of the same thing.
|
|
||||||
type APIGroupVersion struct {
|
type APIGroupVersion struct {
|
||||||
handler RESTHandler
|
storage map[string]RESTStorage
|
||||||
|
codec runtime.Codec
|
||||||
|
prefix string
|
||||||
|
linker runtime.SelfLinker
|
||||||
|
admit admission.Interface
|
||||||
mapper meta.RESTMapper
|
mapper meta.RESTMapper
|
||||||
|
// TODO: put me into a cleaner interface
|
||||||
|
info *APIRequestInfoResolver
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAPIGroupVersion returns an object that will serve a set of REST resources and their
|
// NewAPIGroupVersion returns an object that will serve a set of REST resources and their
|
||||||
@@ -119,18 +131,15 @@ type APIGroupVersion struct {
|
|||||||
// This is a helper method for registering multiple sets of REST handlers under different
|
// This is a helper method for registering multiple sets of REST handlers under different
|
||||||
// prefixes onto a server.
|
// prefixes onto a server.
|
||||||
// TODO: add multitype codec serialization
|
// TODO: add multitype codec serialization
|
||||||
func NewAPIGroupVersion(storage map[string]RESTStorage, codec runtime.Codec, apiRoot, canonicalPrefix string, selfLinker runtime.SelfLinker, admissionControl admission.Interface, mapper meta.RESTMapper) *APIGroupVersion {
|
func NewAPIGroupVersion(storage map[string]RESTStorage, codec runtime.Codec, root, prefix string, linker runtime.SelfLinker, admissionControl admission.Interface, mapper meta.RESTMapper) *APIGroupVersion {
|
||||||
return &APIGroupVersion{
|
return &APIGroupVersion{
|
||||||
handler: RESTHandler{
|
storage: storage,
|
||||||
storage: storage,
|
codec: codec,
|
||||||
codec: codec,
|
prefix: prefix,
|
||||||
canonicalPrefix: canonicalPrefix,
|
linker: linker,
|
||||||
selfLinker: selfLinker,
|
admit: admissionControl,
|
||||||
ops: NewOperations(),
|
mapper: mapper,
|
||||||
admissionControl: admissionControl,
|
info: &APIRequestInfoResolver{util.NewStringSet(root), latest.RESTMapper},
|
||||||
apiRequestInfoResolver: &APIRequestInfoResolver{util.NewStringSet(apiRoot), latest.RESTMapper},
|
|
||||||
},
|
|
||||||
mapper: mapper,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,7 +148,12 @@ func NewAPIGroupVersion(storage map[string]RESTStorage, codec runtime.Codec, api
|
|||||||
// in a slash. A restful WebService is created for the group and version.
|
// in a slash. A restful WebService is created for the group and version.
|
||||||
func (g *APIGroupVersion) InstallREST(container *restful.Container, root string, version string) error {
|
func (g *APIGroupVersion) InstallREST(container *restful.Container, root string, version string) error {
|
||||||
prefix := path.Join(root, version)
|
prefix := path.Join(root, version)
|
||||||
ws, registrationErrors := (&APIInstaller{prefix, version, &g.handler, g.mapper}).Install()
|
installer := &APIInstaller{
|
||||||
|
group: g,
|
||||||
|
prefix: prefix,
|
||||||
|
version: version,
|
||||||
|
}
|
||||||
|
ws, registrationErrors := installer.Install()
|
||||||
container.Add(ws)
|
container.Add(ws)
|
||||||
return errors.NewAggregate(registrationErrors)
|
return errors.NewAggregate(registrationErrors)
|
||||||
}
|
}
|
||||||
@@ -186,15 +200,15 @@ func AddApiWebService(container *restful.Container, apiPrefix string, versions [
|
|||||||
// TODO: InstallREST should register each version automatically
|
// TODO: InstallREST should register each version automatically
|
||||||
|
|
||||||
versionHandler := APIVersionHandler(versions[:]...)
|
versionHandler := APIVersionHandler(versions[:]...)
|
||||||
getApiVersionsWebService := new(restful.WebService)
|
ws := new(restful.WebService)
|
||||||
getApiVersionsWebService.Path(apiPrefix)
|
ws.Path(apiPrefix)
|
||||||
getApiVersionsWebService.Doc("get available api versions")
|
ws.Doc("get available API versions")
|
||||||
getApiVersionsWebService.Route(getApiVersionsWebService.GET("/").To(versionHandler).
|
ws.Route(ws.GET("/").To(versionHandler).
|
||||||
Doc("get available api versions").
|
Doc("get available API versions").
|
||||||
Operation("getApiVersions").
|
Operation("getAPIVersions").
|
||||||
Produces(restful.MIME_JSON).
|
Produces(restful.MIME_JSON).
|
||||||
Consumes(restful.MIME_JSON))
|
Consumes(restful.MIME_JSON))
|
||||||
container.Add(getApiVersionsWebService)
|
container.Add(ws)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleVersion writes the server's version information.
|
// handleVersion writes the server's version information.
|
||||||
|
@@ -180,17 +180,17 @@ 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 RESTResult, error) {
|
func (storage *SimpleRESTStorage) Delete(ctx api.Context, id string) (runtime.Object, 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
|
||||||
}
|
}
|
||||||
return MakeAsync(func() (runtime.Object, error) {
|
var obj runtime.Object = &api.Status{Status: api.StatusSuccess}
|
||||||
if storage.injectedFunction != nil {
|
var err error
|
||||||
return storage.injectedFunction(&Simple{ObjectMeta: api.ObjectMeta{Name: id}})
|
if storage.injectedFunction != nil {
|
||||||
}
|
obj, err = storage.injectedFunction(&Simple{ObjectMeta: api.ObjectMeta{Name: id}})
|
||||||
return &api.Status{Status: api.StatusSuccess}, nil
|
}
|
||||||
}), nil
|
return obj, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (storage *SimpleRESTStorage) New() runtime.Object {
|
func (storage *SimpleRESTStorage) New() runtime.Object {
|
||||||
@@ -201,30 +201,28 @@ func (storage *SimpleRESTStorage) NewList() runtime.Object {
|
|||||||
return &SimpleList{}
|
return &SimpleList{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (storage *SimpleRESTStorage) Create(ctx api.Context, obj runtime.Object) (<-chan RESTResult, error) {
|
func (storage *SimpleRESTStorage) Create(ctx api.Context, obj runtime.Object) (runtime.Object, 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
|
||||||
}
|
}
|
||||||
return MakeAsync(func() (runtime.Object, error) {
|
var err error
|
||||||
if storage.injectedFunction != nil {
|
if storage.injectedFunction != nil {
|
||||||
return storage.injectedFunction(obj)
|
obj, err = storage.injectedFunction(obj)
|
||||||
}
|
}
|
||||||
return obj, nil
|
return obj, err
|
||||||
}), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (storage *SimpleRESTStorage) Update(ctx api.Context, obj runtime.Object) (<-chan RESTResult, error) {
|
func (storage *SimpleRESTStorage) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, 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, false, err
|
||||||
}
|
}
|
||||||
return MakeAsync(func() (runtime.Object, error) {
|
var err error
|
||||||
if storage.injectedFunction != nil {
|
if storage.injectedFunction != nil {
|
||||||
return storage.injectedFunction(obj)
|
obj, err = storage.injectedFunction(obj)
|
||||||
}
|
}
|
||||||
return obj, nil
|
return obj, false, err
|
||||||
}), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement ResourceWatcher.
|
// Implement ResourceWatcher.
|
||||||
@@ -489,7 +487,9 @@ func TestGet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
selfLinker := &setTestSelfLinker{
|
selfLinker := &setTestSelfLinker{
|
||||||
t: t,
|
t: t,
|
||||||
expectedSet: "/prefix/version/simple/id",
|
expectedSet: "/prefix/version/simple/id?namespace=default",
|
||||||
|
name: "id",
|
||||||
|
namespace: "default",
|
||||||
}
|
}
|
||||||
storage["simple"] = &simpleStorage
|
storage["simple"] = &simpleStorage
|
||||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||||
@@ -497,6 +497,12 @@ func TestGet(t *testing.T) {
|
|||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
resp, err := http.Get(server.URL + "/prefix/version/simple/id")
|
resp, err := http.Get(server.URL + "/prefix/version/simple/id")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
t.Fatalf("unexpected response: %#v", resp)
|
||||||
|
}
|
||||||
var itemOut Simple
|
var itemOut Simple
|
||||||
body, err := extractBody(resp, &itemOut)
|
body, err := extractBody(resp, &itemOut)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -511,6 +517,81 @@ func TestGet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetAlternateSelfLink(t *testing.T) {
|
||||||
|
storage := map[string]RESTStorage{}
|
||||||
|
simpleStorage := SimpleRESTStorage{
|
||||||
|
item: Simple{
|
||||||
|
Other: "foo",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
selfLinker := &setTestSelfLinker{
|
||||||
|
t: t,
|
||||||
|
expectedSet: "/prefix/version/simple/id?namespace=test",
|
||||||
|
name: "id",
|
||||||
|
namespace: "test",
|
||||||
|
}
|
||||||
|
storage["simple"] = &simpleStorage
|
||||||
|
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, legacyNamespaceMapper)
|
||||||
|
server := httptest.NewServer(handler)
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
resp, err := http.Get(server.URL + "/prefix/version/simple/id?namespace=test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
t.Fatalf("unexpected response: %#v", resp)
|
||||||
|
}
|
||||||
|
var itemOut Simple
|
||||||
|
body, err := extractBody(resp, &itemOut)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if itemOut.Name != simpleStorage.item.Name {
|
||||||
|
t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body))
|
||||||
|
}
|
||||||
|
if !selfLinker.called {
|
||||||
|
t.Errorf("Never set self link")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetNamespaceSelfLink(t *testing.T) {
|
||||||
|
storage := map[string]RESTStorage{}
|
||||||
|
simpleStorage := SimpleRESTStorage{
|
||||||
|
item: Simple{
|
||||||
|
Other: "foo",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
selfLinker := &setTestSelfLinker{
|
||||||
|
t: t,
|
||||||
|
expectedSet: "/prefix/version/namespaces/foo/simple/id",
|
||||||
|
name: "id",
|
||||||
|
namespace: "foo",
|
||||||
|
}
|
||||||
|
storage["simple"] = &simpleStorage
|
||||||
|
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, namespaceMapper)
|
||||||
|
server := httptest.NewServer(handler)
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
resp, err := http.Get(server.URL + "/prefix/version/namespaces/foo/simple/id")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
t.Fatalf("unexpected response: %#v", resp)
|
||||||
|
}
|
||||||
|
var itemOut Simple
|
||||||
|
body, err := extractBody(resp, &itemOut)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if itemOut.Name != simpleStorage.item.Name {
|
||||||
|
t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body))
|
||||||
|
}
|
||||||
|
if !selfLinker.called {
|
||||||
|
t.Errorf("Never set self link")
|
||||||
|
}
|
||||||
|
}
|
||||||
func TestGetMissing(t *testing.T) {
|
func TestGetMissing(t *testing.T) {
|
||||||
storage := map[string]RESTStorage{}
|
storage := map[string]RESTStorage{}
|
||||||
simpleStorage := SimpleRESTStorage{
|
simpleStorage := SimpleRESTStorage{
|
||||||
@@ -542,11 +623,13 @@ func TestDelete(t *testing.T) {
|
|||||||
|
|
||||||
client := http.Client{}
|
client := http.Client{}
|
||||||
request, err := http.NewRequest("DELETE", server.URL+"/prefix/version/simple/"+ID, nil)
|
request, err := http.NewRequest("DELETE", server.URL+"/prefix/version/simple/"+ID, nil)
|
||||||
_, err = client.Do(request)
|
res, err := client.Do(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if res.StatusCode != http.StatusOK {
|
||||||
|
t.Errorf("unexpected response: %#v", res)
|
||||||
}
|
}
|
||||||
|
|
||||||
if simpleStorage.deleted != ID {
|
if simpleStorage.deleted != ID {
|
||||||
t.Errorf("Unexpected delete: %s, expected %s", simpleStorage.deleted, ID)
|
t.Errorf("Unexpected delete: %s, expected %s", simpleStorage.deleted, ID)
|
||||||
}
|
}
|
||||||
@@ -602,13 +685,19 @@ func TestUpdate(t *testing.T) {
|
|||||||
storage["simple"] = &simpleStorage
|
storage["simple"] = &simpleStorage
|
||||||
selfLinker := &setTestSelfLinker{
|
selfLinker := &setTestSelfLinker{
|
||||||
t: t,
|
t: t,
|
||||||
expectedSet: "/prefix/version/simple/" + ID,
|
expectedSet: "/prefix/version/simple/" + ID + "?namespace=default",
|
||||||
|
name: ID,
|
||||||
|
namespace: api.NamespaceDefault,
|
||||||
}
|
}
|
||||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
item := &Simple{
|
item := &Simple{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: ID,
|
||||||
|
Namespace: "", // update should allow the client to send an empty namespace
|
||||||
|
},
|
||||||
Other: "bar",
|
Other: "bar",
|
||||||
}
|
}
|
||||||
body, err := codec.Encode(item)
|
body, err := codec.Encode(item)
|
||||||
@@ -637,15 +726,15 @@ func TestUpdateInvokesAdmissionControl(t *testing.T) {
|
|||||||
simpleStorage := SimpleRESTStorage{}
|
simpleStorage := SimpleRESTStorage{}
|
||||||
ID := "id"
|
ID := "id"
|
||||||
storage["simple"] = &simpleStorage
|
storage["simple"] = &simpleStorage
|
||||||
selfLinker := &setTestSelfLinker{
|
|
||||||
t: t,
|
|
||||||
expectedSet: "/prefix/version/simple/" + ID,
|
|
||||||
}
|
|
||||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), mapper)
|
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), mapper)
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
item := &Simple{
|
item := &Simple{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: ID,
|
||||||
|
Namespace: api.NamespaceDefault,
|
||||||
|
},
|
||||||
Other: "bar",
|
Other: "bar",
|
||||||
}
|
}
|
||||||
body, err := codec.Encode(item)
|
body, err := codec.Encode(item)
|
||||||
@@ -665,6 +754,100 @@ func TestUpdateInvokesAdmissionControl(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUpdateRequiresMatchingName(t *testing.T) {
|
||||||
|
storage := map[string]RESTStorage{}
|
||||||
|
simpleStorage := SimpleRESTStorage{}
|
||||||
|
ID := "id"
|
||||||
|
storage["simple"] = &simpleStorage
|
||||||
|
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), mapper)
|
||||||
|
server := httptest.NewServer(handler)
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
item := &Simple{
|
||||||
|
Other: "bar",
|
||||||
|
}
|
||||||
|
body, err := codec.Encode(item)
|
||||||
|
if err != nil {
|
||||||
|
// The following cases will fail, so die now
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := http.Client{}
|
||||||
|
request, err := http.NewRequest("PUT", server.URL+"/prefix/version/simple/"+ID, bytes.NewReader(body))
|
||||||
|
response, err := client.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if response.StatusCode != http.StatusBadRequest {
|
||||||
|
t.Errorf("Unexpected response %#v", response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateAllowsMissingNamespace(t *testing.T) {
|
||||||
|
storage := map[string]RESTStorage{}
|
||||||
|
simpleStorage := SimpleRESTStorage{}
|
||||||
|
ID := "id"
|
||||||
|
storage["simple"] = &simpleStorage
|
||||||
|
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||||
|
server := httptest.NewServer(handler)
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
item := &Simple{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: ID,
|
||||||
|
},
|
||||||
|
Other: "bar",
|
||||||
|
}
|
||||||
|
body, err := codec.Encode(item)
|
||||||
|
if err != nil {
|
||||||
|
// The following cases will fail, so die now
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := http.Client{}
|
||||||
|
request, err := http.NewRequest("PUT", server.URL+"/prefix/version/simple/"+ID, bytes.NewReader(body))
|
||||||
|
response, err := client.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if response.StatusCode != http.StatusOK {
|
||||||
|
t.Errorf("Unexpected response %#v", response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdatePreventsMismatchedNamespace(t *testing.T) {
|
||||||
|
storage := map[string]RESTStorage{}
|
||||||
|
simpleStorage := SimpleRESTStorage{}
|
||||||
|
ID := "id"
|
||||||
|
storage["simple"] = &simpleStorage
|
||||||
|
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||||
|
server := httptest.NewServer(handler)
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
item := &Simple{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: ID,
|
||||||
|
Namespace: "other",
|
||||||
|
},
|
||||||
|
Other: "bar",
|
||||||
|
}
|
||||||
|
body, err := codec.Encode(item)
|
||||||
|
if err != nil {
|
||||||
|
// The following cases will fail, so die now
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := http.Client{}
|
||||||
|
request, err := http.NewRequest("PUT", server.URL+"/prefix/version/simple/"+ID, bytes.NewReader(body))
|
||||||
|
response, err := client.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if response.StatusCode != http.StatusBadRequest {
|
||||||
|
t.Errorf("Unexpected response %#v", response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestUpdateMissing(t *testing.T) {
|
func TestUpdateMissing(t *testing.T) {
|
||||||
storage := map[string]RESTStorage{}
|
storage := map[string]RESTStorage{}
|
||||||
ID := "id"
|
ID := "id"
|
||||||
@@ -677,6 +860,10 @@ func TestUpdateMissing(t *testing.T) {
|
|||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
item := &Simple{
|
item := &Simple{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: ID,
|
||||||
|
Namespace: api.NamespaceDefault,
|
||||||
|
},
|
||||||
Other: "bar",
|
Other: "bar",
|
||||||
}
|
}
|
||||||
body, err := codec.Encode(item)
|
body, err := codec.Encode(item)
|
||||||
@@ -690,7 +877,6 @@ func TestUpdateMissing(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpected error: %v", err)
|
t.Errorf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.StatusCode != http.StatusNotFound {
|
if response.StatusCode != http.StatusNotFound {
|
||||||
t.Errorf("Unexpected response %#v", response)
|
t.Errorf("Unexpected response %#v", response)
|
||||||
}
|
}
|
||||||
@@ -806,7 +992,7 @@ func TestCreate(t *testing.T) {
|
|||||||
if !reflect.DeepEqual(&itemOut, simple) {
|
if !reflect.DeepEqual(&itemOut, simple) {
|
||||||
t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simple, string(body))
|
t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simple, string(body))
|
||||||
}
|
}
|
||||||
if response.StatusCode != http.StatusOK {
|
if response.StatusCode != http.StatusCreated {
|
||||||
t.Errorf("Unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusOK, response)
|
t.Errorf("Unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusOK, response)
|
||||||
}
|
}
|
||||||
if !selfLinker.called {
|
if !selfLinker.called {
|
||||||
@@ -961,7 +1147,7 @@ func TestCreateTimeout(t *testing.T) {
|
|||||||
|
|
||||||
simple := &Simple{Other: "foo"}
|
simple := &Simple{Other: "foo"}
|
||||||
data, _ := codec.Encode(simple)
|
data, _ := codec.Encode(simple)
|
||||||
itemOut := expectApiStatus(t, "POST", server.URL+"/prefix/version/foo?timeout=4ms", data, http.StatusAccepted)
|
itemOut := expectApiStatus(t, "POST", server.URL+"/prefix/version/foo?timeout=4ms", data, apierrs.StatusTryAgainLater)
|
||||||
if itemOut.Status != api.StatusFailure || itemOut.Reason != api.StatusReasonTimeout {
|
if itemOut.Status != api.StatusFailure || itemOut.Reason != api.StatusReasonTimeout {
|
||||||
t.Errorf("Unexpected status %#v", itemOut)
|
t.Errorf("Unexpected status %#v", itemOut)
|
||||||
}
|
}
|
||||||
|
@@ -45,28 +45,3 @@ func MakeAsync(fn WorkFunc) <-chan RESTResult {
|
|||||||
}()
|
}()
|
||||||
return 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 {
|
|
||||||
channel <- 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
|
|
||||||
}
|
|
||||||
|
@@ -35,7 +35,21 @@ func errToAPIStatus(err error) *api.Status {
|
|||||||
switch t := err.(type) {
|
switch t := err.(type) {
|
||||||
case statusError:
|
case statusError:
|
||||||
status := t.Status()
|
status := t.Status()
|
||||||
status.Status = api.StatusFailure
|
if len(status.Status) == 0 {
|
||||||
|
}
|
||||||
|
switch status.Status {
|
||||||
|
case api.StatusSuccess:
|
||||||
|
if status.Code == 0 {
|
||||||
|
status.Code = http.StatusOK
|
||||||
|
}
|
||||||
|
case "":
|
||||||
|
status.Status = api.StatusFailure
|
||||||
|
fallthrough
|
||||||
|
case api.StatusFailure:
|
||||||
|
if status.Code == 0 {
|
||||||
|
status.Code = http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
}
|
||||||
//TODO: check for invalid responses
|
//TODO: check for invalid responses
|
||||||
return &status
|
return &status
|
||||||
default:
|
default:
|
||||||
|
@@ -52,20 +52,29 @@ type RESTDeleter 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 RESTResult, error)
|
// Delete *may* return the object that was deleted, or a status object indicating additional
|
||||||
|
// information about deletion.
|
||||||
|
Delete(ctx api.Context, id string) (runtime.Object, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type RESTCreater interface {
|
type RESTCreater interface {
|
||||||
|
// New returns an empty object that can be used with Create after request data has been put into it.
|
||||||
|
// This object must be a pointer type for use with Codec.DecodeInto([]byte, runtime.Object)
|
||||||
|
New() runtime.Object
|
||||||
|
|
||||||
// Create creates a new version of a resource.
|
// Create creates a new version of a resource.
|
||||||
Create(ctx api.Context, obj runtime.Object) (<-chan RESTResult, error)
|
Create(ctx api.Context, obj runtime.Object) (runtime.Object, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type RESTUpdater interface {
|
type RESTUpdater interface {
|
||||||
|
// New returns an empty object that can be used with Update after request data has been put into it.
|
||||||
|
// This object must be a pointer type for use with Codec.DecodeInto([]byte, runtime.Object)
|
||||||
|
New() runtime.Object
|
||||||
|
|
||||||
// Update finds a resource in the storage and updates it. Some implementations
|
// 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
|
// may allow updates creates the object - they should set the created boolean
|
||||||
// the returned RESTResultto true. In the event of an asynchronous error returned
|
// to true.
|
||||||
// via an api.Status object, the Created flag is ignored.
|
Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error)
|
||||||
Update(ctx api.Context, obj runtime.Object) (<-chan RESTResult, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RESTResult indicates the result of a REST transformation.
|
// RESTResult indicates the result of a REST transformation.
|
||||||
|
@@ -18,7 +18,6 @@ package apiserver
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
|
||||||
@@ -27,73 +26,325 @@ import (
|
|||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/emicklei/go-restful"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RESTHandler implements HTTP verbs on a set of RESTful resources identified by name.
|
// ResourceNameFunc returns a name (and optional namespace) given a request - if no name is present
|
||||||
type RESTHandler struct {
|
// an error must be returned.
|
||||||
storage map[string]RESTStorage
|
type ResourceNameFunc func(req *restful.Request) (namespace, name string, err error)
|
||||||
codec runtime.Codec
|
|
||||||
canonicalPrefix string
|
// ObjectNameFunc returns the name (and optional namespace) of an object
|
||||||
selfLinker runtime.SelfLinker
|
type ObjectNameFunc func(obj runtime.Object) (namespace, name string, err error)
|
||||||
ops *Operations
|
|
||||||
admissionControl admission.Interface
|
// ResourceNamespaceFunc returns the namespace associated with the given request - if no namespace
|
||||||
apiRequestInfoResolver *APIRequestInfoResolver
|
// is present an error must be returned.
|
||||||
|
type ResourceNamespaceFunc func(req *restful.Request) (namespace string, err error)
|
||||||
|
|
||||||
|
// LinkResourceFunc updates the provided object with a SelfLink that is appropriate for the current
|
||||||
|
// request.
|
||||||
|
type LinkResourceFunc func(req *restful.Request, obj runtime.Object) error
|
||||||
|
|
||||||
|
// GetResource returns a function that handles retrieving a single resource from a RESTStorage object.
|
||||||
|
func GetResource(r RESTGetter, nameFn ResourceNameFunc, linkFn LinkResourceFunc, codec runtime.Codec) restful.RouteFunction {
|
||||||
|
return func(req *restful.Request, res *restful.Response) {
|
||||||
|
w := res.ResponseWriter
|
||||||
|
namespace, name, err := nameFn(req)
|
||||||
|
if err != nil {
|
||||||
|
notFound(w, req.Request)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx := api.NewContext()
|
||||||
|
if len(namespace) > 0 {
|
||||||
|
ctx = api.WithNamespace(ctx, namespace)
|
||||||
|
}
|
||||||
|
item, err := r.Get(ctx, name)
|
||||||
|
if err != nil {
|
||||||
|
errorJSON(err, codec, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := linkFn(req, item); err != nil {
|
||||||
|
errorJSON(err, codec, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeJSON(http.StatusOK, codec, item, w)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeHTTP handles requests to all RESTStorage objects.
|
// ListResource returns a function that handles retrieving a list of resources from a RESTStorage object.
|
||||||
func (h *RESTHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
func ListResource(r RESTLister, namespaceFn ResourceNamespaceFunc, linkFn LinkResourceFunc, codec runtime.Codec) restful.RouteFunction {
|
||||||
var verb string
|
return func(req *restful.Request, res *restful.Response) {
|
||||||
var apiResource string
|
w := res.ResponseWriter
|
||||||
var httpCode int
|
|
||||||
reqStart := time.Now()
|
|
||||||
defer func() { monitor("rest", verb, apiResource, httpCode, reqStart) }()
|
|
||||||
|
|
||||||
requestInfo, err := h.apiRequestInfoResolver.GetAPIRequestInfo(req)
|
namespace, err := namespaceFn(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Errorf("Unable to handle request %s %s %v", requestInfo.Namespace, requestInfo.Kind, err)
|
notFound(w, req.Request)
|
||||||
notFound(w, req)
|
return
|
||||||
httpCode = http.StatusNotFound
|
}
|
||||||
return
|
ctx := api.NewContext()
|
||||||
|
if len(namespace) > 0 {
|
||||||
|
ctx = api.WithNamespace(ctx, namespace)
|
||||||
|
}
|
||||||
|
label, err := labels.ParseSelector(req.Request.URL.Query().Get("labels"))
|
||||||
|
if err != nil {
|
||||||
|
errorJSON(err, codec, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
field, err := labels.ParseSelector(req.Request.URL.Query().Get("fields"))
|
||||||
|
if err != nil {
|
||||||
|
errorJSON(err, codec, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
item, err := r.List(ctx, label, field)
|
||||||
|
if err != nil {
|
||||||
|
errorJSON(err, codec, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := linkFn(req, item); err != nil {
|
||||||
|
errorJSON(err, codec, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeJSON(http.StatusOK, codec, item, w)
|
||||||
}
|
}
|
||||||
verb = requestInfo.Verb
|
|
||||||
|
|
||||||
storage, ok := h.storage[requestInfo.Resource]
|
|
||||||
if !ok {
|
|
||||||
notFound(w, req)
|
|
||||||
httpCode = http.StatusNotFound
|
|
||||||
return
|
|
||||||
}
|
|
||||||
apiResource = requestInfo.Resource
|
|
||||||
|
|
||||||
httpCode = h.handleRESTStorage(requestInfo.Parts, req, w, storage, requestInfo.Namespace, requestInfo.Resource)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets the SelfLink field of the object.
|
// CreateResource returns a function that will handle a resource creation.
|
||||||
func (h *RESTHandler) setSelfLink(obj runtime.Object, req *http.Request) error {
|
func CreateResource(r RESTCreater, namespaceFn ResourceNamespaceFunc, linkFn LinkResourceFunc, codec runtime.Codec, resource string, admit admission.Interface) restful.RouteFunction {
|
||||||
newURL := *req.URL
|
return func(req *restful.Request, res *restful.Response) {
|
||||||
newURL.Path = path.Join(h.canonicalPrefix, req.URL.Path)
|
w := res.ResponseWriter
|
||||||
newURL.RawQuery = ""
|
|
||||||
newURL.Fragment = ""
|
// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
|
||||||
namespace, err := h.selfLinker.Namespace(obj)
|
timeout := parseTimeout(req.Request.URL.Query().Get("timeout"))
|
||||||
|
|
||||||
|
namespace, err := namespaceFn(req)
|
||||||
|
if err != nil {
|
||||||
|
notFound(w, req.Request)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx := api.NewContext()
|
||||||
|
if len(namespace) > 0 {
|
||||||
|
ctx = api.WithNamespace(ctx, namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := readBody(req.Request)
|
||||||
|
if err != nil {
|
||||||
|
errorJSON(err, codec, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
obj := r.New()
|
||||||
|
if err := codec.DecodeInto(body, obj); err != nil {
|
||||||
|
errorJSON(err, codec, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = admit.Admit(admission.NewAttributesRecord(obj, namespace, resource, "CREATE"))
|
||||||
|
if err != nil {
|
||||||
|
errorJSON(err, codec, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := finishRequest(timeout, func() (runtime.Object, error) {
|
||||||
|
return r.Create(ctx, obj)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
errorJSON(err, codec, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := linkFn(req, result); err != nil {
|
||||||
|
errorJSON(err, codec, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeJSON(http.StatusCreated, codec, result, w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateResource returns a function that will handle a resource update
|
||||||
|
func UpdateResource(r RESTUpdater, nameFn ResourceNameFunc, objNameFunc ObjectNameFunc, linkFn LinkResourceFunc, codec runtime.Codec, resource string, admit admission.Interface) restful.RouteFunction {
|
||||||
|
return func(req *restful.Request, res *restful.Response) {
|
||||||
|
w := res.ResponseWriter
|
||||||
|
|
||||||
|
// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
|
||||||
|
timeout := parseTimeout(req.Request.URL.Query().Get("timeout"))
|
||||||
|
|
||||||
|
namespace, name, err := nameFn(req)
|
||||||
|
if err != nil {
|
||||||
|
notFound(w, req.Request)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx := api.NewContext()
|
||||||
|
if len(namespace) > 0 {
|
||||||
|
ctx = api.WithNamespace(ctx, namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := readBody(req.Request)
|
||||||
|
if err != nil {
|
||||||
|
errorJSON(err, codec, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
obj := r.New()
|
||||||
|
if err := codec.DecodeInto(body, obj); err != nil {
|
||||||
|
errorJSON(err, codec, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
objNamespace, objName, err := objNameFunc(obj)
|
||||||
|
if err != nil {
|
||||||
|
errorJSON(err, codec, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if objName != name {
|
||||||
|
errorJSON(errors.NewBadRequest("the name of the object does not match the name on the URL"), codec, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(namespace) > 0 {
|
||||||
|
if len(objNamespace) > 0 && objNamespace != namespace {
|
||||||
|
errorJSON(errors.NewBadRequest("the namespace of the object does not match the namespace on the request"), codec, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = admit.Admit(admission.NewAttributesRecord(obj, namespace, resource, "UPDATE"))
|
||||||
|
if err != nil {
|
||||||
|
errorJSON(err, codec, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
wasCreated := false
|
||||||
|
result, err := finishRequest(timeout, func() (runtime.Object, error) {
|
||||||
|
obj, created, err := r.Update(ctx, obj)
|
||||||
|
wasCreated = created
|
||||||
|
return obj, err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
errorJSON(err, codec, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := linkFn(req, result); err != nil {
|
||||||
|
errorJSON(err, codec, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
status := http.StatusOK
|
||||||
|
if wasCreated {
|
||||||
|
status = http.StatusCreated
|
||||||
|
}
|
||||||
|
writeJSON(status, codec, result, w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteResource returns a function that will handle a resource deletion
|
||||||
|
func DeleteResource(r RESTDeleter, nameFn ResourceNameFunc, linkFn LinkResourceFunc, codec runtime.Codec, resource, kind string, admit admission.Interface) restful.RouteFunction {
|
||||||
|
return func(req *restful.Request, res *restful.Response) {
|
||||||
|
w := res.ResponseWriter
|
||||||
|
|
||||||
|
// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
|
||||||
|
timeout := parseTimeout(req.Request.URL.Query().Get("timeout"))
|
||||||
|
|
||||||
|
namespace, name, err := nameFn(req)
|
||||||
|
if err != nil {
|
||||||
|
notFound(w, req.Request)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx := api.NewContext()
|
||||||
|
if len(namespace) > 0 {
|
||||||
|
ctx = api.WithNamespace(ctx, namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = admit.Admit(admission.NewAttributesRecord(nil, namespace, resource, "DELETE"))
|
||||||
|
if err != nil {
|
||||||
|
errorJSON(err, codec, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := finishRequest(timeout, func() (runtime.Object, error) {
|
||||||
|
return r.Delete(ctx, name)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
errorJSON(err, codec, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the RESTDeleter returns a nil object, fill out a status. Callers may return a valid
|
||||||
|
// object with the response.
|
||||||
|
if result == nil {
|
||||||
|
result = &api.Status{
|
||||||
|
Status: api.StatusSuccess,
|
||||||
|
Code: http.StatusOK,
|
||||||
|
Details: &api.StatusDetails{
|
||||||
|
ID: name,
|
||||||
|
Kind: kind,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// when a non-status response is returned, set the self link
|
||||||
|
if _, ok := result.(*api.Status); !ok {
|
||||||
|
if err := linkFn(req, result); err != nil {
|
||||||
|
errorJSON(err, codec, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeJSON(http.StatusOK, codec, result, w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// resultFunc is a function that returns a rest result and can be run in a goroutine
|
||||||
|
type resultFunc func() (runtime.Object, error)
|
||||||
|
|
||||||
|
// finishRequest makes a given resultFunc asynchronous and handles errors returned by the response.
|
||||||
|
// Any api.Status object returned is considered an "error", which interrupts the normal response flow.
|
||||||
|
func finishRequest(timeout time.Duration, fn resultFunc) (result runtime.Object, err error) {
|
||||||
|
ch := make(chan runtime.Object)
|
||||||
|
errCh := make(chan error)
|
||||||
|
go func() {
|
||||||
|
if result, err := fn(); err != nil {
|
||||||
|
errCh <- err
|
||||||
|
} else {
|
||||||
|
ch <- result
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case result = <-ch:
|
||||||
|
if status, ok := result.(*api.Status); ok {
|
||||||
|
return nil, errors.FromObject(status)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
case err = <-errCh:
|
||||||
|
return nil, err
|
||||||
|
case <-time.After(timeout):
|
||||||
|
return nil, errors.NewTimeoutError("request did not complete within allowed duration")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type linkFunc func(namespace, name string) (path string, query string)
|
||||||
|
|
||||||
|
// setSelfLink sets the self link of an object (or the child items in a list) to the base URL of the request
|
||||||
|
// plus the path and query generated by the provided linkFunc
|
||||||
|
func setSelfLink(obj runtime.Object, req *http.Request, linker runtime.SelfLinker, fn linkFunc) error {
|
||||||
|
namespace, err := linker.Namespace(obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
name, err := linker.Name(obj)
|
||||||
// we need to add namespace as a query param, if its not in the resource path
|
|
||||||
if len(namespace) > 0 {
|
|
||||||
parts := splitPath(req.URL.Path)
|
|
||||||
if parts[0] != "ns" {
|
|
||||||
query := newURL.Query()
|
|
||||||
query.Set("namespace", namespace)
|
|
||||||
newURL.RawQuery = query.Encode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = h.selfLinker.SetSelfLink(obj, newURL.String())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
path, query := fn(namespace, name)
|
||||||
|
|
||||||
|
newURL := *req.URL
|
||||||
|
newURL.Path = path
|
||||||
|
newURL.RawQuery = query
|
||||||
|
newURL.Fragment = ""
|
||||||
|
|
||||||
|
if err := linker.SetSelfLink(obj, newURL.String()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if !runtime.IsListType(obj) {
|
if !runtime.IsListType(obj) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -104,231 +355,9 @@ func (h *RESTHandler) setSelfLink(obj runtime.Object, req *http.Request) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for i := range items {
|
for i := range items {
|
||||||
if err := h.setSelfLinkAddName(items[i], req); err != nil {
|
if err := setSelfLink(items[i], req, linker, fn); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return runtime.SetList(obj, items)
|
return runtime.SetList(obj, items)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Like setSelfLink, but appends the object's name.
|
|
||||||
func (h *RESTHandler) setSelfLinkAddName(obj runtime.Object, req *http.Request) error {
|
|
||||||
name, err := h.selfLinker.Name(obj)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
namespace, err := h.selfLinker.Namespace(obj)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
newURL := *req.URL
|
|
||||||
newURL.Path = path.Join(h.canonicalPrefix, req.URL.Path, name)
|
|
||||||
newURL.RawQuery = ""
|
|
||||||
newURL.Fragment = ""
|
|
||||||
// we need to add namespace as a query param, if its not in the resource path
|
|
||||||
if len(namespace) > 0 {
|
|
||||||
parts := splitPath(req.URL.Path)
|
|
||||||
if parts[0] != "ns" {
|
|
||||||
query := newURL.Query()
|
|
||||||
query.Set("namespace", namespace)
|
|
||||||
newURL.RawQuery = query.Encode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return h.selfLinker.SetSelfLink(obj, newURL.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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(RESTResult) {
|
|
||||||
return func(obj RESTResult) {
|
|
||||||
if err := f(obj.Object, req); err != nil {
|
|
||||||
glog.Errorf("unable to set self link for %#v: %v", obj, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleRESTStorage is the main dispatcher for a storage object. It switches on the HTTP method, and then
|
|
||||||
// on path length, according to the following table:
|
|
||||||
// Method Path Action
|
|
||||||
// GET /foo list
|
|
||||||
// GET /foo/bar get 'bar'
|
|
||||||
// POST /foo create
|
|
||||||
// PUT /foo/bar update 'bar'
|
|
||||||
// DELETE /foo/bar delete 'bar'
|
|
||||||
// Responds with a 404 if the method/pattern doesn't match one of these entries.
|
|
||||||
// The s accepts several query parameters:
|
|
||||||
// timeout=<duration> Timeout for synchronous requests
|
|
||||||
// labels=<label-selector> Used for filtering list operations
|
|
||||||
// Returns the HTTP status code written to the response.
|
|
||||||
func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w http.ResponseWriter, storage RESTStorage, namespace, kind string) int {
|
|
||||||
ctx := api.WithNamespace(api.NewContext(), namespace)
|
|
||||||
// TODO: Document the timeout query parameter.
|
|
||||||
timeout := parseTimeout(req.URL.Query().Get("timeout"))
|
|
||||||
switch req.Method {
|
|
||||||
case "GET":
|
|
||||||
switch len(parts) {
|
|
||||||
case 1:
|
|
||||||
label, err := labels.ParseSelector(req.URL.Query().Get("labels"))
|
|
||||||
if err != nil {
|
|
||||||
return errorJSON(err, h.codec, w)
|
|
||||||
}
|
|
||||||
field, err := labels.ParseSelector(req.URL.Query().Get("fields"))
|
|
||||||
if err != nil {
|
|
||||||
return errorJSON(err, h.codec, w)
|
|
||||||
}
|
|
||||||
lister, ok := storage.(RESTLister)
|
|
||||||
if !ok {
|
|
||||||
return errorJSON(errors.NewMethodNotSupported(kind, "list"), h.codec, w)
|
|
||||||
}
|
|
||||||
list, err := lister.List(ctx, label, field)
|
|
||||||
if err != nil {
|
|
||||||
return errorJSON(err, h.codec, w)
|
|
||||||
}
|
|
||||||
if err := h.setSelfLink(list, req); err != nil {
|
|
||||||
return errorJSON(err, h.codec, w)
|
|
||||||
}
|
|
||||||
writeJSON(http.StatusOK, h.codec, list, w)
|
|
||||||
case 2:
|
|
||||||
getter, ok := storage.(RESTGetter)
|
|
||||||
if !ok {
|
|
||||||
return errorJSON(errors.NewMethodNotSupported(kind, "get"), h.codec, w)
|
|
||||||
}
|
|
||||||
item, err := getter.Get(ctx, parts[1])
|
|
||||||
if err != nil {
|
|
||||||
return errorJSON(err, h.codec, w)
|
|
||||||
}
|
|
||||||
if err := h.setSelfLink(item, req); err != nil {
|
|
||||||
return errorJSON(err, h.codec, w)
|
|
||||||
}
|
|
||||||
writeJSON(http.StatusOK, h.codec, item, w)
|
|
||||||
default:
|
|
||||||
notFound(w, req)
|
|
||||||
return http.StatusNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
case "POST":
|
|
||||||
if len(parts) != 1 {
|
|
||||||
notFound(w, req)
|
|
||||||
return http.StatusNotFound
|
|
||||||
}
|
|
||||||
creater, ok := storage.(RESTCreater)
|
|
||||||
if !ok {
|
|
||||||
return errorJSON(errors.NewMethodNotSupported(kind, "create"), h.codec, w)
|
|
||||||
}
|
|
||||||
|
|
||||||
body, err := readBody(req)
|
|
||||||
if err != nil {
|
|
||||||
return errorJSON(err, h.codec, w)
|
|
||||||
}
|
|
||||||
obj := storage.New()
|
|
||||||
err = h.codec.DecodeInto(body, obj)
|
|
||||||
if err != nil {
|
|
||||||
return errorJSON(err, h.codec, w)
|
|
||||||
}
|
|
||||||
|
|
||||||
// invoke admission control
|
|
||||||
err = h.admissionControl.Admit(admission.NewAttributesRecord(obj, namespace, parts[0], "CREATE"))
|
|
||||||
if err != nil {
|
|
||||||
return errorJSON(err, h.codec, w)
|
|
||||||
}
|
|
||||||
|
|
||||||
out, err := creater.Create(ctx, obj)
|
|
||||||
if err != nil {
|
|
||||||
return errorJSON(err, h.codec, w)
|
|
||||||
}
|
|
||||||
op := h.createOperation(out, timeout, curry(h.setSelfLinkAddName, req))
|
|
||||||
return h.finishReq(op, req, w)
|
|
||||||
|
|
||||||
case "DELETE":
|
|
||||||
if len(parts) != 2 {
|
|
||||||
notFound(w, req)
|
|
||||||
return http.StatusNotFound
|
|
||||||
}
|
|
||||||
deleter, ok := storage.(RESTDeleter)
|
|
||||||
if !ok {
|
|
||||||
return errorJSON(errors.NewMethodNotSupported(kind, "delete"), h.codec, w)
|
|
||||||
}
|
|
||||||
|
|
||||||
// invoke admission control
|
|
||||||
err := h.admissionControl.Admit(admission.NewAttributesRecord(nil, namespace, parts[0], "DELETE"))
|
|
||||||
if err != nil {
|
|
||||||
return errorJSON(err, h.codec, w)
|
|
||||||
}
|
|
||||||
|
|
||||||
out, err := deleter.Delete(ctx, parts[1])
|
|
||||||
if err != nil {
|
|
||||||
return errorJSON(err, h.codec, w)
|
|
||||||
}
|
|
||||||
op := h.createOperation(out, timeout, nil)
|
|
||||||
return h.finishReq(op, req, w)
|
|
||||||
|
|
||||||
case "PUT":
|
|
||||||
if len(parts) != 2 {
|
|
||||||
notFound(w, req)
|
|
||||||
return http.StatusNotFound
|
|
||||||
}
|
|
||||||
updater, ok := storage.(RESTUpdater)
|
|
||||||
if !ok {
|
|
||||||
return errorJSON(errors.NewMethodNotSupported(kind, "create"), h.codec, w)
|
|
||||||
}
|
|
||||||
|
|
||||||
body, err := readBody(req)
|
|
||||||
if err != nil {
|
|
||||||
return errorJSON(err, h.codec, w)
|
|
||||||
}
|
|
||||||
obj := storage.New()
|
|
||||||
err = h.codec.DecodeInto(body, obj)
|
|
||||||
if err != nil {
|
|
||||||
return errorJSON(err, h.codec, w)
|
|
||||||
}
|
|
||||||
|
|
||||||
// invoke admission control
|
|
||||||
err = h.admissionControl.Admit(admission.NewAttributesRecord(obj, namespace, parts[0], "UPDATE"))
|
|
||||||
if err != nil {
|
|
||||||
return errorJSON(err, h.codec, w)
|
|
||||||
}
|
|
||||||
|
|
||||||
out, err := updater.Update(ctx, obj)
|
|
||||||
if err != nil {
|
|
||||||
return errorJSON(err, h.codec, w)
|
|
||||||
}
|
|
||||||
op := h.createOperation(out, timeout, curry(h.setSelfLink, req))
|
|
||||||
return h.finishReq(op, req, w)
|
|
||||||
|
|
||||||
default:
|
|
||||||
notFound(w, req)
|
|
||||||
return http.StatusNotFound
|
|
||||||
}
|
|
||||||
return http.StatusOK
|
|
||||||
}
|
|
||||||
|
|
||||||
// createOperation creates an operation to process a channel response.
|
|
||||||
func (h *RESTHandler) createOperation(out <-chan RESTResult, timeout time.Duration, onReceive func(RESTResult)) *Operation {
|
|
||||||
op := h.ops.NewOperation(out, onReceive)
|
|
||||||
op.WaitFor(timeout)
|
|
||||||
return op
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
// Returns the HTTP status code written to the response.
|
|
||||||
func (h *RESTHandler) finishReq(op *Operation, req *http.Request, w http.ResponseWriter) int {
|
|
||||||
result, complete := op.StatusOrResult()
|
|
||||||
obj := result.Object
|
|
||||||
var status int
|
|
||||||
if complete {
|
|
||||||
status = http.StatusOK
|
|
||||||
if result.Created {
|
|
||||||
status = http.StatusCreated
|
|
||||||
}
|
|
||||||
switch stat := obj.(type) {
|
|
||||||
case *api.Status:
|
|
||||||
if stat.Code != 0 {
|
|
||||||
status = stat.Code
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
status = http.StatusAccepted
|
|
||||||
}
|
|
||||||
writeJSON(status, h.codec, obj, w)
|
|
||||||
return status
|
|
||||||
}
|
|
||||||
|
@@ -1,69 +0,0 @@
|
|||||||
/*
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -37,24 +37,24 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type WatchHandler struct {
|
type WatchHandler struct {
|
||||||
storage map[string]RESTStorage
|
storage map[string]RESTStorage
|
||||||
codec runtime.Codec
|
codec runtime.Codec
|
||||||
canonicalPrefix string
|
prefix string
|
||||||
selfLinker runtime.SelfLinker
|
linker runtime.SelfLinker
|
||||||
apiRequestInfoResolver *APIRequestInfoResolver
|
info *APIRequestInfoResolver
|
||||||
}
|
}
|
||||||
|
|
||||||
// setSelfLinkAddName sets the self link, appending the object's name to the canonical path & type.
|
// setSelfLinkAddName sets the self link, appending the object's name to the canonical path & type.
|
||||||
func (h *WatchHandler) setSelfLinkAddName(obj runtime.Object, req *http.Request) error {
|
func (h *WatchHandler) setSelfLinkAddName(obj runtime.Object, req *http.Request) error {
|
||||||
name, err := h.selfLinker.Name(obj)
|
name, err := h.linker.Name(obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
newURL := *req.URL
|
newURL := *req.URL
|
||||||
newURL.Path = path.Join(h.canonicalPrefix, req.URL.Path, name)
|
newURL.Path = path.Join(h.prefix, req.URL.Path, name)
|
||||||
newURL.RawQuery = ""
|
newURL.RawQuery = ""
|
||||||
newURL.Fragment = ""
|
newURL.Fragment = ""
|
||||||
return h.selfLinker.SetSelfLink(obj, newURL.String())
|
return h.linker.SetSelfLink(obj, newURL.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func getWatchParams(query url.Values) (label, field labels.Selector, resourceVersion string, err error) {
|
func getWatchParams(query url.Values) (label, field labels.Selector, resourceVersion string, err error) {
|
||||||
@@ -96,7 +96,7 @@ func (h *WatchHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
requestInfo, err := h.apiRequestInfoResolver.GetAPIRequestInfo(req)
|
requestInfo, err := h.info.GetAPIRequestInfo(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
notFound(w, req)
|
notFound(w, req)
|
||||||
httpCode = http.StatusNotFound
|
httpCode = http.StatusNotFound
|
||||||
|
@@ -17,7 +17,6 @@ limitations under the License.
|
|||||||
package master
|
package master
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
@@ -92,15 +91,8 @@ func (m *Master) createMasterNamespaceIfNeeded(ns string) error {
|
|||||||
Namespace: "",
|
Namespace: "",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
c, err := m.storage["namespaces"].(apiserver.RESTCreater).Create(ctx, namespace)
|
_, err := m.storage["namespaces"].(apiserver.RESTCreater).Create(ctx, namespace)
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
resp := <-c
|
|
||||||
if _, ok := resp.Object.(*api.Service); ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("unexpected response %#v", resp)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// createMasterServiceIfNeeded will create the specified service if it
|
// createMasterServiceIfNeeded will create the specified service if it
|
||||||
@@ -126,18 +118,8 @@ func (m *Master) createMasterServiceIfNeeded(serviceName string, serviceIP net.I
|
|||||||
SessionAffinity: api.AffinityTypeNone,
|
SessionAffinity: api.AffinityTypeNone,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
// Kids, don't do this at home: this is a hack. There's no good way to call the business
|
_, err := m.storage["services"].(apiserver.RESTCreater).Create(ctx, svc)
|
||||||
// logic which lives in the REST object from here.
|
return err
|
||||||
c, err := m.storage["services"].(apiserver.RESTCreater).Create(ctx, svc)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resp := <-c
|
|
||||||
if _, ok := resp.Object.(*api.Service); ok {
|
|
||||||
// If all worked, we get back an *api.Service object.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("unexpected response: %#v", resp.Object)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensureEndpointsContain sets the endpoints for the given service. Also removes
|
// ensureEndpointsContain sets the endpoints for the given service. Also removes
|
||||||
|
@@ -18,9 +18,9 @@ package binding
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -44,15 +44,13 @@ 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 apiserver.RESTResult, error) {
|
func (b *REST) Create(ctx api.Context, obj runtime.Object) (runtime.Object, 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)
|
||||||
}
|
}
|
||||||
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
if err := b.registry.ApplyBinding(ctx, binding); err != nil {
|
||||||
if err := b.registry.ApplyBinding(ctx, binding); err != nil {
|
return nil, err
|
||||||
return nil, err
|
}
|
||||||
}
|
return &api.Status{Status: api.StatusSuccess, Code: http.StatusCreated}, nil
|
||||||
return &api.Status{Status: api.StatusSuccess}, nil
|
|
||||||
}), nil
|
|
||||||
}
|
}
|
||||||
|
@@ -71,22 +71,20 @@ func TestRESTPost(t *testing.T) {
|
|||||||
}
|
}
|
||||||
ctx := api.NewContext()
|
ctx := api.NewContext()
|
||||||
b := NewREST(mockRegistry)
|
b := NewREST(mockRegistry)
|
||||||
resultChan, err := b.Create(ctx, item.b)
|
result, err := b.Create(ctx, item.b)
|
||||||
if err != nil {
|
if err != nil && item.err == nil {
|
||||||
t.Errorf("Unexpected error %v", err)
|
t.Errorf("Unexpected error %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var expect *api.Status
|
if err == nil && item.err != nil {
|
||||||
if item.err == nil {
|
t.Errorf("Unexpected error %v", err)
|
||||||
expect = &api.Status{Status: api.StatusSuccess}
|
continue
|
||||||
} else {
|
|
||||||
expect = &api.Status{
|
|
||||||
Status: api.StatusFailure,
|
|
||||||
Code: http.StatusInternalServerError,
|
|
||||||
Message: item.err.Error(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if e, a := expect, (<-resultChan).Object; !reflect.DeepEqual(e, a) {
|
var expect interface{}
|
||||||
|
if item.err == nil {
|
||||||
|
expect = &api.Status{Status: api.StatusSuccess, Code: http.StatusCreated}
|
||||||
|
}
|
||||||
|
if e, a := expect, result; !reflect.DeepEqual(e, a) {
|
||||||
t.Errorf("%v: expected %#v, got %#v", i, e, a)
|
t.Errorf("%v: expected %#v, got %#v", i, e, a)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -50,7 +50,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 apiserver.RESTResult, error) {
|
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (runtime.Object, 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)
|
||||||
@@ -60,20 +60,16 @@ func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan apiserver.RE
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
if err := rs.registry.CreateController(ctx, controller); err != nil {
|
||||||
if err := rs.registry.CreateController(ctx, controller); err != nil {
|
err = rest.CheckGeneratedNameError(rest.ReplicationControllers, err, controller)
|
||||||
err = rest.CheckGeneratedNameError(rest.ReplicationControllers, err, controller)
|
return apiserver.RESTResult{}, err
|
||||||
return apiserver.RESTResult{}, err
|
}
|
||||||
}
|
return rs.registry.GetController(ctx, controller.Name)
|
||||||
return rs.registry.GetController(ctx, controller.Name)
|
|
||||||
}), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 apiserver.RESTResult, error) {
|
func (rs *REST) Delete(ctx api.Context, id string) (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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get obtains the ReplicationController specified by its id.
|
// Get obtains the ReplicationController specified by its id.
|
||||||
@@ -117,24 +113,23 @@ func (*REST) NewList() 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 apiserver.RESTResult, error) {
|
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, 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, false, fmt.Errorf("not a replication controller: %#v", obj)
|
||||||
}
|
}
|
||||||
if !api.ValidNamespace(ctx, &controller.ObjectMeta) {
|
if !api.ValidNamespace(ctx, &controller.ObjectMeta) {
|
||||||
return nil, errors.NewConflict("controller", controller.Namespace, fmt.Errorf("Controller.Namespace does not match the provided context"))
|
return nil, false, errors.NewConflict("controller", controller.Namespace, fmt.Errorf("Controller.Namespace does not match the provided context"))
|
||||||
}
|
}
|
||||||
if errs := validation.ValidateReplicationController(controller); len(errs) > 0 {
|
if errs := validation.ValidateReplicationController(controller); len(errs) > 0 {
|
||||||
return nil, errors.NewInvalid("replicationController", controller.Name, errs)
|
return nil, false, errors.NewInvalid("replicationController", controller.Name, errs)
|
||||||
}
|
}
|
||||||
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
err := rs.registry.UpdateController(ctx, controller)
|
||||||
err := rs.registry.UpdateController(ctx, controller)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, false, err
|
||||||
return nil, err
|
}
|
||||||
}
|
out, err := rs.registry.GetController(ctx, controller.Name)
|
||||||
return rs.registry.GetController(ctx, controller.Name)
|
return out, false, err
|
||||||
}), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Watch returns ReplicationController events via a watch.Interface.
|
// Watch returns ReplicationController events via a watch.Interface.
|
||||||
|
@@ -22,7 +22,6 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"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"
|
||||||
@@ -268,23 +267,17 @@ func TestCreateController(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
ctx := api.NewDefaultContext()
|
ctx := api.NewDefaultContext()
|
||||||
channel, err := storage.Create(ctx, controller)
|
obj, err := storage.Create(ctx, controller)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if obj == nil {
|
||||||
t.Errorf("unexpected error: %v", err)
|
t.Errorf("unexpected object")
|
||||||
}
|
}
|
||||||
if !api.HasObjectMetaSystemFieldValues(&controller.ObjectMeta) {
|
if !api.HasObjectMetaSystemFieldValues(&controller.ObjectMeta) {
|
||||||
t.Errorf("storage did not populate object meta field values")
|
t.Errorf("storage did not populate object meta field values")
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
|
||||||
case <-channel:
|
|
||||||
// expected case
|
|
||||||
case <-time.After(time.Millisecond * 100):
|
|
||||||
t.Error("Unexpected timeout from async channel")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: remove, covered by TestCreate
|
// TODO: remove, covered by TestCreate
|
||||||
@@ -338,9 +331,9 @@ func TestControllerStorageValidatesUpdate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
ctx := api.NewDefaultContext()
|
ctx := api.NewDefaultContext()
|
||||||
for _, failureCase := range failureCases {
|
for _, failureCase := range failureCases {
|
||||||
c, err := storage.Update(ctx, &failureCase)
|
c, created, err := storage.Update(ctx, &failureCase)
|
||||||
if c != nil {
|
if c != nil || created {
|
||||||
t.Errorf("Expected nil channel")
|
t.Errorf("Expected nil object and not created")
|
||||||
}
|
}
|
||||||
if !errors.IsInvalid(err) {
|
if !errors.IsInvalid(err) {
|
||||||
t.Errorf("Expected to get an invalid resource error, got %v", err)
|
t.Errorf("Expected to get an invalid resource error, got %v", err)
|
||||||
@@ -441,9 +434,9 @@ func TestUpdateControllerWithConflictingNamespace(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx := api.NewDefaultContext()
|
ctx := api.NewDefaultContext()
|
||||||
channel, err := storage.Update(ctx, controller)
|
obj, created, err := storage.Update(ctx, controller)
|
||||||
if channel != nil {
|
if obj != nil || created {
|
||||||
t.Error("Expected a nil channel, but we got a value")
|
t.Error("Expected a nil object, but we got a value or created was true")
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("Expected an error, but we didn't get one")
|
t.Errorf("Expected an error, but we didn't get one")
|
||||||
|
@@ -21,7 +21,6 @@ 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/apiserver"
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
||||||
@@ -59,7 +58,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 apiserver.RESTResult, error) {
|
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (runtime.Object, 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)
|
||||||
@@ -72,28 +71,25 @@ func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan apiserver.RE
|
|||||||
}
|
}
|
||||||
api.FillObjectMetaSystemFields(ctx, &endpoints.ObjectMeta)
|
api.FillObjectMetaSystemFields(ctx, &endpoints.ObjectMeta)
|
||||||
|
|
||||||
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
err := rs.registry.UpdateEndpoints(ctx, endpoints)
|
||||||
err := rs.registry.UpdateEndpoints(ctx, endpoints)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, err
|
||||||
return nil, err
|
}
|
||||||
}
|
return rs.registry.GetEndpoints(ctx, endpoints.Name)
|
||||||
return rs.registry.GetEndpoints(ctx, endpoints.Name)
|
|
||||||
}), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update satisfies the RESTStorage interface.
|
// Update satisfies the RESTStorage interface.
|
||||||
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
|
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, 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, false, fmt.Errorf("not an endpoints: %#v", obj)
|
||||||
}
|
}
|
||||||
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
err := rs.registry.UpdateEndpoints(ctx, endpoints)
|
||||||
err := rs.registry.UpdateEndpoints(ctx, endpoints)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, false, err
|
||||||
return nil, err
|
}
|
||||||
}
|
out, err := rs.registry.GetEndpoints(ctx, endpoints.Name)
|
||||||
return rs.registry.GetEndpoints(ctx, endpoints.Name)
|
return out, false, err
|
||||||
}), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New implements the RESTStorage interface.
|
// New implements the RESTStorage interface.
|
||||||
|
@@ -22,7 +22,6 @@ 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/validation"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
@@ -42,7 +41,7 @@ func NewREST(registry generic.Registry) *REST {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
|
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (runtime.Object, 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,41 +56,38 @@ func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan apiserver.RE
|
|||||||
}
|
}
|
||||||
api.FillObjectMetaSystemFields(ctx, &event.ObjectMeta)
|
api.FillObjectMetaSystemFields(ctx, &event.ObjectMeta)
|
||||||
|
|
||||||
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
err := rs.registry.Create(ctx, event.Name, event)
|
||||||
err := rs.registry.Create(ctx, event.Name, event)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, err
|
||||||
return nil, err
|
}
|
||||||
}
|
return rs.registry.Get(ctx, event.Name)
|
||||||
return rs.registry.Get(ctx, event.Name)
|
|
||||||
}), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update replaces an existing Event instance in storage.registry, with the given instance.
|
// Update replaces an existing Event instance in storage.registry, with the given instance.
|
||||||
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
|
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
|
||||||
event, ok := obj.(*api.Event)
|
event, ok := obj.(*api.Event)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("not an event object: %#v", obj)
|
return nil, false, fmt.Errorf("not an event object: %#v", obj)
|
||||||
}
|
}
|
||||||
if api.NamespaceValue(ctx) != "" {
|
if api.NamespaceValue(ctx) != "" {
|
||||||
if !api.ValidNamespace(ctx, &event.ObjectMeta) {
|
if !api.ValidNamespace(ctx, &event.ObjectMeta) {
|
||||||
return nil, errors.NewConflict("event", event.Namespace, fmt.Errorf("event.namespace does not match the provided context"))
|
return nil, false, errors.NewConflict("event", event.Namespace, fmt.Errorf("event.namespace does not match the provided context"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if errs := validation.ValidateEvent(event); len(errs) > 0 {
|
if errs := validation.ValidateEvent(event); len(errs) > 0 {
|
||||||
return nil, errors.NewInvalid("event", event.Name, errs)
|
return nil, false, errors.NewInvalid("event", event.Name, errs)
|
||||||
}
|
}
|
||||||
api.FillObjectMetaSystemFields(ctx, &event.ObjectMeta)
|
api.FillObjectMetaSystemFields(ctx, &event.ObjectMeta)
|
||||||
|
|
||||||
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
err := rs.registry.Update(ctx, event.Name, event)
|
||||||
err := rs.registry.Update(ctx, event.Name, event)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, false, err
|
||||||
return nil, err
|
}
|
||||||
}
|
out, err := rs.registry.Get(ctx, event.Name)
|
||||||
return rs.registry.Get(ctx, event.Name)
|
return out, false, err
|
||||||
}), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *REST) Delete(ctx api.Context, id string) (<-chan apiserver.RESTResult, error) {
|
func (rs *REST) Delete(ctx api.Context, id string) (runtime.Object, 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
|
||||||
@@ -100,9 +96,7 @@ func (rs *REST) Delete(ctx api.Context, id string) (<-chan apiserver.RESTResult,
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("invalid object type")
|
return nil, fmt.Errorf("invalid object type")
|
||||||
}
|
}
|
||||||
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
return &api.Status{Status: api.StatusSuccess}, rs.registry.Delete(ctx, id)
|
||||||
return &api.Status{Status: api.StatusSuccess}, rs.registry.Delete(ctx, id)
|
|
||||||
}), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *REST) Get(ctx api.Context, id string) (runtime.Object, error) {
|
func (rs *REST) Get(ctx api.Context, id string) (runtime.Object, error) {
|
||||||
|
@@ -89,7 +89,7 @@ func TestRESTCreate(t *testing.T) {
|
|||||||
if !api.HasObjectMetaSystemFieldValues(&item.event.ObjectMeta) {
|
if !api.HasObjectMetaSystemFieldValues(&item.event.ObjectMeta) {
|
||||||
t.Errorf("storage did not populate object meta field values")
|
t.Errorf("storage did not populate object meta field values")
|
||||||
}
|
}
|
||||||
if e, a := item.event, (<-c).Object; !reflect.DeepEqual(e, a) {
|
if e, a := item.event, c; !reflect.DeepEqual(e, a) {
|
||||||
t.Errorf("diff: %s", util.ObjectDiff(e, a))
|
t.Errorf("diff: %s", util.ObjectDiff(e, a))
|
||||||
}
|
}
|
||||||
// Ensure we implement the interface
|
// Ensure we implement the interface
|
||||||
@@ -100,11 +100,10 @@ func TestRESTCreate(t *testing.T) {
|
|||||||
func TestRESTUpdate(t *testing.T) {
|
func TestRESTUpdate(t *testing.T) {
|
||||||
_, rest := NewTestREST()
|
_, rest := NewTestREST()
|
||||||
eventA := testEvent("foo")
|
eventA := testEvent("foo")
|
||||||
c, err := rest.Create(api.NewDefaultContext(), eventA)
|
_, err := rest.Create(api.NewDefaultContext(), eventA)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error %v", err)
|
t.Fatalf("Unexpected error %v", err)
|
||||||
}
|
}
|
||||||
<-c
|
|
||||||
got, err := rest.Get(api.NewDefaultContext(), eventA.Name)
|
got, err := rest.Get(api.NewDefaultContext(), eventA.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error %v", err)
|
t.Fatalf("Unexpected error %v", err)
|
||||||
@@ -113,11 +112,10 @@ func TestRESTUpdate(t *testing.T) {
|
|||||||
t.Errorf("diff: %s", util.ObjectDiff(e, a))
|
t.Errorf("diff: %s", util.ObjectDiff(e, a))
|
||||||
}
|
}
|
||||||
eventB := testEvent("bar")
|
eventB := testEvent("bar")
|
||||||
u, err := rest.Update(api.NewDefaultContext(), eventB)
|
_, _, err = rest.Update(api.NewDefaultContext(), eventB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error %v", err)
|
t.Fatalf("Unexpected error %v", err)
|
||||||
}
|
}
|
||||||
<-u
|
|
||||||
got2, err := rest.Get(api.NewDefaultContext(), eventB.Name)
|
got2, err := rest.Get(api.NewDefaultContext(), eventB.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error %v", err)
|
t.Fatalf("Unexpected error %v", err)
|
||||||
@@ -131,16 +129,15 @@ func TestRESTUpdate(t *testing.T) {
|
|||||||
func TestRESTDelete(t *testing.T) {
|
func TestRESTDelete(t *testing.T) {
|
||||||
_, rest := NewTestREST()
|
_, rest := NewTestREST()
|
||||||
eventA := testEvent("foo")
|
eventA := testEvent("foo")
|
||||||
c, err := rest.Create(api.NewDefaultContext(), eventA)
|
_, err := rest.Create(api.NewDefaultContext(), eventA)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error %v", err)
|
t.Fatalf("Unexpected error %v", err)
|
||||||
}
|
}
|
||||||
<-c
|
c, err := rest.Delete(api.NewDefaultContext(), eventA.Name)
|
||||||
c, err = rest.Delete(api.NewDefaultContext(), eventA.Name)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error %v", err)
|
t.Fatalf("Unexpected error %v", err)
|
||||||
}
|
}
|
||||||
if stat := (<-c).Object.(*api.Status); stat.Status != api.StatusSuccess {
|
if stat := c.(*api.Status); stat.Status != api.StatusSuccess {
|
||||||
t.Errorf("unexpected status: %v", stat)
|
t.Errorf("unexpected status: %v", stat)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -148,11 +145,10 @@ func TestRESTDelete(t *testing.T) {
|
|||||||
func TestRESTGet(t *testing.T) {
|
func TestRESTGet(t *testing.T) {
|
||||||
_, rest := NewTestREST()
|
_, rest := NewTestREST()
|
||||||
eventA := testEvent("foo")
|
eventA := testEvent("foo")
|
||||||
c, err := rest.Create(api.NewDefaultContext(), eventA)
|
_, err := rest.Create(api.NewDefaultContext(), eventA)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error %v", err)
|
t.Fatalf("Unexpected error %v", err)
|
||||||
}
|
}
|
||||||
<-c
|
|
||||||
got, err := rest.Get(api.NewDefaultContext(), eventA.Name)
|
got, err := rest.Get(api.NewDefaultContext(), eventA.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error %v", err)
|
t.Fatalf("Unexpected error %v", err)
|
||||||
|
@@ -22,7 +22,6 @@ 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/validation"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
@@ -44,7 +43,7 @@ func NewREST(registry generic.Registry) *REST {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a LimitRange object
|
// Create a LimitRange object
|
||||||
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
|
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) {
|
||||||
limitRange, ok := obj.(*api.LimitRange)
|
limitRange, ok := obj.(*api.LimitRange)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("invalid object type")
|
return nil, fmt.Errorf("invalid object type")
|
||||||
@@ -63,29 +62,27 @@ func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan apiserver.RE
|
|||||||
}
|
}
|
||||||
api.FillObjectMetaSystemFields(ctx, &limitRange.ObjectMeta)
|
api.FillObjectMetaSystemFields(ctx, &limitRange.ObjectMeta)
|
||||||
|
|
||||||
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
err := rs.registry.Create(ctx, limitRange.Name, limitRange)
|
||||||
err := rs.registry.Create(ctx, limitRange.Name, limitRange)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, err
|
||||||
return nil, err
|
}
|
||||||
}
|
return rs.registry.Get(ctx, limitRange.Name)
|
||||||
return rs.registry.Get(ctx, limitRange.Name)
|
|
||||||
}), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update updates a LimitRange object.
|
// Update updates a LimitRange object.
|
||||||
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
|
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
|
||||||
limitRange, ok := obj.(*api.LimitRange)
|
limitRange, ok := obj.(*api.LimitRange)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("invalid object type")
|
return nil, false, fmt.Errorf("invalid object type")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !api.ValidNamespace(ctx, &limitRange.ObjectMeta) {
|
if !api.ValidNamespace(ctx, &limitRange.ObjectMeta) {
|
||||||
return nil, errors.NewConflict("limitRange", limitRange.Namespace, fmt.Errorf("LimitRange.Namespace does not match the provided context"))
|
return nil, false, errors.NewConflict("limitRange", limitRange.Namespace, fmt.Errorf("LimitRange.Namespace does not match the provided context"))
|
||||||
}
|
}
|
||||||
|
|
||||||
oldObj, err := rs.registry.Get(ctx, limitRange.Name)
|
oldObj, err := rs.registry.Get(ctx, limitRange.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
editLimitRange := oldObj.(*api.LimitRange)
|
editLimitRange := oldObj.(*api.LimitRange)
|
||||||
@@ -97,20 +94,18 @@ func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan apiserver.RE
|
|||||||
editLimitRange.Spec = limitRange.Spec
|
editLimitRange.Spec = limitRange.Spec
|
||||||
|
|
||||||
if errs := validation.ValidateLimitRange(editLimitRange); len(errs) > 0 {
|
if errs := validation.ValidateLimitRange(editLimitRange); len(errs) > 0 {
|
||||||
return nil, errors.NewInvalid("limitRange", editLimitRange.Name, errs)
|
return nil, false, errors.NewInvalid("limitRange", editLimitRange.Name, errs)
|
||||||
}
|
}
|
||||||
|
|
||||||
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
if err := rs.registry.Update(ctx, editLimitRange.Name, editLimitRange); err != nil {
|
||||||
err := rs.registry.Update(ctx, editLimitRange.Name, editLimitRange)
|
return nil, false, err
|
||||||
if err != nil {
|
}
|
||||||
return nil, err
|
out, err := rs.registry.Get(ctx, editLimitRange.Name)
|
||||||
}
|
return out, false, err
|
||||||
return rs.registry.Get(ctx, editLimitRange.Name)
|
|
||||||
}), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete deletes the LimitRange with the specified name
|
// Delete deletes the LimitRange with the specified name
|
||||||
func (rs *REST) Delete(ctx api.Context, name string) (<-chan apiserver.RESTResult, error) {
|
func (rs *REST) Delete(ctx api.Context, name string) (runtime.Object, error) {
|
||||||
obj, err := rs.registry.Get(ctx, name)
|
obj, err := rs.registry.Get(ctx, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -119,9 +114,7 @@ func (rs *REST) Delete(ctx api.Context, name string) (<-chan apiserver.RESTResul
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("invalid object type")
|
return nil, fmt.Errorf("invalid object type")
|
||||||
}
|
}
|
||||||
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
return &api.Status{Status: api.StatusSuccess}, rs.registry.Delete(ctx, name)
|
||||||
return &api.Status{Status: api.StatusSuccess}, rs.registry.Delete(ctx, name)
|
|
||||||
}), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get gets a LimitRange with the specified name
|
// Get gets a LimitRange with the specified name
|
||||||
|
@@ -26,7 +26,6 @@ import (
|
|||||||
kerrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
kerrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/master/ports"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/master/ports"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
@@ -49,7 +48,7 @@ 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.")
|
||||||
|
|
||||||
// Create satisfies the RESTStorage interface.
|
// Create satisfies the RESTStorage interface.
|
||||||
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
|
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) {
|
||||||
minion, ok := obj.(*api.Node)
|
minion, ok := obj.(*api.Node)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("not a minion: %#v", obj)
|
return nil, fmt.Errorf("not a minion: %#v", obj)
|
||||||
@@ -59,17 +58,15 @@ func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan apiserver.RE
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
if err := rs.registry.CreateMinion(ctx, minion); err != nil {
|
||||||
if err := rs.registry.CreateMinion(ctx, minion); err != nil {
|
err = rest.CheckGeneratedNameError(rest.Nodes, err, minion)
|
||||||
err = rest.CheckGeneratedNameError(rest.Nodes, err, minion)
|
return nil, err
|
||||||
return nil, err
|
}
|
||||||
}
|
return minion, nil
|
||||||
return minion, nil
|
|
||||||
}), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete satisfies the RESTStorage interface.
|
// Delete satisfies the RESTStorage interface.
|
||||||
func (rs *REST) Delete(ctx api.Context, id string) (<-chan apiserver.RESTResult, error) {
|
func (rs *REST) Delete(ctx api.Context, id string) (runtime.Object, 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
|
||||||
@@ -77,9 +74,7 @@ func (rs *REST) Delete(ctx api.Context, id string) (<-chan apiserver.RESTResult,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
return &api.Status{Status: api.StatusSuccess}, rs.registry.DeleteMinion(ctx, id)
|
||||||
return &api.Status{Status: api.StatusSuccess}, rs.registry.DeleteMinion(ctx, id)
|
|
||||||
}), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get satisfies the RESTStorage interface.
|
// Get satisfies the RESTStorage interface.
|
||||||
@@ -108,10 +103,10 @@ func (*REST) NewList() runtime.Object {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update satisfies the RESTStorage interface.
|
// Update satisfies the RESTStorage interface.
|
||||||
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
|
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
|
||||||
minion, ok := obj.(*api.Node)
|
minion, ok := obj.(*api.Node)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("not a minion: %#v", obj)
|
return nil, false, fmt.Errorf("not a minion: %#v", obj)
|
||||||
}
|
}
|
||||||
// This is hacky, but minions don't really have a namespace, but kubectl currently automatically
|
// This is hacky, but minions don't really have a namespace, but kubectl currently automatically
|
||||||
// stuffs one in there. Fix it here temporarily until we fix kubectl
|
// stuffs one in there. Fix it here temporarily until we fix kubectl
|
||||||
@@ -123,7 +118,7 @@ func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan apiserver.RE
|
|||||||
|
|
||||||
oldMinion, err := rs.registry.GetMinion(ctx, minion.Name)
|
oldMinion, err := rs.registry.GetMinion(ctx, minion.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is hacky, but minion HostIP has been moved from spec to status since v1beta2. When updating
|
// This is hacky, but minion HostIP has been moved from spec to status since v1beta2. When updating
|
||||||
@@ -134,16 +129,14 @@ func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan apiserver.RE
|
|||||||
}
|
}
|
||||||
|
|
||||||
if errs := validation.ValidateMinionUpdate(oldMinion, minion); len(errs) > 0 {
|
if errs := validation.ValidateMinionUpdate(oldMinion, minion); len(errs) > 0 {
|
||||||
return nil, kerrors.NewInvalid("minion", minion.Name, errs)
|
return nil, false, kerrors.NewInvalid("minion", minion.Name, errs)
|
||||||
}
|
}
|
||||||
|
|
||||||
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
if err := rs.registry.UpdateMinion(ctx, minion); err != nil {
|
||||||
err := rs.registry.UpdateMinion(ctx, minion)
|
return nil, false, err
|
||||||
if err != nil {
|
}
|
||||||
return nil, err
|
out, err := rs.registry.GetMinion(ctx, minion.Name)
|
||||||
}
|
return out, false, err
|
||||||
return rs.registry.GetMinion(ctx, minion.Name)
|
|
||||||
}), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Watch returns Minions events via a watch.Interface.
|
// Watch returns Minions events via a watch.Interface.
|
||||||
|
@@ -39,27 +39,25 @@ func TestMinionRegistryREST(t *testing.T) {
|
|||||||
t.Errorf("has unexpected error: %v", err)
|
t.Errorf("has unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := ms.Create(ctx, &api.Node{ObjectMeta: api.ObjectMeta{Name: "baz"}})
|
obj, err := ms.Create(ctx, &api.Node{ObjectMeta: api.ObjectMeta{Name: "baz"}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("insert failed: %v", err)
|
t.Fatalf("insert failed: %v", err)
|
||||||
}
|
}
|
||||||
obj := <-c
|
if !api.HasObjectMetaSystemFieldValues(&obj.(*api.Node).ObjectMeta) {
|
||||||
if !api.HasObjectMetaSystemFieldValues(&obj.Object.(*api.Node).ObjectMeta) {
|
|
||||||
t.Errorf("storage did not populate object meta field values")
|
t.Errorf("storage did not populate object meta field values")
|
||||||
}
|
}
|
||||||
if m, ok := obj.Object.(*api.Node); !ok || m.Name != "baz" {
|
if m, ok := obj.(*api.Node); !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.Node).Name != "baz" {
|
if obj, err := ms.Get(ctx, "baz"); err != nil || obj.(*api.Node).Name != "baz" {
|
||||||
t.Errorf("insert didn't actually insert")
|
t.Errorf("insert didn't actually insert")
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err = ms.Delete(ctx, "bar")
|
obj, err = ms.Delete(ctx, "bar")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("delete failed")
|
t.Fatalf("delete failed")
|
||||||
}
|
}
|
||||||
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"); !errors.IsNotFound(err) {
|
if _, err := ms.Get(ctx, "bar"); !errors.IsNotFound(err) {
|
||||||
@@ -103,7 +101,7 @@ func TestMinionRegistryValidUpdate(t *testing.T) {
|
|||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
"baz": "home",
|
"baz": "home",
|
||||||
}
|
}
|
||||||
if _, err = storage.Update(ctx, minion); err != nil {
|
if _, _, err = storage.Update(ctx, minion); err != nil {
|
||||||
t.Errorf("Unexpected error: %v", err)
|
t.Errorf("Unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,7 +134,7 @@ func TestMinionRegistryValidatesCreate(t *testing.T) {
|
|||||||
for _, failureCase := range failureCases {
|
for _, failureCase := range failureCases {
|
||||||
c, err := storage.Create(ctx, &failureCase)
|
c, err := storage.Create(ctx, &failureCase)
|
||||||
if c != nil {
|
if c != nil {
|
||||||
t.Errorf("Expected nil channel")
|
t.Errorf("Expected nil object")
|
||||||
}
|
}
|
||||||
if !errors.IsInvalid(err) {
|
if !errors.IsInvalid(err) {
|
||||||
t.Errorf("Expected to get an invalid resource error, got %v", err)
|
t.Errorf("Expected to get an invalid resource error, got %v", err)
|
||||||
|
@@ -23,7 +23,6 @@ import (
|
|||||||
kerrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
kerrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
@@ -44,48 +43,44 @@ func NewREST(registry generic.Registry) *REST {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create creates a Namespace object
|
// Create creates a Namespace object
|
||||||
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
|
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) {
|
||||||
namespace := obj.(*api.Namespace)
|
namespace := obj.(*api.Namespace)
|
||||||
if err := rest.BeforeCreate(rest.Namespaces, ctx, obj); err != nil {
|
if err := rest.BeforeCreate(rest.Namespaces, ctx, obj); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
if err := rs.registry.Create(ctx, namespace.Name, namespace); err != nil {
|
||||||
if err := rs.registry.Create(ctx, namespace.Name, namespace); err != nil {
|
err = rest.CheckGeneratedNameError(rest.Namespaces, err, namespace)
|
||||||
err = rest.CheckGeneratedNameError(rest.Namespaces, err, namespace)
|
return nil, err
|
||||||
return nil, err
|
}
|
||||||
}
|
return rs.registry.Get(ctx, namespace.Name)
|
||||||
return rs.registry.Get(ctx, namespace.Name)
|
|
||||||
}), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update updates a Namespace object.
|
// Update updates a Namespace object.
|
||||||
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
|
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
|
||||||
namespace, ok := obj.(*api.Namespace)
|
namespace, ok := obj.(*api.Namespace)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("not a namespace: %#v", obj)
|
return nil, false, fmt.Errorf("not a namespace: %#v", obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
oldObj, err := rs.registry.Get(ctx, namespace.Name)
|
oldObj, err := rs.registry.Get(ctx, namespace.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
oldNamespace := oldObj.(*api.Namespace)
|
oldNamespace := oldObj.(*api.Namespace)
|
||||||
if errs := validation.ValidateNamespaceUpdate(oldNamespace, namespace); len(errs) > 0 {
|
if errs := validation.ValidateNamespaceUpdate(oldNamespace, namespace); len(errs) > 0 {
|
||||||
return nil, kerrors.NewInvalid("namespace", namespace.Name, errs)
|
return nil, false, kerrors.NewInvalid("namespace", namespace.Name, errs)
|
||||||
}
|
}
|
||||||
|
|
||||||
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
if err := rs.registry.Update(ctx, oldNamespace.Name, oldNamespace); err != nil {
|
||||||
err := rs.registry.Update(ctx, oldNamespace.Name, oldNamespace)
|
return nil, false, err
|
||||||
if err != nil {
|
}
|
||||||
return nil, err
|
out, err := rs.registry.Get(ctx, oldNamespace.Name)
|
||||||
}
|
return out, false, err
|
||||||
return rs.registry.Get(ctx, oldNamespace.Name)
|
|
||||||
}), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete deletes the Namespace with the specified name
|
// Delete deletes the Namespace with the specified name
|
||||||
func (rs *REST) Delete(ctx api.Context, id string) (<-chan apiserver.RESTResult, error) {
|
func (rs *REST) Delete(ctx api.Context, id string) (runtime.Object, 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
|
||||||
@@ -94,10 +89,7 @@ func (rs *REST) Delete(ctx api.Context, id string) (<-chan apiserver.RESTResult,
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("invalid object type")
|
return nil, fmt.Errorf("invalid object type")
|
||||||
}
|
}
|
||||||
|
return &api.Status{Status: api.StatusSuccess}, rs.registry.Delete(ctx, id)
|
||||||
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
|
||||||
return &api.Status{Status: api.StatusSuccess}, rs.registry.Delete(ctx, id)
|
|
||||||
}), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *REST) Get(ctx api.Context, id string) (runtime.Object, error) {
|
func (rs *REST) Get(ctx api.Context, id string) (runtime.Object, error) {
|
||||||
|
@@ -78,7 +78,7 @@ func TestRESTCreate(t *testing.T) {
|
|||||||
if !api.HasObjectMetaSystemFieldValues(&item.namespace.ObjectMeta) {
|
if !api.HasObjectMetaSystemFieldValues(&item.namespace.ObjectMeta) {
|
||||||
t.Errorf("storage did not populate object meta field values")
|
t.Errorf("storage did not populate object meta field values")
|
||||||
}
|
}
|
||||||
if e, a := item.namespace, (<-c).Object; !reflect.DeepEqual(e, a) {
|
if e, a := item.namespace, c; !reflect.DeepEqual(e, a) {
|
||||||
t.Errorf("diff: %s", util.ObjectDiff(e, a))
|
t.Errorf("diff: %s", util.ObjectDiff(e, a))
|
||||||
}
|
}
|
||||||
// Ensure we implement the interface
|
// Ensure we implement the interface
|
||||||
@@ -89,11 +89,10 @@ func TestRESTCreate(t *testing.T) {
|
|||||||
func TestRESTUpdate(t *testing.T) {
|
func TestRESTUpdate(t *testing.T) {
|
||||||
_, rest := NewTestREST()
|
_, rest := NewTestREST()
|
||||||
namespaceA := testNamespace("foo")
|
namespaceA := testNamespace("foo")
|
||||||
c, err := rest.Create(api.NewDefaultContext(), namespaceA)
|
_, err := rest.Create(api.NewDefaultContext(), namespaceA)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error %v", err)
|
t.Fatalf("Unexpected error %v", err)
|
||||||
}
|
}
|
||||||
<-c
|
|
||||||
got, err := rest.Get(api.NewDefaultContext(), namespaceA.Name)
|
got, err := rest.Get(api.NewDefaultContext(), namespaceA.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error %v", err)
|
t.Fatalf("Unexpected error %v", err)
|
||||||
@@ -102,11 +101,10 @@ func TestRESTUpdate(t *testing.T) {
|
|||||||
t.Errorf("diff: %s", util.ObjectDiff(e, a))
|
t.Errorf("diff: %s", util.ObjectDiff(e, a))
|
||||||
}
|
}
|
||||||
namespaceB := testNamespace("foo")
|
namespaceB := testNamespace("foo")
|
||||||
u, err := rest.Update(api.NewDefaultContext(), namespaceB)
|
_, _, err = rest.Update(api.NewDefaultContext(), namespaceB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error %v", err)
|
t.Fatalf("Unexpected error %v", err)
|
||||||
}
|
}
|
||||||
<-u
|
|
||||||
got2, err := rest.Get(api.NewDefaultContext(), namespaceB.Name)
|
got2, err := rest.Get(api.NewDefaultContext(), namespaceB.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error %v", err)
|
t.Fatalf("Unexpected error %v", err)
|
||||||
@@ -120,16 +118,15 @@ func TestRESTUpdate(t *testing.T) {
|
|||||||
func TestRESTDelete(t *testing.T) {
|
func TestRESTDelete(t *testing.T) {
|
||||||
_, rest := NewTestREST()
|
_, rest := NewTestREST()
|
||||||
namespaceA := testNamespace("foo")
|
namespaceA := testNamespace("foo")
|
||||||
c, err := rest.Create(api.NewContext(), namespaceA)
|
_, err := rest.Create(api.NewContext(), namespaceA)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error %v", err)
|
t.Fatalf("Unexpected error %v", err)
|
||||||
}
|
}
|
||||||
<-c
|
c, err := rest.Delete(api.NewContext(), namespaceA.Name)
|
||||||
c, err = rest.Delete(api.NewContext(), namespaceA.Name)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error %v", err)
|
t.Fatalf("Unexpected error %v", err)
|
||||||
}
|
}
|
||||||
if stat := (<-c).Object.(*api.Status); stat.Status != api.StatusSuccess {
|
if stat := c.(*api.Status); stat.Status != api.StatusSuccess {
|
||||||
t.Errorf("unexpected status: %v", stat)
|
t.Errorf("unexpected status: %v", stat)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -137,11 +134,10 @@ func TestRESTDelete(t *testing.T) {
|
|||||||
func TestRESTGet(t *testing.T) {
|
func TestRESTGet(t *testing.T) {
|
||||||
_, rest := NewTestREST()
|
_, rest := NewTestREST()
|
||||||
namespaceA := testNamespace("foo")
|
namespaceA := testNamespace("foo")
|
||||||
c, err := rest.Create(api.NewContext(), namespaceA)
|
_, err := rest.Create(api.NewContext(), namespaceA)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error %v", err)
|
t.Fatalf("Unexpected error %v", err)
|
||||||
}
|
}
|
||||||
<-c
|
|
||||||
got, err := rest.Get(api.NewContext(), namespaceA.Name)
|
got, err := rest.Get(api.NewContext(), namespaceA.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error %v", err)
|
t.Fatalf("Unexpected error %v", err)
|
||||||
|
@@ -25,7 +25,6 @@ import (
|
|||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
||||||
@@ -55,32 +54,28 @@ func NewREST(config *RESTConfig) *REST {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
|
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) {
|
||||||
pod := obj.(*api.Pod)
|
pod := obj.(*api.Pod)
|
||||||
|
|
||||||
if err := rest.BeforeCreate(rest.Pods, ctx, obj); err != nil {
|
if err := rest.BeforeCreate(rest.Pods, ctx, obj); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
if err := rs.registry.CreatePod(ctx, pod); err != nil {
|
||||||
if err := rs.registry.CreatePod(ctx, pod); err != nil {
|
err = rest.CheckGeneratedNameError(rest.Pods, err, pod)
|
||||||
err = rest.CheckGeneratedNameError(rest.Pods, err, pod)
|
return nil, err
|
||||||
return nil, err
|
}
|
||||||
}
|
return rs.registry.GetPod(ctx, pod.Name)
|
||||||
return rs.registry.GetPod(ctx, pod.Name)
|
|
||||||
}), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *REST) Delete(ctx api.Context, id string) (<-chan apiserver.RESTResult, error) {
|
func (rs *REST) Delete(ctx api.Context, id string) (runtime.Object, error) {
|
||||||
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
namespace, found := api.NamespaceFrom(ctx)
|
||||||
namespace, found := api.NamespaceFrom(ctx)
|
if !found {
|
||||||
if !found {
|
return &api.Status{Status: api.StatusFailure}, nil
|
||||||
return &api.Status{Status: api.StatusFailure}, nil
|
}
|
||||||
}
|
rs.podCache.ClearPodStatus(namespace, id)
|
||||||
rs.podCache.ClearPodStatus(namespace, id)
|
|
||||||
|
|
||||||
return &api.Status{Status: api.StatusSuccess}, rs.registry.DeletePod(ctx, id)
|
return &api.Status{Status: api.StatusSuccess}, rs.registry.DeletePod(ctx, id)
|
||||||
}), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *REST) Get(ctx api.Context, id string) (runtime.Object, error) {
|
func (rs *REST) Get(ctx api.Context, id string) (runtime.Object, error) {
|
||||||
@@ -167,20 +162,19 @@ func (*REST) NewList() runtime.Object {
|
|||||||
return &api.PodList{}
|
return &api.PodList{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
|
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, 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, false, errors.NewConflict("pod", pod.Namespace, fmt.Errorf("Pod.Namespace does not match the provided context"))
|
||||||
}
|
}
|
||||||
if errs := validation.ValidatePod(pod); len(errs) > 0 {
|
if errs := validation.ValidatePod(pod); len(errs) > 0 {
|
||||||
return nil, errors.NewInvalid("pod", pod.Name, errs)
|
return nil, false, errors.NewInvalid("pod", pod.Name, errs)
|
||||||
}
|
}
|
||||||
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
if err := rs.registry.UpdatePod(ctx, pod); err != nil {
|
||||||
if err := rs.registry.UpdatePod(ctx, pod); err != nil {
|
return nil, false, err
|
||||||
return nil, err
|
}
|
||||||
}
|
out, err := rs.registry.GetPod(ctx, pod.Name)
|
||||||
return rs.registry.GetPod(ctx, pod.Name)
|
return out, false, err
|
||||||
}), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResourceLocation returns a URL to which one can send traffic for the specified pod.
|
// ResourceLocation returns a URL to which one can send traffic for the specified pod.
|
||||||
|
@@ -21,7 +21,6 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"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"
|
||||||
@@ -31,6 +30,7 @@ import (
|
|||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -55,9 +55,8 @@ func (f *fakeCache) ClearPodStatus(namespace, name string) {
|
|||||||
f.clearedName = name
|
f.clearedName = name
|
||||||
}
|
}
|
||||||
|
|
||||||
func expectApiStatusError(t *testing.T, ch <-chan apiserver.RESTResult, msg string) {
|
func expectApiStatusError(t *testing.T, out runtime.Object, msg string) {
|
||||||
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
|
||||||
@@ -67,9 +66,8 @@ func expectApiStatusError(t *testing.T, ch <-chan apiserver.RESTResult, msg stri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func expectPod(t *testing.T, ch <-chan apiserver.RESTResult) (*api.Pod, bool) {
|
func expectPod(t *testing.T, out runtime.Object) (*api.Pod, bool) {
|
||||||
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
|
||||||
@@ -94,11 +92,10 @@ func TestCreatePodRegistryError(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
ctx := api.NewDefaultContext()
|
ctx := api.NewDefaultContext()
|
||||||
ch, err := storage.Create(ctx, pod)
|
_, err := storage.Create(ctx, pod)
|
||||||
if err != nil {
|
if err != podRegistry.Err {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
expectApiStatusError(t, ch, podRegistry.Err.Error())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreatePodSetsIds(t *testing.T) {
|
func TestCreatePodSetsIds(t *testing.T) {
|
||||||
@@ -118,11 +115,10 @@ func TestCreatePodSetsIds(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
ctx := api.NewDefaultContext()
|
ctx := api.NewDefaultContext()
|
||||||
ch, err := storage.Create(ctx, pod)
|
_, err := storage.Create(ctx, pod)
|
||||||
if err != nil {
|
if err != podRegistry.Err {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
expectApiStatusError(t, ch, podRegistry.Err.Error())
|
|
||||||
|
|
||||||
if len(podRegistry.Pod.Name) == 0 {
|
if len(podRegistry.Pod.Name) == 0 {
|
||||||
t.Errorf("Expected pod ID to be set, Got %#v", pod)
|
t.Errorf("Expected pod ID to be set, Got %#v", pod)
|
||||||
@@ -149,11 +145,10 @@ func TestCreatePodSetsUID(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
ctx := api.NewDefaultContext()
|
ctx := api.NewDefaultContext()
|
||||||
ch, err := storage.Create(ctx, pod)
|
_, err := storage.Create(ctx, pod)
|
||||||
if err != nil {
|
if err != podRegistry.Err {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
expectApiStatusError(t, ch, podRegistry.Err.Error())
|
|
||||||
|
|
||||||
if len(podRegistry.Pod.UID) == 0 {
|
if len(podRegistry.Pod.UID) == 0 {
|
||||||
t.Errorf("Expected pod UID to be set, Got %#v", pod)
|
t.Errorf("Expected pod UID to be set, Got %#v", pod)
|
||||||
@@ -471,15 +466,12 @@ func TestCreatePod(t *testing.T) {
|
|||||||
}
|
}
|
||||||
pod.Name = "foo"
|
pod.Name = "foo"
|
||||||
ctx := api.NewDefaultContext()
|
ctx := api.NewDefaultContext()
|
||||||
channel, err := storage.Create(ctx, pod)
|
obj, err := storage.Create(ctx, pod)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
select {
|
if obj == nil {
|
||||||
case <-channel:
|
t.Fatalf("unexpected object: %#v", obj)
|
||||||
// Do nothing, this is expected.
|
|
||||||
case <-time.After(time.Millisecond * 100):
|
|
||||||
t.Error("Unexpected timeout on async channel")
|
|
||||||
}
|
}
|
||||||
if !api.HasObjectMetaSystemFieldValues(&podRegistry.Pod.ObjectMeta) {
|
if !api.HasObjectMetaSystemFieldValues(&podRegistry.Pod.ObjectMeta) {
|
||||||
t.Errorf("Expected ObjectMeta field values were populated")
|
t.Errorf("Expected ObjectMeta field values were populated")
|
||||||
@@ -520,9 +512,9 @@ func TestUpdatePodWithConflictingNamespace(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx := api.NewDefaultContext()
|
ctx := api.NewDefaultContext()
|
||||||
channel, err := storage.Update(ctx, pod)
|
obj, created, err := storage.Update(ctx, pod)
|
||||||
if channel != nil {
|
if obj != nil || created {
|
||||||
t.Error("Expected a nil channel, but we got a value")
|
t.Error("Expected a nil channel, but we got a value or created")
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("Expected an error, but we didn't get one")
|
t.Errorf("Expected an error, but we didn't get one")
|
||||||
@@ -648,19 +640,12 @@ func TestDeletePod(t *testing.T) {
|
|||||||
podCache: fakeCache,
|
podCache: fakeCache,
|
||||||
}
|
}
|
||||||
ctx := api.NewDefaultContext()
|
ctx := api.NewDefaultContext()
|
||||||
channel, err := storage.Delete(ctx, "foo")
|
result, err := storage.Delete(ctx, "foo")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
var result apiserver.RESTResult
|
|
||||||
select {
|
|
||||||
case result = <-channel:
|
|
||||||
// Do nothing, this is expected.
|
|
||||||
case <-time.After(time.Millisecond * 100):
|
|
||||||
t.Error("Unexpected timeout on async channel")
|
|
||||||
}
|
|
||||||
if fakeCache.clearedNamespace != "default" || fakeCache.clearedName != "foo" {
|
if fakeCache.clearedNamespace != "default" || fakeCache.clearedName != "foo" {
|
||||||
t.Errorf("Unexpeceted cache delete: %s %s %#v", fakeCache.clearedName, fakeCache.clearedNamespace, result.Object)
|
t.Errorf("Unexpeceted cache delete: %s %s %#v", fakeCache.clearedName, fakeCache.clearedNamespace, result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -22,7 +22,6 @@ 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/validation"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
@@ -44,7 +43,7 @@ func NewREST(registry generic.Registry) *REST {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a ResourceQuota object
|
// Create a ResourceQuota object
|
||||||
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
|
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) {
|
||||||
resourceQuota, ok := obj.(*api.ResourceQuota)
|
resourceQuota, ok := obj.(*api.ResourceQuota)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("invalid object type")
|
return nil, fmt.Errorf("invalid object type")
|
||||||
@@ -66,29 +65,27 @@ func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan apiserver.RE
|
|||||||
}
|
}
|
||||||
api.FillObjectMetaSystemFields(ctx, &resourceQuota.ObjectMeta)
|
api.FillObjectMetaSystemFields(ctx, &resourceQuota.ObjectMeta)
|
||||||
|
|
||||||
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
err := rs.registry.Create(ctx, resourceQuota.Name, resourceQuota)
|
||||||
err := rs.registry.Create(ctx, resourceQuota.Name, resourceQuota)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, err
|
||||||
return nil, err
|
}
|
||||||
}
|
return rs.registry.Get(ctx, resourceQuota.Name)
|
||||||
return rs.registry.Get(ctx, resourceQuota.Name)
|
|
||||||
}), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update updates a ResourceQuota object.
|
// Update updates a ResourceQuota object.
|
||||||
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
|
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
|
||||||
resourceQuota, ok := obj.(*api.ResourceQuota)
|
resourceQuota, ok := obj.(*api.ResourceQuota)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("invalid object type")
|
return nil, false, fmt.Errorf("invalid object type")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !api.ValidNamespace(ctx, &resourceQuota.ObjectMeta) {
|
if !api.ValidNamespace(ctx, &resourceQuota.ObjectMeta) {
|
||||||
return nil, errors.NewConflict("resourceQuota", resourceQuota.Namespace, fmt.Errorf("ResourceQuota.Namespace does not match the provided context"))
|
return nil, false, errors.NewConflict("resourceQuota", resourceQuota.Namespace, fmt.Errorf("ResourceQuota.Namespace does not match the provided context"))
|
||||||
}
|
}
|
||||||
|
|
||||||
oldObj, err := rs.registry.Get(ctx, resourceQuota.Name)
|
oldObj, err := rs.registry.Get(ctx, resourceQuota.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
editResourceQuota := oldObj.(*api.ResourceQuota)
|
editResourceQuota := oldObj.(*api.ResourceQuota)
|
||||||
@@ -100,20 +97,18 @@ func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan apiserver.RE
|
|||||||
editResourceQuota.Spec = resourceQuota.Spec
|
editResourceQuota.Spec = resourceQuota.Spec
|
||||||
|
|
||||||
if errs := validation.ValidateResourceQuota(editResourceQuota); len(errs) > 0 {
|
if errs := validation.ValidateResourceQuota(editResourceQuota); len(errs) > 0 {
|
||||||
return nil, errors.NewInvalid("resourceQuota", editResourceQuota.Name, errs)
|
return nil, false, errors.NewInvalid("resourceQuota", editResourceQuota.Name, errs)
|
||||||
}
|
}
|
||||||
|
|
||||||
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
if err := rs.registry.Update(ctx, editResourceQuota.Name, editResourceQuota); err != nil {
|
||||||
err := rs.registry.Update(ctx, editResourceQuota.Name, editResourceQuota)
|
return nil, false, err
|
||||||
if err != nil {
|
}
|
||||||
return nil, err
|
out, err := rs.registry.Get(ctx, editResourceQuota.Name)
|
||||||
}
|
return out, false, err
|
||||||
return rs.registry.Get(ctx, editResourceQuota.Name)
|
|
||||||
}), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete deletes the ResourceQuota with the specified name
|
// Delete deletes the ResourceQuota with the specified name
|
||||||
func (rs *REST) Delete(ctx api.Context, name string) (<-chan apiserver.RESTResult, error) {
|
func (rs *REST) Delete(ctx api.Context, name string) (runtime.Object, error) {
|
||||||
obj, err := rs.registry.Get(ctx, name)
|
obj, err := rs.registry.Get(ctx, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -122,9 +117,7 @@ func (rs *REST) Delete(ctx api.Context, name string) (<-chan apiserver.RESTResul
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("invalid object type")
|
return nil, fmt.Errorf("invalid object type")
|
||||||
}
|
}
|
||||||
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
return &api.Status{Status: api.StatusSuccess}, rs.registry.Delete(ctx, name)
|
||||||
return &api.Status{Status: api.StatusSuccess}, rs.registry.Delete(ctx, name)
|
|
||||||
}), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get gets a ResourceQuota with the specified name
|
// Get gets a ResourceQuota with the specified name
|
||||||
|
@@ -20,7 +20,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -42,15 +41,13 @@ func (*REST) New() runtime.Object {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create takes the incoming ResourceQuotaUsage and applies the latest status atomically to a ResourceQuota
|
// Create takes the incoming ResourceQuotaUsage and applies the latest status atomically to a ResourceQuota
|
||||||
func (b *REST) Create(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
|
func (b *REST) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) {
|
||||||
resourceQuotaUsage, ok := obj.(*api.ResourceQuotaUsage)
|
resourceQuotaUsage, ok := obj.(*api.ResourceQuotaUsage)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("incorrect type: %#v", obj)
|
return nil, fmt.Errorf("incorrect type: %#v", obj)
|
||||||
}
|
}
|
||||||
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
if err := b.registry.ApplyStatus(ctx, resourceQuotaUsage); err != nil {
|
||||||
if err := b.registry.ApplyStatus(ctx, resourceQuotaUsage); err != nil {
|
return nil, err
|
||||||
return nil, err
|
}
|
||||||
}
|
return &api.Status{Status: api.StatusSuccess}, nil
|
||||||
return &api.Status{Status: api.StatusSuccess}, nil
|
|
||||||
}), nil
|
|
||||||
}
|
}
|
||||||
|
@@ -25,7 +25,6 @@ import (
|
|||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/minion"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/minion"
|
||||||
@@ -80,7 +79,7 @@ func reloadIPsFromStorage(ipa *ipAllocator, registry Registry) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
|
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) {
|
||||||
service := obj.(*api.Service)
|
service := obj.(*api.Service)
|
||||||
|
|
||||||
if err := rest.BeforeCreate(rest.Services, ctx, obj); err != nil {
|
if err := rest.BeforeCreate(rest.Services, ctx, obj); err != nil {
|
||||||
@@ -102,61 +101,59 @@ func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan apiserver.RE
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
// TODO: Move this to post-creation rectification loop, so that we make/remove external load balancers
|
||||||
// TODO: Move this to post-creation rectification loop, so that we make/remove external load balancers
|
// correctly no matter what http operations happen.
|
||||||
// correctly no matter what http operations happen.
|
if service.Spec.CreateExternalLoadBalancer {
|
||||||
if service.Spec.CreateExternalLoadBalancer {
|
if rs.cloud == nil {
|
||||||
if rs.cloud == nil {
|
return nil, fmt.Errorf("requested an external service, but no cloud provider supplied.")
|
||||||
return nil, fmt.Errorf("requested an external service, but no cloud provider supplied.")
|
|
||||||
}
|
|
||||||
if service.Spec.Protocol != api.ProtocolTCP {
|
|
||||||
// TODO: Support UDP here too.
|
|
||||||
return nil, fmt.Errorf("external load balancers for non TCP services are not currently supported.")
|
|
||||||
}
|
|
||||||
balancer, ok := rs.cloud.TCPLoadBalancer()
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("the cloud provider does not support external TCP load balancers.")
|
|
||||||
}
|
|
||||||
zones, ok := rs.cloud.Zones()
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("the cloud provider does not support zone enumeration.")
|
|
||||||
}
|
|
||||||
hosts, err := rs.machines.ListMinions(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
zone, err := zones.GetZone()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// TODO: We should be able to rely on valid input, and not do defaulting here.
|
|
||||||
var affinityType api.AffinityType = service.Spec.SessionAffinity
|
|
||||||
if affinityType == "" {
|
|
||||||
affinityType = api.AffinityTypeNone
|
|
||||||
}
|
|
||||||
if len(service.Spec.PublicIPs) > 0 {
|
|
||||||
for _, publicIP := range service.Spec.PublicIPs {
|
|
||||||
_, err = balancer.CreateTCPLoadBalancer(service.Name, zone.Region, net.ParseIP(publicIP), service.Spec.Port, hostsFromMinionList(hosts), affinityType)
|
|
||||||
if err != nil {
|
|
||||||
// TODO: have to roll-back any successful calls.
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ip, err := balancer.CreateTCPLoadBalancer(service.Name, zone.Region, nil, service.Spec.Port, hostsFromMinionList(hosts), affinityType)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
service.Spec.PublicIPs = []string{ip.String()}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if service.Spec.Protocol != api.ProtocolTCP {
|
||||||
if err := rs.registry.CreateService(ctx, service); err != nil {
|
// TODO: Support UDP here too.
|
||||||
err = rest.CheckGeneratedNameError(rest.Services, err, service)
|
return nil, fmt.Errorf("external load balancers for non TCP services are not currently supported.")
|
||||||
|
}
|
||||||
|
balancer, ok := rs.cloud.TCPLoadBalancer()
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("the cloud provider does not support external TCP load balancers.")
|
||||||
|
}
|
||||||
|
zones, ok := rs.cloud.Zones()
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("the cloud provider does not support zone enumeration.")
|
||||||
|
}
|
||||||
|
hosts, err := rs.machines.ListMinions(ctx)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return rs.registry.GetService(ctx, service.Name)
|
zone, err := zones.GetZone()
|
||||||
}), nil
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// TODO: We should be able to rely on valid input, and not do defaulting here.
|
||||||
|
var affinityType api.AffinityType = service.Spec.SessionAffinity
|
||||||
|
if affinityType == "" {
|
||||||
|
affinityType = api.AffinityTypeNone
|
||||||
|
}
|
||||||
|
if len(service.Spec.PublicIPs) > 0 {
|
||||||
|
for _, publicIP := range service.Spec.PublicIPs {
|
||||||
|
_, err = balancer.CreateTCPLoadBalancer(service.Name, zone.Region, net.ParseIP(publicIP), service.Spec.Port, hostsFromMinionList(hosts), affinityType)
|
||||||
|
if err != nil {
|
||||||
|
// TODO: have to roll-back any successful calls.
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ip, err := balancer.CreateTCPLoadBalancer(service.Name, zone.Region, nil, service.Spec.Port, hostsFromMinionList(hosts), affinityType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
service.Spec.PublicIPs = []string{ip.String()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rs.registry.CreateService(ctx, service); err != nil {
|
||||||
|
err = rest.CheckGeneratedNameError(rest.Services, err, service)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return rs.registry.GetService(ctx, service.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func hostsFromMinionList(list *api.NodeList) []string {
|
func hostsFromMinionList(list *api.NodeList) []string {
|
||||||
@@ -167,16 +164,14 @@ func hostsFromMinionList(list *api.NodeList) []string {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *REST) Delete(ctx api.Context, id string) (<-chan apiserver.RESTResult, error) {
|
func (rs *REST) Delete(ctx api.Context, id string) (runtime.Object, 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
|
||||||
}
|
}
|
||||||
rs.portalMgr.Release(net.ParseIP(service.Spec.PortalIP))
|
rs.portalMgr.Release(net.ParseIP(service.Spec.PortalIP))
|
||||||
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
rs.deleteExternalLoadBalancer(service)
|
||||||
rs.deleteExternalLoadBalancer(service)
|
return &api.Status{Status: api.StatusSuccess}, rs.registry.DeleteService(ctx, id)
|
||||||
return &api.Status{Status: api.StatusSuccess}, rs.registry.DeleteService(ctx, id)
|
|
||||||
}), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *REST) Get(ctx api.Context, id string) (runtime.Object, error) {
|
func (rs *REST) Get(ctx api.Context, id string) (runtime.Object, error) {
|
||||||
@@ -217,30 +212,29 @@ func (*REST) NewList() runtime.Object {
|
|||||||
return &api.Service{}
|
return &api.Service{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan apiserver.RESTResult, error) {
|
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, 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, false, errors.NewConflict("service", service.Namespace, fmt.Errorf("Service.Namespace does not match the provided context"))
|
||||||
}
|
}
|
||||||
|
|
||||||
oldService, err := rs.registry.GetService(ctx, service.Name)
|
oldService, err := rs.registry.GetService(ctx, service.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy over non-user fields
|
// Copy over non-user fields
|
||||||
// TODO: make this a merge function
|
// TODO: make this a merge function
|
||||||
if errs := validation.ValidateServiceUpdate(oldService, service); len(errs) > 0 {
|
if errs := validation.ValidateServiceUpdate(oldService, service); len(errs) > 0 {
|
||||||
return nil, errors.NewInvalid("service", service.Name, errs)
|
return nil, false, errors.NewInvalid("service", service.Name, errs)
|
||||||
}
|
}
|
||||||
return apiserver.MakeAsync(func() (runtime.Object, error) {
|
// TODO: check to see if external load balancer status changed
|
||||||
// TODO: check to see if external load balancer status changed
|
err = rs.registry.UpdateService(ctx, service)
|
||||||
err = rs.registry.UpdateService(ctx, service)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, false, err
|
||||||
return nil, err
|
}
|
||||||
}
|
out, err := rs.registry.GetService(ctx, service.Name)
|
||||||
return rs.registry.GetService(ctx, service.Name)
|
return out, false, err
|
||||||
}), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResourceLocation returns a URL to which one can send traffic for the specified service.
|
// ResourceLocation returns a URL to which one can send traffic for the specified service.
|
||||||
|
@@ -56,9 +56,8 @@ func TestServiceRegistryCreate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
ctx := api.NewDefaultContext()
|
ctx := api.NewDefaultContext()
|
||||||
c, _ := storage.Create(ctx, svc)
|
created_svc, _ := storage.Create(ctx, svc)
|
||||||
created_svc := <-c
|
created_service := created_svc.(*api.Service)
|
||||||
created_service := created_svc.Object.(*api.Service)
|
|
||||||
if !api.HasObjectMetaSystemFieldValues(&created_service.ObjectMeta) {
|
if !api.HasObjectMetaSystemFieldValues(&created_service.ObjectMeta) {
|
||||||
t.Errorf("storage did not populate object meta field values")
|
t.Errorf("storage did not populate object meta field values")
|
||||||
}
|
}
|
||||||
@@ -109,7 +108,7 @@ func TestServiceStorageValidatesCreate(t *testing.T) {
|
|||||||
for _, failureCase := range failureCases {
|
for _, failureCase := range failureCases {
|
||||||
c, err := storage.Create(ctx, &failureCase)
|
c, err := storage.Create(ctx, &failureCase)
|
||||||
if c != nil {
|
if c != nil {
|
||||||
t.Errorf("Expected nil channel")
|
t.Errorf("Expected nil object")
|
||||||
}
|
}
|
||||||
if !errors.IsInvalid(err) {
|
if !errors.IsInvalid(err) {
|
||||||
t.Errorf("Expected to get an invalid resource error, got %v", err)
|
t.Errorf("Expected to get an invalid resource error, got %v", err)
|
||||||
@@ -129,7 +128,7 @@ func TestServiceRegistryUpdate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
storage := NewREST(registry, nil, nil, makeIPNet(t))
|
storage := NewREST(registry, nil, nil, makeIPNet(t))
|
||||||
c, err := storage.Update(ctx, &api.Service{
|
updated_svc, created, err := storage.Update(ctx, &api.Service{
|
||||||
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
||||||
Spec: api.ServiceSpec{
|
Spec: api.ServiceSpec{
|
||||||
Port: 6502,
|
Port: 6502,
|
||||||
@@ -141,11 +140,13 @@ func TestServiceRegistryUpdate(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Expected no error: %v", err)
|
t.Fatalf("Expected no error: %v", err)
|
||||||
}
|
}
|
||||||
if c == nil {
|
if updated_svc == nil {
|
||||||
t.Errorf("Expected non-nil channel")
|
t.Errorf("Expected non-nil object")
|
||||||
}
|
}
|
||||||
updated_svc := <-c
|
if created {
|
||||||
updated_service := updated_svc.Object.(*api.Service)
|
t.Errorf("expected not created")
|
||||||
|
}
|
||||||
|
updated_service := updated_svc.(*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)
|
||||||
}
|
}
|
||||||
@@ -186,9 +187,9 @@ func TestServiceStorageValidatesUpdate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, failureCase := range failureCases {
|
for _, failureCase := range failureCases {
|
||||||
c, err := storage.Update(ctx, &failureCase)
|
c, created, err := storage.Update(ctx, &failureCase)
|
||||||
if c != nil {
|
if c != nil || created {
|
||||||
t.Errorf("Expected nil channel")
|
t.Errorf("Expected nil object or created false")
|
||||||
}
|
}
|
||||||
if !errors.IsInvalid(err) {
|
if !errors.IsInvalid(err) {
|
||||||
t.Errorf("Expected to get an invalid resource error, got %v", err)
|
t.Errorf("Expected to get an invalid resource error, got %v", err)
|
||||||
@@ -212,8 +213,7 @@ func TestServiceRegistryExternalService(t *testing.T) {
|
|||||||
SessionAffinity: api.AffinityTypeNone,
|
SessionAffinity: api.AffinityTypeNone,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
c, _ := storage.Create(ctx, svc)
|
storage.Create(ctx, svc)
|
||||||
<-c
|
|
||||||
if len(fakeCloud.Calls) != 2 || fakeCloud.Calls[0] != "get-zone" || fakeCloud.Calls[1] != "create" {
|
if len(fakeCloud.Calls) != 2 || fakeCloud.Calls[0] != "get-zone" || fakeCloud.Calls[1] != "create" {
|
||||||
t.Errorf("Unexpected call(s): %#v", fakeCloud.Calls)
|
t.Errorf("Unexpected call(s): %#v", fakeCloud.Calls)
|
||||||
}
|
}
|
||||||
@@ -244,8 +244,7 @@ func TestServiceRegistryExternalServiceError(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
ctx := api.NewDefaultContext()
|
ctx := api.NewDefaultContext()
|
||||||
c, _ := storage.Create(ctx, svc)
|
storage.Create(ctx, svc)
|
||||||
<-c
|
|
||||||
if len(fakeCloud.Calls) != 1 || fakeCloud.Calls[0] != "get-zone" {
|
if len(fakeCloud.Calls) != 1 || fakeCloud.Calls[0] != "get-zone" {
|
||||||
t.Errorf("Unexpected call(s): %#v", fakeCloud.Calls)
|
t.Errorf("Unexpected call(s): %#v", fakeCloud.Calls)
|
||||||
}
|
}
|
||||||
@@ -269,8 +268,7 @@ func TestServiceRegistryDelete(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
registry.CreateService(ctx, svc)
|
registry.CreateService(ctx, svc)
|
||||||
c, _ := storage.Delete(ctx, svc.Name)
|
storage.Delete(ctx, svc.Name)
|
||||||
<-c
|
|
||||||
if len(fakeCloud.Calls) != 0 {
|
if len(fakeCloud.Calls) != 0 {
|
||||||
t.Errorf("Unexpected call(s): %#v", fakeCloud.Calls)
|
t.Errorf("Unexpected call(s): %#v", fakeCloud.Calls)
|
||||||
}
|
}
|
||||||
@@ -295,8 +293,7 @@ func TestServiceRegistryDeleteExternal(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
registry.CreateService(ctx, svc)
|
registry.CreateService(ctx, svc)
|
||||||
c, _ := storage.Delete(ctx, svc.Name)
|
storage.Delete(ctx, svc.Name)
|
||||||
<-c
|
|
||||||
if len(fakeCloud.Calls) != 2 || fakeCloud.Calls[0] != "get-zone" || fakeCloud.Calls[1] != "delete" {
|
if len(fakeCloud.Calls) != 2 || fakeCloud.Calls[0] != "get-zone" || fakeCloud.Calls[1] != "delete" {
|
||||||
t.Errorf("Unexpected call(s): %#v", fakeCloud.Calls)
|
t.Errorf("Unexpected call(s): %#v", fakeCloud.Calls)
|
||||||
}
|
}
|
||||||
@@ -413,9 +410,8 @@ func TestServiceRegistryIPAllocation(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
ctx := api.NewDefaultContext()
|
ctx := api.NewDefaultContext()
|
||||||
c1, _ := rest.Create(ctx, svc1)
|
created_svc1, _ := rest.Create(ctx, svc1)
|
||||||
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)
|
||||||
}
|
}
|
||||||
@@ -432,9 +428,8 @@ func TestServiceRegistryIPAllocation(t *testing.T) {
|
|||||||
SessionAffinity: api.AffinityTypeNone,
|
SessionAffinity: api.AffinityTypeNone,
|
||||||
}}
|
}}
|
||||||
ctx = api.NewDefaultContext()
|
ctx = api.NewDefaultContext()
|
||||||
c2, _ := rest.Create(ctx, svc2)
|
created_svc2, _ := rest.Create(ctx, svc2)
|
||||||
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)
|
||||||
}
|
}
|
||||||
@@ -453,9 +448,8 @@ func TestServiceRegistryIPAllocation(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
ctx = api.NewDefaultContext()
|
ctx = api.NewDefaultContext()
|
||||||
c3, _ := rest.Create(ctx, svc3)
|
created_svc3, _ := rest.Create(ctx, svc3)
|
||||||
created_svc3 := <-c3
|
created_service_3 := created_svc3.(*api.Service)
|
||||||
created_service_3 := created_svc3.Object.(*api.Service)
|
|
||||||
if created_service_3.Spec.PortalIP != "1.2.3.93" { // specific IP
|
if created_service_3.Spec.PortalIP != "1.2.3.93" { // specific IP
|
||||||
t.Errorf("Unexpected PortalIP: %s", created_service_3.Spec.PortalIP)
|
t.Errorf("Unexpected PortalIP: %s", created_service_3.Spec.PortalIP)
|
||||||
}
|
}
|
||||||
@@ -478,9 +472,8 @@ func TestServiceRegistryIPReallocation(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
ctx := api.NewDefaultContext()
|
ctx := api.NewDefaultContext()
|
||||||
c1, _ := rest.Create(ctx, svc1)
|
created_svc1, _ := rest.Create(ctx, svc1)
|
||||||
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)
|
||||||
}
|
}
|
||||||
@@ -488,8 +481,7 @@ func TestServiceRegistryIPReallocation(t *testing.T) {
|
|||||||
t.Errorf("Unexpected PortalIP: %s", created_service_1.Spec.PortalIP)
|
t.Errorf("Unexpected PortalIP: %s", created_service_1.Spec.PortalIP)
|
||||||
}
|
}
|
||||||
|
|
||||||
c, _ := rest.Delete(ctx, created_service_1.Name)
|
rest.Delete(ctx, created_service_1.Name)
|
||||||
<-c
|
|
||||||
|
|
||||||
svc2 := &api.Service{
|
svc2 := &api.Service{
|
||||||
ObjectMeta: api.ObjectMeta{Name: "bar"},
|
ObjectMeta: api.ObjectMeta{Name: "bar"},
|
||||||
@@ -501,9 +493,8 @@ func TestServiceRegistryIPReallocation(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
ctx = api.NewDefaultContext()
|
ctx = api.NewDefaultContext()
|
||||||
c2, _ := rest.Create(ctx, svc2)
|
created_svc2, _ := rest.Create(ctx, svc2)
|
||||||
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)
|
||||||
}
|
}
|
||||||
@@ -529,9 +520,8 @@ func TestServiceRegistryIPUpdate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
ctx := api.NewDefaultContext()
|
ctx := api.NewDefaultContext()
|
||||||
c, _ := rest.Create(ctx, svc)
|
created_svc, _ := rest.Create(ctx, svc)
|
||||||
created_svc := <-c
|
created_service := created_svc.(*api.Service)
|
||||||
created_service := created_svc.Object.(*api.Service)
|
|
||||||
if created_service.Spec.Port != 6502 {
|
if created_service.Spec.Port != 6502 {
|
||||||
t.Errorf("Expected port 6502, but got %v", created_service.Spec.Port)
|
t.Errorf("Expected port 6502, but got %v", created_service.Spec.Port)
|
||||||
}
|
}
|
||||||
@@ -543,9 +533,8 @@ func TestServiceRegistryIPUpdate(t *testing.T) {
|
|||||||
*update = *created_service
|
*update = *created_service
|
||||||
update.Spec.Port = 6503
|
update.Spec.Port = 6503
|
||||||
|
|
||||||
c, _ = rest.Update(ctx, update)
|
updated_svc, _, _ := rest.Update(ctx, update)
|
||||||
updated_svc := <-c
|
updated_service := updated_svc.(*api.Service)
|
||||||
updated_service := updated_svc.Object.(*api.Service)
|
|
||||||
if updated_service.Spec.Port != 6503 {
|
if updated_service.Spec.Port != 6503 {
|
||||||
t.Errorf("Expected port 6503, but got %v", updated_service.Spec.Port)
|
t.Errorf("Expected port 6503, but got %v", updated_service.Spec.Port)
|
||||||
}
|
}
|
||||||
@@ -554,7 +543,7 @@ func TestServiceRegistryIPUpdate(t *testing.T) {
|
|||||||
update.Spec.Port = 6503
|
update.Spec.Port = 6503
|
||||||
update.Spec.PortalIP = "1.2.3.76" // error
|
update.Spec.PortalIP = "1.2.3.76" // error
|
||||||
|
|
||||||
_, err := rest.Update(ctx, update)
|
_, _, err := rest.Update(ctx, update)
|
||||||
if err == nil || !errors.IsInvalid(err) {
|
if err == nil || !errors.IsInvalid(err) {
|
||||||
t.Error("Unexpected error type: %v", err)
|
t.Error("Unexpected error type: %v", err)
|
||||||
}
|
}
|
||||||
@@ -578,9 +567,8 @@ func TestServiceRegistryIPExternalLoadBalancer(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
ctx := api.NewDefaultContext()
|
ctx := api.NewDefaultContext()
|
||||||
c, _ := rest.Create(ctx, svc)
|
created_svc, _ := rest.Create(ctx, svc)
|
||||||
created_svc := <-c
|
created_service := created_svc.(*api.Service)
|
||||||
created_service := created_svc.Object.(*api.Service)
|
|
||||||
if created_service.Spec.Port != 6502 {
|
if created_service.Spec.Port != 6502 {
|
||||||
t.Errorf("Expected port 6502, but got %v", created_service.Spec.Port)
|
t.Errorf("Expected port 6502, but got %v", created_service.Spec.Port)
|
||||||
}
|
}
|
||||||
@@ -591,7 +579,7 @@ func TestServiceRegistryIPExternalLoadBalancer(t *testing.T) {
|
|||||||
update := new(api.Service)
|
update := new(api.Service)
|
||||||
*update = *created_service
|
*update = *created_service
|
||||||
|
|
||||||
_, err := rest.Update(ctx, update)
|
_, _, err := rest.Update(ctx, update)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Unexpected error %v", err)
|
t.Errorf("Unexpected error %v", err)
|
||||||
}
|
}
|
||||||
@@ -614,8 +602,7 @@ func TestServiceRegistryIPReloadFromStorage(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
ctx := api.NewDefaultContext()
|
ctx := api.NewDefaultContext()
|
||||||
c, _ := rest1.Create(ctx, svc)
|
rest1.Create(ctx, svc)
|
||||||
<-c
|
|
||||||
svc = &api.Service{
|
svc = &api.Service{
|
||||||
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: api.NamespaceDefault},
|
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: api.NamespaceDefault},
|
||||||
Spec: api.ServiceSpec{
|
Spec: api.ServiceSpec{
|
||||||
@@ -625,8 +612,7 @@ func TestServiceRegistryIPReloadFromStorage(t *testing.T) {
|
|||||||
SessionAffinity: api.AffinityTypeNone,
|
SessionAffinity: api.AffinityTypeNone,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
c, _ = rest1.Create(ctx, svc)
|
rest1.Create(ctx, svc)
|
||||||
<-c
|
|
||||||
|
|
||||||
// This will reload from storage, finding the previous 2
|
// This will reload from storage, finding the previous 2
|
||||||
rest2 := NewREST(registry, fakeCloud, registrytest.NewMinionRegistry(machines, api.NodeResources{}), makeIPNet(t))
|
rest2 := NewREST(registry, fakeCloud, registrytest.NewMinionRegistry(machines, api.NodeResources{}), makeIPNet(t))
|
||||||
@@ -641,9 +627,8 @@ func TestServiceRegistryIPReloadFromStorage(t *testing.T) {
|
|||||||
SessionAffinity: api.AffinityTypeNone,
|
SessionAffinity: api.AffinityTypeNone,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
c, _ = rest2.Create(ctx, svc)
|
created_svc, _ := rest2.Create(ctx, svc)
|
||||||
created_svc := <-c
|
created_service := created_svc.(*api.Service)
|
||||||
created_service := created_svc.Object.(*api.Service)
|
|
||||||
if created_service.Spec.PortalIP != "1.2.3.3" {
|
if created_service.Spec.PortalIP != "1.2.3.3" {
|
||||||
t.Errorf("Unexpected PortalIP: %s", created_service.Spec.PortalIP)
|
t.Errorf("Unexpected PortalIP: %s", created_service.Spec.PortalIP)
|
||||||
}
|
}
|
||||||
@@ -657,9 +642,9 @@ func TestCreateServiceWithConflictingNamespace(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx := api.NewDefaultContext()
|
ctx := api.NewDefaultContext()
|
||||||
channel, err := storage.Create(ctx, service)
|
obj, err := storage.Create(ctx, service)
|
||||||
if channel != nil {
|
if obj != nil {
|
||||||
t.Error("Expected a nil channel, but we got a value")
|
t.Error("Expected a nil object, but we got a value")
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("Expected an error, but we didn't get one")
|
t.Errorf("Expected an error, but we didn't get one")
|
||||||
@@ -675,9 +660,9 @@ func TestUpdateServiceWithConflictingNamespace(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx := api.NewDefaultContext()
|
ctx := api.NewDefaultContext()
|
||||||
channel, err := storage.Update(ctx, service)
|
obj, created, err := storage.Update(ctx, service)
|
||||||
if channel != nil {
|
if obj != nil || created {
|
||||||
t.Error("Expected a nil channel, but we got a value")
|
t.Error("Expected a nil object, but we got a value or created was true")
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("Expected an error, but we didn't get one")
|
t.Errorf("Expected an error, but we didn't get one")
|
||||||
|
@@ -78,6 +78,20 @@ var aPod string = `
|
|||||||
}%s
|
}%s
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
var aPodInBar string = `
|
||||||
|
{
|
||||||
|
"kind": "Pod",
|
||||||
|
"apiVersion": "v1beta1",
|
||||||
|
"id": "a",
|
||||||
|
"desiredState": {
|
||||||
|
"manifest": {
|
||||||
|
"version": "v1beta1",
|
||||||
|
"id": "a",
|
||||||
|
"containers": [{ "name": "foo", "image": "bar/foo", }]
|
||||||
|
}
|
||||||
|
}%s
|
||||||
|
}
|
||||||
|
`
|
||||||
var aRC string = `
|
var aRC string = `
|
||||||
{
|
{
|
||||||
"kind": "ReplicationController",
|
"kind": "ReplicationController",
|
||||||
@@ -126,7 +140,6 @@ var aEvent string = `
|
|||||||
{
|
{
|
||||||
"kind": "Event",
|
"kind": "Event",
|
||||||
"apiVersion": "v1beta1",
|
"apiVersion": "v1beta1",
|
||||||
"namespace": "default",
|
|
||||||
"id": "a",
|
"id": "a",
|
||||||
"involvedObject": {
|
"involvedObject": {
|
||||||
"kind": "Minion",
|
"kind": "Minion",
|
||||||
@@ -162,6 +175,7 @@ var timeoutFlag = "?timeout=60s"
|
|||||||
// Requests to try. Each one should be forbidden or not forbidden
|
// Requests to try. Each one should be forbidden or not forbidden
|
||||||
// depending on the authentication and authorization setup of the master.
|
// depending on the authentication and authorization setup of the master.
|
||||||
var code200 = map[int]bool{200: true}
|
var code200 = map[int]bool{200: true}
|
||||||
|
var code201 = map[int]bool{201: true}
|
||||||
var code400 = map[int]bool{400: true}
|
var code400 = map[int]bool{400: true}
|
||||||
var code403 = map[int]bool{403: true}
|
var code403 = map[int]bool{403: true}
|
||||||
var code404 = map[int]bool{404: true}
|
var code404 = map[int]bool{404: true}
|
||||||
@@ -184,7 +198,7 @@ func getTestRequests() []struct {
|
|||||||
}{
|
}{
|
||||||
// Normal methods on pods
|
// Normal methods on pods
|
||||||
{"GET", "/api/v1beta1/pods", "", code200},
|
{"GET", "/api/v1beta1/pods", "", code200},
|
||||||
{"POST", "/api/v1beta1/pods" + timeoutFlag, aPod, code200},
|
{"POST", "/api/v1beta1/pods" + timeoutFlag, aPod, code201},
|
||||||
{"PUT", "/api/v1beta1/pods/a" + timeoutFlag, aPod, code200},
|
{"PUT", "/api/v1beta1/pods/a" + timeoutFlag, aPod, code200},
|
||||||
{"GET", "/api/v1beta1/pods", "", code200},
|
{"GET", "/api/v1beta1/pods", "", code200},
|
||||||
{"GET", "/api/v1beta1/pods/a", "", code200},
|
{"GET", "/api/v1beta1/pods/a", "", code200},
|
||||||
@@ -204,7 +218,7 @@ func getTestRequests() []struct {
|
|||||||
|
|
||||||
// Normal methods on services
|
// Normal methods on services
|
||||||
{"GET", "/api/v1beta1/services", "", code200},
|
{"GET", "/api/v1beta1/services", "", code200},
|
||||||
{"POST", "/api/v1beta1/services" + timeoutFlag, aService, code200},
|
{"POST", "/api/v1beta1/services" + timeoutFlag, aService, code201},
|
||||||
{"PUT", "/api/v1beta1/services/a" + timeoutFlag, aService, code200},
|
{"PUT", "/api/v1beta1/services/a" + timeoutFlag, aService, code200},
|
||||||
{"GET", "/api/v1beta1/services", "", code200},
|
{"GET", "/api/v1beta1/services", "", code200},
|
||||||
{"GET", "/api/v1beta1/services/a", "", code200},
|
{"GET", "/api/v1beta1/services/a", "", code200},
|
||||||
@@ -212,7 +226,7 @@ func getTestRequests() []struct {
|
|||||||
|
|
||||||
// Normal methods on replicationControllers
|
// Normal methods on replicationControllers
|
||||||
{"GET", "/api/v1beta1/replicationControllers", "", code200},
|
{"GET", "/api/v1beta1/replicationControllers", "", code200},
|
||||||
{"POST", "/api/v1beta1/replicationControllers" + timeoutFlag, aRC, code200},
|
{"POST", "/api/v1beta1/replicationControllers" + timeoutFlag, aRC, code201},
|
||||||
{"PUT", "/api/v1beta1/replicationControllers/a" + timeoutFlag, aRC, code200},
|
{"PUT", "/api/v1beta1/replicationControllers/a" + timeoutFlag, aRC, code200},
|
||||||
{"GET", "/api/v1beta1/replicationControllers", "", code200},
|
{"GET", "/api/v1beta1/replicationControllers", "", code200},
|
||||||
{"GET", "/api/v1beta1/replicationControllers/a", "", code200},
|
{"GET", "/api/v1beta1/replicationControllers/a", "", code200},
|
||||||
@@ -220,7 +234,7 @@ func getTestRequests() []struct {
|
|||||||
|
|
||||||
// Normal methods on endpoints
|
// Normal methods on endpoints
|
||||||
{"GET", "/api/v1beta1/endpoints", "", code200},
|
{"GET", "/api/v1beta1/endpoints", "", code200},
|
||||||
{"POST", "/api/v1beta1/endpoints" + timeoutFlag, aEndpoints, code200},
|
{"POST", "/api/v1beta1/endpoints" + timeoutFlag, aEndpoints, code201},
|
||||||
{"PUT", "/api/v1beta1/endpoints/a" + timeoutFlag, aEndpoints, code200},
|
{"PUT", "/api/v1beta1/endpoints/a" + timeoutFlag, aEndpoints, code200},
|
||||||
{"GET", "/api/v1beta1/endpoints", "", code200},
|
{"GET", "/api/v1beta1/endpoints", "", code200},
|
||||||
{"GET", "/api/v1beta1/endpoints/a", "", code200},
|
{"GET", "/api/v1beta1/endpoints/a", "", code200},
|
||||||
@@ -228,7 +242,7 @@ func getTestRequests() []struct {
|
|||||||
|
|
||||||
// Normal methods on minions
|
// Normal methods on minions
|
||||||
{"GET", "/api/v1beta1/minions", "", code200},
|
{"GET", "/api/v1beta1/minions", "", code200},
|
||||||
{"POST", "/api/v1beta1/minions" + timeoutFlag, aMinion, code200},
|
{"POST", "/api/v1beta1/minions" + timeoutFlag, aMinion, code201},
|
||||||
{"PUT", "/api/v1beta1/minions/a" + timeoutFlag, aMinion, code409}, // See #2115 about why 409
|
{"PUT", "/api/v1beta1/minions/a" + timeoutFlag, aMinion, code409}, // See #2115 about why 409
|
||||||
{"GET", "/api/v1beta1/minions", "", code200},
|
{"GET", "/api/v1beta1/minions", "", code200},
|
||||||
{"GET", "/api/v1beta1/minions/a", "", code200},
|
{"GET", "/api/v1beta1/minions/a", "", code200},
|
||||||
@@ -236,7 +250,7 @@ func getTestRequests() []struct {
|
|||||||
|
|
||||||
// Normal methods on events
|
// Normal methods on events
|
||||||
{"GET", "/api/v1beta1/events", "", code200},
|
{"GET", "/api/v1beta1/events", "", code200},
|
||||||
{"POST", "/api/v1beta1/events" + timeoutFlag, aEvent, code200},
|
{"POST", "/api/v1beta1/events" + timeoutFlag, aEvent, code201},
|
||||||
{"PUT", "/api/v1beta1/events/a" + timeoutFlag, aEvent, code200},
|
{"PUT", "/api/v1beta1/events/a" + timeoutFlag, aEvent, code200},
|
||||||
{"GET", "/api/v1beta1/events", "", code200},
|
{"GET", "/api/v1beta1/events", "", code200},
|
||||||
{"GET", "/api/v1beta1/events", "", code200},
|
{"GET", "/api/v1beta1/events", "", code200},
|
||||||
@@ -245,8 +259,8 @@ func getTestRequests() []struct {
|
|||||||
|
|
||||||
// Normal methods on bindings
|
// Normal methods on bindings
|
||||||
{"GET", "/api/v1beta1/bindings", "", code405}, // Bindings are write-only
|
{"GET", "/api/v1beta1/bindings", "", code405}, // Bindings are write-only
|
||||||
{"POST", "/api/v1beta1/pods" + timeoutFlag, aPod, code200}, // Need a pod to bind or you get a 404
|
{"POST", "/api/v1beta1/pods" + timeoutFlag, aPod, code201}, // Need a pod to bind or you get a 404
|
||||||
{"POST", "/api/v1beta1/bindings" + timeoutFlag, aBinding, code200},
|
{"POST", "/api/v1beta1/bindings" + timeoutFlag, aBinding, code201},
|
||||||
{"PUT", "/api/v1beta1/bindings/a" + timeoutFlag, aBinding, code404},
|
{"PUT", "/api/v1beta1/bindings/a" + timeoutFlag, aBinding, code404},
|
||||||
{"GET", "/api/v1beta1/bindings", "", code405},
|
{"GET", "/api/v1beta1/bindings", "", code405},
|
||||||
{"GET", "/api/v1beta1/bindings/a", "", code404}, // No bindings instances
|
{"GET", "/api/v1beta1/bindings/a", "", code404}, // No bindings instances
|
||||||
@@ -316,14 +330,16 @@ func TestAuthModeAlwaysAllow(t *testing.T) {
|
|||||||
t.Logf("case %v", r)
|
t.Logf("case %v", r)
|
||||||
var bodyStr string
|
var bodyStr string
|
||||||
if r.body != "" {
|
if r.body != "" {
|
||||||
bodyStr = fmt.Sprintf(r.body, "")
|
sub := ""
|
||||||
if r.verb == "PUT" && r.body != "" {
|
if r.verb == "PUT" && r.body != "" {
|
||||||
// For update operations, insert previous resource version
|
// For update operations, insert previous resource version
|
||||||
if resVersion := previousResourceVersion[getPreviousResourceVersionKey(r.URL, "")]; resVersion != 0 {
|
if resVersion := previousResourceVersion[getPreviousResourceVersionKey(r.URL, "")]; resVersion != 0 {
|
||||||
resourceVersionJson := fmt.Sprintf(",\r\n\"resourceVersion\": %v", resVersion)
|
sub += fmt.Sprintf(",\r\n\"resourceVersion\": %v", resVersion)
|
||||||
bodyStr = fmt.Sprintf(r.body, resourceVersionJson)
|
|
||||||
}
|
}
|
||||||
|
namespace := "default"
|
||||||
|
sub += fmt.Sprintf(",\r\n\"namespace\": %v", namespace)
|
||||||
}
|
}
|
||||||
|
bodyStr = fmt.Sprintf(r.body, sub)
|
||||||
}
|
}
|
||||||
bodyBytes := bytes.NewReader([]byte(bodyStr))
|
bodyBytes := bytes.NewReader([]byte(bodyStr))
|
||||||
req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes)
|
req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes)
|
||||||
@@ -483,14 +499,16 @@ func TestAliceNotForbiddenOrUnauthorized(t *testing.T) {
|
|||||||
t.Logf("case %v", r)
|
t.Logf("case %v", r)
|
||||||
var bodyStr string
|
var bodyStr string
|
||||||
if r.body != "" {
|
if r.body != "" {
|
||||||
bodyStr = fmt.Sprintf(r.body, "")
|
sub := ""
|
||||||
if r.verb == "PUT" && r.body != "" {
|
if r.verb == "PUT" && r.body != "" {
|
||||||
// For update operations, insert previous resource version
|
// For update operations, insert previous resource version
|
||||||
if resVersion := previousResourceVersion[getPreviousResourceVersionKey(r.URL, "")]; resVersion != 0 {
|
if resVersion := previousResourceVersion[getPreviousResourceVersionKey(r.URL, "")]; resVersion != 0 {
|
||||||
resourceVersionJson := fmt.Sprintf(",\r\n\"resourceVersion\": %v", resVersion)
|
sub += fmt.Sprintf(",\r\n\"resourceVersion\": %v", resVersion)
|
||||||
bodyStr = fmt.Sprintf(r.body, resourceVersionJson)
|
|
||||||
}
|
}
|
||||||
|
namespace := "default"
|
||||||
|
sub += fmt.Sprintf(",\r\n\"namespace\": %v", namespace)
|
||||||
}
|
}
|
||||||
|
bodyStr = fmt.Sprintf(r.body, sub)
|
||||||
}
|
}
|
||||||
bodyBytes := bytes.NewReader([]byte(bodyStr))
|
bodyBytes := bytes.NewReader([]byte(bodyStr))
|
||||||
req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes)
|
req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes)
|
||||||
@@ -705,24 +723,25 @@ func TestNamespaceAuthorization(t *testing.T) {
|
|||||||
requests := []struct {
|
requests := []struct {
|
||||||
verb string
|
verb string
|
||||||
URL string
|
URL string
|
||||||
|
namespace string
|
||||||
body string
|
body string
|
||||||
statusCodes map[int]bool // allowed status codes.
|
statusCodes map[int]bool // allowed status codes.
|
||||||
}{
|
}{
|
||||||
|
|
||||||
{"POST", "/api/v1beta1/pods" + timeoutFlag + "&namespace=foo", aPod, code200},
|
{"POST", "/api/v1beta1/pods" + timeoutFlag + "&namespace=foo", "foo", aPod, code201},
|
||||||
{"GET", "/api/v1beta1/pods?namespace=foo", "", code200},
|
{"GET", "/api/v1beta1/pods?namespace=foo", "foo", "", code200},
|
||||||
{"GET", "/api/v1beta1/pods/a?namespace=foo", "", code200},
|
{"GET", "/api/v1beta1/pods/a?namespace=foo", "foo", "", code200},
|
||||||
{"DELETE", "/api/v1beta1/pods/a" + timeoutFlag + "&namespace=foo", "", code200},
|
{"DELETE", "/api/v1beta1/pods/a" + timeoutFlag + "&namespace=foo", "foo", "", code200},
|
||||||
|
|
||||||
{"POST", "/api/v1beta1/pods" + timeoutFlag + "&namespace=bar", aPod, code403},
|
{"POST", "/api/v1beta1/pods" + timeoutFlag + "&namespace=bar", "bar", aPod, code403},
|
||||||
{"GET", "/api/v1beta1/pods?namespace=bar", "", code403},
|
{"GET", "/api/v1beta1/pods?namespace=bar", "bar", "", code403},
|
||||||
{"GET", "/api/v1beta1/pods/a?namespace=bar", "", code403},
|
{"GET", "/api/v1beta1/pods/a?namespace=bar", "bar", "", code403},
|
||||||
{"DELETE", "/api/v1beta1/pods/a" + timeoutFlag + "&namespace=bar", "", code403},
|
{"DELETE", "/api/v1beta1/pods/a" + timeoutFlag + "&namespace=bar", "bar", "", code403},
|
||||||
|
|
||||||
{"POST", "/api/v1beta1/pods" + timeoutFlag, aPod, code403},
|
{"POST", "/api/v1beta1/pods" + timeoutFlag, "", aPod, code403},
|
||||||
{"GET", "/api/v1beta1/pods", "", code403},
|
{"GET", "/api/v1beta1/pods", "", "", code403},
|
||||||
{"GET", "/api/v1beta1/pods/a", "", code403},
|
{"GET", "/api/v1beta1/pods/a", "", "", code403},
|
||||||
{"DELETE", "/api/v1beta1/pods/a" + timeoutFlag, "", code403},
|
{"DELETE", "/api/v1beta1/pods/a" + timeoutFlag, "", "", code403},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, r := range requests {
|
for _, r := range requests {
|
||||||
@@ -730,14 +749,19 @@ func TestNamespaceAuthorization(t *testing.T) {
|
|||||||
t.Logf("case %v", r)
|
t.Logf("case %v", r)
|
||||||
var bodyStr string
|
var bodyStr string
|
||||||
if r.body != "" {
|
if r.body != "" {
|
||||||
bodyStr = fmt.Sprintf(r.body, "")
|
sub := ""
|
||||||
if r.verb == "PUT" && r.body != "" {
|
if r.verb == "PUT" && r.body != "" {
|
||||||
// For update operations, insert previous resource version
|
// For update operations, insert previous resource version
|
||||||
if resVersion := previousResourceVersion[getPreviousResourceVersionKey(r.URL, "")]; resVersion != 0 {
|
if resVersion := previousResourceVersion[getPreviousResourceVersionKey(r.URL, "")]; resVersion != 0 {
|
||||||
resourceVersionJson := fmt.Sprintf(",\r\n\"resourceVersion\": %v", resVersion)
|
sub += fmt.Sprintf(",\r\n\"resourceVersion\": %v", resVersion)
|
||||||
bodyStr = fmt.Sprintf(r.body, resourceVersionJson)
|
|
||||||
}
|
}
|
||||||
|
namespace := r.namespace
|
||||||
|
if len(namespace) == 0 {
|
||||||
|
namespace = "default"
|
||||||
|
}
|
||||||
|
sub += fmt.Sprintf(",\r\n\"namespace\": %v", namespace)
|
||||||
}
|
}
|
||||||
|
bodyStr = fmt.Sprintf(r.body, sub)
|
||||||
}
|
}
|
||||||
bodyBytes := bytes.NewReader([]byte(bodyStr))
|
bodyBytes := bytes.NewReader([]byte(bodyStr))
|
||||||
req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes)
|
req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes)
|
||||||
@@ -815,7 +839,7 @@ func TestKindAuthorization(t *testing.T) {
|
|||||||
body string
|
body string
|
||||||
statusCodes map[int]bool // allowed status codes.
|
statusCodes map[int]bool // allowed status codes.
|
||||||
}{
|
}{
|
||||||
{"POST", "/api/v1beta1/services" + timeoutFlag, aService, code200},
|
{"POST", "/api/v1beta1/services" + timeoutFlag, aService, code201},
|
||||||
{"GET", "/api/v1beta1/services", "", code200},
|
{"GET", "/api/v1beta1/services", "", code200},
|
||||||
{"GET", "/api/v1beta1/services/a", "", code200},
|
{"GET", "/api/v1beta1/services/a", "", code200},
|
||||||
{"DELETE", "/api/v1beta1/services/a" + timeoutFlag, "", code200},
|
{"DELETE", "/api/v1beta1/services/a" + timeoutFlag, "", code200},
|
||||||
|
Reference in New Issue
Block a user