Add a subbindings resource as /pods/{name}/binding

Allows POST to create a binding as a child. Also refactors internal
and v1beta3 Binding to be more generic (so that other resources can
support Bindings).
This commit is contained in:
Clayton Coleman
2015-03-04 15:55:41 -05:00
parent 227a1d306d
commit dfc19185f5
19 changed files with 175 additions and 255 deletions

View File

@@ -1,21 +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 binding contains the middle layer logic for bindings.
// Bindings are objects containing instructions for how a pod ought to
// be bound to a host. This allows a registry object which supports this
// action (ApplyBinding) to be served through an apiserver.
package binding

View File

@@ -1,30 +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 binding
import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
)
// MockRegistry can be used for testing.
type MockRegistry struct {
OnApplyBinding func(binding *api.Binding) error
}
func (mr MockRegistry) ApplyBinding(ctx api.Context, binding *api.Binding) error {
return mr.OnApplyBinding(binding)
}

View File

@@ -1,28 +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 binding
import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
)
// Registry contains the functions needed to support a BindingStorage.
type Registry interface {
// ApplyBinding should apply the binding. That is, it should actually
// assign or place pod binding.PodID on machine binding.Host.
ApplyBinding(ctx api.Context, binding *api.Binding) error
}

View File

@@ -1,56 +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 binding
import (
"fmt"
"net/http"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
)
// REST implements the RESTStorage interface for bindings. When bindings are written, it
// changes the location of the affected pods. This information is eventually reflected
// in the pod's CurrentState.Host field.
type REST struct {
registry Registry
}
// NewREST creates a new REST backed by the given bindingRegistry.
func NewREST(bindingRegistry Registry) *REST {
return &REST{
registry: bindingRegistry,
}
}
// New returns a new binding object fit for having data unmarshalled into it.
func (*REST) New() runtime.Object {
return &api.Binding{}
}
// Create attempts to make the assignment indicated by the binding it recieves.
func (b *REST) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) {
binding, ok := obj.(*api.Binding)
if !ok {
return nil, fmt.Errorf("incorrect type: %#v", obj)
}
if err := b.registry.ApplyBinding(ctx, binding); err != nil {
return nil, err
}
return &api.Status{Status: api.StatusSuccess, Code: http.StatusCreated}, nil
}

View File

@@ -1,91 +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 binding
import (
"errors"
"net/http"
"reflect"
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
)
func TestNewREST(t *testing.T) {
mockRegistry := MockRegistry{
OnApplyBinding: func(b *api.Binding) error { return nil },
}
b := NewREST(mockRegistry)
binding := &api.Binding{
PodID: "foo",
Host: "bar",
}
body, err := latest.Codec.Encode(binding)
if err != nil {
t.Fatalf("Unexpected encode error %v", err)
}
obj := b.New()
err = latest.Codec.DecodeInto(body, obj)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
if e, a := binding, obj; !reflect.DeepEqual(e, a) {
t.Errorf("Expected %#v, but got %#v", e, a)
}
}
func TestRESTPost(t *testing.T) {
table := []struct {
b *api.Binding
err error
}{
{b: &api.Binding{PodID: "foo", Host: "bar"}, err: errors.New("no host bar")},
{b: &api.Binding{PodID: "baz", Host: "qux"}, err: nil},
{b: &api.Binding{PodID: "dvorak", Host: "qwerty"}, err: nil},
}
for i, item := range table {
mockRegistry := MockRegistry{
OnApplyBinding: func(b *api.Binding) error {
if !reflect.DeepEqual(item.b, b) {
t.Errorf("%v: expected %#v, but got %#v", i, item, b)
}
return item.err
},
}
ctx := api.NewContext()
b := NewREST(mockRegistry)
result, err := b.Create(ctx, item.b)
if err != nil && item.err == nil {
t.Errorf("Unexpected error %v", err)
continue
}
if err == nil && item.err != nil {
t.Errorf("Unexpected error %v", err)
continue
}
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)
}
}
}

View File

@@ -20,6 +20,7 @@ import (
"fmt"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
etcderr "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors/etcd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/constraint"
@@ -146,7 +147,14 @@ func (r *BindingREST) New() runtime.Object {
// Create ensures a pod is bound to a specific host.
func (r *BindingREST) Create(ctx api.Context, obj runtime.Object) (out runtime.Object, err error) {
binding := obj.(*api.Binding)
err = r.assignPod(ctx, binding.PodID, binding.Host)
// TODO: move me to a binding strategy
if len(binding.Target.Kind) != 0 && (binding.Target.Kind != "Node" && binding.Target.Kind != "Minion") {
return nil, errors.NewInvalid("binding", binding.Name, errors.ValidationErrorList{errors.NewFieldInvalid("to.kind", binding.Target.Kind, "must be empty, 'Node', or 'Minion'")})
}
if len(binding.Target.Name) == 0 {
return nil, errors.NewInvalid("binding", binding.Name, errors.ValidationErrorList{errors.NewFieldRequired("to.name", binding.Target.Name)})
}
err = r.assignPod(ctx, binding.Name, binding.Target.Name)
err = etcderr.InterpretCreateError(err, "binding", "")
out = &api.Status{Status: api.StatusSuccess}
return

View File

@@ -817,7 +817,10 @@ func TestEtcdCreate(t *testing.T) {
}
// Suddenly, a wild scheduler appears:
_, err = bindingRegistry.Create(ctx, &api.Binding{PodID: "foo", Host: "machine", ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault}})
_, err = bindingRegistry.Create(ctx, &api.Binding{
ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault, Name: "foo"},
Target: api.ObjectReference{Name: "machine"},
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@@ -865,7 +868,10 @@ func TestEtcdCreateBindingNoPod(t *testing.T) {
// - Create (apiserver)
// - Schedule (scheduler)
// - Delete (apiserver)
_, err := bindingRegistry.Create(ctx, &api.Binding{PodID: "foo", Host: "machine", ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault}})
_, err := bindingRegistry.Create(ctx, &api.Binding{
ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault, Name: "foo"},
Target: api.ObjectReference{Name: "machine"},
})
if err == nil {
t.Fatalf("Expected not-found-error but got nothing")
}
@@ -935,7 +941,10 @@ func TestEtcdCreateWithContainersError(t *testing.T) {
}
// Suddenly, a wild scheduler appears:
_, err = bindingRegistry.Create(ctx, &api.Binding{PodID: "foo", Host: "machine"})
_, err = bindingRegistry.Create(ctx, &api.Binding{
ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault, Name: "foo"},
Target: api.ObjectReference{Name: "machine"},
})
if !errors.IsAlreadyExists(err) {
t.Fatalf("Unexpected error returned: %#v", err)
}
@@ -973,7 +982,10 @@ func TestEtcdCreateWithContainersNotFound(t *testing.T) {
}
// Suddenly, a wild scheduler appears:
_, err = bindingRegistry.Create(ctx, &api.Binding{PodID: "foo", Host: "machine"})
_, err = bindingRegistry.Create(ctx, &api.Binding{
ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault, Name: "foo"},
Target: api.ObjectReference{Name: "machine"},
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@@ -1025,7 +1037,10 @@ func TestEtcdCreateWithExistingContainers(t *testing.T) {
}
// Suddenly, a wild scheduler appears:
_, err = bindingRegistry.Create(ctx, &api.Binding{PodID: "foo", Host: "machine"})
_, err = bindingRegistry.Create(ctx, &api.Binding{
ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault, Name: "foo"},
Target: api.ObjectReference{Name: "machine"},
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@@ -1055,6 +1070,70 @@ func TestEtcdCreateWithExistingContainers(t *testing.T) {
}
}
func TestEtcdCreateBinding(t *testing.T) {
registry, bindingRegistry, _, fakeClient, _ := newStorage(t)
ctx := api.NewDefaultContext()
fakeClient.TestIndex = true
testCases := map[string]struct {
binding api.Binding
errOK func(error) bool
}{
"noName": {
binding: api.Binding{
ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault, Name: "foo"},
Target: api.ObjectReference{},
},
errOK: func(err error) bool { return errors.IsInvalid(err) },
},
"badKind": {
binding: api.Binding{
ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault, Name: "foo"},
Target: api.ObjectReference{Name: "machine", Kind: "unknown"},
},
errOK: func(err error) bool { return errors.IsInvalid(err) },
},
"emptyKind": {
binding: api.Binding{
ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault, Name: "foo"},
Target: api.ObjectReference{Name: "machine"},
},
errOK: func(err error) bool { return err == nil },
},
"kindNode": {
binding: api.Binding{
ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault, Name: "foo"},
Target: api.ObjectReference{Name: "machine", Kind: "Node"},
},
errOK: func(err error) bool { return err == nil },
},
"kindMinion": {
binding: api.Binding{
ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault, Name: "foo"},
Target: api.ObjectReference{Name: "machine", Kind: "Minion"},
},
errOK: func(err error) bool { return err == nil },
},
}
for k, test := range testCases {
key, _ := registry.store.KeyFunc(ctx, "foo")
fakeClient.Data[key] = tools.EtcdResponseWithError{
R: &etcd.Response{
Node: nil,
},
E: tools.EtcdErrorNotFound,
}
fakeClient.Set("/registry/nodes/machine/boundpods", runtime.EncodeOrDie(latest.Codec, &api.BoundPods{}), 0)
if _, err := registry.Create(ctx, validNewPod()); err != nil {
t.Fatalf("%s: unexpected error: %v", k, err)
}
fakeClient.Set("/registry/nodes/machine/boundpods", runtime.EncodeOrDie(latest.Codec, &api.BoundPods{}), 0)
if _, err := bindingRegistry.Create(ctx, &test.binding); !test.errOK(err) {
t.Errorf("%s: unexpected error: %v", k, err)
}
}
}
func TestEtcdUpdateNotFound(t *testing.T) {
registry, _, _, fakeClient, _ := newStorage(t)
ctx := api.NewDefaultContext()