From 595f4cabae7f9469a00dea8f3317a4a7484ee2e9 Mon Sep 17 00:00:00 2001 From: Tobias Henkel Date: Fri, 14 Sep 2018 15:40:17 +0200 Subject: [PATCH] Add support for configured NAT source variable In some clouds there are more than one potential NAT source and shade's auto_ip can't figure it out propery. os-client-config added a config option similar to nat_destination called nat_source to help people set a config in such environments. Add support for it. This is a port of I4b50c2323a487b5ce90f9d38a48be249cfb739c5 by Monty Taylor from shade to openstacksdk. Change-Id: Ie2d01168a24172f37ebf32f754d8e1a52149cb6e Co-Authored-By: Monty Taylor --- openstack/cloud/openstackcloud.py | 44 +++- .../unit/cloud/test_floating_ip_neutron.py | 194 ++++++++++++++++++ .../nat-source-support-92aaf6b336d0b848.yaml | 4 + 3 files changed, 237 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/nat-source-support-92aaf6b336d0b848.yaml diff --git a/openstack/cloud/openstackcloud.py b/openstack/cloud/openstackcloud.py index f6a1534ab..ae713856b 100755 --- a/openstack/cloud/openstackcloud.py +++ b/openstack/cloud/openstackcloud.py @@ -146,6 +146,7 @@ class OpenStackCloud(_normalize.Normalizer): self._external_ipv6_names = self.config.get_external_ipv6_networks() self._internal_ipv6_names = self.config.get_internal_ipv6_networks() self._nat_destination = self.config.get_nat_destination() + self._nat_source = self.config.get_nat_source() self._default_network = self.config.get_default_network() self._floating_ip_source = self.config.config.get( @@ -2365,6 +2366,7 @@ class OpenStackCloud(_normalize.Normalizer): self._external_ipv6_networks = [] self._internal_ipv6_networks = [] self._nat_destination_network = None + self._nat_source_network = None self._default_network_network = None self._network_list_stamp = False @@ -2375,6 +2377,7 @@ class OpenStackCloud(_normalize.Normalizer): external_ipv6_networks = [] internal_ipv6_networks = [] nat_destination = None + nat_source = None default_network = None all_subnets = None @@ -2406,10 +2409,6 @@ class OpenStackCloud(_normalize.Normalizer): network['id'] not in self._internal_ipv4_names): external_ipv4_networks.append(network) - # External Floating IPv4 networks - if ('router:external' in network and network['router:external']): - external_ipv4_floating_networks.append(network) - # Internal networks if (network['name'] in self._internal_ipv4_names or network['id'] in self._internal_ipv4_names): @@ -2438,6 +2437,25 @@ class OpenStackCloud(_normalize.Normalizer): network['id'] not in self._external_ipv6_names): internal_ipv6_networks.append(network) + # External Floating IPv4 networks + if self._nat_source in ( + network['name'], network['id']): + if nat_source: + raise exc.OpenStackCloudException( + 'Multiple networks were found matching' + ' {nat_net} which is the network configured' + ' to be the NAT source. Please check your' + ' cloud resources. It is probably a good idea' + ' to configure this network by ID rather than' + ' by name.'.format( + nat_net=self._nat_source)) + external_ipv4_floating_networks.append(network) + nat_source = network + elif self._nat_source is None: + if network.get('router:external'): + external_ipv4_floating_networks.append(network) + nat_source = nat_source or network + # NAT Destination if self._nat_destination in ( network['name'], network['id']): @@ -2522,6 +2540,13 @@ class OpenStackCloud(_normalize.Normalizer): ' found'.format( network=self._nat_destination)) + if self._nat_source and not nat_source: + raise exc.OpenStackCloudException( + 'Network {network} was configured to be the' + ' source for inbound NAT but it could not be' + ' found'.format( + network=self._nat_source)) + if self._default_network and not default_network: raise exc.OpenStackCloudException( 'Network {network} was configured to be the' @@ -2535,6 +2560,7 @@ class OpenStackCloud(_normalize.Normalizer): self._external_ipv6_networks = external_ipv6_networks self._internal_ipv6_networks = internal_ipv6_networks self._nat_destination_network = nat_destination + self._nat_source_network = nat_source self._default_network_network = default_network def _find_interesting_networks(self): @@ -2561,6 +2587,14 @@ class OpenStackCloud(_normalize.Normalizer): self._find_interesting_networks() return self._nat_destination_network + def get_nat_source(self): + """Return the network that is configured to be the NAT destination. + + :returns: A network dict if one is found + """ + self._find_interesting_networks() + return self._nat_source_network + def get_default_network(self): """Return the network that is configured to be the default interface. @@ -6413,7 +6447,7 @@ class OpenStackCloud(_normalize.Normalizer): skip_attach = False created = False if reuse: - f_ip = self.available_floating_ip() + f_ip = self.available_floating_ip(server=server) else: start_time = time.time() f_ip = self.create_floating_ip( diff --git a/openstack/tests/unit/cloud/test_floating_ip_neutron.py b/openstack/tests/unit/cloud/test_floating_ip_neutron.py index 8c8b7a66b..bf4871f2c 100644 --- a/openstack/tests/unit/cloud/test_floating_ip_neutron.py +++ b/openstack/tests/unit/cloud/test_floating_ip_neutron.py @@ -998,3 +998,197 @@ class TestFloatingIP(base.TestCase): self.cloud._neutron_create_floating_ip, server=dict(id='some-server')) self.assert_calls() + + def test_find_nat_source_inferred(self): + # payloads contrived but based on ones from citycloud + self.register_uris([ + dict(method='GET', + uri='https://network.example.com/v2.0/networks.json', + json={"networks": [{ + "status": "ACTIVE", + "subnets": [ + "df3e17fa-a4b2-47ae-9015-bc93eb076ba2", + "6b0c3dc9-b0b8-4d87-976a-7f2ebf13e7ec", + "fc541f48-fc7f-48c0-a063-18de6ee7bdd7"], + "availability_zone_hints": [], + "availability_zones": ["nova"], + "name": "ext-net", + "admin_state_up": True, + "tenant_id": "a564613210ee43708b8a7fc6274ebd63", + "tags": [], + "ipv6_address_scope": "9f03124f-89af-483a-b6fd-10f08079db4d", # noqa + "mtu": 0, + "is_default": False, + "router:external": True, + "ipv4_address_scope": None, + "shared": False, + "id": "0232c17f-2096-49bc-b205-d3dcd9a30ebf", + "description": None + }, { + "status": "ACTIVE", + "subnets": [ + "df3e17fa-a4b2-47ae-9015-bc93eb076ba2", + "6b0c3dc9-b0b8-4d87-976a-7f2ebf13e7ec", + "fc541f48-fc7f-48c0-a063-18de6ee7bdd7"], + "availability_zone_hints": [], + "availability_zones": ["nova"], + "name": "my-network", + "admin_state_up": True, + "tenant_id": "a564613210ee43708b8a7fc6274ebd63", + "tags": [], + "ipv6_address_scope": "9f03124f-89af-483a-b6fd-10f08079db4d", # noqa + "mtu": 0, + "is_default": False, + "router:external": True, + "ipv4_address_scope": None, + "shared": False, + "id": "0232c17f-2096-49bc-b205-d3dcd9a30ebg", + "description": None + }, { + "status": "ACTIVE", + "subnets": ["f0ad1df5-53ee-473f-b86b-3604ea5591e9"], + "availability_zone_hints": [], + "availability_zones": ["nova"], + "name": "private", + "admin_state_up": True, + "tenant_id": "65222a4d09ea4c68934fa1028c77f394", + "created_at": "2016-10-22T13:46:26", + "tags": [], + "updated_at": "2016-10-22T13:46:26", + "ipv6_address_scope": None, + "router:external": False, + "ipv4_address_scope": None, + "shared": False, + "mtu": 1450, + "id": "2c9adcb5-c123-4c5a-a2ba-1ad4c4e1481f", + "description": "" + }]}), + dict(method='GET', + uri='https://network.example.com/v2.0/subnets.json', + json={"subnets": [{ + "description": "", + "enable_dhcp": True, + "network_id": "2c9adcb5-c123-4c5a-a2ba-1ad4c4e1481f", + "tenant_id": "65222a4d09ea4c68934fa1028c77f394", + "created_at": "2016-10-22T13:46:26", + "dns_nameservers": [ + "89.36.90.101", + "89.36.90.102"], + "updated_at": "2016-10-22T13:46:26", + "gateway_ip": "10.4.0.1", + "ipv6_ra_mode": None, + "allocation_pools": [{ + "start": "10.4.0.2", + "end": "10.4.0.200"}], + "host_routes": [], + "ip_version": 4, + "ipv6_address_mode": None, + "cidr": "10.4.0.0/24", + "id": "f0ad1df5-53ee-473f-b86b-3604ea5591e9", + "subnetpool_id": None, + "name": "private-subnet-ipv4", + }]}) + ]) + + self.assertEqual( + 'ext-net', self.cloud.get_nat_source()['name']) + + self.assert_calls() + + def test_find_nat_source_config(self): + self.cloud._nat_source = 'my-network' + + # payloads contrived but based on ones from citycloud + self.register_uris([ + dict(method='GET', + uri='https://network.example.com/v2.0/networks.json', + json={"networks": [{ + "status": "ACTIVE", + "subnets": [ + "df3e17fa-a4b2-47ae-9015-bc93eb076ba2", + "6b0c3dc9-b0b8-4d87-976a-7f2ebf13e7ec", + "fc541f48-fc7f-48c0-a063-18de6ee7bdd7"], + "availability_zone_hints": [], + "availability_zones": ["nova"], + "name": "ext-net", + "admin_state_up": True, + "tenant_id": "a564613210ee43708b8a7fc6274ebd63", + "tags": [], + "ipv6_address_scope": "9f03124f-89af-483a-b6fd-10f08079db4d", # noqa + "mtu": 0, + "is_default": False, + "router:external": True, + "ipv4_address_scope": None, + "shared": False, + "id": "0232c17f-2096-49bc-b205-d3dcd9a30ebf", + "description": None + }, { + "status": "ACTIVE", + "subnets": [ + "df3e17fa-a4b2-47ae-9015-bc93eb076ba2", + "6b0c3dc9-b0b8-4d87-976a-7f2ebf13e7ec", + "fc541f48-fc7f-48c0-a063-18de6ee7bdd7"], + "availability_zone_hints": [], + "availability_zones": ["nova"], + "name": "my-network", + "admin_state_up": True, + "tenant_id": "a564613210ee43708b8a7fc6274ebd63", + "tags": [], + "ipv6_address_scope": "9f03124f-89af-483a-b6fd-10f08079db4d", # noqa + "mtu": 0, + "is_default": False, + "router:external": True, + "ipv4_address_scope": None, + "shared": False, + "id": "0232c17f-2096-49bc-b205-d3dcd9a30ebg", + "description": None + }, { + "status": "ACTIVE", + "subnets": ["f0ad1df5-53ee-473f-b86b-3604ea5591e9"], + "availability_zone_hints": [], + "availability_zones": ["nova"], + "name": "private", + "admin_state_up": True, + "tenant_id": "65222a4d09ea4c68934fa1028c77f394", + "created_at": "2016-10-22T13:46:26", + "tags": [], + "updated_at": "2016-10-22T13:46:26", + "ipv6_address_scope": None, + "router:external": False, + "ipv4_address_scope": None, + "shared": False, + "mtu": 1450, + "id": "2c9adcb5-c123-4c5a-a2ba-1ad4c4e1481f", + "description": "" + }]}), + dict(method='GET', + uri='https://network.example.com/v2.0/subnets.json', + json={"subnets": [{ + "description": "", + "enable_dhcp": True, + "network_id": "2c9adcb5-c123-4c5a-a2ba-1ad4c4e1481f", + "tenant_id": "65222a4d09ea4c68934fa1028c77f394", + "created_at": "2016-10-22T13:46:26", + "dns_nameservers": [ + "89.36.90.101", + "89.36.90.102"], + "updated_at": "2016-10-22T13:46:26", + "gateway_ip": "10.4.0.1", + "ipv6_ra_mode": None, + "allocation_pools": [{ + "start": "10.4.0.2", + "end": "10.4.0.200"}], + "host_routes": [], + "ip_version": 4, + "ipv6_address_mode": None, + "cidr": "10.4.0.0/24", + "id": "f0ad1df5-53ee-473f-b86b-3604ea5591e9", + "subnetpool_id": None, + "name": "private-subnet-ipv4", + }]}) + ]) + + self.assertEqual( + 'my-network', self.cloud.get_nat_source()['name']) + + self.assert_calls() diff --git a/releasenotes/notes/nat-source-support-92aaf6b336d0b848.yaml b/releasenotes/notes/nat-source-support-92aaf6b336d0b848.yaml new file mode 100644 index 000000000..efd8713a4 --- /dev/null +++ b/releasenotes/notes/nat-source-support-92aaf6b336d0b848.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add support for networks being configured as the + primary nat_source in clouds.yaml.