diff --git a/neutron/db/l3_db.py b/neutron/db/l3_db.py index 3fe6d154dbd..f32aab414f0 100644 --- a/neutron/db/l3_db.py +++ b/neutron/db/l3_db.py @@ -105,6 +105,15 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase, l3plugin.prevent_l3_port_deletion( payload.context, payload.resource_id) + @staticmethod + def _validate_subnet_address_mode(subnet): + if (subnet['ip_version'] == 6 and subnet['ipv6_ra_mode'] is None and + subnet['ipv6_address_mode'] is not None): + msg = (_('IPv6 subnet %s configured to receive RAs from an ' + 'external router cannot be added to Neutron Router.') % + subnet['id']) + raise n_exc.BadRequest(resource='router', msg=msg) + @property def _is_dns_integration_supported(self): if self._dns_integration is None: @@ -687,6 +696,13 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase, if not port['fixed_ips']: msg = _('Router port must have at least one fixed IP') raise n_exc.BadRequest(resource='router', msg=msg) + + fixed_ips = [ip for ip in port['fixed_ips']] + for fixed_ip in fixed_ips: + subnet = self._core_plugin.get_subnet( + context, fixed_ip['subnet_id']) + self._validate_subnet_address_mode(subnet) + return port def _validate_port_in_range_or_admin(self, context, subnets, port): @@ -811,12 +827,7 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase, msg = (_('Cannot add interface to router because subnet %s is not ' 'owned by project making the request') % subnet_id) raise n_exc.BadRequest(resource='router', msg=msg) - if (subnet['ip_version'] == 6 and subnet['ipv6_ra_mode'] is None and - subnet['ipv6_address_mode'] is not None): - msg = (_('IPv6 subnet %s configured to receive RAs from an ' - 'external router cannot be added to Neutron Router.') % - subnet['id']) - raise n_exc.BadRequest(resource='router', msg=msg) + self._validate_subnet_address_mode(subnet) self._check_for_dup_router_subnets(context, router, subnet['network_id'], [subnet]) fixed_ip = {'ip_address': subnet['gateway_ip'], diff --git a/neutron/tests/unit/extensions/test_l3.py b/neutron/tests/unit/extensions/test_l3.py index f7eeda28015..10f9190090c 100644 --- a/neutron/tests/unit/extensions/test_l3.py +++ b/neutron/tests/unit/extensions/test_l3.py @@ -1490,6 +1490,33 @@ class L3NatTestCaseBase(L3NatTestCaseMixin): None, p['port']['id']) + def test_update_router_interface_port_ipv6_subnet_ext_ra(self): + use_cases = [{'msg': 'IPv6 Subnet Modes (none, slaac)', + 'ra_mode': None, + 'address_mode': lib_constants.IPV6_SLAAC}, + {'msg': 'IPv6 Subnet Modes (none, dhcpv6-stateful)', + 'ra_mode': None, + 'address_mode': lib_constants.DHCPV6_STATEFUL}, + {'msg': 'IPv6 Subnet Modes (none, dhcpv6-stateless)', + 'ra_mode': None, + 'address_mode': lib_constants.DHCPV6_STATELESS}] + for uc in use_cases: + with self.network() as network, self.router() as router: + with self.subnet( + network=network, cidr='fd00::/64', + ip_version=lib_constants.IP_VERSION_6, + ipv6_ra_mode=uc['ra_mode'], + ipv6_address_mode=uc['address_mode']) as subnet: + fixed_ips = [{'subnet_id': subnet['subnet']['id']}] + with self.port(subnet=subnet, fixed_ips=fixed_ips) as port: + self._router_interface_action( + 'add', + router['router']['id'], + None, + port['port']['id'], + expected_code=exc.HTTPBadRequest.code, + msg=uc['msg']) + def _assert_body_port_id_and_update_port(self, body, mock_update_port, port_id, device_id): self.assertNotIn('port_id', body)