Commit 367284b6 authored by Clayton Coleman's avatar Clayton Coleman

Merge pull request #5767 from kazegusuri/endpoints_storage

port endpoints to generic etcd
parents a163f780 410e11c3
...@@ -151,6 +151,13 @@ func ValidateSecretName(name string, prefix bool) (bool, string) { ...@@ -151,6 +151,13 @@ func ValidateSecretName(name string, prefix bool) (bool, string) {
return nameIsDNSSubdomain(name, prefix) return nameIsDNSSubdomain(name, prefix)
} }
// ValidateEndpointsName can be used to check whether the given endpoints name is valid.
// Prefix indicates this name will be used as part of generation, in which case
// trailing dashes are allowed.
func ValidateEndpointsName(name string, prefix bool) (bool, string) {
return nameIsDNSSubdomain(name, prefix)
}
// nameIsDNSSubdomain is a ValidateNameFunc for names that must be a DNS subdomain. // nameIsDNSSubdomain is a ValidateNameFunc for names that must be a DNS subdomain.
func nameIsDNSSubdomain(name string, prefix bool) (bool, string) { func nameIsDNSSubdomain(name string, prefix bool) (bool, string) {
if prefix { if prefix {
...@@ -1087,7 +1094,7 @@ func validateFinalizerName(stringValue string) errs.ValidationErrorList { ...@@ -1087,7 +1094,7 @@ func validateFinalizerName(stringValue string) errs.ValidationErrorList {
return errs.ValidationErrorList{} return errs.ValidationErrorList{}
} }
// ValidateNamespaceUpdate tests to make sure a mamespace update can be applied. Modifies oldNamespace. // ValidateNamespaceUpdate tests to make sure a namespace update can be applied. Modifies oldNamespace.
func ValidateNamespaceUpdate(oldNamespace *api.Namespace, namespace *api.Namespace) errs.ValidationErrorList { func ValidateNamespaceUpdate(oldNamespace *api.Namespace, namespace *api.Namespace) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{} allErrs := errs.ValidationErrorList{}
allErrs = append(allErrs, ValidateObjectMetaUpdate(&oldNamespace.ObjectMeta, &namespace.ObjectMeta).Prefix("metadata")...) allErrs = append(allErrs, ValidateObjectMetaUpdate(&oldNamespace.ObjectMeta, &namespace.ObjectMeta).Prefix("metadata")...)
...@@ -1127,3 +1134,17 @@ func ValidateNamespaceFinalizeUpdate(newNamespace, oldNamespace *api.Namespace) ...@@ -1127,3 +1134,17 @@ func ValidateNamespaceFinalizeUpdate(newNamespace, oldNamespace *api.Namespace)
fmt.Printf("NEW NAMESPACE FINALIZERS : %v\n", newNamespace.Spec.Finalizers) fmt.Printf("NEW NAMESPACE FINALIZERS : %v\n", newNamespace.Spec.Finalizers)
return allErrs return allErrs
} }
// ValidateEndpoints tests if required fields are set.
func ValidateEndpoints(endpoints *api.Endpoints) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}
allErrs = append(allErrs, ValidateObjectMeta(&endpoints.ObjectMeta, true, ValidateEndpointsName).Prefix("metadata")...)
return allErrs
}
// ValidateEndpointsUpdate tests to make sure an endpoints update can be applied.
func ValidateEndpointsUpdate(oldEndpoints *api.Endpoints, endpoints *api.Endpoints) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}
allErrs = append(allErrs, ValidateObjectMetaUpdate(&oldEndpoints.ObjectMeta, &endpoints.ObjectMeta).Prefix("metadata")...)
return allErrs
}
...@@ -44,6 +44,7 @@ import ( ...@@ -44,6 +44,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/master/ports" "github.com/GoogleCloudPlatform/kubernetes/pkg/master/ports"
controlleretcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/controller/etcd" controlleretcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/controller/etcd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/endpoint" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/endpoint"
endpointsetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/endpoint/etcd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/etcd" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/etcd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/event" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/event"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/limitrange" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/limitrange"
...@@ -361,11 +362,13 @@ func (m *Master) init(c *Config) { ...@@ -361,11 +362,13 @@ func (m *Master) init(c *Config) {
namespaceStorage, namespaceStatusStorage, namespaceFinalizeStorage := namespaceetcd.NewStorage(c.EtcdHelper) namespaceStorage, namespaceStatusStorage, namespaceFinalizeStorage := namespaceetcd.NewStorage(c.EtcdHelper)
m.namespaceRegistry = namespace.NewRegistry(namespaceStorage) m.namespaceRegistry = namespace.NewRegistry(namespaceStorage)
endpointsStorage := endpointsetcd.NewStorage(c.EtcdHelper)
m.endpointRegistry = endpoint.NewRegistry(endpointsStorage)
// TODO: split me up into distinct storage registries // TODO: split me up into distinct storage registries
registry := etcd.NewRegistry(c.EtcdHelper, podRegistry) registry := etcd.NewRegistry(c.EtcdHelper, podRegistry, m.endpointRegistry)
m.serviceRegistry = registry m.serviceRegistry = registry
m.endpointRegistry = registry
m.nodeRegistry = registry m.nodeRegistry = registry
nodeStorage := minion.NewStorage(m.nodeRegistry, c.KubeletClient) nodeStorage := minion.NewStorage(m.nodeRegistry, c.KubeletClient)
...@@ -394,8 +397,8 @@ func (m *Master) init(c *Config) { ...@@ -394,8 +397,8 @@ func (m *Master) init(c *Config) {
"bindings": bindingStorage, "bindings": bindingStorage,
"replicationControllers": controllerStorage, "replicationControllers": controllerStorage,
"services": service.NewStorage(m.serviceRegistry, c.Cloud, m.nodeRegistry, m.portalNet, c.ClusterName), "services": service.NewStorage(m.serviceRegistry, c.Cloud, m.nodeRegistry, m.endpointRegistry, m.portalNet, c.ClusterName),
"endpoints": endpoint.NewStorage(m.endpointRegistry), "endpoints": endpointsStorage,
"minions": nodeStorage, "minions": nodeStorage,
"nodes": nodeStorage, "nodes": nodeStorage,
"events": event.NewStorage(eventRegistry), "events": event.NewStorage(eventRegistry),
......
/*
Copyright 2015 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 etcd
import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/endpoint"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
etcdgeneric "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic/etcd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
)
// rest implements a RESTStorage for endpoints against etcd
type REST struct {
*etcdgeneric.Etcd
}
// NewStorage returns a RESTStorage object that will work against endpoints.
func NewStorage(h tools.EtcdHelper) *REST {
prefix := "/registry/services/endpoints"
return &REST{
&etcdgeneric.Etcd{
NewFunc: func() runtime.Object { return &api.Endpoints{} },
NewListFunc: func() runtime.Object { return &api.EndpointsList{} },
KeyRootFunc: func(ctx api.Context) string {
return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix)
},
KeyFunc: func(ctx api.Context, name string) (string, error) {
return etcdgeneric.NamespaceKeyFunc(ctx, prefix, name)
},
ObjectNameFunc: func(obj runtime.Object) (string, error) {
return obj.(*api.Endpoints).Name, nil
},
PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher {
return endpoint.MatchEndpoints(label, field)
},
EndpointName: "endpoints",
CreateStrategy: endpoint.Strategy,
UpdateStrategy: endpoint.Strategy,
Helper: h,
},
}
}
/*
Copyright 2015 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 etcd
import (
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest/resttest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/coreos/go-etcd/etcd"
)
func newHelper(t *testing.T) (*tools.FakeEtcdClient, tools.EtcdHelper) {
fakeEtcdClient := tools.NewFakeEtcdClient(t)
fakeEtcdClient.TestIndex = true
helper := tools.NewEtcdHelper(fakeEtcdClient, latest.Codec)
return fakeEtcdClient, helper
}
func newStorage(t *testing.T) (*REST, *tools.FakeEtcdClient) {
fakeEtcdClient, h := newHelper(t)
storage := NewStorage(h)
return storage, fakeEtcdClient
}
func validNewEndpoints() *api.Endpoints {
return &api.Endpoints{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Protocol: "TCP",
Endpoints: []api.Endpoint{{IP: "baz"}},
}
}
func validChangedEndpoints() *api.Endpoints {
endpoints := validNewEndpoints()
endpoints.ResourceVersion = "1"
endpoints.Endpoints = []api.Endpoint{{IP: "baz"}, {IP: "bar"}}
return endpoints
}
func TestCreate(t *testing.T) {
storage, fakeEtcdClient := newStorage(t)
test := resttest.New(t, storage, fakeEtcdClient.SetError)
endpoints := validNewEndpoints()
endpoints.ObjectMeta = api.ObjectMeta{}
test.TestCreate(
// valid
endpoints,
// invalid
&api.Endpoints{
ObjectMeta: api.ObjectMeta{Name: "_-a123-a_"},
},
)
}
func TestDelete(t *testing.T) {
ctx := api.NewDefaultContext()
storage, fakeEtcdClient := newStorage(t)
test := resttest.New(t, storage, fakeEtcdClient.SetError)
endpoints := validChangedEndpoints()
key, _ := storage.KeyFunc(ctx, endpoints.Name)
createFn := func() runtime.Object {
fakeEtcdClient.Data[key] = tools.EtcdResponseWithError{
R: &etcd.Response{
Node: &etcd.Node{
Value: runtime.EncodeOrDie(latest.Codec, endpoints),
ModifiedIndex: 1,
},
},
}
return endpoints
}
gracefulSetFn := func() bool {
if fakeEtcdClient.Data[key].R.Node == nil {
return false
}
return fakeEtcdClient.Data[key].R.Node.TTL == 30
}
test.TestDeleteNoGraceful(createFn, gracefulSetFn)
}
func TestEtcdListEndpoints(t *testing.T) {
ctx := api.NewDefaultContext()
storage, fakeClient := newStorage(t)
key := storage.KeyRootFunc(ctx)
fakeClient.Data[key] = tools.EtcdResponseWithError{
R: &etcd.Response{
Node: &etcd.Node{
Nodes: []*etcd.Node{
{
Value: runtime.EncodeOrDie(latest.Codec, &api.Endpoints{ObjectMeta: api.ObjectMeta{Name: "foo"}, Protocol: "TCP", Endpoints: []api.Endpoint{{IP: "127.0.0.1", Port: 8345}}}),
},
{
Value: runtime.EncodeOrDie(latest.Codec, &api.Endpoints{ObjectMeta: api.ObjectMeta{Name: "bar"}, Protocol: "TCP"}),
},
},
},
},
E: nil,
}
endpointsObj, err := storage.List(ctx, labels.Everything(), fields.Everything())
if err != nil {
t.Errorf("unexpected error: %v", err)
}
endpoints := endpointsObj.(*api.EndpointsList)
if len(endpoints.Items) != 2 || endpoints.Items[0].Name != "foo" || endpoints.Items[1].Name != "bar" {
t.Errorf("Unexpected endpoints list: %#v", endpoints)
}
}
func TestEtcdGetEndpoints(t *testing.T) {
ctx := api.NewDefaultContext()
storage, fakeClient := newStorage(t)
endpoints := validNewEndpoints()
name := endpoints.Name
key, _ := storage.KeyFunc(ctx, name)
fakeClient.Set(key, runtime.EncodeOrDie(latest.Codec, endpoints), 0)
response, err := fakeClient.Get(key, false, false)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
var endpointsOut api.Endpoints
err = latest.Codec.DecodeInto([]byte(response.Node.Value), &endpointsOut)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
obj, err := storage.Get(ctx, name)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
got := obj.(*api.Endpoints)
endpoints.ObjectMeta.ResourceVersion = got.ObjectMeta.ResourceVersion
if e, a := endpoints, got; !api.Semantic.DeepEqual(*e, *a) {
t.Errorf("Unexpected endpoints: %#v, expected %#v", e, a)
}
}
func TestListEmptyEndpointsList(t *testing.T) {
ctx := api.NewDefaultContext()
storage, fakeClient := newStorage(t)
fakeClient.ChangeIndex = 1
key := storage.KeyRootFunc(ctx)
fakeClient.Data[key] = tools.EtcdResponseWithError{
R: &etcd.Response{},
E: fakeClient.NewError(tools.EtcdErrorCodeNotFound),
}
endpoints, err := storage.List(ctx, labels.Everything(), fields.Everything())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(endpoints.(*api.EndpointsList).Items) != 0 {
t.Errorf("Unexpected non-zero pod list: %#v", endpoints)
}
if endpoints.(*api.EndpointsList).ResourceVersion != "1" {
t.Errorf("Unexpected resource version: %#v", endpoints)
}
}
func TestListEndpointsList(t *testing.T) {
ctx := api.NewDefaultContext()
storage, fakeClient := newStorage(t)
fakeClient.ChangeIndex = 1
key := storage.KeyRootFunc(ctx)
fakeClient.Data[key] = tools.EtcdResponseWithError{
R: &etcd.Response{
Node: &etcd.Node{
Nodes: []*etcd.Node{
{
Value: runtime.EncodeOrDie(latest.Codec, &api.Endpoints{
ObjectMeta: api.ObjectMeta{Name: "foo"},
}),
},
{
Value: runtime.EncodeOrDie(latest.Codec, &api.Endpoints{
ObjectMeta: api.ObjectMeta{Name: "bar"},
}),
},
},
},
},
}
endpointsObj, err := storage.List(ctx, labels.Everything(), fields.Everything())
endpoints := endpointsObj.(*api.EndpointsList)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(endpoints.Items) != 2 {
t.Errorf("Unexpected endpoints list: %#v", endpoints)
}
if endpoints.Items[0].Name != "foo" {
t.Errorf("Unexpected endpoints: %#v", endpoints.Items[0])
}
if endpoints.Items[1].Name != "bar" {
t.Errorf("Unexpected endpoints: %#v", endpoints.Items[1])
}
}
func TestEndpointsDecode(t *testing.T) {
storage, _ := newStorage(t)
expected := validNewEndpoints()
body, err := latest.Codec.Encode(expected)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
actual := storage.New()
if err := latest.Codec.DecodeInto(body, actual); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !api.Semantic.DeepEqual(expected, actual) {
t.Errorf("mismatch: %s", util.ObjectDiff(expected, actual))
}
}
func TestEtcdUpdateEndpoints(t *testing.T) {
ctx := api.NewDefaultContext()
storage, fakeClient := newStorage(t)
endpoints := validChangedEndpoints()
key, _ := storage.KeyFunc(ctx, "foo")
fakeClient.Set(key, runtime.EncodeOrDie(latest.Codec, validNewEndpoints()), 0)
_, _, err := storage.Update(ctx, endpoints)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
response, err := fakeClient.Get(key, false, false)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
var endpointsOut api.Endpoints
err = latest.Codec.DecodeInto([]byte(response.Node.Value), &endpointsOut)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
endpoints.ObjectMeta.ResourceVersion = endpointsOut.ObjectMeta.ResourceVersion
if !api.Semantic.DeepEqual(endpoints, &endpointsOut) {
t.Errorf("Unexpected endpoints: %#v, expected %#v", &endpointsOut, endpoints)
}
}
func TestDeleteEndpoints(t *testing.T) {
ctx := api.NewDefaultContext()
storage, fakeClient := newStorage(t)
endpoints := validNewEndpoints()
name := endpoints.Name
key, _ := storage.KeyFunc(ctx, name)
fakeClient.ChangeIndex = 1
fakeClient.Data[key] = tools.EtcdResponseWithError{
R: &etcd.Response{
Node: &etcd.Node{
Value: runtime.EncodeOrDie(latest.Codec, endpoints),
ModifiedIndex: 1,
CreatedIndex: 1,
},
},
}
_, err := storage.Delete(ctx, name, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
...@@ -18,6 +18,7 @@ package endpoint ...@@ -18,6 +18,7 @@ package endpoint
import ( import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields" "github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch" "github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
...@@ -29,4 +30,46 @@ type Registry interface { ...@@ -29,4 +30,46 @@ type Registry interface {
GetEndpoints(ctx api.Context, name string) (*api.Endpoints, error) GetEndpoints(ctx api.Context, name string) (*api.Endpoints, error)
WatchEndpoints(ctx api.Context, labels labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) WatchEndpoints(ctx api.Context, labels labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error)
UpdateEndpoints(ctx api.Context, e *api.Endpoints) error UpdateEndpoints(ctx api.Context, e *api.Endpoints) error
DeleteEndpoints(ctx api.Context, name string) error
}
// storage puts strong typing around storage calls
type storage struct {
rest.StandardStorage
}
// NewRegistry returns a new Registry interface for the given Storage. Any mismatched
// types will panic.
func NewRegistry(s rest.StandardStorage) Registry {
return &storage{s}
}
func (s *storage) ListEndpoints(ctx api.Context) (*api.EndpointsList, error) {
obj, err := s.List(ctx, labels.Everything(), fields.Everything())
if err != nil {
return nil, err
}
return obj.(*api.EndpointsList), nil
}
func (s *storage) WatchEndpoints(ctx api.Context, label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) {
return s.Watch(ctx, label, field, resourceVersion)
}
func (s *storage) GetEndpoints(ctx api.Context, name string) (*api.Endpoints, error) {
obj, err := s.Get(ctx, name)
if err != nil {
return nil, err
}
return obj.(*api.Endpoints), nil
}
func (s *storage) UpdateEndpoints(ctx api.Context, endpoints *api.Endpoints) error {
_, _, err := s.Update(ctx, endpoints)
return err
}
func (s *storage) DeleteEndpoints(ctx api.Context, name string) error {
_, err := s.Delete(ctx, name, nil)
return err
} }
...@@ -20,84 +20,65 @@ import ( ...@@ -20,84 +20,65 @@ import (
"fmt" "fmt"
"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/validation"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields" "github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch" "github.com/GoogleCloudPlatform/kubernetes/pkg/util/fielderrors"
) )
// REST adapts endpoints into apiserver's RESTStorage model. // endpointsStrategy implements behavior for Endpoints
type REST struct { type endpointsStrategy struct {
registry Registry runtime.ObjectTyper
api.NameGenerator
} }
// NewStorage returns a new rest.Storage implementation for endpoints // Strategy is the default logic that applies when creating and updating Endpoint
func NewStorage(registry Registry) *REST { // objects via the REST API.
return &REST{ var Strategy = endpointsStrategy{api.Scheme, api.SimpleNameGenerator}
registry: registry,
}
}
// Get satisfies the RESTStorage interface. // NamespaceScoped is true for endpoints.
func (rs *REST) Get(ctx api.Context, id string) (runtime.Object, error) { func (endpointsStrategy) NamespaceScoped() bool {
return rs.registry.GetEndpoints(ctx, id) return true
} }
// List satisfies the RESTStorage interface. // ResetBeforeCreate clears fields that are not allowed to be set by end users on creation.
func (rs *REST) List(ctx api.Context, label labels.Selector, field fields.Selector) (runtime.Object, error) { func (endpointsStrategy) ResetBeforeCreate(obj runtime.Object) {
if !label.Empty() || !field.Empty() { _ = obj.(*api.Endpoints)
return nil, errors.NewBadRequest("label/field selectors are not supported on endpoints")
}
return rs.registry.ListEndpoints(ctx)
} }
// Watch returns Endpoint events via a watch.Interface. // Validate validates a new endpoints.
// It implements rest.Watcher. func (endpointsStrategy) Validate(obj runtime.Object) fielderrors.ValidationErrorList {
func (rs *REST) Watch(ctx api.Context, label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) { return validation.ValidateEndpoints(obj.(*api.Endpoints))
return rs.registry.WatchEndpoints(ctx, label, field, resourceVersion)
} }
// Create satisfies the RESTStorage interface. // AllowCreateOnUpdate is true for endpoints.
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) { func (endpointsStrategy) AllowCreateOnUpdate() bool {
endpoints, ok := obj.(*api.Endpoints) return true
if !ok {
return nil, fmt.Errorf("not an endpoints: %#v", obj)
}
if len(endpoints.Name) == 0 {
return nil, fmt.Errorf("id is required: %#v", obj)
}
if !api.ValidNamespace(ctx, &endpoints.ObjectMeta) {
return nil, errors.NewConflict("endpoints", endpoints.Namespace, fmt.Errorf("Endpoints.Namespace does not match the provided context"))
}
api.FillObjectMetaSystemFields(ctx, &endpoints.ObjectMeta)
err := rs.registry.UpdateEndpoints(ctx, endpoints)
if err != nil {
return nil, err
}
return rs.registry.GetEndpoints(ctx, endpoints.Name)
} }
// Update satisfies the RESTStorage interface. // ValidateUpdate is the default update validation for an end user.
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) { func (endpointsStrategy) ValidateUpdate(obj, old runtime.Object) fielderrors.ValidationErrorList {
endpoints, ok := obj.(*api.Endpoints) return validation.ValidateEndpointsUpdate(old.(*api.Endpoints), obj.(*api.Endpoints))
if !ok {
return nil, false, fmt.Errorf("not an endpoints: %#v", obj)
}
err := rs.registry.UpdateEndpoints(ctx, endpoints)
if err != nil {
return nil, false, err
}
out, err := rs.registry.GetEndpoints(ctx, endpoints.Name)
return out, false, err
} }
// New implements the RESTStorage interface. // MatchEndpoints returns a generic matcher for a given label and field selector.
func (rs REST) New() runtime.Object { func MatchEndpoints(label labels.Selector, field fields.Selector) generic.Matcher {
return &api.Endpoints{} return generic.MatcherFunc(func(obj runtime.Object) (bool, error) {
endpoints, ok := obj.(*api.Endpoints)
if !ok {
return false, fmt.Errorf("not a endpoints")
}
fields := EndpointsToSelectableFields(endpoints)
return label.Matches(labels.Set(endpoints.Labels)) && field.Matches(fields), nil
})
} }
func (*REST) NewList() runtime.Object { // EndpointsToSelectableFields returns a label set that represents the object
return &api.EndpointsList{} // TODO: fields are not labels, and the validation rules for them do not apply.
func EndpointsToSelectableFields(endpoints *api.Endpoints) labels.Set {
return labels.Set{
"name": endpoints.Name,
}
} }
/*
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 endpoint
import (
"reflect"
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest"
)
func TestGetEndpoints(t *testing.T) {
registry := &registrytest.ServiceRegistry{
Endpoints: api.Endpoints{
ObjectMeta: api.ObjectMeta{Name: "foo"},
Endpoints: []api.Endpoint{{IP: "127.0.0.1", Port: 9000}},
},
}
storage := NewStorage(registry)
ctx := api.NewContext()
obj, err := storage.Get(ctx, "foo")
if err != nil {
t.Fatalf("unexpected error: %#v", err)
}
if !reflect.DeepEqual([]api.Endpoint{{IP: "127.0.0.1", Port: 9000}}, obj.(*api.Endpoints).Endpoints) {
t.Errorf("unexpected endpoints: %#v", obj)
}
}
func TestGetEndpointsMissingService(t *testing.T) {
registry := &registrytest.ServiceRegistry{
Err: errors.NewNotFound("service", "foo"),
}
storage := NewStorage(registry)
ctx := api.NewContext()
// returns service not found
_, err := storage.Get(ctx, "foo")
if !errors.IsNotFound(err) || !reflect.DeepEqual(err, errors.NewNotFound("service", "foo")) {
t.Errorf("expected NotFound error, got %#v", err)
}
// returns empty endpoints
registry.Err = nil
registry.Service = &api.Service{
ObjectMeta: api.ObjectMeta{Name: "foo"},
}
obj, err := storage.Get(ctx, "foo")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if obj.(*api.Endpoints).Endpoints != nil {
t.Errorf("unexpected endpoints: %#v", obj)
}
}
func TestEndpointsRegistryList(t *testing.T) {
registry := registrytest.NewServiceRegistry()
storage := NewStorage(registry)
registry.EndpointsList = api.EndpointsList{
ListMeta: api.ListMeta{ResourceVersion: "1"},
Items: []api.Endpoints{
{ObjectMeta: api.ObjectMeta{Name: "foo"}},
{ObjectMeta: api.ObjectMeta{Name: "bar"}},
},
}
ctx := api.NewContext()
s, _ := storage.List(ctx, labels.Everything(), fields.Everything())
sl := s.(*api.EndpointsList)
if len(sl.Items) != 2 {
t.Fatalf("Expected 2 endpoints, but got %v", len(sl.Items))
}
if e, a := "foo", sl.Items[0].Name; e != a {
t.Errorf("Expected %v, but got %v", e, a)
}
if e, a := "bar", sl.Items[1].Name; e != a {
t.Errorf("Expected %v, but got %v", e, a)
}
if sl.ResourceVersion != "1" {
t.Errorf("Unexpected resource version: %#v", sl)
}
}
...@@ -20,9 +20,11 @@ import ( ...@@ -20,9 +20,11 @@ import (
"fmt" "fmt"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
etcderr "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors/etcd" etcderr "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors/etcd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields" "github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/endpoint"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools" "github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
...@@ -36,8 +38,6 @@ const ( ...@@ -36,8 +38,6 @@ const (
ControllerPath string = "/registry/controllers" ControllerPath string = "/registry/controllers"
// ServicePath is the path to service resources in etcd // ServicePath is the path to service resources in etcd
ServicePath string = "/registry/services/specs" ServicePath string = "/registry/services/specs"
// ServiceEndpointPath is the path to service endpoints resources in etcd
ServiceEndpointPath string = "/registry/services/endpoints"
// NodePath is the path to node resources in etcd // NodePath is the path to node resources in etcd
NodePath string = "/registry/minions" NodePath string = "/registry/minions"
) )
...@@ -49,14 +49,16 @@ const ( ...@@ -49,14 +49,16 @@ const (
// MinionRegistry, PodRegistry and ServiceRegistry, backed by etcd. // MinionRegistry, PodRegistry and ServiceRegistry, backed by etcd.
type Registry struct { type Registry struct {
tools.EtcdHelper tools.EtcdHelper
pods pod.Registry pods pod.Registry
endpoints endpoint.Registry
} }
// NewRegistry creates an etcd registry. // NewRegistry creates an etcd registry.
func NewRegistry(helper tools.EtcdHelper, pods pod.Registry) *Registry { func NewRegistry(helper tools.EtcdHelper, pods pod.Registry, endpoints endpoint.Registry) *Registry {
registry := &Registry{ registry := &Registry{
EtcdHelper: helper, EtcdHelper: helper,
pods: pods, pods: pods,
endpoints: endpoints,
} }
return registry return registry
} }
...@@ -226,30 +228,6 @@ func (r *Registry) GetService(ctx api.Context, name string) (*api.Service, error ...@@ -226,30 +228,6 @@ func (r *Registry) GetService(ctx api.Context, name string) (*api.Service, error
return &svc, nil return &svc, nil
} }
// GetEndpoints obtains the endpoints for the service identified by 'name'.
func (r *Registry) GetEndpoints(ctx api.Context, name string) (*api.Endpoints, error) {
var endpoints api.Endpoints
key, err := makeServiceEndpointsKey(ctx, name)
if err != nil {
return nil, err
}
err = r.ExtractObj(key, &endpoints, false)
if err != nil {
return nil, etcderr.InterpretGetError(err, "endpoints", name)
}
return &endpoints, nil
}
// makeServiceEndpointsListKey constructs etcd paths to service endpoint directories enforcing namespace rules.
func makeServiceEndpointsListKey(ctx api.Context) string {
return MakeEtcdListKey(ctx, ServiceEndpointPath)
}
// makeServiceEndpointsListKey constructs etcd paths to service endpoint items enforcing namespace rules.
func makeServiceEndpointsKey(ctx api.Context, name string) (string, error) {
return MakeEtcdItemKey(ctx, ServiceEndpointPath, name)
}
// DeleteService deletes a Service specified by its name. // DeleteService deletes a Service specified by its name.
func (r *Registry) DeleteService(ctx api.Context, name string) error { func (r *Registry) DeleteService(ctx api.Context, name string) error {
key, err := makeServiceKey(ctx, name) key, err := makeServiceKey(ctx, name)
...@@ -263,13 +241,10 @@ func (r *Registry) DeleteService(ctx api.Context, name string) error { ...@@ -263,13 +241,10 @@ func (r *Registry) DeleteService(ctx api.Context, name string) error {
// TODO: can leave dangling endpoints, and potentially return incorrect // TODO: can leave dangling endpoints, and potentially return incorrect
// endpoints if a new service is created with the same name // endpoints if a new service is created with the same name
key, err = makeServiceEndpointsKey(ctx, name) err = r.endpoints.DeleteEndpoints(ctx, name)
if err != nil { if err != nil && !errors.IsNotFound(err) {
return err return err
} }
if err := r.Delete(key, true); err != nil && !tools.IsEtcdNotFound(err) {
return etcderr.InterpretDeleteError(err, "endpoints", name)
}
return nil return nil
} }
...@@ -306,51 +281,6 @@ func (r *Registry) WatchServices(ctx api.Context, label labels.Selector, field f ...@@ -306,51 +281,6 @@ func (r *Registry) WatchServices(ctx api.Context, label labels.Selector, field f
return nil, fmt.Errorf("only the 'name' and default (everything) field selectors are supported") return nil, fmt.Errorf("only the 'name' and default (everything) field selectors are supported")
} }
// ListEndpoints obtains a list of Services.
func (r *Registry) ListEndpoints(ctx api.Context) (*api.EndpointsList, error) {
list := &api.EndpointsList{}
key := makeServiceEndpointsListKey(ctx)
err := r.ExtractToList(key, list)
return list, err
}
// UpdateEndpoints update Endpoints of a Service.
func (r *Registry) UpdateEndpoints(ctx api.Context, endpoints *api.Endpoints) error {
key, err := makeServiceEndpointsKey(ctx, endpoints.Name)
if err != nil {
return err
}
// TODO: this is a really bad misuse of AtomicUpdate, need to compute a diff inside the loop.
err = r.AtomicUpdate(key, &api.Endpoints{}, true,
func(input runtime.Object) (runtime.Object, uint64, error) {
// TODO: racy - label query is returning different results for two simultaneous updaters
return endpoints, 0, nil
})
return etcderr.InterpretUpdateError(err, "endpoints", endpoints.Name)
}
// WatchEndpoints begins watching for new, changed, or deleted endpoint configurations.
func (r *Registry) WatchEndpoints(ctx api.Context, label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) {
version, err := tools.ParseWatchResourceVersion(resourceVersion, "endpoints")
if err != nil {
return nil, err
}
if !label.Empty() {
return nil, fmt.Errorf("label selectors are not supported on endpoints")
}
if value, found := field.RequiresExactMatch("name"); found {
key, err := makeServiceEndpointsKey(ctx, value)
if err != nil {
return nil, err
}
return r.Watch(key, version), nil
}
if field.Empty() {
return r.WatchList(makeServiceEndpointsListKey(ctx), version, tools.Everything)
}
return nil, fmt.Errorf("only the 'ID' and default (everything) field selectors are supported")
}
func makeNodeKey(nodeID string) string { func makeNodeKey(nodeID string) string {
return NodePath + "/" + nodeID return NodePath + "/" + nodeID
} }
......
...@@ -26,6 +26,8 @@ import ( ...@@ -26,6 +26,8 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields" "github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/endpoint"
endpointetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/endpoint/etcd"
etcdgeneric "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic/etcd" etcdgeneric "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic/etcd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod"
podetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod/etcd" podetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod/etcd"
...@@ -36,14 +38,15 @@ import ( ...@@ -36,14 +38,15 @@ import (
) )
func NewTestEtcdRegistry(client tools.EtcdClient) *Registry { func NewTestEtcdRegistry(client tools.EtcdClient) *Registry {
registry := NewRegistry(tools.NewEtcdHelper(client, latest.Codec), nil) registry := NewRegistry(tools.NewEtcdHelper(client, latest.Codec), nil, nil)
return registry return registry
} }
func NewTestEtcdRegistryWithPods(client tools.EtcdClient) *Registry { func NewTestEtcdRegistryWithPods(client tools.EtcdClient) *Registry {
helper := tools.NewEtcdHelper(client, latest.Codec) helper := tools.NewEtcdHelper(client, latest.Codec)
podStorage, _, _ := podetcd.NewStorage(helper) podStorage, _, _ := podetcd.NewStorage(helper)
registry := NewRegistry(helper, pod.NewRegistry(podStorage)) endpointStorage := endpointetcd.NewStorage(helper)
registry := NewRegistry(helper, pod.NewRegistry(podStorage), endpoint.NewRegistry(endpointStorage))
return registry return registry
} }
...@@ -532,10 +535,10 @@ func TestEtcdGetServiceNotFound(t *testing.T) { ...@@ -532,10 +535,10 @@ func TestEtcdGetServiceNotFound(t *testing.T) {
func TestEtcdDeleteService(t *testing.T) { func TestEtcdDeleteService(t *testing.T) {
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
fakeClient := tools.NewFakeEtcdClient(t) fakeClient := tools.NewFakeEtcdClient(t)
registry := NewTestEtcdRegistry(fakeClient) registry := NewTestEtcdRegistryWithPods(fakeClient)
key, _ := makeServiceKey(ctx, "foo") key, _ := makeServiceKey(ctx, "foo")
fakeClient.Set(key, runtime.EncodeOrDie(latest.Codec, &api.Service{ObjectMeta: api.ObjectMeta{Name: "foo"}}), 0) fakeClient.Set(key, runtime.EncodeOrDie(latest.Codec, &api.Service{ObjectMeta: api.ObjectMeta{Name: "foo"}}), 0)
endpointsKey, _ := makeServiceEndpointsKey(ctx, "foo") endpointsKey, _ := etcdgeneric.NamespaceKeyFunc(ctx, "/registry/services/endpoints", "foo")
fakeClient.Set(endpointsKey, runtime.EncodeOrDie(latest.Codec, &api.Endpoints{ObjectMeta: api.ObjectMeta{Name: "foo"}, Protocol: "TCP"}), 0) fakeClient.Set(endpointsKey, runtime.EncodeOrDie(latest.Codec, &api.Endpoints{ObjectMeta: api.ObjectMeta{Name: "foo"}, Protocol: "TCP"}), 0)
err := registry.DeleteService(ctx, "foo") err := registry.DeleteService(ctx, "foo")
...@@ -594,89 +597,6 @@ func TestEtcdUpdateService(t *testing.T) { ...@@ -594,89 +597,6 @@ func TestEtcdUpdateService(t *testing.T) {
} }
} }
func TestEtcdListEndpoints(t *testing.T) {
ctx := api.NewDefaultContext()
fakeClient := tools.NewFakeEtcdClient(t)
key := makeServiceEndpointsListKey(ctx)
fakeClient.Data[key] = tools.EtcdResponseWithError{
R: &etcd.Response{
Node: &etcd.Node{
Nodes: []*etcd.Node{
{
Value: runtime.EncodeOrDie(latest.Codec, &api.Endpoints{ObjectMeta: api.ObjectMeta{Name: "foo"}, Protocol: "TCP", Endpoints: []api.Endpoint{{IP: "127.0.0.1", Port: 8345}}}),
},
{
Value: runtime.EncodeOrDie(latest.Codec, &api.Endpoints{ObjectMeta: api.ObjectMeta{Name: "bar"}, Protocol: "TCP"}),
},
},
},
},
E: nil,
}
registry := NewTestEtcdRegistry(fakeClient)
services, err := registry.ListEndpoints(ctx)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if len(services.Items) != 2 || services.Items[0].Name != "foo" || services.Items[1].Name != "bar" {
t.Errorf("Unexpected endpoints list: %#v", services)
}
}
func TestEtcdGetEndpoints(t *testing.T) {
ctx := api.NewDefaultContext()
fakeClient := tools.NewFakeEtcdClient(t)
registry := NewTestEtcdRegistry(fakeClient)
endpoints := &api.Endpoints{
ObjectMeta: api.ObjectMeta{Name: "foo"},
Protocol: "TCP",
Endpoints: []api.Endpoint{{IP: "127.0.0.1", Port: 34855}},
}
key, _ := makeServiceEndpointsKey(ctx, "foo")
fakeClient.Set(key, runtime.EncodeOrDie(latest.Codec, endpoints), 0)
got, err := registry.GetEndpoints(ctx, "foo")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if e, a := endpoints, got; !api.Semantic.DeepEqual(e, a) {
t.Errorf("Unexpected endpoints: %#v, expected %#v", e, a)
}
}
func TestEtcdUpdateEndpoints(t *testing.T) {
ctx := api.NewDefaultContext()
fakeClient := tools.NewFakeEtcdClient(t)
fakeClient.TestIndex = true
registry := NewTestEtcdRegistry(fakeClient)
endpoints := api.Endpoints{
ObjectMeta: api.ObjectMeta{Name: "foo"},
Protocol: "TCP",
Endpoints: []api.Endpoint{{IP: "baz"}, {IP: "bar"}},
}
key, _ := makeServiceEndpointsKey(ctx, "foo")
fakeClient.Set(key, runtime.EncodeOrDie(latest.Codec, &api.Endpoints{}), 0)
err := registry.UpdateEndpoints(ctx, &endpoints)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
response, err := fakeClient.Get(key, false, false)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
var endpointsOut api.Endpoints
err = latest.Codec.DecodeInto([]byte(response.Node.Value), &endpointsOut)
if !api.Semantic.DeepEqual(endpoints, endpointsOut) {
t.Errorf("Unexpected endpoints: %#v, expected %#v", endpointsOut, endpoints)
}
}
func TestEtcdWatchServices(t *testing.T) { func TestEtcdWatchServices(t *testing.T) {
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
fakeClient := tools.NewFakeEtcdClient(t) fakeClient := tools.NewFakeEtcdClient(t)
...@@ -733,8 +653,8 @@ func TestEtcdWatchServicesBadSelector(t *testing.T) { ...@@ -733,8 +653,8 @@ func TestEtcdWatchServicesBadSelector(t *testing.T) {
func TestEtcdWatchEndpoints(t *testing.T) { func TestEtcdWatchEndpoints(t *testing.T) {
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
fakeClient := tools.NewFakeEtcdClient(t) fakeClient := tools.NewFakeEtcdClient(t)
registry := NewTestEtcdRegistry(fakeClient) registry := NewTestEtcdRegistryWithPods(fakeClient)
watching, err := registry.WatchEndpoints( watching, err := registry.endpoints.WatchEndpoints(
ctx, ctx,
labels.Everything(), labels.Everything(),
fields.SelectorFromSet(fields.Set{"name": "foo"}), fields.SelectorFromSet(fields.Set{"name": "foo"}),
...@@ -762,8 +682,8 @@ func TestEtcdWatchEndpoints(t *testing.T) { ...@@ -762,8 +682,8 @@ func TestEtcdWatchEndpoints(t *testing.T) {
func TestEtcdWatchEndpointsAcrossNamespaces(t *testing.T) { func TestEtcdWatchEndpointsAcrossNamespaces(t *testing.T) {
ctx := api.NewContext() ctx := api.NewContext()
fakeClient := tools.NewFakeEtcdClient(t) fakeClient := tools.NewFakeEtcdClient(t)
registry := NewTestEtcdRegistry(fakeClient) registry := NewTestEtcdRegistryWithPods(fakeClient)
watching, err := registry.WatchEndpoints( watching, err := registry.endpoints.WatchEndpoints(
ctx, ctx,
labels.Everything(), labels.Everything(),
fields.Everything(), fields.Everything(),
...@@ -788,31 +708,6 @@ func TestEtcdWatchEndpointsAcrossNamespaces(t *testing.T) { ...@@ -788,31 +708,6 @@ func TestEtcdWatchEndpointsAcrossNamespaces(t *testing.T) {
watching.Stop() watching.Stop()
} }
func TestEtcdWatchEndpointsBadSelector(t *testing.T) {
ctx := api.NewContext()
fakeClient := tools.NewFakeEtcdClient(t)
registry := NewTestEtcdRegistry(fakeClient)
_, err := registry.WatchEndpoints(
ctx,
labels.Everything(),
fields.SelectorFromSet(fields.Set{"Field.Selector": "foo"}),
"",
)
if err == nil {
t.Errorf("unexpected non-error: %v", err)
}
_, err = registry.WatchEndpoints(
ctx,
labels.SelectorFromSet(labels.Set{"Label.Selector": "foo"}),
fields.Everything(),
"",
)
if err == nil {
t.Errorf("unexpected non-error: %v", err)
}
}
func TestEtcdListMinions(t *testing.T) { func TestEtcdListMinions(t *testing.T) {
ctx := api.NewContext() ctx := api.NewContext()
fakeClient := tools.NewFakeEtcdClient(t) fakeClient := tools.NewFakeEtcdClient(t)
......
...@@ -91,3 +91,7 @@ func (e *EndpointRegistry) UpdateEndpoints(ctx api.Context, endpoints *api.Endpo ...@@ -91,3 +91,7 @@ func (e *EndpointRegistry) UpdateEndpoints(ctx api.Context, endpoints *api.Endpo
e.Endpoints.Items = append(e.Endpoints.Items, *endpoints) e.Endpoints.Items = append(e.Endpoints.Items, *endpoints)
return nil return nil
} }
func (e *EndpointRegistry) DeleteEndpoints(ctx api.Context, name string) error {
return fmt.Errorf("unimplemented!")
}
...@@ -30,12 +30,10 @@ func NewServiceRegistry() *ServiceRegistry { ...@@ -30,12 +30,10 @@ func NewServiceRegistry() *ServiceRegistry {
} }
type ServiceRegistry struct { type ServiceRegistry struct {
mu sync.Mutex mu sync.Mutex
List api.ServiceList List api.ServiceList
Service *api.Service Service *api.Service
Err error Err error
Endpoints api.Endpoints
EndpointsList api.EndpointsList
DeletedID string DeletedID string
GottenID string GottenID string
...@@ -114,33 +112,3 @@ func (r *ServiceRegistry) WatchServices(ctx api.Context, label labels.Selector, ...@@ -114,33 +112,3 @@ func (r *ServiceRegistry) WatchServices(ctx api.Context, label labels.Selector,
return nil, r.Err return nil, r.Err
} }
func (r *ServiceRegistry) ListEndpoints(ctx api.Context) (*api.EndpointsList, error) {
r.mu.Lock()
defer r.mu.Unlock()
return &r.EndpointsList, r.Err
}
func (r *ServiceRegistry) GetEndpoints(ctx api.Context, id string) (*api.Endpoints, error) {
r.mu.Lock()
defer r.mu.Unlock()
r.GottenID = id
return &r.Endpoints, r.Err
}
func (r *ServiceRegistry) UpdateEndpoints(ctx api.Context, e *api.Endpoints) error {
r.mu.Lock()
defer r.mu.Unlock()
r.Endpoints = *e
return r.Err
}
func (r *ServiceRegistry) WatchEndpoints(ctx api.Context, label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) {
r.mu.Lock()
defer r.mu.Unlock()
return nil, r.Err
}
...@@ -20,7 +20,6 @@ import ( ...@@ -20,7 +20,6 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields" "github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/endpoint"
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch" "github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
) )
...@@ -32,8 +31,4 @@ type Registry interface { ...@@ -32,8 +31,4 @@ type Registry interface {
DeleteService(ctx api.Context, name string) error DeleteService(ctx api.Context, name string) error
UpdateService(ctx api.Context, svc *api.Service) (*api.Service, error) UpdateService(ctx api.Context, svc *api.Service) (*api.Service, error)
WatchServices(ctx api.Context, labels labels.Selector, fields fields.Selector, resourceVersion string) (watch.Interface, error) WatchServices(ctx api.Context, labels labels.Selector, fields fields.Selector, resourceVersion string) (watch.Interface, error)
// TODO: endpoints and their implementation should be separated, setting endpoints should be
// supported via the API, and the endpoints-controller should use the API to update endpoints.
endpoint.Registry
} }
...@@ -31,6 +31,7 @@ import ( ...@@ -31,6 +31,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields" "github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/endpoint"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/minion" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/minion"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/fielderrors" "github.com/GoogleCloudPlatform/kubernetes/pkg/util/fielderrors"
...@@ -43,12 +44,13 @@ type REST struct { ...@@ -43,12 +44,13 @@ type REST struct {
registry Registry registry Registry
cloud cloudprovider.Interface cloud cloudprovider.Interface
machines minion.Registry machines minion.Registry
endpoints endpoint.Registry
portalMgr *ipAllocator portalMgr *ipAllocator
clusterName string clusterName string
} }
// NewStorage returns a new REST. // NewStorage returns a new REST.
func NewStorage(registry Registry, cloud cloudprovider.Interface, machines minion.Registry, portalNet *net.IPNet, func NewStorage(registry Registry, cloud cloudprovider.Interface, machines minion.Registry, endpoints endpoint.Registry, portalNet *net.IPNet,
clusterName string) *REST { clusterName string) *REST {
// TODO: Before we can replicate masters, this has to be synced (e.g. lives in etcd) // TODO: Before we can replicate masters, this has to be synced (e.g. lives in etcd)
ipa := newIPAllocator(portalNet) ipa := newIPAllocator(portalNet)
...@@ -61,6 +63,7 @@ func NewStorage(registry Registry, cloud cloudprovider.Interface, machines minio ...@@ -61,6 +63,7 @@ func NewStorage(registry Registry, cloud cloudprovider.Interface, machines minio
registry: registry, registry: registry,
cloud: cloud, cloud: cloud,
machines: machines, machines: machines,
endpoints: endpoints,
portalMgr: ipa, portalMgr: ipa,
clusterName: clusterName, clusterName: clusterName,
} }
...@@ -230,7 +233,7 @@ var _ = rest.Redirector(&REST{}) ...@@ -230,7 +233,7 @@ var _ = rest.Redirector(&REST{})
// 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.
func (rs *REST) ResourceLocation(ctx api.Context, id string) (*url.URL, http.RoundTripper, error) { func (rs *REST) ResourceLocation(ctx api.Context, id string) (*url.URL, http.RoundTripper, error) {
eps, err := rs.registry.GetEndpoints(ctx, id) eps, err := rs.endpoints.GetEndpoints(ctx, id)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
......
...@@ -247,7 +247,7 @@ func getTestRequests() []struct { ...@@ -247,7 +247,7 @@ func getTestRequests() []struct {
{"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},
{"DELETE", "/api/v1beta1/endpoints/a" + timeoutFlag, "", code405}, {"DELETE", "/api/v1beta1/endpoints/a" + timeoutFlag, "", code200},
// Normal methods on minions // Normal methods on minions
{"GET", "/api/v1beta1/minions", "", code200}, {"GET", "/api/v1beta1/minions", "", code200},
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment