| ... | ... |
@@ -19713,6 +19713,8 @@ _openshift_infra_f5-router() |
| 19713 | 19713 |
local_nonpersistent_flags+=("--f5-https-vserver=")
|
| 19714 | 19714 |
flags+=("--f5-insecure")
|
| 19715 | 19715 |
local_nonpersistent_flags+=("--f5-insecure")
|
| 19716 |
+ flags+=("--f5-internal-address=")
|
|
| 19717 |
+ local_nonpersistent_flags+=("--f5-internal-address=")
|
|
| 19716 | 19718 |
flags+=("--f5-partition-path=")
|
| 19717 | 19719 |
local_nonpersistent_flags+=("--f5-partition-path=")
|
| 19718 | 19720 |
flags+=("--f5-password=")
|
| ... | ... |
@@ -19721,6 +19723,8 @@ _openshift_infra_f5-router() |
| 19721 | 19721 |
local_nonpersistent_flags+=("--f5-private-key=")
|
| 19722 | 19722 |
flags+=("--f5-username=")
|
| 19723 | 19723 |
local_nonpersistent_flags+=("--f5-username=")
|
| 19724 |
+ flags+=("--f5-vxlan-gateway-cidr=")
|
|
| 19725 |
+ local_nonpersistent_flags+=("--f5-vxlan-gateway-cidr=")
|
|
| 19724 | 19726 |
flags+=("--fields=")
|
| 19725 | 19727 |
local_nonpersistent_flags+=("--fields=")
|
| 19726 | 19728 |
flags+=("--hostname-template=")
|
| ... | ... |
@@ -19874,6 +19874,8 @@ _openshift_infra_f5-router() |
| 19874 | 19874 |
local_nonpersistent_flags+=("--f5-https-vserver=")
|
| 19875 | 19875 |
flags+=("--f5-insecure")
|
| 19876 | 19876 |
local_nonpersistent_flags+=("--f5-insecure")
|
| 19877 |
+ flags+=("--f5-internal-address=")
|
|
| 19878 |
+ local_nonpersistent_flags+=("--f5-internal-address=")
|
|
| 19877 | 19879 |
flags+=("--f5-partition-path=")
|
| 19878 | 19880 |
local_nonpersistent_flags+=("--f5-partition-path=")
|
| 19879 | 19881 |
flags+=("--f5-password=")
|
| ... | ... |
@@ -19882,6 +19884,8 @@ _openshift_infra_f5-router() |
| 19882 | 19882 |
local_nonpersistent_flags+=("--f5-private-key=")
|
| 19883 | 19883 |
flags+=("--f5-username=")
|
| 19884 | 19884 |
local_nonpersistent_flags+=("--f5-username=")
|
| 19885 |
+ flags+=("--f5-vxlan-gateway-cidr=")
|
|
| 19886 |
+ local_nonpersistent_flags+=("--f5-vxlan-gateway-cidr=")
|
|
| 19885 | 19887 |
flags+=("--fields=")
|
| 19886 | 19888 |
local_nonpersistent_flags+=("--fields=")
|
| 19887 | 19889 |
flags+=("--hostname-template=")
|
| ... | ... |
@@ -72,6 +72,10 @@ You may restrict the set of routes exposed to a single project (with \-\-namespa |
| 72 | 72 |
Skip strict certificate verification |
| 73 | 73 |
|
| 74 | 74 |
.PP |
| 75 |
+\fB\-\-f5\-internal\-address\fP="" |
|
| 76 |
+ The F5 BIG\-IP internal interface's IP address |
|
| 77 |
+ |
|
| 78 |
+.PP |
|
| 75 | 79 |
\fB\-\-f5\-partition\-path\fP="/Common" |
| 76 | 80 |
The F5 BIG\-IP partition path to use |
| 77 | 81 |
|
| ... | ... |
@@ -88,6 +92,10 @@ You may restrict the set of routes exposed to a single project (with \-\-namespa |
| 88 | 88 |
The username for F5 BIG\-IP's management utility |
| 89 | 89 |
|
| 90 | 90 |
.PP |
| 91 |
+\fB\-\-f5\-vxlan\-gateway\-cidr\fP="" |
|
| 92 |
+ The F5 BIG\-IP gateway\-ip\-address/cidr\-mask for setting up the VxLAN |
|
| 93 |
+ |
|
| 94 |
+.PP |
|
| 91 | 95 |
\fB\-\-fields\fP="" |
| 92 | 96 |
A field selector to apply to routes to watch |
| 93 | 97 |
|
| ... | ... |
@@ -76,6 +76,17 @@ type F5Router struct {
|
| 76 | 76 |
// normally used to create access control boundaries for users |
| 77 | 77 |
// and applications. |
| 78 | 78 |
PartitionPath string |
| 79 |
+ |
|
| 80 |
+ // VxlanGateway is the ip address assigned to the local tunnel interface |
|
| 81 |
+ // inside F5 box. This address is the one that the packets generated from F5 |
|
| 82 |
+ // will carry. The pods will return the packets to this address itself. |
|
| 83 |
+ // It is important that the gateway be one of the ip addresses of the subnet |
|
| 84 |
+ // that has been generated for F5. |
|
| 85 |
+ VxlanGateway string |
|
| 86 |
+ |
|
| 87 |
+ // InternalAddress is the ip address of the vtep interface used to connect to |
|
| 88 |
+ // VxLAN overlay. It is the hostIP address listed in the subnet generated for F5 |
|
| 89 |
+ InternalAddress string |
|
| 79 | 90 |
} |
| 80 | 91 |
|
| 81 | 92 |
// Bind binds F5Router arguments to flags |
| ... | ... |
@@ -89,6 +100,8 @@ func (o *F5Router) Bind(flag *pflag.FlagSet) {
|
| 89 | 89 |
flag.StringVar(&o.PrivateKey, "f5-private-key", util.Env("ROUTER_EXTERNAL_HOST_PRIVKEY", ""), "The path to the F5 BIG-IP SSH private key file")
|
| 90 | 90 |
flag.BoolVar(&o.Insecure, "f5-insecure", util.Env("ROUTER_EXTERNAL_HOST_INSECURE", "") == "true", "Skip strict certificate verification")
|
| 91 | 91 |
flag.StringVar(&o.PartitionPath, "f5-partition-path", util.Env("ROUTER_EXTERNAL_HOST_PARTITION_PATH", f5plugin.F5DefaultPartitionPath), "The F5 BIG-IP partition path to use")
|
| 92 |
+ flag.StringVar(&o.InternalAddress, "f5-internal-address", util.Env("ROUTER_EXTERNAL_HOST_INTERNAL_ADDRESS", ""), "The F5 BIG-IP internal interface's IP address")
|
|
| 93 |
+ flag.StringVar(&o.VxlanGateway, "f5-vxlan-gateway-cidr", util.Env("ROUTER_EXTERNAL_HOST_VXLAN_GW_CIDR", ""), "The F5 BIG-IP gateway-ip-address/cidr-mask for setting up the VxLAN")
|
|
| 92 | 94 |
} |
| 93 | 95 |
|
| 94 | 96 |
// Validate verifies the required F5 flags are present |
| ... | ... |
@@ -109,6 +122,11 @@ func (o *F5Router) Validate() error {
|
| 109 | 109 |
return errors.New("F5 HTTP and HTTPS vservers cannot both be blank")
|
| 110 | 110 |
} |
| 111 | 111 |
|
| 112 |
+ valid := (len(o.VxlanGateway) == 0 && len(o.InternalAddress) == 0) || (len(o.VxlanGateway) != 0 && len(o.InternalAddress) != 0) |
|
| 113 |
+ if !valid {
|
|
| 114 |
+ return errors.New("For VxLAN setup, both internal-address and gateway-cidr must be specified")
|
|
| 115 |
+ } |
|
| 116 |
+ |
|
| 112 | 117 |
return nil |
| 113 | 118 |
} |
| 114 | 119 |
|
| ... | ... |
@@ -158,14 +176,16 @@ func (o *F5RouterOptions) Validate() error {
|
| 158 | 158 |
// Run launches an F5 route sync process using the provided options. It never exits. |
| 159 | 159 |
func (o *F5RouterOptions) Run() error {
|
| 160 | 160 |
cfg := f5plugin.F5PluginConfig{
|
| 161 |
- Host: o.Host, |
|
| 162 |
- Username: o.Username, |
|
| 163 |
- Password: o.Password, |
|
| 164 |
- HttpVserver: o.HttpVserver, |
|
| 165 |
- HttpsVserver: o.HttpsVserver, |
|
| 166 |
- PrivateKey: o.PrivateKey, |
|
| 167 |
- Insecure: o.Insecure, |
|
| 168 |
- PartitionPath: o.PartitionPath, |
|
| 161 |
+ Host: o.Host, |
|
| 162 |
+ Username: o.Username, |
|
| 163 |
+ Password: o.Password, |
|
| 164 |
+ HttpVserver: o.HttpVserver, |
|
| 165 |
+ HttpsVserver: o.HttpsVserver, |
|
| 166 |
+ PrivateKey: o.PrivateKey, |
|
| 167 |
+ Insecure: o.Insecure, |
|
| 168 |
+ PartitionPath: o.PartitionPath, |
|
| 169 |
+ InternalAddress: o.InternalAddress, |
|
| 170 |
+ VxlanGateway: o.VxlanGateway, |
|
| 169 | 171 |
} |
| 170 | 172 |
f5Plugin, err := f5plugin.NewF5Plugin(cfg) |
| 171 | 173 |
if err != nil {
|
| ... | ... |
@@ -181,7 +201,8 @@ func (o *F5RouterOptions) Run() error {
|
| 181 | 181 |
plugin := controller.NewUniqueHost(statusPlugin, o.RouteSelectionFunc(), statusPlugin) |
| 182 | 182 |
|
| 183 | 183 |
factory := o.RouterSelection.NewFactory(oc, kc) |
| 184 |
- controller := factory.Create(plugin) |
|
| 184 |
+ watchNodes := (len(o.InternalAddress) != 0 && len(o.VxlanGateway) != 0) |
|
| 185 |
+ controller := factory.Create(plugin, watchNodes) |
|
| 185 | 186 |
controller.Run() |
| 186 | 187 |
|
| 187 | 188 |
select {}
|
| ... | ... |
@@ -210,7 +210,7 @@ func (o *TemplateRouterOptions) Run() error {
|
| 210 | 210 |
plugin := controller.NewUniqueHost(nextPlugin, o.RouteSelectionFunc(), controller.RejectionRecorder(statusPlugin)) |
| 211 | 211 |
|
| 212 | 212 |
factory := o.RouterSelection.NewFactory(oc, kc) |
| 213 |
- controller := factory.Create(plugin) |
|
| 213 |
+ controller := factory.Create(plugin, false) |
|
| 214 | 214 |
controller.Run() |
| 215 | 215 |
|
| 216 | 216 |
proc.StartReaper() |
| ... | ... |
@@ -531,6 +531,7 @@ func GetBootstrapClusterRoles() []authorizationapi.ClusterRole {
|
| 531 | 531 |
Rules: []authorizationapi.PolicyRule{
|
| 532 | 532 |
authorizationapi.NewRule("list", "watch").Groups(kapiGroup).Resources("endpoints").RuleOrDie(),
|
| 533 | 533 |
authorizationapi.NewRule("list", "watch").Groups(kapiGroup).Resources("services").RuleOrDie(),
|
| 534 |
+ authorizationapi.NewRule("list", "watch").Groups(kapiGroup).Resources("nodes").RuleOrDie(),
|
|
| 534 | 535 |
|
| 535 | 536 |
authorizationapi.NewRule("list", "watch").Groups(routeGroup).Resources("routes").RuleOrDie(),
|
| 536 | 537 |
authorizationapi.NewRule("update").Groups(routeGroup).Resources("routes/status").RuleOrDie(),
|
| ... | ... |
@@ -28,6 +28,7 @@ type RouterController struct {
|
| 28 | 28 |
|
| 29 | 29 |
Plugin router.Plugin |
| 30 | 30 |
NextRoute func() (watch.EventType, *routeapi.Route, error) |
| 31 |
+ NextNode func() (watch.EventType, *kapi.Node, error) |
|
| 31 | 32 |
NextEndpoints func() (watch.EventType, *kapi.Endpoints, error) |
| 32 | 33 |
|
| 33 | 34 |
RoutesListConsumed func() bool |
| ... | ... |
@@ -36,6 +37,8 @@ type RouterController struct {
|
| 36 | 36 |
endpointsListConsumed bool |
| 37 | 37 |
filteredByNamespace bool |
| 38 | 38 |
|
| 39 |
+ WatchNodes bool |
|
| 40 |
+ |
|
| 39 | 41 |
Namespaces NamespaceLister |
| 40 | 42 |
NamespaceSyncInterval time.Duration |
| 41 | 43 |
NamespaceWaitInterval time.Duration |
| ... | ... |
@@ -51,6 +54,9 @@ func (c *RouterController) Run() {
|
| 51 | 51 |
} |
| 52 | 52 |
go utilwait.Forever(c.HandleRoute, 0) |
| 53 | 53 |
go utilwait.Forever(c.HandleEndpoints, 0) |
| 54 |
+ if c.WatchNodes {
|
|
| 55 |
+ go utilwait.Forever(c.HandleNode, 0) |
|
| 56 |
+ } |
|
| 54 | 57 |
} |
| 55 | 58 |
|
| 56 | 59 |
func (c *RouterController) HandleNamespaces() {
|
| ... | ... |
@@ -78,6 +84,25 @@ func (c *RouterController) HandleNamespaces() {
|
| 78 | 78 |
glog.V(4).Infof("Unable to update list of namespaces")
|
| 79 | 79 |
} |
| 80 | 80 |
|
| 81 |
+// HandleNode handles a single Node event and synchronizes the router backend |
|
| 82 |
+func (c *RouterController) HandleNode() {
|
|
| 83 |
+ eventType, node, err := c.NextNode() |
|
| 84 |
+ if err != nil {
|
|
| 85 |
+ utilruntime.HandleError(fmt.Errorf("unable to read nodes: %v", err))
|
|
| 86 |
+ return |
|
| 87 |
+ } |
|
| 88 |
+ |
|
| 89 |
+ c.lock.Lock() |
|
| 90 |
+ defer c.lock.Unlock() |
|
| 91 |
+ |
|
| 92 |
+ glog.V(4).Infof("Processing Node : %s", node.Name)
|
|
| 93 |
+ glog.V(4).Infof(" Event: %s", eventType)
|
|
| 94 |
+ |
|
| 95 |
+ if err := c.Plugin.HandleNode(eventType, node); err != nil {
|
|
| 96 |
+ utilruntime.HandleError(err) |
|
| 97 |
+ } |
|
| 98 |
+} |
|
| 99 |
+ |
|
| 81 | 100 |
// HandleRoute handles a single Route event and synchronizes the router backend. |
| 82 | 101 |
func (c *RouterController) HandleRoute() {
|
| 83 | 102 |
eventType, route, err := c.NextRoute() |
| ... | ... |
@@ -17,6 +17,9 @@ type fakeRouterPlugin struct {
|
| 17 | 17 |
func (p *fakeRouterPlugin) HandleRoute(t watch.EventType, route *routeapi.Route) error {
|
| 18 | 18 |
return nil |
| 19 | 19 |
} |
| 20 |
+func (p *fakeRouterPlugin) HandleNode(t watch.EventType, node *kapi.Node) error {
|
|
| 21 |
+ return nil |
|
| 22 |
+} |
|
| 20 | 23 |
func (p *fakeRouterPlugin) HandleEndpoints(watch.EventType, *kapi.Endpoints) error {
|
| 21 | 24 |
return nil |
| 22 | 25 |
} |
| ... | ... |
@@ -46,6 +49,9 @@ func TestRouterController_updateLastSyncProcessed(t *testing.T) {
|
| 46 | 46 |
NextRoute: func() (watch.EventType, *routeapi.Route, error) {
|
| 47 | 47 |
return watch.Modified, &routeapi.Route{}, nil
|
| 48 | 48 |
}, |
| 49 |
+ NextNode: func() (watch.EventType, *kapi.Node, error) {
|
|
| 50 |
+ return watch.Modified, &kapi.Node{}, nil
|
|
| 51 |
+ }, |
|
| 49 | 52 |
EndpointsListConsumed: func() bool {
|
| 50 | 53 |
return true |
| 51 | 54 |
}, |
| ... | ... |
@@ -38,6 +38,11 @@ func NewExtendedValidator(plugin router.Plugin, recorder RejectionRecorder) *Ext |
| 38 | 38 |
} |
| 39 | 39 |
} |
| 40 | 40 |
|
| 41 |
+// HandleNode processes watch events on the node resource |
|
| 42 |
+func (p *ExtendedValidator) HandleNode(eventType watch.EventType, node *kapi.Node) error {
|
|
| 43 |
+ return p.plugin.HandleNode(eventType, node) |
|
| 44 |
+} |
|
| 45 |
+ |
|
| 41 | 46 |
// HandleEndpoints processes watch events on the Endpoints resource. |
| 42 | 47 |
func (p *ExtendedValidator) HandleEndpoints(eventType watch.EventType, endpoints *kapi.Endpoints) error {
|
| 43 | 48 |
return p.plugin.HandleEndpoints(eventType, endpoints) |
| ... | ... |
@@ -27,6 +27,7 @@ import ( |
| 27 | 27 |
type RouterControllerFactory struct {
|
| 28 | 28 |
KClient kclient.EndpointsNamespacer |
| 29 | 29 |
OSClient osclient.RoutesNamespacer |
| 30 |
+ NodeClient kclient.NodesInterface |
|
| 30 | 31 |
Namespaces controller.NamespaceLister |
| 31 | 32 |
ResyncInterval time.Duration |
| 32 | 33 |
Namespace string |
| ... | ... |
@@ -35,10 +36,11 @@ type RouterControllerFactory struct {
|
| 35 | 35 |
} |
| 36 | 36 |
|
| 37 | 37 |
// NewDefaultRouterControllerFactory initializes a default router controller factory. |
| 38 |
-func NewDefaultRouterControllerFactory(oc osclient.RoutesNamespacer, kc kclient.EndpointsNamespacer) *RouterControllerFactory {
|
|
| 38 |
+func NewDefaultRouterControllerFactory(oc osclient.RoutesNamespacer, kc kclient.Interface) *RouterControllerFactory {
|
|
| 39 | 39 |
return &RouterControllerFactory{
|
| 40 | 40 |
KClient: kc, |
| 41 | 41 |
OSClient: oc, |
| 42 |
+ NodeClient: kc, |
|
| 42 | 43 |
ResyncInterval: 10 * time.Minute, |
| 43 | 44 |
|
| 44 | 45 |
Namespace: kapi.NamespaceAll, |
| ... | ... |
@@ -49,7 +51,7 @@ func NewDefaultRouterControllerFactory(oc osclient.RoutesNamespacer, kc kclient. |
| 49 | 49 |
|
| 50 | 50 |
// Create begins listing and watching against the API server for the desired route and endpoint |
| 51 | 51 |
// resources. It spawns child goroutines that cannot be terminated. |
| 52 |
-func (factory *RouterControllerFactory) Create(plugin router.Plugin) *controller.RouterController {
|
|
| 52 |
+func (factory *RouterControllerFactory) Create(plugin router.Plugin, watchNodes bool) *controller.RouterController {
|
|
| 53 | 53 |
routeEventQueue := oscache.NewEventQueue(cache.MetaNamespaceKeyFunc) |
| 54 | 54 |
cache.NewReflector(&routeLW{
|
| 55 | 55 |
client: factory.OSClient, |
| ... | ... |
@@ -65,6 +67,15 @@ func (factory *RouterControllerFactory) Create(plugin router.Plugin) *controller |
| 65 | 65 |
// we do not scope endpoints by labels or fields because the route labels != endpoints labels |
| 66 | 66 |
}, &kapi.Endpoints{}, endpointsEventQueue, factory.ResyncInterval).Run()
|
| 67 | 67 |
|
| 68 |
+ nodeEventQueue := oscache.NewEventQueue(cache.MetaNamespaceKeyFunc) |
|
| 69 |
+ if watchNodes {
|
|
| 70 |
+ cache.NewReflector(&nodeLW{
|
|
| 71 |
+ client: factory.NodeClient, |
|
| 72 |
+ field: fields.Everything(), |
|
| 73 |
+ label: labels.Everything(), |
|
| 74 |
+ }, &kapi.Node{}, nodeEventQueue, factory.ResyncInterval).Run()
|
|
| 75 |
+ } |
|
| 76 |
+ |
|
| 68 | 77 |
return &controller.RouterController{
|
| 69 | 78 |
Plugin: plugin, |
| 70 | 79 |
NextEndpoints: func() (watch.EventType, *kapi.Endpoints, error) {
|
| ... | ... |
@@ -81,6 +92,13 @@ func (factory *RouterControllerFactory) Create(plugin router.Plugin) *controller |
| 81 | 81 |
} |
| 82 | 82 |
return eventType, obj.(*routeapi.Route), nil |
| 83 | 83 |
}, |
| 84 |
+ NextNode: func() (watch.EventType, *kapi.Node, error) {
|
|
| 85 |
+ eventType, obj, err := nodeEventQueue.Pop() |
|
| 86 |
+ if err != nil {
|
|
| 87 |
+ return watch.Error, nil, err |
|
| 88 |
+ } |
|
| 89 |
+ return eventType, obj.(*kapi.Node), nil |
|
| 90 |
+ }, |
|
| 84 | 91 |
EndpointsListConsumed: func() bool {
|
| 85 | 92 |
return endpointsEventQueue.ListConsumed() |
| 86 | 93 |
}, |
| ... | ... |
@@ -94,6 +112,7 @@ func (factory *RouterControllerFactory) Create(plugin router.Plugin) *controller |
| 94 | 94 |
NamespaceSyncInterval: factory.ResyncInterval - 10*time.Second, |
| 95 | 95 |
NamespaceWaitInterval: 10 * time.Second, |
| 96 | 96 |
NamespaceRetries: 5, |
| 97 |
+ WatchNodes: watchNodes, |
|
| 97 | 98 |
} |
| 98 | 99 |
} |
| 99 | 100 |
|
| ... | ... |
@@ -256,3 +275,23 @@ func (lw *endpointsLW) Watch(options kapi.ListOptions) (watch.Interface, error) |
| 256 | 256 |
} |
| 257 | 257 |
return lw.client.Endpoints(lw.namespace).Watch(opts) |
| 258 | 258 |
} |
| 259 |
+ |
|
| 260 |
+// nodeLW is a list watcher for nodes. |
|
| 261 |
+type nodeLW struct {
|
|
| 262 |
+ client kclient.NodesInterface |
|
| 263 |
+ label labels.Selector |
|
| 264 |
+ field fields.Selector |
|
| 265 |
+} |
|
| 266 |
+ |
|
| 267 |
+func (lw *nodeLW) List(options kapi.ListOptions) (runtime.Object, error) {
|
|
| 268 |
+ return lw.client.Nodes().List(options) |
|
| 269 |
+} |
|
| 270 |
+ |
|
| 271 |
+func (lw *nodeLW) Watch(options kapi.ListOptions) (watch.Interface, error) {
|
|
| 272 |
+ opts := kapi.ListOptions{
|
|
| 273 |
+ LabelSelector: lw.label, |
|
| 274 |
+ FieldSelector: lw.field, |
|
| 275 |
+ ResourceVersion: options.ResourceVersion, |
|
| 276 |
+ } |
|
| 277 |
+ return lw.client.Nodes().Watch(opts) |
|
| 278 |
+} |
| ... | ... |
@@ -295,6 +295,10 @@ func (a *StatusAdmitter) HandleRoute(eventType watch.EventType, route *routeapi. |
| 295 | 295 |
return a.plugin.HandleRoute(eventType, route) |
| 296 | 296 |
} |
| 297 | 297 |
|
| 298 |
+func (a *StatusAdmitter) HandleNode(eventType watch.EventType, node *kapi.Node) error {
|
|
| 299 |
+ return a.plugin.HandleNode(eventType, node) |
|
| 300 |
+} |
|
| 301 |
+ |
|
| 298 | 302 |
func (a *StatusAdmitter) HandleEndpoints(eventType watch.EventType, route *kapi.Endpoints) error {
|
| 299 | 303 |
return a.plugin.HandleEndpoints(eventType, route) |
| 300 | 304 |
} |
| ... | ... |
@@ -28,6 +28,11 @@ func (p *fakePlugin) HandleRoute(t watch.EventType, route *routeapi.Route) error |
| 28 | 28 |
p.t, p.route = t, route |
| 29 | 29 |
return p.err |
| 30 | 30 |
} |
| 31 |
+ |
|
| 32 |
+func (p *fakePlugin) HandleNode(t watch.EventType, node *kapi.Node) error {
|
|
| 33 |
+ return fmt.Errorf("not expected")
|
|
| 34 |
+} |
|
| 35 |
+ |
|
| 31 | 36 |
func (p *fakePlugin) HandleEndpoints(watch.EventType, *kapi.Endpoints) error {
|
| 32 | 37 |
return fmt.Errorf("not expected")
|
| 33 | 38 |
} |
| ... | ... |
@@ -84,6 +84,11 @@ func (p *UniqueHost) HandleEndpoints(eventType watch.EventType, endpoints *kapi. |
| 84 | 84 |
return p.plugin.HandleEndpoints(eventType, endpoints) |
| 85 | 85 |
} |
| 86 | 86 |
|
| 87 |
+// HandleNode processes watch events on the Node resource and calls the router |
|
| 88 |
+func (p *UniqueHost) HandleNode(eventType watch.EventType, node *kapi.Node) error {
|
|
| 89 |
+ return p.plugin.HandleNode(eventType, node) |
|
| 90 |
+} |
|
| 91 |
+ |
|
| 87 | 92 |
// HandleRoute processes watch events on the Route resource. |
| 88 | 93 |
// TODO: this function can probably be collapsed with the router itself, as a function that |
| 89 | 94 |
// determines which component needs to be recalculated (which template) and then does so |
| ... | ... |
@@ -7,6 +7,7 @@ import ( |
| 7 | 7 |
"fmt" |
| 8 | 8 |
"io" |
| 9 | 9 |
"io/ioutil" |
| 10 |
+ "net" |
|
| 10 | 11 |
"net/http" |
| 11 | 12 |
"os" |
| 12 | 13 |
"os/exec" |
| ... | ... |
@@ -21,6 +22,9 @@ import ( |
| 21 | 21 |
const ( |
| 22 | 22 |
// Default F5 partition path to use for syncing route config. |
| 23 | 23 |
F5DefaultPartitionPath = "/Common" |
| 24 |
+ F5VxLANTunnelName = "vxlan5000" |
|
| 25 |
+ F5VxLANProfileName = "vxlan-ose" |
|
| 26 |
+ HTTP_CONFLICT_CODE = 409 |
|
| 24 | 27 |
) |
| 25 | 28 |
|
| 26 | 29 |
// Error implements the error interface. |
| ... | ... |
@@ -104,6 +108,21 @@ type f5LTMCfg struct {
|
| 104 | 104 |
// are normally used to create an access control boundary for |
| 105 | 105 |
// F5 users and applications. |
| 106 | 106 |
partitionPath string |
| 107 |
+ |
|
| 108 |
+ // vxlanGateway is the ip address assigned to the local tunnel interface |
|
| 109 |
+ // inside F5 box. This address is the one that the packets generated from F5 |
|
| 110 |
+ // will carry. The pods will return the packets to this address itself. |
|
| 111 |
+ // It is important that the gateway be one of the ip addresses of the subnet |
|
| 112 |
+ // that has been generated for F5. |
|
| 113 |
+ vxlanGateway string |
|
| 114 |
+ |
|
| 115 |
+ // internalAddress is the ip address of the vtep interface used to connect to |
|
| 116 |
+ // VxLAN overlay. It is the hostIP address listed in the subnet generated for F5 |
|
| 117 |
+ internalAddress string |
|
| 118 |
+ |
|
| 119 |
+ // setupOSDNVxLAN is the boolean that conveys if F5 needs to setup a VxLAN |
|
| 120 |
+ // to hook up with openshift-sdn |
|
| 121 |
+ setupOSDNVxLAN bool |
|
| 107 | 122 |
} |
| 108 | 123 |
|
| 109 | 124 |
const ( |
| ... | ... |
@@ -327,17 +346,21 @@ func newF5LTM(cfg f5LTMCfg) (*f5LTM, error) {
|
| 327 | 327 |
|
| 328 | 328 |
// Ensure path is rooted. |
| 329 | 329 |
partitionPath = path.Join("/", partitionPath)
|
| 330 |
+ setupOSDNVxLAN := (len(cfg.vxlanGateway) != 0 && len(cfg.internalAddress) != 0) |
|
| 330 | 331 |
|
| 331 | 332 |
router := &f5LTM{
|
| 332 | 333 |
f5LTMCfg: f5LTMCfg{
|
| 333 |
- host: cfg.host, |
|
| 334 |
- username: cfg.username, |
|
| 335 |
- password: cfg.password, |
|
| 336 |
- httpVserver: cfg.httpVserver, |
|
| 337 |
- httpsVserver: cfg.httpsVserver, |
|
| 338 |
- privkey: privkeyFileName, |
|
| 339 |
- insecure: cfg.insecure, |
|
| 340 |
- partitionPath: partitionPath, |
|
| 334 |
+ host: cfg.host, |
|
| 335 |
+ username: cfg.username, |
|
| 336 |
+ password: cfg.password, |
|
| 337 |
+ httpVserver: cfg.httpVserver, |
|
| 338 |
+ httpsVserver: cfg.httpsVserver, |
|
| 339 |
+ privkey: privkeyFileName, |
|
| 340 |
+ insecure: cfg.insecure, |
|
| 341 |
+ partitionPath: partitionPath, |
|
| 342 |
+ vxlanGateway: cfg.vxlanGateway, |
|
| 343 |
+ internalAddress: cfg.internalAddress, |
|
| 344 |
+ setupOSDNVxLAN: setupOSDNVxLAN, |
|
| 341 | 345 |
}, |
| 342 | 346 |
poolMembers: map[string]map[string]bool{},
|
| 343 | 347 |
routes: map[string]map[string]bool{},
|
| ... | ... |
@@ -395,6 +418,7 @@ func (f5 *f5LTM) restRequest(verb string, url string, payload io.Reader, |
| 395 | 395 |
|
| 396 | 396 |
client := &http.Client{Transport: tr}
|
| 397 | 397 |
|
| 398 |
+ glog.V(4).Infof("Request sent: %v\n", req)
|
|
| 398 | 399 |
resp, err := client.Do(req) |
| 399 | 400 |
if err != nil {
|
| 400 | 401 |
errorResult.err = fmt.Errorf("client.Do failed: %v", err)
|
| ... | ... |
@@ -461,6 +485,68 @@ func (f5 *f5LTM) delete(url string, result interface{}) error {
|
| 461 | 461 |
// Routines for controlling F5. |
| 462 | 462 |
// |
| 463 | 463 |
|
| 464 |
+// ensureVxLANTunnel sets up the VxLAN tunnel profile and tunnel+selfIP |
|
| 465 |
+func (f5 *f5LTM) ensureVxLANTunnel() error {
|
|
| 466 |
+ glog.V(4).Infof("Checking and installing VxLAN setup")
|
|
| 467 |
+ |
|
| 468 |
+ // create the profile |
|
| 469 |
+ url := fmt.Sprintf("https://%s/mgmt/tm/net/tunnels/vxlan", f5.host)
|
|
| 470 |
+ profilePayload := f5CreateVxLANProfilePayload{
|
|
| 471 |
+ Name: F5VxLANProfileName, |
|
| 472 |
+ Partition: f5.partitionPath, |
|
| 473 |
+ FloodingType: "multipoint", |
|
| 474 |
+ Port: 4789, |
|
| 475 |
+ } |
|
| 476 |
+ err := f5.post(url, profilePayload, nil) |
|
| 477 |
+ if err != nil && err.(F5Error).httpStatusCode != HTTP_CONFLICT_CODE {
|
|
| 478 |
+ // error HTTP_CONFLICT_CODE is fine, it just means the tunnel profile already exists |
|
| 479 |
+ glog.V(4).Infof("Error while creating vxlan tunnel - %v", err)
|
|
| 480 |
+ return err |
|
| 481 |
+ } |
|
| 482 |
+ |
|
| 483 |
+ // create the tunnel |
|
| 484 |
+ url = fmt.Sprintf("https://%s/mgmt/tm/net/tunnels/tunnel", f5.host)
|
|
| 485 |
+ tunnelPayload := f5CreateVxLANTunnelPayload{
|
|
| 486 |
+ Name: F5VxLANTunnelName, |
|
| 487 |
+ Partition: f5.partitionPath, |
|
| 488 |
+ Key: 0, |
|
| 489 |
+ LocalAddress: f5.internalAddress, |
|
| 490 |
+ Mode: "bidirectional", |
|
| 491 |
+ Mtu: "0", |
|
| 492 |
+ Profile: path.Join(f5.partitionPath, F5VxLANProfileName), |
|
| 493 |
+ Tos: "preserve", |
|
| 494 |
+ Transparent: "disabled", |
|
| 495 |
+ UsePmtu: "enabled", |
|
| 496 |
+ } |
|
| 497 |
+ err = f5.post(url, tunnelPayload, nil) |
|
| 498 |
+ if err != nil && err.(F5Error).httpStatusCode != HTTP_CONFLICT_CODE {
|
|
| 499 |
+ // error HTTP_CONFLICT_CODE is fine, it just means the tunnel already exists |
|
| 500 |
+ return err |
|
| 501 |
+ } |
|
| 502 |
+ |
|
| 503 |
+ selfUrl := fmt.Sprintf("https://%s/mgmt/tm/net/self", f5.host)
|
|
| 504 |
+ netSelfPayload := f5CreateNetSelfPayload{
|
|
| 505 |
+ Name: f5.vxlanGateway, |
|
| 506 |
+ Partition: f5.partitionPath, |
|
| 507 |
+ Address: f5.vxlanGateway, |
|
| 508 |
+ AddressSource: "from-user", |
|
| 509 |
+ Floating: "disabled", |
|
| 510 |
+ InheritedTrafficGroup: "false", |
|
| 511 |
+ TrafficGroup: path.Join(f5.partitionPath, "traffic-group-local-only"), |
|
| 512 |
+ Unit: 0, |
|
| 513 |
+ Vlan: path.Join(f5.partitionPath, F5VxLANTunnelName), |
|
| 514 |
+ AllowService: "all", |
|
| 515 |
+ } |
|
| 516 |
+ // create the net self IP |
|
| 517 |
+ err = f5.post(selfUrl, netSelfPayload, nil) |
|
| 518 |
+ if err != nil && err.(F5Error).httpStatusCode != HTTP_CONFLICT_CODE {
|
|
| 519 |
+ // error HTTP_CONFLICT_CODE is ok, netSelf already exists |
|
| 520 |
+ return err |
|
| 521 |
+ } |
|
| 522 |
+ |
|
| 523 |
+ return nil |
|
| 524 |
+} |
|
| 525 |
+ |
|
| 464 | 526 |
// ensurePolicyExists checks whether the specified policy exists and creates it |
| 465 | 527 |
// if not. |
| 466 | 528 |
func (f5 *f5LTM) ensurePolicyExists(policyName string) error {
|
| ... | ... |
@@ -484,14 +570,28 @@ func (f5 *f5LTM) ensurePolicyExists(policyName string) error {
|
| 484 | 484 |
|
| 485 | 485 |
policiesUrl := fmt.Sprintf("https://%s/mgmt/tm/ltm/policy", f5.host)
|
| 486 | 486 |
|
| 487 |
- policyPayload := f5Policy{
|
|
| 488 |
- Name: policyName, |
|
| 489 |
- Controls: []string{"forwarding"},
|
|
| 490 |
- Requires: []string{"http"},
|
|
| 491 |
- Strategy: "best-match", |
|
| 487 |
+ if f5.setupOSDNVxLAN {
|
|
| 488 |
+ // if vxlan needs to be setup, it will only happen |
|
| 489 |
+ // with ver12, for which we need to use a different payload |
|
| 490 |
+ policyPayload := f5Ver12Policy{
|
|
| 491 |
+ Name: policyName, |
|
| 492 |
+ TmPartition: f5.partitionPath, |
|
| 493 |
+ Controls: []string{"forwarding"},
|
|
| 494 |
+ Requires: []string{"http"},
|
|
| 495 |
+ Strategy: "best-match", |
|
| 496 |
+ Legacy: true, |
|
| 497 |
+ } |
|
| 498 |
+ err = f5.post(policiesUrl, policyPayload, nil) |
|
| 499 |
+ } else {
|
|
| 500 |
+ policyPayload := f5Policy{
|
|
| 501 |
+ Name: policyName, |
|
| 502 |
+ Controls: []string{"forwarding"},
|
|
| 503 |
+ Requires: []string{"http"},
|
|
| 504 |
+ Strategy: "best-match", |
|
| 505 |
+ } |
|
| 506 |
+ err = f5.post(policiesUrl, policyPayload, nil) |
|
| 492 | 507 |
} |
| 493 | 508 |
|
| 494 |
- err = f5.post(policiesUrl, policyPayload, nil) |
|
| 495 | 509 |
if err != nil {
|
| 496 | 510 |
return err |
| 497 | 511 |
} |
| ... | ... |
@@ -717,7 +817,7 @@ func (f5 *f5LTM) addPartitionPath(pathName string) (bool, error) {
|
| 717 | 717 |
payload := f5AddPartitionPathPayload{Name: pathName}
|
| 718 | 718 |
err := f5.post(uri, payload, nil) |
| 719 | 719 |
if err != nil {
|
| 720 |
- if err.(F5Error).httpStatusCode != 409 {
|
|
| 720 |
+ if err.(F5Error).httpStatusCode != HTTP_CONFLICT_CODE {
|
|
| 721 | 721 |
glog.Errorf("Error adding partition path %q error: %v", pathName, err)
|
| 722 | 722 |
return false, err |
| 723 | 723 |
} |
| ... | ... |
@@ -828,11 +928,77 @@ func (f5 *f5LTM) Initialize() error {
|
| 828 | 828 |
} |
| 829 | 829 |
} |
| 830 | 830 |
|
| 831 |
+ if f5.setupOSDNVxLAN {
|
|
| 832 |
+ err = f5.ensureVxLANTunnel() |
|
| 833 |
+ if err != nil {
|
|
| 834 |
+ return err |
|
| 835 |
+ } |
|
| 836 |
+ } |
|
| 837 |
+ |
|
| 831 | 838 |
glog.V(4).Infof("F5 initialization is complete.")
|
| 832 | 839 |
|
| 833 | 840 |
return nil |
| 834 | 841 |
} |
| 835 | 842 |
|
| 843 |
+func checkIPAndGetMac(ipStr string) (string, error) {
|
|
| 844 |
+ ip := net.ParseIP(ipStr) |
|
| 845 |
+ if ip == nil {
|
|
| 846 |
+ errStr := fmt.Sprintf("vtep IP '%s' is not a valid IP address", ipStr)
|
|
| 847 |
+ glog.Warning(errStr) |
|
| 848 |
+ return "", fmt.Errorf(errStr) |
|
| 849 |
+ } |
|
| 850 |
+ ip4 := ip.To4() |
|
| 851 |
+ if ip4 == nil {
|
|
| 852 |
+ errStr := fmt.Sprintf("vtep IP '%s' is not a valid IPv4 address", ipStr)
|
|
| 853 |
+ glog.Warning(errStr) |
|
| 854 |
+ return "", fmt.Errorf(errStr) |
|
| 855 |
+ } |
|
| 856 |
+ macAddr := fmt.Sprintf("0a:0a:%02x:%02x:%02x:%02x", ip4[0], ip4[1], ip4[2], ip4[3])
|
|
| 857 |
+ return macAddr, nil |
|
| 858 |
+} |
|
| 859 |
+ |
|
| 860 |
+// AddVtep adds the Vtep IP to the VxLAN device's FDB |
|
| 861 |
+func (f5 *f5LTM) AddVtep(ipStr string) error {
|
|
| 862 |
+ if !f5.setupOSDNVxLAN {
|
|
| 863 |
+ return nil |
|
| 864 |
+ } |
|
| 865 |
+ macAddr, err := checkIPAndGetMac(ipStr) |
|
| 866 |
+ if err != nil {
|
|
| 867 |
+ return err |
|
| 868 |
+ } |
|
| 869 |
+ |
|
| 870 |
+ err = f5.ensurePartitionPathExists(f5.partitionPath) |
|
| 871 |
+ if err != nil {
|
|
| 872 |
+ return err |
|
| 873 |
+ } |
|
| 874 |
+ |
|
| 875 |
+ url := fmt.Sprintf("https://%s/mgmt/tm/net/fdb/tunnel/%s~%s/records", f5.host, strings.Replace(f5.partitionPath, "/", "~", -1), F5VxLANTunnelName)
|
|
| 876 |
+ payload := f5AddFDBRecordPayload{
|
|
| 877 |
+ Name: macAddr, |
|
| 878 |
+ Endpoint: ipStr, |
|
| 879 |
+ } |
|
| 880 |
+ return f5.post(url, payload, nil) |
|
| 881 |
+} |
|
| 882 |
+ |
|
| 883 |
+// RemoveVtep removes the Vtep IP from the VxLAN device's FDB |
|
| 884 |
+func (f5 *f5LTM) RemoveVtep(ipStr string) error {
|
|
| 885 |
+ if !f5.setupOSDNVxLAN {
|
|
| 886 |
+ return nil |
|
| 887 |
+ } |
|
| 888 |
+ macAddr, err := checkIPAndGetMac(ipStr) |
|
| 889 |
+ if err != nil {
|
|
| 890 |
+ return err |
|
| 891 |
+ } |
|
| 892 |
+ |
|
| 893 |
+ err = f5.ensurePartitionPathExists(f5.partitionPath) |
|
| 894 |
+ if err != nil {
|
|
| 895 |
+ return err |
|
| 896 |
+ } |
|
| 897 |
+ |
|
| 898 |
+ url := fmt.Sprintf("https://%s/mgmt/tm/net/fdb/tunnel/%s~%s/records/%s", f5.host, strings.Replace(f5.partitionPath, "/", "~", -1), F5VxLANTunnelName, macAddr)
|
|
| 899 |
+ return f5.delete(url, nil) |
|
| 900 |
+} |
|
| 901 |
+ |
|
| 836 | 902 |
// CreatePool creates a pool named poolname on F5 BIG-IP. |
| 837 | 903 |
func (f5 *f5LTM) CreatePool(poolname string) error {
|
| 838 | 904 |
url := fmt.Sprintf("https://%s/mgmt/tm/ltm/pool", f5.host)
|
| ... | ... |
@@ -1095,7 +1261,7 @@ func (f5 *f5LTM) addRoute(policyname, routename, poolname, hostname, |
| 1095 | 1095 |
|
| 1096 | 1096 |
err := f5.post(rulesUrl, rulesPayload, nil) |
| 1097 | 1097 |
if err != nil {
|
| 1098 |
- if err.(F5Error).httpStatusCode == 409 {
|
|
| 1098 |
+ if err.(F5Error).httpStatusCode == HTTP_CONFLICT_CODE {
|
|
| 1099 | 1099 |
glog.V(4).Infof("Warning: Rule %s already exists; continuing with"+
|
| 1100 | 1100 |
" initialization in case the existing rule is only partially"+ |
| 1101 | 1101 |
" initialized...", routename) |
| ... | ... |
@@ -9,6 +9,7 @@ import ( |
| 9 | 9 |
"k8s.io/kubernetes/pkg/watch" |
| 10 | 10 |
|
| 11 | 11 |
routeapi "github.com/openshift/origin/pkg/route/api" |
| 12 |
+ "github.com/openshift/origin/pkg/util/netutils" |
|
| 12 | 13 |
) |
| 13 | 14 |
|
| 14 | 15 |
// F5Plugin holds state for the f5 plugin. |
| ... | ... |
@@ -52,19 +53,32 @@ type F5PluginConfig struct {
|
| 52 | 52 |
// PartitionPath specifies the F5 partition path to use. This is used |
| 53 | 53 |
// to create an access control boundary for users and applications. |
| 54 | 54 |
PartitionPath string |
| 55 |
+ |
|
| 56 |
+ // VxlanGateway is the ip address assigned to the local tunnel interface |
|
| 57 |
+ // inside F5 box. This address is the one that the packets generated from F5 |
|
| 58 |
+ // will carry. The pods will return the packets to this address itself. |
|
| 59 |
+ // It is important that the gateway be one of the ip addresses of the subnet |
|
| 60 |
+ // that has been generated for F5. |
|
| 61 |
+ VxlanGateway string |
|
| 62 |
+ |
|
| 63 |
+ // InternalAddress is the ip address of the vtep interface used to connect to |
|
| 64 |
+ // VxLAN overlay. It is the hostIP address listed in the subnet generated for F5 |
|
| 65 |
+ InternalAddress string |
|
| 55 | 66 |
} |
| 56 | 67 |
|
| 57 | 68 |
// NewF5Plugin makes a new f5 router plugin. |
| 58 | 69 |
func NewF5Plugin(cfg F5PluginConfig) (*F5Plugin, error) {
|
| 59 | 70 |
f5LTMCfg := f5LTMCfg{
|
| 60 |
- host: cfg.Host, |
|
| 61 |
- username: cfg.Username, |
|
| 62 |
- password: cfg.Password, |
|
| 63 |
- httpVserver: cfg.HttpVserver, |
|
| 64 |
- httpsVserver: cfg.HttpsVserver, |
|
| 65 |
- privkey: cfg.PrivateKey, |
|
| 66 |
- insecure: cfg.Insecure, |
|
| 67 |
- partitionPath: cfg.PartitionPath, |
|
| 71 |
+ host: cfg.Host, |
|
| 72 |
+ username: cfg.Username, |
|
| 73 |
+ password: cfg.Password, |
|
| 74 |
+ httpVserver: cfg.HttpVserver, |
|
| 75 |
+ httpsVserver: cfg.HttpsVserver, |
|
| 76 |
+ privkey: cfg.PrivateKey, |
|
| 77 |
+ insecure: cfg.Insecure, |
|
| 78 |
+ partitionPath: cfg.PartitionPath, |
|
| 79 |
+ vxlanGateway: cfg.VxlanGateway, |
|
| 80 |
+ internalAddress: cfg.InternalAddress, |
|
| 68 | 81 |
} |
| 69 | 82 |
f5, err := newF5LTM(f5LTMCfg) |
| 70 | 83 |
if err != nil {
|
| ... | ... |
@@ -466,10 +480,54 @@ func (p *F5Plugin) deleteRoute(routename string) error {
|
| 466 | 466 |
return nil |
| 467 | 467 |
} |
| 468 | 468 |
|
| 469 |
+func getNodeIP(node *kapi.Node) (string, error) {
|
|
| 470 |
+ if len(node.Status.Addresses) > 0 && node.Status.Addresses[0].Address != "" {
|
|
| 471 |
+ return node.Status.Addresses[0].Address, nil |
|
| 472 |
+ } else {
|
|
| 473 |
+ return netutils.GetNodeIP(node.Name) |
|
| 474 |
+ } |
|
| 475 |
+} |
|
| 476 |
+ |
|
| 469 | 477 |
func (p *F5Plugin) HandleNamespaces(namespaces sets.String) error {
|
| 470 | 478 |
return fmt.Errorf("namespace limiting for F5 is not implemented")
|
| 471 | 479 |
} |
| 472 | 480 |
|
| 481 |
+func (p *F5Plugin) HandleNode(eventType watch.EventType, node *kapi.Node) error {
|
|
| 482 |
+ // The F5 appliance, if hooked to use the VxLAN encapsulation |
|
| 483 |
+ // should have its FDB updated depending on nodes arriving and leaving the cluster |
|
| 484 |
+ switch eventType {
|
|
| 485 |
+ case watch.Added: |
|
| 486 |
+ // New VTEP created, add the record to the vxlan fdb |
|
| 487 |
+ ip, err := getNodeIP(node) |
|
| 488 |
+ if err != nil {
|
|
| 489 |
+ // just log the error |
|
| 490 |
+ glog.Warningf("Error in obtaining IP address of newly added node %s - %v", node.Name, err)
|
|
| 491 |
+ return nil |
|
| 492 |
+ } |
|
| 493 |
+ err = p.F5Client.AddVtep(ip) |
|
| 494 |
+ if err != nil {
|
|
| 495 |
+ glog.Errorf("Error in adding node '%s' to F5s FDB - %v", ip, err)
|
|
| 496 |
+ return err |
|
| 497 |
+ } |
|
| 498 |
+ case watch.Deleted: |
|
| 499 |
+ // VTEP deleted, delete the record from vxlan fdb |
|
| 500 |
+ ip, err := getNodeIP(node) |
|
| 501 |
+ if err != nil {
|
|
| 502 |
+ // just log the error |
|
| 503 |
+ glog.Warningf("Error in obtaining IP address of deleted node %s - %v", node.Name, err)
|
|
| 504 |
+ return nil |
|
| 505 |
+ } |
|
| 506 |
+ err = p.F5Client.RemoveVtep(ip) |
|
| 507 |
+ if err != nil {
|
|
| 508 |
+ glog.Errorf("Error in removing node '%s' from F5s FDB - %v", ip, err)
|
|
| 509 |
+ return err |
|
| 510 |
+ } |
|
| 511 |
+ case watch.Modified: |
|
| 512 |
+ // ignore the modified event. Change in IP address of the node is not supported. |
|
| 513 |
+ } |
|
| 514 |
+ return nil |
|
| 515 |
+} |
|
| 516 |
+ |
|
| 473 | 517 |
// HandleRoute processes watch events on the Route resource and |
| 474 | 518 |
// creates and deletes policy rules in response. |
| 475 | 519 |
func (p *F5Plugin) HandleRoute(eventType watch.EventType, |
| ... | ... |
@@ -100,6 +100,37 @@ type f5PoolMemberset struct {
|
| 100 | 100 |
Members []f5PoolMember `json:"items"` |
| 101 | 101 |
} |
| 102 | 102 |
|
| 103 |
+// f5Ver12Policy represents an F5 BIG-IP LTM policy for versions 12.x |
|
| 104 |
+// It describes the payload for a POST request by which the router creates a new policy. |
|
| 105 |
+type f5Ver12Policy struct {
|
|
| 106 |
+ // Name is the name of the policy. |
|
| 107 |
+ Name string `json:"name"` |
|
| 108 |
+ |
|
| 109 |
+ // TmPartition is the partition name for the policy |
|
| 110 |
+ TmPartition string `json:"tmPartition"` |
|
| 111 |
+ |
|
| 112 |
+ // Controls is a list of F5 BIG-IP LTM features enabled for the pool. |
|
| 113 |
+ // Typically we use just forwarding; other possible values are caching, |
|
| 114 |
+ // classification, compression, request-adaption, response-adaption, and |
|
| 115 |
+ // server-ssl. |
|
| 116 |
+ Controls []string `json:"controls"` |
|
| 117 |
+ |
|
| 118 |
+ // Requires is a list of available profile types. Typically we use just http; |
|
| 119 |
+ // other possible values are client-ssl, ssl-persistence, and tcp. |
|
| 120 |
+ Requires []string `json:"requires"` |
|
| 121 |
+ |
|
| 122 |
+ // Strategy is the strategy according to which rules are applied to incoming |
|
| 123 |
+ // connections when more than one rule matches. Typically we use best-match; |
|
| 124 |
+ // other possible values are all-match and first-match. |
|
| 125 |
+ Strategy string `json:"strategy"` |
|
| 126 |
+ |
|
| 127 |
+ // Legacy is the boolean keyword by which ver12.1 can be programmed |
|
| 128 |
+ // for creating a policy using this payload. Eventually we need to move |
|
| 129 |
+ // to creating Draft policies and then associating them with the virtual servers |
|
| 130 |
+ // Note that this keyword will only work with versions 12.1 and above |
|
| 131 |
+ Legacy bool `json:"legacy"` |
|
| 132 |
+} |
|
| 133 |
+ |
|
| 103 | 134 |
// f5Policy represents an F5 BIG-IP LTM policy. It describes the payload for |
| 104 | 135 |
// a POST request by which the F5 router creates a new policy. |
| 105 | 136 |
type f5Policy struct {
|
| ... | ... |
@@ -293,3 +324,46 @@ type f5AddPartitionPathPayload struct {
|
| 293 | 293 |
// Name is the partition path to be added. |
| 294 | 294 |
Name string `json:"name"` |
| 295 | 295 |
} |
| 296 |
+ |
|
| 297 |
+// Method:POST URL:/mgmt/tm/net/tunnels/vxlan |
|
| 298 |
+type f5CreateVxLANProfilePayload struct {
|
|
| 299 |
+ Name string `json:"name"` // <vxlan-profile-name> e.g. vxlan-ose |
|
| 300 |
+ Partition string `json:"partition"` // /Common |
|
| 301 |
+ FloodingType string `json:"floodingType"` // multipoint |
|
| 302 |
+ Port int `json:"port"` // 4789 (nothing else will work) |
|
| 303 |
+} |
|
| 304 |
+ |
|
| 305 |
+// Method:POST URL:/mgmt/tm/net/tunnels/tunnel |
|
| 306 |
+type f5CreateVxLANTunnelPayload struct {
|
|
| 307 |
+ Name string `json:"name"` // vxlan5000 |
|
| 308 |
+ Partition string `json:"partition"` // /Common |
|
| 309 |
+ Key uint32 `json:"key"` // 0 |
|
| 310 |
+ LocalAddress string `json:"localAddress"` // 172.30.1.5 |
|
| 311 |
+ Mode string `json:"mode"` // bidirectional |
|
| 312 |
+ Mtu string `json:"mtu"` // 0 |
|
| 313 |
+ Profile string `json:"profile"` // <partition>/<vxlan-profile-name> |
|
| 314 |
+ Tos string `json:"tos"` // preserve |
|
| 315 |
+ Transparent string `json:"transparent"` // disabled |
|
| 316 |
+ UsePmtu string `json:"usePmtu"` // enabled |
|
| 317 |
+} |
|
| 318 |
+ |
|
| 319 |
+// tmsh create net self <local-overlay-address>/<prefix> vlan vxlan5000 |
|
| 320 |
+// Method: POST URL: /mgmt/tm/net/self |
|
| 321 |
+type f5CreateNetSelfPayload struct {
|
|
| 322 |
+ Name string `json:"name"` // “10.0.1.10/16", |
|
| 323 |
+ Partition string `json:"partition"` // "Common", |
|
| 324 |
+ Address string `json:"address"` // “10.0.1.10/16", |
|
| 325 |
+ AddressSource string `json:"addressSource"` // "from-user", |
|
| 326 |
+ Floating string `json:"floating"` // "disabled", |
|
| 327 |
+ InheritedTrafficGroup string `json:"inheritedTrafficGroup"` // "false", |
|
| 328 |
+ TrafficGroup string `json:"trafficGroup"` // "/Common/traffic-group-local-only", |
|
| 329 |
+ Unit uint32 `json:"unit"` // 0, |
|
| 330 |
+ Vlan string `json:"vlan"` // "/Common/vxlan5000", |
|
| 331 |
+ AllowService string `json:"allowService"` // "all" |
|
| 332 |
+} |
|
| 333 |
+ |
|
| 334 |
+// POST /mgmt/tm/net/fdb/tunnel/~Common~vxlan5000/records |
|
| 335 |
+type f5AddFDBRecordPayload struct {
|
|
| 336 |
+ Name string `json:"name"` // "02:50:56:c0:00:06", |
|
| 337 |
+ Endpoint string `json:"endpoint"` // "10.139.1.1" |
|
| 338 |
+} |
| ... | ... |
@@ -15,5 +15,6 @@ type Plugin interface {
|
| 15 | 15 |
HandleEndpoints(watch.EventType, *kapi.Endpoints) error |
| 16 | 16 |
// If sent, filter the list of accepted routes and endpoints to this set |
| 17 | 17 |
HandleNamespaces(namespaces sets.String) error |
| 18 |
+ HandleNode(watch.EventType, *kapi.Node) error |
|
| 18 | 19 |
SetLastSyncProcessed(processed bool) error |
| 19 | 20 |
} |
| ... | ... |
@@ -176,6 +176,13 @@ func (p *TemplatePlugin) HandleEndpoints(eventType watch.EventType, endpoints *k |
| 176 | 176 |
return nil |
| 177 | 177 |
} |
| 178 | 178 |
|
| 179 |
+// HandleNode processes watch events on the Node resource |
|
| 180 |
+// The template type of plugin currently does not need to act on such events |
|
| 181 |
+// so the implementation just returns without error |
|
| 182 |
+func (p *TemplatePlugin) HandleNode(eventType watch.EventType, node *kapi.Node) error {
|
|
| 183 |
+ return nil |
|
| 184 |
+} |
|
| 185 |
+ |
|
| 179 | 186 |
// HandleRoute processes watch events on the Route resource. |
| 180 | 187 |
// TODO: this function can probably be collapsed with the router itself, as a function that |
| 181 | 188 |
// determines which component needs to be recalculated (which template) and then does so |
| ... | ... |
@@ -23,7 +23,9 @@ var _ = g.Describe("[networking][router] openshift routers", func() {
|
| 23 | 23 |
|
| 24 | 24 |
g.BeforeEach(func() {
|
| 25 | 25 |
// defer oc.Run("delete").Args("-f", configPath).Execute()
|
| 26 |
- err := oc.Run("create").Args("-f", configPath).Execute()
|
|
| 26 |
+ err := oc.AsAdmin().Run("policy").Args("add-role-to-user", "system:router", oc.Username()).Execute()
|
|
| 27 |
+ o.Expect(err).NotTo(o.HaveOccurred()) |
|
| 28 |
+ err = oc.Run("create").Args("-f", configPath).Execute()
|
|
| 27 | 29 |
o.Expect(err).NotTo(o.HaveOccurred()) |
| 28 | 30 |
}) |
| 29 | 31 |
|
| ... | ... |
@@ -27,7 +27,9 @@ var _ = g.Describe("[networking][router] weighted openshift router", func() {
|
| 27 | 27 |
|
| 28 | 28 |
g.BeforeEach(func() {
|
| 29 | 29 |
// defer oc.Run("delete").Args("-f", configPath).Execute()
|
| 30 |
- err := oc.Run("create").Args("-f", configPath).Execute()
|
|
| 30 |
+ err := oc.AsAdmin().Run("policy").Args("add-role-to-user", "system:router", oc.Username()).Execute()
|
|
| 31 |
+ o.Expect(err).NotTo(o.HaveOccurred()) |
|
| 32 |
+ err = oc.Run("create").Args("-f", configPath).Execute()
|
|
| 31 | 33 |
o.Expect(err).NotTo(o.HaveOccurred()) |
| 32 | 34 |
}) |
| 33 | 35 |
|
| ... | ... |
@@ -36,6 +36,8 @@ func GetDefaultLocalAddress() string {
|
| 36 | 36 |
func NewTestHttpService() *TestHttpService {
|
| 37 | 37 |
endpointChannel := make(chan string) |
| 38 | 38 |
routeChannel := make(chan string) |
| 39 |
+ nodeChannel := make(chan string) |
|
| 40 |
+ svcChannel := make(chan string) |
|
| 39 | 41 |
|
| 40 | 42 |
addr := GetDefaultLocalAddress() |
| 41 | 43 |
|
| ... | ... |
@@ -55,6 +57,8 @@ func NewTestHttpService() *TestHttpService {
|
| 55 | 55 |
PodHttpsCaCert: []byte(ExampleCACert), |
| 56 | 56 |
EndpointChannel: endpointChannel, |
| 57 | 57 |
RouteChannel: routeChannel, |
| 58 |
+ NodeChannel: nodeChannel, |
|
| 59 |
+ SvcChannel: svcChannel, |
|
| 58 | 60 |
} |
| 59 | 61 |
} |
| 60 | 62 |
|
| ... | ... |
@@ -77,6 +81,8 @@ type TestHttpService struct {
|
| 77 | 77 |
PodTestPath string |
| 78 | 78 |
EndpointChannel chan string |
| 79 | 79 |
RouteChannel chan string |
| 80 |
+ NodeChannel chan string |
|
| 81 |
+ SvcChannel chan string |
|
| 80 | 82 |
|
| 81 | 83 |
listeners []net.Listener |
| 82 | 84 |
} |
| ... | ... |
@@ -131,6 +137,30 @@ func (s *TestHttpService) handleHelloPodTestSecure(w http.ResponseWriter, r *htt |
| 131 | 131 |
fmt.Fprint(w, HelloPodPathSecure) |
| 132 | 132 |
} |
| 133 | 133 |
|
| 134 |
+// handleSvcList handles calls to /api/v1beta1/services and always returns empty data |
|
| 135 |
+func (s *TestHttpService) handleSvcList(w http.ResponseWriter, r *http.Request) {
|
|
| 136 |
+ w.Header().Set("Content-Type", "application/json")
|
|
| 137 |
+ fmt.Fprint(w, "{}")
|
|
| 138 |
+} |
|
| 139 |
+ |
|
| 140 |
+// handleSvcWatch handles calls to /api/v1beta1/watch/services and uses the svc channel to simulate watch events |
|
| 141 |
+func (s *TestHttpService) handleSvcWatch(w http.ResponseWriter, r *http.Request) {
|
|
| 142 |
+ w.Header().Set("Content-Type", "application/json")
|
|
| 143 |
+ io.WriteString(w, <-s.SvcChannel) |
|
| 144 |
+} |
|
| 145 |
+ |
|
| 146 |
+// handleNodeList handles calls to /api/v1beta1/nodes and always returns empty data |
|
| 147 |
+func (s *TestHttpService) handleNodeList(w http.ResponseWriter, r *http.Request) {
|
|
| 148 |
+ w.Header().Set("Content-Type", "application/json")
|
|
| 149 |
+ fmt.Fprint(w, "{}")
|
|
| 150 |
+} |
|
| 151 |
+ |
|
| 152 |
+// handleNodeWatch handles calls to /api/v1beta1/watch/nodes and uses the node channel to simulate watch events |
|
| 153 |
+func (s *TestHttpService) handleNodeWatch(w http.ResponseWriter, r *http.Request) {
|
|
| 154 |
+ w.Header().Set("Content-Type", "application/json")
|
|
| 155 |
+ io.WriteString(w, <-s.NodeChannel) |
|
| 156 |
+} |
|
| 157 |
+ |
|
| 134 | 158 |
// handleRouteWatch handles calls to /osapi/v1beta1/watch/routes and uses the route channel to simulate watch events |
| 135 | 159 |
func (s *TestHttpService) handleRouteWatch(w http.ResponseWriter, r *http.Request) {
|
| 136 | 160 |
w.Header().Set("Content-Type", "application/json")
|
| ... | ... |
@@ -208,6 +238,10 @@ func (s *TestHttpService) startMaster() error {
|
| 208 | 208 |
masterServer.HandleFunc(fmt.Sprintf("/oapi/%s/routes", version), s.handleRouteList)
|
| 209 | 209 |
masterServer.HandleFunc(fmt.Sprintf("/oapi/%s/namespaces/", version), s.handleRouteCalls)
|
| 210 | 210 |
masterServer.HandleFunc(fmt.Sprintf("/oapi/%s/watch/routes", version), s.handleRouteWatch)
|
| 211 |
+ masterServer.HandleFunc(fmt.Sprintf("/api/%s/nodes", version), s.handleNodeList)
|
|
| 212 |
+ masterServer.HandleFunc(fmt.Sprintf("/api/%s/watch/nodes", version), s.handleNodeWatch)
|
|
| 213 |
+ masterServer.HandleFunc(fmt.Sprintf("/api/%s/services", version), s.handleSvcList)
|
|
| 214 |
+ masterServer.HandleFunc(fmt.Sprintf("/api/%s/watch/services", version), s.handleSvcWatch)
|
|
| 211 | 215 |
} |
| 212 | 216 |
|
| 213 | 217 |
if err := s.startServing(s.MasterHttpAddr, http.Handler(masterServer)); err != nil {
|
| ... | ... |
@@ -238,6 +238,11 @@ func (p *DelayPlugin) HandleRoute(eventType watch.EventType, route *routeapi.Rou |
| 238 | 238 |
return p.plugin.HandleRoute(eventType, route) |
| 239 | 239 |
} |
| 240 | 240 |
|
| 241 |
+func (p *DelayPlugin) HandleNode(eventType watch.EventType, node *kapi.Node) error {
|
|
| 242 |
+ p.delay() |
|
| 243 |
+ return p.plugin.HandleNode(eventType, node) |
|
| 244 |
+} |
|
| 245 |
+ |
|
| 241 | 246 |
func (p *DelayPlugin) HandleEndpoints(eventType watch.EventType, endpoints *kapi.Endpoints) error {
|
| 242 | 247 |
p.delay() |
| 243 | 248 |
return p.plugin.HandleEndpoints(eventType, endpoints) |
| ... | ... |
@@ -277,7 +282,7 @@ func launchRouter(oc osclient.Interface, kc kclient.Interface, maxDelay int32, n |
| 277 | 277 |
} |
| 278 | 278 |
|
| 279 | 279 |
factory := controllerfactory.NewDefaultRouterControllerFactory(oc, kc) |
| 280 |
- controller := factory.Create(plugin) |
|
| 280 |
+ controller := factory.Create(plugin, false) |
|
| 281 | 281 |
controller.Run() |
| 282 | 282 |
|
| 283 | 283 |
return |
| ... | ... |
@@ -1776,6 +1776,14 @@ items: |
| 1776 | 1776 |
- "" |
| 1777 | 1777 |
attributeRestrictions: null |
| 1778 | 1778 |
resources: |
| 1779 |
+ - nodes |
|
| 1780 |
+ verbs: |
|
| 1781 |
+ - list |
|
| 1782 |
+ - watch |
|
| 1783 |
+ - apiGroups: |
|
| 1784 |
+ - "" |
|
| 1785 |
+ attributeRestrictions: null |
|
| 1786 |
+ resources: |
|
| 1779 | 1787 |
- routes |
| 1780 | 1788 |
verbs: |
| 1781 | 1789 |
- list |