diff --git a/nova/api/openstack/compute/servers.py b/nova/api/openstack/compute/servers.py index 14649fb18191..de49a9bec6aa 100644 --- a/nova/api/openstack/compute/servers.py +++ b/nova/api/openstack/compute/servers.py @@ -417,8 +417,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 0bfa4ce7f161..837e4ebd560e 100644 --- a/nova/network/neutronv2/api.py +++ b/nova/network/neutronv2/api.py @@ -1465,7 +1465,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 9005f2f6aa0f..23f305fac1bb 100644 --- a/nova/tests/unit/network/test_neutronv2.py +++ b/nova/tests/unit/network/test_neutronv2.py @@ -3098,6 +3098,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())