def create_acl(self, sg): """Creates an ACL on Arista Switch. Deals with multiple configurations - such as multiple switches """ # Do nothing if Security Groups are not enabled if not self.sg_enabled: return if not sg: msg = _('Invalid or Empty Security Group Specified') raise arista_exc.AristaSecurityGroupError(msg=msg) in_cmds, out_cmds = self._create_acl_shell(sg['id']) for sgr in sg['security_group_rules']: in_cmds, out_cmds = self._create_acl_rule(in_cmds, out_cmds, sgr) in_cmds.append('exit') out_cmds.append('exit') for s in self._servers: try: self._run_openstack_sg_cmds(in_cmds, s) self._run_openstack_sg_cmds(out_cmds, s) except Exception: msg = (_('Failed to create ACL on EOS %s') % s) LOG.exception(msg) raise arista_exc.AristaSecurityGroupError(msg=msg)
def _clean_acls(self, sg, failed_switch, switches_to_clean): """This is a helper function to clean up ACLs on switches. This called from within an exception - when apply_acl fails. Therefore, ensure that exception is raised after the cleanup is done. :param sg: Security Group to be removed :param failed_switch: IP of the switch where ACL failed :param switches_to_clean: List of switches containing link info """ if not switches_to_clean: # This means the no switch needs cleaning - so, simply raise the # the exception and bail out msg = (_("Failed to apply ACL %(sg)s on switch %(switch)s") % { 'sg': sg, 'switch': failed_switch }) LOG.error(msg) for s in switches_to_clean: try: # Port is being updated to remove security groups self.security_group_driver.remove_acl(sg, s['switch_id'], s['port_id'], s['switch_info']) except Exception: msg = (_("Failed to remove ACL %(sg)s on switch %(switch)%") % { 'sg': sg, 'switch': s['switch_info'] }) LOG.warning(msg) raise arista_exc.AristaSecurityGroupError(msg=msg)
def _validate_config(self): if cfg.CONF.ml2_arista.get('eapi_host') == '': msg = _('Required option eapi_host is not set') LOG.error(msg) raise arista_exc.AristaConfigError(msg=msg) if cfg.CONF.ml2_arista.get('eapi_username') == '': msg = _('Required option eapi_username is not set') LOG.error(msg) raise arista_exc.AristaConfigError(msg=msg)
def _validate_config(self): if cfg.CONF.l3_arista.get('primary_l3_host') == '': msg = _('Required option primary_l3_host is not set') LOG.error(msg) raise arista_exc.AristaServicePluginConfigError(msg=msg) if cfg.CONF.l3_arista.get('mlag_config'): if cfg.CONF.l3_arista.get('secondary_l3_host') == '': msg = _('Required option secondary_l3_host is not set') LOG.error(msg) raise arista_exc.AristaServicePluginConfigError(msg=msg) if cfg.CONF.l3_arista.get('primary_l3_host_username') == '': msg = _('Required option primary_l3_host_username is not set') LOG.error(msg) raise arista_exc.AristaServicePluginConfigError(msg=msg)
def create_router(self, context, router): """Creates a router on Arista Switch. Deals with multiple configurations - such as Router per VRF, a router in default VRF, Virtual Router in MLAG configurations """ if router: router_name = self._arista_router_name(router['id'], router['name']) hashed = hashlib.sha256(router_name.encode('utf-8')) rdm = str(int(hashed.hexdigest(), 16) % 65536) mlag_peer_failed = False for s in self._servers: try: self.create_router_on_eos(router_name, rdm, s) mlag_peer_failed = False except Exception: if self._mlag_configured and not mlag_peer_failed: # In paied switch, it is OK to fail on one switch mlag_peer_failed = True else: msg = (_('Failed to create router %s on EOS') % router_name) LOG.exception(msg) raise arista_exc.AristaServicePluginRpcError(msg=msg)
def update_security_group(self, sg): try: self.rpc.create_acl(sg) except Exception: msg = (_('Failed to create ACL on EOS %s') % sg) LOG.exception(msg) raise arista_exc.AristaSecurityGroupError(msg=msg)
def create_security_group_rule(self, sgr): try: self.rpc.create_acl_rule(sgr) except Exception: msg = (_('Failed to create ACL rule on EOS %s') % sgr) LOG.exception(msg) raise arista_exc.AristaSecurityGroupError(msg=msg)
def _run_eos_cmds(self, commands, server): """Execute/sends a CAPI (Command API) command to EOS. This method is useful for running show commands that require no prefix or postfix commands. :param commands : List of commands to be executed on EOS. :param server: Server endpoint on the Arista switch to be configured """ LOG.info(_LI('Executing command on Arista EOS: %s'), commands) try: # this returns array of return values for every command in # commands list ret = server.execute(commands) LOG.info(_LI('Results of execution on Arista EOS: %s'), ret) return ret except Exception: msg = (_('Error occurred while trying to execute ' 'commands %(cmd)s on EOS %(host)s') % { 'cmd': commands, 'host': server }) LOG.exception(msg) raise arista_exc.AristaServicePluginRpcError(msg=msg)
def _run_openstack_sg_cmds(self, commands, server): """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 server: Server endpoint on the Arista switch to be configured """ command_start = ['enable', 'configure'] command_end = ['exit'] full_command = command_start + commands + command_end LOG.info(_LI('Executing command on Arista EOS: %s'), full_command) try: # this returns array of return values for every command in # full_command list ret = server.runCmds(version=1, cmds=full_command) LOG.info(_LI('Results of execution on Arista EOS: %s'), ret) except Exception: msg = (_('Error occurred while trying to execute ' 'commands %(cmd)s on EOS %(host)s') % { 'cmd': full_command, 'host': server }) LOG.exception(msg) raise arista_exc.AristaServicePluginRpcError(msg=msg)
def create_acl_rule(self, sgr): """Creates an ACL on Arista Switch. For a given Security Group (ACL), it adds additional rule Deals with multiple configurations - such as multiple switches """ # Do nothing if Security Groups are not enabled if not self.sg_enabled: return name = self._arista_acl_name(sgr['security_group_id'], sgr['direction']) cmds = [] for c in self.aclCreateDict['create']: cmds.append(c.format(name)) in_cmds, out_cmds = self._create_acl_rule(cmds, cmds, sgr) cmds = in_cmds if sgr['direction'] == 'egress': cmds = out_cmds cmds.append('exit') for s in self._servers: try: self._run_openstack_sg_cmds(cmds, s) except Exception: msg = (_('Failed to create ACL rule on EOS %s') % s) LOG.exception(msg) raise arista_exc.AristaSecurityGroupError(msg=msg)
def delete_acl_rule(self, sgr): """Deletes an ACL rule on Arista Switch. For a given Security Group (ACL), it adds removes a rule Deals with multiple configurations - such as multiple switches """ # Do nothing if Security Groups are not enabled if not self.sg_enabled: return # Only deal with valid protocols - skip the rest if not sgr or sgr['protocol'] not in SUPPORTED_SG_PROTOCOLS: return # Build seperate ACL for ingress and egress name = self._arista_acl_name(sgr['security_group_id'], sgr['direction']) remote_ip = sgr['remote_ip_prefix'] if not remote_ip: remote_ip = 'any' min_port = sgr['port_range_min'] if not min_port: min_port = 0 max_port = sgr['port_range_max'] if not max_port and sgr['protocol'] != 'icmp': max_port = 65535 for s in self._servers: try: self._delete_acl_rule_from_eos(name, sgr['protocol'], remote_ip, min_port, max_port, sgr['direction'], s) except Exception: msg = (_('Failed to delete ACL on EOS %s') % s) LOG.exception(msg) raise arista_exc.AristaSecurityGroupError(msg=msg)
def _validate_config(self): if not self.sg_enabled: return if len(cfg.CONF.ml2_arista.get('switch_info')) < 1: msg = _('Required option - when "sec_group_support" is enabled, ' 'at least one switch must be specified ') LOG.exception(msg) raise arista_exc.AristaConfigError(msg=msg)
def remove_acl(self, sgs, switch_id, port_id, switch_info): """Removes an ACL from Arista Switch. Removes ACLs from the baremetal ports only. The port/switch details is passed throuhg the parameters. param sgs: List of Security Groups param switch_id: Switch ID of TOR where ACL needs to be removed param port_id: Port ID of port where ACL needs to be removed param switch_info: IP address of the TOR """ # Do nothing if Security Groups are not enabled if not self.sg_enabled: return # We do not support more than one security group on a port if not sgs or len(sgs) > 1: msg = (_('Only one Security Group Supported on a port %s') % sgs) raise arista_exc.AristaSecurityGroupError(msg=msg) sg = self._ndb.get_security_group(sgs[0]) # We already have ACLs on the TORs. # Here we need to find out which ACL is applicable - i.e. # Ingress ACL, egress ACL or both direction = [] for sgr in sg['security_group_rules']: # Only deal with valid protocols - skip the rest if not sgr or sgr['protocol'] not in SUPPORTED_SG_PROTOCOLS: continue if sgr['direction'] not in direction: direction.append(sgr['direction']) # THIS IS TOTAL HACK NOW - just for testing # Assumes the credential of all switches are same as specified # in the condig file server = jsonrpclib.Server(self._eapi_host_url(switch_info)) for d in range(len(direction)): name = self._arista_acl_name(sg['id'], direction[d]) try: self._remove_acl_from_eos(port_id, name, direction[d], server) except Exception: msg = (_('Failed to remove ACL on port %s') % port_id) LOG.exception(msg)
def delete_security_group_rule(self, sgr_id): if sgr_id: sgr = self.ndb.get_security_group_rule(sgr_id) if sgr: try: self.rpc.delete_acl_rule(sgr) except Exception: msg = (_('Failed to delete ACL rule on EOS %s') % sgr) LOG.exception(msg) raise arista_exc.AristaSecurityGroupError(msg=msg)
def _validate_config(self): if cfg.CONF.l3_arista.get('primary_l3_host') == '': msg = _('Required option primary_l3_host is not set') LOG.error(msg) raise arista_exc.AristaServicePluginConfigError(msg=msg) if cfg.CONF.l3_arista.get('mlag_config'): if cfg.CONF.l3_arista.get('use_vrf'): # This is invalid/unsupported configuration msg = _('VRFs are not supported MLAG config mode') LOG.error(msg) raise arista_exc.AristaServicePluginConfigError(msg=msg) if cfg.CONF.l3_arista.get('secondary_l3_host') == '': msg = _('Required option secondary_l3_host is not set') LOG.error(msg) raise arista_exc.AristaServicePluginConfigError(msg=msg) if cfg.CONF.l3_arista.get('primary_l3_host_username') == '': msg = _('Required option primary_l3_host_username is not set') LOG.error(msg) raise arista_exc.AristaServicePluginConfigError(msg=msg)
class VlanUnavailable(exceptions.NeutronException): """An exception indicating VLAN creation failed because it's not available. A specialization of the NeutronException indicating network creation failed because a specified VLAN is unavailable on the physical network. :param vlan_id: The VLAN ID. :param physical_network: The physical network. """ message = _("Unable to create the network. " "The VLAN %(vlan_id)s on physical network " "%(physical_network)s is not available.")
def _create_acl_on_eos(self, in_cmds, out_cmds, protocol, cidr, from_port, to_port, direction): """Creates an ACL on Arista HW Device. :param name: Name for the ACL :param server: Server endpoint on the Arista switch to be configured """ if protocol == 'icmp': # ICMP rules require special processing if ((from_port and to_port) or (not from_port and not to_port)): rule = 'icmp_custom2' elif from_port and not to_port: rule = 'icmp_custom1' else: msg = _('Invalid ICMP rule specified') LOG.exception(msg) raise arista_exc.AristaSecurityGroupError(msg=msg) rule_type = 'in' cmds = in_cmds if direction == 'egress': rule_type = 'out' cmds = out_cmds final_rule = rule_type + '_' + rule acl_dict = self.aclCreateDict[final_rule] # None port is probematic - should be replaced with 0 if not from_port: from_port = 0 if not to_port: to_port = 0 for c in acl_dict: if rule == 'icmp_custom2': cmds.append(c.format(cidr, from_port, to_port)) else: cmds.append(c.format(cidr, from_port)) return in_cmds, out_cmds else: # Non ICMP rules processing here acl_dict = self.aclCreateDict['in_rule'] cmds = in_cmds if direction == 'egress': acl_dict = self.aclCreateDict['out_rule'] cmds = out_cmds if not protocol: acl_dict = self.aclCreateDict['default'] for c in acl_dict: cmds.append(c.format(protocol, cidr, from_port, to_port)) return in_cmds, out_cmds
def apply_acl(self, sgs, switch_id, port_id, switch_info): """Creates an ACL on Arista Switch. Applies ACLs to the baremetal ports only. The port/switch details is passed through the parameters. Deals with multiple configurations - such as multiple switches param sgs: List of Security Groups param switch_id: Switch ID of TOR where ACL needs to be applied param port_id: Port ID of port where ACL needs to be applied param switch_info: IP address of the TOR """ # Do nothing if Security Groups are not enabled if not self.sg_enabled: return # We do not support more than one security group on a port if not sgs or len(sgs) > 1: msg = (_('Only one Security Group Supported on a port %s') % sgs) raise arista_exc.AristaSecurityGroupError(msg=msg) sg = self._ndb.get_security_group(sgs[0]) # We already have ACLs on the TORs. # Here we need to find out which ACL is applicable - i.e. # Ingress ACL, egress ACL or both direction = ['ingress', 'egress'] server = self._make_eapi_client(switch_info) for d in range(len(direction)): name = self._arista_acl_name(sg['id'], direction[d]) try: self._apply_acl_on_eos(port_id, name, direction[d], server) except Exception: msg = (_('Failed to apply ACL on port %s') % port_id) LOG.exception(msg) raise arista_exc.AristaSecurityGroupError(msg=msg)
def _run_eos_cmds(self, commands, server): LOG.info(_LI('Executing command on Arista EOS: %s'), commands) try: # this returns array of return values for every command in # full_command list ret = server.execute(commands) LOG.info(_LI('Results of execution on Arista EOS: %s'), ret) return ret except Exception: msg = (_('Error occurred while trying to execute ' 'commands %(cmd)s on EOS %(host)s') % {'cmd': commands, 'host': server}) LOG.exception(msg) raise arista_exc.AristaServicePluginRpcError(msg=msg)
def delete_acl(self, sg): """Deletes an ACL from Arista Switch. Deals with multiple configurations - such as multiple switches """ # Do nothing if Security Groups are not enabled if not self.sg_enabled: return if not sg: msg = _('Invalid or Empty Security Group Specified') raise arista_exc.AristaSecurityGroupError(msg=msg) direction = ['ingress', 'egress'] for d in range(len(direction)): name = self._arista_acl_name(sg['id'], direction[d]) for s in self._servers: try: self._delete_acl_from_eos(name, s) except Exception: msg = (_('Failed to create ACL on EOS %s') % s) LOG.exception(msg) raise arista_exc.AristaSecurityGroupError(msg=msg)
def _delete_acl_rule_from_eos(self, name, protocol, cidr, from_port, to_port, direction, server): """deletes an ACL from Arista HW Device. :param name: Name for the ACL :param server: Server endpoint on the Arista switch to be configured """ cmds = [] if protocol == 'icmp': # ICMP rules require special processing if ((from_port and to_port) or (not from_port and not to_port)): rule = 'icmp_custom2' elif from_port and not to_port: rule = 'icmp_custom1' else: msg = _('Invalid ICMP rule specified') LOG.exception(msg) raise arista_exc.AristaSecurityGroupError(msg=msg) rule_type = 'del_in' if direction == 'egress': rule_type = 'del_out' final_rule = rule_type + '_' + rule acl_dict = self.aclCreateDict[final_rule] # None port is probematic - should be replaced with 0 if not from_port: from_port = 0 if not to_port: to_port = 0 for c in acl_dict: if rule == 'icmp_custom2': cmds.append(c.format(name, cidr, from_port, to_port)) else: cmds.append(c.format(name, cidr, from_port)) else: acl_dict = self.aclCreateDict['del_in_acl_rule'] if direction == 'egress': acl_dict = self.aclCreateDict['del_out_acl_rule'] for c in acl_dict: cmds.append(c.format(name, protocol, cidr, from_port, to_port)) self._run_openstack_sg_cmds(cmds, server)
def _select_dicts(self, ipv): if self._use_vrf: if ipv == 6: msg = (_('IPv6 subnets are not supported with VRFs')) LOG.exception(msg) raise arista_exc.AristaServicePluginRpcError(msg=msg) self._interfaceDict = router_in_vrf['interface'] else: if ipv == 6: # for IPv6 use IPv6 commmands self._interfaceDict = router_in_default_vrf_v6['interface'] self._additionalInterfaceCmdsDict = ( additional_cmds_for_mlag_v6['interface']) else: self._interfaceDict = router_in_default_vrf['interface'] self._additionalInterfaceCmdsDict = ( additional_cmds_for_mlag['interface'])
def get_vlan_allocation(self): """Returns the status of the region's VLAN pool in CVX :returns: dictionary containg the assigned, allocated and available VLANs for the region """ if not self.cli_commands['resource-pool']: LOG.warning(_('The version of CVX you are using does not support' 'arista VLAN type driver.')) return None cmd = ['show openstack resource-pools region %s' % self.region] command_output = self._run_eos_cmds(cmd) if command_output: regions = command_output[0]['physicalNetwork'] if self.region in regions.keys(): return regions[self.region]['vlanPool']['default'] return {'assignedVlans': '', 'availableVlans': '', 'allocatedVlans': ''}
def delete_router(self, context, tenant_id, router_id, router): """Deletes a router from Arista Switch.""" if router: router_name = self._arista_router_name(tenant_id, router['name']) mlag_peer_failed = False for s in self._servers: try: self.delete_router_from_eos(router_name, s) mlag_peer_failed = False except Exception: if self._mlag_configured and not mlag_peer_failed: # In paied switch, it is OK to fail on one switch mlag_peer_failed = True else: msg = (_('Failed to create router %s on EOS') % router_name) LOG.exception(msg) raise arista_exc.AristaServicePluginRpcError(msg=msg)
def delete_router(self, context, router_id, router): """Deletes a router from Arista Switch.""" if router: router_name = self._arista_router_name(router_id, router['name']) mlag_peer_failed = False for s in self._servers: try: self.delete_router_from_eos(router_name, s) mlag_peer_failed = False except Exception: if self._mlag_configured and not mlag_peer_failed: # In paied switch, it is OK to fail on one switch mlag_peer_failed = True else: msg = (_('Failed to create router %s on EOS') % router_name) LOG.exception(msg) raise arista_exc.AristaServicePluginRpcError(msg=msg)
def get_vlan_allocation(self): """Returns the status of the region's VLAN pool in CVX :returns: dictionary containg the assigned, allocated and available VLANs for the region """ if not self.cli_commands['resource-pool']: LOG.warning(_('The version of CVX you are using does not support' 'arista VLAN type driver.')) else: cmd = ['show openstack resource-pools region %s' % self.region] command_output = self._run_eos_cmds(cmd) if command_output: regions = command_output[0]['physicalNetwork'] if self.region in regions.keys(): return regions[self.region]['vlanPool']['default'] return {'assignedVlans': '', 'availableVlans': '', 'allocatedVlans': ''}
def add_router_interface(self, context, router_info): """Adds an interface to a router created on Arista HW router. This deals with both IPv6 and IPv4 configurations. """ if router_info: self._select_dicts(router_info['ip_version']) cidr = router_info['cidr'] subnet_mask = cidr.split('/')[1] router_name = self._arista_router_name(router_info['tenant_id'], router_info['name']) if self._mlag_configured: # For MLAG, we send a specific IP address as opposed to cidr # For now, we are using x.x.x.253 and x.x.x.254 as virtual IP mlag_peer_failed = False for i, server in enumerate(self._servers): # Get appropriate virtual IP address for this router router_ip = self._get_router_ip(cidr, i, router_info['ip_version']) try: self.add_interface_to_router(router_info['seg_id'], router_name, router_info['gip'], router_ip, subnet_mask, server) mlag_peer_failed = False except Exception: if not mlag_peer_failed: mlag_peer_failed = True else: msg = (_('Failed to add interface to router ' '%s on EOS') % router_name) LOG.exception(msg) raise arista_exc.AristaServicePluginRpcError( msg=msg) else: for s in self._servers: self.add_interface_to_router(router_info['seg_id'], router_name, router_info['gip'], None, subnet_mask, s)
def add_router_interface(self, context, router_info): """Adds an interface to a router created on Arista HW router. This deals with both IPv6 and IPv4 configurations. """ if router_info: self._select_dicts(router_info['ip_version']) cidr = router_info['cidr'] subnet_mask = cidr.split('/')[1] router_name = self._arista_router_name(router_info['id'], router_info['name']) if self._mlag_configured: # For MLAG, we send a specific IP address as opposed to cidr # For now, we are using x.x.x.253 and x.x.x.254 as virtual IP mlag_peer_failed = False for i, server in enumerate(self._servers): # Get appropriate virtual IP address for this router router_ip = self._get_router_ip(cidr, i, router_info['ip_version']) try: self.add_interface_to_router(router_info['seg_id'], router_name, router_info['gip'], router_ip, subnet_mask, server) mlag_peer_failed = False except Exception: if not mlag_peer_failed: mlag_peer_failed = True else: msg = (_('Failed to add interface to router ' '%s on EOS') % router_name) LOG.exception(msg) raise arista_exc.AristaServicePluginRpcError( msg=msg) else: for s in self._servers: self.add_interface_to_router(router_info['seg_id'], router_name, router_info['gip'], None, subnet_mask, s)
def remove_router_interface(self, context, router_info): """Removes previously configured interface from router on Arista HW. This deals with both IPv6 and IPv4 configurations. """ if router_info: router_name = self._arista_router_name(router_info['id'], router_info['name']) mlag_peer_failed = False for s in self._servers: try: self.delete_interface_from_router(router_info['seg_id'], router_name, s) if self._mlag_configured: mlag_peer_failed = False except Exception: if self._mlag_configured and not mlag_peer_failed: mlag_peer_failed = True else: msg = (_('Failed to add interface to router ' '%s on EOS') % router_name) LOG.exception(msg) raise arista_exc.AristaServicePluginRpcError(msg=msg)
def remove_router_interface(self, context, router_info): """Removes previously configured interface from router on Arista HW. This deals with both IPv6 and IPv4 configurations. """ if router_info: router_name = self._arista_router_name(router_info['tenant_id'], router_info['name']) mlag_peer_failed = False for s in self._servers: try: self.delete_interface_from_router(router_info['seg_id'], router_name, s) if self._mlag_configured: mlag_peer_failed = False except Exception: if self._mlag_configured and not mlag_peer_failed: mlag_peer_failed = True else: msg = (_('Failed to add interface to router ' '%s on EOS') % router_name) LOG.exception(msg) raise arista_exc.AristaServicePluginRpcError(msg=msg)
def create_router(self, context, tenant_id, router): """Creates a router on Arista Switch. Deals with multiple configurations - such as Router per VRF, a router in default VRF, Virtual Router in MLAG configurations """ if router: router_name = self._arista_router_name(tenant_id, router['name']) rdm = str(int(hashlib.sha256(router_name).hexdigest(), 16) % 65536) mlag_peer_failed = False for s in self._servers: try: self.create_router_on_eos(router_name, rdm, s) mlag_peer_failed = False except Exception: if self._mlag_configured and not mlag_peer_failed: # In paied switch, it is OK to fail on one switch mlag_peer_failed = True else: msg = (_('Failed to create router %s on EOS') % router_name) LOG.exception(msg) raise arista_exc.AristaServicePluginRpcError(msg=msg)
from networking_arista._i18n import _ # Arista ML2 Mechanism driver specific configuration knobs. # # Following are user configurable options for Arista ML2 Mechanism # driver. The eapi_username, eapi_password, and eapi_host are # required options. Region Name must be the same that is used by # Keystone service. This option is available to support multiple # OpenStack/Neutron controllers. ARISTA_DRIVER_OPTS = [ cfg.StrOpt('eapi_username', default='', help=_('Username for Arista EOS. This is required field. ' 'If not set, all communications to Arista EOS ' 'will fail.')), cfg.StrOpt( 'eapi_password', default='', secret=True, # do not expose value in the logs help=_('Password for Arista EOS. This is required field. ' 'If not set, all communications to Arista EOS ' 'will fail.')), cfg.StrOpt('eapi_host', default='', help=_('Arista EOS IP address. This is required field. ' 'If not set, all communications to Arista EOS ' 'will fail.')), cfg.BoolOpt('use_fqdn', default=True,
ALL_RESOURCE_TYPES = [TENANT_RESOURCE, NETWORK_RESOURCE, SEGMENT_RESOURCE, DHCP_RESOURCE, ROUTER_RESOURCE, VM_RESOURCE, BAREMETAL_RESOURCE, DHCP_PORT_RESOURCE, VM_PORT_RESOURCE, BAREMETAL_PORT_RESOURCE, PORT_BINDING_RESOURCE] # Constants INTERNAL_TENANT_ID = 'INTERNAL-TENANT-ID' MECHANISM_DRV_NAME = 'arista' # SG Constants # When a SG is applied to a VM, ingress refers to traffic flowing # into a VM and egress refers to traffic flowing out. # In the baremetal case, traffic flowing out of a switchport is # flowing into the baremetal. Therefore, INGRESS SG rules # should be applied as 'out' ACLs and EGRESS rules as 'in' ACLs. INGRESS_DIRECTION = 'out' EGRESS_DIRECTION = 'in' # EAPI error messages of interest EOS_UNREACHABLE_MSG = _('Unable to reach EOS') ERR_CVX_NOT_LEADER = _('Only available on cluster leader')
def _send_eapi_req(self, cmds, commands_to_log=None): # This method handles all EAPI requests (using the requests library) # and returns either None or response.json()['result'] from the EAPI # request. # # Exceptions related to failures in connecting/ timeouts are caught # here and logged. Other unexpected exceptions are logged and raised request_headers = {} request_headers['Content-Type'] = 'application/json' request_headers['Accept'] = 'application/json' url = self._api_host_url(host=self._server_ip) params = {} params['timestamps'] = "false" params['format'] = "json" params['version'] = 1 params['cmds'] = cmds data = {} data['id'] = "Arista ML2 driver" data['method'] = "runCmds" data['jsonrpc'] = "2.0" data['params'] = params response = None try: # NOTE(pbourke): shallow copy data and params to remove sensitive # information before logging log_data = dict(data) log_data['params'] = dict(params) log_data['params']['cmds'] = commands_to_log or cmds msg = (_('EAPI request to %(ip)s contains %(cmd)s') % {'ip': self._server_ip, 'cmd': json.dumps(log_data)}) LOG.info(msg) response = requests.post(url, timeout=self.conn_timeout, verify=False, data=json.dumps(data)) LOG.info(_LI('EAPI response contains: %s'), response.json()) try: return response.json()['result'] except KeyError: if response.json()['error']['code'] == 1002: for data in response.json()['error']['data']: if type(data) == dict and 'errors' in data: if const.ERR_CVX_NOT_LEADER in data['errors'][0]: msg = six.text_type("%s is not the master" % ( self._server_ip)) LOG.info(msg) return None msg = "Unexpected EAPI error" LOG.info(msg) raise arista_exc.AristaRpcError(msg=msg) except requests.exceptions.ConnectionError: msg = (_('Error while trying to connect to %(ip)s') % {'ip': self._server_ip}) LOG.warning(msg) return None except requests.exceptions.ConnectTimeout: msg = (_('Timed out while trying to connect to %(ip)s') % {'ip': self._server_ip}) LOG.warning(msg) return None except requests.exceptions.Timeout: msg = (_('Timed out during an EAPI request to %(ip)s') % {'ip': self._server_ip}) LOG.warning(msg) return None except requests.exceptions.InvalidURL: msg = (_('Ignore attempt to connect to invalid URL %(ip)s') % {'ip': self._server_ip}) LOG.warning(msg) return None except ValueError: LOG.info("Ignoring invalid JSON response") return None except Exception as error: msg = six.text_type(error) LOG.warning(msg) raise
class AristaServicePluginConfigError(exceptions.NeutronException): message = _('%(msg)s')
class AristaConfigError(exceptions.NeutronException): message = _('%(msg)s')
class AristaSecurityGroupError(exceptions.NeutronException): message = _('%(msg)s')
from neutron.plugins.common import constants as p_const from neutron.plugins.ml2.common import exceptions as ml2_exc from neutron.plugins.ml2 import driver_api from networking_arista._i18n import _, _LI, _LE from networking_arista.common import config # noqa from networking_arista.common import db from networking_arista.common import db_lib from networking_arista.common import exceptions as arista_exc from networking_arista.ml2 import arista_ml2 from networking_arista.ml2 import sec_group_callback LOG = logging.getLogger(__name__) # Messages EOS_UNREACHABLE_MSG = _('Unable to reach EOS') UNABLE_TO_DELETE_PORT_MSG = _('Unable to delete port from EOS') UNABLE_TO_DELETE_DEVICE_MSG = _('Unable to delete device') # Constants INTERNAL_TENANT_ID = 'INTERNAL-TENANT-ID' PORT_BINDING_HOST = 'binding:host_id' MECHANISM_DRV_NAME = 'arista' def pretty_log(tag, obj): import json log_data = json.dumps(obj, sort_keys=True, indent=4) LOG.debug(tag) LOG.debug(log_data)
import hashlib import socket import struct from neutron_lib import constants as const from oslo_config import cfg from oslo_log import log as logging from networking_arista._i18n import _, _LI from networking_arista.common import api from networking_arista.common import exceptions as arista_exc LOG = logging.getLogger(__name__) cfg.CONF.import_group('l3_arista', 'networking_arista.common.config') EOS_UNREACHABLE_MSG = _('Unable to reach EOS') DEFAULT_VLAN = 1 MLAG_SWITCHES = 2 VIRTUAL_ROUTER_MAC = '00:11:22:33:44:55' IPV4_BITS = 32 IPV6_BITS = 128 # This string-format-at-a-distance confuses pylint :( # pylint: disable=too-many-format-args router_in_vrf = { 'router': {'create': ['vrf definition {0}', 'rd {1}', 'exit'], 'delete': ['no vrf definition {0}']}, 'interface': {'add': ['ip routing vrf {1}',
from networking_arista._i18n import _ # Arista ML2 Mechanism driver specific configuration knobs. # # Following are user configurable options for Arista ML2 Mechanism # driver. The eapi_username, eapi_password, and eapi_host are # required options. Region Name must be the same that is used by # Keystone service. This option is available to support multiple # OpenStack/Neutron controllers. ARISTA_DRIVER_OPTS = [ cfg.StrOpt('eapi_username', default='', help=_('Username for Arista EOS. This is required field. ' 'If not set, all communications to Arista EOS ' 'will fail.')), cfg.StrOpt('eapi_password', default='', secret=True, # do not expose value in the logs help=_('Password for Arista EOS. This is required field. ' 'If not set, all communications to Arista EOS ' 'will fail.')), cfg.StrOpt('eapi_host', default='', help=_('Arista EOS IP address. This is required field. ' 'If not set, all communications to Arista EOS ' 'will fail.')), cfg.BoolOpt('use_fqdn', default=True, help=_('Defines if hostnames are sent to Arista EOS as FQDNs '
# License for the specific language governing permissions and limitations # under the License. import json import jsonrpclib from oslo_config import cfg from oslo_log import log as logging from networking_arista._i18n import _, _LI from networking_arista.common import db_lib from networking_arista.common import exceptions as arista_exc LOG = logging.getLogger(__name__) EOS_UNREACHABLE_MSG = _('Unable to reach EOS') # Note 'None,null' means default rule - i.e. deny everything SUPPORTED_SG_PROTOCOLS = [None, 'tcp', 'udp', 'icmp'] acl_cmd = { 'acl': { 'create': ['ip access-list {0}'], 'in_rule': ['permit {0} {1} any range {2} {3}'], 'out_rule': ['permit {0} any {1} range {2} {3}'], 'in_icmp_custom1': ['permit icmp {0} any {1}'], 'out_icmp_custom1': ['permit icmp any {0} {1}'], 'in_icmp_custom2': ['permit icmp {0} any {1} {2}'], 'out_icmp_custom2': ['permit icmp any {0} {1} {2}'], 'default': [], 'delete_acl': ['no ip access-list {0}'],
def _send_request(self, host, path, method, data=None, sanitized_data=None): request_headers = { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Sync-ID': self.current_sync_name } url = self._api_host_url(host=host) + path # Don't log the password log_url = self._get_url(host=host, user=self._api_username(), password="******") + path resp = None data = json.dumps(data) try: msg = (_('JSON request type: %(type)s url %(url)s data: ' '%(data)s sync_id: %(sync)s') % {'type': method, 'url': log_url, 'data': sanitized_data or data, 'sync': self.current_sync_name}) LOG.info(msg) func_lookup = { 'GET': requests.get, 'POST': requests.post, 'PUT': requests.put, 'PATCH': requests.patch, 'DELETE': requests.delete } func = func_lookup.get(method) if not func: LOG.warning(_LW('Unrecognized HTTP method %s'), method) return None resp = func(url, timeout=self.conn_timeout, verify=False, data=data, headers=request_headers) msg = (_LI('JSON response contains: %(code)s %(resp)s') % {'code': resp.status_code, 'resp': resp.json()}) LOG.info(msg) if resp.ok: return resp.json() else: raise arista_exc.AristaRpcError(msg=resp.json().get('error')) except requests.exceptions.ConnectionError: msg = (_('Error connecting to %(url)s') % {'url': url}) LOG.warning(msg) except requests.exceptions.ConnectTimeout: msg = (_('Timed out connecting to API request to %(url)s') % {'url': url}) LOG.warning(msg) except requests.exceptions.Timeout: msg = (_('Timed out during API request to %(url)s') % {'url': url}) LOG.warning(msg) except requests.exceptions.InvalidURL: msg = (_('Ignore attempt to connect to invalid URL %(url)s') % {'url': self._server_ip}) LOG.warning(msg) except ValueError: LOG.warning(_LW("Ignoring invalid JSON response: %s"), resp.text) except Exception as error: msg = six.text_type(error) LOG.warning(msg) # reraise the exception with excutils.save_and_reraise_exception() as ctxt: ctxt.reraise = True return {} if method == 'GET' else None