Skip to content

Commit e5a36f9

Browse files
committed
Add private snat rule create endpoint
1 parent b419664 commit e5a36f9

11 files changed

Lines changed: 282 additions & 1 deletion

File tree

doc/source/sdk/guides/privatenat.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,12 @@ the output with query parameters.
129129

130130
.. literalinclude:: ../examples/natv3/list_private_snat_rules.py
131131
:lines: 16-23
132+
133+
Create Private SNAT Rule
134+
^^^^^^^^^^^^^^^^^^^^^^^^
135+
136+
This interface is used to create an SNAT rule.
137+
:class:`~otcextensions.sdk.natv3.v3.snat.PrivateSnat`.
138+
139+
.. literalinclude:: ../examples/natv3/create_private_snat_rule.py
140+
:lines: 16-28

doc/source/sdk/proxies/privatenat.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ Private NAT Gateway Operations
1616

1717
.. autoclass:: otcextensions.sdk.natv3.v3._proxy.Proxy
1818
:noindex:
19-
:members: private_nat_gateways, get_private_nat_gateway, private_dnat_rules, private_snat_rules
19+
:members: private_nat_gateways, get_private_nat_gateway, private_dnat_rules, create_private_dnat_rule, private_snat_rules, create_private_snat_rule
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#!/usr/bin/env python3
2+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
3+
# not use this file except in compliance with the License. You may obtain
4+
# a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11+
# License for the specific language governing permissions and limitations
12+
# under the License.
13+
"""
14+
Create a Private SNAT Rule
15+
"""
16+
17+
import openstack
18+
19+
openstack.enable_logging(True)
20+
conn = openstack.connect(cloud="otc")
21+
22+
snat_rule_attrs = {
23+
"gateway_id": "80da6f26-94eb-4537-97f0-5a56f4d04cfb",
24+
"virsubnet_id": "5b9ea497-727d-4ad0-a99e-3984b3f5aaed",
25+
"transit_ip_ids": ["36a3049a-1682-48b3-b1cf-cb986a3350ef"],
26+
"description": "my_snat_rule01",
27+
}
28+
29+
snat_rule = conn.natv3.create_private_snat_rule(**snat_rule_attrs)
30+
print(snat_rule)

otcextensions/osclient/privatenat/v3/snat.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,21 @@
1414

1515
import logging
1616

17+
from osc_lib import exceptions
1718
from osc_lib import utils
1819
from osc_lib.command import command
1920

21+
from otcextensions.common import sdk_utils
2022
from otcextensions.i18n import _
2123

2224
LOG = logging.getLogger(__name__)
2325

2426

27+
def _get_columns(item):
28+
column_map = {}
29+
return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map)
30+
31+
2532
def _get_transit_ip_addresses(rule):
2633
return ", ".join(
2734
association.transit_ip_address
@@ -190,3 +197,84 @@ def take_action(self, parsed_args):
190197
for s in data
191198
),
192199
)
200+
201+
202+
class CreatePrivateSnatRule(command.ShowOne):
203+
204+
_description = _("Create a private SNAT rule.")
205+
206+
def get_parser(self, prog_name):
207+
parser = super(CreatePrivateSnatRule, self).get_parser(prog_name)
208+
209+
parser.add_argument(
210+
"--gateway-id",
211+
metavar="<gateway_id>",
212+
required=True,
213+
help=_("Specifies the private NAT gateway ID."),
214+
)
215+
parser.add_argument(
216+
"--cidr",
217+
metavar="<cidr>",
218+
help=_("Specifies the CIDR block that matches the SNAT rule."),
219+
)
220+
parser.add_argument(
221+
"--virsubnet-id",
222+
metavar="<virsubnet_id>",
223+
help=_("Specifies the ID of the subnet that matches the SNAT rule."),
224+
)
225+
parser.add_argument(
226+
"--description",
227+
metavar="<description>",
228+
help=_("Provides supplementary information about the SNAT rule."),
229+
)
230+
parser.add_argument(
231+
"--transit-ip-id",
232+
metavar="<transit_ip_id>",
233+
action="append",
234+
dest="transit_ip_ids",
235+
required=True,
236+
help=_(
237+
"Specifies the transit IP address ID. "
238+
"Repeat to associate multiple transit IPs."
239+
),
240+
)
241+
return parser
242+
243+
def _build_attrs(self, parsed_args):
244+
attrs = {
245+
"gateway_id": parsed_args.gateway_id,
246+
"transit_ip_ids": parsed_args.transit_ip_ids,
247+
}
248+
249+
if len(parsed_args.transit_ip_ids) > 20:
250+
raise exceptions.CommandError(
251+
_("A maximum number of 20 --transit-ip-id values is allowed.")
252+
)
253+
254+
if parsed_args.cidr and parsed_args.virsubnet_id:
255+
raise exceptions.CommandError(
256+
_("Specify either --cidr or --virsubnet-id, but not both.")
257+
)
258+
259+
if not parsed_args.cidr and not parsed_args.virsubnet_id:
260+
raise exceptions.CommandError(
261+
_("One of --cidr or --virsubnet-id must be specified.")
262+
)
263+
264+
if parsed_args.cidr:
265+
attrs["cidr"] = parsed_args.cidr
266+
if parsed_args.virsubnet_id:
267+
attrs["virsubnet_id"] = parsed_args.virsubnet_id
268+
if parsed_args.description is not None:
269+
attrs["description"] = parsed_args.description
270+
271+
return attrs
272+
273+
def take_action(self, parsed_args):
274+
client = self.app.client_manager.privatenat
275+
attrs = self._build_attrs(parsed_args)
276+
obj = client.create_private_snat_rule(**attrs)
277+
278+
display_columns, columns = _get_columns(obj)
279+
data = utils.get_item_properties(obj, columns)
280+
return display_columns, data

otcextensions/sdk/natv3/v3/_proxy.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,3 +179,16 @@ def private_snat_rules(self, **query):
179179
:class:`~otcextensions.sdk.natv3.v3.snat.PrivateSnat`
180180
"""
181181
return self._list(_snat.PrivateSnat, **query)
182+
183+
def create_private_snat_rule(self, **attrs):
184+
"""Create a private SNAT rule from attributes.
185+
186+
:param dict attrs: Keyword arguments which will be used to create
187+
a :class:`~otcextensions.sdk.natv3.v3.snat.PrivateSnat`,
188+
comprised of the properties on the PrivateSnat class.
189+
190+
:returns: The result of private SNAT rule creation.
191+
192+
:rtype: :class:`~otcextensions.sdk.natv3.v3.snat.PrivateSnat`
193+
"""
194+
return self._create(_snat.PrivateSnat, **attrs)

otcextensions/sdk/natv3/v3/snat.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class PrivateSnat(resource.Resource):
2727
base_path = "/private-nat/snat-rules"
2828

2929
# capabilities
30+
allow_create = True
3031
allow_list = True
3132

3233
_query_mapping = resource.QueryParameters(
@@ -62,6 +63,10 @@ class PrivateSnat(resource.Resource):
6263
#: Provides supplementary information about the SNAT rule.
6364
description = resource.Body("description")
6465

66+
#: Specifies the IDs of associated transit IP addresses.
67+
#: Accepted on create and update requests.
68+
transit_ip_ids = resource.Body("transit_ip_ids", type=list)
69+
6570
#: Specifies the list of details of associated transit IP addresses.
6671
transit_ip_associations = resource.Body(
6772
"transit_ip_associations", type=list, list_type=AssociatedTransitIp

otcextensions/tests/unit/osclient/privatenat/v3/fakes.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ def generate(cls):
119119
"transit_ip_address": "172.20.1.10",
120120
}
121121
],
122+
"transit_ip_ids": [],
122123
"enterprise_project_id": "ep-" + uuid.uuid4().hex,
123124
"created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f"),
124125
"updated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f"),

otcextensions/tests/unit/osclient/privatenat/v3/test_snat.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
from unittest import mock
1414

15+
from osc_lib import exceptions
16+
1517
from otcextensions.osclient.privatenat.v3 import snat
1618
from otcextensions.tests.unit.osclient.privatenat.v3 import fakes
1719

@@ -128,3 +130,130 @@ def test_list_with_supported_filters(self):
128130
)
129131
self.assertEqual(self.columns, columns)
130132
self.assertEqual(self.data, list(data))
133+
134+
135+
class TestCreatePrivateSnatRule(fakes.TestPrivateNat):
136+
_data = fakes.FakePrivateSnatRule.create_one()
137+
138+
def setUp(self):
139+
super(TestCreatePrivateSnatRule, self).setUp()
140+
self.cmd = snat.CreatePrivateSnatRule(self.app, None)
141+
self.client.create_private_snat_rule = mock.Mock(return_value=self._data)
142+
143+
def test_create_with_virsubnet(self):
144+
arglist = [
145+
"--gateway-id",
146+
self._data.gateway_id,
147+
"--virsubnet-id",
148+
self._data.virsubnet_id,
149+
"--transit-ip-id",
150+
"tip-1",
151+
"--transit-ip-id",
152+
"tip-2",
153+
"--description",
154+
self._data.description,
155+
]
156+
verifylist = [
157+
("gateway_id", self._data.gateway_id),
158+
("virsubnet_id", self._data.virsubnet_id),
159+
("transit_ip_ids", ["tip-1", "tip-2"]),
160+
("description", self._data.description),
161+
]
162+
163+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
164+
columns, data = self.cmd.take_action(parsed_args)
165+
166+
self.client.create_private_snat_rule.assert_called_once_with(
167+
gateway_id=self._data.gateway_id,
168+
virsubnet_id=self._data.virsubnet_id,
169+
transit_ip_ids=["tip-1", "tip-2"],
170+
description=self._data.description,
171+
)
172+
self.assertEqual(len(columns), len(data))
173+
self.assertIn("id", columns)
174+
175+
def test_create_with_cidr(self):
176+
arglist = [
177+
"--gateway-id",
178+
self._data.gateway_id,
179+
"--cidr",
180+
"10.1.1.64/30",
181+
"--transit-ip-id",
182+
"tip-1",
183+
]
184+
verifylist = [
185+
("gateway_id", self._data.gateway_id),
186+
("cidr", "10.1.1.64/30"),
187+
("transit_ip_ids", ["tip-1"]),
188+
]
189+
190+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
191+
self.cmd.take_action(parsed_args)
192+
193+
self.client.create_private_snat_rule.assert_called_once_with(
194+
gateway_id=self._data.gateway_id,
195+
cidr="10.1.1.64/30",
196+
transit_ip_ids=["tip-1"],
197+
)
198+
199+
def test_create_requires_cidr_or_virsubnet(self):
200+
arglist = [
201+
"--gateway-id",
202+
self._data.gateway_id,
203+
"--transit-ip-id",
204+
"tip-1",
205+
]
206+
verifylist = [
207+
("gateway_id", self._data.gateway_id),
208+
("transit_ip_ids", ["tip-1"]),
209+
]
210+
211+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
212+
213+
with self.assertRaises(exceptions.CommandError):
214+
self.cmd.take_action(parsed_args)
215+
216+
def test_create_both_cidr_and_virsubnet(self):
217+
arglist = [
218+
"--gateway-id",
219+
self._data.gateway_id,
220+
"--cidr",
221+
"10.1.1.64/30",
222+
"--virsubnet-id",
223+
self._data.virsubnet_id,
224+
"--transit-ip-id",
225+
"tip-1",
226+
]
227+
verifylist = [
228+
("gateway_id", self._data.gateway_id),
229+
("cidr", "10.1.1.64/30"),
230+
("virsubnet_id", self._data.virsubnet_id),
231+
("transit_ip_ids", ["tip-1"]),
232+
]
233+
234+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
235+
236+
with self.assertRaises(exceptions.CommandError):
237+
self.cmd.take_action(parsed_args)
238+
239+
def test_create_more_than_20_transit_ip_ids(self):
240+
arglist = [
241+
"--gateway-id",
242+
self._data.gateway_id,
243+
"--virsubnet-id",
244+
self._data.virsubnet_id,
245+
]
246+
verifylist = [
247+
("gateway_id", self._data.gateway_id),
248+
("virsubnet_id", self._data.virsubnet_id),
249+
]
250+
251+
for i in range(21):
252+
arglist.extend(["--transit-ip-id", "tip-%s" % i])
253+
254+
verifylist.append(("transit_ip_ids", ["tip-%s" % i for i in range(21)]))
255+
256+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
257+
258+
with self.assertRaises(exceptions.CommandError):
259+
self.cmd.take_action(parsed_args)

otcextensions/tests/unit/sdk/natv3/v3/test_proxy.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,6 @@ class TestPrivateSnat(TestNatv3Proxy):
6969

7070
def test_private_snat_rules(self):
7171
self.verify_list(self.proxy.private_snat_rules, snat.PrivateSnat)
72+
73+
def test_create_private_snat_rule(self):
74+
self.verify_create(self.proxy.create_private_snat_rule, snat.PrivateSnat)

otcextensions/tests/unit/sdk/natv3/v3/test_snat.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ def test_basic(self):
4242
self.assertEqual("snat_rule", sot.resource_key)
4343
self.assertEqual("snat_rules", sot.resources_key)
4444
self.assertEqual("/private-nat/snat-rules", sot.base_path)
45+
self.assertTrue(sot.allow_create)
4546
self.assertTrue(sot.allow_list)
4647

4748
def test_make_it(self):
@@ -52,6 +53,7 @@ def test_make_it(self):
5253
self.assertEqual(EXAMPLE["gateway_id"], sot.gateway_id)
5354
self.assertEqual(EXAMPLE["cidr"], sot.cidr)
5455
self.assertEqual(EXAMPLE["virsubnet_id"], sot.virsubnet_id)
56+
self.assertIsNone(sot.transit_ip_ids)
5557
self.assertEqual(1, len(sot.transit_ip_associations))
5658
self.assertIsInstance(sot.transit_ip_associations[0], snat.AssociatedTransitIp)
5759
self.assertEqual(

0 commit comments

Comments
 (0)