Adds support for filtering projects by label and field based on
the underlying namespace selector.
| ... | ... |
@@ -20,8 +20,11 @@ const ( |
| 20 | 20 |
Start an F5 route synchronizer |
| 21 | 21 |
|
| 22 | 22 |
This command launches a process that will synchronize an F5 to the route configuration of your master. |
| 23 |
-You may restrict the set of routes the F5 will expose by using the --labels, --fields, or --namespace |
|
| 24 |
-arguments.` |
|
| 23 |
+ |
|
| 24 |
+You may restrict the set of routes exposed to a single project (with --namespace), projects your client has |
|
| 25 |
+access to with a set of labels (--project-labels), namespaces matching a label (--namespace-labels), or all |
|
| 26 |
+namespaces (no argument). You can limit the routes to those matching a --labels or --fields selector. Note |
|
| 27 |
+that you must have a cluster-wide administrative role to view all namespaces.` |
|
| 25 | 28 |
) |
| 26 | 29 |
|
| 27 | 30 |
// F5RouterOptions represent the complete structure needed to start an F5 router |
| ... | ... |
@@ -10,30 +10,38 @@ import ( |
| 10 | 10 |
kclient "k8s.io/kubernetes/pkg/client" |
| 11 | 11 |
"k8s.io/kubernetes/pkg/fields" |
| 12 | 12 |
"k8s.io/kubernetes/pkg/labels" |
| 13 |
+ "k8s.io/kubernetes/pkg/util" |
|
| 13 | 14 |
|
| 14 | 15 |
oclient "github.com/openshift/origin/pkg/client" |
| 15 |
- "github.com/openshift/origin/pkg/cmd/util" |
|
| 16 |
+ cmdutil "github.com/openshift/origin/pkg/cmd/util" |
|
| 16 | 17 |
controllerfactory "github.com/openshift/origin/pkg/router/controller/factory" |
| 17 | 18 |
) |
| 18 | 19 |
|
| 19 | 20 |
// RouterSelection controls what routes and resources on the server are considered |
| 20 | 21 |
// part of this router. |
| 21 | 22 |
type RouterSelection struct {
|
| 23 |
+ ResyncInterval time.Duration |
|
| 24 |
+ |
|
| 22 | 25 |
LabelSelector string |
| 23 | 26 |
Labels labels.Selector |
| 24 | 27 |
FieldSelector string |
| 25 | 28 |
Fields fields.Selector |
| 26 | 29 |
|
| 27 |
- Namespace string |
|
| 30 |
+ Namespace string |
|
| 31 |
+ NamespaceLabelSelector string |
|
| 32 |
+ NamespaceLabels labels.Selector |
|
| 28 | 33 |
|
| 29 |
- ResyncInterval time.Duration |
|
| 34 |
+ ProjectLabelSelector string |
|
| 35 |
+ ProjectLabels labels.Selector |
|
| 30 | 36 |
} |
| 31 | 37 |
|
| 32 | 38 |
// Bind sets the appropriate labels |
| 33 | 39 |
func (o *RouterSelection) Bind(flag *pflag.FlagSet) {
|
| 34 |
- flag.StringVar(&o.LabelSelector, "labels", util.Env("ROUTE_LABELS", ""), "A label selector to apply to the routes to watch")
|
|
| 35 |
- flag.StringVar(&o.FieldSelector, "fields", util.Env("ROUTE_FIELDS", ""), "A field selector to apply to routes to watch")
|
|
| 36 | 40 |
flag.DurationVar(&o.ResyncInterval, "resync-interval", 10*time.Minute, "The interval at which the route list should be fully refreshed") |
| 41 |
+ flag.StringVar(&o.LabelSelector, "labels", cmdutil.Env("ROUTE_LABELS", ""), "A label selector to apply to the routes to watch")
|
|
| 42 |
+ flag.StringVar(&o.FieldSelector, "fields", cmdutil.Env("ROUTE_FIELDS", ""), "A field selector to apply to routes to watch")
|
|
| 43 |
+ flag.StringVar(&o.ProjectLabelSelector, "project-labels", cmdutil.Env("PROJECT_LABELS", ""), "A label selector to apply to projects to watch; if '*' watches all projects the client can access")
|
|
| 44 |
+ flag.StringVar(&o.NamespaceLabelSelector, "namespace-labels", cmdutil.Env("NAMESPACE_LABELS", ""), "A label selector to apply to namespaces to watch")
|
|
| 37 | 45 |
} |
| 38 | 46 |
|
| 39 | 47 |
// Complete converts string representations of field and label selectors to their parsed equivalent, or |
| ... | ... |
@@ -58,6 +66,36 @@ func (o *RouterSelection) Complete() error {
|
| 58 | 58 |
} else {
|
| 59 | 59 |
o.Fields = fields.Everything() |
| 60 | 60 |
} |
| 61 |
+ |
|
| 62 |
+ if len(o.ProjectLabelSelector) > 0 {
|
|
| 63 |
+ if len(o.Namespace) > 0 {
|
|
| 64 |
+ return fmt.Errorf("only one of --project-labels and --namespace may be used")
|
|
| 65 |
+ } |
|
| 66 |
+ if len(o.NamespaceLabelSelector) > 0 {
|
|
| 67 |
+ return fmt.Errorf("only one of --namespace-labels and --project-labels may be used")
|
|
| 68 |
+ } |
|
| 69 |
+ |
|
| 70 |
+ if o.ProjectLabelSelector == "*" {
|
|
| 71 |
+ o.ProjectLabels = labels.Everything() |
|
| 72 |
+ } else {
|
|
| 73 |
+ s, err := labels.Parse(o.ProjectLabelSelector) |
|
| 74 |
+ if err != nil {
|
|
| 75 |
+ return fmt.Errorf("--project-labels selector is not valid: %v", err)
|
|
| 76 |
+ } |
|
| 77 |
+ o.ProjectLabels = s |
|
| 78 |
+ } |
|
| 79 |
+ } |
|
| 80 |
+ |
|
| 81 |
+ if len(o.NamespaceLabelSelector) > 0 {
|
|
| 82 |
+ if len(o.Namespace) > 0 {
|
|
| 83 |
+ return fmt.Errorf("only one of --namespace-labels and --namespace may be used")
|
|
| 84 |
+ } |
|
| 85 |
+ s, err := labels.Parse(o.NamespaceLabelSelector) |
|
| 86 |
+ if err != nil {
|
|
| 87 |
+ return fmt.Errorf("--namespace-labels selector is not valid: %v", err)
|
|
| 88 |
+ } |
|
| 89 |
+ o.NamespaceLabels = s |
|
| 90 |
+ } |
|
| 61 | 91 |
return nil |
| 62 | 92 |
} |
| 63 | 93 |
|
| ... | ... |
@@ -68,8 +106,53 @@ func (o *RouterSelection) NewFactory(oc oclient.Interface, kc kclient.Interface) |
| 68 | 68 |
factory.Fields = o.Fields |
| 69 | 69 |
factory.Namespace = o.Namespace |
| 70 | 70 |
factory.ResyncInterval = o.ResyncInterval |
| 71 |
- if len(factory.Namespace) > 0 {
|
|
| 71 |
+ switch {
|
|
| 72 |
+ case o.NamespaceLabels != nil: |
|
| 73 |
+ glog.Infof("Router is only using routes in namespaces matching %s", o.NamespaceLabels)
|
|
| 74 |
+ factory.Namespaces = namespaceNames{kc.Namespaces(), o.NamespaceLabels}
|
|
| 75 |
+ case o.ProjectLabels != nil: |
|
| 76 |
+ glog.Infof("Router is only using routes in projects matching %s", o.ProjectLabels)
|
|
| 77 |
+ factory.Namespaces = projectNames{oc.Projects(), o.ProjectLabels}
|
|
| 78 |
+ case len(factory.Namespace) > 0: |
|
| 72 | 79 |
glog.Infof("Router is only using resources in namespace %s", factory.Namespace)
|
| 80 |
+ default: |
|
| 81 |
+ glog.Infof("Router is including routes in all namespaces")
|
|
| 73 | 82 |
} |
| 74 | 83 |
return factory |
| 75 | 84 |
} |
| 85 |
+ |
|
| 86 |
+// projectNames returns the names of projects matching the label selector |
|
| 87 |
+type projectNames struct {
|
|
| 88 |
+ client oclient.ProjectInterface |
|
| 89 |
+ selector labels.Selector |
|
| 90 |
+} |
|
| 91 |
+ |
|
| 92 |
+func (n projectNames) NamespaceNames() (util.StringSet, error) {
|
|
| 93 |
+ all, err := n.client.List(n.selector, fields.Everything()) |
|
| 94 |
+ if err != nil {
|
|
| 95 |
+ return nil, err |
|
| 96 |
+ } |
|
| 97 |
+ names := make(util.StringSet, len(all.Items)) |
|
| 98 |
+ for i := range all.Items {
|
|
| 99 |
+ names.Insert(all.Items[i].Name) |
|
| 100 |
+ } |
|
| 101 |
+ return names, nil |
|
| 102 |
+} |
|
| 103 |
+ |
|
| 104 |
+// namespaceNames returns the names of namespaces matching the label selector |
|
| 105 |
+type namespaceNames struct {
|
|
| 106 |
+ client kclient.NamespaceInterface |
|
| 107 |
+ selector labels.Selector |
|
| 108 |
+} |
|
| 109 |
+ |
|
| 110 |
+func (n namespaceNames) NamespaceNames() (util.StringSet, error) {
|
|
| 111 |
+ all, err := n.client.List(n.selector, fields.Everything()) |
|
| 112 |
+ if err != nil {
|
|
| 113 |
+ return nil, err |
|
| 114 |
+ } |
|
| 115 |
+ names := make(util.StringSet, len(all.Items)) |
|
| 116 |
+ for i := range all.Items {
|
|
| 117 |
+ names.Insert(all.Items[i].Name) |
|
| 118 |
+ } |
|
| 119 |
+ return names, nil |
|
| 120 |
+} |
| ... | ... |
@@ -27,7 +27,10 @@ created by users and keeps a local router configuration up to date with those ch |
| 27 | 27 |
|
| 28 | 28 |
You may customize the router by providing your own --template and --reload scripts. |
| 29 | 29 |
|
| 30 |
-You may restrict the set of routes exposed by using the --labels, --fields, or --namespace arguments.` |
|
| 30 |
+You may restrict the set of routes exposed to a single project (with --namespace), projects your client has |
|
| 31 |
+access to with a set of labels (--project-labels), namespaces matching a label (--namespace-labels), or all |
|
| 32 |
+namespaces (no argument). You can limit the routes to those matching a --labels or --fields selector. Note |
|
| 33 |
+that you must have a cluster-wide administrative role to view all namespaces.` |
|
| 31 | 34 |
) |
| 32 | 35 |
|
| 33 | 36 |
type TemplateRouterOptions struct {
|
| ... | ... |
@@ -116,7 +119,6 @@ func (o *TemplateRouterOptions) Complete() error {
|
| 116 | 116 |
} |
| 117 | 117 |
o.StatsPort = statsPort |
| 118 | 118 |
} |
| 119 |
- |
|
| 120 | 119 |
return o.RouterSelection.Complete() |
| 121 | 120 |
} |
| 122 | 121 |
|
| ... | ... |
@@ -9,6 +9,8 @@ import ( |
| 9 | 9 |
kclient "k8s.io/kubernetes/pkg/client" |
| 10 | 10 |
"k8s.io/kubernetes/pkg/fields" |
| 11 | 11 |
"k8s.io/kubernetes/pkg/labels" |
| 12 |
+ "k8s.io/kubernetes/pkg/registry/generic" |
|
| 13 |
+ nsregistry "k8s.io/kubernetes/pkg/registry/namespace" |
|
| 12 | 14 |
"k8s.io/kubernetes/pkg/runtime" |
| 13 | 15 |
|
| 14 | 16 |
"github.com/openshift/origin/pkg/project/api" |
| ... | ... |
@@ -98,7 +100,12 @@ func (s *REST) List(ctx kapi.Context, label labels.Selector, field fields.Select |
| 98 | 98 |
if err != nil {
|
| 99 | 99 |
return nil, err |
| 100 | 100 |
} |
| 101 |
- return convertNamespaceList(namespaceList), nil |
|
| 101 |
+ m := nsregistry.MatchNamespace(label, field) |
|
| 102 |
+ list, err := generic.FilterList(namespaceList, m, nil) |
|
| 103 |
+ if err != nil {
|
|
| 104 |
+ return nil, err |
|
| 105 |
+ } |
|
| 106 |
+ return convertNamespaceList(list.(*kapi.NamespaceList)), nil |
|
| 102 | 107 |
} |
| 103 | 108 |
|
| 104 | 109 |
// Get retrieves a Project by name |
| ... | ... |
@@ -3,6 +3,7 @@ package controller |
| 3 | 3 |
import ( |
| 4 | 4 |
"fmt" |
| 5 | 5 |
"sync" |
| 6 |
+ "time" |
|
| 6 | 7 |
|
| 7 | 8 |
"github.com/golang/glog" |
| 8 | 9 |
kapi "k8s.io/kubernetes/pkg/api" |
| ... | ... |
@@ -13,22 +14,56 @@ import ( |
| 13 | 13 |
"github.com/openshift/origin/pkg/router" |
| 14 | 14 |
) |
| 15 | 15 |
|
| 16 |
+// NamespaceLister returns all the names that should be watched by the client |
|
| 17 |
+type NamespaceLister interface {
|
|
| 18 |
+ NamespaceNames() (util.StringSet, error) |
|
| 19 |
+} |
|
| 20 |
+ |
|
| 16 | 21 |
// RouterController abstracts the details of watching the Route and Endpoints |
| 17 | 22 |
// resources from the Plugin implementation being used. |
| 18 | 23 |
type RouterController struct {
|
| 19 |
- lock sync.Mutex |
|
| 24 |
+ lock sync.Mutex |
|
| 25 |
+ |
|
| 20 | 26 |
Plugin router.Plugin |
| 21 | 27 |
NextRoute func() (watch.EventType, *routeapi.Route, error) |
| 22 | 28 |
NextEndpoints func() (watch.EventType, *kapi.Endpoints, error) |
| 29 |
+ |
|
| 30 |
+ Namespaces NamespaceLister |
|
| 31 |
+ NamespaceSyncInterval time.Duration |
|
| 32 |
+ NamespaceWaitInterval time.Duration |
|
| 33 |
+ NamespaceRetries int |
|
| 23 | 34 |
} |
| 24 | 35 |
|
| 25 | 36 |
// Run begins watching and syncing. |
| 26 | 37 |
func (c *RouterController) Run() {
|
| 27 | 38 |
glog.V(4).Info("Running router controller")
|
| 39 |
+ if c.Namespaces != nil {
|
|
| 40 |
+ c.HandleNamespaces() |
|
| 41 |
+ go util.Forever(c.HandleNamespaces, c.NamespaceSyncInterval) |
|
| 42 |
+ } |
|
| 28 | 43 |
go util.Forever(c.HandleRoute, 0) |
| 29 | 44 |
go util.Forever(c.HandleEndpoints, 0) |
| 30 | 45 |
} |
| 31 | 46 |
|
| 47 |
+func (c *RouterController) HandleNamespaces() {
|
|
| 48 |
+ for i := 0; i < c.NamespaceRetries; i++ {
|
|
| 49 |
+ namespaces, err := c.Namespaces.NamespaceNames() |
|
| 50 |
+ if err == nil {
|
|
| 51 |
+ c.lock.Lock() |
|
| 52 |
+ defer c.lock.Unlock() |
|
| 53 |
+ |
|
| 54 |
+ glog.V(4).Infof("Updating watched namespaces: %v", namespaces)
|
|
| 55 |
+ if err := c.Plugin.HandleNamespaces(namespaces); err != nil {
|
|
| 56 |
+ util.HandleError(err) |
|
| 57 |
+ } |
|
| 58 |
+ return |
|
| 59 |
+ } |
|
| 60 |
+ util.HandleError(fmt.Errorf("unable to find namespaces for router: %v", err))
|
|
| 61 |
+ time.Sleep(c.NamespaceWaitInterval) |
|
| 62 |
+ } |
|
| 63 |
+ glog.V(4).Infof("Unable to update list of namespaces")
|
|
| 64 |
+} |
|
| 65 |
+ |
|
| 32 | 66 |
// HandleRoute handles a single Route event and synchronizes the router backend. |
| 33 | 67 |
func (c *RouterController) HandleRoute() {
|
| 34 | 68 |
eventType, route, err := c.NextRoute() |
| ... | ... |
@@ -26,6 +26,7 @@ import ( |
| 26 | 26 |
type RouterControllerFactory struct {
|
| 27 | 27 |
KClient kclient.EndpointsNamespacer |
| 28 | 28 |
OSClient osclient.RoutesNamespacer |
| 29 |
+ Namespaces controller.NamespaceLister |
|
| 29 | 30 |
ResyncInterval time.Duration |
| 30 | 31 |
Namespace string |
| 31 | 32 |
Labels labels.Selector |
| ... | ... |
@@ -79,6 +80,13 @@ func (factory *RouterControllerFactory) Create(plugin router.Plugin) *controller |
| 79 | 79 |
} |
| 80 | 80 |
return eventType, obj.(*routeapi.Route), nil |
| 81 | 81 |
}, |
| 82 |
+ Namespaces: factory.Namespaces, |
|
| 83 |
+ // check namespaces a bit more often than we resync events, so that we aren't always waiting |
|
| 84 |
+ // the maximum interval for new items to come into the list |
|
| 85 |
+ // TODO: trigger a reflector resync after every namespace sync? |
|
| 86 |
+ NamespaceSyncInterval: factory.ResyncInterval - 10*time.Second, |
|
| 87 |
+ NamespaceWaitInterval: 10 * time.Second, |
|
| 88 |
+ NamespaceRetries: 5, |
|
| 82 | 89 |
} |
| 83 | 90 |
} |
| 84 | 91 |
|
| ... | ... |
@@ -2,6 +2,7 @@ package router |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
kapi "k8s.io/kubernetes/pkg/api" |
| 5 |
+ "k8s.io/kubernetes/pkg/util" |
|
| 5 | 6 |
"k8s.io/kubernetes/pkg/watch" |
| 6 | 7 |
|
| 7 | 8 |
routeapi "github.com/openshift/origin/pkg/route/api" |
| ... | ... |
@@ -12,4 +13,6 @@ import ( |
| 12 | 12 |
type Plugin interface {
|
| 13 | 13 |
HandleRoute(watch.EventType, *routeapi.Route) error |
| 14 | 14 |
HandleEndpoints(watch.EventType, *kapi.Endpoints) error |
| 15 |
+ // If sent, filter the list of accepted routes and endpoints to this set |
|
| 16 |
+ HandleNamespaces(namespaces util.StringSet) error |
|
| 15 | 17 |
} |
| ... | ... |
@@ -5,6 +5,7 @@ import ( |
| 5 | 5 |
|
| 6 | 6 |
"github.com/golang/glog" |
| 7 | 7 |
kapi "k8s.io/kubernetes/pkg/api" |
| 8 |
+ "k8s.io/kubernetes/pkg/util" |
|
| 8 | 9 |
"k8s.io/kubernetes/pkg/watch" |
| 9 | 10 |
|
| 10 | 11 |
routeapi "github.com/openshift/origin/pkg/route/api" |
| ... | ... |
@@ -448,6 +449,10 @@ func (p *F5Plugin) deleteRoute(routename string) error {
|
| 448 | 448 |
return nil |
| 449 | 449 |
} |
| 450 | 450 |
|
| 451 |
+func (p *F5Plugin) HandleNamespaces(namespaces util.StringSet) error {
|
|
| 452 |
+ return fmt.Errorf("namespace limiting for F5 is not implemented")
|
|
| 453 |
+} |
|
| 454 |
+ |
|
| 451 | 455 |
// HandleRoute processes watch events on the Route resource and |
| 452 | 456 |
// creates and deletes policy rules in response. |
| 453 | 457 |
func (p *F5Plugin) HandleRoute(eventType watch.EventType, |
| ... | ... |
@@ -9,6 +9,7 @@ import ( |
| 9 | 9 |
"github.com/golang/glog" |
| 10 | 10 |
kapi "k8s.io/kubernetes/pkg/api" |
| 11 | 11 |
ktypes "k8s.io/kubernetes/pkg/types" |
| 12 |
+ "k8s.io/kubernetes/pkg/util" |
|
| 12 | 13 |
"k8s.io/kubernetes/pkg/watch" |
| 13 | 14 |
|
| 14 | 15 |
routeapi "github.com/openshift/origin/pkg/route/api" |
| ... | ... |
@@ -19,16 +20,20 @@ import ( |
| 19 | 19 |
type TemplatePlugin struct {
|
| 20 | 20 |
Router router |
| 21 | 21 |
HostForRoute func(*routeapi.Route) string |
| 22 |
- hostToRoute HostToRouteMap |
|
| 23 |
- routeToHost RouteToHostMap |
|
| 22 |
+ |
|
| 23 |
+ hostToRoute HostToRouteMap |
|
| 24 |
+ routeToHost RouteToHostMap |
|
| 25 |
+ // nil means different than empty |
|
| 26 |
+ allowedNamespaces util.StringSet |
|
| 24 | 27 |
} |
| 25 | 28 |
|
| 26 | 29 |
func newDefaultTemplatePlugin(router router) *TemplatePlugin {
|
| 27 | 30 |
return &TemplatePlugin{
|
| 28 | 31 |
Router: router, |
| 29 | 32 |
HostForRoute: defaultHostForRoute, |
| 30 |
- hostToRoute: make(HostToRouteMap), |
|
| 31 |
- routeToHost: make(RouteToHostMap), |
|
| 33 |
+ |
|
| 34 |
+ hostToRoute: make(HostToRouteMap), |
|
| 35 |
+ routeToHost: make(RouteToHostMap), |
|
| 32 | 36 |
} |
| 33 | 37 |
} |
| 34 | 38 |
|
| ... | ... |
@@ -69,7 +74,8 @@ type router interface {
|
| 69 | 69 |
AddRoute(id string, route *routeapi.Route, host string) bool |
| 70 | 70 |
// RemoveRoute removes the given route for the given id. |
| 71 | 71 |
RemoveRoute(id string, route *routeapi.Route) |
| 72 |
- |
|
| 72 |
+ // Reduce the list of routes to only these namespaces |
|
| 73 |
+ FilterNamespaces(namespaces util.StringSet) |
|
| 73 | 74 |
// Commit refreshes the backend and persists the router state. |
| 74 | 75 |
Commit() error |
| 75 | 76 |
} |
| ... | ... |
@@ -112,6 +118,10 @@ func NewTemplatePlugin(cfg TemplatePluginConfig) (*TemplatePlugin, error) {
|
| 112 | 112 |
|
| 113 | 113 |
// HandleEndpoints processes watch events on the Endpoints resource. |
| 114 | 114 |
func (p *TemplatePlugin) HandleEndpoints(eventType watch.EventType, endpoints *kapi.Endpoints) error {
|
| 115 |
+ if p.allowedNamespaces != nil && !p.allowedNamespaces.Has(endpoints.Namespace) {
|
|
| 116 |
+ return nil |
|
| 117 |
+ } |
|
| 118 |
+ |
|
| 115 | 119 |
key := endpointsKey(endpoints) |
| 116 | 120 |
|
| 117 | 121 |
glog.V(4).Infof("Processing %d Endpoints for Name: %v (%v)", len(endpoints.Subsets), endpoints.Name, eventType)
|
| ... | ... |
@@ -139,7 +149,15 @@ func (p *TemplatePlugin) HandleEndpoints(eventType watch.EventType, endpoints *k |
| 139 | 139 |
} |
| 140 | 140 |
|
| 141 | 141 |
// HandleRoute processes watch events on the Route resource. |
| 142 |
+// TODO: this function can probably be collapsed with the router itself, as a function that |
|
| 143 |
+// determines which component needs to be recalculated (which template) and then does so |
|
| 144 |
+// on demand. |
|
| 142 | 145 |
func (p *TemplatePlugin) HandleRoute(eventType watch.EventType, route *routeapi.Route) error {
|
| 146 |
+ // TODO: refactor so this is handled at the cache reflector level |
|
| 147 |
+ if p.allowedNamespaces != nil && !p.allowedNamespaces.Has(route.Namespace) {
|
|
| 148 |
+ return nil |
|
| 149 |
+ } |
|
| 150 |
+ |
|
| 143 | 151 |
key := routeKey(route) |
| 144 | 152 |
routeName := routeNameKey(route) |
| 145 | 153 |
|
| ... | ... |
@@ -195,7 +213,7 @@ func (p *TemplatePlugin) HandleRoute(eventType watch.EventType, route *routeapi. |
| 195 | 195 |
|
| 196 | 196 |
switch eventType {
|
| 197 | 197 |
case watch.Added, watch.Modified: |
| 198 |
- // TODO: this could be abstracted above this layer? |
|
| 198 |
+ // TODO: this could be abstracted above this layer |
|
| 199 | 199 |
if old, ok := p.routeToHost[routeName]; ok {
|
| 200 | 200 |
if old != host {
|
| 201 | 201 |
glog.V(4).Infof("Route %s changed from serving host %s to host %s", key, old, host)
|
| ... | ... |
@@ -237,6 +255,28 @@ func (p *TemplatePlugin) HandleRoute(eventType watch.EventType, route *routeapi. |
| 237 | 237 |
return nil |
| 238 | 238 |
} |
| 239 | 239 |
|
| 240 |
+// HandleAllowedNamespaces limits the scope of valid routes to only those that match |
|
| 241 |
+// the provided namespace list. |
|
| 242 |
+func (p *TemplatePlugin) HandleNamespaces(namespaces util.StringSet) error {
|
|
| 243 |
+ p.allowedNamespaces = namespaces |
|
| 244 |
+ changed := false |
|
| 245 |
+ for k, v := range p.hostToRoute {
|
|
| 246 |
+ if namespaces.Has(v[0].Namespace) {
|
|
| 247 |
+ continue |
|
| 248 |
+ } |
|
| 249 |
+ delete(p.hostToRoute, k) |
|
| 250 |
+ for i := range v {
|
|
| 251 |
+ delete(p.routeToHost, routeNameKey(v[i])) |
|
| 252 |
+ } |
|
| 253 |
+ changed = true |
|
| 254 |
+ } |
|
| 255 |
+ if !changed && len(namespaces) > 0 {
|
|
| 256 |
+ return nil |
|
| 257 |
+ } |
|
| 258 |
+ p.Router.FilterNamespaces(namespaces) |
|
| 259 |
+ return p.Router.Commit() |
|
| 260 |
+} |
|
| 261 |
+ |
|
| 240 | 262 |
// defaultHostForRoute return the host based on the string value on a route. |
| 241 | 263 |
func defaultHostForRoute(route *routeapi.Route) string {
|
| 242 | 264 |
return route.Host |
| ... | ... |
@@ -3,6 +3,7 @@ package templaterouter |
| 3 | 3 |
import ( |
| 4 | 4 |
"fmt" |
| 5 | 5 |
"reflect" |
| 6 |
+ "strings" |
|
| 6 | 7 |
"testing" |
| 7 | 8 |
"time" |
| 8 | 9 |
|
| ... | ... |
@@ -97,6 +98,21 @@ func (r *TestRouter) RemoveRoute(id string, route *routeapi.Route) {
|
| 97 | 97 |
} |
| 98 | 98 |
} |
| 99 | 99 |
|
| 100 |
+func (r *TestRouter) FilterNamespaces(namespaces util.StringSet) {
|
|
| 101 |
+ if len(namespaces) == 0 {
|
|
| 102 |
+ r.State = make(map[string]ServiceUnit) |
|
| 103 |
+ } |
|
| 104 |
+ for k := range r.State {
|
|
| 105 |
+ // TODO: the id of a service unit should be defined inside this class, not passed in from the outside |
|
| 106 |
+ // remove the leak of the abstraction when we refactor this code |
|
| 107 |
+ ns := strings.SplitN(k, "/", 2)[0] |
|
| 108 |
+ if namespaces.Has(ns) {
|
|
| 109 |
+ continue |
|
| 110 |
+ } |
|
| 111 |
+ delete(r.State, k) |
|
| 112 |
+ } |
|
| 113 |
+} |
|
| 114 |
+ |
|
| 100 | 115 |
// routeKey create an identifier for the route consisting of host-path |
| 101 | 116 |
func (r *TestRouter) routeKey(route *routeapi.Route) string {
|
| 102 | 117 |
return route.Host + "-" + route.Path |
| ... | ... |
@@ -347,7 +363,59 @@ func TestHandleRoute(t *testing.T) {
|
| 347 | 347 |
if len(plugin.hostToRoute) != 0 {
|
| 348 | 348 |
t.Errorf("did not clear claimed route: %#v", plugin.hostToRoute)
|
| 349 | 349 |
} |
| 350 |
+} |
|
| 351 |
+ |
|
| 352 |
+func TestNamespaceScopingFromEmpty(t *testing.T) {
|
|
| 353 |
+ router := newTestRouter(make(map[string]ServiceUnit)) |
|
| 354 |
+ plugin := newDefaultTemplatePlugin(router) |
|
| 355 |
+ |
|
| 356 |
+ // no namespaces allowed |
|
| 357 |
+ plugin.HandleNamespaces(util.StringSet{})
|
|
| 358 |
+ |
|
| 359 |
+ //add |
|
| 360 |
+ route := &routeapi.Route{
|
|
| 361 |
+ ObjectMeta: kapi.ObjectMeta{Namespace: "foo", Name: "test"},
|
|
| 362 |
+ Host: "www.example.com", |
|
| 363 |
+ ServiceName: "TestService", |
|
| 364 |
+ } |
|
| 365 |
+ |
|
| 366 |
+ // ignores all events for namespace that doesn't match |
|
| 367 |
+ for _, s := range []watch.EventType{watch.Added, watch.Modified, watch.Deleted} {
|
|
| 368 |
+ plugin.HandleRoute(s, route) |
|
| 369 |
+ if _, ok := router.FindServiceUnit("foo/TestService"); ok || len(plugin.hostToRoute) != 0 {
|
|
| 370 |
+ t.Errorf("unexpected router state %#v", router)
|
|
| 371 |
+ } |
|
| 372 |
+ } |
|
| 373 |
+ |
|
| 374 |
+ // allow non matching |
|
| 375 |
+ plugin.HandleNamespaces(util.NewStringSet("bar"))
|
|
| 376 |
+ for _, s := range []watch.EventType{watch.Added, watch.Modified, watch.Deleted} {
|
|
| 377 |
+ plugin.HandleRoute(s, route) |
|
| 378 |
+ if _, ok := router.FindServiceUnit("foo/TestService"); ok || len(plugin.hostToRoute) != 0 {
|
|
| 379 |
+ t.Errorf("unexpected router state %#v", router)
|
|
| 380 |
+ } |
|
| 381 |
+ } |
|
| 350 | 382 |
|
| 383 |
+ // allow foo |
|
| 384 |
+ plugin.HandleNamespaces(util.NewStringSet("foo", "bar"))
|
|
| 385 |
+ plugin.HandleRoute(watch.Added, route) |
|
| 386 |
+ if _, ok := router.FindServiceUnit("foo/TestService"); !ok || len(plugin.hostToRoute) != 1 {
|
|
| 387 |
+ t.Errorf("unexpected router state %#v", router)
|
|
| 388 |
+ } |
|
| 389 |
+ |
|
| 390 |
+ // forbid foo, and make sure it's cleared |
|
| 391 |
+ plugin.HandleNamespaces(util.NewStringSet("bar"))
|
|
| 392 |
+ if _, ok := router.FindServiceUnit("foo/TestService"); ok || len(plugin.hostToRoute) != 0 {
|
|
| 393 |
+ t.Errorf("unexpected router state %#v", router)
|
|
| 394 |
+ } |
|
| 395 |
+ plugin.HandleRoute(watch.Modified, route) |
|
| 396 |
+ if _, ok := router.FindServiceUnit("foo/TestService"); ok || len(plugin.hostToRoute) != 0 {
|
|
| 397 |
+ t.Errorf("unexpected router state %#v", router)
|
|
| 398 |
+ } |
|
| 399 |
+ plugin.HandleRoute(watch.Added, route) |
|
| 400 |
+ if _, ok := router.FindServiceUnit("foo/TestService"); ok || len(plugin.hostToRoute) != 0 {
|
|
| 401 |
+ t.Errorf("unexpected router state %#v", router)
|
|
| 402 |
+ } |
|
| 351 | 403 |
} |
| 352 | 404 |
|
| 353 | 405 |
func TestUnchangingEndpointsDoesNotCommit(t *testing.T) {
|
| ... | ... |
@@ -8,10 +8,13 @@ import ( |
| 8 | 8 |
"os/exec" |
| 9 | 9 |
"path/filepath" |
| 10 | 10 |
"reflect" |
| 11 |
+ "strings" |
|
| 11 | 12 |
"text/template" |
| 12 | 13 |
|
| 13 | 14 |
"github.com/golang/glog" |
| 14 | 15 |
|
| 16 |
+ "k8s.io/kubernetes/pkg/util" |
|
| 17 |
+ |
|
| 15 | 18 |
routeapi "github.com/openshift/origin/pkg/route/api" |
| 16 | 19 |
) |
| 17 | 20 |
|
| ... | ... |
@@ -253,6 +256,21 @@ func (r *templateRouter) reloadRouter() error {
|
| 253 | 253 |
return nil |
| 254 | 254 |
} |
| 255 | 255 |
|
| 256 |
+func (r *templateRouter) FilterNamespaces(namespaces util.StringSet) {
|
|
| 257 |
+ if len(namespaces) == 0 {
|
|
| 258 |
+ r.state = make(map[string]ServiceUnit) |
|
| 259 |
+ } |
|
| 260 |
+ for k := range r.state {
|
|
| 261 |
+ // TODO: the id of a service unit should be defined inside this class, not passed in from the outside |
|
| 262 |
+ // remove the leak of the abstraction when we refactor this code |
|
| 263 |
+ ns := strings.SplitN(k, "/", 2)[0] |
|
| 264 |
+ if namespaces.Has(ns) {
|
|
| 265 |
+ continue |
|
| 266 |
+ } |
|
| 267 |
+ delete(r.state, k) |
|
| 268 |
+ } |
|
| 269 |
+} |
|
| 270 |
+ |
|
| 256 | 271 |
// CreateServiceUnit creates a new service named with the given id. |
| 257 | 272 |
func (r *templateRouter) CreateServiceUnit(id string) {
|
| 258 | 273 |
service := ServiceUnit{
|
| ... | ... |
@@ -265,9 +283,9 @@ func (r *templateRouter) CreateServiceUnit(id string) {
|
| 265 | 265 |
} |
| 266 | 266 |
|
| 267 | 267 |
// FindServiceUnit finds the service with the given id. |
| 268 |
-func (r *templateRouter) FindServiceUnit(id string) (v ServiceUnit, ok bool) {
|
|
| 269 |
- v, ok = r.state[id] |
|
| 270 |
- return |
|
| 268 |
+func (r *templateRouter) FindServiceUnit(id string) (ServiceUnit, bool) {
|
|
| 269 |
+ v, ok := r.state[id] |
|
| 270 |
+ return v, ok |
|
| 271 | 271 |
} |
| 272 | 272 |
|
| 273 | 273 |
// DeleteServiceUnit deletes the service with the given id. |
| ... | ... |
@@ -293,6 +311,8 @@ func (r *templateRouter) DeleteEndpoints(id string) {
|
| 293 | 293 |
|
| 294 | 294 |
r.state[id] = service |
| 295 | 295 |
|
| 296 |
+ // TODO: this is not safe (assuming that the subset of elements we are watching includes the peer endpoints) |
|
| 297 |
+ // should be a DNS lookup for endpoints of our service name. |
|
| 296 | 298 |
if id == r.peerEndpointsKey {
|
| 297 | 299 |
r.peerEndpoints = []Endpoint{}
|
| 298 | 300 |
glog.V(4).Infof("Peer endpoint table has been cleared")
|
| ... | ... |
@@ -4577,6 +4577,8 @@ _openshift_infra_router() |
| 4577 | 4577 |
flags+=("--master=")
|
| 4578 | 4578 |
flags+=("--namespace=")
|
| 4579 | 4579 |
two_word_flags+=("-n")
|
| 4580 |
+ flags+=("--namespace-labels=")
|
|
| 4581 |
+ flags+=("--project-labels=")
|
|
| 4580 | 4582 |
flags+=("--reload=")
|
| 4581 | 4583 |
flags+=("--resync-interval=")
|
| 4582 | 4584 |
flags+=("--server=")
|
| ... | ... |
@@ -4633,6 +4635,8 @@ _openshift_infra_f5-router() |
| 4633 | 4633 |
flags+=("--master=")
|
| 4634 | 4634 |
flags+=("--namespace=")
|
| 4635 | 4635 |
two_word_flags+=("-n")
|
| 4636 |
+ flags+=("--namespace-labels=")
|
|
| 4637 |
+ flags+=("--project-labels=")
|
|
| 4636 | 4638 |
flags+=("--resync-interval=")
|
| 4637 | 4639 |
flags+=("--server=")
|
| 4638 | 4640 |
flags+=("--token=")
|