Beginning acceptance tests
diff --git a/openstack/networking/v2/extensions/external/doc.go b/openstack/networking/v2/extensions/external/doc.go
new file mode 100755
index 0000000..d244f26
--- /dev/null
+++ b/openstack/networking/v2/extensions/external/doc.go
@@ -0,0 +1 @@
+package external
diff --git a/openstack/networking/v2/extensions/external/results.go b/openstack/networking/v2/extensions/external/results.go
new file mode 100644
index 0000000..4cd2133
--- /dev/null
+++ b/openstack/networking/v2/extensions/external/results.go
@@ -0,0 +1,102 @@
+package external
+
+import (
+ "fmt"
+
+ "github.com/mitchellh/mapstructure"
+ "github.com/rackspace/gophercloud/openstack/networking/v2/networks"
+ "github.com/rackspace/gophercloud/pagination"
+)
+
+// NetworkExternal represents a decorated form of a Network with based on the
+// "external-net" extension.
+type NetworkExternal struct {
+ // UUID for the network
+ ID string `mapstructure:"id" json:"id"`
+
+ // Human-readable name for the network. Might not be unique.
+ Name string `mapstructure:"name" json:"name"`
+
+ // The administrative state of network. If false (down), the network does not forward packets.
+ AdminStateUp bool `mapstructure:"admin_state_up" json:"admin_state_up"`
+
+ // Indicates whether network is currently operational. Possible values include
+ // `ACTIVE', `DOWN', `BUILD', or `ERROR'. Plug-ins might define additional values.
+ Status string `mapstructure:"status" json:"status"`
+
+ // Subnets associated with this network.
+ Subnets []string `mapstructure:"subnets" json:"subnets"`
+
+ // Owner of network. Only admin users can specify a tenant_id other than its own.
+ TenantID string `mapstructure:"tenant_id" json:"tenant_id"`
+
+ // Specifies whether the network resource can be accessed by any tenant or not.
+ Shared bool `mapstructure:"shared" json:"shared"`
+
+ // Specifies whether the network is an external network or not.
+ External bool `mapstructure:"router:external" json:"router:external"`
+}
+
+// ExtractGet decorates a GetResult struct returned from a networks.Get()
+// function with extended attributes.
+func ExtractGet(r networks.GetResult) (*NetworkExternal, error) {
+ if r.Err != nil {
+ return nil, r.Err
+ }
+ var res struct {
+ Network *NetworkExternal `json:"network"`
+ }
+ err := mapstructure.Decode(r.Resp, &res)
+ if err != nil {
+ return nil, fmt.Errorf("Error decoding Neutron network: %v", err)
+ }
+ return res.Network, nil
+}
+
+// ExtractCreate decorates a CreateResult struct returned from a networks.Create()
+// function with extended attributes.
+func ExtractCreate(r networks.CreateResult) (*NetworkExternal, error) {
+ if r.Err != nil {
+ return nil, r.Err
+ }
+ var res struct {
+ Network *NetworkExternal `json:"network"`
+ }
+ err := mapstructure.Decode(r.Resp, &res)
+ if err != nil {
+ return nil, fmt.Errorf("Error decoding Neutron network: %v", err)
+ }
+ return res.Network, nil
+}
+
+// ExtractUpdate decorates a UpdateResult struct returned from a
+// networks.Update() function with extended attributes.
+func ExtractUpdate(r networks.UpdateResult) (*NetworkExternal, error) {
+ if r.Err != nil {
+ return nil, r.Err
+ }
+ var res struct {
+ Network *NetworkExternal `json:"network"`
+ }
+ err := mapstructure.Decode(r.Resp, &res)
+ if err != nil {
+ return nil, fmt.Errorf("Error decoding Neutron network: %v", err)
+ }
+ return res.Network, nil
+}
+
+// ExtractList accepts a Page struct, specifically a NetworkPage struct, and
+// extracts the elements into a slice of NetworkExtAttrs structs. In other
+// words, a generic collection is mapped into a relevant slice.
+func ExtractList(page pagination.Page) ([]NetworkExternal, error) {
+ var resp struct {
+ Networks []NetworkExternal `mapstructure:"networks" json:"networks"`
+ }
+
+ err := mapstructure.Decode(page.(networks.NetworkPage).Body, &resp)
+ if err != nil {
+ return nil, err
+ }
+
+ return resp.Networks, nil
+}
diff --git a/openstack/networking/v2/extensions/external/results_test.go b/openstack/networking/v2/extensions/external/results_test.go
new file mode 100644
index 0000000..6c7e69a
--- /dev/null
+++ b/openstack/networking/v2/extensions/external/results_test.go
@@ -0,0 +1,238 @@
+package external
+
+import (
+ "fmt"
+ "net/http"
+ "testing"
+
+ "github.com/rackspace/gophercloud"
+ "github.com/rackspace/gophercloud/openstack/networking/v2/networks"
+ "github.com/rackspace/gophercloud/pagination"
+ th "github.com/rackspace/gophercloud/testhelper"
+)
+
+const tokenID = "123"
+
+func serviceClient() *gophercloud.ServiceClient {
+ return &gophercloud.ServiceClient{
+ Provider: &gophercloud.ProviderClient{TokenID: tokenID},
+ Endpoint: th.Endpoint(),
+ }
+}
+
+func TestList(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/v2.0/networks", 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, `
+{
+ "networks": [
+ {
+ "admin_state_up": true,
+ "id": "0f38d5ad-10a6-428f-a5fc-825cfe0f1970",
+ "name": "net1",
+ "router:external": false,
+ "shared": false,
+ "status": "ACTIVE",
+ "subnets": [
+ "25778974-48a8-46e7-8998-9dc8c70d2f06"
+ ],
+ "tenant_id": "b575417a6c444a6eb5cc3a58eb4f714a"
+ },
+ {
+ "admin_state_up": true,
+ "id": "8d05a1b1-297a-46ca-8974-17debf51ca3c",
+ "name": "ext_net",
+ "router:external": true,
+ "shared": false,
+ "status": "ACTIVE",
+ "subnets": [
+ "2f1fb918-9b0e-4bf9-9a50-6cebbb4db2c5"
+ ],
+ "tenant_id": "5eb8995cf717462c9df8d1edfa498010"
+ }
+ ]
+}
+ `)
+ })
+
+ count := 0
+
+ networks.List(serviceClient(), networks.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) {
+ count++
+ actual, err := ExtractList(page)
+ if err != nil {
+ t.Errorf("Failed to extract networks: %v", err)
+ return false, err
+ }
+
+ expected := []NetworkExternal{
+ NetworkExternal{
+ Status: "ACTIVE",
+ Subnets: []string{"25778974-48a8-46e7-8998-9dc8c70d2f06"},
+ Name: "net1",
+ AdminStateUp: true,
+ TenantID: "b575417a6c444a6eb5cc3a58eb4f714a",
+ Shared: false,
+ ID: "0f38d5ad-10a6-428f-a5fc-825cfe0f1970",
+ External: false,
+ },
+ NetworkExternal{
+ Status: "ACTIVE",
+ Subnets: []string{"2f1fb918-9b0e-4bf9-9a50-6cebbb4db2c5"},
+ Name: "ext_net",
+ AdminStateUp: true,
+ TenantID: "5eb8995cf717462c9df8d1edfa498010",
+ Shared: false,
+ ID: "8d05a1b1-297a-46ca-8974-17debf51ca3c",
+ External: true,
+ },
+ }
+
+ th.CheckDeepEquals(t, expected, actual)
+
+ return true, nil
+ })
+
+ if count != 1 {
+ t.Errorf("Expected 1 page, got %d", count)
+ }
+}
+
+func TestGet(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/v2.0/networks/d32019d3-bc6e-4319-9c1d-6722fc136a22", 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, `
+{
+ "network": {
+ "admin_state_up": true,
+ "id": "8d05a1b1-297a-46ca-8974-17debf51ca3c",
+ "name": "ext_net",
+ "router:external": true,
+ "shared": false,
+ "status": "ACTIVE",
+ "subnets": [
+ "2f1fb918-9b0e-4bf9-9a50-6cebbb4db2c5"
+ ],
+ "tenant_id": "5eb8995cf717462c9df8d1edfa498010"
+ }
+}
+ `)
+ })
+
+ res := networks.Get(serviceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22")
+ n, err := ExtractGet(res)
+
+ th.AssertNoErr(t, err)
+ th.AssertEquals(t, true, n.External)
+}
+
+func TestCreate(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/v2.0/networks", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "POST")
+ th.TestHeader(t, r, "X-Auth-Token", tokenID)
+ th.TestHeader(t, r, "Content-Type", "application/json")
+ th.TestHeader(t, r, "Accept", "application/json")
+ th.TestJSONRequest(t, r, `
+{
+ "network": {
+ "admin_state_up": true,
+ "name": "ext_net",
+ "router:external": true
+ }
+}
+ `)
+
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusCreated)
+
+ fmt.Fprintf(w, `
+{
+ "network": {
+ "admin_state_up": true,
+ "id": "8d05a1b1-297a-46ca-8974-17debf51ca3c",
+ "name": "ext_net",
+ "router:external": true,
+ "shared": false,
+ "status": "ACTIVE",
+ "subnets": [
+ "2f1fb918-9b0e-4bf9-9a50-6cebbb4db2c5"
+ ],
+ "tenant_id": "5eb8995cf717462c9df8d1edfa498010"
+ }
+}
+ `)
+ })
+
+ options := networks.CreateOpts{External: true}
+ res := networks.Create(serviceClient(), options)
+ n, err := ExtractCreate(res)
+
+ th.AssertNoErr(t, err)
+ th.AssertEquals(t, true, n.External)
+}
+
+func TestUpdate(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/v2.0/networks/4e8e5957-649f-477b-9e5b-f1f75b21c03c", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "PUT")
+ th.TestHeader(t, r, "X-Auth-Token", tokenID)
+ th.TestHeader(t, r, "Content-Type", "application/json")
+ th.TestHeader(t, r, "Accept", "application/json")
+ th.TestJSONRequest(t, r, `
+{
+ "network": {
+ "router:external": true
+ }
+}
+ `)
+
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+
+ fmt.Fprintf(w, `
+{
+ "network": {
+ "admin_state_up": true,
+ "id": "8d05a1b1-297a-46ca-8974-17debf51ca3c",
+ "name": "ext_net",
+ "router:external": true,
+ "shared": false,
+ "status": "ACTIVE",
+ "subnets": [
+ "2f1fb918-9b0e-4bf9-9a50-6cebbb4db2c5"
+ ],
+ "tenant_id": "5eb8995cf717462c9df8d1edfa498010"
+ }
+}
+ `)
+ })
+
+ shared := true
+ options := networks.UpdateOpts{External: true}
+ res := networks.Update(serviceClient(), "4e8e5957-649f-477b-9e5b-f1f75b21c03c", options)
+ n, err := ExtractUpdate(res)
+
+ th.AssertNoErr(t, err)
+ th.AssertEquals(t, true, n.External)
+}