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

Support F5 partition path #13391

Merged
merged 3 commits into from
Apr 5, 2017
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
133 changes: 95 additions & 38 deletions pkg/router/f5/f5.go
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,50 @@ func (f5 *f5LTM) delete(url string, result interface{}) error {
return f5.restRequest("DELETE", url, nil, result)
}

//
// iControl REST resource helper methods.
//

// encodeiControlUriPathComponent returns an encoded resource path for use
// in the URI for the iControl REST calls.
// For example for a path /Common/foo, the corresponding encoded iControl
// URI path component would be ~Common~foo and this can then be used in the
// iControl REST calls ala:
// https://<ip>:<port>/mgmt/tm/ltm/policy/~Common~foo/rules
func encodeiControlUriPathComponent(pathName string) string {
return strings.Replace(pathName, "/", "~", -1)
}

// iControlUriResourceId returns an encoded resource id (resource path
// including the partition), which can be used the iControl REST calls.
// For example, for a policy named openshift_secure_routes policy in the
// /Common partition, the encoded resource id would be:
// ~Common~openshift_secure_routes
// which can then be used as a resource specifier in the URI ala:
// https://<ip>:<port>/mgmt/tm/ltm/policy/~Common~openshift_secure_routes/rules
func (f5 *f5LTM) iControlUriResourceId(resourceName string) string {
resourcePath := path.Join(f5.partitionPath, resourceName)
return encodeiControlUriPathComponent(resourcePath)
}

// iControlUriVserverId returns an encoded Virtual Server id (virtual
// server name including the partition), which can be used in the iControl
// REST calls.
// For example, for a virtual server named ose-vserver in
// the /rht/ose3/config partition path, the encoded Virtual Server id is:
// ~rht~ose-vserver
func (f5 *f5LTM) iControlUriVserverId(vserverName string) string {
// Note: Most resources are stored under the configured partition
// path, which may be a top-level folder or a sub-folder.
// However a virtual server can only be stored under a
// top-level folder.
// Example: For a vserver named ose-server in the /rht/ose3/config
// partition path, the encoded URI path component is:
// ~rht~ose-vserver
pathComponents := strings.Split(f5.partitionPath, "/")
return encodeiControlUriPathComponent(path.Join("/", pathComponents[1], vserverName))
}

//
// Routines for controlling F5.
//
Expand Down Expand Up @@ -532,7 +576,7 @@ func (f5 *f5LTM) ensureVxLANTunnel() error {
AddressSource: "from-user",
Floating: "disabled",
InheritedTrafficGroup: "false",
TrafficGroup: path.Join(f5.partitionPath, "traffic-group-local-only"),
TrafficGroup: path.Join("/Common", "traffic-group-local-only"), // Traffic group is global
Unit: 0,
Vlan: path.Join(f5.partitionPath, F5VxLANTunnelName),

Choose a reason for hiding this comment

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

Vlan is local per partition or is it global similar to traffic-group?

AllowService: "all",
Expand All @@ -552,8 +596,9 @@ func (f5 *f5LTM) ensureVxLANTunnel() error {
func (f5 *f5LTM) ensurePolicyExists(policyName string) error {
glog.V(4).Infof("Checking whether policy %s exists...", policyName)

policyResourceId := f5.iControlUriResourceId(policyName)
policyUrl := fmt.Sprintf("https://%s/mgmt/tm/ltm/policy/%s",
f5.host, policyName)
f5.host, policyResourceId)

err := f5.get(policyUrl, nil)
if err != nil && err.(F5Error).httpStatusCode != 404 {
Expand All @@ -570,11 +615,13 @@ func (f5 *f5LTM) ensurePolicyExists(policyName string) error {

policiesUrl := fmt.Sprintf("https://%s/mgmt/tm/ltm/policy", f5.host)

policyPath := path.Join(f5.partitionPath, policyName)

if f5.setupOSDNVxLAN {
// if vxlan needs to be setup, it will only happen
// with ver12, for which we need to use a different payload
policyPayload := f5Ver12Policy{
Name: policyName,
Name: policyPath,
TmPartition: f5.partitionPath,
Controls: []string{"forwarding"},
Requires: []string{"http"},
Expand All @@ -584,10 +631,11 @@ func (f5 *f5LTM) ensurePolicyExists(policyName string) error {
err = f5.post(policiesUrl, policyPayload, nil)
} else {
policyPayload := f5Policy{
Name: policyName,
Controls: []string{"forwarding"},
Requires: []string{"http"},
Strategy: "best-match",
Name: policyPath,
Partition: f5.partitionPath,
Controls: []string{"forwarding"},
Requires: []string{"http"},
Strategy: "best-match",
}
err = f5.post(policiesUrl, policyPayload, nil)
}
Expand All @@ -602,7 +650,7 @@ func (f5 *f5LTM) ensurePolicyExists(policyName string) error {
glog.V(4).Infof("Policy %s created. Adding no-op rule...", policyName)

rulesUrl := fmt.Sprintf("https://%s/mgmt/tm/ltm/policy/%s/rules",
f5.host, policyName)
f5.host, policyResourceId)

rulesPayload := f5Rule{
Name: "default_noop",
Expand All @@ -624,11 +672,13 @@ func (f5 *f5LTM) ensureVserverHasPolicy(vserverName, policyName string) error {
glog.V(4).Infof("Checking whether vserver %s has policy %s...",
vserverName, policyName)

vserverResourceId := f5.iControlUriVserverId(vserverName)

// We could use fmt.Sprintf("https://%s/mgmt/tm/ltm/virtual/%s/policies/%s",
// f5.host, vserverName, policyName) here, except that F5 iControl REST
// returns a 200 even if the policy does not exist.
// f5.host, vserverResourceId, policyName) here, except that F5
// iControl REST returns a 200 even if the policy does not exist.
vserverPoliciesUrl := fmt.Sprintf("https://%s/mgmt/tm/ltm/virtual/%s/policies",
f5.host, vserverName)
f5.host, vserverResourceId)

res := f5VserverPolicies{}

Expand All @@ -637,8 +687,10 @@ func (f5 *f5LTM) ensureVserverHasPolicy(vserverName, policyName string) error {
return err
}

policyPath := path.Join(f5.partitionPath, policyName)

for _, policy := range res.Policies {
if policy.Name == policyName {
if policy.FullPath == policyPath {
glog.V(4).Infof("Vserver %s has policy %s associated with it;"+
" nothing to do.", vserverName, policyName)
return nil
Expand All @@ -648,7 +700,8 @@ func (f5 *f5LTM) ensureVserverHasPolicy(vserverName, policyName string) error {
glog.V(4).Infof("Adding policy %s to vserver %s...", policyName, vserverName)

vserverPoliciesPayload := f5VserverPolicy{
Name: policyName,
Name: policyPath,
Partition: f5.partitionPath,
}

err = f5.post(vserverPoliciesUrl, vserverPoliciesPayload, nil)
Expand Down Expand Up @@ -709,7 +762,8 @@ func (f5 *f5LTM) ensureDatagroupExists(datagroupName string) error {
func (f5 *f5LTM) ensureIRuleExists(iRuleName, iRule string) error {
glog.V(4).Infof("Checking whether iRule %s exists...", iRuleName)

iRuleUrl := fmt.Sprintf("https://%s/mgmt/tm/ltm/rule/%s", f5.host, iRuleName)
iRuleUrl := fmt.Sprintf("https://%s/mgmt/tm/ltm/rule/%s", f5.host,
f5.iControlUriResourceId(iRuleName))

err := f5.get(iRuleUrl, nil)
if err != nil && err.(F5Error).httpStatusCode != 404 {
Expand All @@ -727,8 +781,9 @@ func (f5 *f5LTM) ensureIRuleExists(iRuleName, iRule string) error {
iRulesUrl := fmt.Sprintf("https://%s/mgmt/tm/ltm/rule", f5.host)

iRulePayload := f5IRule{
Name: iRuleName,
Code: iRule,
Name: iRuleName,

Choose a reason for hiding this comment

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

iRuleName or f5.iControlUriResourceId(iRuleName)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

will confirm with F5 team, but this seems to work so far because the Partition field is also part of the payload

Partition: f5.partitionPath,
Code: iRule,
}

err = f5.post(iRulesUrl, iRulePayload, nil)
Expand All @@ -748,7 +803,7 @@ func (f5 *f5LTM) ensureVserverHasIRule(vserverName, iRuleName string) error {
vserverName, iRuleName)

vserverUrl := fmt.Sprintf("https://%s/mgmt/tm/ltm/virtual/%s",
f5.host, vserverName)
f5.host, f5.iControlUriVserverId(vserverName))

res := f5VserverIRules{}

Expand All @@ -757,7 +812,7 @@ func (f5 *f5LTM) ensureVserverHasIRule(vserverName, iRuleName string) error {
return err
}

commonIRuleName := fmt.Sprintf("%s/%s", f5.partitionPath, iRuleName)
commonIRuleName := path.Join("/", f5.partitionPath, iRuleName)

for _, name := range res.Rules {
if name == commonIRuleName {
Expand All @@ -770,8 +825,9 @@ func (f5 *f5LTM) ensureVserverHasIRule(vserverName, iRuleName string) error {

glog.V(4).Infof("Adding iRule %s to vserver %s...", iRuleName, vserverName)

sslPassthroughIRulePath := path.Join(f5.partitionPath, sslPassthroughIRuleName)
vserverRulesPayload := f5VserverIRules{
Rules: []string{sslPassthroughIRuleName},
Rules: []string{sslPassthroughIRulePath},
}

err = f5.patch(vserverUrl, vserverRulesPayload, nil)
Expand All @@ -788,10 +844,8 @@ func (f5 *f5LTM) ensureVserverHasIRule(vserverName, iRuleName string) error {
func (f5 *f5LTM) checkPartitionPathExists(pathName string) (bool, error) {
glog.V(4).Infof("Checking if partition path %q exists...", pathName)

// F5 iControl REST API expects / characters in the path to be
// escaped as ~.
uri := fmt.Sprintf("https://%s/mgmt/tm/sys/folder/%s",
f5.host, strings.Replace(pathName, "/", "~", -1))
f5.host, encodeiControlUriPathComponent(pathName))

err := f5.get(uri, nil)
if err != nil {
Expand Down Expand Up @@ -1012,9 +1066,10 @@ func (f5 *f5LTM) CreatePool(poolname string) error {
// From @Miciah: In the future, we should allow the administrator
// to specify a different monitor to use.
payload := f5Pool{
Mode: "round-robin",
Monitor: "min 1 of /Common/http /Common/https",
Name: poolname,
Mode: "round-robin",
Monitor: "min 1 of /Common/http /Common/https",

Choose a reason for hiding this comment

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

fmt.Sprintf("min 1 of /%s/http /%s/https", f5.partionPath, f5.partionPath)?

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Cannot be sure of http/https monitors being present in the given partition. It is safe to re-use the common monitor just as we re-use TrafficGroup. Good point though, in ideal case we would want this to exist and probably we can write code to ensure the monitors' presence.
Will add a comment about this.

Copy link

@cpganderton cpganderton Apr 4, 2017

Choose a reason for hiding this comment

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

Agree with @rajatchopra, though long term it should be (as @Miciah hinted ) a configurable option, given that the default LTM traffic monitors don't work properly for the atomic registry console and the workaround right now is to manually change the traffic monitor used for the pool for the registry console (given that /Common/http and /Common/https are read-only objects in LTM)

Partition: f5.partitionPath,
Name: poolname,
}

err := f5.post(url, payload, nil)
Expand All @@ -1036,7 +1091,8 @@ func (f5 *f5LTM) CreatePool(poolname string) error {
// DeletePool deletes the specified pool from F5 BIG-IP, and deletes
// f5.poolMembers[poolname].
func (f5 *f5LTM) DeletePool(poolname string) error {
url := fmt.Sprintf("https://%s/mgmt/tm/ltm/pool/%s", f5.host, poolname)
url := fmt.Sprintf("https://%s/mgmt/tm/ltm/pool/%s", f5.host,
f5.iControlUriResourceId(poolname))

err := f5.delete(url, nil)
if err != nil {
Expand All @@ -1062,7 +1118,7 @@ func (f5 *f5LTM) GetPoolMembers(poolname string) (map[string]bool, error) {
}

url := fmt.Sprintf("https://%s/mgmt/tm/ltm/pool/%s/members",
f5.host, poolname)
f5.host, f5.iControlUriResourceId(poolname))

res := f5PoolMemberset{}

Expand Down Expand Up @@ -1125,7 +1181,7 @@ func (f5 *f5LTM) AddPoolMember(poolname, member string) error {
glog.V(4).Infof("Adding pool member %s to pool %s.", member, poolname)

url := fmt.Sprintf("https://%s/mgmt/tm/ltm/pool/%s/members",
f5.host, poolname)
f5.host, f5.iControlUriResourceId(poolname))

payload := f5PoolMember{
Name: member,
Expand Down Expand Up @@ -1164,7 +1220,7 @@ func (f5 *f5LTM) DeletePoolMember(poolname, member string) error {
}

url := fmt.Sprintf("https://%s/mgmt/tm/ltm/pool/%s/members/%s",
f5.host, poolname, member)
f5.host, f5.iControlUriResourceId(poolname), member)

err = f5.delete(url, nil)
if err != nil {
Expand All @@ -1187,7 +1243,7 @@ func (f5 *f5LTM) getRoutes(policyname string) (map[string]bool, error) {
}

url := fmt.Sprintf("https://%s/mgmt/tm/ltm/policy/%s/rules",
f5.host, policyname)
f5.host, f5.iControlUriResourceId(policyname))

Choose a reason for hiding this comment

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

https://github.com/openshift/origin/pull/13391/files#diff-997442d3bc5e2bf387c3840016b5c335R1261

If there are two partitions with same policy name, then f5.routes[policyname] will overwrite the former. Do we expect this case? If yes, then f5.routes[f5.iControlUriResourceId(policyname)] = routes

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Each instance of the controller pod will run with only one given partition. All internal data structures need not bother about multiple partitions.


res := f5PolicyRuleset{}

Expand Down Expand Up @@ -1257,8 +1313,9 @@ func (f5 *f5LTM) addRoute(policyname, routename, poolname, hostname,
pathname string) error {
success := false

policyResourceId := f5.iControlUriResourceId(policyname)
rulesUrl := fmt.Sprintf("https://%s/mgmt/tm/ltm/policy/%s/rules",
f5.host, policyname)
f5.host, policyResourceId)

rulesPayload := f5Rule{
Name: routename,
Expand Down Expand Up @@ -1288,7 +1345,7 @@ func (f5 *f5LTM) addRoute(policyname, routename, poolname, hostname,
}()

conditionUrl := fmt.Sprintf("https://%s/mgmt/tm/ltm/policy/%s/rules/%s/conditions",
f5.host, policyname, routename)
f5.host, policyResourceId, routename)

conditionPayload := f5RuleCondition{
Name: "0",
Expand Down Expand Up @@ -1330,7 +1387,7 @@ func (f5 *f5LTM) addRoute(policyname, routename, poolname, hostname,
}

actionUrl := fmt.Sprintf("https://%s/mgmt/tm/ltm/policy/%s/rules/%s/actions",
f5.host, policyname, routename)
f5.host, policyResourceId, routename)

actionPayload := f5RuleAction{
Name: "0",
Expand Down Expand Up @@ -1523,7 +1580,7 @@ func (f5 *f5LTM) DeletePassthroughRoute(routename string) error {
// policy.
func (f5 *f5LTM) deleteRoute(policyname, routename string) error {
ruleUrl := fmt.Sprintf("https://%s/mgmt/tm/ltm/policy/%s/rules/%s",
f5.host, policyname, routename)
f5.host, f5.iControlUriResourceId(policyname), routename)

err := f5.delete(ruleUrl, nil)
if err != nil {
Expand Down Expand Up @@ -1844,7 +1901,7 @@ func (f5 *f5LTM) associateClientSslProfileWithVserver(profilename,
profilename, vservername)

vserverProfileUrl := fmt.Sprintf("https://%s/mgmt/tm/ltm/virtual/%s/profiles",
f5.host, vservername)
f5.host, f5.iControlUriVserverId(vservername))

vserverProfilePayload := f5VserverProfilePayload{
Name: profilename,
Expand All @@ -1862,7 +1919,7 @@ func (f5 *f5LTM) associateServerSslProfileWithVserver(profilename,
profilename, vservername)

vserverProfileUrl := fmt.Sprintf("https://%s/mgmt/tm/ltm/virtual/%s/profiles",
f5.host, vservername)
f5.host, f5.iControlUriVserverId(vservername))

vserverProfilePayload := f5VserverProfilePayload{
Name: profilename,
Expand Down Expand Up @@ -1890,7 +1947,7 @@ func (f5 *f5LTM) deleteCertParts(routename string,
routename, f5.httpsVserver)
serverSslProfileName := fmt.Sprintf("%s-server-ssl-profile", routename)
serverSslVserverProfileUrl := fmt.Sprintf("https://%s/mgmt/tm/ltm/virtual/%s/profiles/%s",
f5.host, f5.httpsVserver, serverSslProfileName)
f5.host, f5.iControlUriVserverId(f5.httpsVserver), serverSslProfileName)
err := f5.delete(serverSslVserverProfileUrl, nil)
if err != nil {
// Iff the profile is not associated with the vserver, we can continue on to
Expand Down Expand Up @@ -1925,7 +1982,7 @@ func (f5 *f5LTM) deleteCertParts(routename string,
" from vserver %s...", routename, f5.httpsVserver)
clientSslProfileName := fmt.Sprintf("%s-client-ssl-profile", routename)
clientSslVserverProfileUrl := fmt.Sprintf("https://%s/mgmt/tm/ltm/virtual/%s/profiles/%s",
f5.host, f5.httpsVserver, clientSslProfileName)
f5.host, f5.iControlUriVserverId(f5.httpsVserver), clientSslProfileName)
err := f5.delete(clientSslVserverProfileUrl, nil)
if err != nil {
// Iff the profile is not associated with the vserver, we can continue on
Expand Down
5 changes: 5 additions & 0 deletions pkg/router/f5/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,11 @@ func (p *F5Plugin) HandleEndpoints(eventType watch.EventType,

glog.V(4).Infof("Deleting pool %s", poolname)

// Note: deletePool will throw errors if the route
// has not been deleted as the policy would
// still refer to the pool. That is ok as the
// pool will still get deleted when the route
// gets deleted.
err = p.deletePool(poolname)
if err != nil {
return err
Expand Down
Loading