Expose REST resource for endpoints and watch on services/endpoints

Will allow kube-proxies to listen on endpoints.
This commit is contained in:
Clayton Coleman
2014-08-14 15:48:34 -04:00
parent b5e1e044bc
commit 083d81b6d7
10 changed files with 483 additions and 3 deletions

View File

@@ -340,10 +340,54 @@ func (r *Registry) UpdateService(svc api.Service) error {
return r.SetObj(makeServiceKey(svc.ID), svc)
}
// WatchServices begins watching for new, changed, or deleted service configurations.
func (r *Registry) WatchServices(label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) {
if !label.Empty() {
return nil, fmt.Errorf("label selectors are not supported on services")
}
if value, found := field.RequiresExactMatch("ID"); found {
return r.Watch(makeServiceKey(value), resourceVersion)
}
if field.Empty() {
return r.WatchList("/registry/services/specs", resourceVersion, tools.Everything)
}
return nil, fmt.Errorf("only the 'ID' and default (everything) field selectors are supported")
}
// GetEndpoints obtains endpoints specified by a service name
func (r *Registry) GetEndpoints(name string) (*api.Endpoints, error) {
obj := &api.Endpoints{}
if err := r.ExtractObj(makeServiceEndpointsKey(name), obj, false); err != nil {
if tools.IsEtcdNotFound(err) {
if _, err := r.GetService(name); err != nil && apiserver.IsNotFound(err) {
return nil, apiserver.NewNotFoundErr("service", name)
}
return obj, nil
}
return nil, err
}
return obj, nil
}
// UpdateEndpoints update Endpoints of a Service.
func (r *Registry) UpdateEndpoints(e api.Endpoints) error {
return r.AtomicUpdate(makeServiceEndpointsKey(e.ID), &api.Endpoints{},
func(interface{}) (interface{}, error) {
func(input interface{}) (interface{}, error) {
// TODO: racy - label query is returning different results for two simultaneous updaters
return e, nil
})
}
// WatchEndpoints begins watching for new, changed, or deleted endpoint configurations.
func (r *Registry) WatchEndpoints(label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) {
if !label.Empty() {
return nil, fmt.Errorf("label selectors are not supported on endpoints")
}
if value, found := field.RequiresExactMatch("ID"); found {
return r.Watch(makeServiceEndpointsKey(value), resourceVersion)
}
if field.Empty() {
return r.WatchList("/registry/services/endpoints", resourceVersion, tools.Everything)
}
return nil, fmt.Errorf("only the 'ID' and default (everything) field selectors are supported")
}

View File

@@ -730,7 +730,7 @@ func TestEtcdGetService(t *testing.T) {
}
if service.ID != "foo" {
t.Errorf("Unexpected pod: %#v", service)
t.Errorf("Unexpected service: %#v", service)
}
}
@@ -803,6 +803,23 @@ func TestEtcdUpdateService(t *testing.T) {
}
}
func TestEtcdGetEndpoints(t *testing.T) {
fakeClient := tools.NewFakeEtcdClient(t)
fakeClient.Set("/registry/services/endpoints/foo", api.EncodeOrDie(api.Endpoints{
JSONBase: api.JSONBase{ID: "foo"},
Endpoints: []string{"127.0.0.1:34855"},
}), 0)
registry := NewTestEtcdRegistry(fakeClient, []string{"machine"})
endpoints, err := registry.GetEndpoints("foo")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if endpoints.ID != "foo" || !reflect.DeepEqual(endpoints.Endpoints, []string{"127.0.0.1:34855"}) {
t.Errorf("Unexpected endpoints: %#v", endpoints)
}
}
func TestEtcdUpdateEndpoints(t *testing.T) {
fakeClient := tools.NewFakeEtcdClient(t)
fakeClient.TestIndex = true
@@ -830,6 +847,104 @@ func TestEtcdUpdateEndpoints(t *testing.T) {
}
}
func TestEtcdWatchServices(t *testing.T) {
fakeClient := tools.NewFakeEtcdClient(t)
registry := NewTestEtcdRegistry(fakeClient, []string{"machine"})
watching, err := registry.WatchServices(
labels.Everything(),
labels.SelectorFromSet(labels.Set{"ID": "foo"}),
1,
)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
fakeClient.WaitForWatchCompletion()
select {
case _, ok := <-watching.ResultChan():
if !ok {
t.Errorf("watching channel should be open")
}
default:
}
fakeClient.WatchInjectError <- nil
if _, ok := <-watching.ResultChan(); ok {
t.Errorf("watching channel should be closed")
}
watching.Stop()
}
func TestEtcdWatchServicesBadSelector(t *testing.T) {
fakeClient := tools.NewFakeEtcdClient(t)
registry := NewTestEtcdRegistry(fakeClient, []string{"machine"})
_, err := registry.WatchServices(
labels.Everything(),
labels.SelectorFromSet(labels.Set{"Field.Selector": "foo"}),
0,
)
if err == nil {
t.Errorf("unexpected non-error: %v", err)
}
_, err = registry.WatchServices(
labels.SelectorFromSet(labels.Set{"Label.Selector": "foo"}),
labels.Everything(),
0,
)
if err == nil {
t.Errorf("unexpected non-error: %v", err)
}
}
func TestEtcdWatchEndpoints(t *testing.T) {
fakeClient := tools.NewFakeEtcdClient(t)
registry := NewTestEtcdRegistry(fakeClient, []string{"machine"})
watching, err := registry.WatchEndpoints(
labels.Everything(),
labels.SelectorFromSet(labels.Set{"ID": "foo"}),
1,
)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
fakeClient.WaitForWatchCompletion()
select {
case _, ok := <-watching.ResultChan():
if !ok {
t.Errorf("watching channel should be open")
}
default:
}
fakeClient.WatchInjectError <- nil
if _, ok := <-watching.ResultChan(); ok {
t.Errorf("watching channel should be closed")
}
watching.Stop()
}
func TestEtcdWatchEndpointsBadSelector(t *testing.T) {
fakeClient := tools.NewFakeEtcdClient(t)
registry := NewTestEtcdRegistry(fakeClient, []string{"machine"})
_, err := registry.WatchEndpoints(
labels.Everything(),
labels.SelectorFromSet(labels.Set{"Field.Selector": "foo"}),
0,
)
if err == nil {
t.Errorf("unexpected non-error: %v", err)
}
_, err = registry.WatchEndpoints(
labels.SelectorFromSet(labels.Set{"Label.Selector": "foo"}),
labels.Everything(),
0,
)
if err == nil {
t.Errorf("unexpected non-error: %v", err)
}
}
// TODO We need a test for the compare and swap behavior. This basically requires two things:
// 1) Add a per-operation synchronization channel to the fake etcd client, such that any operation waits on that
// channel, this will enable us to orchestrate the flow of etcd requests in the test.