diff --git a/shade/openstackcloud.py b/shade/openstackcloud.py index 7d7be4d31..88e58d60d 100644 --- a/shade/openstackcloud.py +++ b/shade/openstackcloud.py @@ -4779,9 +4779,7 @@ class OpenStackCloud( return (None, None) port = None if not fixed_address: - if len(ports) == 1: - port = ports[0] - else: + if len(ports) > 1: if nat_destination: nat_network = self.get_network(nat_destination) if not nat_network: @@ -4793,37 +4791,49 @@ class OpenStackCloud( else: nat_network = self.get_nat_destination() - if nat_network: - for maybe_port in ports: - if maybe_port['network_id'] == nat_network['id']: - port = maybe_port - if not port: - raise OpenStackCloudException( - 'No port on server {server} was found matching' - ' the network configured as the NAT destination' - ' {dest}. Please check your config'.format( - server=server['id'], dest=nat_network['name'])) - else: - port = ports[0] - warnings.warn( - 'During Floating IP creation, multiple private' - ' networks were found. {net} is being selected at' - ' random to be the destination of the NAT. If that' - ' is not what you want, please configure the' + if not nat_network: + raise OpenStackCloudException( + 'Multiple ports were found for server {server}' + ' but none of the networks are a valid NAT' + ' destination, so it is impossible to add a' + ' floating IP. If you have a network that is a valid' + ' destination for NAT and we could not find it,' + ' please file a bug. But also configure the' ' nat_destination property of the networks list in' ' your clouds.yaml file. If you do not have a' ' clouds.yaml file, please make one - your setup' - ' is complicated.'.format(net=port['network_id'])) + ' is complicated.'.format(server=server['id'])) - # Select the first available IPv4 address - for address in port.get('fixed_ips', list()): - try: - ip = ipaddress.ip_address(address['ip_address']) - except Exception: - continue - if ip.version == 4: - fixed_address = address['ip_address'] - return port, fixed_address + maybe_ports = [] + for maybe_port in ports: + if maybe_port['network_id'] == nat_network['id']: + maybe_ports.append(maybe_port) + if not maybe_ports: + raise OpenStackCloudException( + 'No port on server {server} was found matching' + ' your NAT destination network {dest}. Please ' + ' check your config'.format( + server=server['id'], dest=nat_network['name'])) + ports = maybe_ports + + # Select the most recent available IPv4 address + # To do this, sort the ports in reverse order by the created_at + # field which is a string containing an ISO DateTime (which + # thankfully sort properly) This way the most recent port created, + # if there are more than one, will be the arbitrary port we + # select. + for port in sorted( + ports, + key=operator.itemgetter('created_at'), + reverse=True): + for address in port.get('fixed_ips', list()): + try: + ip = ipaddress.ip_address(address['ip_address']) + except Exception: + continue + if ip.version == 4: + fixed_address = address['ip_address'] + return port, fixed_address raise OpenStackCloudException( "unable to find a free fixed IPv4 address for server " "{0}".format(server['id'])) diff --git a/shade/tests/unit/test_floating_ip_common.py b/shade/tests/unit/test_floating_ip_common.py index 5031e032d..d208afcb0 100644 --- a/shade/tests/unit/test_floating_ip_common.py +++ b/shade/tests/unit/test_floating_ip_common.py @@ -21,7 +21,6 @@ Tests floating IP resource methods for Neutron and Nova-network. from mock import patch -from shade import exc from shade import meta from shade import OpenStackCloud from shade.tests.fakes import FakeServer @@ -225,12 +224,3 @@ class TestFloatingIP(base.TestCase): mock_add_auto_ip.assert_called_with( server_dict, wait=False, timeout=60, reuse=True) - - @patch.object(OpenStackCloud, 'search_ports', return_value=[{}]) - def test_nat_destination_port_when_no_free_fixed_ip( - self, mock_search_ports): - server = {'id': 42} - self.assertRaisesRegexp( - exc.OpenStackCloudException, 'server 42$', - self.cloud._nat_destination_port, server - ) diff --git a/shade/tests/unit/test_floating_ip_neutron.py b/shade/tests/unit/test_floating_ip_neutron.py index 061e1edc6..72e5c515f 100644 --- a/shade/tests/unit/test_floating_ip_neutron.py +++ b/shade/tests/unit/test_floating_ip_neutron.py @@ -20,6 +20,7 @@ Tests Floating IP resource methods for Neutron """ import copy +import datetime import munch from shade import exc @@ -101,6 +102,7 @@ class TestFloatingIP(base.RequestsMockTestCase): 'status': 'ACTIVE', 'binding:host_id': 'devstack', 'name': 'first-port', + 'created_at': datetime.datetime.now().isoformat(), 'allowed_address_pairs': [], 'admin_state_up': True, 'network_id': '70c1db1f-b701-45bd-96e0-a313ee3430b3', @@ -939,6 +941,7 @@ class TestFloatingIP(base.RequestsMockTestCase): server_port = { "id": "port-id", "device_id": "some-server", + 'created_at': datetime.datetime.now().isoformat(), 'fixed_ips': [ { 'subnet_id': 'subnet-id',