diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index ebb6757ea7b..bd68c5b0284 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -1981,6 +1981,16 @@ "Comment": "v1.0.0-920-g934dbf8", "Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04" }, + { + "ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups", + "Comment": "v1.0.0-920-g934dbf8", + "Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04" + }, + { + "ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules", + "Comment": "v1.0.0-920-g934dbf8", + "Rev": "934dbf81977c67c521c75492dc1f55ca74dc5b04" + }, { "ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/ports", "Comment": "v1.0.0-920-g934dbf8", diff --git a/Godeps/LICENSES b/Godeps/LICENSES index df98fd04f9b..d44afb46eca 100644 --- a/Godeps/LICENSES +++ b/Godeps/LICENSES @@ -62959,6 +62959,404 @@ specific language governing permissions and limitations under the License. ================================================================================ +================================================================================ += vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups licensed under: = + +Copyright 2012-2013 Rackspace, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +------ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + += vendor/github.com/rackspace/gophercloud/LICENSE dd19699707373c2ca31531a659130416 - +================================================================================ + + +================================================================================ += vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules licensed under: = + +Copyright 2012-2013 Rackspace, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +------ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + += vendor/github.com/rackspace/gophercloud/LICENSE dd19699707373c2ca31531a659130416 - +================================================================================ + + ================================================================================ = vendor/github.com/rackspace/gophercloud/openstack/networking/v2/ports licensed under: = diff --git a/pkg/cloudprovider/providers/openstack/openstack.go b/pkg/cloudprovider/providers/openstack/openstack.go index 44cfb43b314..b522ae269b2 100644 --- a/pkg/cloudprovider/providers/openstack/openstack.go +++ b/pkg/cloudprovider/providers/openstack/openstack.go @@ -72,14 +72,16 @@ type LoadBalancer struct { } type LoadBalancerOpts struct { - LBVersion string `gcfg:"lb-version"` // overrides autodetection. v1 or v2 - SubnetId string `gcfg:"subnet-id"` // required - FloatingNetworkId string `gcfg:"floating-network-id"` - LBMethod string `gcfg:"lb-method"` - CreateMonitor bool `gcfg:"create-monitor"` - MonitorDelay MyDuration `gcfg:"monitor-delay"` - MonitorTimeout MyDuration `gcfg:"monitor-timeout"` - MonitorMaxRetries uint `gcfg:"monitor-max-retries"` + LBVersion string `gcfg:"lb-version"` // overrides autodetection. v1 or v2 + SubnetId string `gcfg:"subnet-id"` // required + FloatingNetworkId string `gcfg:"floating-network-id"` + LBMethod string `gcfg:"lb-method"` + CreateMonitor bool `gcfg:"create-monitor"` + MonitorDelay MyDuration `gcfg:"monitor-delay"` + MonitorTimeout MyDuration `gcfg:"monitor-timeout"` + MonitorMaxRetries uint `gcfg:"monitor-max-retries"` + ManageSecurityGroups bool `gcfg:"manage-security-groups"` + NodeSecurityGroupID string `gcfg:"node-security-group"` } // OpenStack is an implementation of cloud provider Interface for OpenStack. diff --git a/pkg/cloudprovider/providers/openstack/openstack_loadbalancer.go b/pkg/cloudprovider/providers/openstack/openstack_loadbalancer.go index de996e9e3d7..399e32ee9e6 100644 --- a/pkg/cloudprovider/providers/openstack/openstack_loadbalancer.go +++ b/pkg/cloudprovider/providers/openstack/openstack_loadbalancer.go @@ -17,8 +17,12 @@ limitations under the License. package openstack import ( + "fmt" + "net" + "strings" "time" + "github.com/golang/glog" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/openstack/networking/v2/extensions" "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" @@ -30,12 +34,11 @@ import ( "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers" v2_monitors "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors" v2_pools "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules" neutron_ports "github.com/rackspace/gophercloud/openstack/networking/v2/ports" "github.com/rackspace/gophercloud/pagination" - "fmt" - - "github.com/golang/glog" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/service" "k8s.io/kubernetes/pkg/cloudprovider" @@ -77,8 +80,9 @@ func networkExtensions(client *gophercloud.ServiceClient) (map[string]bool, erro return seen, err } -func getPortIDByIP(client *gophercloud.ServiceClient, ipAddress string) (string, error) { - var portID string +func getPortByIP(client *gophercloud.ServiceClient, ipAddress string) (neutron_ports.Port, error) { + var targetPort neutron_ports.Port + var portFound = false err := neutron_ports.List(client, neutron_ports.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { portList, err := neutron_ports.ExtractPorts(page) @@ -89,7 +93,8 @@ func getPortIDByIP(client *gophercloud.ServiceClient, ipAddress string) (string, for _, port := range portList { for _, ip := range port.FixedIPs { if ip.IPAddress == ipAddress { - portID = port.ID + targetPort = port + portFound = true return false, nil } } @@ -97,8 +102,18 @@ func getPortIDByIP(client *gophercloud.ServiceClient, ipAddress string) (string, return true, nil }) + if err == nil && !portFound { + err = ErrNotFound + } + return targetPort, err +} - return portID, err +func getPortIDByIP(client *gophercloud.ServiceClient, ipAddress string) (string, error) { + targetPort, err := getPortByIP(client, ipAddress) + if err != nil { + return targetPort.ID, err + } + return targetPort.ID, nil } func getFloatingIPByPortID(client *gophercloud.ServiceClient, portID string) (*floatingips.FloatingIP, error) { @@ -396,6 +411,32 @@ func popMember(members []v2_pools.Member, addr string) []v2_pools.Member { return members } +func getSecurityGroupName(clusterName string, service *api.Service) string { + return fmt.Sprintf("lb-sg-%s-%v", clusterName, service.Name) +} + +func getSecurityGroupRules(client *gophercloud.ServiceClient, opts rules.ListOpts) ([]rules.SecGroupRule, error) { + + pager := rules.List(client, opts) + + var securityRules []rules.SecGroupRule + + err := pager.EachPage(func(page pagination.Page) (bool, error) { + ruleList, err := rules.ExtractRules(page) + if err != nil { + return false, err + } + securityRules = append(securityRules, ruleList...) + return true, nil + }) + + if err != nil { + return nil, err + } + + return securityRules, nil +} + func waitLoadbalancerActiveProvisioningStatus(client *gophercloud.ServiceClient, loadbalancerID string) (string, error) { start := time.Now().Second() for { @@ -438,6 +479,41 @@ func waitLoadbalancerDeleted(client *gophercloud.ServiceClient, loadbalancerID s } } +func createNodeSecurityGroup(client *gophercloud.ServiceClient, nodeSecurityGroupID string, port int, protocol string, lbSecGroup string) error { + v4NodeSecGroupRuleCreateOpts := rules.CreateOpts{ + Direction: "ingress", + PortRangeMax: port, + PortRangeMin: port, + Protocol: strings.ToLower(protocol), + RemoteGroupID: lbSecGroup, + SecGroupID: nodeSecurityGroupID, + EtherType: "IPv4", + } + + v6NodeSecGroupRuleCreateOpts := rules.CreateOpts{ + Direction: "ingress", + PortRangeMax: port, + PortRangeMin: port, + Protocol: strings.ToLower(protocol), + RemoteGroupID: lbSecGroup, + SecGroupID: nodeSecurityGroupID, + EtherType: "IPv6", + } + + _, err := rules.Create(client, v4NodeSecGroupRuleCreateOpts).Extract() + + if err != nil { + return err + } + + _, err = rules.Create(client, v6NodeSecGroupRuleCreateOpts).Extract() + + if err != nil { + return err + } + return nil +} + func (lbaas *LbaasV2) createLoadBalancer(service *api.Service, name string) (*loadbalancers.LoadBalancer, error) { createOpts := loadbalancers.CreateOpts{ Name: name, @@ -495,7 +571,16 @@ func (lbaas *LbaasV2) EnsureLoadBalancer(clusterName string, apiService *api.Ser } } - affinity := api.ServiceAffinityNone //apiService.Spec.SessionAffinity + sourceRanges, err := service.GetLoadBalancerSourceRanges(apiService) + if err != nil { + return nil, err + } + + if !service.IsAllowAll(sourceRanges) && !lbaas.opts.ManageSecurityGroups { + return nil, fmt.Errorf("Source range restrictions are not supported for openstack load balancers without managing security groups") + } + + affinity := api.ServiceAffinityNone var persistence *v2_pools.SessionPersistence switch affinity { case api.ServiceAffinityNone: @@ -506,15 +591,6 @@ func (lbaas *LbaasV2) EnsureLoadBalancer(clusterName string, apiService *api.Ser return nil, fmt.Errorf("unsupported load balancer affinity: %v", affinity) } - sourceRanges, err := service.GetLoadBalancerSourceRanges(apiService) - if err != nil { - return nil, err - } - - if !service.IsAllowAll(sourceRanges) { - return nil, fmt.Errorf("Source range restrictions are not supported for openstack load balancers") - } - name := cloudprovider.GetLoadBalancerName(apiService) loadbalancer, err := getLoadbalancerByName(lbaas.network, name) if err != nil { @@ -706,27 +782,143 @@ func (lbaas *LbaasV2) EnsureLoadBalancer(clusterName string, apiService *api.Ser status.Ingress = []api.LoadBalancerIngress{{IP: loadbalancer.VipAddress}} - portID, err := getPortIDByIP(lbaas.network, loadbalancer.VipAddress) + port, err := getPortByIP(lbaas.network, loadbalancer.VipAddress) if err != nil { return nil, fmt.Errorf("Error getting port for LB vip %s: %v", loadbalancer.VipAddress, err) } - floatIP, err := getFloatingIPByPortID(lbaas.network, portID) + floatIP, err := getFloatingIPByPortID(lbaas.network, port.ID) if err != nil && err != ErrNotFound { - return nil, fmt.Errorf("Error getting floating ip for port %s: %v", portID, err) + return nil, fmt.Errorf("Error getting floating ip for port %s: %v", port.ID, err) } if floatIP == nil && lbaas.opts.FloatingNetworkId != "" { - glog.V(4).Infof("Creating floating ip for loadbalancer %s port %s", loadbalancer.ID, portID) + glog.V(4).Infof("Creating floating ip for loadbalancer %s port %s", loadbalancer.ID, port.ID) floatIPOpts := floatingips.CreateOpts{ FloatingNetworkID: lbaas.opts.FloatingNetworkId, - PortID: portID, + PortID: port.ID, } floatIP, err = floatingips.Create(lbaas.network, floatIPOpts).Extract() if err != nil { return nil, fmt.Errorf("Error creating LB floatingip %+v: %v", floatIPOpts, err) } } + if floatIP != nil { + status.Ingress = append(status.Ingress, api.LoadBalancerIngress{IP: floatIP.FloatingIP}) + } - status.Ingress = append(status.Ingress, api.LoadBalancerIngress{IP: floatIP.FloatingIP}) + if lbaas.opts.ManageSecurityGroups { + lbSecGroupCreateOpts := groups.CreateOpts{ + Name: getSecurityGroupName(clusterName, apiService), + Description: fmt.Sprintf("Securty Group for %v Service LoadBalancer", apiService.Name), + } + + lbSecGroup, err := groups.Create(lbaas.network, lbSecGroupCreateOpts).Extract() + + if err != nil { + // cleanup what was created so far + _ = lbaas.EnsureLoadBalancerDeleted(clusterName, apiService) + return nil, err + } + + for _, port := range ports { + + for _, sourceRange := range sourceRanges.StringSlice() { + ethertype := "IPv4" + network, _, err := net.ParseCIDR(sourceRange) + + if err != nil { + // cleanup what was created so far + glog.Errorf("Error parsing source range %s as a CIDR", sourceRange) + _ = lbaas.EnsureLoadBalancerDeleted(clusterName, apiService) + return nil, err + } + + if network.To4() == nil { + ethertype = "IPv6" + } + + lbSecGroupRuleCreateOpts := rules.CreateOpts{ + Direction: "ingress", + PortRangeMax: int(port.Port), + PortRangeMin: int(port.Port), + Protocol: strings.ToLower(string(port.Protocol)), + RemoteIPPrefix: sourceRange, + SecGroupID: lbSecGroup.ID, + EtherType: ethertype, + } + + _, err = rules.Create(lbaas.network, lbSecGroupRuleCreateOpts).Extract() + + if err != nil { + // cleanup what was created so far + _ = lbaas.EnsureLoadBalancerDeleted(clusterName, apiService) + return nil, err + } + } + + err := createNodeSecurityGroup(lbaas.network, lbaas.opts.NodeSecurityGroupID, int(port.NodePort), string(port.Protocol), lbSecGroup.ID) + if err != nil { + glog.Errorf("Error occured creating security group for loadbalancer %s:", loadbalancer.ID) + _ = lbaas.EnsureLoadBalancerDeleted(clusterName, apiService) + return nil, err + } + } + + lbSecGroupRuleCreateOpts := rules.CreateOpts{ + Direction: "ingress", + PortRangeMax: 4, // ICMP: Code - Values for ICMP "Destination Unreachable: Fragmentation Needed and Don't Fragment was Set" + PortRangeMin: 3, // ICMP: Type + Protocol: "icmp", + RemoteIPPrefix: "0.0.0.0/0", // The Fragmentation packet can come from anywhere along the path back to the sourceRange - we need to all this from all + SecGroupID: lbSecGroup.ID, + EtherType: "IPv4", + } + + _, err = rules.Create(lbaas.network, lbSecGroupRuleCreateOpts).Extract() + + if err != nil { + // cleanup what was created so far + _ = lbaas.EnsureLoadBalancerDeleted(clusterName, apiService) + return nil, err + } + + lbSecGroupRuleCreateOpts = rules.CreateOpts{ + Direction: "ingress", + PortRangeMax: 0, // ICMP: Code - Values for ICMP "Packet Too Big" + PortRangeMin: 2, // ICMP: Type + Protocol: "icmp", + RemoteIPPrefix: "::/0", // The Fragmentation packet can come from anywhere along the path back to the sourceRange - we need to all this from all + SecGroupID: lbSecGroup.ID, + EtherType: "IPv6", + } + + _, err = rules.Create(lbaas.network, lbSecGroupRuleCreateOpts).Extract() + + if err != nil { + // cleanup what was created so far + _ = lbaas.EnsureLoadBalancerDeleted(clusterName, apiService) + return nil, err + } + + // Get the port ID + port, err := getPortByIP(lbaas.network, loadbalancer.VipAddress) + if err != nil { + // cleanup what was created so far + _ = lbaas.EnsureLoadBalancerDeleted(clusterName, apiService) + return nil, err + } + + update_opts := neutron_ports.UpdateOpts{SecurityGroups: []string{lbSecGroup.ID}} + + res := neutron_ports.Update(lbaas.network, port.ID, update_opts) + + if res.Err != nil { + glog.Errorf("Error occured updating port: %s", port.ID) + // cleanup what was created so far + _ = lbaas.EnsureLoadBalancerDeleted(clusterName, apiService) + return nil, res.Err + } + + } return status, nil } @@ -753,6 +945,7 @@ func (lbaas *LbaasV2) UpdateLoadBalancer(clusterName string, service *api.Servic Protocol string Port int } + lbListeners := make(map[portKey]listeners.Listener) err = listeners.List(lbaas.network, listeners.ListOpts{LoadbalancerID: loadbalancer.ID}).EachPage(func(page pagination.Page) (bool, error) { listenersList, err := listeners.ExtractListeners(page) @@ -994,6 +1187,42 @@ func (lbaas *LbaasV2) EnsureLoadBalancerDeleted(clusterName string, service *api return err } waitLoadbalancerDeleted(lbaas.network, loadbalancer.ID) + + // Delete the Security Group + if lbaas.opts.ManageSecurityGroups { + // Generate Name + lbSecGroupName := getSecurityGroupName(clusterName, service) + lbSecGroupID, err := groups.IDFromName(lbaas.network, lbSecGroupName) + if err != nil { + glog.V(1).Infof("Error occurred finding security group: %s: %v", lbSecGroupName, err) + return nil + } + + lbSecGroup := groups.Delete(lbaas.network, lbSecGroupID) + if lbSecGroup.Err != nil && !isNotFound(lbSecGroup.Err) { + return lbSecGroup.Err + } + + // Delete the rules in the Node Security Group + opts := rules.ListOpts{ + SecGroupID: lbaas.opts.NodeSecurityGroupID, + RemoteGroupID: lbSecGroupID, + } + secGroupRules, err := getSecurityGroupRules(lbaas.network, opts) + + if err != nil && !isNotFound(err) { + glog.Errorf("Error finding rules for remote group id %s in security group id %s", lbSecGroupID, lbaas.opts.NodeSecurityGroupID) + return err + } + + for _, rule := range secGroupRules { + res := rules.Delete(lbaas.network, rule.ID) + if res.Err != nil && !isNotFound(res.Err) { + glog.V(1).Infof("Error occurred deleting security group rule: %s: %v", rule.ID, res.Err) + } + } + } + return nil } diff --git a/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups/requests.go b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups/requests.go new file mode 100644 index 00000000000..2712ac1621f --- /dev/null +++ b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups/requests.go @@ -0,0 +1,131 @@ +package groups + +import ( + "fmt" + + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/pagination" +) + +// ListOpts allows the filtering and sorting of paginated collections through +// the API. Filtering is achieved by passing in struct field values that map to +// the floating IP attributes you want to see returned. SortKey allows you to +// sort by a particular network attribute. SortDir sets the direction, and is +// either `asc' or `desc'. Marker and Limit are used for pagination. +type ListOpts struct { + ID string `q:"id"` + Name string `q:"name"` + TenantID string `q:"tenant_id"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` +} + +// List returns a Pager which allows you to iterate over a collection of +// security groups. It accepts a ListOpts struct, which allows you to filter +// and sort the returned collection for greater efficiency. +func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager { + q, err := gophercloud.BuildQueryString(&opts) + if err != nil { + return pagination.Pager{Err: err} + } + u := rootURL(c) + q.String() + return pagination.NewPager(c, u, func(r pagination.PageResult) pagination.Page { + return SecGroupPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +var ( + errNameRequired = fmt.Errorf("Name is required") +) + +// CreateOpts contains all the values needed to create a new security group. +type CreateOpts struct { + // Required. Human-readable name for the VIP. Does not have to be unique. + Name string + + // Required for admins. Indicates the owner of the VIP. + TenantID string + + // Optional. Describes the security group. + Description string +} + +// Create is an operation which provisions a new security group with default +// security group rules for the IPv4 and IPv6 ether types. +func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult { + var res CreateResult + + // Validate required opts + if opts.Name == "" { + res.Err = errNameRequired + return res + } + + type secgroup struct { + Name string `json:"name"` + TenantID string `json:"tenant_id,omitempty"` + Description string `json:"description,omitempty"` + } + + type request struct { + SecGroup secgroup `json:"security_group"` + } + + reqBody := request{SecGroup: secgroup{ + Name: opts.Name, + TenantID: opts.TenantID, + Description: opts.Description, + }} + + _, res.Err = c.Post(rootURL(c), reqBody, &res.Body, nil) + return res +} + +// Get retrieves a particular security group based on its unique ID. +func Get(c *gophercloud.ServiceClient, id string) GetResult { + var res GetResult + _, res.Err = c.Get(resourceURL(c, id), &res.Body, nil) + return res +} + +// Delete will permanently delete a particular security group based on its unique ID. +func Delete(c *gophercloud.ServiceClient, id string) DeleteResult { + var res DeleteResult + _, res.Err = c.Delete(resourceURL(c, id), nil) + return res +} + +// IDFromName is a convenience function that returns a security group's ID given its name. +func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) { + securityGroupCount := 0 + securityGroupID := "" + if name == "" { + return "", fmt.Errorf("A security group name must be provided.") + } + pager := List(client, ListOpts{}) + pager.EachPage(func(page pagination.Page) (bool, error) { + securityGroupList, err := ExtractGroups(page) + if err != nil { + return false, err + } + + for _, s := range securityGroupList { + if s.Name == name { + securityGroupCount++ + securityGroupID = s.ID + } + } + return true, nil + }) + + switch securityGroupCount { + case 0: + return "", fmt.Errorf("Unable to find security group: %s", name) + case 1: + return securityGroupID, nil + default: + return "", fmt.Errorf("Found %d security groups matching %s", securityGroupCount, name) + } +} diff --git a/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups/results.go b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups/results.go new file mode 100644 index 00000000000..49db261c22e --- /dev/null +++ b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups/results.go @@ -0,0 +1,108 @@ +package groups + +import ( + "github.com/mitchellh/mapstructure" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules" + "github.com/rackspace/gophercloud/pagination" +) + +// SecGroup represents a container for security group rules. +type SecGroup struct { + // The UUID for the security group. + ID string + + // Human-readable name for the security group. Might not be unique. Cannot be + // named "default" as that is automatically created for a tenant. + Name string + + // The security group description. + Description string + + // A slice of security group rules that dictate the permitted behaviour for + // traffic entering and leaving the group. + Rules []rules.SecGroupRule `json:"security_group_rules" mapstructure:"security_group_rules"` + + // Owner of the security group. Only admin users can specify a TenantID + // other than their own. + TenantID string `json:"tenant_id" mapstructure:"tenant_id"` +} + +// SecGroupPage is the page returned by a pager when traversing over a +// collection of security groups. +type SecGroupPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of security groups has +// reached the end of a page and the pager seeks to traverse over a new one. In +// order to do this, it needs to construct the next page's URL. +func (p SecGroupPage) NextPageURL() (string, error) { + type resp struct { + Links []gophercloud.Link `mapstructure:"security_groups_links"` + } + + var r resp + err := mapstructure.Decode(p.Body, &r) + if err != nil { + return "", err + } + + return gophercloud.ExtractNextURL(r.Links) +} + +// IsEmpty checks whether a SecGroupPage struct is empty. +func (p SecGroupPage) IsEmpty() (bool, error) { + is, err := ExtractGroups(p) + if err != nil { + return true, nil + } + return len(is) == 0, nil +} + +// ExtractGroups accepts a Page struct, specifically a SecGroupPage struct, +// and extracts the elements into a slice of SecGroup structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractGroups(page pagination.Page) ([]SecGroup, error) { + var resp struct { + SecGroups []SecGroup `mapstructure:"security_groups" json:"security_groups"` + } + + err := mapstructure.Decode(page.(SecGroupPage).Body, &resp) + + return resp.SecGroups, err +} + +type commonResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts a security group. +func (r commonResult) Extract() (*SecGroup, error) { + if r.Err != nil { + return nil, r.Err + } + + var res struct { + SecGroup *SecGroup `mapstructure:"security_group" json:"security_group"` + } + + err := mapstructure.Decode(r.Body, &res) + + return res.SecGroup, err +} + +// CreateResult represents the result of a create operation. +type CreateResult struct { + commonResult +} + +// GetResult represents the result of a get operation. +type GetResult struct { + commonResult +} + +// DeleteResult represents the result of a delete operation. +type DeleteResult struct { + gophercloud.ErrResult +} diff --git a/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups/urls.go b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups/urls.go new file mode 100644 index 00000000000..84f7324f090 --- /dev/null +++ b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/groups/urls.go @@ -0,0 +1,13 @@ +package groups + +import "github.com/rackspace/gophercloud" + +const rootPath = "security-groups" + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(rootPath) +} + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, id) +} diff --git a/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules/requests.go b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules/requests.go new file mode 100644 index 00000000000..e06934a09af --- /dev/null +++ b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules/requests.go @@ -0,0 +1,174 @@ +package rules + +import ( + "fmt" + + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/pagination" +) + +// ListOpts allows the filtering and sorting of paginated collections through +// the API. Filtering is achieved by passing in struct field values that map to +// the security group attributes you want to see returned. SortKey allows you to +// sort by a particular network attribute. SortDir sets the direction, and is +// either `asc' or `desc'. Marker and Limit are used for pagination. +type ListOpts struct { + Direction string `q:"direction"` + EtherType string `q:"ethertype"` + ID string `q:"id"` + PortRangeMax int `q:"port_range_max"` + PortRangeMin int `q:"port_range_min"` + Protocol string `q:"protocol"` + RemoteGroupID string `q:"remote_group_id"` + RemoteIPPrefix string `q:"remote_ip_prefix"` + SecGroupID string `q:"security_group_id"` + TenantID string `q:"tenant_id"` + Limit int `q:"limit"` + Marker string `q:"marker"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` +} + +// List returns a Pager which allows you to iterate over a collection of +// security group rules. It accepts a ListOpts struct, which allows you to filter +// and sort the returned collection for greater efficiency. +func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager { + q, err := gophercloud.BuildQueryString(&opts) + if err != nil { + return pagination.Pager{Err: err} + } + u := rootURL(c) + q.String() + return pagination.NewPager(c, u, func(r pagination.PageResult) pagination.Page { + return SecGroupRulePage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// Errors +var ( + errValidDirectionRequired = fmt.Errorf("A valid Direction is required") + errValidEtherTypeRequired = fmt.Errorf("A valid EtherType is required") + errSecGroupIDRequired = fmt.Errorf("A valid SecGroupID is required") + errValidProtocolRequired = fmt.Errorf("A valid Protocol is required") +) + +// Constants useful for CreateOpts +const ( + DirIngress = "ingress" + DirEgress = "egress" + Ether4 = "IPv4" + Ether6 = "IPv6" + ProtocolTCP = "tcp" + ProtocolUDP = "udp" + ProtocolICMP = "icmp" +) + +// CreateOpts contains all the values needed to create a new security group rule. +type CreateOpts struct { + // Required. Must be either "ingress" or "egress": the direction in which the + // security group rule is applied. + Direction string + + // Required. Must be "IPv4" or "IPv6", and addresses represented in CIDR must + // match the ingress or egress rules. + EtherType string + + // Required. The security group ID to associate with this security group rule. + SecGroupID string + + // Optional. The maximum port number in the range that is matched by the + // security group rule. The PortRangeMin attribute constrains the PortRangeMax + // attribute. If the protocol is ICMP, this value must be an ICMP type. + PortRangeMax int + + // Optional. The minimum port number in the range that is matched by the + // security group rule. If the protocol is TCP or UDP, this value must be + // less than or equal to the value of the PortRangeMax attribute. If the + // protocol is ICMP, this value must be an ICMP type. + PortRangeMin int + + // Optional. The protocol that is matched by the security group rule. Valid + // values are "tcp", "udp", "icmp" or an empty string. + Protocol string + + // Optional. The remote group ID to be associated with this security group + // rule. You can specify either RemoteGroupID or RemoteIPPrefix. + RemoteGroupID string + + // Optional. The remote IP prefix to be associated with this security group + // rule. You can specify either RemoteGroupID or RemoteIPPrefix. This + // attribute matches the specified IP prefix as the source IP address of the + // IP packet. + RemoteIPPrefix string + + // Required for admins. Indicates the owner of the VIP. + TenantID string +} + +// Create is an operation which adds a new security group rule and associates it +// with an existing security group (whose ID is specified in CreateOpts). +func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult { + var res CreateResult + + // Validate required opts + if opts.Direction != DirIngress && opts.Direction != DirEgress { + res.Err = errValidDirectionRequired + return res + } + if opts.EtherType != Ether4 && opts.EtherType != Ether6 { + res.Err = errValidEtherTypeRequired + return res + } + if opts.SecGroupID == "" { + res.Err = errSecGroupIDRequired + return res + } + if opts.Protocol != "" && opts.Protocol != ProtocolTCP && opts.Protocol != ProtocolUDP && opts.Protocol != ProtocolICMP { + res.Err = errValidProtocolRequired + return res + } + + type secrule struct { + Direction string `json:"direction"` + EtherType string `json:"ethertype"` + SecGroupID string `json:"security_group_id"` + PortRangeMax int `json:"port_range_max,omitempty"` + PortRangeMin int `json:"port_range_min,omitempty"` + Protocol string `json:"protocol,omitempty"` + RemoteGroupID string `json:"remote_group_id,omitempty"` + RemoteIPPrefix string `json:"remote_ip_prefix,omitempty"` + TenantID string `json:"tenant_id,omitempty"` + } + + type request struct { + SecRule secrule `json:"security_group_rule"` + } + + reqBody := request{SecRule: secrule{ + Direction: opts.Direction, + EtherType: opts.EtherType, + SecGroupID: opts.SecGroupID, + PortRangeMax: opts.PortRangeMax, + PortRangeMin: opts.PortRangeMin, + Protocol: opts.Protocol, + RemoteGroupID: opts.RemoteGroupID, + RemoteIPPrefix: opts.RemoteIPPrefix, + TenantID: opts.TenantID, + }} + + _, res.Err = c.Post(rootURL(c), reqBody, &res.Body, nil) + return res +} + +// Get retrieves a particular security group rule based on its unique ID. +func Get(c *gophercloud.ServiceClient, id string) GetResult { + var res GetResult + _, res.Err = c.Get(resourceURL(c, id), &res.Body, nil) + return res +} + +// Delete will permanently delete a particular security group rule based on its unique ID. +func Delete(c *gophercloud.ServiceClient, id string) DeleteResult { + var res DeleteResult + _, res.Err = c.Delete(resourceURL(c, id), nil) + return res +} diff --git a/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules/results.go b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules/results.go new file mode 100644 index 00000000000..6e138576893 --- /dev/null +++ b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules/results.go @@ -0,0 +1,133 @@ +package rules + +import ( + "github.com/mitchellh/mapstructure" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/pagination" +) + +// SecGroupRule represents a rule to dictate the behaviour of incoming or +// outgoing traffic for a particular security group. +type SecGroupRule struct { + // The UUID for this security group rule. + ID string + + // The direction in which the security group rule is applied. The only values + // allowed are "ingress" or "egress". For a compute instance, an ingress + // security group rule is applied to incoming (ingress) traffic for that + // instance. An egress rule is applied to traffic leaving the instance. + Direction string + + // Must be IPv4 or IPv6, and addresses represented in CIDR must match the + // ingress or egress rules. + EtherType string `json:"ethertype" mapstructure:"ethertype"` + + // The security group ID to associate with this security group rule. + SecGroupID string `json:"security_group_id" mapstructure:"security_group_id"` + + // The minimum port number in the range that is matched by the security group + // rule. If the protocol is TCP or UDP, this value must be less than or equal + // to the value of the PortRangeMax attribute. If the protocol is ICMP, this + // value must be an ICMP type. + PortRangeMin int `json:"port_range_min" mapstructure:"port_range_min"` + + // The maximum port number in the range that is matched by the security group + // rule. The PortRangeMin attribute constrains the PortRangeMax attribute. If + // the protocol is ICMP, this value must be an ICMP type. + PortRangeMax int `json:"port_range_max" mapstructure:"port_range_max"` + + // The protocol that is matched by the security group rule. Valid values are + // "tcp", "udp", "icmp" or an empty string. + Protocol string + + // The remote group ID to be associated with this security group rule. You + // can specify either RemoteGroupID or RemoteIPPrefix. + RemoteGroupID string `json:"remote_group_id" mapstructure:"remote_group_id"` + + // The remote IP prefix to be associated with this security group rule. You + // can specify either RemoteGroupID or RemoteIPPrefix . This attribute + // matches the specified IP prefix as the source IP address of the IP packet. + RemoteIPPrefix string `json:"remote_ip_prefix" mapstructure:"remote_ip_prefix"` + + // The owner of this security group rule. + TenantID string `json:"tenant_id" mapstructure:"tenant_id"` +} + +// SecGroupRulePage is the page returned by a pager when traversing over a +// collection of security group rules. +type SecGroupRulePage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of security group rules has +// reached the end of a page and the pager seeks to traverse over a new one. In +// order to do this, it needs to construct the next page's URL. +func (p SecGroupRulePage) NextPageURL() (string, error) { + type resp struct { + Links []gophercloud.Link `mapstructure:"security_group_rules_links"` + } + + var r resp + err := mapstructure.Decode(p.Body, &r) + if err != nil { + return "", err + } + + return gophercloud.ExtractNextURL(r.Links) +} + +// IsEmpty checks whether a SecGroupRulePage struct is empty. +func (p SecGroupRulePage) IsEmpty() (bool, error) { + is, err := ExtractRules(p) + if err != nil { + return true, nil + } + return len(is) == 0, nil +} + +// ExtractRules accepts a Page struct, specifically a SecGroupRulePage struct, +// and extracts the elements into a slice of SecGroupRule structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractRules(page pagination.Page) ([]SecGroupRule, error) { + var resp struct { + SecGroupRules []SecGroupRule `mapstructure:"security_group_rules" json:"security_group_rules"` + } + + err := mapstructure.Decode(page.(SecGroupRulePage).Body, &resp) + + return resp.SecGroupRules, err +} + +type commonResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts a security rule. +func (r commonResult) Extract() (*SecGroupRule, error) { + if r.Err != nil { + return nil, r.Err + } + + var res struct { + SecGroupRule *SecGroupRule `mapstructure:"security_group_rule" json:"security_group_rule"` + } + + err := mapstructure.Decode(r.Body, &res) + + return res.SecGroupRule, err +} + +// CreateResult represents the result of a create operation. +type CreateResult struct { + commonResult +} + +// GetResult represents the result of a get operation. +type GetResult struct { + commonResult +} + +// DeleteResult represents the result of a delete operation. +type DeleteResult struct { + gophercloud.ErrResult +} diff --git a/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules/urls.go b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules/urls.go new file mode 100644 index 00000000000..8e2b2bb28d2 --- /dev/null +++ b/vendor/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/security/rules/urls.go @@ -0,0 +1,13 @@ +package rules + +import "github.com/rackspace/gophercloud" + +const rootPath = "security-group-rules" + +func rootURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(rootPath) +} + +func resourceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(rootPath, id) +}