Browse code

Filter routes by namespace or project labels

Adds support for filtering projects by label and field based on
the underlying namespace selector.

Clayton Coleman authored on 2015/09/02 13:17:38
Showing 13 changed files
... ...
@@ -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
 
... ...
@@ -101,6 +101,7 @@ func (cfg *Config) bindEnv() error {
101 101
 		if err != nil {
102 102
 			return err
103 103
 		}
104
+
104 105
 		if !cfg.MasterAddr.Provided {
105 106
 			cfg.MasterAddr.Set(cfg.CommonConfig.Host)
106 107
 		}
... ...
@@ -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=")