From 54b8d7770ac7ba0ab49417ceacf82ebc708a3b3b Mon Sep 17 00:00:00 2001 From: Carl Baldwin Date: Wed, 30 Mar 2016 19:22:41 +0000 Subject: [PATCH] Enable deferred IP on Neutron ports When booting a VM using an existing port, Nova checks that the port has fixed ips and fails if it doesn't. In the context of Neutron routed networks, a port may not have an IP address because address allocation was deferred. Neutron can communicate that this is ok through the ip_allocation attribute of the port added in the patch on which this one depends. In the absence of the ip_allocation attribute, Nova falls back to its previous behavior. Change-Id: I03f2e02377a743f4dd10ca12e6c31bb71ee36767 Depends-On: I591d32df512712e4de36fe20e389b3a14d58157f Depends-On: I8dc8890907d1e241dd12448fa184cea1b0620663 Partially-Implements: blueprint neutron-routed-networks --- nova/api/openstack/compute/servers.py | 4 +-- nova/network/neutronv2/api.py | 7 ++++- nova/network/security_group/neutron_driver.py | 3 +- .../compute/test_neutron_security_groups.py | 21 +++++++++++-- nova/tests/unit/network/test_neutronv2.py | 31 +++++++++++++++++++ 5 files changed, 60 insertions(+), 6 deletions(-) diff --git a/nova/api/openstack/compute/servers.py b/nova/api/openstack/compute/servers.py index c970aedb81bd..556c8015fc91 100644 --- a/nova/api/openstack/compute/servers.py +++ b/nova/api/openstack/compute/servers.py @@ -420,8 +420,8 @@ class ServersController(wsgi.Controller): raise exc.HTTPBadRequest(explanation=msg) if request.address is not None: msg = _("Specified Fixed IP '%(addr)s' cannot be used " - "with port '%(port)s': port already has " - "a Fixed IP allocated.") % { + "with port '%(port)s': the two cannot be " + "specified together.") % { "addr": request.address, "port": request.port_id} raise exc.HTTPBadRequest(explanation=msg) diff --git a/nova/network/neutronv2/api.py b/nova/network/neutronv2/api.py index 9b842525d9cb..dbf663596587 100644 --- a/nova/network/neutronv2/api.py +++ b/nova/network/neutronv2/api.py @@ -1413,7 +1413,12 @@ class API(base_api.NetworkAPI): neutron_client=neutron) if port.get('device_id', None): raise exception.PortInUse(port_id=request.port_id) - if not port.get('fixed_ips'): + deferred_ip = port.get('ip_allocation') == 'deferred' + # NOTE(carl_baldwin) A deferred IP port doesn't have an + # address here. If it fails to get one later when nova + # updates it with host info, Neutron will error which + # raises an exception. + if not deferred_ip and not port.get('fixed_ips'): raise exception.PortRequiresFixedIP( port_id=request.port_id) request.network_id = port['network_id'] diff --git a/nova/network/security_group/neutron_driver.py b/nova/network/security_group/neutron_driver.py index f5f904779de2..21f2c76ceff0 100644 --- a/nova/network/security_group/neutron_driver.py +++ b/nova/network/security_group/neutron_driver.py @@ -419,7 +419,8 @@ class SecurityGroupAPI(security_group_base.SecurityGroupBase): def _has_security_group_requirements(self, port): port_security_enabled = port.get('port_security_enabled', True) has_ip = port.get('fixed_ips') - if has_ip: + deferred_ip = port.get('ip_allocation') == 'deferred' + if has_ip or deferred_ip: return port_security_enabled return False diff --git a/nova/tests/unit/api/openstack/compute/test_neutron_security_groups.py b/nova/tests/unit/api/openstack/compute/test_neutron_security_groups.py index d69cde7536d5..84d7ffe4224e 100644 --- a/nova/tests/unit/api/openstack/compute/test_neutron_security_groups.py +++ b/nova/tests/unit/api/openstack/compute/test_neutron_security_groups.py @@ -73,7 +73,7 @@ class TestNeutronSecurityGroupsV21( def _create_port(self, **kwargs): body = {'port': {'binding:vnic_type': model.VNIC_TYPE_NORMAL}} fields = ['security_groups', 'device_id', 'network_id', - 'port_security_enabled'] + 'port_security_enabled', 'ip_allocation'] for field in fields: if field in kwargs: body['port'][field] = kwargs[field] @@ -278,6 +278,22 @@ class TestNeutronSecurityGroupsV21( self.manager._addSecurityGroup, req, UUID_SERVER, body) + def test_associate_deferred_ip_port(self): + sg = self._create_sg_template().get('security_group') + net = self._create_network() + self._create_port( + network_id=net['network']['id'], security_groups=[sg['id']], + port_security_enabled=True, ip_allocation='deferred', + device_id=UUID_SERVER) + + self.stub_out('nova.db.instance_get_by_uuid', + test_security_groups.return_server) + body = dict(addSecurityGroup=dict(name="test")) + + req = fakes.HTTPRequest.blank('/v2/fake/servers/%s/action' % + UUID_SERVER) + self.manager._addSecurityGroup(req, UUID_SERVER, body) + def test_disassociate_by_non_existing_security_group_name(self): self.stub_out('nova.db.instance_get_by_uuid', test_security_groups.return_server) @@ -696,6 +712,7 @@ class MockClient(object): 'admin_state_up': p.get('admin_state_up', True), 'security_groups': p.get('security_groups', []), 'network_id': p.get('network_id'), + 'ip_allocation': p.get('ip_allocation'), 'binding:vnic_type': p.get('binding:vnic_type') or model.VNIC_TYPE_NORMAL} @@ -710,7 +727,7 @@ class MockClient(object): if not port_security and ret['security_groups']: raise exception.SecurityGroupCannotBeApplied() - if network['subnets']: + if network['subnets'] and p.get('ip_allocation') != 'deferred': ret['fixed_ips'] = [{'subnet_id': network['subnets'][0], 'ip_address': '10.0.0.1'}] if not ret['security_groups'] and (port_security is None or diff --git a/nova/tests/unit/network/test_neutronv2.py b/nova/tests/unit/network/test_neutronv2.py index 3c065135dd48..fc8301f2b494 100644 --- a/nova/tests/unit/network/test_neutronv2.py +++ b/nova/tests/unit/network/test_neutronv2.py @@ -3055,6 +3055,37 @@ class TestNeutronv2WithMock(test.TestCase): 'fake-user', 'fake-project', auth_token='bff4a5a6b9eb4ea2a6efec6eefb77936') + @mock.patch('nova.network.neutronv2.api.API._show_port') + def test_deferred_ip_port_immediate_allocation(self, mock_show): + port = {'network_id': 'my_netid1', + 'device_id': None, + 'id': uuids.port, + 'fixed_ips': [], # no fixed ip + 'ip_allocation': 'immediate', } + + mock_show.return_value = port + + requested_networks = objects.NetworkRequestList( + objects=[objects.NetworkRequest(port_id=port['id'])]) + self.assertRaises(exception.PortRequiresFixedIP, + self.api.validate_networks, + self.context, requested_networks, 1) + + @mock.patch('nova.network.neutronv2.api.API._show_port') + def test_deferred_ip_port_deferred_allocation(self, mock_show): + port = {'network_id': 'my_netid1', + 'device_id': None, + 'id': uuids.port, + 'fixed_ips': [], # no fixed ip + 'ip_allocation': 'deferred', } + + mock_show.return_value = port + + requested_networks = objects.NetworkRequestList( + objects=[objects.NetworkRequest(port_id=port['id'])]) + count = self.api.validate_networks(self.context, requested_networks, 1) + self.assertEqual(1, count) + @mock.patch('oslo_concurrency.lockutils.lock') def test_get_instance_nw_info_locks_per_instance(self, mock_lock): instance = objects.Instance(uuid=uuid.uuid4())