示例#1
0
    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)
示例#2
0
    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)
示例#3
0
 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)
示例#4
0
 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)
示例#9
0
    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)
示例#10
0
    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)
示例#11
0
    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)
示例#12
0
    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)
示例#13
0
 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)
示例#14
0
    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)
示例#16
0
 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)
示例#17
0
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.")
示例#18
0
    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
示例#19
0
    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)
示例#21
0
    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)
示例#22
0
    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'])
示例#24
0
    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': ''}
示例#25
0
    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)
示例#27
0
    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': ''}
示例#28
0
    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)
示例#31
0
    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)
示例#32
0
    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)
示例#33
0
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,
示例#34
0
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')
示例#35
0
    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}',
示例#41
0
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 '
示例#42
0
#    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}'],
示例#43
0
    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
示例#44
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