From de4ca18685944fd52b9c4f2ef0aa7a85b96c5742 Mon Sep 17 00:00:00 2001 From: ramr Date: Mon, 7 Mar 2016 15:37:19 -0800 Subject: [PATCH 1/3] F5 router partition path changes: o Use fully qualified names so that there is no defaulting to /Common and need to have all the referenced objects in the same partition, otherwise F5 has reference errors across partitions. o Fix policy partition path + rework and ensure we check the vserver which is inside the partition we are configured in. o Comment re: delete errors. o Bug fixes. o F5 unit test changes for supporting partition paths. --- pkg/router/f5/f5.go | 127 ++++++--- pkg/router/f5/plugin.go | 5 + pkg/router/f5/plugin_test.go | 503 ++++++++++++++++++++--------------- pkg/router/f5/types.go | 13 +- 4 files changed, 402 insertions(+), 246 deletions(-) diff --git a/pkg/router/f5/f5.go b/pkg/router/f5/f5.go index 38700b8c04a2..a7669a53b809 100644 --- a/pkg/router/f5/f5.go +++ b/pkg/router/f5/f5.go @@ -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://:/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://:/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. // @@ -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 { @@ -584,10 +629,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: policyName, + Partition: f5.partitionPath, + Controls: []string{"forwarding"}, + Requires: []string{"http"}, + Strategy: "best-match", } err = f5.post(policiesUrl, policyPayload, nil) } @@ -602,7 +648,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", @@ -624,11 +670,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{} @@ -637,8 +685,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 @@ -648,7 +698,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: policyName, + Partition: f5.partitionPath, } err = f5.post(vserverPoliciesUrl, vserverPoliciesPayload, nil) @@ -709,7 +760,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 { @@ -727,8 +779,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, + Partition: f5.partitionPath, + Code: iRule, } err = f5.post(iRulesUrl, iRulePayload, nil) @@ -748,7 +801,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{} @@ -757,7 +810,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 { @@ -770,8 +823,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) @@ -788,10 +842,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 { @@ -1012,9 +1064,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", + Partition: f5.partitionPath, + Name: poolname, } err := f5.post(url, payload, nil) @@ -1036,7 +1089,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 { @@ -1062,7 +1116,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{} @@ -1125,7 +1179,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, @@ -1164,7 +1218,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 { @@ -1187,7 +1241,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)) res := f5PolicyRuleset{} @@ -1257,8 +1311,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, @@ -1288,7 +1343,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", @@ -1330,7 +1385,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", @@ -1523,7 +1578,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 { @@ -1844,7 +1899,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, @@ -1862,7 +1917,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, @@ -1890,7 +1945,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 @@ -1925,7 +1980,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 diff --git a/pkg/router/f5/plugin.go b/pkg/router/f5/plugin.go index 037d1b73ba22..ac850bf2dac9 100644 --- a/pkg/router/f5/plugin.go +++ b/pkg/router/f5/plugin.go @@ -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 diff --git a/pkg/router/f5/plugin_test.go b/pkg/router/f5/plugin_test.go index d709a9009b84..bc7f65e1d1b5 100644 --- a/pkg/router/f5/plugin_test.go +++ b/pkg/router/f5/plugin_test.go @@ -8,6 +8,7 @@ import ( "net/http/httptest" "net/url" "os/exec" + "path" "reflect" "strings" "testing" @@ -108,6 +109,21 @@ type ( // A pool comprises a set of strings of the form addr:port. pool map[string]bool + + // An internal mock of an F5 iControl REST API resource. + mockF5iControlResource struct { + // Type is the type of the f5 iControl resource. + Type string + + // Name is the name of the f5 iControl resource. + Name string + + // FullPath is the full path to the f5 iControl resource. + FullPath string + + // Partition is the path of the "owning" partition for the f5 iControl resource. + Partition string + } ) const ( @@ -227,18 +243,20 @@ func newTestRouterWithState(state mockF5State, partitionPath string) (*F5Plugin, // will be nil if an error is returned. func newTestRouter(partitionPath string) (*F5Plugin, *mockF5, error) { pathKey := strings.Replace(partitionPath, "/", "~", -1) + httpVserverPath := path.Join(partitionPath, httpVserverName) + httpsVserverPath := path.Join(partitionPath, httpsVserverName) state := mockF5State{ policies: map[string]map[string]policyRule{}, vserverPolicies: map[string]map[string]bool{ - httpVserverName: {}, - httpsVserverName: {}, + httpVserverPath: {}, + httpsVserverPath: {}, }, certs: map[string]bool{}, keys: map[string]bool{}, serverSslProfiles: map[string]bool{}, clientSslProfiles: map[string]bool{}, vserverProfiles: map[string]map[string]bool{ - httpsVserverName: {}, + httpsVserverPath: {}, }, datagroups: map[string]datagroup{}, iRules: map[string]iRule{}, @@ -256,14 +274,47 @@ func (f5 *mockF5) close() { f5.server.Close() } -func validatePolicyName(response http.ResponseWriter, request *http.Request, - f5state mockF5State, policyName string) bool { - _, ok := f5state.policies[policyName] +func normalizeiControlUriPath(pathName string) string { + return strings.Replace(pathName, "~", "/", -1) +} + +func normalizeResourcePath(resourcePath string) string { + unescapedPath := normalizeiControlUriPath(resourcePath) + if strings.HasPrefix(unescapedPath, "/") { + return unescapedPath + } + + return path.Join(F5DefaultPartitionPath, unescapedPath) +} + +func (r *mockF5iControlResource) id() string { + return r.FullPath +} + +func (r *mockF5iControlResource) uriPath() string { + return encodeiControlUriPathComponent(r.FullPath) +} + +func newMockF5iControlResource(resourceType, resourceName string) *mockF5iControlResource { + resourcePath := normalizeResourcePath(resourceName) + resourcePartitionPath, _ := path.Split(resourcePath) + + return &mockF5iControlResource{ + Type: resourceType, + Name: resourceName, + FullPath: resourcePath, + Partition: resourcePartitionPath, + } +} + +func validatePolicy(response http.ResponseWriter, request *http.Request, + f5state mockF5State, policy *mockF5iControlResource) bool { + _, ok := f5state.policies[policy.id()] if !ok { response.WriteHeader(http.StatusNotFound) fmt.Fprintf(response, - `{"code":404,"errorStack":[],"message":"01020036:3: The requested Policy (/Common/%s) was not found."}`, - policyName) + `{"code":404,"errorStack":[],"message":"01020036:3: The requested Policy (%s) was not found."}`, + policy.FullPath) return false } @@ -273,15 +324,16 @@ func validatePolicyName(response http.ResponseWriter, request *http.Request, func getPolicyHandler(f5state mockF5State) http.HandlerFunc { return func(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) - policyName := vars["policyName"] + policy := newMockF5iControlResource("policy", vars["policyName"]) - if !validatePolicyName(response, request, f5state, policyName) { + if !validatePolicy(response, request, f5state, policy) { return } + policyUriPath := policy.uriPath() fmt.Fprintf(response, - `{"controls":["forwarding"],"fullPath":"%s","generation":1,"kind":"tm:ltm:policy:policystate","name":"%s","requires":["http"],"rulesReference":{"isSubcollection":true,"link":"https://localhost/mgmt/tm/ltm/policy/~Common~%s/rules?ver=11.6.0"},"selfLink":"https://localhost/mgmt/tm/ltm/policy/%s?ver=11.6.0","strategy":"/Common/best-match"}`, - policyName, policyName, policyName, policyName) + `{"controls":["forwarding"],"fullPath":"%s","generation":1,"kind":"tm:ltm:policy:policystate","name":"%s","requires":["http"],"rulesReference":{"isSubcollection":true,"link":"https://localhost/mgmt/tm/ltm/policy/%s/rules?ver=11.6.0"},"selfLink":"https://localhost/mgmt/tm/ltm/policy/%s?ver=11.6.0","strategy":"/Common/best-match"}`, + policy.FullPath, policy.Name, policyUriPath, policyUriPath) } } @@ -292,14 +344,16 @@ func OK(response http.ResponseWriter) { func postPolicyHandler(f5state mockF5State) http.HandlerFunc { return func(response http.ResponseWriter, request *http.Request) { payload := struct { - Name string `json:"name"` + Name string `json:"name"` + Partition string `json:"partition"` }{} decoder := json.NewDecoder(request.Body) decoder.Decode(&payload) - policyName := payload.Name + policyPath := path.Join(payload.Partition, payload.Name) + policy := newMockF5iControlResource("policy", policyPath) - f5state.policies[policyName] = map[string]policyRule{} + f5state.policies[policy.id()] = map[string]policyRule{} OK(response) } @@ -308,9 +362,9 @@ func postPolicyHandler(f5state mockF5State) http.HandlerFunc { func postRuleHandler(f5state mockF5State) http.HandlerFunc { return func(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) - policyName := vars["policyName"] + policy := newMockF5iControlResource("policy", vars["policyName"]) - if !validatePolicyName(response, request, f5state, policyName) { + if !validatePolicy(response, request, f5state, policy) { return } @@ -323,19 +377,19 @@ func postRuleHandler(f5state mockF5State) http.HandlerFunc { ruleName := payload.Name newRule := policyRule{[]policyCondition{}} - f5state.policies[policyName][ruleName] = newRule + f5state.policies[policy.id()][ruleName] = newRule OK(response) } } -func validateVserverName(response http.ResponseWriter, request *http.Request, - f5state mockF5State, vserverName string) bool { - if !recogniseVserver(vserverName) { +func validateVserver(response http.ResponseWriter, request *http.Request, + f5state mockF5State, vserver *mockF5iControlResource) bool { + if !recogniseVserver(vserver) { response.WriteHeader(http.StatusNotFound) fmt.Fprintf(response, - `{"code":404,"errorStack":[],"message":"01020036:3: The requested Virtual Server (/Common/%s) was not found."}`, - vserverName) + `{"code":404,"errorStack":[],"message":"01020036:3: The requested Virtual Server (%s) was not found."}`, + vserver.FullPath) return false } @@ -345,42 +399,48 @@ func validateVserverName(response http.ResponseWriter, request *http.Request, func getPoliciesHandler(f5state mockF5State) http.HandlerFunc { return func(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) - vserverName := vars["vserverName"] + vserver := newMockF5iControlResource("vserver", vars["vserverName"]) - if !validateVserverName(response, request, f5state, vserverName) { + if !validateVserver(response, request, f5state, vserver) { return } fmt.Fprint(response, `{"items":[{"controls":["classification"],"fullPath":"/Common/_sys_CEC_SSL_client_policy","generation":1,"hints":["no-write","no-delete","no-exclusion"],"kind":"tm:ltm:policy:policystate","name":"_sys_CEC_SSL_client_policy","partition":"Common","requires":["ssl-persistence"],"rulesReference":{"isSubcollection":true,"link":"https://localhost/mgmt/tm/ltm/policy/~Common~_sys_CEC_SSL_client_policy/rules?ver=11.6.0"},"selfLink":"https://localhost/mgmt/tm/ltm/policy/~Common~_sys_CEC_SSL_client_policy?ver=11.6.0","strategy":"/Common/first-match"},{"controls":["classification"],"fullPath":"/Common/_sys_CEC_SSL_server_policy","generation":1,"hints":["no-write","no-delete","no-exclusion"],"kind":"tm:ltm:policy:policystate","name":"_sys_CEC_SSL_server_policy","partition":"Common","requires":["ssl-persistence"],"rulesReference":{"isSubcollection":true,"link":"https://localhost/mgmt/tm/ltm/policy/~Common~_sys_CEC_SSL_server_policy/rules?ver=11.6.0"},"selfLink":"https://localhost/mgmt/tm/ltm/policy/~Common~_sys_CEC_SSL_server_policy?ver=11.6.0","strategy":"/Common/first-match"},{"controls":["classification"],"fullPath":"/Common/_sys_CEC_video_policy","generation":1,"hints":["no-write","no-delete","no-exclusion"],"kind":"tm:ltm:policy:policystate","name":"_sys_CEC_video_policy","partition":"Common","requires":["http"],"rulesReference":{"isSubcollection":true,"link":"https://localhost/mgmt/tm/ltm/policy/~Common~_sys_CEC_video_policy/rules?ver=11.6.0"},"selfLink":"https://localhost/mgmt/tm/ltm/policy/~Common~_sys_CEC_video_policy?ver=11.6.0","strategy":"/Common/first-match"}`) - for policyName := range f5state.vserverPolicies[vserverName] { + for policyName := range f5state.vserverPolicies[vserver.id()] { + policy := newMockF5iControlResource("policy", policyName) + policyUriPath := policy.uriPath() fmt.Fprintf(response, - `,{"controls":["forwarding"],"fullPath":"/Common/%s","generation":1,"kind":"tm:ltm:policy:policystate","name":"%s","partition":"Common","requires":["http"],"rulesReference":{"isSubcollection":true,"link":"https://localhost/mgmt/tm/ltm/policy/~Common~%s/rules?ver=11.6.0"},"selfLink":"https://localhost/mgmt/tm/ltm/policy/~Common~%s?ver=11.6.0","strategy":"/Common/best-match"}`, - policyName, policyName, policyName, policyName) + `,{"controls":["forwarding"],"fullPath":"%s","generation":1,"kind":"tm:ltm:policy:policystate","name":"%s","partition":"%s","requires":["http"],"rulesReference":{"isSubcollection":true,"link":"https://localhost/mgmt/tm/ltm/policy/%s/rules?ver=11.6.0"},"selfLink":"https://localhost/mgmt/tm/ltm/policy/%s?ver=11.6.0","strategy":"/Common/best-match"}`, + policy.FullPath, policy.Name, policy.Partition, + policyUriPath, policyUriPath) } fmt.Fprintf(response, `],"kind":"tm:ltm:policy:policycollectionstate","selfLink":"https://localhost/mgmt/tm/ltm/policy?ver=11.6.0"}`) } } -func recogniseVserver(vserverName string) bool { - return vserverName == httpVserverName || vserverName == httpsVserverName +func recogniseVserver(vserver *mockF5iControlResource) bool { + isHttpVserver := strings.HasSuffix(vserver.FullPath, httpVserverName) + isHttpsVserver := strings.HasSuffix(vserver.FullPath, httpsVserverName) + return isHttpVserver || isHttpsVserver } func associatePolicyWithVserverHandler(f5state mockF5State) http.HandlerFunc { return func(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) - vserverName := vars["vserverName"] + vserver := newMockF5iControlResource("vserver", vars["vserverName"]) payload := struct { - Name string `json:"name"` + Name string `json:"name"` + Partition string `json:"partition"` }{} decoder := json.NewDecoder(request.Body) decoder.Decode(&payload) - policyName := payload.Name + policy := newMockF5iControlResource("policy", path.Join(payload.Partition, payload.Name)) - validVserver := recogniseVserver(vserverName) - _, validPolicy := f5state.policies[policyName] + validVserver := recogniseVserver(vserver) + _, validPolicy := f5state.policies[policy.id()] if !validVserver || !validPolicy { response.WriteHeader(http.StatusNotFound) @@ -388,39 +448,46 @@ func associatePolicyWithVserverHandler(f5state mockF5State) http.HandlerFunc { if !validVserver && !validPolicy { fmt.Fprintf(response, - `{"code":400,"errorStack":[],"message":"01070712:3: Values (%s) specified for virtual server policy (/Common/%s %s): foreign key index (policy_FK) do not point at an item that exists in the database."}`, - policyName, vserverName, policyName) + `{"code":400,"errorStack":[],"message":"01070712:3: Values (%s) specified for virtual server policy (%s %s): foreign key index (policy_FK) do not point at an item that exists in the database."}`, + policy.FullPath, vserver.FullPath, policy.FullPath) return } if !validVserver { fmt.Fprintf(response, - `{"code":400,"errorStack":[],"message":"01070712:3: Values (/Common/%s) specified for virtual server policy (/Common/%s /Common/%s): foreign key index (vs_FK) do not point at an item that exists in the database."}`, - vserverName, vserverName, policyName) + `{"code":400,"errorStack":[],"message":"01070712:3: Values (%s) specified for virtual server policy (%s %s): foreign key index (vs_FK) do not point at an item that exists in the database."}`, + vserver.FullPath, vserver.FullPath, policy.FullPath) return } if !validPolicy { fmt.Fprintf(response, `{"code":404,"errorStack":[],"message":"01020036:3: The requested policy (%s) was not found."}`, - policyName) + policy.FullPath) + return + } + + if _, found := f5state.vserverPolicies[vserver.id()]; !found { + fmt.Fprintf(response, + `{"code":400,"errorStack":[],"message":"01070712:3: Values (%s) specified for virtual server policy (%s %s): foreign key index (vs_FK) do not point at an item that exists in the database."}`, + vserver.FullPath, vserver.FullPath, policy.FullPath) return } - f5state.vserverPolicies[vserverName][policyName] = true + f5state.vserverPolicies[vserver.id()][policy.id()] = true OK(response) } } -func validateDatagroupName(response http.ResponseWriter, request *http.Request, - f5state mockF5State, datagroupName string) bool { - _, ok := f5state.datagroups[datagroupName] +func validateDatagroup(response http.ResponseWriter, request *http.Request, + f5state mockF5State, datagroupResource *mockF5iControlResource) bool { + _, ok := f5state.datagroups[datagroupResource.id()] if !ok { response.WriteHeader(http.StatusNotFound) fmt.Fprintf(response, - `{"code":404,"errorStack":[],"message":"01020036:3: The requested value list (/Common/%s) was not found."}`, - datagroupName) + `{"code":404,"errorStack":[],"message":"01020036:3: The requested value list (%s) was not found."}`, + datagroupResource.FullPath) return false } @@ -430,17 +497,17 @@ func validateDatagroupName(response http.ResponseWriter, request *http.Request, func getDatagroupHandler(f5state mockF5State) http.HandlerFunc { return func(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) - datagroupName := vars["datagroupName"] + datagroupResource := newMockF5iControlResource("datagroup", vars["datagroupName"]) - if !validateDatagroupName(response, request, f5state, datagroupName) { + if !validateDatagroup(response, request, f5state, datagroupResource) { return } - datagroup := f5state.datagroups[datagroupName] + datagroup := f5state.datagroups[datagroupResource.id()] fmt.Fprintf(response, `{"fullPath":"%s","generation":1556,"kind":"tm:ltm:data-group:internal:internalstate","name":"%s","records":[`, - datagroupName, datagroupName) + datagroupResource.FullPath, datagroupResource.Name) first := true for key, value := range datagroup { @@ -454,16 +521,16 @@ func getDatagroupHandler(f5state mockF5State) http.HandlerFunc { } fmt.Fprintf(response, `],"selfLink":"https://localhost/mgmt/tm/ltm/data-group/internal/%s?ver=11.6.0","type":"string"}`, - datagroupName) + datagroupResource.uriPath()) } } func patchDatagroupHandler(f5state mockF5State) http.HandlerFunc { return func(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) - datagroupName := vars["datagroupName"] + datagroupResource := newMockF5iControlResource("datagroup", vars["datagroupName"]) - if !validateDatagroupName(response, request, f5state, datagroupName) { + if !validateDatagroup(response, request, f5state, datagroupResource) { return } @@ -482,7 +549,7 @@ func patchDatagroupHandler(f5state mockF5State) http.HandlerFunc { dg[record.Key] = record.Value } - f5state.datagroups[datagroupName] = dg + f5state.datagroups[datagroupResource.id()] = dg OK(response) } @@ -496,18 +563,18 @@ func postDatagroupHandler(f5state mockF5State) http.HandlerFunc { decoder := json.NewDecoder(request.Body) decoder.Decode(&payload) - datagroupName := payload.Name + datagroupResource := newMockF5iControlResource("datagroup", payload.Name) - _, datagroupAlreadyExists := f5state.datagroups[datagroupName] + _, datagroupAlreadyExists := f5state.datagroups[datagroupResource.id()] if datagroupAlreadyExists { response.WriteHeader(http.StatusConflict) fmt.Fprintf(response, - `{"code":409,"errorStack":[],"message":"01020066:3: The requested value list (/Common/%s) already exists in partition Common."}`, - datagroupName) + `{"code":409,"errorStack":[],"message":"01020066:3: The requested value list (%s) already exists in partition Common."}`, + datagroupResource.FullPath) return } - f5state.datagroups[datagroupName] = map[string]string{} + f5state.datagroups[datagroupResource.id()] = map[string]string{} OK(response) } @@ -516,20 +583,20 @@ func postDatagroupHandler(f5state mockF5State) http.HandlerFunc { func getIRuleHandler(f5state mockF5State) http.HandlerFunc { return func(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) - iRuleName := vars["iRuleName"] + rule := newMockF5iControlResource("irule", vars["iRuleName"]) - iRuleCode, ok := f5state.iRules[iRuleName] + iRuleCode, ok := f5state.iRules[rule.id()] if !ok { response.WriteHeader(http.StatusNotFound) fmt.Fprintf(response, - `{"code":404,"errorStack":[],"message":"01020036:3: The requested iRule (/Common/%s) was not found."}`, - iRuleName) + `{"code":404,"errorStack":[],"message":"01020036:3: The requested iRule (%s) was not found."}`, + rule.FullPath) return } fmt.Fprintf(response, `{"apiAnonymous":"%s","fullPath":"%s","generation":386,"kind":"tm:ltm:rule:rulestate","name":"%s","selfLink":"https://localhost/mgmt/tm/ltm/rule/%s?ver=11.6.0"}`, - iRuleCode, iRuleName, iRuleName, iRuleName) + iRuleCode, rule.FullPath, rule.Name, rule.uriPath()) } } @@ -542,15 +609,15 @@ func postIRuleHandler(f5state mockF5State) http.HandlerFunc { decoder := json.NewDecoder(request.Body) decoder.Decode(&payload) - iRuleName := payload.Name + rule := newMockF5iControlResource("irule", payload.Name) iRuleCode := payload.Code - _, iRuleAlreadyExists := f5state.iRules[iRuleName] + _, iRuleAlreadyExists := f5state.iRules[rule.id()] if iRuleAlreadyExists { response.WriteHeader(http.StatusConflict) fmt.Fprintf(response, - `{"code":409,"errorStack":[],"message":"01020066:3: The requested iRule (/Common/%s) already exists in partition Common."}`, - iRuleName) + `{"code":409,"errorStack":[],"message":"01020066:3: The requested iRule (%s) already exists in partition %s."}`, + rule.FullPath, rule.Partition) return } @@ -567,7 +634,7 @@ func postIRuleHandler(f5state mockF5State) http.HandlerFunc { return } - f5state.iRules[iRuleName] = iRule(iRuleCode) + f5state.iRules[rule.id()] = iRule(iRuleCode) OK(response) } @@ -576,45 +643,48 @@ func postIRuleHandler(f5state mockF5State) http.HandlerFunc { func getVserverHandler(f5state mockF5State) http.HandlerFunc { return func(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) - vserverName := vars["vserverName"] + vserver := newMockF5iControlResource("vserver", vars["vserverName"]) - if !validateVserverName(response, request, f5state, vserverName) { + if !validateVserver(response, request, f5state, vserver) { return } description := "OpenShift Enterprise Virtual Server for HTTPS connections" destination := "10.1.1.1:443" - if vserverName == httpVserverName { + + if strings.HasSuffix(vserver.FullPath, httpVserverName) { description = "OpenShift Enterprise Virtual Server for HTTP connections" destination = "10.1.1.2:80" } + vserverUriPath := vserver.uriPath() fmt.Fprintf(response, - `{"addressStatus":"yes","autoLasthop":"default","cmpEnabled":"yes","connectionLimit":0,"description":"%s","destination":"/Common/%s","enabled":true,"fullPath":"%s","generation":387,"gtmScore":0,"ipProtocol":"tcp","kind":"tm:ltm:virtual:virtualstate","mask":"255.255.255.255","mirror":"disabled","mobileAppTunnel":"disabled","name":"%s","nat64":"disabled","policiesReference":{"isSubcollection":true,"link":"https://localhost/mgmt/tm/ltm/virtual/~Common~%s/policies?ver=11.6.0"},"profilesReference":{"isSubcollection":true,"link":"https://localhost/mgmt/tm/ltm/virtual/~Common~%s/profiles?ver=11.6.0"},"rateLimit":"disabled","rateLimitDstMask":0,"rateLimitMode":"object","rateLimitSrcMask":0,"rules":[`, - description, destination, vserverName, - vserverName, vserverName, vserverName) + `{"addressStatus":"yes","autoLasthop":"default","cmpEnabled":"yes","connectionLimit":0,"description":"%s","destination":"%s/%s","enabled":true,"fullPath":"%s","generation":387,"gtmScore":0,"ipProtocol":"tcp","kind":"tm:ltm:virtual:virtualstate","mask":"255.255.255.255","mirror":"disabled","mobileAppTunnel":"disabled","name":"%s","nat64":"disabled","policiesReference":{"isSubcollection":true,"link":"https://localhost/mgmt/tm/ltm/virtual/%s/policies?ver=11.6.0"},"profilesReference":{"isSubcollection":true,"link":"https://localhost/mgmt/tm/ltm/virtual/%s/profiles?ver=11.6.0"},"rateLimit":"disabled","rateLimitDstMask":0,"rateLimitMode":"object","rateLimitSrcMask":0,"rules":[`, + description, vserver.Partition, destination, vserver.FullPath, + vserver.Name, vserverUriPath, vserverUriPath) first := true - for _, ruleName := range f5state.vserverIRules[vserverName] { + for _, ruleName := range f5state.vserverIRules[vserver.id()] { if first { first = false } else { fmt.Fprintf(response, ",") } - fmt.Fprintf(response, `"/Common/%s"`, ruleName) + fmt.Fprintf(response, `"%s"`, ruleName) } - fmt.Fprintf(response, `],"selfLink":"https://localhost/mgmt/tm/ltm/virtual/%s?ver=11.6.0","source":"0.0.0.0/0","sourceAddressTranslation":{"type":"none"},"sourcePort":"preserve","synCookieStatus":"not-activated","translateAddress":"enabled","translatePort":"enabled","vlansDisabled":true,"vsIndex":11}`, vserverName) + fmt.Fprintf(response, `],"selfLink":"https://localhost/mgmt/tm/ltm/virtual/%s?ver=11.6.0","source":"0.0.0.0/0","sourceAddressTranslation":{"type":"none"},"sourcePort":"preserve","synCookieStatus":"not-activated","translateAddress":"enabled","translatePort":"enabled","vlansDisabled":true,"vsIndex":11}`, + vserverUriPath) } } func patchVserverHandler(f5state mockF5State) http.HandlerFunc { return func(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) - vserverName := vars["vserverName"] + vserver := newMockF5iControlResource("policy", vars["vserverName"]) - if !validateVserverName(response, request, f5state, vserverName) { + if !validateVserver(response, request, f5state, vserver) { return } @@ -626,20 +696,20 @@ func patchVserverHandler(f5state mockF5State) http.HandlerFunc { iRules := []string(payload.Rules) - f5state.vserverIRules[vserverName] = iRules + f5state.vserverIRules[vserver.id()] = iRules OK(response) } } -func validatePartitionPath(response http.ResponseWriter, request *http.Request, - f5state mockF5State, partitionPath string) bool { - _, ok := f5state.partitionPaths[partitionPath] +func validatePartition(response http.ResponseWriter, request *http.Request, + f5state mockF5State, partition *mockF5iControlResource) bool { + _, ok := f5state.partitionPaths[partition.id()] if !ok { response.WriteHeader(http.StatusNotFound) fmt.Fprintf(response, `{"code":404,"errorStack":[],"message":"01020036:3: The requested folder (%s) was not found."}`, - partitionPath) + partition.FullPath) return false } @@ -649,21 +719,16 @@ func validatePartitionPath(response http.ResponseWriter, request *http.Request, func getPartitionPath(f5state mockF5State) http.HandlerFunc { return func(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) - partitionPath := vars["partitionPath"] + partition := newMockF5iControlResource("partition", vars["partitionPath"]) - if !validatePartitionPath(response, request, f5state, partitionPath) { + if !validatePartition(response, request, f5state, partition) { return } - fullPath := f5state.partitionPaths[partitionPath] - parts := strings.Split(fullPath, "/") - partitionName := parts[0] - if len(parts) > 1 { - partitionName = parts[1] - } + fullPath := f5state.partitionPaths[partition.id()] fmt.Fprintf(response, `{"deviceGroup":"%s/ose-sync-failover","fullPath":"%s","generation":580,"hidden":"false","inheritedDevicegroup":"true","inheritedTrafficGroup":"true","kind":"tm:sys:folder:folderstate","name":"%s","noRefCheck":"false","selfLink":"https://localhost/mgmt/tm/sys/folder/%s?ver=11.6.0","subPath":"/"}`, - fullPath, fullPath, partitionName, partitionPath) + fullPath, fullPath, partition.Name, encodeiControlUriPathComponent(fullPath)) } } @@ -675,12 +740,9 @@ func postPartitionPathHandler(f5state mockF5State) http.HandlerFunc { decoder := json.NewDecoder(request.Body) decoder.Decode(&payload) - partitionPath := payload.Name + partition := newMockF5iControlResource("partition", payload.Name) - // Convert / form to ~ form and add it to the map. This - // makes the GETs simpler: check/get the key in the map. - pathKey := strings.Replace(partitionPath, "/", "~", -1) - f5state.partitionPaths[pathKey] = partitionPath + f5state.partitionPaths[partition.id()] = partition.FullPath OK(response) } @@ -694,31 +756,31 @@ func postPoolHandler(f5state mockF5State) http.HandlerFunc { decoder := json.NewDecoder(request.Body) decoder.Decode(&payload) - poolName := payload.Name + poolResource := newMockF5iControlResource("pool", payload.Name) - _, poolAlreadyExists := f5state.pools[poolName] + _, poolAlreadyExists := f5state.pools[poolResource.id()] if poolAlreadyExists { response.WriteHeader(http.StatusConflict) fmt.Fprintf(response, - `{"code":409,"errorStack":[],"message":"01020066:3: The requested Pool (/Common/%s) already exists in partition Common."}`, - poolName) + `{"code":409,"errorStack":[],"message":"01020066:3: The requested Pool (%s) already exists in partition %s."}`, + poolResource.FullPath, poolResource.Partition) return } - f5state.pools[poolName] = pool{} + f5state.pools[poolResource.id()] = pool{} OK(response) } } -func validatePoolName(response http.ResponseWriter, request *http.Request, - f5state mockF5State, poolName string) bool { - _, ok := f5state.pools[poolName] +func validatePool(response http.ResponseWriter, request *http.Request, + f5state mockF5State, poolResource *mockF5iControlResource) bool { + _, ok := f5state.pools[poolResource.id()] if !ok { response.WriteHeader(http.StatusNotFound) fmt.Fprintf(response, - `{"code":404,"errorStack":[],"message":"01020036:3:The requested Pool (/Common/%s) was not found."}`, - poolName) + `{"code":404,"errorStack":[],"message":"01020036:3:The requested Pool (%s) was not found."}`, + poolResource.FullPath) return false } @@ -728,15 +790,15 @@ func validatePoolName(response http.ResponseWriter, request *http.Request, func deletePoolHandler(f5state mockF5State) http.HandlerFunc { return func(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) - poolName := vars["poolName"] + poolResource := newMockF5iControlResource("pool", vars["poolName"]) - if !validatePoolName(response, request, f5state, poolName) { + if !validatePool(response, request, f5state, poolResource) { return } // TODO: Validate that no rule references the pool. - delete(f5state.pools, poolName) + delete(f5state.pools, poolResource.id()) OK(response) } @@ -745,16 +807,16 @@ func deletePoolHandler(f5state mockF5State) http.HandlerFunc { func getPoolMembersHandler(f5state mockF5State) http.HandlerFunc { return func(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) - poolName := vars["poolName"] + poolResource := newMockF5iControlResource("pool", vars["poolName"]) - if !validatePoolName(response, request, f5state, poolName) { + if !validatePool(response, request, f5state, poolResource) { return } fmt.Fprint(response, `{"items":[`) first := true - for member := range f5state.pools[poolName] { + for member := range f5state.pools[poolResource.id()] { if first { first = false } else { @@ -763,22 +825,22 @@ func getPoolMembersHandler(f5state mockF5State) http.HandlerFunc { addr := strings.Split(member, ":")[0] fmt.Fprintf(response, - `{"address":"%s","connectionLimit":0,"dynamicRatio":1,"ephemeral":"false","fqdn":{"autopopulate":"disabled"},"fullPath":"/Common/%s","generation":1190,"inheritProfile":"enabled","kind":"tm:ltm:pool:members:membersstate","logging":"disabled","monitor":"default","name":"%s","partition":"Common","priorityGroup":0,"rateLimit":"disabled","ratio":1,"selfLink":"https://localhost/mgmt/tm/ltm/pool/%s/members/~Common~%s?ver=11.6.0","session":"monitor-enabled","state":"up"}`, + `{"address":"%s","connectionLimit":0,"dynamicRatio":1,"ephemeral":"false","fqdn":{"autopopulate":"disabled"},"fullPath":"%s","generation":1190,"inheritProfile":"enabled","kind":"tm:ltm:pool:members:membersstate","logging":"disabled","monitor":"default","name":"%s","partition":"Common","priorityGroup":0,"rateLimit":"disabled","ratio":1,"selfLink":"https://localhost/mgmt/tm/ltm/pool/%s/members/%s?ver=11.6.0","session":"monitor-enabled","state":"up"}`, addr, member, member, member, member) } fmt.Fprintf(response, `],"kind":"tm:ltm:pool:members:memberscollectionstate","selfLink":"https://localhost/mgmt/tm/ltm/pool/%s/members?ver=11.6.0"}`, - poolName) + poolResource.uriPath()) } } func postPoolMemberHandler(f5state mockF5State) http.HandlerFunc { return func(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) - poolName := vars["poolName"] + poolResource := newMockF5iControlResource("pool", vars["poolName"]) - if !validatePoolName(response, request, f5state, poolName) { + if !validatePool(response, request, f5state, poolResource) { return } @@ -790,16 +852,16 @@ func postPoolMemberHandler(f5state mockF5State) http.HandlerFunc { memberName := payload.Member - _, memberAlreadyExists := f5state.pools[poolName][memberName] + _, memberAlreadyExists := f5state.pools[poolResource.id()][memberName] if memberAlreadyExists { response.WriteHeader(http.StatusConflict) fmt.Fprintf(response, - `{"code":409,"message":"01020066:3: The requested Pool Member (/Common/%s /Common/%s) already exists in partition Common.","errorStack":[]}`, - poolName, strings.Replace(memberName, ":", " ", 1)) + `{"code":409,"message":"01020066:3: The requested Pool Member (%s %s) already exists in partition %s.","errorStack":[]}`, + poolResource.FullPath, strings.Replace(memberName, ":", " ", 1), poolResource.Partition) return } - f5state.pools[poolName][memberName] = true + f5state.pools[poolResource.id()][memberName] = true OK(response) } @@ -808,21 +870,21 @@ func postPoolMemberHandler(f5state mockF5State) http.HandlerFunc { func deletePoolMemberHandler(f5state mockF5State) http.HandlerFunc { return func(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) - poolName := vars["poolName"] + poolResource := newMockF5iControlResource("policy", vars["poolName"]) memberName := vars["memberName"] - if !validatePoolName(response, request, f5state, poolName) { + if !validatePool(response, request, f5state, poolResource) { return } - _, foundMember := f5state.pools[poolName][memberName] + _, foundMember := f5state.pools[poolResource.id()][memberName] if !foundMember { fmt.Fprintf(response, - `{"code":404,"message":"01020036:3: The requested Pool Member (/Common/%s /Common/%s) was not found.","errorStack":[]}`, - poolName, strings.Replace(memberName, ":", " ", 1)) + `{"code":404,"message":"01020036:3: The requested Pool Member (%s %s) was not found.","errorStack":[]}`, + poolResource.FullPath, strings.Replace(memberName, ":", " ", 1)) } - delete(f5state.pools[poolName], memberName) + delete(f5state.pools[poolResource.id()], memberName) OK(response) } @@ -831,37 +893,41 @@ func deletePoolMemberHandler(f5state mockF5State) http.HandlerFunc { func getRulesHandler(f5state mockF5State) http.HandlerFunc { return func(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) - policyName := vars["policyName"] + policy := newMockF5iControlResource("policy", vars["policyName"]) - if !validatePolicyName(response, request, f5state, policyName) { + if !validatePolicy(response, request, f5state, policy) { return } fmt.Fprint(response, `{"items": [`) + policyUriPath := policy.uriPath() first := true - for ruleName := range f5state.policies[policyName] { + for ruleName := range f5state.policies[policy.id()] { if first { first = false } else { fmt.Fprintf(response, ",") } + ruleUriPath := encodeiControlUriPathComponent(ruleName) fmt.Fprintf(response, `{"actionsReference":{"isSubcollection":true,"link":"https://localhost/mgmt/tm/ltm/policy/%s/rules/%s/actions?ver=11.6.0"},"conditionsReference":{"isSubcollection":true,"link":"https://localhost/mgmt/tm/ltm/policy/%s/rules/%s/conditions?ver=11.6.0"},"fullPath":"%s","generation":1218,"kind":"tm:ltm:policy:rules:rulesstate","name":"%s","ordinal":0,"selfLink":"https://localhost/mgmt/tm/ltm/policy/%s/rules/%s?ver=11.6.0"}`, - policyName, ruleName, policyName, ruleName, - ruleName, ruleName, policyName, ruleName) + policyUriPath, ruleUriPath, + policyUriPath, ruleUriPath, + ruleName, ruleName, + policyUriPath, ruleUriPath) } fmt.Fprintf(response, `],"kind":"tm:ltm:policy:rules:rulescollectionstate","selfLink":"https://localhost/mgmt/tm/ltm/policy/%s/rules?ver=11.6.0"}`, - policyName) + policyUriPath) } } func validateRuleName(response http.ResponseWriter, request *http.Request, - f5state mockF5State, policyName, ruleName string) bool { - for rule := range f5state.policies[policyName] { + f5state mockF5State, policy *mockF5iControlResource, ruleName string) bool { + for rule := range f5state.policies[policy.id()] { if rule == ruleName { return true } @@ -869,24 +935,23 @@ func validateRuleName(response http.ResponseWriter, request *http.Request, response.WriteHeader(http.StatusNotFound) fmt.Fprintf(response, - `{"code":404,"errorStack":[],"message":"01020036:3: The requested policy rule (/Common/%s %s) was not found."}`, - policyName, ruleName) - + `{"code":404,"errorStack":[],"message":"01020036:3: The requested policy rule (%s %s) was not found."}`, + policy.FullPath, ruleName) return false } func postConditionHandler(f5state mockF5State) http.HandlerFunc { return func(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) - policyName := vars["policyName"] + policy := newMockF5iControlResource("policy", vars["policyName"]) ruleName := vars["ruleName"] - if !validatePolicyName(response, request, f5state, policyName) { + if !validatePolicy(response, request, f5state, policy) { return } foundRule := validateRuleName(response, request, f5state, - policyName, ruleName) + policy, ruleName) if !foundRule { return } @@ -897,9 +962,9 @@ func postConditionHandler(f5state mockF5State) http.HandlerFunc { // TODO: Validate more fields in the payload: equals, request, maybe others. - conditions := f5state.policies[policyName][ruleName].conditions + conditions := f5state.policies[policy.id()][ruleName].conditions conditions = append(conditions, payload) - f5state.policies[policyName][ruleName] = policyRule{conditions} + f5state.policies[policy.id()][ruleName] = policyRule{conditions} OK(response) } @@ -908,15 +973,15 @@ func postConditionHandler(f5state mockF5State) http.HandlerFunc { func postActionHandler(f5state mockF5State) http.HandlerFunc { return func(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) - policyName := vars["policyName"] + policy := newMockF5iControlResource("policy", vars["policyName"]) ruleName := vars["ruleName"] - if !validatePolicyName(response, request, f5state, policyName) { + if !validatePolicy(response, request, f5state, policy) { return } foundRule := validateRuleName(response, request, f5state, - policyName, ruleName) + policy, ruleName) if !foundRule { return } @@ -930,20 +995,20 @@ func postActionHandler(f5state mockF5State) http.HandlerFunc { func deleteRuleHandler(f5state mockF5State) http.HandlerFunc { return func(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) - policyName := vars["policyName"] + policy := newMockF5iControlResource("policy", vars["policyName"]) ruleName := vars["ruleName"] - if !validatePolicyName(response, request, f5state, policyName) { + if !validatePolicy(response, request, f5state, policy) { return } foundRule := validateRuleName(response, request, f5state, - policyName, ruleName) + policy, ruleName) if !foundRule { return } - delete(f5state.policies[policyName], ruleName) + delete(f5state.policies[policy.id()], ruleName) OK(response) } @@ -1167,9 +1232,9 @@ func deleteServerSslProfileHandler(f5state mockF5State) http.HandlerFunc { func associateProfileWithVserver(f5state mockF5State) http.HandlerFunc { return func(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) - vserverName := vars["vserverName"] + vserver := newMockF5iControlResource("vserver", vars["vserverName"]) - if !validateVserverName(response, request, f5state, vserverName) { + if !validateVserver(response, request, f5state, vserver) { return } @@ -1181,7 +1246,7 @@ func associateProfileWithVserver(f5state mockF5State) http.HandlerFunc { profileName := payload.Name - f5state.vserverProfiles[vserverName][profileName] = true + f5state.vserverProfiles[vserver.id()][profileName] = true OK(response) } @@ -1190,14 +1255,14 @@ func associateProfileWithVserver(f5state mockF5State) http.HandlerFunc { func deleteSslVserverProfileHandler(f5state mockF5State) http.HandlerFunc { return func(response http.ResponseWriter, request *http.Request) { vars := mux.Vars(request) - vserverName := vars["vserverName"] + vserver := newMockF5iControlResource("vserver", vars["vserverName"]) profileName := vars["profileName"] - if !validateVserverName(response, request, f5state, vserverName) { + if !validateVserver(response, request, f5state, vserver) { return } - delete(f5state.vserverProfiles[vserverName], profileName) + delete(f5state.vserverProfiles[vserver.id()], profileName) OK(response) } @@ -1265,7 +1330,8 @@ func TestInitializeF5Plugin(t *testing.T) { // exist. expectedPolicies := []string{insecureRoutesPolicyName, secureRoutesPolicyName} for _, policyName := range expectedPolicies { - _, ok := mockF5.state.policies[policyName] + policy := newMockF5iControlResource("policy", path.Join(F5DefaultPartitionPath, policyName)) + _, ok := mockF5.state.policies[policy.id()] if !ok { t.Errorf("%s policy was not created; policies map: %v", policyName, mockF5.state.policies) @@ -1274,8 +1340,9 @@ func TestInitializeF5Plugin(t *testing.T) { // The HTTPS vserver should have the policy for secure routes associated. foundSecureRoutesPolicy := false - for policyName := range mockF5.state.vserverPolicies[httpsVserverName] { - if policyName == secureRoutesPolicyName { + httpsVserver := newMockF5iControlResource("vserver", path.Join(F5DefaultPartitionPath, httpsVserverName)) + for policyName := range mockF5.state.vserverPolicies[httpsVserver.id()] { + if strings.HasSuffix(policyName, secureRoutesPolicyName) { foundSecureRoutesPolicy = true } else { t.Errorf("Encountered unexpected policy associated to vserver %s: %s", @@ -1289,8 +1356,9 @@ func TestInitializeF5Plugin(t *testing.T) { // The HTTP vserver should have the policy for insecure routes associated. foundInsecureRoutesPolicy := false - for policyName := range mockF5.state.vserverPolicies[httpVserverName] { - if policyName == insecureRoutesPolicyName { + httpVserver := newMockF5iControlResource("vserver", path.Join(F5DefaultPartitionPath, httpVserverName)) + for policyName := range mockF5.state.vserverPolicies[httpVserver.id()] { + if strings.HasSuffix(policyName, insecureRoutesPolicyName) { foundInsecureRoutesPolicy = true } else { t.Errorf("Encountered unexpected policy associated to vserver %s: %s", @@ -1302,10 +1370,12 @@ func TestInitializeF5Plugin(t *testing.T) { insecureRoutesPolicyName, httpVserverName) } + resource := newMockF5iControlResource("datagroup", passthroughIRuleDatagroupName) + // The datagroup for passthrough routes should exist. foundPassthroughIRuleDatagroup := false for datagroupName := range mockF5.state.datagroups { - if datagroupName == passthroughIRuleDatagroupName { + if datagroupName == resource.FullPath { foundPassthroughIRuleDatagroup = true } } @@ -1317,7 +1387,7 @@ func TestInitializeF5Plugin(t *testing.T) { // passthrough routes. foundPassthroughIRule := false for iRuleName, iRuleCode := range mockF5.state.iRules { - if iRuleName == passthroughIRuleName { + if strings.HasSuffix(iRuleName, passthroughIRuleName) { foundPassthroughIRule = true if !strings.Contains(string(iRuleCode), passthroughIRuleDatagroupName) { @@ -1336,8 +1406,8 @@ func TestInitializeF5Plugin(t *testing.T) { // The HTTPS vserver should have the passthrough iRule associated. foundPassthroughIRuleUnderVserver := false - for _, iRuleName := range mockF5.state.vserverIRules[httpsVserverName] { - if iRuleName == passthroughIRuleName { + for _, iRuleName := range mockF5.state.vserverIRules[httpsVserver.id()] { + if strings.HasSuffix(iRuleName, passthroughIRuleName) { foundPassthroughIRuleUnderVserver = true } else { t.Errorf("Encountered unexpected iRule associated with vserver %s: %s", @@ -1350,9 +1420,9 @@ func TestInitializeF5Plugin(t *testing.T) { } // The HTTP vserver should have no iRules associated. - if len(mockF5.state.vserverIRules[httpVserverName]) != 0 { + if len(mockF5.state.vserverIRules[httpVserver.id()]) != 0 { t.Errorf("Vserver %s has iRules associated: %v", - httpVserverName, mockF5.state.vserverIRules[httpVserverName]) + httpVserverName, mockF5.state.vserverIRules[httpVserver.id()]) } // Initialization should be idempotent. @@ -1405,12 +1475,11 @@ func TestF5RouterPartition(t *testing.T) { t.Fatalf("Test case %q failed to initialize test router: %v", tc.name, err) } - name := strings.Replace(tc.partition, "/", "~", -1) - _, ok := mockF5.state.partitionPaths[name] + defer mockF5.close() + _, ok := mockF5.state.partitionPaths[tc.partition] if !ok { - t.Fatalf("Test case %q missing partition key %s", tc.name, name) + t.Fatalf("Test case %q missing partition key %s", tc.name, tc.partition) } - mockF5.close() } } @@ -1605,13 +1674,14 @@ func TestHandleRoute(t *testing.T) { }, validate: func(tc testCase) error { rulename := routeName(*tc.route) + policy := newMockF5iControlResource("policy", insecureRoutesPolicyName) - rule, ok := mockF5.state.policies[insecureRoutesPolicyName][rulename] + rule, ok := mockF5.state.policies[policy.id()][rulename] if !ok { return fmt.Errorf("Policy %s should have rule %s,"+ " but no rule was found: %v", insecureRoutesPolicyName, rulename, - mockF5.state.policies[insecureRoutesPolicyName]) + mockF5.state.policies[policy.id()]) } if len(rule.conditions) != 1 { @@ -1660,13 +1730,14 @@ func TestHandleRoute(t *testing.T) { }, validate: func(tc testCase) error { rulename := routeName(*tc.route) + policy := newMockF5iControlResource("policy", insecureRoutesPolicyName) - rule, ok := mockF5.state.policies[insecureRoutesPolicyName][rulename] + rule, ok := mockF5.state.policies[policy.id()][rulename] if !ok { return fmt.Errorf("Policy %s should have rule %s,"+ " but no rule was found: %v", insecureRoutesPolicyName, rulename, - mockF5.state.policies[insecureRoutesPolicyName]) + mockF5.state.policies[policy.id()]) } if len(rule.conditions) != 3 { return fmt.Errorf("Insecure route with pathname should have rule"+ @@ -1712,13 +1783,14 @@ func TestHandleRoute(t *testing.T) { }, validate: func(tc testCase) error { rulename := routeName(*tc.route) + policy := newMockF5iControlResource("policy", secureRoutesPolicyName) - _, found := mockF5.state.policies[secureRoutesPolicyName][rulename] + _, found := mockF5.state.policies[policy.id()][rulename] if found { return fmt.Errorf("Rule %s should have been deleted from policy %s"+ " when the corresponding route was deleted, but it remains yet: %v", rulename, secureRoutesPolicyName, - mockF5.state.policies[secureRoutesPolicyName]) + mockF5.state.policies[policy.id()]) } return nil @@ -1746,13 +1818,14 @@ func TestHandleRoute(t *testing.T) { }, validate: func(tc testCase) error { rulename := routeName(*tc.route) + policy := newMockF5iControlResource("policy", secureRoutesPolicyName) - _, found := mockF5.state.policies[secureRoutesPolicyName][rulename] + _, found := mockF5.state.policies[policy.id()][rulename] if !found { return fmt.Errorf("Policy %s should have rule %s,"+ " but no such rule was found: %v", secureRoutesPolicyName, rulename, - mockF5.state.policies[secureRoutesPolicyName]) + mockF5.state.policies[policy.id()]) } certfname := fmt.Sprintf("%s-https-cert.crt", rulename) @@ -1779,12 +1852,13 @@ func TestHandleRoute(t *testing.T) { clientSslProfileName, mockF5.state.clientSslProfiles) } - _, found = mockF5.state.vserverProfiles[httpsVserverName][clientSslProfileName] + httpsVserverPath := normalizeResourcePath(httpsVserverName) + _, found = mockF5.state.vserverProfiles[httpsVserverPath][clientSslProfileName] if !found { return fmt.Errorf("client-ssl profile %s should have been"+ " associated with the vserver but was not: %v", clientSslProfileName, - mockF5.state.vserverProfiles[httpsVserverName]) + mockF5.state.vserverProfiles[httpsVserverPath]) } return nil @@ -1812,13 +1886,14 @@ func TestHandleRoute(t *testing.T) { }, validate: func(tc testCase) error { rulename := routeName(*tc.route) + policy := newMockF5iControlResource("policy", secureRoutesPolicyName) - _, found := mockF5.state.policies[secureRoutesPolicyName][rulename] + _, found := mockF5.state.policies[policy.id()][rulename] if found { return fmt.Errorf("Rule %s should have been deleted from policy %s"+ " when the corresponding route was deleted, but it remains yet: %v", rulename, secureRoutesPolicyName, - mockF5.state.policies[secureRoutesPolicyName]) + mockF5.state.policies[policy.id()]) } certfname := fmt.Sprintf("%s-https-cert.crt", rulename) @@ -1838,12 +1913,13 @@ func TestHandleRoute(t *testing.T) { } clientSslProfileName := fmt.Sprintf("%s-client-ssl-profile", rulename) - _, found = mockF5.state.vserverProfiles[httpsVserverName][clientSslProfileName] + httpsVserverPath := normalizeResourcePath(httpsVserverName) + _, found = mockF5.state.vserverProfiles[httpsVserverPath][clientSslProfileName] if found { return fmt.Errorf("client-ssl profile %s should have been deleted"+ " from the vserver when the route was deleted but remains yet: %v", clientSslProfileName, - mockF5.state.vserverProfiles[httpsVserverName]) + mockF5.state.vserverProfiles[httpsVserverPath]) } _, found = mockF5.state.clientSslProfiles[clientSslProfileName] @@ -1875,13 +1951,14 @@ func TestHandleRoute(t *testing.T) { }, }, validate: func(tc testCase) error { - _, found := mockF5.state.datagroups[passthroughIRuleDatagroupName][tc.route.Spec.Host] + resource := newMockF5iControlResource("datagroup", passthroughIRuleDatagroupName) + _, found := mockF5.state.datagroups[resource.id()][tc.route.Spec.Host] if !found { return fmt.Errorf("Datagroup entry for %s should have been created"+ " in the %s datagroup for the passthrough route but cannot be"+ " found: %v", tc.route.Spec.Host, passthroughIRuleDatagroupName, - mockF5.state.datagroups[passthroughIRuleDatagroupName]) + mockF5.state.datagroups[resource.id()]) } return nil @@ -1908,13 +1985,14 @@ func TestHandleRoute(t *testing.T) { }, }, validate: func(tc testCase) error { - _, found := mockF5.state.datagroups[passthroughIRuleDatagroupName][tc.route.Spec.Host] + resource := newMockF5iControlResource("datagroup", passthroughIRuleDatagroupName) + _, found := mockF5.state.datagroups[resource.id()][tc.route.Spec.Host] if !found { return fmt.Errorf("Datagroup entry for %s should still exist"+ " in the %s datagroup after a secure route with the same hostname"+ " was created, but the datagroup entry cannot be found: %v", tc.route.Spec.Host, passthroughIRuleDatagroupName, - mockF5.state.datagroups[passthroughIRuleDatagroupName]) + mockF5.state.datagroups[resource.id()]) } return nil @@ -1936,13 +2014,14 @@ func TestHandleRoute(t *testing.T) { }, }, validate: func(tc testCase) error { - _, found := mockF5.state.datagroups[passthroughIRuleDatagroupName][tc.route.Spec.Host] + resource := newMockF5iControlResource("datagroup", passthroughIRuleDatagroupName) + _, found := mockF5.state.datagroups[resource.id()][tc.route.Spec.Host] if !found { return fmt.Errorf("Datagroup entry for %s should still exist"+ " in the %s datagroup after a secure route with the same hostname"+ " was updated, but the datagroup entry cannot be found: %v", tc.route.Spec.Host, passthroughIRuleDatagroupName, - mockF5.state.datagroups[passthroughIRuleDatagroupName]) + mockF5.state.datagroups[resource.id()]) } return nil @@ -1964,13 +2043,14 @@ func TestHandleRoute(t *testing.T) { }, }, validate: func(tc testCase) error { - _, found := mockF5.state.datagroups[passthroughIRuleDatagroupName][tc.route.Spec.Host] + resource := newMockF5iControlResource("datagroup", passthroughIRuleDatagroupName) + _, found := mockF5.state.datagroups[resource.id()][tc.route.Spec.Host] if !found { return fmt.Errorf("Datagroup entry for %s should still exist"+ " in the %s datagroup after a secure route with the same hostname"+ " was deleted, but the datagroup entry cannot be found: %v", tc.route.Spec.Host, passthroughIRuleDatagroupName, - mockF5.state.datagroups[passthroughIRuleDatagroupName]) + mockF5.state.datagroups[resource.id()]) } return nil @@ -1995,13 +2075,14 @@ func TestHandleRoute(t *testing.T) { }, }, validate: func(tc testCase) error { - _, found := mockF5.state.datagroups[passthroughIRuleDatagroupName][tc.route.Spec.Host] + resource := newMockF5iControlResource("datagroup", passthroughIRuleDatagroupName) + _, found := mockF5.state.datagroups[resource.id()][tc.route.Spec.Host] if found { return fmt.Errorf("Datagroup entry for %s should have been deleted"+ " from the %s datagroup for the passthrough route but remains"+ " yet: %v", tc.route.Spec.Host, passthroughIRuleDatagroupName, - mockF5.state.datagroups[passthroughIRuleDatagroupName]) + mockF5.state.datagroups[resource.id()]) } return nil @@ -2031,13 +2112,14 @@ func TestHandleRoute(t *testing.T) { }, validate: func(tc testCase) error { rulename := routeName(*tc.route) + policy := newMockF5iControlResource("policy", secureRoutesPolicyName) - _, found := mockF5.state.policies[secureRoutesPolicyName][rulename] + _, found := mockF5.state.policies[policy.id()][rulename] if !found { return fmt.Errorf("Policy %s should have rule %s for secure route,"+ " but no rule was found: %v", secureRoutesPolicyName, rulename, - mockF5.state.policies[secureRoutesPolicyName]) + mockF5.state.policies[policy.id()]) } certcafname := fmt.Sprintf("%s-https-chain.crt", rulename) @@ -2064,12 +2146,13 @@ func TestHandleRoute(t *testing.T) { clientSslProfileName, mockF5.state.clientSslProfiles) } - _, found = mockF5.state.vserverProfiles[httpsVserverName][clientSslProfileName] + httpsVserver := newMockF5iControlResource("vserver", httpsVserverName) + _, found = mockF5.state.vserverProfiles[httpsVserver.id()][clientSslProfileName] if !found { return fmt.Errorf("client-ssl profile %s should have been"+ " associated with the vserver but was not: %v", clientSslProfileName, - mockF5.state.vserverProfiles[httpsVserverName]) + mockF5.state.vserverProfiles[httpsVserver.id()]) } serverSslProfileName := fmt.Sprintf("%s-server-ssl-profile", rulename) @@ -2080,12 +2163,12 @@ func TestHandleRoute(t *testing.T) { serverSslProfileName, mockF5.state.serverSslProfiles) } - _, found = mockF5.state.vserverProfiles[httpsVserverName][serverSslProfileName] + _, found = mockF5.state.vserverProfiles[httpsVserver.id()][serverSslProfileName] if !found { return fmt.Errorf("server-ssl profile %s should have been"+ " associated with the vserver but was not: %v", serverSslProfileName, - mockF5.state.vserverProfiles[httpsVserverName]) + mockF5.state.vserverProfiles[httpsVserver.id()]) } return nil @@ -2115,13 +2198,14 @@ func TestHandleRoute(t *testing.T) { }, validate: func(tc testCase) error { rulename := routeName(*tc.route) + policy := newMockF5iControlResource("policy", secureRoutesPolicyName) - _, found := mockF5.state.policies[secureRoutesPolicyName][rulename] + _, found := mockF5.state.policies[policy.id()][rulename] if found { return fmt.Errorf("Rule %s should have been deleted from policy %s"+ " when the corresponding route was deleted, but it remains yet: %v", rulename, secureRoutesPolicyName, - mockF5.state.policies[secureRoutesPolicyName]) + mockF5.state.policies[policy.id()]) } certcafname := fmt.Sprintf("%s-https-chain.crt", rulename) @@ -2140,22 +2224,23 @@ func TestHandleRoute(t *testing.T) { keyfname, mockF5.state.keys) } + httpsVserver := newMockF5iControlResource("vserver", httpsVserverName) clientSslProfileName := fmt.Sprintf("%s-client-ssl-profile", rulename) - _, found = mockF5.state.vserverProfiles[httpsVserverName][clientSslProfileName] + _, found = mockF5.state.vserverProfiles[httpsVserver.id()][clientSslProfileName] if found { return fmt.Errorf("client-ssl profile %s should have been deleted"+ " from the vserver when the route was deleted but remains yet: %v", clientSslProfileName, - mockF5.state.vserverProfiles[httpsVserverName]) + mockF5.state.vserverProfiles[httpsVserver.id()]) } serverSslProfileName := fmt.Sprintf("%s-server-ssl-profile", rulename) - _, found = mockF5.state.vserverProfiles[httpsVserverName][clientSslProfileName] + _, found = mockF5.state.vserverProfiles[httpsVserver.id()][clientSslProfileName] if found { return fmt.Errorf("server-ssl profile %s should have been deleted"+ " from the vserver when the route was deleted but remains yet: %v", serverSslProfileName, - mockF5.state.vserverProfiles[httpsVserverName]) + mockF5.state.vserverProfiles[httpsVserver.id()]) } _, found = mockF5.state.serverSslProfiles[serverSslProfileName] diff --git a/pkg/router/f5/types.go b/pkg/router/f5/types.go index df12b29e1411..62ca56b98dff 100644 --- a/pkg/router/f5/types.go +++ b/pkg/router/f5/types.go @@ -41,7 +41,9 @@ type F5Error struct { // The F5 router uses it within f5Vserver to unmarshal the JSON response when // requesting a vserver from F5 BIG-IP. type f5VserverPolicy struct { - Name string `json:"name"` + Name string `json:"name"` + Partition string `json:"partition"` + FullPath string `json:"fullPath"` } // f5VserverPolicies represents the policies associated with an F5 BIG-IP LTM @@ -77,6 +79,9 @@ type f5Pool struct { // uses /Common/http. Monitor string `json:"monitor"` + // Partition is the F5 partition to use for the pool. + Partition string `json:"partition"` + // Name is the name of the pool. The F5 router uses names of the form // openshift__. Name string `json:"name"` @@ -137,6 +142,9 @@ type f5Policy struct { // Name is the name of the policy. Name string `json:"name"` + // Partition is the F5 partition to use for the policy. + Partition string `json:"partition"` + // Controls is a list of F5 BIG-IP LTM features enabled for the pool. // Typically we use just forwarding; other possible values are caching, // classification, compression, request-adaption, response-adaption, and @@ -265,6 +273,9 @@ type f5IRule struct { // Name is the name of the iRule. Name string `json:"name"` + // Partition is the F5 partition to use for the iRule. + Partition string `json:"partition"` + // Code is the TCL code of the iRule. Code string `json:"apiAnonymous"` } From 98d640de0b5272c09badaa7cc1a46677b6e96df6 Mon Sep 17 00:00:00 2001 From: Rajat Chopra Date: Fri, 31 Mar 2017 02:41:40 -0400 Subject: [PATCH 2/3] policy urls should support partitions --- pkg/router/f5/f5.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/router/f5/f5.go b/pkg/router/f5/f5.go index a7669a53b809..7a3e10255178 100644 --- a/pkg/router/f5/f5.go +++ b/pkg/router/f5/f5.go @@ -576,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), AllowService: "all", @@ -615,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"}, @@ -629,7 +631,7 @@ func (f5 *f5LTM) ensurePolicyExists(policyName string) error { err = f5.post(policiesUrl, policyPayload, nil) } else { policyPayload := f5Policy{ - Name: policyName, + Name: policyPath, Partition: f5.partitionPath, Controls: []string{"forwarding"}, Requires: []string{"http"}, @@ -698,7 +700,7 @@ 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, } From 6ee3a7eeae290bba5631dbd3877ac61494c9dfdd Mon Sep 17 00:00:00 2001 From: Rajat Chopra Date: Mon, 3 Apr 2017 18:27:12 -0400 Subject: [PATCH 3/3] Fix go tests; policy requests now include the partition paths --- pkg/router/f5/plugin_test.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/router/f5/plugin_test.go b/pkg/router/f5/plugin_test.go index bc7f65e1d1b5..cb562d3969d4 100644 --- a/pkg/router/f5/plugin_test.go +++ b/pkg/router/f5/plugin_test.go @@ -350,8 +350,7 @@ func postPolicyHandler(f5state mockF5State) http.HandlerFunc { decoder := json.NewDecoder(request.Body) decoder.Decode(&payload) - policyPath := path.Join(payload.Partition, payload.Name) - policy := newMockF5iControlResource("policy", policyPath) + policy := newMockF5iControlResource("policy", payload.Name) f5state.policies[policy.id()] = map[string]policyRule{} @@ -437,7 +436,7 @@ func associatePolicyWithVserverHandler(f5state mockF5State) http.HandlerFunc { decoder := json.NewDecoder(request.Body) decoder.Decode(&payload) - policy := newMockF5iControlResource("policy", path.Join(payload.Partition, payload.Name)) + policy := newMockF5iControlResource("policy", payload.Name) validVserver := recogniseVserver(vserver) _, validPolicy := f5state.policies[policy.id()]