Merge "Improves Arista's ML2 driver's sync performance"
This commit is contained in:
commit
7d1cbdbeb1
@ -92,13 +92,8 @@ def remember_tenant(tenant_id):
|
||||
"""
|
||||
session = db.get_session()
|
||||
with session.begin():
|
||||
tenant = (session.query(AristaProvisionedTenants).
|
||||
filter_by(tenant_id=tenant_id).first())
|
||||
|
||||
if not tenant:
|
||||
tenant = AristaProvisionedTenants(
|
||||
tenant_id=tenant_id)
|
||||
session.add(tenant)
|
||||
tenant = AristaProvisionedTenants(tenant_id=tenant_id)
|
||||
session.add(tenant)
|
||||
|
||||
|
||||
def forget_tenant(tenant_id):
|
||||
@ -138,19 +133,13 @@ def remember_vm(vm_id, host_id, port_id, network_id, tenant_id):
|
||||
"""
|
||||
session = db.get_session()
|
||||
with session.begin():
|
||||
vm = (session.query(AristaProvisionedVms).
|
||||
filter_by(vm_id=vm_id, host_id=host_id,
|
||||
port_id=port_id, tenant_id=tenant_id,
|
||||
network_id=network_id).first())
|
||||
|
||||
if not vm:
|
||||
vm = AristaProvisionedVms(
|
||||
vm_id=vm_id,
|
||||
host_id=host_id,
|
||||
port_id=port_id,
|
||||
network_id=network_id,
|
||||
tenant_id=tenant_id)
|
||||
session.add(vm)
|
||||
vm = AristaProvisionedVms(
|
||||
vm_id=vm_id,
|
||||
host_id=host_id,
|
||||
port_id=port_id,
|
||||
network_id=network_id,
|
||||
tenant_id=tenant_id)
|
||||
session.add(vm)
|
||||
|
||||
|
||||
def forget_vm(vm_id, host_id, port_id, network_id, tenant_id):
|
||||
@ -179,16 +168,11 @@ def remember_network(tenant_id, network_id, segmentation_id):
|
||||
"""
|
||||
session = db.get_session()
|
||||
with session.begin():
|
||||
net = (session.query(AristaProvisionedNets).
|
||||
filter_by(tenant_id=tenant_id,
|
||||
network_id=network_id).first())
|
||||
|
||||
if not net:
|
||||
net = AristaProvisionedNets(
|
||||
tenant_id=tenant_id,
|
||||
network_id=network_id,
|
||||
segmentation_id=segmentation_id)
|
||||
session.add(net)
|
||||
net = AristaProvisionedNets(
|
||||
tenant_id=tenant_id,
|
||||
network_id=network_id,
|
||||
segmentation_id=segmentation_id)
|
||||
session.add(net)
|
||||
|
||||
|
||||
def forget_network(tenant_id, network_id):
|
||||
@ -411,12 +395,6 @@ class NeutronNets(db_base_plugin_v2.NeutronDbPluginV2):
|
||||
return super(NeutronNets,
|
||||
self).get_ports(self.admin_ctx, filters=filters) or []
|
||||
|
||||
def get_all_ports_for_vm(self, tenant_id, vm_id):
|
||||
filters = {'tenant_id': [tenant_id],
|
||||
'device_id': [vm_id]}
|
||||
return super(NeutronNets,
|
||||
self).get_ports(self.admin_ctx, filters=filters) or []
|
||||
|
||||
def _get_network(self, tenant_id, network_id):
|
||||
filters = {'tenant_id': [tenant_id],
|
||||
'id': [network_id]}
|
||||
|
@ -43,6 +43,36 @@ class AristaRPCWrapper(object):
|
||||
self._server = jsonrpclib.Server(self._eapi_host_url())
|
||||
self.keystone_conf = cfg.CONF.keystone_authtoken
|
||||
self.region = cfg.CONF.ml2_arista.region_name
|
||||
self._region_updated_time = None
|
||||
# The cli_commands dict stores the mapping between the CLI command key
|
||||
# and the actual CLI command.
|
||||
self.cli_commands = {}
|
||||
self.initialize_cli_commands()
|
||||
|
||||
def _get_exit_mode_cmds(self, modes):
|
||||
"""Returns a list of 'exit' commands for the modes.
|
||||
|
||||
:param modes: a list of CLI modes to exit out of.
|
||||
"""
|
||||
return ['exit'] * len(modes)
|
||||
|
||||
def initialize_cli_commands(self):
|
||||
self.cli_commands['timestamp'] = []
|
||||
|
||||
def check_cli_commands(self):
|
||||
"""Checks whether the CLI commands are vaild.
|
||||
|
||||
This method tries to execute the commands on EOS and if it succeedes
|
||||
the command is stored.
|
||||
"""
|
||||
cmd = ['show openstack config region %s timestamp' % self.region]
|
||||
try:
|
||||
self._run_eos_cmds(cmd)
|
||||
self.cli_commands['timestamp'] = cmd
|
||||
except arista_exc.AristaRpcError:
|
||||
self.cli_commands['timestamp'] = []
|
||||
msg = _("'timestamp' command '%s' is not available on EOS") % cmd
|
||||
LOG.warn(msg)
|
||||
|
||||
def _keystone_url(self):
|
||||
keystone_auth_url = ('%s://%s:%s/v2.0/' %
|
||||
@ -58,7 +88,7 @@ class AristaRPCWrapper(object):
|
||||
and VMs allocated per tenant
|
||||
"""
|
||||
cmds = ['show openstack config region %s' % self.region]
|
||||
command_output = self._run_openstack_cmds(cmds)
|
||||
command_output = self._run_eos_cmds(cmds)
|
||||
tenants = command_output[0]['tenants']
|
||||
|
||||
return tenants
|
||||
@ -168,25 +198,35 @@ class AristaRPCWrapper(object):
|
||||
'exit']
|
||||
self._run_openstack_cmds(cmds)
|
||||
|
||||
def create_network(self, tenant_id, network_id, network_name, seg_id):
|
||||
def create_network(self, tenant_id, network):
|
||||
"""Creates a single network on Arista hardware
|
||||
|
||||
:param tenant_id: globally unique neutron tenant identifier
|
||||
:param network: dict containing network_id, network_name and
|
||||
segmentation_id
|
||||
"""
|
||||
self.create_network_bulk(tenant_id, [network])
|
||||
|
||||
def create_network_bulk(self, tenant_id, network_list):
|
||||
"""Creates a network on Arista Hardware
|
||||
|
||||
:param tenant_id: globally unique neutron tenant identifier
|
||||
:param network_id: globally unique neutron network identifier
|
||||
:param network_name: Network name - for display purposes
|
||||
:param seg_id: Segment ID of the network
|
||||
:param network_list: list of dicts containing network_id, network_name
|
||||
and segmentation_id
|
||||
"""
|
||||
cmds = ['tenant %s' % tenant_id]
|
||||
if network_name:
|
||||
cmds.append('network id %s name "%s"' %
|
||||
(network_id, network_name))
|
||||
else:
|
||||
cmds.append('network id %s' % network_id)
|
||||
cmds.append('segment 1 type vlan id %d' % seg_id)
|
||||
cmds.append('exit')
|
||||
cmds.append('exit')
|
||||
cmds.append('exit')
|
||||
|
||||
# Create a reference to function to avoid name lookups in the loop
|
||||
append_cmd = cmds.append
|
||||
for network in network_list:
|
||||
try:
|
||||
append_cmd('network id %s name "%s"' %
|
||||
(network['network_id'], network['network_name']))
|
||||
except KeyError:
|
||||
append_cmd('network id %s' % network['network_id'])
|
||||
# Enter segment mode without exiting out of network mode
|
||||
append_cmd('segment 1 type vlan id %d' %
|
||||
network['segmentation_id'])
|
||||
cmds.extend(self._get_exit_mode_cmds(['segment', 'network', 'tenant']))
|
||||
self._run_openstack_cmds(cmds)
|
||||
|
||||
def create_network_segments(self, tenant_id, network_id,
|
||||
@ -222,10 +262,19 @@ class AristaRPCWrapper(object):
|
||||
:param tenant_id: globally unique neutron tenant identifier
|
||||
:param network_id: globally unique neutron network identifier
|
||||
"""
|
||||
cmds = ['tenant %s' % tenant_id,
|
||||
'no network id %s' % network_id,
|
||||
'exit',
|
||||
'exit']
|
||||
self.delete_network_bulk(tenant_id, [network_id])
|
||||
|
||||
def delete_network_bulk(self, tenant_id, network_id_list):
|
||||
"""Deletes the network ids specified for a tenant
|
||||
|
||||
:param tenant_id: globally unique neutron tenant identifier
|
||||
:param network_id_list: list of globally unique neutron network
|
||||
identifiers
|
||||
"""
|
||||
cmds = ['tenant %s' % tenant_id]
|
||||
for network_id in network_id_list:
|
||||
cmds.append('no network id %s' % network_id)
|
||||
cmds.extend(self._get_exit_mode_cmds(['network', 'tenant']))
|
||||
self._run_openstack_cmds(cmds)
|
||||
|
||||
def delete_vm(self, tenant_id, vm_id):
|
||||
@ -234,10 +283,58 @@ class AristaRPCWrapper(object):
|
||||
:param tenant_id : globally unique neutron tenant identifier
|
||||
:param vm_id : id of a VM that needs to be deleted.
|
||||
"""
|
||||
cmds = ['tenant %s' % tenant_id,
|
||||
'no vm id %s' % vm_id,
|
||||
'exit',
|
||||
'exit']
|
||||
self.delete_vm_bulk(tenant_id, [vm_id])
|
||||
|
||||
def delete_vm_bulk(self, tenant_id, vm_id_list):
|
||||
"""Deletes VMs from EOS for a given tenant
|
||||
|
||||
:param tenant_id : globally unique neutron tenant identifier
|
||||
:param vm_id_list : ids of VMs that needs to be deleted.
|
||||
"""
|
||||
cmds = ['tenant %s' % tenant_id]
|
||||
for vm_id in vm_id_list:
|
||||
cmds.append('no vm id %s' % vm_id)
|
||||
cmds.extend(self._get_exit_mode_cmds(['vm', 'tenant']))
|
||||
self._run_openstack_cmds(cmds)
|
||||
|
||||
def create_vm_port_bulk(self, tenant_id, vm_port_list, vms):
|
||||
"""Sends a bulk request to create ports.
|
||||
|
||||
:param tenant_id: globaly unique neutron tenant identifier
|
||||
:param vm_port_list: list of ports that need to be created.
|
||||
:param vms: list of vms to which the ports will be attached to.
|
||||
"""
|
||||
cmds = ['tenant %s' % tenant_id]
|
||||
# Create a reference to function to avoid name lookups in the loop
|
||||
append_cmd = cmds.append
|
||||
for port in vm_port_list:
|
||||
try:
|
||||
vm = vms[port['device_id']]
|
||||
except KeyError:
|
||||
msg = _("VM id %(vmid)s not found for port %(portid)s") % {
|
||||
'vmid': port['device_id'],
|
||||
'portid': port['id']}
|
||||
LOG.warn(msg)
|
||||
continue
|
||||
|
||||
port_name = '' if 'name' not in port else 'name "%s"' % (
|
||||
port['name']
|
||||
)
|
||||
|
||||
if port['device_owner'] == n_const.DEVICE_OWNER_DHCP:
|
||||
append_cmd('network id %s' % port['network_id'])
|
||||
append_cmd('dhcp id %s hostid %s port-id %s %s' %
|
||||
(vm['vmId'], vm['host'], port['id'], port_name))
|
||||
elif port['device_owner'].startswith('compute'):
|
||||
append_cmd('vm id %s hostid %s' % (vm['vmId'], vm['host']))
|
||||
append_cmd('port id %s %s network-id %s' %
|
||||
(port['id'], port_name, port['network_id']))
|
||||
else:
|
||||
msg = _("Unknown device owner: %s") % port['device_owner']
|
||||
LOG.warn(msg)
|
||||
continue
|
||||
|
||||
append_cmd('exit')
|
||||
self._run_openstack_cmds(cmds)
|
||||
|
||||
def delete_tenant(self, tenant_id):
|
||||
@ -245,68 +342,149 @@ class AristaRPCWrapper(object):
|
||||
|
||||
:param tenant_id: globally unique neutron tenant identifier
|
||||
"""
|
||||
cmds = ['no tenant %s' % tenant_id, 'exit']
|
||||
self.delete_tenant_bulk([tenant_id])
|
||||
|
||||
def delete_tenant_bulk(self, tenant_list):
|
||||
"""Sends a bulk request to delete the tenants.
|
||||
|
||||
:param tenant_list: list of globaly unique neutron tenant ids which
|
||||
need to be deleted.
|
||||
"""
|
||||
|
||||
cmds = []
|
||||
for tenant in tenant_list:
|
||||
cmds.append('no tenant %s' % tenant)
|
||||
cmds.append('exit')
|
||||
self._run_openstack_cmds(cmds)
|
||||
|
||||
def delete_this_region(self):
|
||||
"""Deletes this entire region from EOS.
|
||||
"""Deleted the region data from EOS."""
|
||||
cmds = ['enable',
|
||||
'configure',
|
||||
'management openstack',
|
||||
'no region %s' % self.region,
|
||||
'exit',
|
||||
'exit']
|
||||
self._run_eos_cmds(cmds)
|
||||
|
||||
This is equivalent of unregistering this Neurtron stack from EOS
|
||||
All networks for all tenants are removed.
|
||||
"""
|
||||
cmds = []
|
||||
self._run_openstack_cmds(cmds, deleteRegion=True)
|
||||
|
||||
def _register_with_eos(self):
|
||||
def register_with_eos(self):
|
||||
"""This is the registration request with EOS.
|
||||
|
||||
This the initial handshake between Neutron and EOS.
|
||||
critical end-point information is registered with EOS.
|
||||
"""
|
||||
cmds = ['auth url %s user %s password %s' %
|
||||
cmds = ['auth url %s user "%s" password "%s"' %
|
||||
(self._keystone_url(),
|
||||
self.keystone_conf.admin_user,
|
||||
self.keystone_conf.admin_password)]
|
||||
|
||||
self._run_openstack_cmds(cmds)
|
||||
log_cmds = ['auth url %s user %s password ******' %
|
||||
(self._keystone_url(),
|
||||
self.keystone_conf.admin_user)]
|
||||
|
||||
def _run_openstack_cmds(self, commands, deleteRegion=None):
|
||||
self._run_openstack_cmds(cmds, commands_to_log=log_cmds)
|
||||
|
||||
def clear_region_updated_time(self):
|
||||
"""Clear the region updated time which forces a resync."""
|
||||
|
||||
self._region_updated_time = None
|
||||
|
||||
def region_in_sync(self):
|
||||
"""Check whether EOS is in sync with Neutron."""
|
||||
|
||||
eos_region_updated_times = self.get_region_updated_time()
|
||||
return (self._region_updated_time and
|
||||
(self._region_updated_time['regionTimestamp'] ==
|
||||
eos_region_updated_times['regionTimestamp']))
|
||||
|
||||
def get_region_updated_time(self):
|
||||
"""Return the timestamp of the last update.
|
||||
|
||||
This method returns the time at which any entities in the region
|
||||
were updated.
|
||||
"""
|
||||
timestamp_cmd = self.cli_commands['timestamp']
|
||||
if timestamp_cmd:
|
||||
return self._run_eos_cmds(commands=timestamp_cmd)[0]
|
||||
return None
|
||||
|
||||
def _run_eos_cmds(self, commands, commands_to_log=None):
|
||||
"""Execute/sends a CAPI (Command API) command to EOS.
|
||||
|
||||
In this method, list of commands is appended with prefix and
|
||||
postfix commands - to make is understandble by EOS.
|
||||
|
||||
:param commands : List of command to be executed on EOS.
|
||||
:param deleteRegion : True/False - to delte entire region from EOS
|
||||
:param commands_to_log : This should be set to the command that is
|
||||
logged. If it is None, then the commands
|
||||
param is logged.
|
||||
"""
|
||||
command_start = ['enable', 'configure', 'management openstack']
|
||||
if deleteRegion:
|
||||
command_start.append('no region %s' % self.region)
|
||||
else:
|
||||
command_start.append('region %s' % self.region)
|
||||
command_end = ['exit', 'exit']
|
||||
full_command = command_start + commands + command_end
|
||||
|
||||
LOG.info(_('Executing command on Arista EOS: %s'), full_command)
|
||||
log_cmd = commands
|
||||
if commands_to_log:
|
||||
log_cmd = commands_to_log
|
||||
|
||||
LOG.info(_('Executing command on Arista EOS: %s'), log_cmd)
|
||||
|
||||
try:
|
||||
# this returns array of return values for every command in
|
||||
# full_command list
|
||||
ret = self._server.runCmds(version=1, cmds=full_command)
|
||||
|
||||
# Remove return values for 'configure terminal',
|
||||
# 'management openstack' and 'exit' commands
|
||||
ret = ret[len(command_start):-len(command_end)]
|
||||
ret = self._server.runCmds(version=1, cmds=commands)
|
||||
except Exception as error:
|
||||
host = cfg.CONF.ml2_arista.eapi_host
|
||||
msg = (_('Error %(err)s while trying to execute '
|
||||
'commands %(cmd)s on EOS %(host)s') %
|
||||
{'err': error, 'cmd': full_command, 'host': host})
|
||||
{'err': error, 'cmd': commands_to_log, 'host': host})
|
||||
LOG.exception(msg)
|
||||
raise arista_exc.AristaRpcError(msg=msg)
|
||||
|
||||
return ret
|
||||
|
||||
def _build_command(self, cmds):
|
||||
"""Build full EOS's openstack CLI command.
|
||||
|
||||
Helper method to add commands to enter and exit from openstack
|
||||
CLI modes.
|
||||
|
||||
:param cmds: The openstack CLI commands that need to be executed
|
||||
in the openstack config mode.
|
||||
"""
|
||||
|
||||
full_command = [
|
||||
'enable',
|
||||
'configure',
|
||||
'management openstack',
|
||||
'region %s' % self.region,
|
||||
]
|
||||
full_command.extend(cmds)
|
||||
full_command.extend(self._get_exit_mode_cmds(['region',
|
||||
'openstack']))
|
||||
full_command.extend(self.cli_commands['timestamp'])
|
||||
return full_command
|
||||
|
||||
def _run_openstack_cmds(self, commands, commands_to_log=None):
|
||||
"""Execute/sends a CAPI (Command API) command to EOS.
|
||||
|
||||
In this method, list of commands is appended with prefix and
|
||||
postfix commands - to make is understandble by EOS.
|
||||
|
||||
:param commands : List of command to be executed on EOS.
|
||||
:param commands_to_logs : This should be set to the command that is
|
||||
logged. If it is None, then the commands
|
||||
param is logged.
|
||||
"""
|
||||
|
||||
full_command = self._build_command(commands)
|
||||
if commands_to_log:
|
||||
full_log_command = self._build_command(commands_to_log)
|
||||
else:
|
||||
full_log_command = None
|
||||
ret = self._run_eos_cmds(full_command, full_log_command)
|
||||
# Remove return values for 'configure terminal',
|
||||
# 'management openstack' and 'exit' commands
|
||||
if self.cli_commands['timestamp']:
|
||||
self._region_updated_time = ret[-1]
|
||||
|
||||
def _eapi_host_url(self):
|
||||
self._validate_config()
|
||||
|
||||
@ -339,18 +517,31 @@ class SyncService(object):
|
||||
def __init__(self, rpc_wrapper, neutron_db):
|
||||
self._rpc = rpc_wrapper
|
||||
self._ndb = neutron_db
|
||||
self._force_sync = True
|
||||
|
||||
def synchronize(self):
|
||||
"""Sends data to EOS which differs from neutron DB."""
|
||||
|
||||
LOG.info(_('Syncing Neutron <-> EOS'))
|
||||
try:
|
||||
# Get the time at which entities in the region were updated.
|
||||
# If the times match, then ML2 is in sync with EOS. Otherwise
|
||||
# perform a complete sync.
|
||||
if not self._force_sync and self._rpc.region_in_sync():
|
||||
LOG.info(_('OpenStack and EOS are in sync!'))
|
||||
return
|
||||
except arista_exc.AristaRpcError:
|
||||
LOG.warning(EOS_UNREACHABLE_MSG)
|
||||
self._force_sync = True
|
||||
return
|
||||
|
||||
try:
|
||||
#Always register with EOS to ensure that it has correct credentials
|
||||
self._rpc._register_with_eos()
|
||||
self._rpc.register_with_eos()
|
||||
eos_tenants = self._rpc.get_tenants()
|
||||
except arista_exc.AristaRpcError:
|
||||
msg = _('EOS is not available, will try sync later')
|
||||
LOG.warning(msg)
|
||||
LOG.warning(EOS_UNREACHABLE_MSG)
|
||||
self._force_sync = True
|
||||
return
|
||||
|
||||
db_tenants = db.get_tenants()
|
||||
@ -362,24 +553,32 @@ class SyncService(object):
|
||||
msg = _('No Tenants configured in Neutron DB. But %d '
|
||||
'tenants disovered in EOS during synchronization.'
|
||||
'Enitre EOS region is cleared') % len(eos_tenants)
|
||||
LOG.info(msg)
|
||||
# Re-register with EOS so that the timestamp is updated.
|
||||
self._rpc.register_with_eos()
|
||||
# Region has been completely cleaned. So there is nothing to
|
||||
# syncronize
|
||||
self._force_sync = False
|
||||
except arista_exc.AristaRpcError:
|
||||
msg = _('EOS is not available, failed to delete this region')
|
||||
LOG.warning(msg)
|
||||
LOG.warning(EOS_UNREACHABLE_MSG)
|
||||
self._force_sync = True
|
||||
return
|
||||
|
||||
# EOS and Neutron has matching set of tenants. Now check
|
||||
# to ensure that networks and VMs match on both sides for
|
||||
# each tenant.
|
||||
for tenant in eos_tenants.keys():
|
||||
if tenant not in db_tenants:
|
||||
#send delete tenant to EOS
|
||||
try:
|
||||
self._rpc.delete_tenant(tenant)
|
||||
del eos_tenants[tenant]
|
||||
except arista_exc.AristaRpcError:
|
||||
msg = _('EOS is not available, '
|
||||
'failed to delete tenant %s') % tenant
|
||||
LOG.warning(msg)
|
||||
# Delete tenants that are in EOS, but not in the database
|
||||
tenants_to_delete = frozenset(eos_tenants.keys()).difference(
|
||||
db_tenants.keys())
|
||||
|
||||
if tenants_to_delete:
|
||||
try:
|
||||
self._rpc.delete_tenant_bulk(tenants_to_delete)
|
||||
except arista_exc.AristaRpcError:
|
||||
LOG.warning(EOS_UNREACHABLE_MSG)
|
||||
self._force_sync = True
|
||||
return
|
||||
|
||||
# None of the commands have failed till now. But if subsequent
|
||||
# operations fail, then force_sync is set to true
|
||||
self._force_sync = False
|
||||
|
||||
for tenant in db_tenants:
|
||||
db_nets = db.get_networks(tenant)
|
||||
@ -387,74 +586,55 @@ class SyncService(object):
|
||||
eos_nets = self._get_eos_networks(eos_tenants, tenant)
|
||||
eos_vms = self._get_eos_vms(eos_tenants, tenant)
|
||||
|
||||
# Check for the case if everything is already in sync.
|
||||
if eos_nets == db_nets:
|
||||
# Net list is same in both Neutron and EOS.
|
||||
# check the vM list
|
||||
if eos_vms == db_vms:
|
||||
# Nothing to do. Everything is in sync for this tenant
|
||||
continue
|
||||
db_nets_key_set = frozenset(db_nets.keys())
|
||||
db_vms_key_set = frozenset(db_vms.keys())
|
||||
eos_nets_key_set = frozenset(eos_nets.keys())
|
||||
eos_vms_key_set = frozenset(eos_vms.keys())
|
||||
|
||||
# Neutron DB and EOS reruires synchronization.
|
||||
# First delete anything which should not be EOS
|
||||
# delete VMs from EOS if it is not present in neutron DB
|
||||
for vm_id in eos_vms:
|
||||
if vm_id not in db_vms:
|
||||
try:
|
||||
self._rpc.delete_vm(tenant, vm_id)
|
||||
except arista_exc.AristaRpcError:
|
||||
msg = _('EOS is not available,'
|
||||
'failed to delete vm %s') % vm_id
|
||||
LOG.warning(msg)
|
||||
# Find the networks that are present on EOS, but not in Neutron DB
|
||||
nets_to_delete = eos_nets_key_set.difference(db_nets_key_set)
|
||||
|
||||
# delete network from EOS if it is not present in neutron DB
|
||||
for net_id in eos_nets:
|
||||
if net_id not in db_nets:
|
||||
try:
|
||||
self._rpc.delete_network(tenant, net_id)
|
||||
except arista_exc.AristaRpcError:
|
||||
msg = _('EOS is not available,'
|
||||
'failed to delete network %s') % net_id
|
||||
LOG.warning(msg)
|
||||
# Find the VMs that are present on EOS, but not in Neutron DB
|
||||
vms_to_delete = eos_vms_key_set.difference(db_vms_key_set)
|
||||
|
||||
# update networks in EOS if it is present in neutron DB
|
||||
for net_id in db_nets:
|
||||
if net_id not in eos_nets:
|
||||
vlan_id = db_nets[net_id]['segmentationTypeId']
|
||||
net_name = self._ndb.get_network_name(tenant, net_id)
|
||||
try:
|
||||
self._rpc.create_network(tenant, net_id,
|
||||
net_name,
|
||||
vlan_id)
|
||||
except arista_exc.AristaRpcError:
|
||||
msg = _('EOS is not available, failed to create'
|
||||
'network id %s') % net_id
|
||||
LOG.warning(msg)
|
||||
# Find the Networks that are present in Neutron DB, but not on EOS
|
||||
nets_to_update = db_nets_key_set.difference(eos_nets_key_set)
|
||||
|
||||
# Update VMs in EOS if it is present in neutron DB
|
||||
for vm_id in db_vms:
|
||||
if vm_id not in eos_vms:
|
||||
vm = db_vms[vm_id]
|
||||
ports = self._ndb.get_all_ports_for_vm(tenant, vm_id)
|
||||
for port in ports:
|
||||
port_id = port['id']
|
||||
net_id = port['network_id']
|
||||
port_name = port['name']
|
||||
device_owner = port['device_owner']
|
||||
vm_id = vm['vmId']
|
||||
host_id = vm['host']
|
||||
try:
|
||||
self._rpc.plug_port_into_network(vm_id,
|
||||
host_id,
|
||||
port_id,
|
||||
net_id,
|
||||
tenant,
|
||||
port_name,
|
||||
device_owner)
|
||||
except arista_exc.AristaRpcError:
|
||||
msg = _('EOS is not available, failed to create '
|
||||
'vm id %s') % vm['vmId']
|
||||
LOG.warning(msg)
|
||||
# Find the VMs that are present in Neutron DB, but not on EOS
|
||||
vms_to_update = db_vms_key_set.difference(eos_vms_key_set)
|
||||
|
||||
try:
|
||||
if vms_to_delete:
|
||||
self._rpc.delete_vm_bulk(tenant, vms_to_delete)
|
||||
if nets_to_delete:
|
||||
self._rpc.delete_network_bulk(tenant, nets_to_delete)
|
||||
if nets_to_update:
|
||||
# Create a dict of networks keyed by id.
|
||||
neutron_nets = dict(
|
||||
(network['id'], network) for network in
|
||||
self._ndb.get_all_networks_for_tenant(tenant)
|
||||
)
|
||||
|
||||
networks = [
|
||||
{'network_id': net_id,
|
||||
'segmentation_id':
|
||||
db_nets[net_id]['segmentationTypeId'],
|
||||
'network_name':
|
||||
neutron_nets.get(net_id, {'name': ''})['name'], }
|
||||
for net_id in nets_to_update
|
||||
]
|
||||
self._rpc.create_network_bulk(tenant, networks)
|
||||
if vms_to_update:
|
||||
# Filter the ports to only the vms that we are interested
|
||||
# in.
|
||||
vm_ports = [
|
||||
port for port in self._ndb.get_all_ports_for_tenant(
|
||||
tenant) if port['device_id'] in vms_to_update
|
||||
]
|
||||
self._rpc.create_vm_port_bulk(tenant, vm_ports, db_vms)
|
||||
except arista_exc.AristaRpcError:
|
||||
LOG.warning(EOS_UNREACHABLE_MSG)
|
||||
self._force_sync = True
|
||||
|
||||
def _get_eos_networks(self, eos_tenants, tenant):
|
||||
networks = {}
|
||||
@ -492,8 +672,12 @@ class AristaDriver(driver_api.MechanismDriver):
|
||||
self.eos_sync_lock = threading.Lock()
|
||||
|
||||
def initialize(self):
|
||||
self.rpc._register_with_eos()
|
||||
self._cleanupDb()
|
||||
self.rpc.register_with_eos()
|
||||
self._cleanup_db()
|
||||
self.rpc.check_cli_commands()
|
||||
# Registering with EOS updates self.rpc.region_updated_time. Clear it
|
||||
# to force an initial sync
|
||||
self.rpc.clear_region_updated_time()
|
||||
self._synchronization_thread()
|
||||
|
||||
def create_network_precommit(self, context):
|
||||
@ -522,10 +706,11 @@ class AristaDriver(driver_api.MechanismDriver):
|
||||
with self.eos_sync_lock:
|
||||
if db.is_network_provisioned(tenant_id, network_id):
|
||||
try:
|
||||
self.rpc.create_network(tenant_id,
|
||||
network_id,
|
||||
network_name,
|
||||
vlan_id)
|
||||
network_dict = {
|
||||
'network_id': network_id,
|
||||
'segmentation_id': vlan_id,
|
||||
'network_name': network_name}
|
||||
self.rpc.create_network(tenant_id, network_dict)
|
||||
except arista_exc.AristaRpcError:
|
||||
LOG.info(EOS_UNREACHABLE_MSG)
|
||||
raise ml2_exc.MechanismDriverError()
|
||||
@ -563,10 +748,11 @@ class AristaDriver(driver_api.MechanismDriver):
|
||||
with self.eos_sync_lock:
|
||||
if db.is_network_provisioned(tenant_id, network_id):
|
||||
try:
|
||||
self.rpc.create_network(tenant_id,
|
||||
network_id,
|
||||
network_name,
|
||||
vlan_id)
|
||||
network_dict = {
|
||||
'network_id': network_id,
|
||||
'segmentation_id': vlan_id,
|
||||
'network_name': network_name}
|
||||
self.rpc.create_network(tenant_id, network_dict)
|
||||
except arista_exc.AristaRpcError:
|
||||
LOG.info(EOS_UNREACHABLE_MSG)
|
||||
raise ml2_exc.MechanismDriverError()
|
||||
@ -810,7 +996,7 @@ class AristaDriver(driver_api.MechanismDriver):
|
||||
self.timer.cancel()
|
||||
self.timer = None
|
||||
|
||||
def _cleanupDb(self):
|
||||
def _cleanup_db(self):
|
||||
"""Clean up any uncessary entries in our DB."""
|
||||
db_tenants = db.get_tenants()
|
||||
for tenant in db_tenants:
|
||||
|
@ -17,6 +17,7 @@
|
||||
import mock
|
||||
from oslo.config import cfg
|
||||
|
||||
from neutron.common import constants as n_const
|
||||
import neutron.db.api as ndb
|
||||
from neutron.plugins.ml2.drivers.mech_arista import db
|
||||
from neutron.plugins.ml2.drivers.mech_arista import exceptions as arista_exc
|
||||
@ -217,6 +218,9 @@ class PositiveRPCWrapperValidConfigTestCase(base.BaseTestCase):
|
||||
self.region = 'RegionOne'
|
||||
self.drv._server = mock.MagicMock()
|
||||
|
||||
def _get_exit_mode_cmds(self, modes):
|
||||
return ['exit'] * len(modes)
|
||||
|
||||
def test_no_exception_on_correct_configuration(self):
|
||||
self.assertIsNotNone(self.drv)
|
||||
|
||||
@ -290,10 +294,11 @@ class PositiveRPCWrapperValidConfigTestCase(base.BaseTestCase):
|
||||
|
||||
def test_create_network(self):
|
||||
tenant_id = 'ten-1'
|
||||
network_id = 'net-id'
|
||||
network_name = 'net-name'
|
||||
vlan_id = 123
|
||||
self.drv.create_network(tenant_id, network_id, network_name, vlan_id)
|
||||
network = {
|
||||
'network_id': 'net-id',
|
||||
'network_name': 'net-name',
|
||||
'segmentation_id': 123}
|
||||
self.drv.create_network(tenant_id, network)
|
||||
cmds = ['enable', 'configure', 'management openstack',
|
||||
'region RegionOne',
|
||||
'tenant ten-1', 'network id net-id name "net-name"',
|
||||
@ -301,6 +306,30 @@ class PositiveRPCWrapperValidConfigTestCase(base.BaseTestCase):
|
||||
'exit', 'exit', 'exit', 'exit', 'exit']
|
||||
self.drv._server.runCmds.assert_called_once_with(version=1, cmds=cmds)
|
||||
|
||||
def test_create_network_bulk(self):
|
||||
tenant_id = 'ten-2'
|
||||
num_networks = 10
|
||||
networks = [{
|
||||
'network_id': 'net-id-%d' % net_id,
|
||||
'network_name': 'net-name-%d' % net_id,
|
||||
'segmentation_id': net_id} for net_id in range(1, num_networks)
|
||||
]
|
||||
|
||||
self.drv.create_network_bulk(tenant_id, networks)
|
||||
cmds = ['enable',
|
||||
'configure',
|
||||
'management openstack',
|
||||
'region RegionOne',
|
||||
'tenant ten-2']
|
||||
for net_id in range(1, num_networks):
|
||||
cmds.append('network id net-id-%d name "net-name-%d"' %
|
||||
(net_id, net_id))
|
||||
cmds.append('segment 1 type vlan id %d' % net_id)
|
||||
|
||||
cmds.extend(self._get_exit_mode_cmds(['tenant', 'region', 'openstack',
|
||||
'configure', 'enable']))
|
||||
self.drv._server.runCmds.assert_called_once_with(version=1, cmds=cmds)
|
||||
|
||||
def test_delete_network(self):
|
||||
tenant_id = 'ten-1'
|
||||
network_id = 'net-id'
|
||||
@ -311,6 +340,29 @@ class PositiveRPCWrapperValidConfigTestCase(base.BaseTestCase):
|
||||
'exit', 'exit', 'exit', 'exit']
|
||||
self.drv._server.runCmds.assert_called_once_with(version=1, cmds=cmds)
|
||||
|
||||
def test_delete_network_bulk(self):
|
||||
tenant_id = 'ten-2'
|
||||
num_networks = 10
|
||||
networks = [{
|
||||
'network_id': 'net-id-%d' % net_id,
|
||||
'network_name': 'net-name-%d' % net_id,
|
||||
'segmentation_id': net_id} for net_id in range(1, num_networks)
|
||||
]
|
||||
|
||||
networks = ['net-id-%d' % net_id for net_id in range(1, num_networks)]
|
||||
self.drv.delete_network_bulk(tenant_id, networks)
|
||||
cmds = ['enable',
|
||||
'configure',
|
||||
'management openstack',
|
||||
'region RegionOne',
|
||||
'tenant ten-2']
|
||||
for net_id in range(1, num_networks):
|
||||
cmds.append('no network id net-id-%d' % net_id)
|
||||
|
||||
cmds.extend(self._get_exit_mode_cmds(['tenant', 'region', 'openstack',
|
||||
'configure']))
|
||||
self.drv._server.runCmds.assert_called_once_with(version=1, cmds=cmds)
|
||||
|
||||
def test_delete_vm(self):
|
||||
tenant_id = 'ten-1'
|
||||
vm_id = 'vm-id'
|
||||
@ -321,6 +373,84 @@ class PositiveRPCWrapperValidConfigTestCase(base.BaseTestCase):
|
||||
'exit', 'exit', 'exit', 'exit']
|
||||
self.drv._server.runCmds.assert_called_once_with(version=1, cmds=cmds)
|
||||
|
||||
def test_delete_vm_bulk(self):
|
||||
tenant_id = 'ten-2'
|
||||
num_vms = 10
|
||||
vm_ids = ['vm-id-%d' % vm_id for vm_id in range(1, num_vms)]
|
||||
self.drv.delete_vm_bulk(tenant_id, vm_ids)
|
||||
|
||||
cmds = ['enable',
|
||||
'configure',
|
||||
'management openstack',
|
||||
'region RegionOne',
|
||||
'tenant ten-2']
|
||||
|
||||
for vm_id in range(1, num_vms):
|
||||
cmds.append('no vm id vm-id-%d' % vm_id)
|
||||
|
||||
cmds.extend(self._get_exit_mode_cmds(['tenant', 'region', 'openstack',
|
||||
'configure']))
|
||||
self.drv._server.runCmds.assert_called_once_with(version=1, cmds=cmds)
|
||||
|
||||
def test_create_vm_port_bulk(self):
|
||||
tenant_id = 'ten-3'
|
||||
num_vms = 10
|
||||
num_ports_per_vm = 2
|
||||
|
||||
vms = dict(
|
||||
('vm-id-%d' % vm_id, {
|
||||
'vmId': 'vm-id-%d' % vm_id,
|
||||
'host': 'host_%d' % vm_id,
|
||||
}
|
||||
) for vm_id in range(1, num_vms)
|
||||
)
|
||||
|
||||
devices = [n_const.DEVICE_OWNER_DHCP, 'compute']
|
||||
vm_port_list = []
|
||||
|
||||
net_count = 1
|
||||
for vm_id in range(1, num_vms):
|
||||
for port_id in range(1, num_ports_per_vm):
|
||||
port = {
|
||||
'id': 'port-id-%d-%d' % (vm_id, port_id),
|
||||
'device_id': 'vm-id-%d' % vm_id,
|
||||
'device_owner': devices[(vm_id + port_id) % 2],
|
||||
'network_id': 'network-id-%d' % net_count,
|
||||
'name': 'port-%d-%d' % (vm_id, port_id)
|
||||
}
|
||||
vm_port_list.append(port)
|
||||
net_count += 1
|
||||
|
||||
self.drv.create_vm_port_bulk(tenant_id, vm_port_list, vms)
|
||||
cmds = ['enable',
|
||||
'configure',
|
||||
'management openstack',
|
||||
'region RegionOne',
|
||||
'tenant ten-3']
|
||||
|
||||
net_count = 1
|
||||
for vm_count in range(1, num_vms):
|
||||
host = 'host_%s' % vm_count
|
||||
for port_count in range(1, num_ports_per_vm):
|
||||
vm_id = 'vm-id-%d' % vm_count
|
||||
device_owner = devices[(vm_count + port_count) % 2]
|
||||
port_name = '"port-%d-%d"' % (vm_count, port_count)
|
||||
network_id = 'network-id-%d' % net_count
|
||||
port_id = 'port-id-%d-%d' % (vm_count, port_count)
|
||||
if device_owner == 'network:dhcp':
|
||||
cmds.append('network id %s' % network_id)
|
||||
cmds.append('dhcp id %s hostid %s port-id %s name %s' % (
|
||||
vm_id, host, port_id, port_name))
|
||||
elif device_owner == 'compute':
|
||||
cmds.append('vm id %s hostid %s' % (vm_id, host))
|
||||
cmds.append('port id %s name %s network-id %s' % (
|
||||
port_id, port_name, network_id))
|
||||
net_count += 1
|
||||
|
||||
cmds.extend(self._get_exit_mode_cmds(['tenant', 'region',
|
||||
'openstack']))
|
||||
self.drv._server.runCmds.assert_called_once_with(version=1, cmds=cmds)
|
||||
|
||||
def test_delete_tenant(self):
|
||||
tenant_id = 'ten-1'
|
||||
self.drv.delete_tenant(tenant_id)
|
||||
@ -329,6 +459,21 @@ class PositiveRPCWrapperValidConfigTestCase(base.BaseTestCase):
|
||||
'exit', 'exit', 'exit']
|
||||
self.drv._server.runCmds.assert_called_once_with(version=1, cmds=cmds)
|
||||
|
||||
def test_delete_tenant_bulk(self):
|
||||
num_tenants = 10
|
||||
tenant_list = ['ten-%d' % t_id for t_id in range(1, num_tenants)]
|
||||
self.drv.delete_tenant_bulk(tenant_list)
|
||||
cmds = ['enable',
|
||||
'configure',
|
||||
'management openstack',
|
||||
'region RegionOne']
|
||||
for ten_id in range(1, num_tenants):
|
||||
cmds.append('no tenant ten-%d' % ten_id)
|
||||
|
||||
cmds.extend(self._get_exit_mode_cmds(['region', 'openstack',
|
||||
'configure']))
|
||||
self.drv._server.runCmds.assert_called_once_with(version=1, cmds=cmds)
|
||||
|
||||
def test_get_network_info_returns_none_when_no_such_net(self):
|
||||
expected = []
|
||||
self.drv.get_tenants = mock.MagicMock()
|
||||
@ -353,6 +498,11 @@ class PositiveRPCWrapperValidConfigTestCase(base.BaseTestCase):
|
||||
self.assertEqual(net_info, valid_net_info,
|
||||
('Must return network info for a valid net'))
|
||||
|
||||
def test_check_cli_commands(self):
|
||||
self.drv.check_cli_commands()
|
||||
cmds = ['show openstack config region RegionOne timestamp']
|
||||
self.drv._server.runCmds.assert_called_once_with(version=1, cmds=cmds)
|
||||
|
||||
|
||||
class AristaRPCWrapperInvalidConfigTestCase(base.BaseTestCase):
|
||||
"""Negative test cases to test the Arista Driver configuration."""
|
||||
|
Loading…
Reference in New Issue
Block a user