Skip to content

Commit

Permalink
Add simple shard allocator plugin to autogenerate host names for routes
Browse files Browse the repository at this point in the history
based on service and namespace and hook it into the route processing [GOFM].
  • Loading branch information
ramr committed Mar 13, 2015
1 parent ce53783 commit 38cdd38
Show file tree
Hide file tree
Showing 17 changed files with 561 additions and 14 deletions.
17 changes: 16 additions & 1 deletion pkg/cmd/server/origin/master.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -85,6 +86,7 @@ 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 (
Expand Down Expand Up @@ -197,6 +199,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{
Expand Down Expand Up @@ -238,7 +241,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),

Expand Down Expand Up @@ -739,6 +742,18 @@ 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(),
}

// TODO(ramr): Get plugin name + params from config.
plugin, _ := routeplugin.NewSimpleAllocationPlugin("")
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 {
Expand Down
12 changes: 12 additions & 0 deletions pkg/route/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions pkg/route/api/v1beta1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

// 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
Expand Down
46 changes: 46 additions & 0 deletions pkg/route/controller/allocation/controller.go
Original file line number Diff line number Diff line change
@@ -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) Allocate(route *routeapi.Route) (*routeapi.RouterShard, error) {

glog.V(4).Infof("RoutingAllocationController: Allocating shard for Route: %s [alias=%s]",
route.ServiceName, route.Host)

shard, err := c.Plugin.Allocate(route)

if err != nil {
glog.Errorf("RoutingAllocationController: Unable to allocate router shard: %v", err)
return shard, err
}

glog.V(4).Infof("RoutingAllocationController: 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
}
87 changes: 87 additions & 0 deletions pkg/route/controller/allocation/controller_test.go
Original file line number Diff line number Diff line change
@@ -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.Allocate(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))
}
}
}
2 changes: 2 additions & 0 deletions pkg/route/controller/allocation/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package allocation contains all the route allocation controllers.
package allocation
23 changes: 23 additions & 0 deletions pkg/route/controller/allocation/factory.go
Original file line number Diff line number Diff line change
@@ -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}
}
30 changes: 30 additions & 0 deletions pkg/route/controller/allocation/test/controller.go
Original file line number Diff line number Diff line change
@@ -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}
}
2 changes: 2 additions & 0 deletions pkg/route/controller/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package controller contains all the route handling controllers.
package controller
12 changes: 12 additions & 0 deletions pkg/route/interfaces.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
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
}
18 changes: 15 additions & 3 deletions pkg/route/registry/route/rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,19 @@ import (

"github.com/openshift/origin/pkg/route/api"
"github.com/openshift/origin/pkg/route/api/validation"
rac "github.com/openshift/origin/pkg/route/controller/allocation"
)

// REST is an implementation of RESTStorage for the api server.
type REST struct {
registry Registry
registry Registry
allocator rac.RouteAllocationController
}

func NewREST(registry Registry) *REST {
func NewREST(registry Registry, allocator rac.RouteAllocationController) *REST {
return &REST{
registry: registry,
registry: registry,
allocator: allocator,
}
}

Expand Down Expand Up @@ -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, allocError := rs.allocator.Allocate(route)
if allocError != nil {
return nil, fmt.Errorf("allocation error: %s for route: %#v", allocError, 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)
}
Expand Down
Loading

0 comments on commit 38cdd38

Please sign in to comment.