Skip to content

Commit b419664

Browse files
authored
Functiona tests for Private nat gateway (#649)
Functiona tests for Private nat gateway Summary This PR adds functional tests for private nat gateway. Details Added functional tests for private nat gateway. Added Mixin class to keep common functionality for private dnat rule and snat rule Refactored and moved cleanup_routers helper into base class to reuse it in both test_zone and test_private_nat_gateway Reviewed-by: Anton Sidelnikov
1 parent 2853b71 commit b419664

5 files changed

Lines changed: 328 additions & 33 deletions

File tree

otcextensions/tests/functional/base.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@
1010
# License for the specific language governing permissions and limitations
1111
# under the License.
1212

13+
import logging
1314
import os
1415
import uuid
1516

1617
import openstack.config
1718
from keystoneauth1 import exceptions as _exceptions
1819

1920
from openstack import connection
21+
from openstack import exceptions as sdk_exceptions
2022
from openstack.tests import base
2123
from otcextensions import sdk
2224

@@ -37,6 +39,7 @@ def _get_resource_value(resource_key, default):
3739

3840
IMAGE_NAME = _get_resource_value("image_name", "cirros-0.3.5-x86_64-disk")
3941
FLAVOR_NAME = _get_resource_value("flavor_name", "m1.small")
42+
_logger = logging.getLogger(__name__)
4043

4144

4245
class BaseFunctionalTest(base.TestCase):
@@ -76,6 +79,31 @@ def setUp(self):
7679
)
7780
)
7881

82+
def cleanup_stale_routers(self, prefix):
83+
"""Clean up stale Neutron routers created by functional tests."""
84+
for router in self.conn.network.routers():
85+
if not router.name or not router.name.startswith(prefix):
86+
continue
87+
try:
88+
ports = list(self.conn.network.ports(device_id=router.id))
89+
for port in ports:
90+
fixed_ips = getattr(port, "fixed_ips", []) or []
91+
for fixed_ip in fixed_ips:
92+
subnet_id = fixed_ip.get("subnet_id")
93+
if not subnet_id:
94+
continue
95+
try:
96+
self.conn.network.remove_interface_from_router(
97+
router, subnet_id=subnet_id
98+
)
99+
except sdk_exceptions.SDKException:
100+
pass
101+
self.conn.network.delete_router(router, ignore_missing=True)
102+
except sdk_exceptions.SDKException as e:
103+
_logger.warning(
104+
"Failed to cleanup stale router %s: %s", router.id, str(e)
105+
)
106+
79107

80108
class NetworkBaseFunctionalTest(BaseFunctionalTest):
81109
def create_network(self, prefix="sdk-test"):

otcextensions/tests/functional/osclient/privatenat/v3/test_private_nat_gateway.py

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,107 @@
1111
# under the License.
1212

1313
import json
14+
import uuid
1415

1516
from openstackclient.tests.functional import base
1617

18+
from openstack import connection
19+
from openstack import exceptions as sdk_exceptions
20+
from openstack import resource
21+
from otcextensions import sdk
22+
from otcextensions.tests.functional import base as sdk_base
23+
from otcextensions.tests.functional.privatenat import PrivateNatEnvironmentMixin
1724

18-
class TestPrivateNatGateway(base.TestCase):
19-
"""Functional Tests for Private NAT Gateway"""
25+
26+
class TestPrivateNatGateway(PrivateNatEnvironmentMixin, base.TestCase):
27+
"""Functional tests for private NAT gateway CLI commands."""
2028

2129
def setUp(self):
2230
super(TestPrivateNatGateway, self).setUp()
31+
self.conn = connection.Connection(config=sdk_base.TEST_CLOUD_REGION)
32+
sdk.register_otc_extensions(self.conn)
33+
34+
def _create_gateway(self, prefix, description):
35+
stack = self._create_private_nat_network_stack(prefix)
36+
self.addCleanup(self._cleanup_private_nat_network_stack, stack)
37+
gateway_name = "{prefix}-{suffix}".format(
38+
prefix=prefix, suffix=uuid.uuid4().hex[:8]
39+
)
40+
41+
gateway = json.loads(
42+
self.openstack(
43+
"privatenat gateway create "
44+
"--name {name} "
45+
"--description {description} "
46+
"--downlink-vpc virsubnet_id={virsubnet_id} "
47+
"-f json".format(
48+
name=gateway_name,
49+
description=description,
50+
virsubnet_id=stack["subnet"].id,
51+
)
52+
)
53+
)
54+
self.addCleanup(self._delete_gateway, gateway["id"])
55+
return {"stack": stack, "gateway": gateway}
56+
57+
def _delete_gateway(self, gateway_id):
58+
try:
59+
gateway = self.conn.natv3.get_private_nat_gateway(gateway_id)
60+
except sdk_exceptions.ResourceNotFound:
61+
return
62+
63+
self.conn.natv3.delete_private_nat_gateway(gateway_id, ignore_missing=True)
64+
resource.wait_for_delete(self.conn.natv3, gateway, 2, 120)
2365

2466
def test_private_nat_gateway_list(self):
2567
json_output = json.loads(self.openstack("privatenat gateway list -f json "))
2668
self.assertIsNotNone(json_output)
69+
70+
def test_private_nat_gateway_create(self):
71+
env = self._create_gateway("cli-private-nat-create", "cli-private-nat-create")
72+
created = env["gateway"]
73+
74+
self.assertIsNotNone(created)
75+
self.assertEqual("cli-private-nat-create", created["description"])
76+
77+
def test_private_nat_gateway_show(self):
78+
env = self._create_gateway("cli-private-nat-show", "cli-private-nat-show")
79+
gateway_id = env["gateway"]["id"]
80+
81+
shown = json.loads(
82+
self.openstack("privatenat gateway show -f json " + gateway_id)
83+
)
84+
self.assertEqual(gateway_id, shown["id"])
85+
self.assertEqual("cli-private-nat-show", shown["description"])
86+
87+
def test_private_nat_gateway_update(self):
88+
env = self._create_gateway(
89+
"cli-private-nat-update", "cli-private-nat-before-update"
90+
)
91+
gateway_id = env["gateway"]["id"]
92+
93+
updated = json.loads(
94+
self.openstack(
95+
"privatenat gateway update "
96+
"--description cli-private-nat-after-update "
97+
"{gateway_id} -f json".format(gateway_id=gateway_id)
98+
)
99+
)
100+
self.assertEqual(gateway_id, updated["id"])
101+
self.assertEqual("cli-private-nat-after-update", updated["description"])
102+
103+
shown = json.loads(
104+
self.openstack("privatenat gateway show -f json " + gateway_id)
105+
)
106+
self.assertEqual("cli-private-nat-after-update", shown["description"])
107+
108+
def test_private_nat_gateway_delete(self):
109+
env = self._create_gateway("cli-private-nat-delete", "cli-private-nat-delete")
110+
gateway_id = env["gateway"]["id"]
111+
gateway = self.conn.natv3.get_private_nat_gateway(gateway_id)
112+
113+
self.openstack("privatenat gateway delete " + gateway_id)
114+
resource.wait_for_delete(self.conn.natv3, gateway, 2, 120)
115+
116+
listed = json.loads(self.openstack("privatenat gateway list -f json "))
117+
self.assertFalse(any(item["Id"] == gateway_id for item in listed))
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
2+
# not use this file except in compliance with the License. You may obtain
3+
# a copy of the License at
4+
#
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10+
# License for the specific language governing permissions and limitations
11+
# under the License.
12+
13+
import uuid
14+
15+
from openstack import exceptions as sdk_exceptions
16+
from openstack import resource
17+
from otcextensions.tests.functional import base
18+
19+
20+
class PrivateNatEnvironmentMixin(object):
21+
def _private_transit_ip_id(self):
22+
transit_ip_id = base._get_resource_value("private_transit_ip_id", None)
23+
if not transit_ip_id:
24+
self.skipTest("functional.private_transit_ip_id is required")
25+
return transit_ip_id
26+
27+
def _create_private_nat_network_stack(self, prefix):
28+
cidr = "192.168.0.0/16"
29+
suffix = uuid.uuid4().hex[:8]
30+
vpc_name = "{prefix}-vpc-{suffix}".format(prefix=prefix, suffix=suffix)
31+
subnet_name = "{prefix}-subnet-{suffix}".format(prefix=prefix, suffix=suffix)
32+
33+
vpc = self.conn.vpc.create_vpc(name=vpc_name, cidr=cidr)
34+
gw, _ = cidr.split("/")
35+
subnet = self.conn.vpc.create_subnet(
36+
name=subnet_name,
37+
vpc_id=vpc.id,
38+
cidr=vpc.cidr,
39+
gateway_ip=gw[:-2] + ".1",
40+
dns_list=["100.125.4.25", "100.125.129.199"],
41+
)
42+
resource.wait_for_status(self.conn.vpc, subnet, "ACTIVE", None, 2, 60)
43+
44+
return {
45+
"vpc": vpc,
46+
"subnet": subnet,
47+
"network_id": subnet.neutron_network_id,
48+
}
49+
50+
def _cleanup_private_nat_network_stack(self, stack):
51+
subnet = stack.get("subnet")
52+
vpc = stack.get("vpc")
53+
54+
if subnet:
55+
try:
56+
subnet = self.conn.vpc.get_subnet(subnet.id)
57+
except sdk_exceptions.ResourceNotFound:
58+
subnet = None
59+
if subnet:
60+
resource.wait_for_status(self.conn.vpc, subnet, "ACTIVE", None, 2, 60)
61+
self.conn.vpc.delete_subnet(subnet, ignore_missing=True)
62+
resource.wait_for_delete(self.conn.vpc, subnet, 2, 120)
63+
64+
if vpc:
65+
try:
66+
vpc = self.conn.vpc.get_vpc(vpc.id)
67+
except sdk_exceptions.ResourceNotFound:
68+
vpc = None
69+
if vpc:
70+
self.conn.vpc.delete_vpc(vpc, ignore_missing=True)
71+
resource.wait_for_delete(self.conn.vpc, vpc, 2, 120)
72+
73+
def _create_private_nat_gateway(self, subnet_id, prefix):
74+
gateway = self.conn.natv3.create_private_nat_gateway(
75+
name="{prefix}-gateway-{suffix}".format(
76+
prefix=prefix, suffix=uuid.uuid4().hex[:8]
77+
),
78+
spec="Small",
79+
downlink_vpcs=[{"virsubnet_id": subnet_id}],
80+
)
81+
resource.wait_for_status(self.conn.natv3, gateway, "ACTIVE", None, 2, 120)
82+
return gateway
83+
84+
def _delete_private_nat_gateway(self, gateway):
85+
try:
86+
gateway = self.conn.natv3.get_private_nat_gateway(gateway.id)
87+
except sdk_exceptions.ResourceNotFound:
88+
return
89+
90+
self.conn.natv3.delete_private_nat_gateway(gateway, ignore_missing=True)
91+
resource.wait_for_delete(self.conn.natv3, gateway, 2, 120)
92+
93+
def _create_private_nat_port(self, network_id, prefix):
94+
if hasattr(self, "create_port"):
95+
return self.create_port(network_id, prefix=prefix)
96+
return self.conn.network.create_port(
97+
name="{prefix}-port-{suffix}".format(
98+
prefix=prefix, suffix=uuid.uuid4().hex[:8]
99+
),
100+
network_id=network_id,
101+
)
102+
103+
def _delete_private_nat_port(self, port):
104+
if hasattr(self, "destroy_port"):
105+
try:
106+
self.destroy_port(port)
107+
except sdk_exceptions.ResourceNotFound:
108+
return
109+
return
110+
self.conn.network.delete_port(port, ignore_missing=True)
111+
112+
def _prepare_private_nat_environment(self, prefix):
113+
env = {
114+
"transit_ip_id": self._private_transit_ip_id(),
115+
}
116+
117+
stack = self._prepare_private_nat_gateway_environment(prefix)
118+
env["stack"] = stack["stack"]
119+
env["gateway"] = stack["gateway"]
120+
121+
port = self._create_private_nat_port(stack["stack"]["network_id"], prefix)
122+
self.addCleanup(self._delete_private_nat_port, port)
123+
env["port"] = port
124+
return env
125+
126+
def _prepare_private_nat_gateway_environment(self, prefix):
127+
stack = self._create_private_nat_network_stack(prefix)
128+
self.addCleanup(self._cleanup_private_nat_network_stack, stack)
129+
130+
gateway = self._create_private_nat_gateway(stack["subnet"].id, prefix)
131+
self.addCleanup(self._delete_private_nat_gateway, gateway)
132+
return {"stack": stack, "gateway": gateway}

otcextensions/tests/functional/sdk/dns/v2/test_zone.py

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
class TestZone(TestDns):
2121
def setUp(self):
2222
super(TestZone, self).setUp()
23-
self._cleanup_routers()
23+
self.cleanup_stale_routers("sdk-dns-test-add-router-")
2424
self.zone = None
2525
self.networks = []
2626

@@ -52,32 +52,6 @@ def tearDown(self):
5252

5353
super(TestZone, self).tearDown()
5454

55-
def _cleanup_routers(self):
56-
"""Clean up router resources if they are not deleted for any reason."""
57-
for router in self.conn.network.routers():
58-
if router.name and router.name.startswith("sdk-dns-test-add-router-"):
59-
try:
60-
ports = list(self.conn.network.ports(device_id=router.id))
61-
for port in ports:
62-
fixed_ips = getattr(port, "fixed_ips", []) or []
63-
for fixed_ip in fixed_ips:
64-
subnet_id = fixed_ip.get("subnet_id")
65-
if subnet_id:
66-
try:
67-
self.conn.network.remove_interface_from_router(
68-
router, subnet_id=subnet_id
69-
)
70-
except openstack.exceptions.SDKException:
71-
pass
72-
73-
self.conn.network.delete_router(router, ignore_missing=True)
74-
except openstack.exceptions.SDKException as e:
75-
_logger.warning(
76-
"Failed to cleanup stale router %s: %s",
77-
router.id,
78-
str(e),
79-
)
80-
8155
def _create_zone(self, zone_name, router_id=None, zone_type="public"):
8256
attrs = {"name": zone_name}
8357

0 commit comments

Comments
 (0)