Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

f5 vxlan integration with sdn #11181

Merged
merged 1 commit into from
Oct 24, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions contrib/completions/bash/openshift
Original file line number Diff line number Diff line change
Expand Up @@ -19713,6 +19713,8 @@ _openshift_infra_f5-router()
local_nonpersistent_flags+=("--f5-https-vserver=")
flags+=("--f5-insecure")
local_nonpersistent_flags+=("--f5-insecure")
flags+=("--f5-internal-address=")
local_nonpersistent_flags+=("--f5-internal-address=")
flags+=("--f5-partition-path=")
local_nonpersistent_flags+=("--f5-partition-path=")
flags+=("--f5-password=")
Expand All @@ -19721,6 +19723,8 @@ _openshift_infra_f5-router()
local_nonpersistent_flags+=("--f5-private-key=")
flags+=("--f5-username=")
local_nonpersistent_flags+=("--f5-username=")
flags+=("--f5-vxlan-gateway-cidr=")
local_nonpersistent_flags+=("--f5-vxlan-gateway-cidr=")
flags+=("--fields=")
local_nonpersistent_flags+=("--fields=")
flags+=("--hostname-template=")
Expand Down
4 changes: 4 additions & 0 deletions contrib/completions/zsh/openshift
Original file line number Diff line number Diff line change
Expand Up @@ -19874,6 +19874,8 @@ _openshift_infra_f5-router()
local_nonpersistent_flags+=("--f5-https-vserver=")
flags+=("--f5-insecure")
local_nonpersistent_flags+=("--f5-insecure")
flags+=("--f5-internal-address=")
local_nonpersistent_flags+=("--f5-internal-address=")
flags+=("--f5-partition-path=")
local_nonpersistent_flags+=("--f5-partition-path=")
flags+=("--f5-password=")
Expand All @@ -19882,6 +19884,8 @@ _openshift_infra_f5-router()
local_nonpersistent_flags+=("--f5-private-key=")
flags+=("--f5-username=")
local_nonpersistent_flags+=("--f5-username=")
flags+=("--f5-vxlan-gateway-cidr=")
local_nonpersistent_flags+=("--f5-vxlan-gateway-cidr=")
flags+=("--fields=")
local_nonpersistent_flags+=("--fields=")
flags+=("--hostname-template=")
Expand Down
8 changes: 8 additions & 0 deletions docs/man/man1/openshift-infra-f5-router.1
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ You may restrict the set of routes exposed to a single project (with \-\-namespa
\fB\-\-f5\-insecure\fP=false
Skip strict certificate verification

.PP
\fB\-\-f5\-internal\-address\fP=""
The F5 BIG\-IP internal interface's IP address

.PP
\fB\-\-f5\-partition\-path\fP="/Common"
The F5 BIG\-IP partition path to use
Expand All @@ -87,6 +91,10 @@ You may restrict the set of routes exposed to a single project (with \-\-namespa
\fB\-\-f5\-username\fP=""
The username for F5 BIG\-IP's management utility

.PP
\fB\-\-f5\-vxlan\-gateway\-cidr\fP=""
The F5 BIG\-IP gateway\-ip\-address/cidr\-mask for setting up the VxLAN

.PP
\fB\-\-fields\fP=""
A field selector to apply to routes to watch
Expand Down
39 changes: 30 additions & 9 deletions pkg/cmd/infra/router/f5.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,17 @@ type F5Router struct {
// normally used to create access control boundaries for users
// and applications.
PartitionPath string

// VxlanGateway is the ip address assigned to the local tunnel interface
// inside F5 box. This address is the one that the packets generated from F5
// will carry. The pods will return the packets to this address itself.
// It is important that the gateway be one of the ip addresses of the subnet
// that has been generated for F5.
VxlanGateway string

// InternalAddress is the ip address of the vtep interface used to connect to
// VxLAN overlay. It is the hostIP address listed in the subnet generated for F5
InternalAddress string
}

// Bind binds F5Router arguments to flags
Expand All @@ -89,6 +100,8 @@ func (o *F5Router) Bind(flag *pflag.FlagSet) {
flag.StringVar(&o.PrivateKey, "f5-private-key", util.Env("ROUTER_EXTERNAL_HOST_PRIVKEY", ""), "The path to the F5 BIG-IP SSH private key file")
flag.BoolVar(&o.Insecure, "f5-insecure", util.Env("ROUTER_EXTERNAL_HOST_INSECURE", "") == "true", "Skip strict certificate verification")
flag.StringVar(&o.PartitionPath, "f5-partition-path", util.Env("ROUTER_EXTERNAL_HOST_PARTITION_PATH", f5plugin.F5DefaultPartitionPath), "The F5 BIG-IP partition path to use")
flag.StringVar(&o.InternalAddress, "f5-internal-address", util.Env("ROUTER_EXTERNAL_HOST_INTERNAL_ADDRESS", ""), "The F5 BIG-IP internal interface's IP address")
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")
}

// Validate verifies the required F5 flags are present
Expand All @@ -109,6 +122,11 @@ func (o *F5Router) Validate() error {
return errors.New("F5 HTTP and HTTPS vservers cannot both be blank")
}

valid := (len(o.VxlanGateway) == 0 && len(o.InternalAddress) == 0) || (len(o.VxlanGateway) != 0 && len(o.InternalAddress) != 0)
if !valid {
return errors.New("For VxLAN setup, both internal-address and gateway-cidr must be specified")
}

return nil
}

Expand Down Expand Up @@ -158,14 +176,16 @@ func (o *F5RouterOptions) Validate() error {
// Run launches an F5 route sync process using the provided options. It never exits.
func (o *F5RouterOptions) Run() error {
cfg := f5plugin.F5PluginConfig{
Host: o.Host,
Username: o.Username,
Password: o.Password,
HttpVserver: o.HttpVserver,
HttpsVserver: o.HttpsVserver,
PrivateKey: o.PrivateKey,
Insecure: o.Insecure,
PartitionPath: o.PartitionPath,
Host: o.Host,
Username: o.Username,
Password: o.Password,
HttpVserver: o.HttpVserver,
HttpsVserver: o.HttpsVserver,
PrivateKey: o.PrivateKey,
Insecure: o.Insecure,
PartitionPath: o.PartitionPath,
InternalAddress: o.InternalAddress,
VxlanGateway: o.VxlanGateway,
}
f5Plugin, err := f5plugin.NewF5Plugin(cfg)
if err != nil {
Expand All @@ -181,7 +201,8 @@ func (o *F5RouterOptions) Run() error {
plugin := controller.NewUniqueHost(statusPlugin, o.RouteSelectionFunc(), statusPlugin)

factory := o.RouterSelection.NewFactory(oc, kc)
controller := factory.Create(plugin)
watchNodes := (len(o.InternalAddress) != 0 && len(o.VxlanGateway) != 0)
controller := factory.Create(plugin, watchNodes)
controller.Run()

select {}
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/infra/router/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ func (o *TemplateRouterOptions) Run() error {
plugin := controller.NewUniqueHost(nextPlugin, o.RouteSelectionFunc(), controller.RejectionRecorder(statusPlugin))

factory := o.RouterSelection.NewFactory(oc, kc)
controller := factory.Create(plugin)
controller := factory.Create(plugin, false)
controller.Run()

proc.StartReaper()
Expand Down
1 change: 1 addition & 0 deletions pkg/cmd/server/bootstrappolicy/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,7 @@ func GetBootstrapClusterRoles() []authorizationapi.ClusterRole {
Rules: []authorizationapi.PolicyRule{
authorizationapi.NewRule("list", "watch").Groups(kapiGroup).Resources("endpoints").RuleOrDie(),
authorizationapi.NewRule("list", "watch").Groups(kapiGroup).Resources("services").RuleOrDie(),
authorizationapi.NewRule("list", "watch").Groups(kapiGroup).Resources("nodes").RuleOrDie(),

authorizationapi.NewRule("list", "watch").Groups(routeGroup).Resources("routes").RuleOrDie(),
authorizationapi.NewRule("update").Groups(routeGroup).Resources("routes/status").RuleOrDie(),
Expand Down
25 changes: 25 additions & 0 deletions pkg/router/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type RouterController struct {

Plugin router.Plugin
NextRoute func() (watch.EventType, *routeapi.Route, error)
NextNode func() (watch.EventType, *kapi.Node, error)
NextEndpoints func() (watch.EventType, *kapi.Endpoints, error)

RoutesListConsumed func() bool
Expand All @@ -36,6 +37,8 @@ type RouterController struct {
endpointsListConsumed bool
filteredByNamespace bool

WatchNodes bool

Namespaces NamespaceLister
NamespaceSyncInterval time.Duration
NamespaceWaitInterval time.Duration
Expand All @@ -51,6 +54,9 @@ func (c *RouterController) Run() {
}
go utilwait.Forever(c.HandleRoute, 0)
go utilwait.Forever(c.HandleEndpoints, 0)
if c.WatchNodes {
go utilwait.Forever(c.HandleNode, 0)
}
}

func (c *RouterController) HandleNamespaces() {
Expand Down Expand Up @@ -78,6 +84,25 @@ func (c *RouterController) HandleNamespaces() {
glog.V(4).Infof("Unable to update list of namespaces")
}

// HandleNode handles a single Node event and synchronizes the router backend
func (c *RouterController) HandleNode() {
eventType, node, err := c.NextNode()
if err != nil {
utilruntime.HandleError(fmt.Errorf("unable to read nodes: %v", err))
return
}

c.lock.Lock()
defer c.lock.Unlock()

glog.V(4).Infof("Processing Node : %s", node.Name)
glog.V(4).Infof(" Event: %s", eventType)

if err := c.Plugin.HandleNode(eventType, node); err != nil {
utilruntime.HandleError(err)
}
}

// HandleRoute handles a single Route event and synchronizes the router backend.
func (c *RouterController) HandleRoute() {
eventType, route, err := c.NextRoute()
Expand Down
6 changes: 6 additions & 0 deletions pkg/router/controller/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ type fakeRouterPlugin struct {
func (p *fakeRouterPlugin) HandleRoute(t watch.EventType, route *routeapi.Route) error {
return nil
}
func (p *fakeRouterPlugin) HandleNode(t watch.EventType, node *kapi.Node) error {
return nil
}
func (p *fakeRouterPlugin) HandleEndpoints(watch.EventType, *kapi.Endpoints) error {
return nil
}
Expand Down Expand Up @@ -46,6 +49,9 @@ func TestRouterController_updateLastSyncProcessed(t *testing.T) {
NextRoute: func() (watch.EventType, *routeapi.Route, error) {
return watch.Modified, &routeapi.Route{}, nil
},
NextNode: func() (watch.EventType, *kapi.Node, error) {
return watch.Modified, &kapi.Node{}, nil
},
EndpointsListConsumed: func() bool {
return true
},
Expand Down
5 changes: 5 additions & 0 deletions pkg/router/controller/extended_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ func NewExtendedValidator(plugin router.Plugin, recorder RejectionRecorder) *Ext
}
}

// HandleNode processes watch events on the node resource
func (p *ExtendedValidator) HandleNode(eventType watch.EventType, node *kapi.Node) error {
return p.plugin.HandleNode(eventType, node)
}

// HandleEndpoints processes watch events on the Endpoints resource.
func (p *ExtendedValidator) HandleEndpoints(eventType watch.EventType, endpoints *kapi.Endpoints) error {
return p.plugin.HandleEndpoints(eventType, endpoints)
Expand Down
43 changes: 41 additions & 2 deletions pkg/router/controller/factory/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
type RouterControllerFactory struct {
KClient kclient.EndpointsNamespacer
OSClient osclient.RoutesNamespacer
NodeClient kclient.NodesInterface
Namespaces controller.NamespaceLister
ResyncInterval time.Duration
Namespace string
Expand All @@ -35,10 +36,11 @@ type RouterControllerFactory struct {
}

// NewDefaultRouterControllerFactory initializes a default router controller factory.
func NewDefaultRouterControllerFactory(oc osclient.RoutesNamespacer, kc kclient.EndpointsNamespacer) *RouterControllerFactory {
func NewDefaultRouterControllerFactory(oc osclient.RoutesNamespacer, kc kclient.Interface) *RouterControllerFactory {
return &RouterControllerFactory{
KClient: kc,
OSClient: oc,
NodeClient: kc,
ResyncInterval: 10 * time.Minute,

Namespace: kapi.NamespaceAll,
Expand All @@ -49,7 +51,7 @@ func NewDefaultRouterControllerFactory(oc osclient.RoutesNamespacer, kc kclient.

// Create begins listing and watching against the API server for the desired route and endpoint
// resources. It spawns child goroutines that cannot be terminated.
func (factory *RouterControllerFactory) Create(plugin router.Plugin) *controller.RouterController {
func (factory *RouterControllerFactory) Create(plugin router.Plugin, watchNodes bool) *controller.RouterController {
routeEventQueue := oscache.NewEventQueue(cache.MetaNamespaceKeyFunc)
cache.NewReflector(&routeLW{
client: factory.OSClient,
Expand All @@ -65,6 +67,15 @@ func (factory *RouterControllerFactory) Create(plugin router.Plugin) *controller
// we do not scope endpoints by labels or fields because the route labels != endpoints labels
}, &kapi.Endpoints{}, endpointsEventQueue, factory.ResyncInterval).Run()

nodeEventQueue := oscache.NewEventQueue(cache.MetaNamespaceKeyFunc)
if watchNodes {
cache.NewReflector(&nodeLW{
client: factory.NodeClient,
field: fields.Everything(),
label: labels.Everything(),
}, &kapi.Node{}, nodeEventQueue, factory.ResyncInterval).Run()
}

return &controller.RouterController{
Plugin: plugin,
NextEndpoints: func() (watch.EventType, *kapi.Endpoints, error) {
Expand All @@ -81,6 +92,13 @@ func (factory *RouterControllerFactory) Create(plugin router.Plugin) *controller
}
return eventType, obj.(*routeapi.Route), nil
},
NextNode: func() (watch.EventType, *kapi.Node, error) {
eventType, obj, err := nodeEventQueue.Pop()
if err != nil {
return watch.Error, nil, err
}
return eventType, obj.(*kapi.Node), nil
},
EndpointsListConsumed: func() bool {
return endpointsEventQueue.ListConsumed()
},
Expand All @@ -94,6 +112,7 @@ func (factory *RouterControllerFactory) Create(plugin router.Plugin) *controller
NamespaceSyncInterval: factory.ResyncInterval - 10*time.Second,
NamespaceWaitInterval: 10 * time.Second,
NamespaceRetries: 5,
WatchNodes: watchNodes,
}
}

Expand Down Expand Up @@ -256,3 +275,23 @@ func (lw *endpointsLW) Watch(options kapi.ListOptions) (watch.Interface, error)
}
return lw.client.Endpoints(lw.namespace).Watch(opts)
}

// nodeLW is a list watcher for nodes.
type nodeLW struct {
client kclient.NodesInterface
label labels.Selector
field fields.Selector
}

func (lw *nodeLW) List(options kapi.ListOptions) (runtime.Object, error) {
return lw.client.Nodes().List(options)
}

func (lw *nodeLW) Watch(options kapi.ListOptions) (watch.Interface, error) {
opts := kapi.ListOptions{
LabelSelector: lw.label,
FieldSelector: lw.field,
ResourceVersion: options.ResourceVersion,
}
return lw.client.Nodes().Watch(opts)
}
4 changes: 4 additions & 0 deletions pkg/router/controller/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,10 @@ func (a *StatusAdmitter) HandleRoute(eventType watch.EventType, route *routeapi.
return a.plugin.HandleRoute(eventType, route)
}

func (a *StatusAdmitter) HandleNode(eventType watch.EventType, node *kapi.Node) error {
return a.plugin.HandleNode(eventType, node)
}

func (a *StatusAdmitter) HandleEndpoints(eventType watch.EventType, route *kapi.Endpoints) error {
return a.plugin.HandleEndpoints(eventType, route)
}
Expand Down
5 changes: 5 additions & 0 deletions pkg/router/controller/status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ func (p *fakePlugin) HandleRoute(t watch.EventType, route *routeapi.Route) error
p.t, p.route = t, route
return p.err
}

func (p *fakePlugin) HandleNode(t watch.EventType, node *kapi.Node) error {
return fmt.Errorf("not expected")
}

func (p *fakePlugin) HandleEndpoints(watch.EventType, *kapi.Endpoints) error {
return fmt.Errorf("not expected")
}
Expand Down
5 changes: 5 additions & 0 deletions pkg/router/controller/unique_host.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ func (p *UniqueHost) HandleEndpoints(eventType watch.EventType, endpoints *kapi.
return p.plugin.HandleEndpoints(eventType, endpoints)
}

// HandleNode processes watch events on the Node resource and calls the router
func (p *UniqueHost) HandleNode(eventType watch.EventType, node *kapi.Node) error {
return p.plugin.HandleNode(eventType, node)
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, depending on which PR merges first - #11201 - will need to add HandleNode to the host_admitter plugin.

// HandleRoute processes watch events on the Route resource.
// TODO: this function can probably be collapsed with the router itself, as a function that
// determines which component needs to be recalculated (which template) and then does so
Expand Down
Loading