diff --git a/pkg/master/master.go b/pkg/master/master.go index 341855072b6..cbe6f01ca2c 100644 --- a/pkg/master/master.go +++ b/pkg/master/master.go @@ -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), } } diff --git a/pkg/registry/binding/doc.go b/pkg/registry/binding/doc.go new file mode 100644 index 00000000000..2fdfa915dba --- /dev/null +++ b/pkg/registry/binding/doc.go @@ -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 diff --git a/pkg/registry/binding/mock.go b/pkg/registry/binding/mock.go new file mode 100644 index 00000000000..92af87429c1 --- /dev/null +++ b/pkg/registry/binding/mock.go @@ -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) +} diff --git a/pkg/registry/binding/registry.go b/pkg/registry/binding/registry.go new file mode 100644 index 00000000000..3cf6b9ec99e --- /dev/null +++ b/pkg/registry/binding/registry.go @@ -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 +} diff --git a/pkg/registry/binding/storage.go b/pkg/registry/binding/storage.go index 14926f6c57a..51282c8558a 100644 --- a/pkg/registry/binding/storage.go +++ b/pkg/registry/binding/storage.go @@ -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. diff --git a/pkg/registry/binding/storage_test.go b/pkg/registry/binding/storage_test.go index e60afcf6dc8..a9711e5d8b2 100644 --- a/pkg/registry/binding/storage_test.go +++ b/pkg/registry/binding/storage_test.go @@ -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) + } + } +} diff --git a/pkg/registry/etcd/etcd.go b/pkg/registry/etcd/etcd.go index bf6b70f9bc8..1f54041c8c2 100644 --- a/pkg/registry/etcd/etcd.go +++ b/pkg/registry/etcd/etcd.go @@ -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