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=") |