diff --git a/pkg/cmd/server/origin/master.go b/pkg/cmd/server/origin/master.go index 1be38095501a..ba9dad8b32cd 100644 --- a/pkg/cmd/server/origin/master.go +++ b/pkg/cmd/server/origin/master.go @@ -65,6 +65,7 @@ import ( clientauthorizationregistry "github.com/openshift/origin/pkg/oauth/registry/clientauthorization" oauthetcd "github.com/openshift/origin/pkg/oauth/registry/etcd" projectregistry "github.com/openshift/origin/pkg/project/registry/project" + routeallocationcontroller "github.com/openshift/origin/pkg/route/controller/allocation" routeetcd "github.com/openshift/origin/pkg/route/registry/etcd" routeregistry "github.com/openshift/origin/pkg/route/registry/route" "github.com/openshift/origin/pkg/service" @@ -85,12 +86,14 @@ import ( rolebindingregistry "github.com/openshift/origin/pkg/authorization/registry/rolebinding" subjectaccessreviewregistry "github.com/openshift/origin/pkg/authorization/registry/subjectaccessreview" "github.com/openshift/origin/pkg/cmd/server/bootstrappolicy" + routeplugin "github.com/openshift/origin/plugins/route/allocation/simple" ) const ( OpenShiftAPIPrefix = "/osapi" OpenShiftAPIV1Beta1 = "v1beta1" OpenShiftAPIPrefixV1Beta1 = OpenShiftAPIPrefix + "/" + OpenShiftAPIV1Beta1 + OpenShiftRouteSubdomain = "router.default.local" swaggerAPIPrefix = "/swaggerapi/" ) @@ -197,6 +200,7 @@ func (c *MasterConfig) InstallProtectedAPI(container *restful.Container) []strin imageRepositoryMappingStorage := imagerepositorymapping.NewREST(imageRegistry, imageRepositoryRegistry) imageRepositoryTagStorage := imagerepositorytag.NewREST(imageRegistry, imageRepositoryRegistry) imageStreamImageStorage := imagestreamimage.NewREST(imageRegistry, imageRepositoryRegistry) + routeAllocator := c.RouteAllocator() // TODO: with sharding, this needs to be changed deployConfigGenerator := &deployconfiggenerator.DeploymentConfigGenerator{ @@ -238,7 +242,7 @@ func (c *MasterConfig) InstallProtectedAPI(container *restful.Container) []strin "templateConfigs": templateregistry.NewREST(), "templates": templateetcd.NewREST(c.EtcdHelper), - "routes": routeregistry.NewREST(routeEtcd), + "routes": routeregistry.NewREST(routeEtcd, routeAllocator), "projects": projectregistry.NewREST(kclient.Namespaces(), c.ProjectAuthorizationCache), @@ -739,6 +743,26 @@ func (c *MasterConfig) RunDeploymentImageChangeTriggerController() { controller.Run() } +// RouteAllocator returns a route allocation controller. +func (c *MasterConfig) RouteAllocator() *routeallocationcontroller.RouteAllocationController { + factory := routeallocationcontroller.RouteAllocationControllerFactory{ + OSClient: c.OSClient, + KubeClient: c.KubeClient(), + } + + subdomain := os.Getenv("OPENSHIFT_ROUTE_SUBDOMAIN") + if len(subdomain) == 0 { + subdomain = OpenShiftRouteSubdomain + } + + plugin, err := routeplugin.NewSimpleAllocationPlugin(subdomain) + if err != nil { + glog.Fatalf("Route plugin initialization failed: %v", err) + } + + return factory.Create(plugin) +} + // ensureCORSAllowedOrigins takes a string list of origins and attempts to covert them to CORS origin // regexes, or exits if it cannot. func (c *MasterConfig) ensureCORSAllowedOrigins() []*regexp.Regexp { diff --git a/pkg/route/api/types.go b/pkg/route/api/types.go index 94ec611a9e96..f36cd2acc477 100644 --- a/pkg/route/api/types.go +++ b/pkg/route/api/types.go @@ -30,6 +30,18 @@ type RouteList struct { Items []Route `json:"items"` } +// RouterShard has information of a routing shard and is used to +// generate host names and routing table entries when a routing shard is +// allocated for a specific route. +type RouterShard struct { + // Shard name uniquely identifies a router shard in the "set" of + // routers used for routing traffic to the services. + ShardName string + + // The DNS suffix for the shard ala: shard-1.v3.openshift.com + DNSSuffix string +} + // TLSConfig defines config used to secure a route and provide termination type TLSConfig struct { // Termination indicates termination type. If termination type is not set, any termination config will be ignored diff --git a/pkg/route/api/v1beta1/types.go b/pkg/route/api/v1beta1/types.go index 1e1a1082f160..dad96b9cd7a0 100644 --- a/pkg/route/api/v1beta1/types.go +++ b/pkg/route/api/v1beta1/types.go @@ -30,6 +30,20 @@ type RouteList struct { Items []Route `json:"items"` } +// RouterShard has information of a routing shard and is used to +// generate host names and routing table entries when a routing shard is +// allocated for a specific route. +// Caveat: This is WIP and will likely undergo modifications when sharding +// support is added. +type RouterShard struct { + // Shard name uniquely identifies a router shard in the "set" of + // routers used for routing traffic to the services. + ShardName string `json:"shardName"` + + // The DNS suffix for the shard ala: shard-1.v3.openshift.com + DNSSuffix string `json:"dnsSuffix"` +} + // TLSConfig defines config used to secure a route and provide termination type TLSConfig struct { // Termination indicates termination type. If termination type is not set, any termination config will be ignored diff --git a/pkg/route/controller/allocation/controller.go b/pkg/route/controller/allocation/controller.go new file mode 100644 index 000000000000..c0d358f69b80 --- /dev/null +++ b/pkg/route/controller/allocation/controller.go @@ -0,0 +1,46 @@ +package allocation + +import ( + "github.com/golang/glog" + + "github.com/openshift/origin/pkg/route" + routeapi "github.com/openshift/origin/pkg/route/api" +) + +// RouteAllocationController abstracts the details of how routes are +// allocated to router shards. +type RouteAllocationController struct { + Plugin route.AllocationPlugin +} + +// Allocate a router shard for the given route. +func (c *RouteAllocationController) AllocateRouterShard(route *routeapi.Route) (*routeapi.RouterShard, error) { + + glog.V(4).Infof("Allocating router shard for Route: %s [alias=%s]", + route.ServiceName, route.Host) + + shard, err := c.Plugin.Allocate(route) + + if err != nil { + glog.Errorf("unable to allocate router shard: %v", err) + return shard, err + } + + glog.V(4).Infof("Route %s allocated to shard %s [suffix=%s]", + route.ServiceName, shard.ShardName, shard.DNSSuffix) + + return shard, err +} + +// Generate a host name for the given route and router shard combination. +func (c *RouteAllocationController) GenerateHostname(route *routeapi.Route, shard *routeapi.RouterShard) string { + glog.V(4).Infof("Generating host name for Route: %s", + route.ServiceName) + + s := c.Plugin.GenerateHostname(route, shard) + + glog.V(4).Infof("Route: %s, generated host name/alias=%s", + route.ServiceName, s) + + return s +} diff --git a/pkg/route/controller/allocation/controller_test.go b/pkg/route/controller/allocation/controller_test.go new file mode 100644 index 000000000000..d98b155b395d --- /dev/null +++ b/pkg/route/controller/allocation/controller_test.go @@ -0,0 +1,87 @@ +package allocation + +import ( + "fmt" + "testing" + + kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + routeapi "github.com/openshift/origin/pkg/route/api" +) + +type TestAllocationPlugin struct { + Name string +} + +func (p *TestAllocationPlugin) Allocate(route *routeapi.Route) (*routeapi.RouterShard, error) { + + return &routeapi.RouterShard{ShardName: "test", DNSSuffix: "openshift.test"}, nil +} + +func (p *TestAllocationPlugin) GenerateHostname(route *routeapi.Route, shard *routeapi.RouterShard) string { + if len(route.ServiceName) > 0 && len(route.Namespace) > 0 { + return fmt.Sprintf("%s-%s.%s", route.ServiceName, route.Namespace, shard.DNSSuffix) + } + + return "test-test-test.openshift.test" +} + +func TestRouteAllocationController(t *testing.T) { + tests := []struct { + name string + route *routeapi.Route + }{ + { + name: "No Name", + route: &routeapi.Route{ + ObjectMeta: kapi.ObjectMeta{ + Namespace: "namespace", + }, + ServiceName: "service", + }, + }, + { + name: "No namespace", + route: &routeapi.Route{ + ObjectMeta: kapi.ObjectMeta{ + Name: "name", + }, + ServiceName: "nonamespace", + }, + }, + { + name: "No service name", + route: &routeapi.Route{ + ObjectMeta: kapi.ObjectMeta{ + Name: "name", + Namespace: "foo", + }, + }, + }, + { + name: "Valid route", + route: &routeapi.Route{ + ObjectMeta: kapi.ObjectMeta{ + Name: "name", + Namespace: "foo", + }, + Host: "www.example.org", + ServiceName: "serviceName", + }, + }, + } + + plugin := &TestAllocationPlugin{Name: "test allocation plugin"} + fac := &RouteAllocationControllerFactory{nil, nil} + allocator := fac.Create(plugin) + for _, tc := range tests { + shard, err := allocator.AllocateRouterShard(tc.route) + if err != nil { + t.Errorf("Test case %s got an error %s", tc.name, err) + continue + } + name := allocator.GenerateHostname(tc.route, shard) + if len(name) <= 0 { + t.Errorf("Test case %s got %d length name", tc.name, len(name)) + } + } +} diff --git a/pkg/route/controller/allocation/doc.go b/pkg/route/controller/allocation/doc.go new file mode 100644 index 000000000000..c4246665e5b1 --- /dev/null +++ b/pkg/route/controller/allocation/doc.go @@ -0,0 +1,2 @@ +// Package allocation contains all the route allocation controllers. +package allocation diff --git a/pkg/route/controller/allocation/factory.go b/pkg/route/controller/allocation/factory.go new file mode 100644 index 000000000000..779747a0d241 --- /dev/null +++ b/pkg/route/controller/allocation/factory.go @@ -0,0 +1,23 @@ +package allocation + +import ( + kclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client" + + osclient "github.com/openshift/origin/pkg/client" + "github.com/openshift/origin/pkg/route" +) + +// RouteAllocationControllerFactory creates a RouteAllocationController +// that allocates router shards to specific routes. +type RouteAllocationControllerFactory struct { + // Client is is an OpenShift client. + OSClient osclient.Interface + + // KubeClient is a Kubernetes client. + KubeClient kclient.Interface +} + +// Create a RouteAllocationController instance. +func (factory *RouteAllocationControllerFactory) Create(plugin route.AllocationPlugin) *RouteAllocationController { + return &RouteAllocationController{Plugin: plugin} +} diff --git a/pkg/route/controller/allocation/test/controller.go b/pkg/route/controller/allocation/test/controller.go new file mode 100644 index 000000000000..2914436995c4 --- /dev/null +++ b/pkg/route/controller/allocation/test/controller.go @@ -0,0 +1,30 @@ +package test + +import ( + "fmt" + + routeapi "github.com/openshift/origin/pkg/route/api" + "github.com/openshift/origin/pkg/route/controller/allocation" +) + +type TestAllocationPlugin struct { + Name string +} + +func (p *TestAllocationPlugin) Allocate(route *routeapi.Route) (*routeapi.RouterShard, error) { + + return &routeapi.RouterShard{ShardName: "test", DNSSuffix: "openshift.test"}, nil +} + +func (p *TestAllocationPlugin) GenerateHostname(route *routeapi.Route, shard *routeapi.RouterShard) string { + if len(route.ServiceName) > 0 && len(route.Namespace) > 0 { + return fmt.Sprintf("%s-%s.%s", route.ServiceName, route.Namespace, shard.DNSSuffix) + } + + return "test-test-test.openshift.test" +} + +func NewTestRouteAllocationController() *allocation.RouteAllocationController { + plugin := &TestAllocationPlugin{"test route allocation plugin"} + return &allocation.RouteAllocationController{Plugin: plugin} +} diff --git a/pkg/route/controller/doc.go b/pkg/route/controller/doc.go new file mode 100644 index 000000000000..beca750b75d8 --- /dev/null +++ b/pkg/route/controller/doc.go @@ -0,0 +1,2 @@ +// Package controller contains all the route handling controllers. +package controller diff --git a/pkg/route/interfaces.go b/pkg/route/interfaces.go new file mode 100644 index 000000000000..c4e66782f6e9 --- /dev/null +++ b/pkg/route/interfaces.go @@ -0,0 +1,19 @@ +package route + +import ( + api "github.com/openshift/origin/pkg/route/api" +) + +// AllocationPlugin is the interface the route controller dispatches +// requests for RouterShard allocation and name generation. +type AllocationPlugin interface { + Allocate(*api.Route) (*api.RouterShard, error) + GenerateHostname(*api.Route, *api.RouterShard) string +} + +// RouteAllocator is the interface for the route allocation controller +// which handles requests for RouterShard allocation and name generation. +type RouteAllocator interface { + AllocateRouterShard(*api.Route) (*api.RouterShard, error) + GenerateHostname(*api.Route, *api.RouterShard) string +} diff --git a/pkg/route/registry/route/rest.go b/pkg/route/registry/route/rest.go index 4a25b09268f7..7f92a44a5d5d 100644 --- a/pkg/route/registry/route/rest.go +++ b/pkg/route/registry/route/rest.go @@ -11,18 +11,21 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/watch" + "github.com/openshift/origin/pkg/route" "github.com/openshift/origin/pkg/route/api" "github.com/openshift/origin/pkg/route/api/validation" ) // REST is an implementation of RESTStorage for the api server. type REST struct { - registry Registry + registry Registry + allocator route.RouteAllocator } -func NewREST(registry Registry) *REST { +func NewREST(registry Registry, allocator route.RouteAllocator) *REST { return &REST{ - registry: registry, + registry: registry, + allocator: allocator, } } @@ -71,6 +74,15 @@ func (rs *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, er return nil, errors.NewConflict("route", route.Namespace, fmt.Errorf("Route.Namespace does not match the provided context")) } + shard, err := rs.allocator.AllocateRouterShard(route) + if err != nil { + return nil, fmt.Errorf("allocation error: %s for route: %#v", err, obj) + } + + if len(route.Host) == 0 { + route.Host = rs.allocator.GenerateHostname(route, shard) + } + if errs := validation.ValidateRoute(route); len(errs) > 0 { return nil, errors.NewInvalid("route", route.Name, errs) } @@ -82,7 +94,7 @@ func (rs *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, er escapeNewLines(route.TLS) - err := rs.registry.CreateRoute(ctx, route) + err = rs.registry.CreateRoute(ctx, route) if err != nil { return nil, err } diff --git a/pkg/route/registry/route/rest_test.go b/pkg/route/registry/route/rest_test.go index cfbba8100501..df6889906139 100644 --- a/pkg/route/registry/route/rest_test.go +++ b/pkg/route/registry/route/rest_test.go @@ -10,17 +10,20 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/openshift/origin/pkg/route/api" + ractest "github.com/openshift/origin/pkg/route/controller/allocation/test" "github.com/openshift/origin/pkg/route/registry/test" ) func TestListRoutesEmptyList(t *testing.T) { mockRegistry := test.NewRouteRegistry() + mockAllocator := ractest.NewTestRouteAllocationController() mockRegistry.Routes = &api.RouteList{ Items: []api.Route{}, } storage := REST{ - registry: mockRegistry, + registry: mockRegistry, + allocator: mockAllocator, } routes, err := storage.List(kapi.NewDefaultContext(), labels.Everything(), labels.Everything()) @@ -35,6 +38,7 @@ func TestListRoutesEmptyList(t *testing.T) { func TestListRoutesPopulatedList(t *testing.T) { mockRegistry := test.NewRouteRegistry() + mockAllocator := ractest.NewTestRouteAllocationController() mockRegistry.Routes = &api.RouteList{ Items: []api.Route{ { @@ -51,7 +55,8 @@ func TestListRoutesPopulatedList(t *testing.T) { } storage := REST{ - registry: mockRegistry, + registry: mockRegistry, + allocator: mockAllocator, } list, err := storage.List(kapi.NewDefaultContext(), labels.Everything(), labels.Everything()) @@ -80,7 +85,11 @@ func TestCreateRouteBadObject(t *testing.T) { func TestCreateRouteOK(t *testing.T) { mockRegistry := test.NewRouteRegistry() - storage := REST{registry: mockRegistry} + mockAllocator := ractest.NewTestRouteAllocationController() + storage := REST{ + registry: mockRegistry, + allocator: mockAllocator, + } obj, err := storage.Create(kapi.NewDefaultContext(), &api.Route{ ObjectMeta: kapi.ObjectMeta{Name: "foo"}, @@ -105,7 +114,11 @@ func TestCreateRouteOK(t *testing.T) { func TestGetRouteError(t *testing.T) { mockRegistry := test.NewRouteRegistry() - storage := REST{registry: mockRegistry} + mockAllocator := ractest.NewTestRouteAllocationController() + storage := REST{ + registry: mockRegistry, + allocator: mockAllocator, + } route, err := storage.Get(kapi.NewDefaultContext(), "foo") if route != nil { @@ -119,6 +132,7 @@ func TestGetRouteError(t *testing.T) { func TestGetRouteOK(t *testing.T) { mockRegistry := test.NewRouteRegistry() + mockAllocator := ractest.NewTestRouteAllocationController() mockRegistry.Routes = &api.RouteList{ Items: []api.Route{ { @@ -126,7 +140,10 @@ func TestGetRouteOK(t *testing.T) { }, }, } - storage := REST{registry: mockRegistry} + storage := REST{ + registry: mockRegistry, + allocator: mockAllocator, + } route, err := storage.Get(kapi.NewDefaultContext(), "foo") if route == nil { @@ -166,7 +183,11 @@ func TestUpdateRouteMissingID(t *testing.T) { func TestUpdateRegistryErrorSaving(t *testing.T) { mockRepositoryRegistry := test.NewRouteRegistry() - storage := REST{registry: mockRepositoryRegistry} + mockAllocator := ractest.NewTestRouteAllocationController() + storage := REST{ + registry: mockRepositoryRegistry, + allocator: mockAllocator, + } _, _, err := storage.Update(kapi.NewDefaultContext(), &api.Route{ ObjectMeta: kapi.ObjectMeta{Name: "foo"}, @@ -180,6 +201,7 @@ func TestUpdateRegistryErrorSaving(t *testing.T) { func TestUpdateRouteOK(t *testing.T) { mockRepositoryRegistry := test.NewRouteRegistry() + mockAllocator := ractest.NewTestRouteAllocationController() mockRepositoryRegistry.Routes = &api.RouteList{ Items: []api.Route{ { @@ -190,7 +212,10 @@ func TestUpdateRouteOK(t *testing.T) { }, } - storage := REST{registry: mockRepositoryRegistry} + storage := REST{ + registry: mockRepositoryRegistry, + allocator: mockAllocator, + } obj, created, err := storage.Update(kapi.NewDefaultContext(), &api.Route{ ObjectMeta: kapi.ObjectMeta{Name: "bar"}, @@ -222,7 +247,11 @@ func TestUpdateRouteOK(t *testing.T) { func TestDeleteRouteError(t *testing.T) { mockRegistry := test.NewRouteRegistry() - storage := REST{registry: mockRegistry} + mockAllocator := ractest.NewTestRouteAllocationController() + storage := REST{ + registry: mockRegistry, + allocator: mockAllocator, + } _, err := storage.Delete(kapi.NewDefaultContext(), "foo") if err == nil { t.Errorf("Unexpected nil error: %#v", err) @@ -234,6 +263,7 @@ func TestDeleteRouteError(t *testing.T) { func TestDeleteRouteOk(t *testing.T) { mockRegistry := test.NewRouteRegistry() + mockAllocator := ractest.NewTestRouteAllocationController() mockRegistry.Routes = &api.RouteList{ Items: []api.Route{ { @@ -241,7 +271,10 @@ func TestDeleteRouteOk(t *testing.T) { }, }, } - storage := REST{registry: mockRegistry} + storage := REST{ + registry: mockRegistry, + allocator: mockAllocator, + } obj, err := storage.Delete(kapi.NewDefaultContext(), "foo") if obj == nil { t.Error("Unexpected nil obj") @@ -275,7 +308,11 @@ func TestCreateRouteConflictingNamespace(t *testing.T) { func TestUpdateRouteConflictingNamespace(t *testing.T) { mockRepositoryRegistry := test.NewRouteRegistry() - storage := REST{registry: mockRepositoryRegistry} + mockAllocator := ractest.NewTestRouteAllocationController() + storage := REST{ + registry: mockRepositoryRegistry, + allocator: mockAllocator, + } obj, created, err := storage.Update(kapi.WithNamespace(kapi.NewContext(), "legal-name"), &api.Route{ ObjectMeta: kapi.ObjectMeta{Name: "bar", Namespace: "some-value"}, diff --git a/plugins/route/allocation/doc.go b/plugins/route/allocation/doc.go new file mode 100644 index 000000000000..51f5c17971bb --- /dev/null +++ b/plugins/route/allocation/doc.go @@ -0,0 +1,2 @@ +// Package allocation contains all the route allocation plugins. +package allocation diff --git a/plugins/route/allocation/simple/doc.go b/plugins/route/allocation/simple/doc.go new file mode 100644 index 000000000000..4b723d6a049a --- /dev/null +++ b/plugins/route/allocation/simple/doc.go @@ -0,0 +1,2 @@ +// Package simple contains the SimpleAllocation route plugin. +package simple diff --git a/plugins/route/allocation/simple/plugin.go b/plugins/route/allocation/simple/plugin.go new file mode 100644 index 000000000000..17df7b03c292 --- /dev/null +++ b/plugins/route/allocation/simple/plugin.go @@ -0,0 +1,69 @@ +package simple + +import ( + "fmt" + + "code.google.com/p/go-uuid/uuid" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" + "github.com/golang/glog" + + routeapi "github.com/openshift/origin/pkg/route/api" +) + +// Default DNS suffix to use if no configuration is passed to this plugin. +// Would be better if we could use "v3.openshift.app", someone bought that! +const defaultDNSSuffix = "v3.openshift.com" + +// SimpleAllocationPlugin implements the route.AllocationPlugin interface +// to provide a simple unsharded (or single sharded) allocation plugin. +type SimpleAllocationPlugin struct { + DNSSuffix string +} + +// Creates a new SimpleAllocationPlugin. +func NewSimpleAllocationPlugin(suffix string) (*SimpleAllocationPlugin, error) { + if len(suffix) == 0 { + suffix = defaultDNSSuffix + } + + glog.V(4).Infof("Route plugin initialized with suffix=%s", suffix) + + // Check that the DNS suffix is valid. + if !util.IsDNSSubdomain(suffix) { + return nil, fmt.Errorf("invalid DNS suffix: %s", suffix) + } + + return &SimpleAllocationPlugin{DNSSuffix: suffix}, nil +} + +// Allocate a router shard for the given route. This plugin always returns +// the "global" router shard. +func (p *SimpleAllocationPlugin) Allocate(route *routeapi.Route) (*routeapi.RouterShard, error) { + + glog.V(4).Infof("Allocating global shard *.%s to Route: %s", + p.DNSSuffix, route.ServiceName) + + return &routeapi.RouterShard{ShardName: "global", DNSSuffix: p.DNSSuffix}, nil +} + +// Generate a host name for a route - using the service name, +// namespace (if provided) and the router shard dns suffix. +func (p *SimpleAllocationPlugin) GenerateHostname(route *routeapi.Route, shard *routeapi.RouterShard) string { + + name := route.ServiceName + if len(name) == 0 { + name = uuid.NewUUID().String() + glog.V(4).Infof("No service name passed, using generated name: %s", name) + } + + s := "" + if len(route.Namespace) <= 0 { + s = fmt.Sprintf("%s.%s", name, shard.DNSSuffix) + } else { + s = fmt.Sprintf("%s-%s.%s", name, route.Namespace, shard.DNSSuffix) + } + + glog.V(4).Infof("Generated hostname=%s for Route: %s", s, route.ServiceName) + + return s +} diff --git a/plugins/route/allocation/simple/plugin_test.go b/plugins/route/allocation/simple/plugin_test.go new file mode 100644 index 000000000000..38cc67b51bb1 --- /dev/null +++ b/plugins/route/allocation/simple/plugin_test.go @@ -0,0 +1,177 @@ +package simple + +import ( + "testing" + + kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" + + "github.com/openshift/origin/pkg/route/api" + rac "github.com/openshift/origin/pkg/route/controller/allocation" +) + +func TestNewSimpleAllocationPlugin(t *testing.T) { + tests := []struct { + Name string + ErrorExpectation bool + }{ + { + Name: "www.example.org", + ErrorExpectation: false, + }, + { + Name: "www^acme^org", + ErrorExpectation: true, + }, + { + Name: "bad wolf.whoswho", + ErrorExpectation: true, + }, + { + Name: "tardis#1.watch", + ErrorExpectation: true, + }, + { + Name: "こんにちはopenshift.com", + ErrorExpectation: true, + }, + { + Name: "yo!yo!@#$%%$%^&*(0){[]}:;',<>?/1.test", + ErrorExpectation: true, + }, + } + + for _, tc := range tests { + _, err := NewSimpleAllocationPlugin(tc.Name) + if err != nil && !tc.ErrorExpectation { + t.Errorf("Test case for %s got an error where none was expected", tc.Name) + } + } +} + +func TestSimpleAllocationPlugin(t *testing.T) { + tests := []struct { + name string + route *api.Route + }{ + { + name: "No Name", + route: &api.Route{ + ObjectMeta: kapi.ObjectMeta{ + Namespace: "namespace", + }, + ServiceName: "service", + }, + }, + { + name: "No namespace", + route: &api.Route{ + ObjectMeta: kapi.ObjectMeta{ + Name: "name", + }, + ServiceName: "nonamespace", + }, + }, + { + name: "No service name", + route: &api.Route{ + ObjectMeta: kapi.ObjectMeta{ + Name: "name", + Namespace: "foo", + }, + }, + }, + { + name: "Valid route", + route: &api.Route{ + ObjectMeta: kapi.ObjectMeta{ + Name: "name", + Namespace: "foo", + }, + Host: "www.example.com", + ServiceName: "myservice", + }, + }, + } + + plugin, err := NewSimpleAllocationPlugin("www.example.org") + if err != nil { + t.Errorf("Error creating SimpleAllocationPlugin got %s", err) + return + } + + for _, tc := range tests { + shard, _ := plugin.Allocate(tc.route) + name := plugin.GenerateHostname(tc.route, shard) + if len(name) <= 0 { + t.Errorf("Test case %s got %d length name.", tc.name, len(name)) + } + if !util.IsDNSSubdomain(name) { + t.Errorf("Test case %s got %s - invalid DNS name.", tc.name, name) + } + } +} + +func TestSimpleAllocationPluginViaController(t *testing.T) { + tests := []struct { + name string + route *api.Route + }{ + { + name: "No Name", + route: &api.Route{ + ObjectMeta: kapi.ObjectMeta{ + Namespace: "namespace", + }, + ServiceName: "service", + }, + }, + { + name: "No namespace", + route: &api.Route{ + ObjectMeta: kapi.ObjectMeta{ + Name: "name", + }, + ServiceName: "nonamespace", + }, + }, + { + name: "No service name", + route: &api.Route{ + ObjectMeta: kapi.ObjectMeta{ + Name: "name", + Namespace: "foo", + }, + }, + }, + { + name: "Valid route", + route: &api.Route{ + ObjectMeta: kapi.ObjectMeta{ + Name: "name", + Namespace: "foo", + }, + Host: "www.example.com", + ServiceName: "s3", + }, + }, + } + + plugin, _ := NewSimpleAllocationPlugin("www.example.org") + fac := &rac.RouteAllocationControllerFactory{nil, nil} + sac := fac.Create(plugin) + + for _, tc := range tests { + shard, err := sac.AllocateRouterShard(tc.route) + if err != nil { + t.Errorf("Test case %s got an error %s", tc.name, err) + } + name := sac.GenerateHostname(tc.route, shard) + if len(name) <= 0 { + t.Errorf("Test case %s got %d length name", tc.name, len(name)) + } + if !util.IsDNSSubdomain(name) { + t.Errorf("Test case %s got %s - invalid DNS name.", tc.name, name) + } + } +} diff --git a/plugins/route/doc.go b/plugins/route/doc.go new file mode 100644 index 000000000000..31e86d8a2e9a --- /dev/null +++ b/plugins/route/doc.go @@ -0,0 +1,2 @@ +// Package route contains all the route plugins. +package route