Implement multi-port Endpoints

This is a part of multi-port services.
This commit is contained in:
Tim Hockin
2015-02-21 01:05:18 -08:00
parent e0fd83096c
commit 160f288832
33 changed files with 755 additions and 387 deletions

View File

@@ -27,10 +27,20 @@ import (
)
func TestGetEndpoints(t *testing.T) {
expected := []api.Endpoint{
{IP: "127.0.0.1", Ports: []api.EndpointPort{
{Name: "p", Port: 9000, Protocol: api.ProtocolTCP},
{Name: "q", Port: 9000, Protocol: api.ProtocolUDP},
}},
{IP: "127.0.0.2", Ports: []api.EndpointPort{
{Name: "p", Port: 8000, Protocol: api.ProtocolTCP},
{Name: "q", Port: 8000, Protocol: api.ProtocolUDP},
}},
}
registry := &registrytest.ServiceRegistry{
Endpoints: api.Endpoints{
ObjectMeta: api.ObjectMeta{Name: "foo"},
Endpoints: []api.Endpoint{{IP: "127.0.0.1", Port: 9000}},
Endpoints: expected,
},
}
storage := NewREST(registry)
@@ -39,7 +49,7 @@ func TestGetEndpoints(t *testing.T) {
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) {
if !reflect.DeepEqual(expected, obj.(*api.Endpoints).Endpoints) {
t.Errorf("unexpected endpoints: %#v", obj)
}
}

View File

@@ -24,6 +24,8 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
etcdgeneric "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic/etcd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod"
@@ -598,10 +600,10 @@ func TestEtcdListEndpoints(t *testing.T) {
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: "foo"}, Endpoints: []api.Endpoint{{IP: "127.0.0.1", Ports: []api.EndpointPort{{Name: "p", Port: 8345, Protocol: api.ProtocolTCP}}}}}),
},
{
Value: runtime.EncodeOrDie(latest.Codec, &api.Endpoints{ObjectMeta: api.ObjectMeta{Name: "bar"}, Protocol: "TCP"}),
Value: runtime.EncodeOrDie(latest.Codec, &api.Endpoints{ObjectMeta: api.ObjectMeta{Name: "bar"}}),
},
},
},
@@ -625,8 +627,7 @@ func TestEtcdGetEndpoints(t *testing.T) {
registry := NewTestEtcdRegistry(fakeClient)
endpoints := &api.Endpoints{
ObjectMeta: api.ObjectMeta{Name: "foo"},
Protocol: "TCP",
Endpoints: []api.Endpoint{{IP: "127.0.0.1", Port: 34855}},
Endpoints: []api.Endpoint{{IP: "127.0.0.1", Ports: []api.EndpointPort{{Port: 34855, Protocol: api.ProtocolTCP}}}},
}
key, _ := makeServiceEndpointsKey(ctx, "foo")
@@ -647,10 +648,19 @@ func TestEtcdUpdateEndpoints(t *testing.T) {
fakeClient := tools.NewFakeEtcdClient(t)
fakeClient.TestIndex = true
registry := NewTestEtcdRegistry(fakeClient)
// TODO: Once we drop single-port APIs, make this test use the
// multi-port features. This will force a compile error when those APIs
// are deleted.
_ = v1beta1.Dependency
_ = v1beta2.Dependency
endpoints := api.Endpoints{
ObjectMeta: api.ObjectMeta{Name: "foo"},
Protocol: "TCP",
Endpoints: []api.Endpoint{{IP: "baz"}, {IP: "bar"}},
Endpoints: []api.Endpoint{
{IP: "baz", Ports: []api.EndpointPort{{Port: 1, Protocol: api.ProtocolTCP}}},
{IP: "bar", Ports: []api.EndpointPort{{Port: 2, Protocol: api.ProtocolTCP}}},
},
}
key, _ := makeServiceEndpointsKey(ctx, "foo")

View File

@@ -21,6 +21,7 @@ import (
"math/rand"
"net"
"strconv"
"strings"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
@@ -218,17 +219,57 @@ func (rs *REST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, boo
// ResourceLocation returns a URL to which one can send traffic for the specified service.
func (rs *REST) ResourceLocation(ctx api.Context, id string) (string, error) {
eps, err := rs.registry.GetEndpoints(ctx, id)
// Allow ID as "svcname" or "svcname:port". Choose an endpoint at
// random. If the port is specified as a number, use that value
// directly. If the port is specified as a name, try to look up that
// name on the chosen endpoint. If port is not specified, try to use
// the first unnamed port on the chosen endpoint. If there are no
// unnamed ports, try to use the first defined port.
parts := strings.Split(id, ":")
if len(parts) > 2 {
return "", errors.NewBadRequest(fmt.Sprintf("invalid service request %q", id))
}
name := parts[0]
port := ""
if len(parts) == 2 {
port = parts[1]
}
eps, err := rs.registry.GetEndpoints(ctx, name)
if err != nil {
return "", err
}
if len(eps.Endpoints) == 0 {
return "", fmt.Errorf("no endpoints available for %v", id)
return "", fmt.Errorf("no endpoints available for %v", name)
}
ep := &eps.Endpoints[rand.Intn(len(eps.Endpoints))]
// Try to figure out a port.
if _, err := strconv.Atoi(port); err != nil {
// Do nothing - port is correct as is.
} else {
// Try a name lookup, even if name is "".
for i := range ep.Ports {
if ep.Ports[i].Name == port {
port = strconv.Itoa(ep.Ports[i].Port)
break
}
}
}
if port == "" {
// Still nothing - try the first defined port.
if len(ep.Ports) > 0 {
port = strconv.Itoa(ep.Ports[0].Port)
}
}
// We leave off the scheme ('http://') because we have no idea what sort of server
// is listening at this endpoint.
ep := &eps.Endpoints[rand.Intn(len(eps.Endpoints))]
return net.JoinHostPort(ep.IP, strconv.Itoa(ep.Port)), nil
loc := ep.IP
if port != "" {
loc += fmt.Sprintf(":%s", port)
}
return loc, nil
}
func (rs *REST) createExternalLoadBalancer(ctx api.Context, service *api.Service) error {

View File

@@ -370,7 +370,7 @@ func TestServiceRegistryGet(t *testing.T) {
func TestServiceRegistryResourceLocation(t *testing.T) {
ctx := api.NewDefaultContext()
registry := registrytest.NewServiceRegistry()
registry.Endpoints = api.Endpoints{Endpoints: []api.Endpoint{{IP: "foo", Port: 80}}}
registry.Endpoints = api.Endpoints{Endpoints: []api.Endpoint{{IP: "foo", Ports: []api.EndpointPort{{Port: 80}}}}}
fakeCloud := &cloud.FakeCloud{}
machines := []string{"foo", "bar", "baz"}
storage := NewREST(registry, fakeCloud, registrytest.NewMinionRegistry(machines, api.NodeResources{}), makeIPNet(t))