Skip to content

Commit f290fdb

Browse files
committed
Add create private nat gateway endpoint
1 parent 8549ee9 commit f290fdb

11 files changed

Lines changed: 386 additions & 15 deletions

File tree

doc/source/sdk/guides/privatenat.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,12 @@ This interface is used to get a Private NAT gateway by ID
3333

3434
.. literalinclude:: ../examples/natv3/get_private_gateway.py
3535
:lines: 16-24
36+
37+
Create Private NAT Gateway
38+
^^^^^^^^^^^^^^^^^^^^^^^^^^
39+
40+
This interface is used to create a Private NAT gateway
41+
:class:`~otcextensions.sdk.natv3.v3.gateway.Gateway`.
42+
43+
.. literalinclude:: ../examples/natv3/create_private_gateway.py
44+
:lines: 16-35
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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 NAT Gateway
15+
"""
16+
17+
import openstack
18+
19+
openstack.enable_logging(True)
20+
conn = openstack.connect(cloud="otc")
21+
22+
downlink_vpc = {
23+
"vpc_id": "3cb66d44-9f75-4237-bfff-e37b14d23ad2",
24+
"virsubnet_id": "373979ee-f4f0-46c5-80e3-0fbf72646b70",
25+
"ngport_ip_address": "10.0.0.17",
26+
}
27+
gateway_attrs = {
28+
"name": "nat_001",
29+
"description": "my private nat gateway 01",
30+
"downlink_vpcs": [downlink_vpc],
31+
"spec": "1",
32+
}
33+
34+
gateway = conn.natv3.create_private_nat_gateway(**gateway_attrs)
35+
print(gateway)

otcextensions/common/utils.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,3 +170,23 @@ def extract_url_parts(url: str, project_id: str) -> list:
170170

171171
# Strip out empty or None segments and return
172172
return [part for part in url_parts if part]
173+
174+
175+
def normalize_tags(tags):
176+
"""Convert list of KEY=VALUE strings to API tag dicts."""
177+
result = []
178+
179+
for tag in tags:
180+
if "=" not in tag:
181+
raise ValueError("Invalid tag '%s'. Expected format key=value" % tag)
182+
183+
key, value = tag.split("=", 1)
184+
key = key.strip()
185+
value = value.strip()
186+
187+
if not key:
188+
raise ValueError("Tag key cannot be empty")
189+
190+
result.append({"key": key, "value": value})
191+
192+
return result

otcextensions/osclient/cbr/v3/vault.py

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from osc_lib.cli import parseractions
2020
from osc_lib.command import command
2121

22+
from otcextensions.common.utils import normalize_tags
2223
from otcextensions.i18n import _
2324

2425
LOG = logging.getLogger(__name__)
@@ -116,17 +117,6 @@ def _add_associated_resources_to_vault_obj(data, columns):
116117
return return_data, columns
117118

118119

119-
def _normalize_tags(tags):
120-
result = []
121-
for tag in tags:
122-
try:
123-
tag = tag.split("=")
124-
result.append({"key": tag[0], "value": tag[1]})
125-
except IndexError:
126-
result.append({"key": tag[0], "value": ""})
127-
return result
128-
129-
130120
class ListVaults(command.Lister):
131121
_description = _("List CBR Vaults")
132122
columns = ("ID", "name", "backup_policy_id", "description", "created_at")
@@ -428,15 +418,15 @@ def take_action(self, parsed_args):
428418
attrs["backup_policy_id"] = parsed_args.backup_policy
429419
if parsed_args.bind_rule:
430420
attrs["bind_rules"] = {"tags": []}
431-
attrs["bind_rules"]["tags"] = _normalize_tags(parsed_args.bind_rule)
421+
attrs["bind_rules"]["tags"] = normalize_tags(parsed_args.bind_rule)
432422
if parsed_args.description:
433423
attrs["description"] = parsed_args.description
434424
if parsed_args.enterprise_project_id:
435425
attrs["enterprise_project_id"] = parsed_args.enterprise_project_id
436426
if parsed_args.auto_bind:
437427
attrs["auto_bind"] = parsed_args.auto_bind
438428
if parsed_args.tag:
439-
attrs["tags"] = _normalize_tags(parsed_args.tag)
429+
attrs["tags"] = normalize_tags(parsed_args.tag)
440430

441431
client = self.app.client_manager.cbr
442432
obj = client.create_vault(**attrs)
@@ -547,7 +537,7 @@ def take_action(self, parsed_args):
547537
attrs["auto_bind"] = parsed_args.auto_bind
548538
if parsed_args.bind_rule:
549539
attrs["bind_rules"] = {"tags": []}
550-
attrs["bind_rules"]["tags"] = _normalize_tags(parsed_args.bind_rule)
540+
attrs["bind_rules"]["tags"] = normalize_tags(parsed_args.bind_rule)
551541
if parsed_args.auto_expand:
552542
attrs["auto_expand"] = parsed_args.auto_expand
553543
if parsed_args.smn_notify:

otcextensions/osclient/privatenat/v3/private_nat_gateway.py

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
import logging
1616

1717
from osc_lib import utils
18+
from osc_lib.cli import parseractions
1819
from osc_lib.command import command
1920

2021
from otcextensions.common import sdk_utils
22+
from otcextensions.common.utils import normalize_tags
2123
from otcextensions.i18n import _
2224

2325
LOG = logging.getLogger(__name__)
@@ -28,6 +30,51 @@ def _get_columns(item):
2830
return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map)
2931

3032

33+
def _flatten_private_nat_gateway(obj):
34+
return {
35+
"id": obj.id,
36+
"name": obj.name,
37+
"description": obj.description,
38+
"spec": obj.spec,
39+
"project_id": obj.project_id,
40+
"enterprise_project_id": obj.enterprise_project_id,
41+
"status": obj.status,
42+
"created_at": obj.created_at,
43+
"updated_at": obj.updated_at,
44+
"rule_max": obj.rule_max,
45+
"transit_ip_pool_size_max": obj.transit_ip_pool_size_max,
46+
}
47+
48+
49+
def _add_downlink_vpcs_to_obj(obj, data, columns):
50+
for index, downlink_vpc in enumerate(obj.downlink_vpcs or [], start=1):
51+
data += (downlink_vpc.get("vpc_id", ""),)
52+
columns += ("downlink_vpc_id_%s" % index,)
53+
54+
data += (downlink_vpc.get("virsubnet_id", ""),)
55+
columns += ("downlink_virsubnet_id_%s" % index,)
56+
57+
data += (downlink_vpc.get("ngport_ip_address", ""),)
58+
columns += ("downlink_ngport_ip_address_%s" % index,)
59+
60+
return data, columns
61+
62+
63+
def _add_tags_to_obj(obj, data, columns):
64+
data += (
65+
"\n".join(
66+
"key=%s, value=%s"
67+
% (
68+
tag.get("key", ""),
69+
tag.get("value", ""),
70+
)
71+
for tag in obj.tags
72+
),
73+
)
74+
columns += ("tags",)
75+
return data, columns
76+
77+
3178
class ListPrivateNatGateways(command.Lister):
3279

3380
_description = _("List Private NAT Gateways.")
@@ -177,3 +224,123 @@ def take_action(self, parsed_args):
177224
data = utils.get_item_properties(obj, columns)
178225

179226
return display_columns, data
227+
228+
229+
class CreatePrivateNatGateway(command.ShowOne):
230+
_description = _("Create new Private NAT Gateway")
231+
columns = (
232+
"id",
233+
"name",
234+
"description",
235+
"spec",
236+
"project_id",
237+
"enterprise_project_id",
238+
"status",
239+
"created_at",
240+
"updated_at",
241+
"rule_max",
242+
"transit_ip_pool_size_max",
243+
)
244+
245+
def get_parser(self, prog_name):
246+
parser = super(CreatePrivateNatGateway, self).get_parser(prog_name)
247+
parser.add_argument(
248+
"--name",
249+
metavar="<name>",
250+
required=True,
251+
help=_("Specifies the name of the Private NAT Gateway."),
252+
)
253+
parser.add_argument(
254+
"--description",
255+
metavar="<description>",
256+
help=_(
257+
"Provides supplementary information about " "the Private NAT Gateway."
258+
),
259+
)
260+
parser.add_argument(
261+
"--spec",
262+
metavar="<spec>",
263+
default="Small",
264+
help=_(
265+
"Specifies the type of the Private NAT Gateway. "
266+
"The value can be:\n"
267+
"1: small type, which supports up to 10,000 "
268+
"SNAT connections.\n"
269+
"2: medium type, which supports up to 50,000 "
270+
"SNAT connections.\n"
271+
"3: large type, which supports up to 200,000 "
272+
"SNAT connections.\n"
273+
"4: extra-large type, which supports up to "
274+
"1,000,000 SNAT connections."
275+
),
276+
)
277+
parser.add_argument(
278+
"--enterprise-project-id",
279+
metavar="<enterprise_project_id>",
280+
default=0,
281+
help=_(
282+
"Specifies the ID of the enterprise project"
283+
" that is associated with the private NAT gateway"
284+
" when the private NAT gateway is created."
285+
),
286+
)
287+
parser.add_argument(
288+
"--downlink-vpc",
289+
metavar="<virsubnet_id=virsubnet_id[,ngport_ip_address=ip]>",
290+
action=parseractions.MultiKeyValueAction,
291+
dest="downlink_vpcs",
292+
required_keys=["virsubnet_id"],
293+
optional_keys=["ngport_ip_address"],
294+
help=_("Specifies the VPC where the private NAT gateway works."),
295+
)
296+
parser.add_argument(
297+
"--tags",
298+
metavar="<tags>",
299+
action="append",
300+
help=_(
301+
"Specifies the tag list in KEY=VALUE format."
302+
"Repeat for multiple values."
303+
),
304+
)
305+
306+
return parser
307+
308+
def _build_attrs(self, parsed_args):
309+
attrs = {
310+
"name": parsed_args.name,
311+
"downlink_vpcs": parsed_args.downlink_vpcs,
312+
}
313+
314+
optional_attrs = (
315+
"description",
316+
"spec",
317+
"enterprise_project_id",
318+
)
319+
for key in optional_attrs:
320+
value = getattr(parsed_args, key, None)
321+
if value is not None:
322+
attrs[key] = value
323+
324+
if parsed_args.tags:
325+
attrs["tags"] = normalize_tags(parsed_args.tags)
326+
327+
return attrs
328+
329+
def take_action(self, parsed_args):
330+
client = self.app.client_manager.privatenat
331+
332+
attrs = self._build_attrs(parsed_args)
333+
obj = client.create_private_nat_gateway(**attrs)
334+
335+
columns = self.columns
336+
data = utils.get_dict_properties(
337+
_flatten_private_nat_gateway(obj),
338+
columns,
339+
)
340+
341+
if obj.downlink_vpcs:
342+
data, columns = _add_downlink_vpcs_to_obj(obj, data, columns)
343+
if obj.tags:
344+
data, columns = _add_tags_to_obj(obj, data, columns)
345+
346+
return columns, data

otcextensions/sdk/natv3/v3/_proxy.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,16 @@ def get_private_nat_gateway(self, gateway):
4242
when no resource can be found.
4343
"""
4444
return self._get(_gateway.PrivateNatGateway, gateway)
45+
46+
def create_private_nat_gateway(self, **attrs):
47+
"""Create a new Private gateway from attributes
48+
49+
:param dict attrs: Keyword arguments which will be used to create
50+
a :class:`~otcextensions.sdk.nat.v2.gateway.Gateway`,
51+
comprised of the properties on the Gateway class.
52+
53+
:returns: The results of the Gateway Creation
54+
55+
:rtype: :class:`~otcextensions.sdk.nat.v3.gateway.PrivateNatGateway`
56+
"""
57+
return self._create(_gateway.PrivateNatGateway, **attrs)

otcextensions/sdk/natv3/v3/gateway.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class PrivateNatGateway(resource.Resource):
3939

4040
allow_list = True
4141
allow_fetch = True
42+
allow_create = True
4243

4344
_query_mapping = resource.QueryParameters(
4445
"description",

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

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,29 @@ def generate(cls):
3838
object_info = {
3939
"id": "id-" + uuid.uuid4().hex,
4040
"name": "name-" + uuid.uuid4().hex,
41+
"description": "private_nat_gateway_description",
4142
"spec": "Small",
4243
"status": "ACTIVE",
4344
"project_id": "project-" + uuid.uuid4().hex,
4445
"enterprise_project_id": "ep-" + uuid.uuid4().hex,
46+
"created_at": "2024-01-01T10:00:00",
47+
"updated_at": "2024-01-01T10:00:00",
48+
"rule_max": 20,
49+
"transit_ip_pool_size_max": 1,
50+
"downlink_vpcs": [
51+
{
52+
"vpc_id": "vpc-" + uuid.uuid4().hex,
53+
"virsubnet_id": "subnet-" + uuid.uuid4().hex,
54+
"ngport_ip_address": "192.168.10.10",
55+
}
56+
],
57+
"tags": [
58+
{
59+
"key": "key1",
60+
"value": "value1",
61+
}
62+
],
4563
}
4664

47-
return gateway.PrivateNatGateway(**object_info)
65+
obj = gateway.PrivateNatGateway.existing(**object_info)
66+
return obj

0 commit comments

Comments
 (0)