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).') -->
+
+
+
+
+
+