Adding create network operation
diff --git a/acceptance/openstack/networking/v2/network_test.go b/acceptance/openstack/networking/v2/network_test.go
index 209a68c..4fe7775 100644
--- a/acceptance/openstack/networking/v2/network_test.go
+++ b/acceptance/openstack/networking/v2/network_test.go
@@ -4,6 +4,7 @@
import (
"os"
+ "reflect"
"testing"
"github.com/rackspace/gophercloud"
@@ -12,7 +13,9 @@
"github.com/rackspace/gophercloud/openstack/utils"
)
-var Client *gophercloud.ServiceClient
+var (
+ Client *gophercloud.ServiceClient
+)
func NewClient() (*gophercloud.ServiceClient, error) {
opts, err := utils.AuthOptions()
@@ -50,6 +53,12 @@
}
}
+func DeepEquals(t *testing.T, actual, expected interface{}) {
+ if !reflect.DeepEqual(actual, expected) {
+ t.Fatalf("Expected %#v but got %#v", expected, actual)
+ }
+}
+
func TestListAPIVersions(t *testing.T) {
Setup(t)
defer Teardown()
@@ -116,22 +125,50 @@
//networks.List()
}
-func TestGetNetwork(t *testing.T) {
- //networks.Get()
-}
+func TestNetworkCRUDOperations(t *testing.T) {
+ Setup(t)
+ defer Teardown()
-func TestCreateNetwork(t *testing.T) {
- //networks.Create()
+ // Create a network
+ res, err := networks.Create(Client, networks.NetworkOpts{Name: "sample_network", AdminStateUp: true})
+ if err != nil {
+ t.Fatalf("Unexpected error when creating network: %#v", err)
+ }
+
+ Equals(t, res.Name, "sample_network")
+ Equals(t, res.AdminStateUp, true)
+
+ networkID := res.ID
+
+ // Get a network
+ if networkID == "" {
+ t.Fatalf("In order to retrieve a network, the NetworkID must be set")
+ }
+
+ Setup(t)
+ defer Teardown()
+
+ n, err := networks.Get(Client, networkID)
+ if err != nil {
+ t.Fatalf("Unexpected error: %#v", err)
+ }
+
+ Equals(t, n.Status, "ACTIVE")
+ DeepEquals(t, n.Subnets, []string{})
+ Equals(t, n.Name, "sample_network")
+ Equals(t, n.ProviderPhysicalNetwork, "")
+ Equals(t, n.ProviderNetworkType, "local")
+ Equals(t, n.ProviderSegmentationID, "")
+ Equals(t, n.AdminStateUp, true)
+ Equals(t, n.RouterExternal, false)
+ Equals(t, n.Shared, false)
+ Equals(t, n.ID, networkID)
+
+ // Update network
+
+ // Delete network
}
func TestCreateMultipleNetworks(t *testing.T) {
//networks.CreateMany()
}
-
-func TestUpdateNetwork(t *testing.T) {
- //networks.Update()
-}
-
-func TestDeleteNetwork(t *testing.T) {
- //networks.Delete()
-}
diff --git a/openstack/networking/v2/networks/errors.go b/openstack/networking/v2/networks/errors.go
index 83c4a6a..7397213 100644
--- a/openstack/networking/v2/networks/errors.go
+++ b/openstack/networking/v2/networks/errors.go
@@ -1 +1,11 @@
package networks
+
+import "fmt"
+
+func requiredAttr(attr string) error {
+ return fmt.Errorf("You must specify %s for this resource", attr)
+}
+
+var (
+ ErrNameRequired = requiredAttr("name")
+)
diff --git a/openstack/networking/v2/networks/requests.go b/openstack/networking/v2/networks/requests.go
index e27445d..7b647fe 100644
--- a/openstack/networking/v2/networks/requests.go
+++ b/openstack/networking/v2/networks/requests.go
@@ -1,26 +1,13 @@
package networks
import (
+ "encoding/json"
+ "fmt"
+
"github.com/racker/perigee"
"github.com/rackspace/gophercloud"
)
-// User-defined options sent to the API when creating or updating a network.
-type NetworkOpts struct {
- // The administrative state of the network, which is up (true) or down (false).
- AdminStateUp bool `json:"admin_state_up"`
- // The network name (optional)
- Name string `json:"name"`
- // Indicates whether this network is shared across all tenants. By default,
- // only administrative users can change this value.
- Shared bool `json:"shared"`
- // Admin-only. The UUID of the tenant that will own the network. This tenant
- // can be different from the tenant that makes the create network request.
- // However, only administrative users can specify a tenant ID other than their
- // own. You cannot change this value through authorization policies.
- TenantID string `json:"tenant_id"`
-}
-
func APIVersions(c *gophercloud.ServiceClient) (*APIVersionsList, error) {
var resp APIVersionsList
_, err := perigee.Request("GET", APIVersionsURL(c), perigee.Options{
@@ -79,3 +66,77 @@
}
return &n, nil
}
+
+type NetworkOpts struct {
+ AdminStateUp bool
+ Name string
+ Shared *bool
+ TenantID string
+}
+
+type NetworkProvider struct {
+ ProviderSegmentationID int `json:"provider:segmentation_id"`
+ ProviderPhysicalNetwork string `json:"provider:physical_network"`
+ ProviderNetworkType string `json:"provider:network_type"`
+}
+
+type NetworkResult struct {
+ Status string `json:"status"`
+ Subnets []interface{} `json:"subnets"`
+ Name string `json:"name"`
+ AdminStateUp bool `json:"admin_state_up"`
+ TenantID string `json:"tenant_id"`
+ Segments []NetworkProvider `json:"segments"`
+ Shared bool `json:"shared"`
+ PortSecurityEnabled bool `json:"port_security_enabled"`
+ ID string `json:"id"`
+}
+
+func Create(c *gophercloud.ServiceClient, opts NetworkOpts) (*NetworkResult, error) {
+ // Define structures
+ type network struct {
+ AdminStateUp bool `json:"admin_state_up"`
+ Name string `json:"name"`
+ Shared *bool `json:"shared,omitempty"`
+ TenantID *string `json:"tenant_id,omitempty"`
+ }
+ type request struct {
+ Network network `json:"network"`
+ }
+ type response struct {
+ Network *NetworkResult `json:"network"`
+ }
+
+ // Validate
+ if opts.Name == "" {
+ return nil, ErrNameRequired
+ }
+
+ // Populate request body
+ reqBody := request{Network: network{
+ AdminStateUp: opts.AdminStateUp,
+ Name: opts.Name,
+ Shared: opts.Shared,
+ }}
+
+ if opts.TenantID != "" {
+ reqBody.Network.TenantID = &opts.TenantID
+ }
+
+ j, _ := json.Marshal(reqBody)
+ fmt.Println(string(j))
+
+ // Send request to API
+ var res response
+ _, err := perigee.Request("POST", CreateURL(c), perigee.Options{
+ MoreHeaders: c.Provider.AuthenticatedHeaders(),
+ ReqBody: &reqBody,
+ Results: &res,
+ OkCodes: []int{201},
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ return res.Network, nil
+}
diff --git a/openstack/networking/v2/networks/requests_test.go b/openstack/networking/v2/networks/requests_test.go
index 5682c33..61d9268 100644
--- a/openstack/networking/v2/networks/requests_test.go
+++ b/openstack/networking/v2/networks/requests_test.go
@@ -262,3 +262,105 @@
Equals(t, n.Shared, true)
Equals(t, n.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22")
}
+
+func TestCreateNetwork(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": {
+ "name": "sample_network",
+ "admin_state_up": true
+ }
+}
+ `)
+
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusCreated)
+
+ fmt.Fprintf(w, `
+{
+ "network": {
+ "status": "ACTIVE",
+ "subnets": [],
+ "name": "net1",
+ "admin_state_up": true,
+ "tenant_id": "9bacb3c5d39d41a79512987f338cf177",
+ "segments": [
+ {
+ "provider:segmentation_id": 2,
+ "provider:physical_network": "8bab8453-1bc9-45af-8c70-f83aa9b50453",
+ "provider:network_type": "vlan"
+ },
+ {
+ "provider:segmentation_id": null,
+ "provider:physical_network": "8bab8453-1bc9-45af-8c70-f83aa9b50453",
+ "provider:network_type": "stt"
+ }
+ ],
+ "shared": false,
+ "port_security_enabled": true,
+ "id": "4e8e5957-649f-477b-9e5b-f1f75b21c03c"
+ }
+}
+ `)
+ })
+
+ options := NetworkOpts{Name: "sample_network", AdminStateUp: true}
+
+ n, err := Create(ServiceClient(), options)
+ if err != nil {
+ t.Fatalf("Unexpected error: %#v", err)
+ }
+
+ Equals(t, n.Status, "ACTIVE")
+ DeepEquals(t, n.Subnets, []interface{}{})
+ Equals(t, n.Name, "net1")
+ Equals(t, n.AdminStateUp, true)
+ Equals(t, n.TenantID, "9bacb3c5d39d41a79512987f338cf177")
+ DeepEquals(t, n.Segments, []NetworkProvider{
+ {ProviderSegmentationID: 2, ProviderPhysicalNetwork: "8bab8453-1bc9-45af-8c70-f83aa9b50453", ProviderNetworkType: "vlan"},
+ {ProviderSegmentationID: 0, ProviderPhysicalNetwork: "8bab8453-1bc9-45af-8c70-f83aa9b50453", ProviderNetworkType: "stt"},
+ })
+ Equals(t, n.Shared, false)
+ Equals(t, n.PortSecurityEnabled, true)
+ Equals(t, n.ID, "4e8e5957-649f-477b-9e5b-f1f75b21c03c")
+}
+
+func TestCreateNetworkWithOptionalFields(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": {
+ "name": "sample_network",
+ "admin_state_up": true,
+ "shared": true,
+ "tenant_id": "12345"
+ }
+}
+ `)
+
+ w.WriteHeader(http.StatusCreated)
+ })
+
+ shared := true
+ options := NetworkOpts{Name: "sample_network", AdminStateUp: true, Shared: &shared, TenantID: "12345"}
+
+ _, err := Create(ServiceClient(), options)
+ if err != nil {
+ t.Fatalf("Unexpected error: %#v", err)
+ }
+}
diff --git a/openstack/networking/v2/networks/urls.go b/openstack/networking/v2/networks/urls.go
index 70cc6e1..0b0b70e 100644
--- a/openstack/networking/v2/networks/urls.go
+++ b/openstack/networking/v2/networks/urls.go
@@ -23,3 +23,7 @@
func NetworkURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL(Version, "networks", id)
}
+
+func CreateURL(c *gophercloud.ServiceClient) string {
+ return c.ServiceURL(Version, "networks")
+}
diff --git a/openstack/networking/v2/networks/urls_test.go b/openstack/networking/v2/networks/urls_test.go
index df8bd02..b38a417 100644
--- a/openstack/networking/v2/networks/urls_test.go
+++ b/openstack/networking/v2/networks/urls_test.go
@@ -43,3 +43,11 @@
t.Fatalf("[%s] does not match expected [%s]", actual, expected)
}
}
+
+func TestCreateURL(t *testing.T) {
+ actual := CreateURL(EndpointClient())
+ expected := Endpoint + "v2.0/networks"
+ if expected != actual {
+ t.Fatalf("[%s] does not match expected [%s]", actual, expected)
+ }
+}