From f92ae6c762ef68e7fbe914f793d14356805a0a50 Mon Sep 17 00:00:00 2001 From: Joe Topjian Date: Wed, 6 Apr 2016 21:24:43 -0600 Subject: [PATCH 1/2] Allow subnets to have no gateway --- .../openstack/networking/v2/subnet_test.go | 43 ++++++- openstack/networking/v2/subnets/requests.go | 6 + .../networking/v2/subnets/requests_test.go | 121 ++++++++++++++++++ 3 files changed, 168 insertions(+), 2 deletions(-) diff --git a/acceptance/openstack/networking/v2/subnet_test.go b/acceptance/openstack/networking/v2/subnet_test.go index 097a303e..882ea670 100644 --- a/acceptance/openstack/networking/v2/subnet_test.go +++ b/acceptance/openstack/networking/v2/subnet_test.go @@ -11,7 +11,7 @@ import ( th "github.com/rackspace/gophercloud/testhelper" ) -func TestList(t *testing.T) { +func TestSubnetList(t *testing.T) { Setup(t) defer Teardown() @@ -32,7 +32,7 @@ func TestList(t *testing.T) { th.CheckNoErr(t, err) } -func TestCRUD(t *testing.T) { +func TestSubnetCRUD(t *testing.T) { Setup(t) defer Teardown() @@ -61,6 +61,7 @@ func TestCRUD(t *testing.T) { th.AssertEquals(t, s.IPVersion, 4) th.AssertEquals(t, s.Name, "my_subnet") th.AssertEquals(t, s.EnableDHCP, false) + th.AssertEquals(t, s.GatewayIP, "192.168.199.1") subnetID := s.ID // Get subnet @@ -79,6 +80,44 @@ func TestCRUD(t *testing.T) { t.Log("Delete subnet") res := subnets.Delete(Client, subnetID) th.AssertNoErr(t, res.Err) + + // Create subnet with no gateway + t.Log("Create subnet with no gateway") + opts = subnets.CreateOpts{ + NetworkID: networkID, + CIDR: "192.168.199.0/24", + IPVersion: subnets.IPv4, + Name: "my_subnet", + EnableDHCP: &enable, + NoGateway: true, + } + s, err = subnets.Create(Client, opts).Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, s.NetworkID, networkID) + th.AssertEquals(t, s.CIDR, "192.168.199.0/24") + th.AssertEquals(t, s.IPVersion, 4) + th.AssertEquals(t, s.Name, "my_subnet") + th.AssertEquals(t, s.EnableDHCP, false) + th.AssertEquals(t, s.GatewayIP, "") + subnetID = s.ID + + // Get subnet + t.Log("Getting subnet with no gateway") + s, err = subnets.Get(Client, subnetID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, s.ID, subnetID) + + // Update subnet + t.Log("Update subnet with no gateway") + s, err = subnets.Update(Client, subnetID, subnets.UpdateOpts{Name: "new_subnet_name"}).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, s.Name, "new_subnet_name") + + // Delete subnet + t.Log("Delete subnet with no gateway") + res = subnets.Delete(Client, subnetID) + th.AssertNoErr(t, res.Err) } func TestBatchCreate(t *testing.T) { diff --git a/openstack/networking/v2/subnets/requests.go b/openstack/networking/v2/subnets/requests.go index 6cde048e..1c7ef7be 100644 --- a/openstack/networking/v2/subnets/requests.go +++ b/openstack/networking/v2/subnets/requests.go @@ -108,6 +108,7 @@ type CreateOpts struct { TenantID string AllocationPools []AllocationPool GatewayIP string + NoGateway bool IPVersion int EnableDHCP *bool DNSNameservers []string @@ -139,6 +140,8 @@ func (opts CreateOpts) ToSubnetCreateMap() (map[string]interface{}, error) { } if opts.GatewayIP != "" { s["gateway_ip"] = opts.GatewayIP + } else if opts.NoGateway { + s["gateway_ip"] = nil } if opts.TenantID != "" { s["tenant_id"] = opts.TenantID @@ -184,6 +187,7 @@ type UpdateOptsBuilder interface { type UpdateOpts struct { Name string GatewayIP string + NoGateway bool DNSNameservers []string HostRoutes []HostRoute EnableDHCP *bool @@ -201,6 +205,8 @@ func (opts UpdateOpts) ToSubnetUpdateMap() (map[string]interface{}, error) { } if opts.GatewayIP != "" { s["gateway_ip"] = opts.GatewayIP + } else if opts.NoGateway { + s["gateway_ip"] = nil } if opts.DNSNameservers != nil { s["dns_nameservers"] = opts.DNSNameservers diff --git a/openstack/networking/v2/subnets/requests_test.go b/openstack/networking/v2/subnets/requests_test.go index 987064ad..6c25d33a 100644 --- a/openstack/networking/v2/subnets/requests_test.go +++ b/openstack/networking/v2/subnets/requests_test.go @@ -59,6 +59,24 @@ func TestList(t *testing.T) { "gateway_ip": "192.0.0.1", "cidr": "192.0.0.0/8", "id": "54d6f61d-db07-451c-9ab3-b9609b6b6f0b" + }, + { + "name": "my_gatewayless_subnet", + "enable_dhcp": true, + "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a23", + "tenant_id": "4fd44f30292945e481c7b8a0c8908869", + "dns_nameservers": [], + "allocation_pools": [ + { + "start": "192.168.1.2", + "end": "192.168.1.254" + } + ], + "host_routes": [], + "ip_version": 4, + "gateway_ip": null, + "cidr": "192.168.1.0/24", + "id": "54d6f61d-db07-451c-9ab3-b9609b6b6f0c" } ] } @@ -112,6 +130,24 @@ func TestList(t *testing.T) { CIDR: "192.0.0.0/8", ID: "54d6f61d-db07-451c-9ab3-b9609b6b6f0b", }, + Subnet{ + Name: "my_gatewayless_subnet", + EnableDHCP: true, + NetworkID: "d32019d3-bc6e-4319-9c1d-6722fc136a23", + TenantID: "4fd44f30292945e481c7b8a0c8908869", + DNSNameservers: []string{}, + AllocationPools: []AllocationPool{ + AllocationPool{ + Start: "192.168.1.2", + End: "192.168.1.254", + }, + }, + HostRoutes: []HostRoute{}, + IPVersion: 4, + GatewayIP: "", + CIDR: "192.168.1.0/24", + ID: "54d6f61d-db07-451c-9ab3-b9609b6b6f0c", + }, } th.CheckDeepEquals(t, expected, actual) @@ -270,6 +306,91 @@ func TestCreate(t *testing.T) { th.AssertEquals(t, s.ID, "3b80198d-4f7b-4f77-9ef5-774d54e17126") } +func TestCreateNoGateway(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", fake.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-6722fc136a23", + "ip_version": 4, + "cidr": "192.168.1.0/24", + "gateway_ip": null, + "allocation_pools": [ + { + "start": "192.168.1.2", + "end": "192.168.1.254" + } + ] + } +} + `) + + 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-6722fc136a23", + "tenant_id": "4fd44f30292945e481c7b8a0c8908869", + "allocation_pools": [ + { + "start": "192.168.1.2", + "end": "192.168.1.254" + } + ], + "host_routes": [], + "ip_version": 4, + "gateway_ip": null, + "cidr": "192.168.1.0/24", + "id": "54d6f61d-db07-451c-9ab3-b9609b6b6f0c" + } +} + `) + }) + + opts := CreateOpts{ + NetworkID: "d32019d3-bc6e-4319-9c1d-6722fc136a23", + IPVersion: 4, + CIDR: "192.168.1.0/24", + NoGateway: true, + AllocationPools: []AllocationPool{ + AllocationPool{ + Start: "192.168.1.2", + End: "192.168.1.254", + }, + }, + DNSNameservers: []string{}, + } + s, err := Create(fake.ServiceClient(), opts).Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, s.Name, "") + th.AssertEquals(t, s.EnableDHCP, true) + th.AssertEquals(t, s.NetworkID, "d32019d3-bc6e-4319-9c1d-6722fc136a23") + th.AssertEquals(t, s.TenantID, "4fd44f30292945e481c7b8a0c8908869") + th.AssertDeepEquals(t, s.AllocationPools, []AllocationPool{ + AllocationPool{ + Start: "192.168.1.2", + End: "192.168.1.254", + }, + }) + th.AssertDeepEquals(t, s.HostRoutes, []HostRoute{}) + th.AssertEquals(t, s.IPVersion, 4) + th.AssertEquals(t, s.GatewayIP, "") + th.AssertEquals(t, s.CIDR, "192.168.1.0/24") + th.AssertEquals(t, s.ID, "54d6f61d-db07-451c-9ab3-b9609b6b6f0c") +} + func TestRequiredCreateOpts(t *testing.T) { res := Create(fake.ServiceClient(), CreateOpts{}) if res.Err == nil { From 2524d119150604a229a40cd7b7b2c902034134fd Mon Sep 17 00:00:00 2001 From: Joe Topjian Date: Thu, 7 Apr 2016 15:41:39 +0000 Subject: [PATCH 2/2] Ensure both GatewayIP and NoGateway cannot be set in subnets --- .../openstack/networking/v2/subnet_test.go | 16 ++++++ openstack/networking/v2/subnets/errors.go | 7 +-- openstack/networking/v2/subnets/requests.go | 10 ++++ .../networking/v2/subnets/requests_test.go | 54 +++++++++++++++++++ 4 files changed, 84 insertions(+), 3 deletions(-) diff --git a/acceptance/openstack/networking/v2/subnet_test.go b/acceptance/openstack/networking/v2/subnet_test.go index 882ea670..e6a789e2 100644 --- a/acceptance/openstack/networking/v2/subnet_test.go +++ b/acceptance/openstack/networking/v2/subnet_test.go @@ -118,6 +118,22 @@ func TestSubnetCRUD(t *testing.T) { t.Log("Delete subnet with no gateway") res = subnets.Delete(Client, subnetID) th.AssertNoErr(t, res.Err) + + // Create subnet with invalid gateway configuration + t.Log("Create subnet with invalid gateway configuration") + opts = subnets.CreateOpts{ + NetworkID: networkID, + CIDR: "192.168.199.0/24", + IPVersion: subnets.IPv4, + Name: "my_subnet", + EnableDHCP: &enable, + NoGateway: true, + GatewayIP: "192.168.199.1", + } + _, err = subnets.Create(Client, opts).Extract() + if err == nil { + t.Fatalf("Expected an error, got none") + } } func TestBatchCreate(t *testing.T) { diff --git a/openstack/networking/v2/subnets/errors.go b/openstack/networking/v2/subnets/errors.go index 0db0a6e6..d2f7b46e 100644 --- a/openstack/networking/v2/subnets/errors.go +++ b/openstack/networking/v2/subnets/errors.go @@ -7,7 +7,8 @@ func err(str string) error { } 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") + 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") + errInvalidGatewayConfig = err("Both disabling the gateway and specifying a gateway is not allowed") ) diff --git a/openstack/networking/v2/subnets/requests.go b/openstack/networking/v2/subnets/requests.go index 1c7ef7be..8fa1e6db 100644 --- a/openstack/networking/v2/subnets/requests.go +++ b/openstack/networking/v2/subnets/requests.go @@ -129,6 +129,11 @@ func (opts CreateOpts) ToSubnetCreateMap() (map[string]interface{}, error) { return nil, errInvalidIPType } + // Both GatewayIP and NoGateway should not be set + if opts.GatewayIP != "" && opts.NoGateway { + return nil, errInvalidGatewayConfig + } + s["network_id"] = opts.NetworkID s["cidr"] = opts.CIDR @@ -197,6 +202,11 @@ type UpdateOpts struct { func (opts UpdateOpts) ToSubnetUpdateMap() (map[string]interface{}, error) { s := make(map[string]interface{}) + // Both GatewayIP and NoGateway should not be set + if opts.GatewayIP != "" && opts.NoGateway { + return nil, errInvalidGatewayConfig + } + if opts.EnableDHCP != nil { s["enable_dhcp"] = &opts.EnableDHCP } diff --git a/openstack/networking/v2/subnets/requests_test.go b/openstack/networking/v2/subnets/requests_test.go index 6c25d33a..4a67a132 100644 --- a/openstack/networking/v2/subnets/requests_test.go +++ b/openstack/networking/v2/subnets/requests_test.go @@ -391,6 +391,60 @@ func TestCreateNoGateway(t *testing.T) { th.AssertEquals(t, s.ID, "54d6f61d-db07-451c-9ab3-b9609b6b6f0c") } +func TestCreateInvalidGatewayConfig(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", fake.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-6722fc136a23", + "ip_version": 4, + "cidr": "192.168.1.0/24", + "gateway_ip": "192.168.1.1", + "allocation_pools": [ + { + "start": "192.168.1.2", + "end": "192.168.1.254" + } + ] + } +} + `) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + }) + + opts := CreateOpts{ + NetworkID: "d32019d3-bc6e-4319-9c1d-6722fc136a23", + IPVersion: 4, + CIDR: "192.168.1.0/24", + NoGateway: true, + GatewayIP: "192.168.1.1", + AllocationPools: []AllocationPool{ + AllocationPool{ + Start: "192.168.1.2", + End: "192.168.1.254", + }, + }, + DNSNameservers: []string{}, + } + _, err := Create(fake.ServiceClient(), opts).Extract() + if err == nil { + t.Fatalf("Expected an error, got none") + } + + if err != errInvalidGatewayConfig { + t.Fatalf("Exected errInvalidGateway but got: %s", err) + } +} + func TestRequiredCreateOpts(t *testing.T) { res := Create(fake.ServiceClient(), CreateOpts{}) if res.Err == nil {