Add create subnet operation
diff --git a/openstack/networking/v2/subnets/errors.go b/openstack/networking/v2/subnets/errors.go
index b15101b..9299927 100644
--- a/openstack/networking/v2/subnets/errors.go
+++ b/openstack/networking/v2/subnets/errors.go
@@ -1 +1,13 @@
package subnets
+
+import "fmt"
+
+func err(str string) error {
+ return fmt.Errorf("%s", str)
+}
+
+var (
+ ErrNetworkIDRequired = err("A network ID is required")
+ ErrCIDRRequired = err("A valid CIDR is required")
+ ErrInvalidIPType = err("An IP type must either be 4 or 6")
+)
diff --git a/openstack/networking/v2/subnets/requests.go b/openstack/networking/v2/subnets/requests.go
index 9d23e56..93641d9 100644
--- a/openstack/networking/v2/subnets/requests.go
+++ b/openstack/networking/v2/subnets/requests.go
@@ -68,3 +68,94 @@
}
return &s, nil
}
+
+const (
+ IPv4 = 4
+ IPv6 = 6
+)
+
+type SubnetOpts struct {
+ // Required
+ NetworkID string
+ CIDR string
+ // Optional
+ Name string
+ TenantID string
+ AllocationPools []AllocationPool
+ GatewayIP string
+ IPVersion int
+ ID string
+ EnableDHCP *bool
+}
+
+// maybeString returns nil for empty strings and nil for empty.
+func maybeString(original string) *string {
+ if original != "" {
+ return &original
+ }
+ return nil
+}
+
+func Create(c *gophercloud.ServiceClient, opts SubnetOpts) (*Subnet, error) {
+ // Validate required options
+ if opts.NetworkID == "" {
+ return nil, ErrNetworkIDRequired
+ }
+ if opts.CIDR == "" {
+ return nil, ErrCIDRRequired
+ }
+ if opts.IPVersion != 0 && opts.IPVersion != IPv4 && opts.IPVersion != IPv6 {
+ return nil, ErrInvalidIPType
+ }
+
+ type subnet struct {
+ NetworkID string `json:"network_id"`
+ CIDR string `json:"cidr"`
+ Name *string `json:"name,omitempty"`
+ TenantID *string `json:"tenant_id,omitempty"`
+ AllocationPools []AllocationPool `json:"allocation_pools,omitempty"`
+ GatewayIP *string `json:"gateway_ip,omitempty"`
+ IPVersion int `json:"ip_version,omitempty"`
+ ID *string `json:"id,omitempty"`
+ EnableDHCP *bool `json:"enable_dhcp,omitempty"`
+ }
+ type request struct {
+ Subnet subnet `json:"subnet"`
+ }
+
+ reqBody := request{Subnet: subnet{
+ NetworkID: opts.NetworkID,
+ CIDR: opts.CIDR,
+ }}
+
+ reqBody.Subnet.Name = maybeString(opts.Name)
+ reqBody.Subnet.TenantID = maybeString(opts.TenantID)
+ reqBody.Subnet.GatewayIP = maybeString(opts.GatewayIP)
+ reqBody.Subnet.ID = maybeString(opts.ID)
+ reqBody.Subnet.EnableDHCP = opts.EnableDHCP
+
+ if opts.IPVersion != 0 {
+ reqBody.Subnet.IPVersion = opts.IPVersion
+ }
+
+ if len(opts.AllocationPools) != 0 {
+ reqBody.Subnet.AllocationPools = opts.AllocationPools
+ }
+
+ type response struct {
+ Subnet *Subnet `json:"subnet"`
+ }
+
+ 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.Subnet, nil
+}
diff --git a/openstack/networking/v2/subnets/requests_test.go b/openstack/networking/v2/subnets/requests_test.go
index fb21ff3..f5716cf 100644
--- a/openstack/networking/v2/subnets/requests_test.go
+++ b/openstack/networking/v2/subnets/requests_test.go
@@ -190,3 +190,71 @@
th.AssertEquals(t, s.CIDR, "192.0.0.0/8")
th.AssertEquals(t, s.ID, "54d6f61d-db07-451c-9ab3-b9609b6b6f0b")
}
+
+func TestCreate(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ th.Mux.HandleFunc("/v2.0/subnets", 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, `
+{
+ "subnet": {
+ "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22",
+ "ip_version": 4,
+ "cidr": "192.168.199.0/24"
+ }
+}
+ `)
+
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusCreated)
+
+ fmt.Fprintf(w, `
+{
+ "subnet": {
+ "name": "",
+ "enable_dhcp": true,
+ "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22",
+ "tenant_id": "4fd44f30292945e481c7b8a0c8908869",
+ "dns_nameservers": [],
+ "allocation_pools": [
+ {
+ "start": "192.168.199.2",
+ "end": "192.168.199.254"
+ }
+ ],
+ "host_routes": [],
+ "ip_version": 4,
+ "gateway_ip": "192.168.199.1",
+ "cidr": "192.168.199.0/24",
+ "id": "3b80198d-4f7b-4f77-9ef5-774d54e17126"
+ }
+}
+ `)
+ })
+
+ opts := SubnetOpts{NetworkID: "d32019d3-bc6e-4319-9c1d-6722fc136a22", IPVersion: 4, CIDR: "192.168.199.0/24"}
+ s, err := Create(ServiceClient(), opts)
+ th.AssertNoErr(t, err)
+
+ th.AssertEquals(t, s.Name, "")
+ th.AssertEquals(t, s.EnableDHCP, true)
+ th.AssertEquals(t, s.NetworkID, "d32019d3-bc6e-4319-9c1d-6722fc136a22")
+ th.AssertEquals(t, s.TenantID, "4fd44f30292945e481c7b8a0c8908869")
+ th.AssertDeepEquals(t, s.DNSNameservers, []interface{}{})
+ th.AssertDeepEquals(t, s.AllocationPools, []AllocationPool{
+ AllocationPool{
+ Start: "192.168.199.2",
+ End: "192.168.199.254",
+ },
+ })
+ th.AssertDeepEquals(t, s.HostRoutes, []interface{}{})
+ th.AssertEquals(t, s.IPVersion, 4)
+ th.AssertEquals(t, s.GatewayIP, "192.168.199.1")
+ th.AssertEquals(t, s.CIDR, "192.168.199.0/24")
+ th.AssertEquals(t, s.ID, "3b80198d-4f7b-4f77-9ef5-774d54e17126")
+}
diff --git a/openstack/networking/v2/subnets/results.go b/openstack/networking/v2/subnets/results.go
index 1790028..3de2668 100644
--- a/openstack/networking/v2/subnets/results.go
+++ b/openstack/networking/v2/subnets/results.go
@@ -6,8 +6,8 @@
)
type AllocationPool struct {
- Start string
- End string
+ Start string `json:"start"`
+ End string `json:"end"`
}
type Subnet struct {
diff --git a/openstack/networking/v2/subnets/urls.go b/openstack/networking/v2/subnets/urls.go
index 2cf128b..485f97a 100644
--- a/openstack/networking/v2/subnets/urls.go
+++ b/openstack/networking/v2/subnets/urls.go
@@ -19,3 +19,7 @@
func GetURL(c *gophercloud.ServiceClient, id string) string {
return ResourceURL(c, id)
}
+
+func CreateURL(c *gophercloud.ServiceClient) string {
+ return RootURL(c)
+}
diff --git a/openstack/networking/v2/subnets/urls_tests.go b/openstack/networking/v2/subnets/urls_tests.go
index 95b179f..336e2fe 100644
--- a/openstack/networking/v2/subnets/urls_tests.go
+++ b/openstack/networking/v2/subnets/urls_tests.go
@@ -24,3 +24,9 @@
expected := Endpoint + "v2.0/subnets/foo"
th.AssertEquals(t, expected, actual)
}
+
+func TestCreateURL(t *testing.T) {
+ actual := CreateURL(EndpointClient())
+ expected := Endpoint + "v2.0/subnets"
+ th.AssertEquals(t, expected, actual)
+}