diff --git a/specs/neutron_bgpvpn.md b/specs/neutron_bgpvpn.md new file mode 100644 index 00000000000..90d58506d4e --- /dev/null +++ b/specs/neutron_bgpvpn.md @@ -0,0 +1,103 @@ +# 1. Introduction +Add the support to the new OpenStack Neutron API extension named **Networking BGP VPN** to Contrail which was added to supporting inter-connections between L3VPNs or E-VPNs and Neutron resources, (i.e. Networks, Routers and Ports). +This extension uses the [plugin Neutron framework](https://wiki.openstack.org/wiki/Neutron/ServiceTypeFramework) and defines a new resource named *bgpvpn* that contains all [attributes](http://docs.openstack.org/developer/networking-bgpvpn/api.html#bgpvpn-resource) needed to declare a BGP VPN connection. Then, that connection can be associated/disassociated to networks or/and routers by users. + +# 2. Problem statement +A typical use-case is the following: +* a tenant already having a BGP IP VPN (a set of external sites) setup outside the datacenter, and they want to be able to trigger the establishment of connectivity between VMs and these VPN sites. +* Another similar need is when E-VPN is used to provide an Ethernet interconnect between multiple sites. + +# 3. Proposed solution +Extend the Contrail schema with that new BGP VPN resource and use the schema transformer to apply BGP VPN's route targets to the associated network resources (ie. routing instance). + +## 3.1 Alternatives considered +N/A + +## 3.2 API schema changes +Add new BGP VPN resource to Contrail data model named `bgpvpn`: + +Resource attributes | Operations | Type +-|-|- +Project Owner | CR | Parent reference +Type | CR | Property `VpnType` string enum `['l2', 'l3']` +Route targets | CRU | ListProperty `RouteTargetList` +Import targets | CRU | ListProperty `RouteTargetList` +Export targets | CRU | ListProperty `RouteTargetList` +Networks | CRU | `virtual-network` back reference +Routers | CRU | `logical-router` back reference + +For the ` bgpvpn` `type` attribute, only the type `l3` can be used for `bgpvpn` associated to `logical routers`. About the `virtual network` association authorizations depend on the `forwarding mode` set on the `virtual network` as describe below: + +Forwarding mode | Authorized VPN types +-|- +`l2_l3` | `l2` and/or `l3` +`l2` | `l2` only +`l3` | `l3` only + +`forwarding mode` update on a `virtual network` can fail depends of the `bgpvpn` `type` associated to it. + +## 3.3 User workflow impact +That feature was designed for the OpenStack Neutron API. Only the cloud administrator can create, allocate to a tenant, update and delete the `bgpvpn` resource. User can only list them (eventually update the name) and define association with network resource (ie. `virtual networks` and `logical router`). + +On the contrail side, we will add that resource without any constraints on resource manipulation. + +## 3.4 UI changes +N/A + +## 3.5 Notification impact +N/A + +# 4. Implementation + +## 4.1 Assignee(s) +### Developers +* Édouard Thuleau + +### Reviewers +* Sachin Bansal + +## 4.2 Work items +### Contrail schema +Add the new `bgpvpn` resource to the Contrail data model as describe in 3.2 + +### VNC API server +Add some checks on the association of a BGP VPN resource to the other resource to avoid any ambiguity like it's describe in the [BGP VPN documentation](http://docs.openstack.org/developer/networking-bgpvpn/api.html#association-constraints). + +### Schema transformer +Add a new ST class to support that new `bgpvpn` resource and add code in classes `VirtualNetworkST` and `LogicalRouterST` to merge BGP VPN's route targets. + +### Neutron BGP VPN driver +Write a driver for the Neutron BGP VPN extension based on that new `bgpvpn` resource. + +# 5. Performance and scaling impact +## 5.1 API and control plane +Insignificant impact on the config and control. + +## 5.2 Forwarding performance +Add some easy routing stuff on vrouter to leak routes between VRFs accordingly to defined route targets. + +# 6. Upgrade +First implementation was done by Orange/Cloudwatt based on the Contrail key/value exposed through the VNC API which was a proof of concept first implementation. + +We could easily add a mechanism to detect if used Contrail API support the new `bgpvpn` resource, if not fallback to the first driver and propose a migration script (or an automatic migration which detects `bgpvpn` resources in kv store when driver is initializing). + +# 7. Deprecation +* No deprecation on the Contrail API. +* Deprecates the first Neutron BGP VPN Contrail driver. + +# 8. Dependencies +N/A + +# 9. Testing +## 9.1 Unit tests +Add unit tests to the added code on the config side. + +## 9.2 Dev tests + +## 9.3 System tests + +# 10. Documentation Impact + +# 11. References +* Blueprint: https://blueprints.launchpad.net/opencontrail/+spec/bgpvpn +* Neutron BGP VPN extension documentation: http://docs.openstack.org/developer/networking-bgpvpn/index.html diff --git a/src/config/api-server/db_manage.py b/src/config/api-server/db_manage.py index 059b84d9b6b..466acb45e1d 100644 --- a/src/config/api-server/db_manage.py +++ b/src/config/api-server/db_manage.py @@ -8,6 +8,7 @@ from netaddr import IPAddress, IPNetwork import argparse from cStringIO import StringIO +import time import kazoo.client import kazoo.exceptions diff --git a/src/config/api-server/tests/test_crud_basic.py b/src/config/api-server/tests/test_crud_basic.py index 8d3fedcf229..7ab4075d6df 100644 --- a/src/config/api-server/tests/test_crud_basic.py +++ b/src/config/api-server/tests/test_crud_basic.py @@ -824,7 +824,8 @@ def test_put_on_wrong_type(self): self.assertThat(rb_obj.display_name, Equals('foobar')) def test_floatingip_as_instanceip(self): - ipam_fixt = self.useFixture(NetworkIpamTestFixtureGen(self._vnc_lib)) + ipam_fixt = self.useFixture(NetworkIpamTestFixtureGen( + self._vnc_lib, network_ipam_name='ipam-%s' % self.id())) project_fixt = self.useFixture(ProjectTestFixtureGen(self._vnc_lib, 'default-project')) @@ -860,7 +861,8 @@ def test_floatingip_as_instanceip(self): # end test_floatingip_as_instanceip def test_aliasip_as_instanceip(self): - ipam_fixt = self.useFixture(NetworkIpamTestFixtureGen(self._vnc_lib)) + ipam_fixt = self.useFixture(NetworkIpamTestFixtureGen( + self._vnc_lib, network_ipam_name='ipam-%s' % self.id())) project_fixt = self.useFixture(ProjectTestFixtureGen(self._vnc_lib, 'default-project')) @@ -1949,6 +1951,180 @@ def test_name_attribute_in_detail_list_resource(self): self.assertIn('name', vn_dict) self.assertEqual(vn_dict['name'], vn_obj.fq_name[-1]) + def test_bgpvpn_type_assoc_with_network_l2_l3_forwarding_mode(self): + # Create virtual network with forwarding mode set to 'l2' and 'l3' + vn_l2_l3 = self.create_virtual_network('vn-l2-l3-%s' % self.id()) + # Create l2 bgpvpn + bgpvpn_l2 = Bgpvpn('bgpvpn-l2-%s' % self.id(), bgpvpn_type='l2') + self._vnc_lib.bgpvpn_create(bgpvpn_l2) + # Create l3 bgpvpn + bgpvpn_l3 = Bgpvpn('bgpvpn-l3-%s' % self.id()) + self._vnc_lib.bgpvpn_create(bgpvpn_l3) + + # Trying to associate a 'l2' bgpvpn on the virtual network + vn_l2_l3.add_bgpvpn(bgpvpn_l2) + self._vnc_lib.virtual_network_update(vn_l2_l3) + vn_l2_l3 = self._vnc_lib.virtual_network_read(id=vn_l2_l3.uuid) + + # Trying to associate a 'l3' bgpvpn on the virtual network + vn_l2_l3.add_bgpvpn(bgpvpn_l3) + self._vnc_lib.virtual_network_update(vn_l2_l3) + vn_l2_l3 = self._vnc_lib.virtual_network_read(id=vn_l2_l3.uuid) + + # Try to change the virtual network forwarding mode to 'l2' only + with ExpectedException(BadRequest): + vn_l2_l3.set_virtual_network_properties( + VirtualNetworkType(forwarding_mode='l2')) + self._vnc_lib.virtual_network_update(vn_l2_l3) + vn_l2_l3 = self._vnc_lib.virtual_network_read(id=vn_l2_l3.uuid) + + # Try to change the virtual network forwarding mode to 'l3' only + with ExpectedException(BadRequest): + vn_l2_l3.set_virtual_network_properties( + VirtualNetworkType(forwarding_mode='l3')) + self._vnc_lib.virtual_network_update(vn_l2_l3) + + def test_bgpvpn_type_assoc_with_network_l2_forwarding_mode(self): + # Create virtual network with forwarding mode set to 'l2' only + vn_l2 = self.create_virtual_network('vn-l2-%s' % self.id()) + vn_l2.set_virtual_network_properties( + VirtualNetworkType(forwarding_mode='l2')) + self._vnc_lib.virtual_network_update(vn_l2) + vn_l2 = self._vnc_lib.virtual_network_read(id=vn_l2.uuid) + # Create l2 bgpvpn + bgpvpn_l2 = Bgpvpn('bgpvpn-l2-%s' % self.id(), bgpvpn_type='l2') + self._vnc_lib.bgpvpn_create(bgpvpn_l2) + # Create l3 bgpvpn + bgpvpn_l3 = Bgpvpn('bgpvpn-l3-%s' % self.id()) + self._vnc_lib.bgpvpn_create(bgpvpn_l3) + + # Trying to associate a 'l2' bgpvpn on the virtual network + vn_l2.add_bgpvpn(bgpvpn_l2) + self._vnc_lib.virtual_network_update(vn_l2) + vn_l2 = self._vnc_lib.virtual_network_read(id=vn_l2.uuid) + + # Trying to associate a 'l3' bgpvpn on the virtual network + with ExpectedException(BadRequest): + vn_l2.add_bgpvpn(bgpvpn_l3) + self._vnc_lib.virtual_network_update(vn_l2) + vn_l2 = self._vnc_lib.virtual_network_read(id=vn_l2.uuid) + + # Try to change the virtual network forwarding mode to 'l3' only + with ExpectedException(BadRequest): + vn_l2.set_virtual_network_properties( + VirtualNetworkType(forwarding_mode='l3')) + self._vnc_lib.virtual_network_update(vn_l2) + vn_l2 = self._vnc_lib.virtual_network_read(id=vn_l2.uuid) + + # Try to change the virtual network forwarding mode to 'l2' and l3' + vn_l2.set_virtual_network_properties( + VirtualNetworkType(forwarding_mode='l2_l3')) + self._vnc_lib.virtual_network_update(vn_l2) + + def test_bgpvpn_type_assoc_with_network_l3_forwarding_mode(self): + # Create virtual network with forwarding mode set to 'l3' only + vn_l3 = self.create_virtual_network('vn-l3-%s' % self.id()) + vn_l3.set_virtual_network_properties( + VirtualNetworkType(forwarding_mode='l3')) + self._vnc_lib.virtual_network_update(vn_l3) + vn_l3 = self._vnc_lib.virtual_network_read(id=vn_l3.uuid) + # Create l2 bgpvpn + bgpvpn_l2 = Bgpvpn('bgpvpn-l2-%s' % self.id(), bgpvpn_type='l2') + self._vnc_lib.bgpvpn_create(bgpvpn_l2) + # Create l3 bgpvpn + bgpvpn_l3 = Bgpvpn('bgpvpn-l3-%s' % self.id()) + self._vnc_lib.bgpvpn_create(bgpvpn_l3) + + # Trying to associate a 'l3' bgpvpn on the virtual network + vn_l3.add_bgpvpn(bgpvpn_l3) + self._vnc_lib.virtual_network_update(vn_l3) + vn_l3 = self._vnc_lib.virtual_network_read(id=vn_l3.uuid) + + # Trying to associate a 'l2' bgpvpn on the virtual network + with ExpectedException(BadRequest): + vn_l3.add_bgpvpn(bgpvpn_l2) + self._vnc_lib.virtual_network_update(vn_l3) + vn_l3 = self._vnc_lib.virtual_network_read(id=vn_l3.uuid) + + # Try to change the virtual network forwarding mode to 'l2' only + with ExpectedException(BadRequest): + vn_l3.set_virtual_network_properties( + VirtualNetworkType(forwarding_mode='l2')) + self._vnc_lib.virtual_network_update(vn_l3) + vn_l3 = self._vnc_lib.virtual_network_read(id=vn_l3.uuid) + + # Try to change the virtual network forwarding mode to 'l2' and l3' + vn_l3.set_virtual_network_properties( + VirtualNetworkType(forwarding_mode='l2_l3')) + self._vnc_lib.virtual_network_update(vn_l3) + + def test_bgpvpn_type_limited_to_l3_for_router_assoc(self): + # Create logical router + lr, _, _, _ = self.create_logical_router( + 'lr-%s' % self.id(), nb_of_attached_networks=0) + # Create l2 bgpvpn + bgpvpn_l2 = Bgpvpn('bgpvpn-l2-%s' % self.id(), bgpvpn_type='l2') + self._vnc_lib.bgpvpn_create(bgpvpn_l2) + + # Trying to associate a 'l2' bgpvpn on the logical router + with ExpectedException(BadRequest): + lr.add_bgpvpn(bgpvpn_l2) + self._vnc_lib.logical_router_update(lr) + + def test_bgpvpn_fail_assoc_network_with_gw_router_assoc_to_bgpvpn(self): + # Create one bgpvpn + bgpvpn = Bgpvpn('bgpvpn-%s' % self.id()) + self._vnc_lib.bgpvpn_create(bgpvpn) + # Create one virtual network with one logical router as gateway + lr, vns, _, _ = self.create_logical_router('lr-%s' % self.id()) + # We attached only one virtual network to the logical router + vn = vns[0] + + # Associate the bgppvpn to the logical router + lr.add_bgpvpn(bgpvpn) + self._vnc_lib.logical_router_update(lr) + lr = self._vnc_lib.logical_router_read(id=lr.uuid) + + # The try to set that same bgpvpn to the virtual network + with ExpectedException(BadRequest): + vn.add_bgpvpn(bgpvpn) + self._vnc_lib.virtual_network_update(vn) + + def test_bgpvpn_fail_assoc_router_with_network_assoc_to_bgpvpn(self): + # Create one bgpvpn + bgpvpn = Bgpvpn('bgpvpn-%s' % self.id()) + self._vnc_lib.bgpvpn_create(bgpvpn) + # Create one virtual network with one logical router as gateway + lr, vns, vmis, _ = self.create_logical_router('lr-%s' % self.id()) + # We attached only one virtual network to the logical router + vn = vns[0] + vmi = vmis[0] + + # Associate the bgpvpn to the virtual network + vn.add_bgpvpn(bgpvpn) + self._vnc_lib.virtual_network_update(vn) + lr = self._vnc_lib.logical_router_read(id=lr.uuid) + + # The try to set that same bgpvpn to the logical router + with ExpectedException(BadRequest): + lr.add_bgpvpn(bgpvpn) + self._vnc_lib.logical_router_update(lr) + lr = self._vnc_lib.logical_router_read(id=lr.uuid) + + # Detatch the logical router from the virtual network + lr.del_virtual_machine_interface(vmi) + self._vnc_lib.logical_router_update(lr) + lr = self._vnc_lib.logical_router_read(id=lr.uuid) + + # Associate the bgpvpn to the logical router + lr.add_bgpvpn(bgpvpn) + self._vnc_lib.logical_router_update(lr) + lr = self._vnc_lib.logical_router_read(id=lr.uuid) + + # Try to reattach the virtual network to the logical router + with ExpectedException(BadRequest): + lr.add_virtual_machine_interface(vmi) + self._vnc_lib.logical_router_update(lr) # end class TestVncCfgApiServer diff --git a/src/config/api-server/vnc_cfg_api_server.py b/src/config/api-server/vnc_cfg_api_server.py index 759cd325e5a..b57bd880e4c 100644 --- a/src/config/api-server/vnc_cfg_api_server.py +++ b/src/config/api-server/vnc_cfg_api_server.py @@ -831,6 +831,8 @@ def http_resource_update(self, obj_type, id): # State modification starts from here. Ensure that cleanup is done for all state changes cleanup_on_failure = [] obj_ids = {'uuid': id} + if 'uuid' not in obj_dict: + obj_dict['uuid'] = id def stateful_update(): get_context().set_state('PRE_DBE_UPDATE') diff --git a/src/config/api-server/vnc_cfg_types.py b/src/config/api-server/vnc_cfg_types.py index 7c3826d69e5..f545d2da4aa 100644 --- a/src/config/api-server/vnc_cfg_types.py +++ b/src/config/api-server/vnc_cfg_types.py @@ -688,7 +688,20 @@ def pre_dbe_create(cls, tenant_name, obj_dict, db_conn): db_conn, obj_dict) if not ok: return (ok, result) - return cls.is_port_in_use_by_vm(obj_dict, db_conn) + + ok, result = cls.is_port_in_use_by_vm(obj_dict, db_conn) + if not ok: + return ok, result + + # Check if type of all associated BGP VPN are 'l3' + ok, result = BgpvpnServer.check_router_supports_vpn_type( + db_conn, obj_dict) + if not ok: + return ok, result + + # Check if we can reference the BGP VPNs + return BgpvpnServer.check_router_has_bgpvpn_assoc_via_network( + db_conn, obj_dict) # end pre_dbe_create @classmethod @@ -697,7 +710,20 @@ def pre_dbe_update(cls, id, fq_name, obj_dict, db_conn, **kwargs): db_conn, obj_dict, id) if not ok: return (ok, result) - return cls.is_port_in_use_by_vm(obj_dict, db_conn) + + ok, result = cls.is_port_in_use_by_vm(obj_dict, db_conn) + if not ok: + return ok, result + + # Check if type of all associated BGP VPN are 'l3' + ok, result = BgpvpnServer.check_router_supports_vpn_type( + db_conn, obj_dict, update=True) + if not ok: + return ok, result + + # Check if we can reference the BGP VPNs + return BgpvpnServer.check_router_has_bgpvpn_assoc_via_network( + db_conn, obj_dict) # end pre_dbe_create # end class LogicalRouterServer @@ -1164,6 +1190,18 @@ def undo_vn_id(): if not ok: return (False, (400, error)) + # Check if network forwarding mode support BGP VPN types + ok, result = BgpvpnServer.check_network_supports_vpn_type( + db_conn, obj_dict) + if not ok: + return ok, result + + # Check if we can reference the BGP VPNs + ok, result = BgpvpnServer.check_network_has_bgpvpn_assoc_via_router( + db_conn, obj_dict) + if not ok: + return ok, result + ipam_refs = obj_dict.get('network_ipam_refs') or [] try: cls.addr_mgmt.net_create_req(obj_dict) @@ -1288,6 +1326,18 @@ def pre_dbe_update(cls, id, fq_name, obj_dict, db_conn, **kwargs): if not ok: return (ok, (409, result)) + # Check if network forwarding mode support BGP VPN types + ok, result = BgpvpnServer.check_network_supports_vpn_type( + db_conn, obj_dict, update=True) + if not ok: + return ok, result + + # Check if we can reference the BGP VPNs + ok, result = BgpvpnServer.check_network_has_bgpvpn_assoc_via_router( + db_conn, obj_dict) + if not ok: + return ok, result + ipam_refs = obj_dict.get('network_ipam_refs') or [] try: cls.addr_mgmt.net_update_req(fq_name, read_result, obj_dict, id) @@ -2412,7 +2462,7 @@ def pre_dbe_create(cls, tenant_name, obj_dict, db_conn): def pre_dbe_update(cls, id, fq_name, obj_dict, db_conn, **kwargs): ok, forwarding_class = cls.dbe_read(db_conn, 'forwarding_class', id) if not ok: - return ok, read_result + return ok, forwarding_class if 'forwarding_class_id' in obj_dict: fc_id = obj_dict['forwarding_class_id'] @@ -2526,6 +2576,7 @@ def pre_dbe_update(cls, id, fq_name, obj_dict, db_conn, **kwargs): return cls._check_qos_values(obj_dict, db_conn) # end class QosConfigServer + class FloatingIpPoolServer(Resource, FloatingIpPool): @classmethod def pre_dbe_create(cls, tenant_name, obj_dict, db_conn): @@ -2592,6 +2643,7 @@ def pre_dbe_create(cls, tenant_name, obj_dict, db_conn): return True, "" # end class FloatingIpPoolServer + class PhysicalRouterServer(Resource, PhysicalRouter): @classmethod def post_dbe_read(cls, obj_dict, db_conn): @@ -2601,3 +2653,206 @@ def post_dbe_read(cls, obj_dict, db_conn): return True, '' # end class PhysicalRouterServer + + +class BgpvpnServer(Resource, Bgpvpn): + @classmethod + def _get_associated_bgpvpn_uuids(cls, db_conn, resource_type, + resource_dict, update=False): + bgpvpn_uuids = set(bgpvpn_ref['uuid'] for bgpvpn_ref + in resource_dict.get('bgpvpn_refs', [])) + if not update: + return True, bgpvpn_uuids + + ok, result = cls.dbe_read(db_conn, resource_type, + resource_dict['uuid'], ['bgpvpn_refs']) + if not ok: + return ok, result + bgpvpn_refs = result.get('bgpvpn_refs', []) + [bgpvpn_uuids.add(bgpvpn_ref['uuid']) for bgpvpn_ref in bgpvpn_refs] + + return True, bgpvpn_uuids + + @classmethod + def check_network_supports_vpn_type(cls, db_conn, vn_dict, update=False): + """ + Validate the associated bgpvpn types correspond to the virtual + forwarding type. + """ + if not vn_dict: + return True, '' + + ok, result = cls._get_associated_bgpvpn_uuids(db_conn, + 'virtual_network', + vn_dict, update) + if not ok: + return ok, result + bgpvpn_uuids = result + if not bgpvpn_uuids: + return True, '' + + forwarding_mode = 'l2_l3' + virtual_network_properties = vn_dict.get('virtual_network_properties') + if virtual_network_properties is not None: + forwarding_mode = virtual_network_properties.get('forwarding_mode') + + # Forwarding mode 'l2_l3' (default mode) can support all vpn types + if forwarding_mode == 'l2_l3': + return True, '' + + ok, result = db_conn.dbe_list('bgpvpn', + obj_uuids=list(bgpvpn_uuids), + field_names=['bgpvpn_type']) + if not ok: + return ok, (500, 'Error in dbe_list: %s' % pformat(result)) + bgpvpns = result + + vpn_types = set(bgpvpn['bgpvpn_type'] for bgpvpn in bgpvpns) + if len(vpn_types) > 1: + msg = ("Cannot associates different bgpvpn types '%s' on a " + "virtual network with a forwarding mode different to" + "'l2_l3'" % vpn_types) + return False, (400, msg) + elif set([forwarding_mode]) != vpn_types: + msg = ("Cannot associates bgpvpn type '%s' with a virtual " + "network in forwarding mode '%s'" % (vpn_types.pop(), + forwarding_mode)) + return False, (400, msg) + return True, '' + + @classmethod + def check_router_supports_vpn_type(cls, db_conn, lr_dict, update=False): + """Limit associated bgpvpn types to 'l3' for logical router.""" + + if not lr_dict: + return True, '' + + ok, result = cls._get_associated_bgpvpn_uuids(db_conn, + 'logical_router', + lr_dict, update) + if not ok: + return ok, result + bgpvpn_uuids = result + if not bgpvpn_uuids: + return True, '' + + ok, result = db_conn.dbe_list('bgpvpn', + obj_uuids=bgpvpn_uuids, + field_names=['bgpvpn_type']) + if not ok: + return ok, (500, 'Error in dbe_list: %s' % pformat(result)) + bgpvpns = result + + bgpvpn_not_supported = [bgpvpn for bgpvpn in bgpvpns + if bgpvpn['bgpvpn_type'] != 'l3'] + + if not bgpvpn_not_supported: + return True, '' + + msg = "Only bgpvpn type 'l3' can be associated to a logical router:\n" + for bgpvpn in bgpvpn_not_supported: + msg += ("- bgpvpn %s(%s) type is %s\n" % + (bgpvpn.get('display_name', bgpvpn['fq_name'][-1]), + bgpvpn['uuid'], bgpvpn['bgpvpn_type'])) + return False, (400, msg) + + @classmethod + def check_network_has_bgpvpn_assoc_via_router(cls, db_conn, vn_dict): + """ + Check if logical routers attached to the network already have + a bgpvpn associated to it. If yes, forbid to add bgpvpn to that + networks. + """ + if vn_dict.get('bgpvpn_refs') is None: + return True, '' + + # List all logical router's vmis of networks + filters = { + 'virtual_machine_interface_device_owner': + ['network:router_interface'] + } + ok, result = db_conn.dbe_list('virtual_machine_interface', + back_ref_uuids=[vn_dict['uuid']], + filters=filters, + field_names=['logical_router_back_refs']) + if not ok: + return ok, (500, 'Error in dbe_list: %s' % pformat(result)) + vmis = result + + # Read bgpvpn refs of logical routers founded + lr_uuids = [vmi['logical_router_back_refs'][0]['uuid'] + for vmi in vmis] + if not lr_uuids: + return True, '' + ok, result = db_conn.dbe_list('logical_router', + obj_uuids=lr_uuids, + field_names=['bgpvpn_refs']) + if not ok: + return ok, (500, 'Error in dbe_list: %s' % pformat(result)) + lrs = result + founded_bgpvpns = [(bgpvpn_ref['to'][-1], bgpvpn_ref['uuid'], + lr.get('display_name', lr['fq_name'][-1]), + lr['uuid']) + for lr in lrs + for bgpvpn_ref in lr.get('bgpvpn_refs', [])] + if not founded_bgpvpns: + return True, '' + msg = ("Network %s (%s) is linked to a logical router which is " + "associated to bgpvpn(s):\n" % + (vn_dict.get('display_name', vn_dict['fq_name'][-1]), + vn_dict['uuid'])) + for founded_bgpvpn in founded_bgpvpns: + msg += ("- bgpvpn %s (%s) associated to router %s (%s)\n" % + founded_bgpvpn) + return False, (400, msg[:-1]) + + @classmethod + def check_router_has_bgpvpn_assoc_via_network(cls, db_conn, lr_dict): + """ + Check if virtual networks attached to the router already have + a bgpvpn associated to it. If yes, forbid to add bgpvpn to that + routers. + """ + if lr_dict.get('bgpvpn_refs') is None: + return True, '' + + vmi_uuids = [vmi_ref['uuid'] for vmi_ref in + lr_dict.get('virtual_machine_interface_refs', [])] + if not vmi_uuids: + return True, '' + + # List vmis to obtain their virtual networks + ok, result = db_conn.dbe_list('virtual_machine_interface', + obj_uuids=vmi_uuids, + field_names=['virtual_network_refs']) + if not ok: + return ok, (500, 'Error in dbe_list: %s' % pformat(result)) + vmis = result + vn_uuids = [vn_ref['uuid'] + for vmi in vmis + for vn_ref in vmi.get('virtual_network_refs', [])] + if not vn_uuids: + return True, '' + + # List bgpvpn refs of virtual networks founded + ok, result = db_conn.dbe_list('virtual_network', + obj_uuids=vn_uuids, + field_names=['bgpvpn_refs']) + if not ok: + return ok, (500, 'Error in dbe_list: %s' % pformat(result)) + vns = result + founded_bgpvpns = [(bgpvpn_ref['to'][-1], bgpvpn_ref['uuid'], + vn.get('display_name', vn['fq_name'][-1]), + vn['uuid']) + for vn in vns + for bgpvpn_ref in vn.get('bgpvpn_refs', [])] + if not founded_bgpvpns: + return True, '' + msg = ("Router %s (%s) is linked to virtual network(s) which is/are " + "associated to bgpvpn(s):\n" % + (lr_dict.get('display_name', lr_dict['fq_name'][-1]), + lr_dict['uuid'])) + for founded_bgpvpn in founded_bgpvpns: + msg += ("- bgpvpn %s (%s) associated to network %s (%s)\n" % + founded_bgpvpn) + return False, (400, msg[:-1]) diff --git a/src/config/common/tests/test_common.py b/src/config/common/tests/test_common.py index 60047f54479..761909bac64 100644 --- a/src/config/common/tests/test_common.py +++ b/src/config/common/tests/test_common.py @@ -17,6 +17,7 @@ from webtest import TestApp import contextlib from lxml import etree +from netaddr import IPNetwork, IPAddress from vnc_api.vnc_api import * import kombu @@ -752,7 +753,7 @@ def get_obj_imid(self, obj): return imid.get_ifmap_id_from_fq_name(obj._type, obj.get_fq_name_str()) # end get_obj_imid - def create_virtual_network(self, vn_name, vn_subnet): + def create_virtual_network(self, vn_name, vn_subnet='10.0.0.0/24'): vn_obj = VirtualNetwork(name=vn_name) ipam_fq_name = [ 'default-domain', 'default-project', 'default-network-ipam'] @@ -760,10 +761,17 @@ def create_virtual_network(self, vn_name, vn_subnet): subnets = [vn_subnet] if isinstance(vn_subnet, basestring) else vn_subnet subnet_infos = [] for subnet in subnets: - cidr = subnet.split('/') - pfx = cidr[0] - pfx_len = int(cidr[1]) - subnet_infos.append(IpamSubnetType(subnet=SubnetType(pfx, pfx_len))) + cidr = IPNetwork(subnet) + subnet_infos.append( + IpamSubnetType( + subnet=SubnetType( + str(cidr.network), + int(cidr.prefixlen), + ), + default_gateway=str(IPAddress(cidr.last - 1)), + subnet_uuid=str(uuid.uuid4()), + ) + ) subnet_data = VnSubnetsType(subnet_infos) vn_obj.add_network_ipam(ipam_obj, subnet_data) self._vnc_lib.virtual_network_create(vn_obj) @@ -875,4 +883,45 @@ def create_network_policy(self, vn1, vn2, service_list=None, mirror_service=None return np # end create_network_policy + def create_logical_router(self, name, nb_of_attached_networks=1, **kwargs): + lr = LogicalRouter(name, **kwargs) + vns = [] + vmis = [] + iips = [] + for idx in range(nb_of_attached_networks): + # Virtual Network + vn = self.create_virtual_network('%s-network%d' % (name, idx), + '10.%d.0.0/24' % idx) + vns.append(vn) + + # Virtual Machine Interface + vmi_name = '%s-network%d-vmi' % (name, idx) + vmi = VirtualMachineInterface( + vmi_name, parent_type='project', + fq_name=['default-domain', 'default-project', vmi_name]) + vmi.set_virtual_machine_interface_device_owner( + 'network:router_interface') + vmi.add_virtual_network(vn) + self._vnc_lib.virtual_machine_interface_create(vmi) + lr.add_virtual_machine_interface(vmi) + vmis.append(vmi) + + # Instance IP + gw_ip = vn.get_network_ipam_refs()[0]['attr'].ipam_subnets[0].\ + default_gateway + subnet_uuid = vn.get_network_ipam_refs()[0]['attr'].\ + ipam_subnets[0].subnet_uuid + iip = InstanceIp(name='%s-network%d-iip' % (name, idx)) + iip.set_subnet_uuid(subnet_uuid) + iip.set_virtual_machine_interface(vmi) + iip.set_virtual_network(vn) + iip.set_instance_ip_family('v4') + iip.set_instance_ip_address(gw_ip) + self._vnc_lib.instance_ip_create(iip) + iips.append(iip) + + + self._vnc_lib.logical_router_create(lr) + return lr, vns, vmis, iips + # end TestCase diff --git a/src/config/schema-transformer/config_db.py b/src/config/schema-transformer/config_db.py index ff5d39ab203..1e3d6c46b69 100644 --- a/src/config/schema-transformer/config_db.py +++ b/src/config/schema-transformer/config_db.py @@ -245,6 +245,7 @@ def __init__(self, name, obj=None, acl_dict=None): self.virtual_machine_interfaces = set() self.connections = set() self.routing_instances = set() + self.bgpvpns = set() self.acl = None self.dynamic_acl = None self.multi_policy_service_chains_enabled = None @@ -260,11 +261,13 @@ def __init__(self, name, obj=None, acl_dict=None): self.dynamic_acl = acl_obj self.ipams = {} - self.get_route_target_lists(self.obj) - for rt in itertools.chain(self.rt_list, self.import_rt_list, - self.export_rt_list): - RouteTargetST.locate(rt) self._route_target = None + self.rt_list = set() + self.import_rt_list = set() + self.export_rt_list = set() + self.bgpvpn_rt_list = set() + self.bgpvpn_import_rt_list = set() + self.bgpvpn_export_rt_list = set() self.route_tables = set() self.service_chains = {} prop = self.obj.get_virtual_network_properties( @@ -323,6 +326,7 @@ def update(self, obj=None): policy.virtual_networks.add(self.name) self.update_multiple_refs('route_table', self.obj) + self.update_multiple_refs('bgpvpn', self.obj) self.ipams = {} for ipam_ref in self.obj.get_network_ipam_refs() or []: subnet = ipam_ref['attr'] @@ -454,6 +458,7 @@ def delete_obj(self): self._object_db.free_vn_id(nid - 1) self.update_multiple_refs('route_table', {}) + self.update_multiple_refs('bgpvpn', {}) self.uve_send(deleted=True) # end delete_obj @@ -704,6 +709,17 @@ def get_route_target_lists(self, obj): else: self.export_rt_list = set() + # Get BGP VPN's route targets associated to that network + self.bgpvpn_rt_list = set() + self.bgpvpn_import_rt_list = set() + self.bgpvpn_export_rt_list = set() + for bgpvpn_name in self.bgpvpns: + bgpvpn = BgpvpnST.get(bgpvpn_name) + if bgpvpn is not None: + self.bgpvpn_rt_list |= bgpvpn.rt_list + self.bgpvpn_import_rt_list |= bgpvpn.import_rt_list + self.bgpvpn_export_rt_list |= bgpvpn.export_rt_list + # if any RT exists in both import and export, just add it to rt_list self.rt_list |= self.import_rt_list & self.export_rt_list # if any RT exists in rt_list, remove it from import/export lists @@ -712,22 +728,39 @@ def get_route_target_lists(self, obj): # end get_route_target_lists def set_route_target_list(self, obj): - ri = self.get_primary_routing_instance() - old_rt_list = set(self.rt_list) - old_import_rt_list = set(self.import_rt_list) - old_export_rt_list = set(self.export_rt_list) - self.get_route_target_lists(obj) + old_rt_list = self.rt_list.copy() + old_import_rt_list = self.import_rt_list.copy() + old_export_rt_list = self.export_rt_list.copy() + old_bgpvpn_rt_list = self.bgpvpn_rt_list.copy() + old_bgpvpn_import_rt_list = self.bgpvpn_import_rt_list.copy() + old_bgpvpn_export_rt_list = self.bgpvpn_export_rt_list.copy() - rt_add = self.rt_list - old_rt_list - rt_add_export = self.export_rt_list - old_export_rt_list - rt_add_import = self.import_rt_list - old_import_rt_list - rt_del = ((old_rt_list - self.rt_list) | - (old_export_rt_list - self.export_rt_list) | - (old_import_rt_list - self.import_rt_list)) + self.get_route_target_lists(obj) + rt_add = ((self.rt_list - old_rt_list) | + (self.bgpvpn_rt_list - old_bgpvpn_rt_list)) + rt_add_import = ((self.import_rt_list - old_import_rt_list) | + (self.bgpvpn_import_rt_list - + old_bgpvpn_import_rt_list)) + rt_add_export = ((self.export_rt_list - old_export_rt_list) | + (self.bgpvpn_export_rt_list - + old_bgpvpn_export_rt_list)) + rt_del = ( + ((old_rt_list - self.rt_list) | + (old_import_rt_list - self.import_rt_list) | + (old_export_rt_list - self.export_rt_list) | + (old_bgpvpn_rt_list - self.bgpvpn_rt_list) | + (old_bgpvpn_import_rt_list - self.bgpvpn_import_rt_list) | + (old_bgpvpn_export_rt_list - self.bgpvpn_export_rt_list)) - + (self.rt_list | self.import_rt_list | self.export_rt_list | + self.bgpvpn_rt_list | self.bgpvpn_import_rt_list | + self.bgpvpn_export_rt_list) + ) if not (rt_add or rt_add_export or rt_add_import or rt_del): return for rt in itertools.chain(rt_add, rt_add_export, rt_add_import): RouteTargetST.locate(rt) + + ri = self.get_primary_routing_instance() if ri: ri.update_route_target_list(rt_add=rt_add, rt_add_import=rt_add_import, @@ -1074,6 +1107,8 @@ def update_pnf_presence(self): # end update_pnf_presence def evaluate(self): + self.set_route_target_list(self.obj) + old_virtual_network_connections = self.expand_connections() old_service_chains = self.service_chains self.connections = set() @@ -1282,20 +1317,31 @@ def handle_st_object_req(self): self._get_sandesh_ref_list('virtual_machine_interface'), sandesh.RefList('virtual_network', self.connections), self._get_sandesh_ref_list('route_table'), - self._get_sandesh_ref_list('service_chain') + self._get_sandesh_ref_list('service_chain'), + self._get_sandesh_ref_list('bgpvpn'), ] resp.properties = [ sandesh.PropList('route_target', self.get_route_target()), sandesh.PropList('network_id', str(self.obj.get_virtual_network_network_id())), sandesh.PropList('multi_service_chains', - str(self.multi_policy_service_chains_enabled)) + str(self.multi_policy_service_chains_enabled)), + sandesh.PropList('rt_list', ', '.join(self.rt_list)), + sandesh.PropList('import_rt_list', + ', '.join(self.import_rt_list)), + sandesh.PropList('export_rt_list', + ', '.join(self.export_rt_list)), + sandesh.PropList('bgpvpn_rt_list', ', '.join(self.bgpvpn_rt_list)), + sandesh.PropList('bgpvpn_import_rt_list', + ', '.join(self.bgpvpn_import_rt_list)), + sandesh.PropList('bgpvpn_export_rt_list', + ', '.join(self.bgpvpn_export_rt_list)), ] return resp # end handle_st_object_req def get_gateway(self, address): - """Returns the defualt gateway of the network + """Returns the default gateway of the network to which the 'address' belongs """ for ipam in self.ipams.values(): @@ -2032,7 +2078,7 @@ def locate_route_target(self): else: self.stale_route_targets.remove(rt_key) if inst_tgt_data: - for rt in vn.rt_list: + for rt in vn.rt_list | vn.bgpvpn_rt_list: if rt not in self.stale_route_targets: rtgt_obj = RouteTargetST.locate(rt) self.obj.add_route_target(rtgt_obj.obj, inst_tgt_data) @@ -2040,7 +2086,8 @@ def locate_route_target(self): else: self.stale_route_targets.remove(rt) if self.is_default: - for rt in vn.export_rt_list: + for rt in (vn.export_rt_list | + vn.bgpvpn_export_rt_list): if rt not in self.stale_route_targets: rtgt_obj = RouteTargetST.locate(rt) self.obj.add_route_target( @@ -2048,7 +2095,8 @@ def locate_route_target(self): update_ri = True else: self.stale_route_targets.remove(rt) - for rt in vn.import_rt_list: + for rt in (vn.import_rt_list | + vn.bgpvpn_import_rt_list): if rt not in self.stale_route_targets: rtgt_obj = RouteTargetST.locate(rt) self.obj.add_route_target( @@ -3740,7 +3788,12 @@ def __init__(self, name, obj=None): self.virtual_networks = set() self.route_tables = set() self.rt_list = set() + self.bgpvpns = set() + self.bgpvpn_rt_list = set() + self.bgpvpn_import_rt_list = set() + self.bgpvpn_export_rt_list = set() self.obj = obj or self.read_vnc_obj(fq_name=name) + rt_ref = self.obj.get_route_target_refs() old_rt_key = None if rt_ref: @@ -3768,18 +3821,22 @@ def update(self, obj=None): self.obj = obj or self.read_vnc_obj(uuid=self.obj.uuid) self.update_multiple_refs('virtual_machine_interface', self.obj) self.update_multiple_refs('route_table', self.obj) - self.update_virtual_networks() - rt_list = self.obj.get_configured_route_target_list() or RouteTargetList() - self.set_route_target_list(rt_list) + self.update_multiple_refs('bgpvpn', self.obj) # end update + def evaluate(self): + self.update_virtual_networks() + self.set_route_target_list() + # end evaluate + def delete_obj(self): self.update_multiple_refs('virtual_machine_interface', {}) self.update_multiple_refs('route_table', {}) + self.update_multiple_refs('bgpvpn', {}) self.update_virtual_networks() rtgt_num = int(self.route_target.split(':')[-1]) self._object_db.free_route_target_by_number(rtgt_num) - RouteTargetST.delete_vnc_obj(self.route_target) + self.delete_route_targets([self.route_target]) # end delete_obj def update_virtual_networks(self): @@ -3798,14 +3855,24 @@ def set_virtual_networks(self, vn_set): vn_obj = VirtualNetworkST.get(vn) if vn_obj is not None: ri_obj = vn_obj.get_primary_routing_instance() - ri_obj.update_route_target_list( - rt_add=set(), rt_del=self.rt_list|set([self.route_target])) + rt_del = (set([self.route_target]) | + self.rt_list | + self.bgpvpn_rt_list | + self.bgpvpn_import_rt_list | + self.bgpvpn_export_rt_list) + ri_obj.update_route_target_list(rt_add=set(), rt_del=rt_del) + self.delete_route_targets(rt_del - set([self.route_target])) for vn in vn_set - self.virtual_networks: vn_obj = VirtualNetworkST.get(vn) if vn_obj is not None: ri_obj = vn_obj.get_primary_routing_instance() + rt_add = (self.rt_list | + set([self.route_target]) | + self.bgpvpn_rt_list) ri_obj.update_route_target_list( - rt_add=self.rt_list|set([self.route_target])) + rt_add=rt_add, + rt_add_import=self.bgpvpn_import_rt_list, + rt_add_export=self.bgpvpn_export_rt_list) self.virtual_networks = vn_set # end set_virtual_networks @@ -3830,32 +3897,83 @@ def update_autonomous_system(self, asn): ri_obj = vn_obj.get_primary_routing_instance() ri_obj.update_route_target_list(rt_del=[old_rt], rt_add=[rt_key]) - RouteTargetST.delete_vnc_obj(old_rt) + self.delete_route_targets([old_rt]) self.route_target = rt_key # end update_autonomous_system - def set_route_target_list(self, rt_list): - old_rt_list = self.rt_list + def set_route_target_list(self): + old_rt_list = self.rt_list.copy() + old_bgpvpn_rt_list = self.bgpvpn_rt_list.copy() + old_bgpvpn_import_rt_list = self.bgpvpn_import_rt_list.copy() + old_bgpvpn_export_rt_list = self.bgpvpn_export_rt_list.copy() + + # Set the system allocated route target + rt_list = self.obj.get_configured_route_target_list()\ + or RouteTargetList() self.rt_list = set(rt_list.get_route_target()) - rt_add = self.rt_list - old_rt_list - rt_del = old_rt_list - self.rt_list + + # Get BGP VPN's route targets associated to that router + self.bgpvpn_rt_list = set() + self.bgpvpn_import_rt_list = set() + self.bgpvpn_export_rt_list = set() + for bgpvpn_name in self.bgpvpns: + bgpvpn = BgpvpnST.get(bgpvpn_name) + if bgpvpn is not None: + self.bgpvpn_rt_list |= bgpvpn.rt_list + self.bgpvpn_import_rt_list |= bgpvpn.import_rt_list + self.bgpvpn_export_rt_list |= bgpvpn.export_rt_list + + rt_add = ((self.rt_list - old_rt_list) | + (self.bgpvpn_rt_list - old_bgpvpn_rt_list)) + rt_add_import = self.bgpvpn_import_rt_list - old_bgpvpn_import_rt_list + rt_add_export = self.bgpvpn_export_rt_list - old_bgpvpn_export_rt_list + rt_del = ( + ((old_rt_list - self.rt_list) | + (old_bgpvpn_rt_list - self.bgpvpn_rt_list) | + (old_bgpvpn_import_rt_list - self.bgpvpn_import_rt_list) | + (old_bgpvpn_export_rt_list - self.bgpvpn_export_rt_list)) - + (self.rt_list | self.bgpvpn_rt_list | self.bgpvpn_import_rt_list | + self.bgpvpn_export_rt_list) + ) + if not (rt_add or rt_add_import or rt_add_export or rt_del): + return for vn in self.virtual_networks: vn_obj = VirtualNetworkST.get(vn) if vn_obj is not None: ri_obj = vn_obj.get_primary_routing_instance() - ri_obj.update_route_target_list(rt_del=rt_del, rt_add=rt_add) + ri_obj.update_route_target_list(rt_add=rt_add, + rt_add_import=rt_add_import, + rt_add_export=rt_add_export, + rt_del=rt_del) + self.delete_route_targets(rt_del) # end set_route_target_list + @staticmethod + def delete_route_targets(route_targets=None): + for rt in route_targets or []: + try: + RouteTargetST.delete_vnc_obj(rt) + except RefsExistError: + pass + def handle_st_object_req(self): resp = super(LogicalRouterST, self).handle_st_object_req() resp.obj_refs = [ self._get_sandesh_ref_list('virtual_machine_interface'), self._get_sandesh_ref_list('virtual_network'), - sandesh.RefList('route_target', self.rt_list) + self._get_sandesh_ref_list('bgpvpn'), + sandesh.RefList('route_target', self.rt_list), ] resp.properties = [ sandesh.PropList('route_target', self.route_target), + sandesh.PropList('configured_rt_list', ', '.join(self.rt_list)), + sandesh.PropList('bgpvpn_router_target_list', + ', '.join(self.bgpvpn_rt_list)), + sandesh.PropList('bgpvpn_import_route_targt_list', + ', '.join(self.bgpvpn_import_rt_list)), + sandesh.PropList('bgpvpn_export_route_target_list', + ', '.join(self.bgpvpn_export_rt_list)), ] return resp # end handle_st_object_req @@ -4261,3 +4379,62 @@ def handle_st_object_req(self): return resp # end handle_st_object_req # end PortTupleST + +class BgpvpnST(DBBaseST): + _dict = {} + obj_type = 'bgpvpn' + + def __init__(self, name, obj=None): + self.name = name + self.virtual_networks = set() + self.logical_routers = set() + self.update(obj) + + def update(self, obj=None): + self.obj = obj or self.read_vnc_obj(fq_name=self.name) + self.update_multiple_refs('virtual_network', self.obj) + self.update_multiple_refs('logical_router', self.obj) + self.get_route_target_lists() + + def delete_obj(self): + self.update_multiple_refs('virtual_network', {}) + self.update_multiple_refs('logical_router', {}) + + def get_route_target_lists(self): + rt_list = self.obj.get_route_target_list() + if rt_list: + self.rt_list = set(rt_list.get_route_target()) + else: + self.rt_list = set() + rt_list = self.obj.get_import_route_target_list() + if rt_list: + self.import_rt_list = set(rt_list.get_route_target()) + else: + self.import_rt_list = set() + rt_list = self.obj.get_export_route_target_list() + if rt_list: + self.export_rt_list = set(rt_list.get_route_target()) + else: + self.export_rt_list = set() + + # if any RT exists in both import and export, just add it to rt_list + self.rt_list |= self.import_rt_list & self.export_rt_list + # if any RT exists in rt_list, remove it from import/export lists + self.import_rt_list -= self.rt_list + self.export_rt_list -= self.rt_list + + def handle_st_object_req(self): + resp = super(BgpvpnST, self).handle_st_object_req() + resp.obj_refs = [ + self._get_sandesh_ref_list('virtual_network'), + self._get_sandesh_ref_list('logical_router'), + ] + resp.properties = [ + sandesh.PropList('bgpvpn_type', self.obj.get_bgpvpn_type()), + sandesh.PropList('rt_list', ', '.join(self.rt_list)), + sandesh.PropList('import_rt_list', + ', '.join(self.import_rt_list)), + sandesh.PropList('export_rt_list', + ', '.join(self.export_rt_list)), + ] + return resp diff --git a/src/config/schema-transformer/test/test_bgpvpn.py b/src/config/schema-transformer/test/test_bgpvpn.py new file mode 100644 index 00000000000..ddf19d6c16f --- /dev/null +++ b/src/config/schema-transformer/test/test_bgpvpn.py @@ -0,0 +1,304 @@ +# +# Copyright (c) 2016 Juniper Networks, Inc. All rights reserved. +# + +from gevent import sleep + +from vnc_api.vnc_api import Bgpvpn +from vnc_api.vnc_api import RouteTargetList + +from test_case import STTestCase +from test_route_target import VerifyRouteTarget + + +class TestBgpvpnWithVirtualNetwork(STTestCase, VerifyRouteTarget): + def test_associating_bgpvpn(self): + # Create one virtual network + vn = self.create_virtual_network('vn-%s' % self.id(), '10.0.0.0/24') + # Create two bgpvpn with route targets + for idx in range(2): + bgpvpn = Bgpvpn('bgpvpn%d-%s' % (idx, self.id())) + bgpvpn.set_route_target_list( + RouteTargetList(['target:%d:0' % idx])) + bgpvpn.set_import_route_target_list( + RouteTargetList(['target:%d:1' % idx])) + bgpvpn.set_export_route_target_list( + RouteTargetList(['target:%d:2' % idx])) + self._vnc_lib.bgpvpn_create(bgpvpn) + vn.add_bgpvpn(bgpvpn) + self._vnc_lib.virtual_network_update(vn) + + # Check bgpvpn's route targets are correctly associated to virtual + # network's primary routing instances. + # Check imported and exported route targets + self.check_rt_in_ri(self.get_ri_name(vn), 'target:0:0') + self.check_rt_in_ri(self.get_ri_name(vn), 'target:0:0') + + # Check imported route targets + self.check_rt_in_ri(self.get_ri_name(vn), 'target:0:1', exim='import') + self.check_rt_in_ri(self.get_ri_name(vn), 'target:0:1', exim='import') + + # Check exported route targets + self.check_rt_in_ri(self.get_ri_name(vn), 'target:0:2', exim='export') + self.check_rt_in_ri(self.get_ri_name(vn), 'target:0:2', exim='export') + + def test_route_target_removed_when_resource_deleted(self): + # Create two virtual networks + vn1 = self.create_virtual_network('vn1-%s' % self.id(), '10.0.0.0/24') + vn2 = self.create_virtual_network('vn2-%s' % self.id(), '10.0.1.0/24') + # Create one bgpvpn with route target + bgpvpn = Bgpvpn('bgpvpn-%s' % self.id()) + rt_name = 'target:2:1' + bgpvpn.set_route_target_list(RouteTargetList([rt_name])) + bgpvpn_id = self._vnc_lib.bgpvpn_create(bgpvpn) + bgpvpn = self._vnc_lib.bgpvpn_read(id=bgpvpn_id) + # Associate bgpvpn to networks + for vn in [vn1, vn2]: + vn.add_bgpvpn(bgpvpn) + self._vnc_lib.virtual_network_update(vn) + + # Check route target is correctly removed when no more networks use + # them. + # Remove one of the associated virtual networks + self._vnc_lib.virtual_network_delete(id=vn2.uuid) + self.check_ri_is_deleted(self.get_ri_name(vn2)) + # Check the bgpvpn's route target is still referenced by + # virtual network's primary routing instance which are still associated + self.check_rt_in_ri(self.get_ri_name(vn1), rt_name) + + # Remove last associated virtual network + self._vnc_lib.virtual_network_delete(id=vn1.uuid) + self.check_ri_is_deleted(fq_name=self.get_ri_name(vn1)) + # Check the bgpvpn's route target was also deleted + self.check_rt_is_deleted(rt_name) + + def test_updating_bgpvpn(self): + # Create one virtual network + vn = self.create_virtual_network('vn-%s' % self.id(), '10.0.0.0/24') + # Create one bgpvpn without any route target associated to the virtual + # network + bgpvpn = Bgpvpn('bgpvpn-%s' % self.id()) + bgpvpn_id = self._vnc_lib.bgpvpn_create(bgpvpn) + bgpvpn = self._vnc_lib.bgpvpn_read(id=bgpvpn_id) + vn.add_bgpvpn(bgpvpn) + self._vnc_lib.virtual_network_update(vn) + + rt_name = 'target:3:1' + + # Check set/unset import and export route target + bgpvpn.set_route_target_list(RouteTargetList([rt_name])) + self._vnc_lib.bgpvpn_update(bgpvpn) + self.check_rt_in_ri(self.get_ri_name(vn), rt_name) + bgpvpn.set_route_target_list(RouteTargetList()) + self._vnc_lib.bgpvpn_update(bgpvpn) + self.check_rt_in_ri(self.get_ri_name(vn), rt_name, is_present=False) + self.check_rt_is_deleted(rt_name) + + # Check set/unset import route target + bgpvpn.set_import_route_target_list(RouteTargetList([rt_name])) + self._vnc_lib.bgpvpn_update(bgpvpn) + self.check_rt_in_ri(self.get_ri_name(vn), rt_name, exim='import') + bgpvpn.set_import_route_target_list(RouteTargetList()) + self._vnc_lib.bgpvpn_update(bgpvpn) + self.check_rt_in_ri(self.get_ri_name(vn), rt_name, is_present=False) + self.check_rt_is_deleted(rt_name) + + # Check set/unset export route target + bgpvpn.set_export_route_target_list(RouteTargetList([rt_name])) + self._vnc_lib.bgpvpn_update(bgpvpn) + self.check_rt_in_ri(self.get_ri_name(vn), rt_name, exim='export') + bgpvpn.set_export_route_target_list(RouteTargetList()) + self._vnc_lib.bgpvpn_update(bgpvpn) + self.check_rt_in_ri(self.get_ri_name(vn), rt_name, is_present=False) + self.check_rt_is_deleted(rt_name) + + def test_route_target_overlapping(self): + rt_name = 'target:4:1' + # Create one virtual network and set a route target in its route + # target list + vn = self.create_virtual_network('vn-%s' % self.id(), '10.0.0.0/24') + vn.set_route_target_list(RouteTargetList([rt_name])) + self._vnc_lib.virtual_network_update(vn) + # Create one bgpvpn, set the same route target in its route target list + # and associate it to the virtual network + bgpvpn = Bgpvpn('bgpvpn-%s' % self.id()) + bgpvpn.set_route_target_list(RouteTargetList([rt_name])) + bgpvpn_id = self._vnc_lib.bgpvpn_create(bgpvpn) + bgpvpn = self._vnc_lib.bgpvpn_read(id=bgpvpn_id) + vn.add_bgpvpn(bgpvpn) + self._vnc_lib.virtual_network_update(vn) + + # Check the route target is set on the virtual network's routing + # instance + self.check_rt_in_ri(self.get_ri_name(vn), rt_name) + + # Remove the route target from the bgpvpn' route target list and check + # the route target is still set on the virtual network's routing + # instance + bgpvpn.set_route_target_list(RouteTargetList()) + self._vnc_lib.bgpvpn_update(bgpvpn) + sleep(1) + self.check_rt_in_ri(self.get_ri_name(vn), rt_name) + + # Remove the route target from the virtual network's route target list + # and check is no more associate to the routing instance + vn.set_route_target_list(RouteTargetList()) + self._vnc_lib.virtual_network_update(vn) + self.check_rt_in_ri(self.get_ri_name(vn), rt_name, is_present=False) + self.check_rt_is_deleted(rt_name) + + +class TestBgpvpnWithLogicalRouter(STTestCase, VerifyRouteTarget): + def test_associating_bgpvpn(self): + # Create one logical with one interface on a virtual network + lr, vns, _, _ = self.create_logical_router('lr-%s' % self.id()) + # We attached only one virtual network to the logical router + vn = vns[0] + # Create two bgpvpn with route targets + for idx in range(2): + bgpvpn = Bgpvpn('bgpvpn%d-%s' % (idx, self.id())) + bgpvpn.set_route_target_list( + RouteTargetList(['target:%d:0' % idx])) + bgpvpn.set_import_route_target_list( + RouteTargetList(['target:%d:1' % idx])) + bgpvpn.set_export_route_target_list( + RouteTargetList(['target:%d:2' % idx])) + self._vnc_lib.bgpvpn_create(bgpvpn) + lr.add_bgpvpn(bgpvpn) + self._vnc_lib.logical_router_update(lr) + + # Check bgpvpn's route targets are correctly associated to virtual + # network's primary routing instances of network attached to the + # logical routers. + # Check imported and exported route targets + self.check_rt_in_ri(self.get_ri_name(vn), 'target:0:0') + self.check_rt_in_ri(self.get_ri_name(vn), 'target:1:0') + + # Check imported route targets + self.check_rt_in_ri(self.get_ri_name(vn), 'target:0:1', exim='import') + self.check_rt_in_ri(self.get_ri_name(vn), 'target:1:1', exim='import') + + # Check exported route targets + self.check_rt_in_ri(self.get_ri_name(vn), 'target:0:2', exim='export') + self.check_rt_in_ri(self.get_ri_name(vn), 'target:1:2', exim='export') + + def test_route_target_removed_when_resource_deleted(self): + # Create two logical router with one virtual network attached + lr1, vns1, _, _ = self.create_logical_router('lr1-%s' % self.id()) + lr2, vns2, _, _ = self.create_logical_router('lr2-%s' % self.id()) + # We attached only one virtual network per logical routers + vn1 = vns1[0] + vn2 = vns2[0] + # Create one bgpvpn with route target + bgpvpn = Bgpvpn('bgpvpn-%s' % self.id()) + rt_name = 'target:2:1' + bgpvpn.set_route_target_list(RouteTargetList([rt_name])) + bgpvpn_id = self._vnc_lib.bgpvpn_create(bgpvpn) + bgpvpn = self._vnc_lib.bgpvpn_read(id=bgpvpn_id) + # Associate bgpvpn to routers + for lr in [lr1, lr2]: + lr.add_bgpvpn(bgpvpn) + self._vnc_lib.logical_router_update(lr) + + # Check route target is correctly removed when no more routers use + # them. + # Remove one of the associated logical router but keep attached virtual + # network and its primary routing instance used to applied bgpvpn's + # route target + self._vnc_lib.logical_router_delete(id=lr2.uuid) + # Chech that routing instance still there but the bgpvpn route target + # were not anymore referenced + self.check_vn_ri_state(self.get_ri_name(vn2)) + self.check_rt_in_ri(self.get_ri_name(vn2), rt_name, is_present=False) + # Check the bgpvpn's route target is still referenced by + # virtual network's primary routing instance which are still associated + # to the first logical router + self.check_rt_in_ri(self.get_ri_name(vn1), rt_name) + + # Remove last associated logical router + self._vnc_lib.logical_router_delete(id=lr1.uuid) + # Chech the routing instance still there but the bgpvpn route target + # were not anymore referenced + self.check_vn_ri_state(self.get_ri_name(vn1)) + self.check_rt_in_ri(self.get_ri_name(vn1), rt_name, is_present=False) + # Check the bgpvpn's route target was also deleted + self.check_rt_is_deleted(rt_name) + + def test_updating_bgpvpn(self): + # Create one logical router with one private virtual network + lr, vns, _, _ = self.create_logical_router('lr-%s' % self.id()) + # We attached only one virtual network to the logical router + vn = vns[0] + # Create one bgpvpn without any route target associated to the logical + # router + bgpvpn = Bgpvpn('bgpvpn-%s' % self.id()) + bgpvpn_id = self._vnc_lib.bgpvpn_create(bgpvpn) + bgpvpn = self._vnc_lib.bgpvpn_read(id=bgpvpn_id) + lr.add_bgpvpn(bgpvpn) + self._vnc_lib.logical_router_update(lr) + + rt_name = 'target:3:1' + + # Check set/unset import and export route target + bgpvpn.set_route_target_list(RouteTargetList([rt_name])) + self._vnc_lib.bgpvpn_update(bgpvpn) + self.check_rt_in_ri(self.get_ri_name(vn), rt_name) + bgpvpn.set_route_target_list(RouteTargetList()) + self._vnc_lib.bgpvpn_update(bgpvpn) + self.check_rt_in_ri(self.get_ri_name(vn), rt_name, is_present=False) + self.check_rt_is_deleted(rt_name) + + # Check set/unset import route target + bgpvpn.set_import_route_target_list(RouteTargetList([rt_name])) + self._vnc_lib.bgpvpn_update(bgpvpn) + self.check_rt_in_ri(self.get_ri_name(vn), rt_name, exim='import') + bgpvpn.set_import_route_target_list(RouteTargetList()) + self._vnc_lib.bgpvpn_update(bgpvpn) + self.check_rt_in_ri(self.get_ri_name(vn), rt_name, is_present=False) + self.check_rt_is_deleted(rt_name) + + # Check set/unset export route target + bgpvpn.set_export_route_target_list(RouteTargetList([rt_name])) + self._vnc_lib.bgpvpn_update(bgpvpn) + self.check_rt_in_ri(self.get_ri_name(vn), rt_name, exim='export') + bgpvpn.set_export_route_target_list(RouteTargetList()) + self._vnc_lib.bgpvpn_update(bgpvpn) + self.check_rt_in_ri(self.get_ri_name(vn), rt_name, is_present=False) + self.check_rt_is_deleted(rt_name) + + def test_route_target_overlapping(self): + rt_name = 'target:4:1' + # Create one logical router with one private virtual network and set a + # route target in its configured route target list + lr, vns, _, _ = self.create_logical_router('lr-%s' % self.id()) + # We attached only one virtual network to the logical router + vn = vns[0] + lr.set_configured_route_target_list(RouteTargetList([rt_name])) + self._vnc_lib.logical_router_update(lr) + # Create one bgpvpn, set the same route target in its route target list + # and associate it to the logical router + bgpvpn = Bgpvpn('bgpvpn-%s' % self.id()) + bgpvpn.set_route_target_list(RouteTargetList([rt_name])) + bgpvpn_id = self._vnc_lib.bgpvpn_create(bgpvpn) + bgpvpn = self._vnc_lib.bgpvpn_read(id=bgpvpn_id) + lr.add_bgpvpn(bgpvpn) + self._vnc_lib.logical_router_update(lr) + + # Check the route target is set on the virtual network's routing + # instance + self.check_rt_in_ri(self.get_ri_name(vn), rt_name) + + # Remove the route target from the bgpvpn' route target list and check + # the route target is still set on the virtual network's routing + # instance + bgpvpn.set_route_target_list(RouteTargetList()) + self._vnc_lib.bgpvpn_update(bgpvpn) + sleep(1) + self.check_rt_in_ri(self.get_ri_name(vn), rt_name) + + # Remove the route target from the logical router's configured route + # target list and check is no more associate to the routing instance + lr.set_configured_route_target_list(RouteTargetList()) + self._vnc_lib.logical_router_update(lr) + self.check_rt_in_ri(self.get_ri_name(vn), rt_name, is_present=False) + self.check_rt_is_deleted(rt_name) diff --git a/src/config/schema-transformer/test/test_policy.py b/src/config/schema-transformer/test/test_policy.py index 68cad4a1ca2..9f6fee7e5b1 100644 --- a/src/config/schema-transformer/test/test_policy.py +++ b/src/config/schema-transformer/test/test_policy.py @@ -94,7 +94,7 @@ def check_acl_implicit_deny_rule(self, fq_name, src_vn, dst_vn): raise Exception('Implicit deny ACL rule not found') @retries(5) - def check_rt_in_ri(self, ri_name, rt_id, is_present, exim=None): + def check_rt_in_ri(self, ri_name, rt_id, is_present=True, exim=None): ri_obj = self._vnc_lib.routing_instance_read(fq_name=ri_name) ri_rt_refs = [ref for ref in ri_obj.get_route_target_refs() or [] if ref['to'][0] == rt_id] @@ -102,6 +102,7 @@ def check_rt_in_ri(self, ri_name, rt_id, is_present, exim=None): self.assertEqual(ri_rt_refs, []) return else: + self.assertEqual(len(ri_rt_refs), 1) self.assertEqual(ri_rt_refs[0]['to'][0], rt_id) self.assertEqual(ri_rt_refs[0]['attr'].import_export, exim) diff --git a/src/config/schema-transformer/to_bgp.py b/src/config/schema-transformer/to_bgp.py index b821768382a..9a36abbfe3d 100644 --- a/src/config/schema-transformer/to_bgp.py +++ b/src/config/schema-transformer/to_bgp.py @@ -73,6 +73,7 @@ class SchemaTransformer(object): 'network_policy': [], 'virtual_machine_interface': [], 'route_table': [], + 'bgpvpn': [], }, 'virtual_machine': { 'self': ['service_instance'], @@ -113,6 +114,7 @@ class SchemaTransformer(object): 'self': ['route_table'], 'virtual_machine_interface': [], 'route_table': [], + 'bgpvpn': [], }, 'floating_ip': { 'self': ['virtual_machine_interface'], @@ -139,7 +141,12 @@ class SchemaTransformer(object): }, 'route_aggregate': { 'self': ['service_instance'], - } + }, + 'bgpvpn': { + 'self': ['virtual_network', 'logical_router'], + 'virtual_network': [], + 'logical_router': [], + }, } def __init__(self, args=None): @@ -181,6 +188,7 @@ def __init__(self, args=None): def reinit(self): GlobalSystemConfigST.reinit() BgpRouterST.reinit() + BgpvpnST.reinit() LogicalRouterST.reinit() vn_list = list(VirtualNetworkST.list_vnc_obj()) vn_id_list = set([vn.uuid for vn in vn_list]) diff --git a/src/schema/bgpvpn.xsd b/src/schema/bgpvpn.xsd new file mode 100644 index 00000000000..b11e43a358e --- /dev/null +++ b/src/schema/bgpvpn.xsd @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/schema/vnc_cfg.xsd b/src/schema/vnc_cfg.xsd index d7a27e622ee..4d9c16e4c28 100644 --- a/src/schema/vnc_cfg.xsd +++ b/src/schema/vnc_cfg.xsd @@ -2802,9 +2802,6 @@ targetNamespace="http://www.contrailsystems.com/2012/VNC-CONFIG/0"> Property('api-access-list-entries', 'api-access-list', 'required', 'CRUD', 'List of rules e.g network.* => admin:CRUD (admin can perform all ops on networks).') --> - - - @@ -2838,6 +2835,12 @@ targetNamespace="http://www.contrailsystems.com/2012/VNC-CONFIG/0"> 'discovery-service-assignment', 'dsa-rule', ['has'], 'optional', 'CRUD', 'Discovery service rule for assigning subscriber to publisher. (set of subscriber) can be assigned to (set of publisher).') --> + + + + + +