diff --git a/nova/network/neutron.py b/nova/network/neutron.py index e02da93b574d..9cf07d7f2c2a 100644 --- a/nova/network/neutron.py +++ b/nova/network/neutron.py @@ -2366,14 +2366,24 @@ class API: neutron_client=neutron) if port.get('device_id', None): raise exception.PortInUse(port_id=request.port_id) + deferred_ip = port.get('ip_allocation') == 'deferred' + ipless_port = port.get('ip_allocation') == 'none' # 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'): + # NOTE(sbauza): We don't need to validate the + # 'connectivity' attribute of the port's + # 'binding:vif_details' to ensure it's 'l2', as Neutron + # already verifies it. + if ( + not (deferred_ip or ipless_port) and + not port.get('fixed_ips') + ): raise exception.PortRequiresFixedIP( port_id=request.port_id) + request.network_id = port['network_id'] else: ports_needed_per_instance += 1 diff --git a/nova/tests/functional/test_ip_allocation.py b/nova/tests/functional/test_ip_allocation.py new file mode 100644 index 000000000000..a899641abe64 --- /dev/null +++ b/nova/tests/functional/test_ip_allocation.py @@ -0,0 +1,53 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from nova.tests.functional import integrated_helpers + + +class IPAllocationTests(integrated_helpers._IntegratedTestBase): + """Test behavior with various IP allocation policies. + + This mainly exists to test the 'deferred' and 'none' policies. + """ + compute_driver = 'fake.MediumFakeDriver' + microversion = 'latest' + ADMIN_API = True + + def setUp(self): + super().setUp() + + # add a port with an ip_allocation of 'none' + port = { + 'name': '', + 'description': '', + 'network_id': self.neutron.network_1['id'], + 'admin_state_up': True, + 'status': 'ACTIVE', + 'mac_address': 'ee:94:88:57:d5:7a', + # The ip_allocation is 'none', so fixed_ips should be null + 'fixed_ips': [], + 'tenant_id': self.neutron.tenant_id, + 'project_id': self.neutron.tenant_id, + 'device_id': '', + 'binding:profile': {}, + 'binding:vnic_type': 'normal', + 'binding:vif_type': 'ovs', + 'binding:vif_details': {}, + 'ip_allocation': 'none', + } + created_port = self.neutron.create_port({'port': port}) + self.port_id = created_port['port']['id'] + + def test_boot_with_none_policy(self): + """Create a port with the 'none' policy.""" + self._create_server( + networks=[{'port': self.port_id}]) diff --git a/nova/tests/unit/network/test_neutron.py b/nova/tests/unit/network/test_neutron.py index 5056b70c4ec8..6c6e6ca5ee91 100644 --- a/nova/tests/unit/network/test_neutron.py +++ b/nova/tests/unit/network/test_neutron.py @@ -3711,6 +3711,27 @@ class TestAPI(TestAPIBase): count = self.api.validate_networks(self.context, requested_networks, 1) self.assertEqual(1, count) + @mock.patch('nova.network.neutron.API._show_port') + def test_deferred_ip_port_none_allocation(self, mock_show): + """Test behavior when the 'none' IP allocation policy is used.""" + port = { + 'network_id': 'my_netid1', + 'device_id': None, + 'id': uuids.port, + 'fixed_ips': [], # no fixed ip + 'ip_allocation': 'none', + 'binding:vif_details': { + 'connectivity': 'l2', + }, + } + + 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=uuids.fake) diff --git a/releasenotes/notes/bp-boot-vm-with-unaddressed-port-4cb05bb6dc859d98.yaml b/releasenotes/notes/bp-boot-vm-with-unaddressed-port-4cb05bb6dc859d98.yaml new file mode 100644 index 000000000000..55c43ddd4c7b --- /dev/null +++ b/releasenotes/notes/bp-boot-vm-with-unaddressed-port-4cb05bb6dc859d98.yaml @@ -0,0 +1,3 @@ +features: + - Nova now allows to create an instance with a non-deferred port that has + no fixed IP address if the network backend has level-2 connectivity.