Skip to content
This repository was archived by the owner on Aug 1, 2023. It is now read-only.

Commit a09b5b4

Browse files
committed
Merge pull request #553 from jtopjian/subnet-no-gateway
[rfr] Allow subnets to have no gateway
2 parents 1270499 + 2524d11 commit a09b5b4

File tree

4 files changed

+252
-5
lines changed

4 files changed

+252
-5
lines changed

acceptance/openstack/networking/v2/subnet_test.go

+57-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
th "github.com/rackspace/gophercloud/testhelper"
1212
)
1313

14-
func TestList(t *testing.T) {
14+
func TestSubnetList(t *testing.T) {
1515
Setup(t)
1616
defer Teardown()
1717

@@ -32,7 +32,7 @@ func TestList(t *testing.T) {
3232
th.CheckNoErr(t, err)
3333
}
3434

35-
func TestCRUD(t *testing.T) {
35+
func TestSubnetCRUD(t *testing.T) {
3636
Setup(t)
3737
defer Teardown()
3838

@@ -61,6 +61,7 @@ func TestCRUD(t *testing.T) {
6161
th.AssertEquals(t, s.IPVersion, 4)
6262
th.AssertEquals(t, s.Name, "my_subnet")
6363
th.AssertEquals(t, s.EnableDHCP, false)
64+
th.AssertEquals(t, s.GatewayIP, "192.168.199.1")
6465
subnetID := s.ID
6566

6667
// Get subnet
@@ -79,6 +80,60 @@ func TestCRUD(t *testing.T) {
7980
t.Log("Delete subnet")
8081
res := subnets.Delete(Client, subnetID)
8182
th.AssertNoErr(t, res.Err)
83+
84+
// Create subnet with no gateway
85+
t.Log("Create subnet with no gateway")
86+
opts = subnets.CreateOpts{
87+
NetworkID: networkID,
88+
CIDR: "192.168.199.0/24",
89+
IPVersion: subnets.IPv4,
90+
Name: "my_subnet",
91+
EnableDHCP: &enable,
92+
NoGateway: true,
93+
}
94+
s, err = subnets.Create(Client, opts).Extract()
95+
th.AssertNoErr(t, err)
96+
97+
th.AssertEquals(t, s.NetworkID, networkID)
98+
th.AssertEquals(t, s.CIDR, "192.168.199.0/24")
99+
th.AssertEquals(t, s.IPVersion, 4)
100+
th.AssertEquals(t, s.Name, "my_subnet")
101+
th.AssertEquals(t, s.EnableDHCP, false)
102+
th.AssertEquals(t, s.GatewayIP, "")
103+
subnetID = s.ID
104+
105+
// Get subnet
106+
t.Log("Getting subnet with no gateway")
107+
s, err = subnets.Get(Client, subnetID).Extract()
108+
th.AssertNoErr(t, err)
109+
th.AssertEquals(t, s.ID, subnetID)
110+
111+
// Update subnet
112+
t.Log("Update subnet with no gateway")
113+
s, err = subnets.Update(Client, subnetID, subnets.UpdateOpts{Name: "new_subnet_name"}).Extract()
114+
th.AssertNoErr(t, err)
115+
th.AssertEquals(t, s.Name, "new_subnet_name")
116+
117+
// Delete subnet
118+
t.Log("Delete subnet with no gateway")
119+
res = subnets.Delete(Client, subnetID)
120+
th.AssertNoErr(t, res.Err)
121+
122+
// Create subnet with invalid gateway configuration
123+
t.Log("Create subnet with invalid gateway configuration")
124+
opts = subnets.CreateOpts{
125+
NetworkID: networkID,
126+
CIDR: "192.168.199.0/24",
127+
IPVersion: subnets.IPv4,
128+
Name: "my_subnet",
129+
EnableDHCP: &enable,
130+
NoGateway: true,
131+
GatewayIP: "192.168.199.1",
132+
}
133+
_, err = subnets.Create(Client, opts).Extract()
134+
if err == nil {
135+
t.Fatalf("Expected an error, got none")
136+
}
82137
}
83138

84139
func TestBatchCreate(t *testing.T) {

openstack/networking/v2/subnets/errors.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ func err(str string) error {
77
}
88

99
var (
10-
errNetworkIDRequired = err("A network ID is required")
11-
errCIDRRequired = err("A valid CIDR is required")
12-
errInvalidIPType = err("An IP type must either be 4 or 6")
10+
errNetworkIDRequired = err("A network ID is required")
11+
errCIDRRequired = err("A valid CIDR is required")
12+
errInvalidIPType = err("An IP type must either be 4 or 6")
13+
errInvalidGatewayConfig = err("Both disabling the gateway and specifying a gateway is not allowed")
1314
)

openstack/networking/v2/subnets/requests.go

+16
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ type CreateOpts struct {
108108
TenantID string
109109
AllocationPools []AllocationPool
110110
GatewayIP string
111+
NoGateway bool
111112
IPVersion int
112113
EnableDHCP *bool
113114
DNSNameservers []string
@@ -128,6 +129,11 @@ func (opts CreateOpts) ToSubnetCreateMap() (map[string]interface{}, error) {
128129
return nil, errInvalidIPType
129130
}
130131

132+
// Both GatewayIP and NoGateway should not be set
133+
if opts.GatewayIP != "" && opts.NoGateway {
134+
return nil, errInvalidGatewayConfig
135+
}
136+
131137
s["network_id"] = opts.NetworkID
132138
s["cidr"] = opts.CIDR
133139

@@ -139,6 +145,8 @@ func (opts CreateOpts) ToSubnetCreateMap() (map[string]interface{}, error) {
139145
}
140146
if opts.GatewayIP != "" {
141147
s["gateway_ip"] = opts.GatewayIP
148+
} else if opts.NoGateway {
149+
s["gateway_ip"] = nil
142150
}
143151
if opts.TenantID != "" {
144152
s["tenant_id"] = opts.TenantID
@@ -184,6 +192,7 @@ type UpdateOptsBuilder interface {
184192
type UpdateOpts struct {
185193
Name string
186194
GatewayIP string
195+
NoGateway bool
187196
DNSNameservers []string
188197
HostRoutes []HostRoute
189198
EnableDHCP *bool
@@ -193,6 +202,11 @@ type UpdateOpts struct {
193202
func (opts UpdateOpts) ToSubnetUpdateMap() (map[string]interface{}, error) {
194203
s := make(map[string]interface{})
195204

205+
// Both GatewayIP and NoGateway should not be set
206+
if opts.GatewayIP != "" && opts.NoGateway {
207+
return nil, errInvalidGatewayConfig
208+
}
209+
196210
if opts.EnableDHCP != nil {
197211
s["enable_dhcp"] = &opts.EnableDHCP
198212
}
@@ -201,6 +215,8 @@ func (opts UpdateOpts) ToSubnetUpdateMap() (map[string]interface{}, error) {
201215
}
202216
if opts.GatewayIP != "" {
203217
s["gateway_ip"] = opts.GatewayIP
218+
} else if opts.NoGateway {
219+
s["gateway_ip"] = nil
204220
}
205221
if opts.DNSNameservers != nil {
206222
s["dns_nameservers"] = opts.DNSNameservers

openstack/networking/v2/subnets/requests_test.go

+175
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,24 @@ func TestList(t *testing.T) {
5959
"gateway_ip": "192.0.0.1",
6060
"cidr": "192.0.0.0/8",
6161
"id": "54d6f61d-db07-451c-9ab3-b9609b6b6f0b"
62+
},
63+
{
64+
"name": "my_gatewayless_subnet",
65+
"enable_dhcp": true,
66+
"network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a23",
67+
"tenant_id": "4fd44f30292945e481c7b8a0c8908869",
68+
"dns_nameservers": [],
69+
"allocation_pools": [
70+
{
71+
"start": "192.168.1.2",
72+
"end": "192.168.1.254"
73+
}
74+
],
75+
"host_routes": [],
76+
"ip_version": 4,
77+
"gateway_ip": null,
78+
"cidr": "192.168.1.0/24",
79+
"id": "54d6f61d-db07-451c-9ab3-b9609b6b6f0c"
6280
}
6381
]
6482
}
@@ -112,6 +130,24 @@ func TestList(t *testing.T) {
112130
CIDR: "192.0.0.0/8",
113131
ID: "54d6f61d-db07-451c-9ab3-b9609b6b6f0b",
114132
},
133+
Subnet{
134+
Name: "my_gatewayless_subnet",
135+
EnableDHCP: true,
136+
NetworkID: "d32019d3-bc6e-4319-9c1d-6722fc136a23",
137+
TenantID: "4fd44f30292945e481c7b8a0c8908869",
138+
DNSNameservers: []string{},
139+
AllocationPools: []AllocationPool{
140+
AllocationPool{
141+
Start: "192.168.1.2",
142+
End: "192.168.1.254",
143+
},
144+
},
145+
HostRoutes: []HostRoute{},
146+
IPVersion: 4,
147+
GatewayIP: "",
148+
CIDR: "192.168.1.0/24",
149+
ID: "54d6f61d-db07-451c-9ab3-b9609b6b6f0c",
150+
},
115151
}
116152

117153
th.CheckDeepEquals(t, expected, actual)
@@ -270,6 +306,145 @@ func TestCreate(t *testing.T) {
270306
th.AssertEquals(t, s.ID, "3b80198d-4f7b-4f77-9ef5-774d54e17126")
271307
}
272308

309+
func TestCreateNoGateway(t *testing.T) {
310+
th.SetupHTTP()
311+
defer th.TeardownHTTP()
312+
313+
th.Mux.HandleFunc("/v2.0/subnets", func(w http.ResponseWriter, r *http.Request) {
314+
th.TestMethod(t, r, "POST")
315+
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
316+
th.TestHeader(t, r, "Content-Type", "application/json")
317+
th.TestHeader(t, r, "Accept", "application/json")
318+
th.TestJSONRequest(t, r, `
319+
{
320+
"subnet": {
321+
"network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a23",
322+
"ip_version": 4,
323+
"cidr": "192.168.1.0/24",
324+
"gateway_ip": null,
325+
"allocation_pools": [
326+
{
327+
"start": "192.168.1.2",
328+
"end": "192.168.1.254"
329+
}
330+
]
331+
}
332+
}
333+
`)
334+
335+
w.Header().Add("Content-Type", "application/json")
336+
w.WriteHeader(http.StatusCreated)
337+
338+
fmt.Fprintf(w, `
339+
{
340+
"subnet": {
341+
"name": "",
342+
"enable_dhcp": true,
343+
"network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a23",
344+
"tenant_id": "4fd44f30292945e481c7b8a0c8908869",
345+
"allocation_pools": [
346+
{
347+
"start": "192.168.1.2",
348+
"end": "192.168.1.254"
349+
}
350+
],
351+
"host_routes": [],
352+
"ip_version": 4,
353+
"gateway_ip": null,
354+
"cidr": "192.168.1.0/24",
355+
"id": "54d6f61d-db07-451c-9ab3-b9609b6b6f0c"
356+
}
357+
}
358+
`)
359+
})
360+
361+
opts := CreateOpts{
362+
NetworkID: "d32019d3-bc6e-4319-9c1d-6722fc136a23",
363+
IPVersion: 4,
364+
CIDR: "192.168.1.0/24",
365+
NoGateway: true,
366+
AllocationPools: []AllocationPool{
367+
AllocationPool{
368+
Start: "192.168.1.2",
369+
End: "192.168.1.254",
370+
},
371+
},
372+
DNSNameservers: []string{},
373+
}
374+
s, err := Create(fake.ServiceClient(), opts).Extract()
375+
th.AssertNoErr(t, err)
376+
377+
th.AssertEquals(t, s.Name, "")
378+
th.AssertEquals(t, s.EnableDHCP, true)
379+
th.AssertEquals(t, s.NetworkID, "d32019d3-bc6e-4319-9c1d-6722fc136a23")
380+
th.AssertEquals(t, s.TenantID, "4fd44f30292945e481c7b8a0c8908869")
381+
th.AssertDeepEquals(t, s.AllocationPools, []AllocationPool{
382+
AllocationPool{
383+
Start: "192.168.1.2",
384+
End: "192.168.1.254",
385+
},
386+
})
387+
th.AssertDeepEquals(t, s.HostRoutes, []HostRoute{})
388+
th.AssertEquals(t, s.IPVersion, 4)
389+
th.AssertEquals(t, s.GatewayIP, "")
390+
th.AssertEquals(t, s.CIDR, "192.168.1.0/24")
391+
th.AssertEquals(t, s.ID, "54d6f61d-db07-451c-9ab3-b9609b6b6f0c")
392+
}
393+
394+
func TestCreateInvalidGatewayConfig(t *testing.T) {
395+
th.SetupHTTP()
396+
defer th.TeardownHTTP()
397+
398+
th.Mux.HandleFunc("/v2.0/subnets", func(w http.ResponseWriter, r *http.Request) {
399+
th.TestMethod(t, r, "POST")
400+
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
401+
th.TestHeader(t, r, "Content-Type", "application/json")
402+
th.TestHeader(t, r, "Accept", "application/json")
403+
th.TestJSONRequest(t, r, `
404+
{
405+
"subnet": {
406+
"network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a23",
407+
"ip_version": 4,
408+
"cidr": "192.168.1.0/24",
409+
"gateway_ip": "192.168.1.1",
410+
"allocation_pools": [
411+
{
412+
"start": "192.168.1.2",
413+
"end": "192.168.1.254"
414+
}
415+
]
416+
}
417+
}
418+
`)
419+
420+
w.Header().Add("Content-Type", "application/json")
421+
w.WriteHeader(http.StatusCreated)
422+
})
423+
424+
opts := CreateOpts{
425+
NetworkID: "d32019d3-bc6e-4319-9c1d-6722fc136a23",
426+
IPVersion: 4,
427+
CIDR: "192.168.1.0/24",
428+
NoGateway: true,
429+
GatewayIP: "192.168.1.1",
430+
AllocationPools: []AllocationPool{
431+
AllocationPool{
432+
Start: "192.168.1.2",
433+
End: "192.168.1.254",
434+
},
435+
},
436+
DNSNameservers: []string{},
437+
}
438+
_, err := Create(fake.ServiceClient(), opts).Extract()
439+
if err == nil {
440+
t.Fatalf("Expected an error, got none")
441+
}
442+
443+
if err != errInvalidGatewayConfig {
444+
t.Fatalf("Exected errInvalidGateway but got: %s", err)
445+
}
446+
}
447+
273448
func TestRequiredCreateOpts(t *testing.T) {
274449
res := Create(fake.ServiceClient(), CreateOpts{})
275450
if res.Err == nil {

0 commit comments

Comments
 (0)