os-networks extension
This commit adds the os-networks extention. This can be used to view
details about the nova-network-based networks that a tenant has access
to.
diff --git a/openstack/compute/v2/extensions/networks/doc.go b/openstack/compute/v2/extensions/networks/doc.go
new file mode 100644
index 0000000..fafe4a0
--- /dev/null
+++ b/openstack/compute/v2/extensions/networks/doc.go
@@ -0,0 +1,2 @@
+// Package network provides the ability to manage nova-networks
+package networks
diff --git a/openstack/compute/v2/extensions/networks/fixtures.go b/openstack/compute/v2/extensions/networks/fixtures.go
new file mode 100644
index 0000000..12b9485
--- /dev/null
+++ b/openstack/compute/v2/extensions/networks/fixtures.go
@@ -0,0 +1,209 @@
+// +build fixtures
+
+package networks
+
+import (
+ "fmt"
+ "net/http"
+ "testing"
+ "time"
+
+ th "github.com/rackspace/gophercloud/testhelper"
+ "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+// ListOutput is a sample response to a List call.
+const ListOutput = `
+{
+ "networks": [
+ {
+ "bridge": "br100",
+ "bridge_interface": "eth0",
+ "broadcast": "10.0.0.7",
+ "cidr": "10.0.0.0/29",
+ "cidr_v6": null,
+ "created_at": "2011-08-15 06:19:19.387525",
+ "deleted": false,
+ "deleted_at": null,
+ "dhcp_start": "10.0.0.3",
+ "dns1": null,
+ "dns2": null,
+ "gateway": "10.0.0.1",
+ "gateway_v6": null,
+ "host": "nsokolov-desktop",
+ "id": "20c8acc0-f747-4d71-a389-46d078ebf047",
+ "injected": false,
+ "label": "mynet_0",
+ "multi_host": false,
+ "netmask": "255.255.255.248",
+ "netmask_v6": null,
+ "priority": null,
+ "project_id": "1234",
+ "rxtx_base": null,
+ "updated_at": "2011-08-16 09:26:13.048257",
+ "vlan": 100,
+ "vpn_private_address": "10.0.0.2",
+ "vpn_public_address": "127.0.0.1",
+ "vpn_public_port": 1000
+ },
+ {
+ "bridge": "br101",
+ "bridge_interface": "eth0",
+ "broadcast": "10.0.0.15",
+ "cidr": "10.0.0.10/29",
+ "cidr_v6": null,
+ "created_at": "2011-08-15 06:19:19.885495",
+ "deleted": false,
+ "deleted_at": null,
+ "dhcp_start": "10.0.0.11",
+ "dns1": null,
+ "dns2": null,
+ "gateway": "10.0.0.9",
+ "gateway_v6": null,
+ "host": null,
+ "id": "20c8acc0-f747-4d71-a389-46d078ebf000",
+ "injected": false,
+ "label": "mynet_1",
+ "multi_host": false,
+ "netmask": "255.255.255.248",
+ "netmask_v6": null,
+ "priority": null,
+ "project_id": null,
+ "rxtx_base": null,
+ "updated_at": null,
+ "vlan": 101,
+ "vpn_private_address": "10.0.0.10",
+ "vpn_public_address": null,
+ "vpn_public_port": 1001
+ }
+ ]
+}
+`
+
+// GetOutput is a sample response to a Get call.
+const GetOutput = `
+{
+ "network": {
+ "bridge": "br101",
+ "bridge_interface": "eth0",
+ "broadcast": "10.0.0.15",
+ "cidr": "10.0.0.10/29",
+ "cidr_v6": null,
+ "created_at": "2011-08-15 06:19:19.885495",
+ "deleted": false,
+ "deleted_at": null,
+ "dhcp_start": "10.0.0.11",
+ "dns1": null,
+ "dns2": null,
+ "gateway": "10.0.0.9",
+ "gateway_v6": null,
+ "host": null,
+ "id": "20c8acc0-f747-4d71-a389-46d078ebf000",
+ "injected": false,
+ "label": "mynet_1",
+ "multi_host": false,
+ "netmask": "255.255.255.248",
+ "netmask_v6": null,
+ "priority": null,
+ "project_id": null,
+ "rxtx_base": null,
+ "updated_at": null,
+ "vlan": 101,
+ "vpn_private_address": "10.0.0.10",
+ "vpn_public_address": null,
+ "vpn_public_port": 1001
+ }
+}
+`
+
+// FirstNetwork is the first result in ListOutput.
+var nilTime time.Time
+var FirstNetwork = Network{
+ Bridge: "br100",
+ BridgeInterface: "eth0",
+ Broadcast: "10.0.0.7",
+ CIDR: "10.0.0.0/29",
+ CIDRv6: "",
+ CreatedAt: time.Date(2011, 8, 15, 6, 19, 19, 387525000, time.UTC),
+ Deleted: false,
+ DeletedAt: nilTime,
+ DHCPStart: "10.0.0.3",
+ DNS1: "",
+ DNS2: "",
+ Gateway: "10.0.0.1",
+ Gatewayv6: "",
+ Host: "nsokolov-desktop",
+ ID: "20c8acc0-f747-4d71-a389-46d078ebf047",
+ Injected: false,
+ Label: "mynet_0",
+ MultiHost: false,
+ Netmask: "255.255.255.248",
+ Netmaskv6: "",
+ Priority: 0,
+ ProjectID: "1234",
+ RXTXBase: 0,
+ UpdatedAt: time.Date(2011, 8, 16, 9, 26, 13, 48257000, time.UTC),
+ VLAN: 100,
+ VPNPrivateAddress: "10.0.0.2",
+ VPNPublicAddress: "127.0.0.1",
+ VPNPublicPort: 1000,
+}
+
+// SecondNetwork is the second result in ListOutput.
+var SecondNetwork = Network{
+ Bridge: "br101",
+ BridgeInterface: "eth0",
+ Broadcast: "10.0.0.15",
+ CIDR: "10.0.0.10/29",
+ CIDRv6: "",
+ CreatedAt: time.Date(2011, 8, 15, 6, 19, 19, 885495000, time.UTC),
+ Deleted: false,
+ DeletedAt: nilTime,
+ DHCPStart: "10.0.0.11",
+ DNS1: "",
+ DNS2: "",
+ Gateway: "10.0.0.9",
+ Gatewayv6: "",
+ Host: "",
+ ID: "20c8acc0-f747-4d71-a389-46d078ebf000",
+ Injected: false,
+ Label: "mynet_1",
+ MultiHost: false,
+ Netmask: "255.255.255.248",
+ Netmaskv6: "",
+ Priority: 0,
+ ProjectID: "",
+ RXTXBase: 0,
+ UpdatedAt: nilTime,
+ VLAN: 101,
+ VPNPrivateAddress: "10.0.0.10",
+ VPNPublicAddress: "",
+ VPNPublicPort: 1001,
+}
+
+// ExpectedNetworkSlice is the slice of results that should be parsed
+// from ListOutput, in the expected order.
+var ExpectedNetworkSlice = []Network{FirstNetwork, SecondNetwork}
+
+// HandleListSuccessfully configures the test server to respond to a List request.
+func HandleListSuccessfully(t *testing.T) {
+ th.Mux.HandleFunc("/os-networks", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+
+ w.Header().Add("Content-Type", "application/json")
+ fmt.Fprintf(w, ListOutput)
+ })
+}
+
+// HandleGetSuccessfully configures the test server to respond to a Get request
+// for an existing network.
+func HandleGetSuccessfully(t *testing.T) {
+ th.Mux.HandleFunc("/os-networks/20c8acc0-f747-4d71-a389-46d078ebf000", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "GET")
+ th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+
+ w.Header().Add("Content-Type", "application/json")
+ fmt.Fprintf(w, GetOutput)
+ })
+}
diff --git a/openstack/compute/v2/extensions/networks/requests.go b/openstack/compute/v2/extensions/networks/requests.go
new file mode 100644
index 0000000..eb20387
--- /dev/null
+++ b/openstack/compute/v2/extensions/networks/requests.go
@@ -0,0 +1,22 @@
+package networks
+
+import (
+ "github.com/rackspace/gophercloud"
+ "github.com/rackspace/gophercloud/pagination"
+)
+
+// List returns a Pager that allows you to iterate over a collection of Network.
+func List(client *gophercloud.ServiceClient) pagination.Pager {
+ url := listURL(client)
+ createPage := func(r pagination.PageResult) pagination.Page {
+ return NetworkPage{pagination.SinglePageBase(r)}
+ }
+ return pagination.NewPager(client, url, createPage)
+}
+
+// Get returns data about a previously created Network.
+func Get(client *gophercloud.ServiceClient, id string) GetResult {
+ var res GetResult
+ _, res.Err = client.Get(getURL(client, id), &res.Body, nil)
+ return res
+}
diff --git a/openstack/compute/v2/extensions/networks/requests_test.go b/openstack/compute/v2/extensions/networks/requests_test.go
new file mode 100644
index 0000000..722b3f0
--- /dev/null
+++ b/openstack/compute/v2/extensions/networks/requests_test.go
@@ -0,0 +1,37 @@
+package networks
+
+import (
+ "testing"
+
+ "github.com/rackspace/gophercloud/pagination"
+ th "github.com/rackspace/gophercloud/testhelper"
+ "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+func TestList(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+ HandleListSuccessfully(t)
+
+ count := 0
+ err := List(client.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) {
+ count++
+ actual, err := ExtractNetworks(page)
+ th.AssertNoErr(t, err)
+ th.CheckDeepEquals(t, ExpectedNetworkSlice, actual)
+
+ return true, nil
+ })
+ th.AssertNoErr(t, err)
+ th.CheckEquals(t, 1, count)
+}
+
+func TestGet(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+ HandleGetSuccessfully(t)
+
+ actual, err := Get(client.ServiceClient(), "20c8acc0-f747-4d71-a389-46d078ebf000").Extract()
+ th.AssertNoErr(t, err)
+ th.CheckDeepEquals(t, &SecondNetwork, actual)
+}
diff --git a/openstack/compute/v2/extensions/networks/results.go b/openstack/compute/v2/extensions/networks/results.go
new file mode 100644
index 0000000..55b361d
--- /dev/null
+++ b/openstack/compute/v2/extensions/networks/results.go
@@ -0,0 +1,222 @@
+package networks
+
+import (
+ "fmt"
+ "time"
+
+ "github.com/mitchellh/mapstructure"
+ "github.com/rackspace/gophercloud"
+ "github.com/rackspace/gophercloud/pagination"
+)
+
+// A Network represents a nova-network that an instance communicates on
+type Network struct {
+ // The Bridge that VIFs on this network are connected to
+ Bridge string `mapstructure:"bridge"`
+
+ // BridgeInterface is what interface is connected to the Bridge
+ BridgeInterface string `mapstructure:"bridge_interface"`
+
+ // The Broadcast address of the network.
+ Broadcast string `mapstructure:"broadcast"`
+
+ // CIDR is the IPv4 subnet.
+ CIDR string `mapstructure:"cidr"`
+
+ // CIDRv6 is the IPv6 subnet.
+ CIDRv6 string `mapstructure:"cidr_v6"`
+
+ // CreatedAt is when the network was created..
+ CreatedAt time.Time `mapstructure:"-"`
+
+ // Deleted shows if the network has been deleted.
+ Deleted bool `mapstructure:"deleted"`
+
+ // DeletedAt is the time when the network was deleted.
+ DeletedAt time.Time `mapstructure:"-"`
+
+ // DHCPStart is the start of the DHCP address range.
+ DHCPStart string `mapstructure:"dhcp_start"`
+
+ // DNS1 is the first DNS server to use through DHCP.
+ DNS1 string `mapstructure:"dns_1"`
+
+ // DNS2 is the first DNS server to use through DHCP.
+ DNS2 string `mapstructure:"dns_2"`
+
+ // Gateway is the network gateway.
+ Gateway string `mapstructure:"gateway"`
+
+ // Gatewayv6 is the IPv6 network gateway.
+ Gatewayv6 string `mapstructure:"gateway_v6"`
+
+ // Host is the host that the network service is running on.
+ Host string `mapstructure:"host"`
+
+ // ID is the UUID of the network.
+ ID string `mapstructure:"id"`
+
+ // Injected determines if network information is injected into the host.
+ Injected bool `mapstructure:"injected"`
+
+ // Label is the common name that the network has..
+ Label string `mapstructure:"label"`
+
+ // MultiHost is if multi-host networking is enablec..
+ MultiHost bool `mapstructure:"multi_host"`
+
+ // Netmask is the network netmask.
+ Netmask string `mapstructure:"netmask"`
+
+ // Netmaskv6 is the IPv6 netmask.
+ Netmaskv6 string `mapstructure:"netmask_v6"`
+
+ // Priority is the network interface priority.
+ Priority int `mapstructure:"priority"`
+
+ // ProjectID is the project associated with this network.
+ ProjectID string `mapstructure:"project_id"`
+
+ // RXTXBase configures bandwidth entitlement.
+ RXTXBase int `mapstructure:"rxtx_base"`
+
+ // UpdatedAt is the time when the network was last updated.
+ UpdatedAt time.Time `mapstructure:"-"`
+
+ // VLAN is the vlan this network runs on.
+ VLAN int `mapstructure:"vlan"`
+
+ // VPNPrivateAddress is the private address of the CloudPipe VPN.
+ VPNPrivateAddress string `mapstructure:"vpn_private_address"`
+
+ // VPNPublicAddress is the public address of the CloudPipe VPN.
+ VPNPublicAddress string `mapstructure:"vpn_public_address"`
+
+ // VPNPublicPort is the port of the CloudPipe VPN.
+ VPNPublicPort int `mapstructure:"vpn_public_port"`
+}
+
+// NetworkPage stores a single, only page of Networks
+// results from a List call.
+type NetworkPage struct {
+ pagination.SinglePageBase
+}
+
+// IsEmpty determines whether or not a NetworkPage is empty.
+func (page NetworkPage) IsEmpty() (bool, error) {
+ va, err := ExtractNetworks(page)
+ return len(va) == 0, err
+}
+
+// ExtractNetworks interprets a page of results as a slice of Networks
+func ExtractNetworks(page pagination.Page) ([]Network, error) {
+ var res struct {
+ Networks []Network `mapstructure:"networks"`
+ }
+
+ err := mapstructure.Decode(page.(NetworkPage).Body, &res)
+
+ var rawNetworks []interface{}
+ body := page.(NetworkPage).Body
+ switch body.(type) {
+ case map[string]interface{}:
+ rawNetworks = body.(map[string]interface{})["networks"].([]interface{})
+ case map[string][]interface{}:
+ rawNetworks = body.(map[string][]interface{})["networks"]
+ default:
+ return res.Networks, fmt.Errorf("Unknown type")
+ }
+
+ for i := range rawNetworks {
+ thisNetwork := rawNetworks[i].(map[string]interface{})
+ if t, ok := thisNetwork["created_at"].(string); ok && t != "" {
+ createdAt, err := time.Parse("2006-01-02 15:04:05.000000", t)
+ if err != nil {
+ return res.Networks, err
+ }
+ res.Networks[i].CreatedAt = createdAt
+ }
+
+ if t, ok := thisNetwork["updated_at"].(string); ok && t != "" {
+ updatedAt, err := time.Parse("2006-01-02 15:04:05.000000", t)
+ if err != nil {
+ return res.Networks, err
+ }
+ res.Networks[i].UpdatedAt = updatedAt
+ }
+
+ if t, ok := thisNetwork["deleted_at"].(string); ok && t != "" {
+ deletedAt, err := time.Parse("2006-01-02 15:04:05.000000", t)
+ if err != nil {
+ return res.Networks, err
+ }
+ res.Networks[i].DeletedAt = deletedAt
+ }
+ }
+
+ return res.Networks, err
+}
+
+type NetworkResult struct {
+ gophercloud.Result
+}
+
+// Extract is a method that attempts to interpret any Network resource
+// response as a Network struct.
+func (r NetworkResult) Extract() (*Network, error) {
+ if r.Err != nil {
+ return nil, r.Err
+ }
+
+ var res struct {
+ Network *Network `json:"network" mapstructure:"network"`
+ }
+
+ config := &mapstructure.DecoderConfig{
+ Result: &res,
+ WeaklyTypedInput: true,
+ }
+ decoder, err := mapstructure.NewDecoder(config)
+ if err != nil {
+ return nil, err
+ }
+
+ if err := decoder.Decode(r.Body); err != nil {
+ return nil, err
+ }
+
+ b := r.Body.(map[string]interface{})["network"].(map[string]interface{})
+
+ if t, ok := b["created_at"].(string); ok && t != "" {
+ createdAt, err := time.Parse("2006-01-02 15:04:05.000000", t)
+ if err != nil {
+ return res.Network, err
+ }
+ res.Network.CreatedAt = createdAt
+ }
+
+ if t, ok := b["updated_at"].(string); ok && t != "" {
+ updatedAt, err := time.Parse("2006-01-02 15:04:05.000000", t)
+ if err != nil {
+ return res.Network, err
+ }
+ res.Network.UpdatedAt = updatedAt
+ }
+
+ if t, ok := b["deleted_at"].(string); ok && t != "" {
+ deletedAt, err := time.Parse("2006-01-02 15:04:05.000000", t)
+ if err != nil {
+ return res.Network, err
+ }
+ res.Network.DeletedAt = deletedAt
+ }
+
+ return res.Network, err
+
+}
+
+// GetResult is the response from a Get operation. Call its Extract method to interpret it
+// as a Network.
+type GetResult struct {
+ NetworkResult
+}
diff --git a/openstack/compute/v2/extensions/networks/urls.go b/openstack/compute/v2/extensions/networks/urls.go
new file mode 100644
index 0000000..6966462
--- /dev/null
+++ b/openstack/compute/v2/extensions/networks/urls.go
@@ -0,0 +1,17 @@
+package networks
+
+import "github.com/rackspace/gophercloud"
+
+const resourcePath = "os-networks"
+
+func resourceURL(c *gophercloud.ServiceClient) string {
+ return c.ServiceURL(resourcePath)
+}
+
+func listURL(c *gophercloud.ServiceClient) string {
+ return resourceURL(c)
+}
+
+func getURL(c *gophercloud.ServiceClient, id string) string {
+ return c.ServiceURL(resourcePath, id)
+}
diff --git a/openstack/compute/v2/extensions/networks/urls_test.go b/openstack/compute/v2/extensions/networks/urls_test.go
new file mode 100644
index 0000000..be54c90
--- /dev/null
+++ b/openstack/compute/v2/extensions/networks/urls_test.go
@@ -0,0 +1,25 @@
+package networks
+
+import (
+ "testing"
+
+ th "github.com/rackspace/gophercloud/testhelper"
+ "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+func TestListURL(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+ c := client.ServiceClient()
+
+ th.CheckEquals(t, c.Endpoint+"os-networks", listURL(c))
+}
+
+func TestGetURL(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+ c := client.ServiceClient()
+ id := "1"
+
+ th.CheckEquals(t, c.Endpoint+"os-networks/"+id, getURL(c, id))
+}