Implement bindings

This will effectively cause no changes until we remove the assignPod
call from CreatePod().
This commit is contained in:
Daniel Smith 2014-08-15 16:01:33 -07:00
parent 852b46a345
commit 138b560efb
7 changed files with 178 additions and 12 deletions

View File

@ -25,6 +25,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/binding"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/controller"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/endpoint"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/etcd"
@ -56,6 +57,7 @@ type Master struct {
controllerRegistry controller.Registry
serviceRegistry service.Registry
minionRegistry minion.Registry
bindingRegistry binding.Registry
storage map[string]apiserver.RESTStorage
client *client.Client
}
@ -68,6 +70,7 @@ func New(c *Config) *Master {
podRegistry: etcd.NewRegistry(etcdClient, minionRegistry),
controllerRegistry: etcd.NewRegistry(etcdClient, minionRegistry),
serviceRegistry: etcd.NewRegistry(etcdClient, minionRegistry),
bindingRegistry: etcd.NewRegistry(etcdClient, minionRegistry),
minionRegistry: minionRegistry,
client: c.Client,
}
@ -122,6 +125,9 @@ func (m *Master) init(cloud cloudprovider.Interface, podInfoGetter client.PodInf
"replicationControllers": controller.NewRegistryStorage(m.controllerRegistry, m.podRegistry),
"services": service.NewRegistryStorage(m.serviceRegistry, cloud, m.minionRegistry),
"minions": minion.NewRegistryStorage(m.minionRegistry),
// TODO: should appear only in scheduler API group.
"bindings": binding.NewBindingStorage(m.bindingRegistry),
}
}

View File

@ -0,0 +1,21 @@
/*
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

@ -0,0 +1,30 @@
/*
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(binding *api.Binding) error {
return mr.OnApplyBinding(binding)
}

View File

@ -0,0 +1,28 @@
/*
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(binding *api.Binding) error
}

View File

@ -22,20 +22,19 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod"
)
// BindingStorage implements the RESTStorage interface. 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 BindingStorage struct {
podRegistry pod.Registry
registry Registry
}
// MakeBindingStorage makes a new BindingStorage backed by the given PodRegistry.
func MakeBindingStorage(podRegistry pod.Registry) *BindingStorage {
// NewBindingStorage makes a new BindingStorage backed by the given bindingRegistry.
func NewBindingStorage(bindingRegistry Registry) *BindingStorage {
return &BindingStorage{
podRegistry: podRegistry,
registry: bindingRegistry,
}
}
@ -65,8 +64,12 @@ func (b *BindingStorage) Create(obj interface{}) (<-chan interface{}, error) {
if !ok {
return nil, fmt.Errorf("incorrect type: %#v", obj)
}
_ = binding
return nil, fmt.Errorf("Implementation coming in the future. Storage layer can't easily support this yet.")
return apiserver.MakeAsync(func() (interface{}, error) {
if err := b.registry.ApplyBinding(binding); err != nil {
return nil, err
}
return &api.Status{Status: api.StatusSuccess}, nil
}), nil
}
// Update returns an error-- this object may not be updated.

View File

@ -17,14 +17,20 @@ limitations under the License.
package binding
import (
"errors"
"net/http"
"reflect"
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
)
func TestBindingStorage_Extract(t *testing.T) {
b := &BindingStorage{}
func TestNewBindingStorage(t *testing.T) {
mockRegistry := MockRegistry{
OnApplyBinding: func(b *api.Binding) error { return nil },
}
b := NewBindingStorage(mockRegistry)
binding := &api.Binding{
PodID: "foo",
@ -43,3 +49,67 @@ func TestBindingStorage_Extract(t *testing.T) {
t.Errorf("Expected %#v, but got %#v", e, a)
}
}
func TestBindingStorageUnsupported(t *testing.T) {
mockRegistry := MockRegistry{
OnApplyBinding: func(b *api.Binding) error { return nil },
}
b := NewBindingStorage(mockRegistry)
if _, err := b.Delete("binding id"); err == nil {
t.Errorf("unexpected non-error")
}
if _, err := b.Update(&api.Binding{PodID: "foo", Host: "new machine"}); err == nil {
t.Errorf("unexpected non-error")
}
if _, err := b.Get("binding id"); err == nil {
t.Errorf("unexpected non-error")
}
if _, err := b.List(labels.Set{"name": "foo"}.AsSelector()); err == nil {
t.Errorf("unexpected non-error")
}
// Try sending wrong object just to get 100% coverage
if _, err := b.Create(&api.Pod{}); err == nil {
t.Errorf("unexpected non-error")
}
}
func TestBindingStoragePost(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
},
}
b := NewBindingStorage(mockRegistry)
resultChan, err := b.Create(item.b)
if err != nil {
t.Errorf("Unexpected error %v", err)
continue
}
var expect *api.Status
if item.err == nil {
expect = &api.Status{Status: api.StatusSuccess}
} else {
expect = &api.Status{
Status: api.StatusFailure,
Code: http.StatusInternalServerError,
Message: item.err.Error(),
}
}
if e, a := expect, <-resultChan; !reflect.DeepEqual(e, a) {
t.Errorf("%v: expected %#v, got %#v", i, e, a)
}
}
}

View File

@ -111,12 +111,17 @@ func (r *Registry) CreatePod(machine string, pod api.Pod) error {
return err
}
// TODO: Until scheduler separation is completed, just assign here.
return r.AssignPod(pod.ID, machine)
return r.assignPod(pod.ID, machine)
}
// AssignPod assigns the given pod to the given machine.
// ApplyBinding implements binding's registry
func (r *Registry) ApplyBinding(binding *api.Binding) error {
return r.assignPod(binding.PodID, binding.Host)
}
// assignPod assigns the given pod to the given machine.
// TODO: hook this up via apiserver, not by calling it from CreatePod().
func (r *Registry) AssignPod(podID string, machine string) error {
func (r *Registry) assignPod(podID string, machine string) error {
podKey := makePodKey(podID)
var finalPod *api.Pod
err := r.AtomicUpdate(podKey, &api.Pod{}, func(obj interface{}) (interface{}, error) {
@ -124,6 +129,9 @@ func (r *Registry) AssignPod(podID string, machine string) error {
if !ok {
return nil, fmt.Errorf("unexpected object: %#v", obj)
}
if pod.DesiredState.Host != "" {
return nil, fmt.Errorf("pod %v is already assigned to host %v", pod.ID, pod.DesiredState.Host)
}
pod.DesiredState.Host = machine
finalPod = pod
return pod, nil