Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 89e66ee

Browse filesBrowse files
authored
SG rules: implement bulk create
Implement bulk creation of security group rules [1]. [1] https://docs.openstack.org/api-ref/network/v2/#bulk-create-security-group-rule
1 parent 04387f1 commit 89e66ee
Copy full SHA for 89e66ee

File tree

Expand file treeCollapse file tree

5 files changed

+175
-0
lines changed
Filter options
Expand file treeCollapse file tree

5 files changed

+175
-0
lines changed

‎internal/acceptance/openstack/networking/v2/extensions/extensions.go

Copy file name to clipboardExpand all lines: internal/acceptance/openstack/networking/v2/extensions/extensions.go
+38Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,44 @@ func CreateSecurityGroupRule(t *testing.T, client *gophercloud.ServiceClient, se
140140
return rule, nil
141141
}
142142

143+
// CreateSecurityGroupRulesBulk will create security group rules with a random name
144+
// and random port between 80 and 99.
145+
// An error will be returned if one was failed to be created.
146+
func CreateSecurityGroupRulesBulk(t *testing.T, client *gophercloud.ServiceClient, secGroupID string) ([]rules.SecGroupRule, error) {
147+
t.Logf("Attempting to bulk create security group rules in group: %s", secGroupID)
148+
149+
sgRulesCreateOpts := make([]rules.CreateOpts, 3)
150+
for i := range 3 {
151+
description := "Rule description"
152+
fromPort := tools.RandomInt(80, 89)
153+
toPort := tools.RandomInt(90, 99)
154+
155+
sgRulesCreateOpts[i] = rules.CreateOpts{
156+
Description: description,
157+
Direction: "ingress",
158+
EtherType: "IPv4",
159+
SecGroupID: secGroupID,
160+
PortRangeMin: fromPort,
161+
PortRangeMax: toPort,
162+
Protocol: rules.ProtocolTCP,
163+
}
164+
}
165+
166+
rules, err := rules.CreateBulk(context.TODO(), client, sgRulesCreateOpts).Extract()
167+
if err != nil {
168+
return rules, err
169+
}
170+
171+
for i, rule := range rules {
172+
t.Logf("Created security group rule: %s", rule.ID)
173+
174+
th.AssertEquals(t, sgRulesCreateOpts[i].SecGroupID, rule.SecGroupID)
175+
th.AssertEquals(t, sgRulesCreateOpts[i].Description, rule.Description)
176+
}
177+
178+
return rules, nil
179+
}
180+
143181
// DeleteSecurityGroup will delete a security group of a specified ID.
144182
// A fatal error will occur if the deletion failed. This works best as a
145183
// deferred function

‎internal/acceptance/openstack/networking/v2/extensions/security_test.go

Copy file name to clipboardExpand all lines: internal/acceptance/openstack/networking/v2/extensions/security_test.go
+6Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ func TestSecurityGroupsCreateUpdateDelete(t *testing.T) {
2626
th.AssertNoErr(t, err)
2727
defer DeleteSecurityGroupRule(t, client, rule.ID)
2828

29+
rules, err := CreateSecurityGroupRulesBulk(t, client, group.ID)
30+
th.AssertNoErr(t, err)
31+
for _, r := range rules {
32+
defer DeleteSecurityGroupRule(t, client, r.ID)
33+
}
34+
2935
tools.PrintResource(t, group)
3036

3137
var name = "Update group"

‎openstack/networking/v2/extensions/security/rules/requests.go

Copy file name to clipboardExpand all lines: openstack/networking/v2/extensions/security/rules/requests.go
+17Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,23 @@ func Create(ctx context.Context, c *gophercloud.ServiceClient, opts CreateOptsBu
150150
return
151151
}
152152

153+
// CreateBulk is an operation which adds new security group rules and associates them
154+
// with an existing security group (whose ID is specified in CreateOpts).
155+
// As of Dalmatian (2024.2) neutron only allows bulk creation of rules when
156+
// they all belong to the same tenant and security group.
157+
// https://github.com/openstack/neutron/blob/6183792/neutron/db/securitygroups_db.py#L814-L828
158+
func CreateBulk(ctx context.Context, c *gophercloud.ServiceClient, opts []CreateOpts) (r CreateBulkResult) {
159+
body, err := gophercloud.BuildRequestBody(opts, "security_group_rules")
160+
if err != nil {
161+
r.Err = err
162+
return
163+
}
164+
165+
resp, err := c.Post(ctx, rootURL(c), body, &r.Body, nil)
166+
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
167+
return
168+
}
169+
153170
// Get retrieves a particular security group rule based on its unique ID.
154171
func Get(ctx context.Context, c *gophercloud.ServiceClient, id string) (r GetResult) {
155172
resp, err := c.Get(ctx, resourceURL(c, id), &r.Body, nil)

‎openstack/networking/v2/extensions/security/rules/results.go

Copy file name to clipboardExpand all lines: openstack/networking/v2/extensions/security/rules/results.go
+19Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ type commonResult struct {
103103
gophercloud.Result
104104
}
105105

106+
type bulkResult struct {
107+
gophercloud.Result
108+
}
109+
106110
// Extract is a function that accepts a result and extracts a security rule.
107111
func (r commonResult) Extract() (*SecGroupRule, error) {
108112
var s struct {
@@ -112,12 +116,27 @@ func (r commonResult) Extract() (*SecGroupRule, error) {
112116
return s.SecGroupRule, err
113117
}
114118

119+
// Extract is a function that accepts a result and extracts security rules.
120+
func (r bulkResult) Extract() ([]SecGroupRule, error) {
121+
var s struct {
122+
SecGroupRules []SecGroupRule `json:"security_group_rules"`
123+
}
124+
err := r.ExtractInto(&s)
125+
return s.SecGroupRules, err
126+
}
127+
115128
// CreateResult represents the result of a create operation. Call its Extract
116129
// method to interpret it as a SecGroupRule.
117130
type CreateResult struct {
118131
commonResult
119132
}
120133

134+
// CreateBulkResult represents the result of a bulk create operation. Call its
135+
// Extract method to interpret it as a slice of SecGroupRules.
136+
type CreateBulkResult struct {
137+
bulkResult
138+
}
139+
121140
// GetResult represents the result of a get operation. Call its Extract
122141
// method to interpret it as a SecGroupRule.
123142
type GetResult struct {

‎openstack/networking/v2/extensions/security/rules/testing/requests_test.go

Copy file name to clipboardExpand all lines: openstack/networking/v2/extensions/security/rules/testing/requests_test.go
+95Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,101 @@ func TestCreateAnyProtocol(t *testing.T) {
222222
th.AssertNoErr(t, err)
223223
}
224224

225+
func TestCreateBulk(t *testing.T) {
226+
th.SetupHTTP()
227+
defer th.TeardownHTTP()
228+
229+
th.Mux.HandleFunc("/v2.0/security-group-rules", func(w http.ResponseWriter, r *http.Request) {
230+
th.TestMethod(t, r, "POST")
231+
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
232+
th.TestHeader(t, r, "Content-Type", "application/json")
233+
th.TestHeader(t, r, "Accept", "application/json")
234+
th.TestJSONRequest(t, r, `
235+
{
236+
"security_group_rules": [
237+
{
238+
"description": "test description of rule",
239+
"direction": "ingress",
240+
"port_range_min": 80,
241+
"ethertype": "IPv4",
242+
"port_range_max": 80,
243+
"protocol": "tcp",
244+
"remote_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5",
245+
"security_group_id": "a7734e61-b545-452d-a3cd-0189cbd9747a"
246+
},
247+
{
248+
"description": "test description of rule",
249+
"direction": "ingress",
250+
"port_range_min": 443,
251+
"ethertype": "IPv4",
252+
"port_range_max": 443,
253+
"protocol": "tcp",
254+
"security_group_id": "a7734e61-b545-452d-a3cd-0189cbd9747a"
255+
}
256+
]
257+
}
258+
`)
259+
260+
w.Header().Add("Content-Type", "application/json")
261+
w.WriteHeader(http.StatusCreated)
262+
263+
fmt.Fprint(w, `
264+
{
265+
"security_group_rules": [
266+
{
267+
"description": "test description of rule",
268+
"direction": "ingress",
269+
"ethertype": "IPv4",
270+
"port_range_max": 80,
271+
"port_range_min": 80,
272+
"protocol": "tcp",
273+
"remote_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5",
274+
"remote_ip_prefix": null,
275+
"security_group_id": "a7734e61-b545-452d-a3cd-0189cbd9747a",
276+
"tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550"
277+
},
278+
{
279+
"description": "test description of rule",
280+
"direction": "ingress",
281+
"ethertype": "IPv4",
282+
"port_range_max": 443,
283+
"port_range_min": 443,
284+
"protocol": "tcp",
285+
"remote_group_id": null,
286+
"remote_ip_prefix": null,
287+
"security_group_id": "a7734e61-b545-452d-a3cd-0189cbd9747a",
288+
"tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550"
289+
}
290+
]
291+
}
292+
`)
293+
})
294+
295+
opts := []rules.CreateOpts{
296+
{
297+
Description: "test description of rule",
298+
Direction: "ingress",
299+
PortRangeMin: 80,
300+
EtherType: rules.EtherType4,
301+
PortRangeMax: 80,
302+
Protocol: "tcp",
303+
RemoteGroupID: "85cc3048-abc3-43cc-89b3-377341426ac5",
304+
SecGroupID: "a7734e61-b545-452d-a3cd-0189cbd9747a",
305+
},
306+
{
307+
Description: "test description of rule",
308+
Direction: "ingress",
309+
PortRangeMin: 443,
310+
EtherType: rules.EtherType4,
311+
PortRangeMax: 443,
312+
Protocol: "tcp",
313+
SecGroupID: "a7734e61-b545-452d-a3cd-0189cbd9747a",
314+
},
315+
}
316+
_, err := rules.CreateBulk(context.TODO(), fake.ServiceClient(), opts).Extract()
317+
th.AssertNoErr(t, err)
318+
}
319+
225320
func TestRequiredCreateOpts(t *testing.T) {
226321
res := rules.Create(context.TODO(), fake.ServiceClient(), rules.CreateOpts{Direction: rules.DirIngress})
227322
if res.Err == nil {

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.