diff --git a/src/config/common/__init__.py b/src/config/common/__init__.py index feaf21504ee..467b7f98daf 100644 --- a/src/config/common/__init__.py +++ b/src/config/common/__init__.py @@ -48,3 +48,9 @@ def wrapper(*args, **kwargs): _illegal_ranges = ["%s-%s" % (unichr(low), unichr(high)) for (low, high) in _illegal_unichrs] illegal_xml_chars_RE = re.compile(u'[%s]' % u''.join(_illegal_ranges)) + +HEX_ELEM = '[0-9A-Fa-f]' +UUID_PATTERN = '-'.join([HEX_ELEM + '{8}', HEX_ELEM + '{4}', + HEX_ELEM + '{4}', HEX_ELEM + '{4}', + HEX_ELEM + '{12}']) + diff --git a/src/config/vnc_openstack/vnc_openstack/neutron_plugin_db.py b/src/config/vnc_openstack/vnc_openstack/neutron_plugin_db.py index e2cd0a73b7d..11df5936976 100644 --- a/src/config/vnc_openstack/vnc_openstack/neutron_plugin_db.py +++ b/src/config/vnc_openstack/vnc_openstack/neutron_plugin_db.py @@ -20,7 +20,7 @@ from cfgm_common import exceptions as vnc_exc from vnc_api.vnc_api import * -from cfgm_common import SG_NO_RULE_FQ_NAME, SG_NO_RULE_NAME +from cfgm_common import SG_NO_RULE_FQ_NAME, SG_NO_RULE_NAME, UUID_PATTERN import vnc_openstack _DEFAULT_HEADERS = { @@ -34,6 +34,8 @@ # SNAT defines _IFACE_ROUTE_TABLE_NAME_PREFIX = 'NEUTRON_IFACE_RT' +_IFACE_ROUTE_TABLE_NAME_PREFIX_REGEX = re.compile( + '%s_%s_%s' % (_IFACE_ROUTE_TABLE_NAME_PREFIX, UUID_PATTERN, UUID_PATTERN)) class DBInterface(object): """ @@ -3708,12 +3710,15 @@ def port_delete(self, port_id): tenant_id = self._get_obj_tenant_id('port', port_id) self._virtual_machine_interface_delete(port_id=port_id) - # delete any interface route table associatd with the port + # delete any interface route table associated with the port to handle + # subnet host route Neutron extension, un-reference others for rt_ref in port_obj.get_interface_route_table_refs() or []: - try: - self._vnc_lib.interface_route_table_delete(id=rt_ref['uuid']) - except (NoIdError, RefsExistError) as e: - pass + if _IFACE_ROUTE_TABLE_NAME_PREFIX_REGEX.match(rt_ref['to'][-1]): + try: + self._vnc_lib.interface_route_table_delete( + id=rt_ref['uuid']) + except (NoIdError, RefsExistError) as e: + pass # delete instance if this was the last port try: diff --git a/src/config/vnc_openstack/vnc_openstack/tests/test_basic.py b/src/config/vnc_openstack/vnc_openstack/tests/test_basic.py index 8e68170cca7..1bde4aa816d 100644 --- a/src/config/vnc_openstack/vnc_openstack/tests/test_basic.py +++ b/src/config/vnc_openstack/vnc_openstack/tests/test_basic.py @@ -6,12 +6,22 @@ import webtest.app sys.path.append('../common/tests') +from cfgm_common.exceptions import NoIdError from test_utils import * import test_common import test_case +_IFACE_ROUTE_TABLE_NAME_PREFIX = 'NEUTRON_IFACE_RT' + class TestBasic(test_case.NeutronBackendTestCase): + @classmethod + def setUpClass(cls): + super(TestBasic, cls).setUpClass( + extra_config_knobs=[ + ('DEFAULTS', 'apply_subnet_host_routes', True) + ]) + def read_resource(self, url_pfx, id): context = {'operation': 'READ', 'user_id': '', @@ -42,6 +52,38 @@ def list_resource(self, url_pfx, return json.loads(resp.text) # end list_resource + def create_resource(self, res_type, proj_id, name=None, + extra_res_fields=None): + context = {'operation': 'CREATE', + 'user_id': '', + 'is_admin': False, + 'roles': '', + 'tenant_id': proj_id} + if name: + res_name = name + else: + res_name = '%s-%s-%s' % (res_type, self.id()) + data = {'resource': {'name': res_name, + 'tenant_id': proj_id}} + if extra_res_fields: + data['resource'].update(extra_res_fields) + + body = {'context': context, 'data': data} + resp = self._api_svr_app.post_json('/neutron/%s' %(res_type), body) + return json.loads(resp.text) + + def delete_resource(self, res_type, proj_id, id): + context = {'operation': 'DELETE', + 'user_id': '', + 'is_admin': False, + 'roles': '', + 'tenant_id': proj_id} + + data = {'id': id} + + body = {'context': context, 'data': data} + self._api_svr_app.post_json('/neutron/%s' %(res_type), body) + def test_list_with_inconsistent_members(self): # 1. create collection # 2. list, verify full collection @@ -384,6 +426,65 @@ def test_sg_rules_delete_when_peer_group_deleted_on_list_rules(self): sgr = [rule.rule_uuid for rule in sg1_obj.get_security_group_entries().get_policy_rule() or []] self.assertIn(sgr_uuid, sgr) + + def test_delete_irt_for_subnet_host_route(self): + proj_obj = self._vnc_lib.project_read( + fq_name=['default-domain', 'default-project']) + ipam_obj = vnc_api.NetworkIpam('ipam-%s' % self.id()) + self._vnc_lib.network_ipam_create(ipam_obj) + vn_obj = vnc_api.VirtualNetwork('vn-%s' % self.id()) + sn_uuid = str(uuid.uuid4()) + vn_obj.add_network_ipam( + ipam_obj, + vnc_api.VnSubnetsType([ + vnc_api.IpamSubnetType( + vnc_api.SubnetType('1.1.1.0', 28), + subnet_uuid=sn_uuid, + host_routes=vnc_api.RouteTableType([ + vnc_api.RouteType( + prefix='2.2.2.0/28', + next_hop='1.1.1.3' + ) + ]) + ) + ]) + ) + self._vnc_lib.virtual_network_create(vn_obj) + # Create default sg as vnc_openstack hooks are disabled in that ut + sg_obj = vnc_api.SecurityGroup('default') + self._vnc_lib.security_group_create(sg_obj) + port_dict = self.create_resource('port', + proj_obj.uuid, + 'vmi-%s' % self.id(), + extra_res_fields={ + 'network_id': vn_obj.uuid, + 'fixed_ips': [{ + 'ip_address': '1.1.1.3' + }] + }) + route_table = vnc_api.RouteTableType('irt-%s' % self.id()) + route_table.set_route([]) + irt_obj = vnc_api.InterfaceRouteTable( + interface_route_table_routes=route_table, + name='irt-%s' % self.id()) + self._vnc_lib.interface_route_table_create(irt_obj) + vmi_obj = self._vnc_lib.virtual_machine_interface_read( + id=port_dict['id']) + vmi_obj.add_interface_route_table(irt_obj) + self._vnc_lib.virtual_machine_interface_update(vmi_obj) + + self.delete_resource('port', vmi_obj.parent_uuid, vmi_obj.uuid) + + host_route_irt_fq_name = proj_obj.fq_name + ['%s_%s_%s' % ( + _IFACE_ROUTE_TABLE_NAME_PREFIX, sn_uuid, vmi_obj.uuid)] + with ExpectedException(NoIdError): + self._vnc_lib.interface_route_table_read(host_route_irt_fq_name) + try: + irt_obj = self._vnc_lib.interface_route_table_read(id=irt_obj.uuid) + except NoIdError: + self.fail("The manually added interface route table as been " + "automatically removed") + self.assertIsNone(irt_obj.get_virtual_machine_interface_back_refs()) # end class TestBasic class TestExtraFieldsPresenceByKnob(test_case.NeutronBackendTestCase):