Merge pull request #31921 from grahamhayes/openstack-loadbalancer-security-groups

Automatic merge from submit-queue

Security Group support for OpenStack Load Balancers

<!--  Thanks for sending a pull request!  Here are some tips for you:
1. If this is your first time, read our contributor guidelines https://github.com/kubernetes/kubernetes/blob/master/CONTRIBUTING.md and developer guide https://github.com/kubernetes/kubernetes/blob/master/docs/devel/development.md
2. If you want *faster* PR reviews, read how: https://github.com/kubernetes/kubernetes/blob/master/docs/devel/faster_reviews.md
3. Follow the instructions for writing a release note: https://github.com/kubernetes/kubernetes/blob/master/docs/devel/pull-requests.md#release-notes
-->

**Add Security Group Support for OpenStack Load Balancers**:

fixes #29745
adds OpenStack support to the work done in #20392

**Release note**:

```
This allows security groups to be created and attached to the neutron
port that the load balancer is using on the subnet.

The security group ID that is assigned to the nodes needs to be
provided, to allow for traffic from the load balancer to the nodePort
to be reflected in the rules.

This adds two config items to the LoadBalancer options -

ManageSecurityGroups (bool)
NodeSecurityGroupID  (string)
```
This commit is contained in:
Kubernetes Submit Queue 2016-10-14 02:29:59 -07:00 committed by GitHub
commit 95ccabdf46
10 changed files with 1242 additions and 31 deletions

10
Godeps/Godeps.json generated
View File

@ -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",

398
Godeps/LICENSES generated
View File

@ -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: =

View File

@ -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.

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}