Browse Source

Create network when project_id is an empty string

If L3 HA is enabled in neutron, by creating a router, a HA network with
no project_id is created. With this patch Arista ML2 driver uses an internal
project_id, i.e. 'INTERNAL-TENANT-ID', to set the project_id of the network when
creating it on the CVX.

Change-Id: Ifb9ce0a4594706945cef47321d902aa07a5944dd
changes/79/659179/12
Nader Lahouti 2 years ago
parent
commit
094b199a71
  1. 4
      etc/ml2_conf_arista.ini
  2. 5
      networking_arista/common/config.py
  3. 31
      networking_arista/common/db_lib.py
  4. 4
      networking_arista/common/utils.py
  5. 51
      networking_arista/ml2/arista_resources.py
  6. 5
      networking_arista/ml2/mechanism_arista.py
  7. 8
      networking_arista/tests/unit/common/test_db_lib.py
  8. 161
      networking_arista/tests/unit/ml2/ml2_test_base.py
  9. 193
      networking_arista/tests/unit/ml2/test_arista_resources.py
  10. 166
      networking_arista/tests/unit/ml2/test_mechanism_arista.py
  11. 94
      networking_arista/tests/unit/utils.py

4
etc/ml2_conf_arista.ini

@ -89,7 +89,9 @@
#eapi_password =
# Arista EOS IP address. This is required field. If not set, all
# communications to Arista EOS will fail. (string value)
# communications to Arista EOS will fail.
# If CVX has been deployed in a highly available (HA) cluster, specify each
# instance IP separated by a comma. (string value)
#eapi_host =
# Defines if hostnames are sent to Arista EOS as FQDNs

5
networking_arista/common/config.py

@ -43,7 +43,10 @@ ARISTA_DRIVER_OPTS = [
default='',
help=_('Arista EOS IP address. This is required field. '
'If not set, all communications to Arista EOS '
'will fail.')),
'will fail.'
'If CVX has been deployed in a highly available (HA) '
'cluster, specify each instance IP separated by '
'a comma.')),
cfg.BoolOpt('use_fqdn',
default=True,
help=_('Defines if hostnames are sent to Arista EOS as FQDNs '

31
networking_arista/common/db_lib.py

@ -56,18 +56,6 @@ def outerjoin_if_necessary(query, *args, **kwargs):
return query.outerjoin(*args, **kwargs)
def filter_unnecessary_segments(query):
"""Filter segments are not needed on CVX"""
segment_model = segment_models.NetworkSegment
network_model = models_v2.Network
query = (query
.join_if_necessary(network_model)
.join_if_necessary(segment_model)
.filter(network_model.project_id != '')
.filter_network_type())
return query
def filter_network_type(query):
"""Filter unsupported segment types"""
segment_model = segment_models.NetworkSegment
@ -198,19 +186,16 @@ Query.filter_by_vnic_type = filter_by_vnic_type
Query.filter_unmanaged_physnets = filter_unmanaged_physnets
Query.filter_inactive_ports = filter_inactive_ports
Query.filter_unnecessary_ports = filter_unnecessary_ports
Query.filter_unnecessary_segments = filter_unnecessary_segments
def get_tenants(tenant_id=None):
"""Returns list of all project/tenant ids that may be relevant on CVX"""
if tenant_id == '':
return []
session = db.get_reader_session()
project_ids = set()
with session.begin():
for m in [models_v2.Network, models_v2.Port]:
q = session.query(m.project_id).filter(m.project_id != '')
if tenant_id:
q = session.query(m.project_id)
if tenant_id is not None:
q = q.filter(m.project_id == tenant_id)
project_ids.update(pid[0] for pid in q.distinct())
return [{'project_id': project_id} for project_id in project_ids]
@ -221,7 +206,7 @@ def get_networks(network_id=None):
session = db.get_reader_session()
with session.begin():
model = models_v2.Network
networks = session.query(model).filter(model.project_id != '')
networks = session.query(model)
if network_id:
networks = networks.filter(model.id == network_id)
return networks.all()
@ -232,7 +217,7 @@ def get_segments(segment_id=None):
session = db.get_reader_session()
with session.begin():
model = segment_models.NetworkSegment
segments = session.query(model).filter_unnecessary_segments()
segments = session.query(model).filter_network_type()
if segment_id:
segments = segments.filter(model.id == segment_id)
return segments.all()
@ -266,7 +251,9 @@ def get_dhcp_instances(instance_id=None):
def get_router_instances(instance_id=None):
"""Returns filtered list of routers that may be relevant on CVX"""
return get_instances(device_owners=[n_const.DEVICE_OWNER_DVR_INTERFACE],
return get_instances(device_owners=[n_const.DEVICE_OWNER_DVR_INTERFACE,
n_const.DEVICE_OWNER_ROUTER_HA_INTF,
n_const.DEVICE_OWNER_ROUTER_INTF],
instance_id=instance_id)
@ -304,7 +291,9 @@ def get_dhcp_ports(port_id=None):
def get_router_ports(port_id=None):
"""Returns filtered list of routers that may be relevant on CVX"""
return get_ports(device_owners=[n_const.DEVICE_OWNER_DVR_INTERFACE],
return get_ports(device_owners=[n_const.DEVICE_OWNER_DVR_INTERFACE,
n_const.DEVICE_OWNER_ROUTER_HA_INTF,
n_const.DEVICE_OWNER_ROUTER_INTF],
port_id=port_id)

4
networking_arista/common/utils.py

@ -31,6 +31,8 @@ SUPPORTED_DEVICE_OWNERS = [
n_const.DEVICE_OWNER_BAREMETAL_PREFIX,
n_const.DEVICE_OWNER_DHCP,
n_const.DEVICE_OWNER_DVR_INTERFACE,
n_const.DEVICE_OWNER_ROUTER_HA_INTF,
n_const.DEVICE_OWNER_ROUTER_INTF,
t_const.TRUNK_SUBPORT_OWNER]
@ -62,6 +64,6 @@ def supported_device_owner(device_owner):
return False
def hostname(hostname):
def hostname(hostname, extra=None):
fqdns_used = cfg.CONF.ml2_arista['use_fqdn']
return hostname if fqdns_used else hostname.split('.')[0]

51
networking_arista/ml2/arista_resources.py

@ -20,6 +20,7 @@ from neutron_lib import constants as n_const
from neutron_lib.services.trunk import constants as t_const
from oslo_log import log as logging
from networking_arista.common import constants as a_const
from networking_arista.common import db_lib
from networking_arista.common import exceptions as arista_exc
from networking_arista.common import utils
@ -43,13 +44,16 @@ class AttributeFormatter(object):
def __init__(self, neutron_key, cvx_key, format=None, submodel=None):
self.neutron_key = neutron_key
self.cvx_key = cvx_key
self.format = format or (lambda arg: arg)
self.format = format or (
lambda neutron_val, base_resource: neutron_val)
self.submodel = submodel
def transform(self, resource):
base_resource = resource
if self.submodel:
resource = getattr(resource, self.submodel)
return (self.cvx_key, self.format(resource[self.neutron_key]))
return (self.cvx_key, self.format(resource[self.neutron_key],
base_resource))
class AristaResourcesBase(object):
@ -261,17 +265,29 @@ class AristaResourcesBase(object):
'pid': os.getpid()})
return resources_to_delete
@staticmethod
def modify_blank_project_id(project_id, resource):
if len(project_id) == 0:
return a_const.INTERNAL_TENANT_ID
return project_id
class Tenants(AristaResourcesBase):
endpoint = 'region/%(region)s/tenant'
formatter = [AttributeFormatter('project_id', 'id')]
formatter = [AttributeFormatter('project_id', 'id',
AristaResourcesBase.modify_blank_project_id
)]
get_db_resources = staticmethod(db_lib.get_tenants)
@classmethod
def format_for_delete(cls, id):
return {cls.id_key: id if id else a_const.INTERNAL_TENANT_ID}
class Networks(AristaResourcesBase):
def _is_shared(rbac_entries):
def _is_shared(rbac_entries, resource):
for entry in rbac_entries:
if (entry.action == 'access_as_shared' and
entry.target_tenant == '*'):
@ -279,7 +295,9 @@ class Networks(AristaResourcesBase):
return False
formatter = [AttributeFormatter('id', 'id'),
AttributeFormatter('project_id', 'tenantId'),
AttributeFormatter('project_id', 'tenantId',
AristaResourcesBase.modify_blank_project_id
),
AttributeFormatter('name', 'name'),
AttributeFormatter('rbac_entries', 'shared', _is_shared)]
endpoint = 'region/%(region)s/network'
@ -293,7 +311,7 @@ class Segments(AristaResourcesBase):
AttributeFormatter('segmentation_id', 'segmentationId'),
AttributeFormatter('network_id', 'networkId'),
AttributeFormatter('is_dynamic', 'segmentType',
lambda x: 'dynamic' if x else 'static')]
lambda n, r: 'dynamic' if n else 'static')]
endpoint = 'region/%(region)s/segment'
get_db_resources = staticmethod(db_lib.get_segments)
@ -313,12 +331,25 @@ class Dhcps(AristaResourcesBase):
class Routers(AristaResourcesBase):
def set_router_host_id(device_owner, resource):
if device_owner == n_const.DEVICE_OWNER_ROUTER_HA_INTF:
return 'HA Router'
if device_owner == n_const.DEVICE_OWNER_DVR_INTERFACE:
return 'distributed'
if device_owner == n_const.DEVICE_OWNER_ROUTER_INTF:
portbinding = getattr(resource, 'PortBinding')
return utils.hostname(portbinding.host) if portbinding else ''
formatter = [AttributeFormatter('device_id', 'id',
submodel='Port'),
AttributeFormatter('device_owner', 'hostId',
lambda *args: 'distributed',
set_router_host_id,
submodel='Port'),
AttributeFormatter('project_id', 'tenantId',
AristaResourcesBase
.modify_blank_project_id,
submodel='Port')]
endpoint = 'region/%(region)s/router'
get_db_resources = staticmethod(db_lib.get_router_instances)
@ -375,7 +406,9 @@ class RouterPorts(AristaResourcesBase):
AttributeFormatter('device_id', 'instanceId'),
AttributeFormatter('device_owner', 'instanceType',
lambda *args: 'router'),
AttributeFormatter('project_id', 'tenantId')]
AttributeFormatter('project_id', 'tenantId',
AristaResourcesBase.modify_blank_project_id
)]
get_db_resources = staticmethod(db_lib.get_router_ports)
@ -405,7 +438,7 @@ class VmPorts(AristaResourcesBase):
class BaremetalPorts(AristaResourcesBase):
def _get_vlan_type(device_owner):
def _get_vlan_type(device_owner, resource):
if (device_owner.startswith(n_const.DEVICE_OWNER_BAREMETAL_PREFIX) or
device_owner.startswith(n_const.DEVICE_OWNER_COMPUTE_PREFIX)):
return 'native'

5
networking_arista/ml2/mechanism_arista.py

@ -121,6 +121,8 @@ class AristaDriver(driver_api.MechanismDriver):
owner_to_type = {
n_const.DEVICE_OWNER_DHCP: a_const.DHCP_RESOURCE,
n_const.DEVICE_OWNER_DVR_INTERFACE: a_const.ROUTER_RESOURCE,
n_const.DEVICE_OWNER_ROUTER_INTF: a_const.ROUTER_RESOURCE,
n_const.DEVICE_OWNER_ROUTER_HA_INTF: a_const.ROUTER_RESOURCE,
trunk_consts.TRUNK_SUBPORT_OWNER: a_const.VM_RESOURCE}
if port['device_owner'] in owner_to_type.keys():
return owner_to_type[port['device_owner']]
@ -397,7 +399,7 @@ class AristaDriver(driver_api.MechanismDriver):
binding_levels = context.original_binding_levels
else:
binding_levels = context.binding_levels
LOG.debug("_try_release_dynamic_segment: "
LOG.debug("_try_to_release_dynamic_segment: "
"binding_levels=%(bl)s", {'bl': binding_levels})
if not binding_levels:
return
@ -413,6 +415,7 @@ class AristaDriver(driver_api.MechanismDriver):
continue
if not db_lib.segment_bound(segment_id):
context.release_dynamic_segment(segment_id)
self.delete_segments([bound_segment])
LOG.debug("Released dynamic segment %(seg)s allocated "
"by %(drv)s", {'seg': segment_id,
'drv': allocating_driver})

8
networking_arista/tests/unit/common/test_db_lib.py

@ -56,7 +56,9 @@ class DbLibTest(testlib_api.SqlTestCase):
{'id': 'n2',
'project_id': tenant_2_id}])
networks = db_lib.get_networks()
self.assertNotIn(tenant_2_id, [net.project_id for net in networks])
project_list = [net.project_id for net in networks]
self.assertIn(tenant_1_id, project_list)
self.assertIn(tenant_2_id, project_list)
def test_get_tenants_with_shared_network_ports(self):
tenant_1_id = 't1'
@ -193,7 +195,9 @@ class DbLibTest(testlib_api.SqlTestCase):
'segmentation_id': 200,
'is_dynamic': True}])
segments = db_lib.get_segments()
self.assertNotIn(net_id2, [seg.network_id for seg in segments])
netid_list = [seg.network_id for seg in segments]
self.assertIn(net_id1, netid_list)
self.assertIn(net_id2, netid_list)
def test_segment_is_dynamic(self):
static_segment_id = 's1'

161
networking_arista/tests/unit/ml2/ml2_test_base.py

@ -24,15 +24,19 @@ from pstats import Stats
from neutron_lib.api.definitions import portbindings
from neutron_lib import constants as n_const
import neutron_lib.context
from neutron_lib.plugins import constants as p_const
from neutron_lib.plugins import directory
from neutron_lib.services.trunk import constants as trunk_const
from oslo_config import cfg
from neutron.api.rpc.handlers import l3_rpc
from neutron.common import utils as common_utils
from neutron.plugins.ml2.drivers import type_vxlan # noqa
from neutron.tests.common import helpers
from neutron.tests.unit.plugins.ml2 import test_plugin
from networking_arista.common import constants as a_const
from networking_arista.common import db_lib
from networking_arista.ml2 import arista_sync
from networking_arista.tests.unit import utils
@ -173,7 +177,6 @@ class MechTestBase(test_plugin.Ml2PluginV2TestCase):
def create_network(self, net_dict):
net = self.plugin.create_network(self.context, net_dict)
n_ctxs = self.plugin.get_network_contexts(self.context, [net['id']])
self.plugin.create_subnet(self.context,
{'subnet':
{'tenant_id': net['tenant_id'],
@ -181,10 +184,13 @@ class MechTestBase(test_plugin.Ml2PluginV2TestCase):
'network_id': net['id'],
'ip_version': 4,
'cidr': '10.0.%d.0/24' % self.net_count,
'gateway_ip': '10.0.%d.1' % self.net_count,
'allocation_pools': None,
'enable_dhcp': False,
'dns_nameservers': None,
'host_routes': None}})
self.net_count += 1
n_ctxs = self.plugin.get_network_contexts(self.context, [net['id']])
return net, n_ctxs[net['id']]
def delete_network(self, net_id):
@ -391,3 +397,156 @@ class MechTestBase(test_plugin.Ml2PluginV2TestCase):
def assertPortBindingDeleted(self, pb_key):
self._assertResourceDeleted('port_binding', pb_key)
class L3HARouterTestFramework(MechTestBase):
L3Plugin = ('neutron.services.l3_router.l3_router_plugin.'
'L3RouterPlugin')
def get_additional_service_plugins(self):
p = super(L3HARouterTestFramework,
self).get_additional_service_plugins()
p.update({'flavors_plugin_name': 'neutron.services.flavors.'
'flavors_plugin.FlavorsPlugin'})
p.update({'l3_plugin_name': self.L3Plugin})
return p
def _register_l3_agent(self, host):
return helpers.register_l3_agent(host=host)
def setUp(self):
super(L3HARouterTestFramework, self).setUp()
self.l3_plugin = directory.get_plugin(p_const.L3)
self.l3_rpc_cb = l3_rpc.L3RpcCallback()
def create_router(self, ha=False):
router = self.l3_plugin.create_router(
self.context, {'router': {'name': 'router',
'admin_state_up': True,
'tenant_id': self._tenant_id,
'ha': ha,
'distributed': False}})
self.l3_plugin.schedule_router(self.context, router['id'])
for host in self.l3_agents:
self.sync_routers(router['id'], host['host'])
return router
def sync_routers(self, router_id, host):
"""Call to L3 Agent plugin sync_routers
Since only l3 agent plugin is enabled in the unit test, to get the
sync data for routers (which causes port update), we need to call the
l3 agent plugin sync_routers method directly.
This call is normally done by a process running periodically on
l3 agent server (neutron-l3-agent).
"""
self.l3_rpc_cb.sync_routers(self.context, host=host,
router_ids=[router_id])
def add_router_interface(self, router, interface_info):
return self.l3_plugin.add_router_interface(self.context, router['id'],
interface_info)
def remove_router_interface(self, router, interface_info):
return self.l3_plugin.remove_router_interface(self.context,
router['id'],
interface_info)
def delete_router(self, router_id):
return self.l3_plugin.delete_router(self.context, router_id)
def get_ha_network(self, router):
networks = db_lib.get_networks()
HA_network_name = n_const.HA_NETWORK_NAME % router['project_id']
net_id = [net.id for net in networks if net.name == HA_network_name]
return net_id[0]
def get_network_segments(self, network_id):
segments = db_lib.get_segments()
net_segs = [seg.id for seg in segments if seg.network_id == network_id]
return net_segs
def update_routers_states(self, router_id, l3_agent):
binding_l3_agent_host = l3_agent['host']
binding_l3_agent_id = l3_agent['id']
self.l3_plugin.update_routers_states(self.context,
{router_id: 'active'},
binding_l3_agent_host)
bindings = self.l3_plugin.get_ha_router_port_bindings(self.context,
[router_id])
binding = [binding for binding in bindings
if binding.l3_agent_id == binding_l3_agent_id][0]
return self.plugin.get_port(self.context, binding.port_id)
def get_legacy_router_port(self, port_id):
return self.plugin.get_port(self.context, port_id)
def assertL3HANetworkCreated(self, router, net_id):
endpoint = self._get_endpoint('network')
HA_network_name = n_const.HA_NETWORK_NAME % router['project_id']
def resource_created():
cvx_data = self.cvx.endpoint_data[endpoint]
return (cvx_data and net_id in cvx_data and
HA_network_name == cvx_data[net_id]['name'])
common_utils.wait_until_true(resource_created)
def assertLegacyRouterCreated(self, router, host):
endpoint = self._get_endpoint('router')
def resource_created():
cvx_data = list(self.cvx.endpoint_data[endpoint].values())
expected_data = {'hostId': host,
'tenantId': router['project_id'],
'id': router['id']}
return cvx_data and cvx_data[0] == expected_data
common_utils.wait_until_true(resource_created)
def assertL3HARouterCreated(self, router_id):
endpoint = self._get_endpoint('router')
def resource_created():
cvx_data = list(self.cvx.endpoint_data[endpoint].values())
expected_data = {'hostId': 'HA Router',
'tenantId': a_const.INTERNAL_TENANT_ID,
'id': router_id}
return cvx_data and cvx_data[0] == expected_data
common_utils.wait_until_true(resource_created)
def assertL3HAPortCreated(self, router, port_id):
endpoint = self._get_endpoint('router_port')
port_name = n_const.HA_PORT_NAME % router['project_id']
def resource_created():
cvx_data = self.cvx.endpoint_data[endpoint]
return (cvx_data and port_id in cvx_data and all(
[cvx_data[port_id]['tenantId'] == a_const.INTERNAL_TENANT_ID,
cvx_data[port_id]['portName'] == port_name,
cvx_data[port_id]['instanceId'] == router['id']]))
common_utils.wait_until_true(resource_created)
def assertSegmentsCreated(self, segments):
segment_endpoint = self._get_endpoint('segment')
def resource_created():
cvx_segments = self.cvx.endpoint_data[segment_endpoint].keys()
return cvx_segments and set(cvx_segments) == set(segments)
common_utils.wait_until_true(resource_created)
def assertSegmentsDeleted(self, segments):
segment_endpoint = self._get_endpoint('segment')
def resource_deleted():
cvx_data = self.cvx.endpoint_data[segment_endpoint]
cvx_segments = cvx_data.keys()
return all(seg not in cvx_segments for seg in segments)
common_utils.wait_until_true(resource_deleted)
def assertRouterPortsDeleted(self, ports):
endpoint = self._get_endpoint('router_port')
def resource_deleted():
cvx_data = self.cvx.endpoint_data[endpoint]
return all(port not in cvx_data for port in ports)
common_utils.wait_until_true(resource_deleted)

193
networking_arista/tests/unit/ml2/test_arista_resources.py

@ -26,6 +26,7 @@ from neutron_lib.plugins import directory
from oslo_config import cfg
from oslo_utils import importutils
from networking_arista.common import constants as a_const
import networking_arista.ml2.arista_resources as resources
from networking_arista.tests.unit import utils
@ -330,25 +331,43 @@ class AristaTenantTest(AristaResourcesTestBase):
self.ar = resources.Tenants(self.rpc)
def delete_helper(self, tenant_id):
if tenant_id == a_const.INTERNAL_TENANT_ID:
tenant_id = ''
utils.delete_ports_for_tenant(tenant_id)
utils.delete_segments_for_tenant(tenant_id)
utils.delete_networks_for_tenant(tenant_id)
def test_scenario_tenants(self):
expect_created = {'t1': {'id': 't1'},
't2': {'id': 't2'}}
't2': {'id': 't2'},
a_const.INTERNAL_TENANT_ID: {
'id': a_const.INTERNAL_TENANT_ID}}
self.run_scenario(expect_created)
def test_format_tenants(self):
# format_for_create test setup
neutron_data = [{'project_id': '1'},
{'project_id': '2'}]
{'project_id': '2'},
{'project_id': ''}]
expected_cvx_data = [{'1': {'id': '1'}},
{'2': {'id': '2'}}]
{'2': {'id': '2'}},
{a_const.INTERNAL_TENANT_ID: {
'id': a_const.INTERNAL_TENANT_ID}}]
test_cases = [(neutron_data[i], expected_cvx_data[i])
for i in range(len(neutron_data))]
self.verify_format_for_create(test_cases)
def test_format_networks_for_create_no_tenant(self):
net1_neutron = {'id': 'HA network',
'project_id': '',
'name': 'l3 ha',
'admin_state_up': True,
'rbac_entries': []}
tenant_expected = {a_const.INTERNAL_TENANT_ID:
{'id': a_const.INTERNAL_TENANT_ID}}
test_cases = [(net1_neutron, tenant_expected)]
self.verify_format_for_create(test_cases)
def test_format_tenants_for_delete(self):
# format_for_delete test setup
neutron_tenant_id = 't3'
@ -377,7 +396,12 @@ class AristaNetworkTest(AristaResourcesTestBase):
'n2': {'id': 'n2',
'tenantId': 't2',
'name': 'hpb',
'shared': False}}
'shared': False},
'HA network': {'id': 'HA network',
'tenantId':
a_const.INTERNAL_TENANT_ID,
'name': 'l3 ha',
'shared': False}}
self.run_scenario(expect_created)
def test_format_networks_for_create(self):
@ -467,6 +491,26 @@ class AristaSegmentTest(AristaResourcesTestBase):
'networkId': 'n2',
'segmentType': 'dynamic',
'segmentationId': 31,
'type': 'vlan'},
's5': {'id': 's5',
'networkId': 'HA network',
'segmentType': 'static',
'segmentationId': 33,
'type': 'vlan'},
's6': {'id': 's6',
'networkId': 'HA network',
'segmentType': 'static',
'segmentationId': 20010,
'type': 'vxlan'},
's7': {'id': 's7',
'networkId': 'HA network',
'segmentationId': 700,
'segmentType': 'dynamic',
'type': 'vlan'},
's8': {'id': 's8',
'networkId': 'HA network',
'segmentationId': 800,
'segmentType': 'dynamic',
'type': 'vlan'}}
self.run_scenario(expect_created)
@ -548,6 +592,8 @@ class AristaRouterTest(AristaInstancesTestBase):
def test_routers_scenario(self):
id_base = n_const.DEVICE_OWNER_DVR_INTERFACE + 'normal'
ha_id_base = n_const.DEVICE_OWNER_ROUTER_HA_INTF + 'normal'
legacy_id_base = n_const.DEVICE_OWNER_ROUTER_INTF + 'normal'
expect_created = {'%s1' % id_base:
{'tenantId': 't1',
'hostId': 'distributed',
@ -555,16 +601,38 @@ class AristaRouterTest(AristaInstancesTestBase):
'%s2' % id_base:
{'tenantId': 't2',
'hostId': 'distributed',
'id': '%s2' % id_base}}
'id': '%s2' % id_base},
'%s' % ha_id_base:
{'tenantId': a_const.INTERNAL_TENANT_ID,
'hostId': 'HA Router',
'id': '%s' % ha_id_base},
'%s1' % legacy_id_base:
{'tenantId': 't1',
'hostId': 'host1',
'id': '%s1' % legacy_id_base},
'%s2' % legacy_id_base:
{'tenantId': 't2',
'hostId': 'host2',
'id': '%s2' % legacy_id_base}}
self.run_scenario(expect_created)
def test_routers_managed_physnets_scenario(self):
cfg.CONF.set_override('managed_physnets', 'switch1', 'ml2_arista')
id_base = n_const.DEVICE_OWNER_DVR_INTERFACE + 'normal'
legacy_id_base = n_const.DEVICE_OWNER_ROUTER_INTF + 'normal'
ha_id_base = n_const.DEVICE_OWNER_ROUTER_HA_INTF + 'normal'
expect_created = {'%s2' % id_base:
{'tenantId': 't2',
'hostId': 'distributed',
'id': '%s2' % id_base}}
'id': '%s2' % id_base},
'%s2' % legacy_id_base:
{'id': '%s2' % legacy_id_base,
'tenantId': 't2',
'hostId': 'host2'},
'%s' % ha_id_base:
{'tenantId': a_const.INTERNAL_TENANT_ID,
'hostId': 'HA Router',
'id': '%s' % ha_id_base}}
self.run_scenario(expect_created)
@ -693,6 +761,8 @@ class AristaRouterPortTest(AristaPortTestBase):
def test_router_ports_scenario(self):
id_base = n_const.DEVICE_OWNER_DVR_INTERFACE + 'normal'
legacy_id_base = n_const.DEVICE_OWNER_ROUTER_INTF + 'normal'
ha_id_base = n_const.DEVICE_OWNER_ROUTER_HA_INTF + 'normal'
expect_created = {'p3': {'id': 'p3',
'portName': 'regular_port',
'tenantId': 't1',
@ -706,19 +776,82 @@ class AristaRouterPortTest(AristaPortTestBase):
'instanceType': 'router',
'instanceId': '%s2' % id_base,
'networkId': 'n2',
'vlanType': 'allowed'}}
'vlanType': 'allowed'},
'uuid-ha-1': {
'id': 'uuid-ha-1',
'portName': 'regular_port',
'tenantId': a_const.INTERNAL_TENANT_ID,
'instanceType': 'router',
'instanceId': '%s' % ha_id_base,
'networkId': 'HA network',
'vlanType': 'allowed'},
'uuid-ha-2': {
'id': 'uuid-ha-2',
'portName': 'regular_port',
'tenantId': a_const.INTERNAL_TENANT_ID,
'instanceType': 'router',
'instanceId': '%s' % ha_id_base,
'networkId': 'HA network',
'vlanType': 'allowed'},
'uuid-hpb-ha-1': {
'id': 'uuid-hpb-ha-1',
'portName': 'ha_router_hpb_port',
'tenantId': a_const.INTERNAL_TENANT_ID,
'instanceType': 'router',
'instanceId': '%s' % ha_id_base,
'networkId': 'HA network',
'vlanType': 'allowed'},
'uuid-hpb-ha-2': {
'id': 'uuid-hpb-ha-2',
'portName': u'ha_router_hpb_port',
'tenantId': a_const.INTERNAL_TENANT_ID,
'instanceType': 'router',
'networkId': u'HA network',
'instanceId': '%s' % ha_id_base,
'vlanType': 'allowed'},
'p15': {'id': 'p15',
'portName': u'regular_port',
'networkId': 'n1',
'instanceId': '%s1' % legacy_id_base,
'instanceType': 'router',
'tenantId': 't1',
'vlanType': 'allowed'},
'p16': {'id': 'p16',
'portName': 'hpb_port',
'tenantId': 't2',
'instanceId': '%s2' % legacy_id_base,
'instanceType': 'router',
'networkId': 'n2',
'vlanType': 'allowed'}}
self.run_scenario(expect_created)
def test_router_ports_managed_physnets_scenario(self):
cfg.CONF.set_override('managed_physnets', 'switch1', 'ml2_arista')
id_base = n_const.DEVICE_OWNER_DVR_INTERFACE + 'normal'
legacy_id_base = n_const.DEVICE_OWNER_ROUTER_INTF + 'normal'
ha_id_base = n_const.DEVICE_OWNER_ROUTER_HA_INTF + 'normal'
expect_created = {'p4': {'id': 'p4',
'portName': 'hpb_port',
'tenantId': 't2',
'instanceType': 'router',
'instanceId': '%s2' % id_base,
'networkId': 'n2',
'vlanType': 'allowed'}}
'vlanType': 'allowed'},
'uuid-hpb-ha-1': {
'id': 'uuid-hpb-ha-1',
'portName': 'ha_router_hpb_port',
'tenantId': a_const.INTERNAL_TENANT_ID,
'instanceType': 'router',
'instanceId': '%s' % ha_id_base,
'networkId': 'HA network',
'vlanType': 'allowed'},
'p16': {'id': 'p16',
'portName': 'hpb_port',
'tenantId': 't2',
'instanceType': 'router',
'instanceId': '%s2' % legacy_id_base,
'networkId': 'n2',
'vlanType': 'allowed'}}
self.run_scenario(expect_created)
@ -1044,7 +1177,36 @@ class AristaPortBindingTest(AristaResourcesTestBase):
'switchBinding': [{'host': 'host2',
'interface': 'Ethernet1/2',
'segment': [{'id': 's2'}, {'id': 's3'}],
'switch': '55:44:33:22:11:00'}]}}
'switch': '55:44:33:22:11:00'}]},
# HA network port
('uuid-ha-1', ('host1')):
{'portId': 'uuid-ha-1',
'switchBinding': [],
'hostBinding': [{'segment': [{'id': 's5'}], 'host': 'host1'}]},
('uuid-ha-2', ('host2')):
{'portId': 'uuid-ha-2',
'switchBinding': [],
'hostBinding': [{'segment': [{'id': 's5'}], 'host': 'host2'}]},
('uuid-hpb-ha-1', ('host1')):
{'portId': 'uuid-hpb-ha-1',
'switchBinding': [],
'hostBinding': [{'segment': [{'id': 's6'}, {'id': 's7'}],
'host': 'host1'}]},
('uuid-hpb-ha-2', ('host2')):
{'portId': 'uuid-hpb-ha-2',
'switchBinding': [],
'hostBinding': [{'segment': [{'id': 's6'}, {'id': 's8'}],
'host': 'host2'}]},
# Legacy router port
('p15', ('host1')):
{'portId': 'p15',
'switchBinding': [],
'hostBinding': [{'segment': [{'id': 's1'}], 'host': 'host1'}]},
('p16', ('host2')):
{'portId': 'p16',
'switchBinding': [],
'hostBinding': [{'segment': [{'id': 's2'}, {'id': 's3'}],
'host': 'host2'}]}}
self.run_scenario(expect_created)
def test_port_binding_managed_physnets_scenario(self):
@ -1128,5 +1290,16 @@ class AristaPortBindingTest(AristaResourcesTestBase):
'switchBinding': [{'host': 'host2',
'interface': 'Ethernet1/2',
'segment': [{'id': 's2'}, {'id': 's3'}],
'switch': '55:44:33:22:11:00'}]}}
'switch': '55:44:33:22:11:00'}]},
# Legacy router
('p16', ('host2')):
{'portId': 'p16',
'switchBinding': [],
'hostBinding': [{'segment': [{'id': 's2'},
{'id': 's3'}], 'host': 'host2'}]},
('uuid-hpb-ha-1', ('host1')):
{'portId': 'uuid-hpb-ha-1',
'switchBinding': [],
'hostBinding': [{'segment': [{'id': 's6'}, {'id': 's7'}],
'host': 'host1'}]}}
self.run_scenario(expect_created)

166
networking_arista/tests/unit/ml2/test_mechanism_arista.py

@ -18,8 +18,11 @@ import mock
from neutron_lib import constants as n_const
from oslo_config import cfg
from networking_arista.common import constants as a_const
from networking_arista.tests.unit.ml2 import ml2_test_base
from neutron_lib.api.definitions import portbindings
class BasicMechDriverTestCase(ml2_test_base.MechTestBase):
@ -1115,3 +1118,166 @@ class ManagedFabricManagedFabricHpbTestCase(ml2_test_base.MechTestBase):
self.assertVmDeleted(device_id)
self.assertVmPortDeleted(port['id'])
self.assertPortBindingDeleted((port['id'], port_host))
class BasicL3HARouterTests(object):
def test_create_delete_router(self):
router = self.create_router(ha=True)
ha_router_ports = []
for l3_agent in self.l3_agents:
port = self.update_routers_states(router['id'], l3_agent)
ha_router_ports.append(port)
ha_network_id = self.get_ha_network(router)
ha_segments = self.get_network_segments(ha_network_id)
self.assertTenantCreated(a_const.INTERNAL_TENANT_ID)
self.assertL3HANetworkCreated(router, ha_network_id)
self.assertRouterCreated(router['id'])
self.assertL3HARouterCreated(router['id'])
for port in ha_router_ports:
self.assertL3HAPortCreated(router, port['id'])
self.assertPortBindingCreated((port['id'],
port[portbindings.HOST_ID]))
self.assertSegmentsCreated(ha_segments)
# Delete the router
self.delete_router(router['id'])
self.assertRouterPortsDeleted([p['id'] for p in ha_router_ports])
self.assertRouterDeleted(router['id'])
self.assertSegmentsDeleted(ha_segments)
self.assertNetworkDeleted(ha_network_id)
self.assertTenantDeleted(a_const.INTERNAL_TENANT_ID)
for port in ha_router_ports:
self.assertPortBindingDeleted((port['id'],
port[portbindings.HOST_ID]))
class BasicRouterTests(object):
def test_create_delete_router(self):
router = self.create_router()
net_list = []
port_list = []
segment_list = []
for net in self.net_dict:
network, net_ctx = self.create_network(net)
net_list.append((network, net_ctx))
self.assertTenantCreated(router['project_id'])
for net, net_ctx in net_list:
interface_info = {'subnet_id': net_ctx.current['subnets'][0]}
intf = self.add_router_interface(router, interface_info)
self.sync_routers(router['id'], self.l3_agent1['host'])
port = self.get_legacy_router_port(intf['port_id'])
self.assertNotEqual(len(port), 0)
port_list.append(port)
self.assertLegacyRouterCreated(router, self.l3_agent1['host'])
for network, _ in net_list:
self.assertNetworkCreated(network['id'])
segment_list.extend(self.get_network_segments(network['id']))
self.assertEqual(len(segment_list), self.total_segments)
self.assertSegmentsCreated(segment_list)
for port in port_list:
self.assertRouterPortCreated(port['id'])
self.assertPortBindingCreated((port['id'],
port[portbindings.HOST_ID]))
# Delete the router interfaces and router
# Remove one of router's interface
network, net_ctx = net_list[0]
interface_info = {'subnet_id': net_ctx.current['subnets'][0]}
intf = self.remove_router_interface(router, interface_info)
self.assertRouterCreated(router['id'])
self.assertRouterPortDeleted(intf['port_id'])
self.assertPortBindingDeleted((intf['port_id'],
port[portbindings.HOST_ID]))
# Remove second router interface
network, net_ctx = net_list[1]
interface_info = {'subnet_id': net_ctx.current['subnets'][0]}
intf = self.remove_router_interface(router, interface_info)
self.assertRouterDeleted(router['id'])
self.assertRouterPortDeleted(intf['port_id'])
self.assertPortBindingDeleted((intf['port_id'],
port[portbindings.HOST_ID]))
for network, _ in net_list:
self.delete_network(network['id'])
self.assertNetworkDeleted(network['id'])
self.assertSegmentsDeleted(segment_list)
class BasicL3HARouterTestCases(ml2_test_base.L3HARouterTestFramework,
BasicL3HARouterTests):
def setUp(self):
cfg.CONF.set_override('tenant_network_types', 'vlan', 'ml2')
super(BasicL3HARouterTestCases, self).setUp()
cfg.CONF.set_override('max_l3_agents_per_router', 2)
self.l3_agent1 = self._register_l3_agent(host=self.host1)
self.l3_agent2 = self._register_l3_agent(host=self.host2)
self.l3_agents = [self.l3_agent1, self.l3_agent2]
class BasicHpbL3HARouterTestCases(ml2_test_base.L3HARouterTestFramework,
BasicL3HARouterTests):
def setUp(self):
cfg.CONF.set_override('manage_fabric', True, 'ml2_arista')
cfg.CONF.set_override('tenant_network_types', 'vxlan', 'ml2')
super(BasicHpbL3HARouterTestCases, self).setUp()
cfg.CONF.set_override('l3_ha', True)
cfg.CONF.set_override('max_l3_agents_per_router', 3)
self.l3_agent1 = self._register_l3_agent(host=self.host1)
self.l3_agent2 = self._register_l3_agent(host=self.host2)
self.l3_agent3 = self._register_l3_agent(host=self.host3)
self.l3_agents = [self.l3_agent1, self.l3_agent2, self.l3_agent3]
def get_host_physnet(context):
if context.host == 'host3':
return self.physnet2
if context.host == 'host1':
return self.physnet
if context.host == 'host2':
return self.physnet
ghp = mock.patch.object(self.drv.eapi, 'get_host_physnet').start()
ghp.side_effect = get_host_physnet
class BasicRouterTestCases(ml2_test_base.L3HARouterTestFramework,
BasicRouterTests):
def setUp(self):
cfg.CONF.set_override('tenant_network_types', 'vlan', 'ml2')
super(BasicRouterTestCases, self).setUp()
self.l3_agent1 = self._register_l3_agent(host=self.host1)
self.net_dict = [
{'network': {'name': 'net-%d' % r,
'tenant_id': self._tenant_id,
'admin_state_up': True,
'shared': False,
'provider:physical_network': self.physnet,
'provider:network_type': 'vlan'}}
for r in range(1, 3)]
self.total_segments = 2
self.l3_agents = [self.l3_agent1]
class BasicHpbRouterTestCases(ml2_test_base.L3HARouterTestFramework,
BasicRouterTests):
def setUp(self):
cfg.CONF.set_override('manage_fabric', True, 'ml2_arista')
cfg.CONF.set_override('tenant_network_types', 'vxlan', 'ml2')
super(BasicHpbRouterTestCases, self).setUp()
self.l3_agent1 = self._register_l3_agent(host=self.host1)
def get_host_physnet(context):
return self.physnet
ghp = mock.patch.object(self.drv.eapi, 'get_host_physnet').start()
ghp.side_effect = get_host_physnet
self.net_dict = [{'network': {'name': 'hpb_net-%d' % r,
'tenant_id': self._tenant_id,
'admin_state_up': True,
'shared': False}}
for r in range(1, 3)]
self.total_segments = 4
self.l3_agents = [self.l3_agent1]

94
networking_arista/tests/unit/utils.py

@ -478,6 +478,12 @@ def setup_scenario():
'admin_state_up': True,
'rbac_entries': []}
ha_network = {'id': 'HA network',
'project_id': '',
'name': 'l3 ha',
'admin_state_up': True,
'rbac_entries': []}
# Create segments
flat_segment = {'id': 'sError',
'network_id': 'n1',
@ -507,6 +513,31 @@ def setup_scenario():
'segmentation_id': 31,
'network_type': 'vlan',
'physical_network': 'switch2'}
ha_segment = {'id': 's5',
'network_id': 'HA network',
'is_dynamic': False,
'segmentation_id': 33,
'network_type': 'vlan',
'physical_network': 'default'}
ha_fabric_segment = {'id': 's6',
'network_id': 'HA network',
'is_dynamic': False,
'segmentation_id': 20010,
'network_type': 'vxlan',
'physical_network': None}
ha_dynamic_segment = {'1': {'id': 's7',
'network_id': 'HA network',
'is_dynamic': True,
'segmentation_id': 700,
'network_type': 'vlan',
'physical_network': 'switch1'},
'2': {'id': 's8',
'network_id': 'HA network',
'is_dynamic': True,
'segmentation_id': 800,
'network_type': 'vlan',
'physical_network': 'switch2'}}
# Create ports
port_ctr = 0
ports = list()
@ -517,7 +548,9 @@ def setup_scenario():
(n_const.DEVICE_OWNER_DVR_INTERFACE, 'normal'),
(n_const.DEVICE_OWNER_COMPUTE_PREFIX, 'normal'),
(n_const.DEVICE_OWNER_COMPUTE_PREFIX, 'baremetal'),
(n_const.DEVICE_OWNER_BAREMETAL_PREFIX, 'baremetal')]
(n_const.DEVICE_OWNER_BAREMETAL_PREFIX, 'baremetal'),
(n_const.DEVICE_OWNER_ROUTER_HA_INTF, 'normal'),
(n_const.DEVICE_OWNER_ROUTER_INTF, 'normal')]
for device_owner, vnic_type in instance_types:
vif_type = 'ovs'
regular_host = 'host1'
@ -609,7 +642,8 @@ def setup_scenario():
'mac_address': '00:00:00:00:00:%02x' % port_ctr,
'name': 'hpb_port',
'binding_levels': hpb_binding_levels}
ports.extend([regular_port, hpb_port])
if device_owner != n_const.DEVICE_OWNER_ROUTER_HA_INTF:
ports.extend([regular_port, hpb_port])
if device_owner == n_const.DEVICE_OWNER_COMPUTE_PREFIX:
port_ctr += 1
trunk_subport = {'admin_state_up': True,
@ -636,10 +670,60 @@ def setup_scenario():
trunk_ctr += 1
trunks.append(trunk)
subports.append(subport)
create_networks([regular_network, hpb_network])
if device_owner == n_const.DEVICE_OWNER_ROUTER_HA_INTF:
vif_type = 'ovs'
ha_router_ports = [{'admin_state_up': True,
'status': 'ACTIVE',
'device_id': '%s%s' % (device_owner,
vnic_type),
'device_owner': device_owner,
'binding': {'host': 'host%d' % p,
'vif_type': vif_type,
'vnic_type': vnic_type,
'profile': binding_profile},
'tenant_id': '',
'id': 'uuid-ha-%d' % p,
'network_id': ha_network['id'],
'mac_address': '00:00:00:00:00:%02x' % p,
'name': 'regular_port',
'binding_levels': [
{'host': 'host%d' % p,
'segment_id': ha_segment['id'],
'level': 0,
'driver': 'openvswitch'}]}
for p in range(1, 3)]
ports.extend(ha_router_ports)
ha_router_hpb_ports = [
{'admin_state_up': True,
'status': 'ACTIVE',
'device_id': '%s%s' % (device_owner,
vnic_type),
'device_owner': device_owner,
'binding': {'host': 'host%d' % p,
'vif_type': vif_type,
'vnic_type': vnic_type,
'profile': binding_profile},
'tenant_id': '',
'id': 'uuid-hpb-ha-%d' % p,
'network_id': ha_network['id'],
'mac_address': '00:00:00:00:01:%02x' % p,
'name': 'ha_router_hpb_port',
'binding_levels': [
{'host': 'host%d' % p,
'segment_id': ha_fabric_segment['id'],
'level': 0,
'driver': 'arista'},
{'host': 'host%d' % p,
'segment_id': ha_dynamic_segment['%d' % p]['id'],
'level': 1,
'driver': 'openvswitch'}]} for p in range(1, 3)]
ports.extend(ha_router_hpb_ports)
create_networks([regular_network, hpb_network, ha_network])
create_segments([regular_segment, fabric_segment, flat_segment,
dynamic_segment1, dynamic_segment2])
dynamic_segment1, dynamic_segment2, ha_segment,
ha_fabric_segment, ha_dynamic_segment['1'],
ha_dynamic_segment['2']])
create_ports(ports)
create_trunks(trunks)
create_subports(subports)

Loading…
Cancel
Save