Adding more documentation and tweaking operations
diff --git a/openstack/networking/v2/extensions/layer3/doc.go b/openstack/networking/v2/extensions/layer3/doc.go
new file mode 100644
index 0000000..d533458
--- /dev/null
+++ b/openstack/networking/v2/extensions/layer3/doc.go
@@ -0,0 +1,5 @@
+// Package layer3 provides access to the Layer-3 networking extension for the
+// OpenStack Neutron service. This extension allows API users to route packets
+// between subnets, forward packets from internal networks to external ones,
+// and access instances from external networks through floating IPs.
+package layer3
diff --git a/openstack/networking/v2/extensions/layer3/floatingips/doc.go b/openstack/networking/v2/extensions/layer3/floatingips/doc.go
deleted file mode 100644
index 6648963..0000000
--- a/openstack/networking/v2/extensions/layer3/floatingips/doc.go
+++ /dev/null
@@ -1 +0,0 @@
-package floatingips
diff --git a/openstack/networking/v2/extensions/layer3/floatingips/requests.go b/openstack/networking/v2/extensions/layer3/floatingips/requests.go
index 151591a..3ec3899 100644
--- a/openstack/networking/v2/extensions/layer3/floatingips/requests.go
+++ b/openstack/networking/v2/extensions/layer3/floatingips/requests.go
@@ -2,13 +2,77 @@
import (
"fmt"
+ "strconv"
"github.com/racker/perigee"
"github.com/rackspace/gophercloud"
+ "github.com/rackspace/gophercloud/openstack/utils"
+ "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
+ FloatingNetworkID string
+ PortID string
+ FixedIP string
+ FloatingIP string
+ TenantID string
+ Limit int
+ Marker string
+ SortKey string
+ SortDir string
+}
+
+// List returns a Pager which allows you to iterate over a collection of
+// floating IP resources. 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 := make(map[string]string)
+ if opts.ID != "" {
+ q["id"] = opts.ID
+ }
+ if opts.FloatingNetworkID != "" {
+ q["floating_network_id"] = opts.FloatingNetworkID
+ }
+ if opts.FixedIP != "" {
+ q["fixed_ip_address"] = opts.FixedIP
+ }
+ if opts.FloatingIP != "" {
+ q["floating_ip_address"] = opts.FloatingIP
+ }
+ if opts.TenantID != "" {
+ q["tenant_id"] = opts.TenantID
+ }
+ if opts.Marker != "" {
+ q["marker"] = opts.Marker
+ }
+ if opts.Limit != 0 {
+ q["limit"] = strconv.Itoa(opts.Limit)
+ }
+ if opts.SortKey != "" {
+ q["sort_key"] = opts.SortKey
+ }
+ if opts.SortDir != "" {
+ q["sort_dir"] = opts.SortDir
+ }
+
+ u := rootURL(c) + utils.BuildQuery(q)
+ return pagination.NewPager(c, u, func(r pagination.LastHTTPResponse) pagination.Page {
+ return FloatingIPPage{pagination.LinkedPageBase(r)}
+ })
+}
+
+// CreateOpts contains all the values needed to create a new floating IP
+// resource. The only required fields are FloatingNetworkID and PortID which
+// refer to the external network and internal port respectively.
type CreateOpts struct {
FloatingNetworkID string
+ FloatingIP string
PortID string
FixedIP string
TenantID string
@@ -19,6 +83,30 @@
errPortIDRequired = fmt.Errorf("A PortID is required")
)
+// Create accepts a CreateOpts struct and uses the values provided to create a
+// new floating IP resource. You can create floating IPs on external networks
+// only. If you provide a FloatingNetworkID which refers to a network that is
+// not external (i.e. its `router:external' attribute is False), the operation
+// will fail and return a 400 error.
+//
+// If you do not specify a FloatingIP address value, the operation will
+// automatically allocate an available address for the new resource. If you do
+// choose to specify one, it must fall within the subnet range for the external
+// network - otherwise the operation returns a 400 error. If the FloatingIP
+// address is already in use, the operation returns a 409 error code.
+//
+// You can associate the new resource with an internal port by using the PortID
+// field. If you specify a PortID that is not valid, the operation will fail and
+// return 404 error code.
+//
+// You must also configure an IP address for the port associated with the PortID
+// you have provided - this is what the FixedIP refers to: an IP fixed to a port.
+// Because a port might be associated with multiple IP addresses, you can use
+// the FixedIP field to associate a particular IP address rather than have the
+// API assume for you. If you specify an IP address that is not valid, the
+// operation will fail and return a 400 error code. If the PortID and FixedIP
+// are already associated with another resource, the operation will fail and
+// returns a 409 error code.
func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult {
var res CreateResult
@@ -35,6 +123,7 @@
// Define structures
type floatingIP struct {
FloatingNetworkID string `json:"floating_network_id"`
+ FloatingIP string `json:"floating_ip_address,omitempty"`
PortID string `json:"port_id"`
FixedIP string `json:"fixed_ip_address,omitempty"`
TenantID string `json:"tenant_id,omitempty"`
@@ -64,6 +153,7 @@
return res
}
+// Get retrieves a particular floating IP resource based on its unique ID.
func Get(c *gophercloud.ServiceClient, id string) GetResult {
var res GetResult
_, err := perigee.Request("GET", resourceURL(c, id), perigee.Options{
@@ -75,10 +165,18 @@
return res
}
+// UpdateOpts contains the values used when updating a floating IP resource. The
+// only value that can be updated is which internal port the floating IP is
+// linked to. To associate the floating IP with a new internal port, provide its
+// ID. To disassociate the floating IP from all ports, provide an empty string.
type UpdateOpts struct {
PortID string
}
+// Update allows floating IP resources to be updated. Currently, the only way to
+// "update" a floating IP is to associate it with a new internal port, or
+// disassociated it from all ports. See UpdateOpts for instructions of how to
+// do this.
func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResult {
type floatingIP struct {
PortID *string `json:"port_id"`
@@ -109,6 +207,9 @@
return res
}
+// Delete will permanently delete a particular floating IP resource. Please
+// ensure this is what you want - you can also disassociate the IP from existing
+// internal ports.
func Delete(c *gophercloud.ServiceClient, id string) DeleteResult {
var res DeleteResult
_, err := perigee.Request("DELETE", resourceURL(c, id), perigee.Options{
diff --git a/openstack/networking/v2/extensions/layer3/floatingips/requests_test.go b/openstack/networking/v2/extensions/layer3/floatingips/requests_test.go
index f03d5ea..a9739ba 100644
--- a/openstack/networking/v2/extensions/layer3/floatingips/requests_test.go
+++ b/openstack/networking/v2/extensions/layer3/floatingips/requests_test.go
@@ -6,6 +6,7 @@
"testing"
"github.com/rackspace/gophercloud"
+ "github.com/rackspace/gophercloud/pagination"
th "github.com/rackspace/gophercloud/testhelper"
)
@@ -18,6 +19,86 @@
}
}
+func TestList(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/v2.0/floatingips", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "X-Auth-Token", tokenID)
+
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+
+ fmt.Fprintf(w, `
+{
+ "floatingips": [
+ {
+ "floating_network_id": "6d67c30a-ddb4-49a1-bec3-a65b286b4170",
+ "router_id": null,
+ "fixed_ip_address": null,
+ "floating_ip_address": "192.0.0.4",
+ "tenant_id": "017d8de156df4177889f31a9bd6edc00",
+ "status": "DOWN",
+ "port_id": null,
+ "id": "2f95fd2b-9f6a-4e8e-9e9a-2cbe286cbf9e"
+ },
+ {
+ "floating_network_id": "90f742b1-6d17-487b-ba95-71881dbc0b64",
+ "router_id": "0a24cb83-faf5-4d7f-b723-3144ed8a2167",
+ "fixed_ip_address": "192.0.0.2",
+ "floating_ip_address": "10.0.0.3",
+ "tenant_id": "017d8de156df4177889f31a9bd6edc00",
+ "status": "DOWN",
+ "port_id": "74a342ce-8e07-4e91-880c-9f834b68fa25",
+ "id": "ada25a95-f321-4f59-b0e0-f3a970dd3d63"
+ }
+ ]
+}
+ `)
+ })
+
+ count := 0
+
+ List(serviceClient(), ListOpts{}).EachPage(func(page pagination.Page) (bool, error) {
+ count++
+ actual, err := ExtractFloatingIPs(page)
+ if err != nil {
+ t.Errorf("Failed to extract floating IPs: %v", err)
+ return false, err
+ }
+
+ expected := []FloatingIP{
+ FloatingIP{
+ FloatingNetworkID: "6d67c30a-ddb4-49a1-bec3-a65b286b4170",
+ FixedIP: "",
+ FloatingIP: "192.0.0.4",
+ TenantID: "017d8de156df4177889f31a9bd6edc00",
+ Status: "DOWN",
+ PortID: "",
+ ID: "2f95fd2b-9f6a-4e8e-9e9a-2cbe286cbf9e",
+ },
+ FloatingIP{
+ FloatingNetworkID: "90f742b1-6d17-487b-ba95-71881dbc0b64",
+ FixedIP: "192.0.0.2",
+ FloatingIP: "10.0.0.3",
+ TenantID: "017d8de156df4177889f31a9bd6edc00",
+ Status: "DOWN",
+ PortID: "74a342ce-8e07-4e91-880c-9f834b68fa25",
+ ID: "ada25a95-f321-4f59-b0e0-f3a970dd3d63",
+ },
+ }
+
+ th.CheckDeepEquals(t, expected, actual)
+
+ return true, nil
+ })
+
+ if count != 1 {
+ t.Errorf("Expected 1 page, got %d", count)
+ }
+}
+
func TestCreate(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
diff --git a/openstack/networking/v2/extensions/layer3/floatingips/results.go b/openstack/networking/v2/extensions/layer3/floatingips/results.go
index a09f1d4..f9c7a08 100644
--- a/openstack/networking/v2/extensions/layer3/floatingips/results.go
+++ b/openstack/networking/v2/extensions/layer3/floatingips/results.go
@@ -5,22 +5,45 @@
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
+ "github.com/rackspace/gophercloud/pagination"
)
+// FloatingIP represents a floating IP resource. A floating IP is an external
+// IP address that is mapped to an internal port and, optionally, a specific
+// IP address on a private network. In other words, it enables access to an
+// instance on a private network from an external network. For thsi reason,
+// floating IPs can only be defined on networks where the `router:external'
+// attribute (provided by the external network extension) is set to True.
type FloatingIP struct {
- ID string `json:"id" mapstructure:"id"`
+ // Unique identifier for the floating IP instance.
+ ID string `json:"id" mapstructure:"id"`
+
+ // UUID of the external network where the floating IP is to be created.
FloatingNetworkID string `json:"floating_network_id" mapstructure:"floating_network_id"`
- FloatingIP string `json:"floating_ip_address" mapstructure:"floating_ip_address"`
- PortID string `json:"port_id" mapstructure:"port_id"`
- FixedIP string `json:"fixed_ip_address" mapstructure:"fixed_ip_address"`
- TenantID string `json:"tenant_id" mapstructure:"tenant_id"`
- Status string `json:"status" mapstructure:"status"`
+
+ // Address of the floating IP on the external network.
+ FloatingIP string `json:"floating_ip_address" mapstructure:"floating_ip_address"`
+
+ // UUID of the port on an internal network that is associated with the floating IP.
+ PortID string `json:"port_id" mapstructure:"port_id"`
+
+ // The specific IP address of the internal port which should be associated
+ // with the floating IP.
+ FixedIP string `json:"fixed_ip_address" mapstructure:"fixed_ip_address"`
+
+ // Owner of the floating IP. Only admin users can specify a tenant identifier
+ // other than its own.
+ TenantID string `json:"tenant_id" mapstructure:"tenant_id"`
+
+ // The condition of the API resource.
+ Status string `json:"status" mapstructure:"status"`
}
type commonResult struct {
gophercloud.CommonResult
}
+// Extract a result and extracts a FloatingIP resource.
func (r commonResult) Extract() (*FloatingIP, error) {
if r.Err != nil {
return nil, r.Err
@@ -53,4 +76,58 @@
commonResult
}
+// DeleteResult represents the result of an update operation.
type DeleteResult commonResult
+
+type FloatingIPPage struct {
+ pagination.LinkedPageBase
+}
+
+func (p FloatingIPPage) NextPageURL() (string, error) {
+ type link struct {
+ Href string `mapstructure:"href"`
+ Rel string `mapstructure:"rel"`
+ }
+ type resp struct {
+ Links []link `mapstructure:"floatingips_links"`
+ }
+
+ var r resp
+ err := mapstructure.Decode(p.Body, &r)
+ if err != nil {
+ return "", err
+ }
+
+ var url string
+ for _, l := range r.Links {
+ if l.Rel == "next" {
+ url = l.Href
+ }
+ }
+ if url == "" {
+ return "", nil
+ }
+
+ return url, nil
+}
+
+func (p FloatingIPPage) IsEmpty() (bool, error) {
+ is, err := ExtractFloatingIPs(p)
+ if err != nil {
+ return true, nil
+ }
+ return len(is) == 0, nil
+}
+
+func ExtractFloatingIPs(page pagination.Page) ([]FloatingIP, error) {
+ var resp struct {
+ FloatingIPs []FloatingIP `mapstructure:"floatingips" json:"floatingips"`
+ }
+
+ err := mapstructure.Decode(page.(FloatingIPPage).Body, &resp)
+ if err != nil {
+ return nil, err
+ }
+
+ return resp.FloatingIPs, nil
+}
diff --git a/openstack/networking/v2/extensions/layer3/routers/doc.go b/openstack/networking/v2/extensions/layer3/routers/doc.go
deleted file mode 100755
index 159906f..0000000
--- a/openstack/networking/v2/extensions/layer3/routers/doc.go
+++ /dev/null
@@ -1 +0,0 @@
-package routers
diff --git a/openstack/networking/v2/extensions/provider/results.go b/openstack/networking/v2/extensions/provider/results.go
index 09fdff6..11b0604 100755
--- a/openstack/networking/v2/extensions/provider/results.go
+++ b/openstack/networking/v2/extensions/provider/results.go
@@ -51,6 +51,8 @@
SegmentationID string `json:"provider:segmentation_id" mapstructure:"provider:segmentation_id"`
}
+// ExtractGet decorates a GetResult struct returned from a networks.Get()
+// function with extended attributes.
func ExtractGet(r networks.GetResult) (*NetworkExtAttrs, error) {
if r.Err != nil {
return nil, r.Err
@@ -65,6 +67,8 @@
return res.Network, nil
}
+// ExtractGet decorates a CreateResult struct returned from a networks.Create()
+// function with extended attributes.
func ExtractCreate(r networks.CreateResult) (*NetworkExtAttrs, error) {
if r.Err != nil {
return nil, r.Err
@@ -79,6 +83,8 @@
return res.Network, nil
}
+// ExtractUpdate decorates a UpdateResult struct returned from a
+// networks.Update() function with extended attributes.
func ExtractUpdate(r networks.UpdateResult) (*NetworkExtAttrs, error) {
if r.Err != nil {
return nil, r.Err