Esempio n. 1
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)
Esempio n. 2
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)
    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 create_network_postcommit(self, context):
        """Provision the network on the Arista Hardware."""

        network = context.current
        network_id = network['id']
        network_name = network['name']
        tenant_id = network['tenant_id'] or INTERNAL_TENANT_ID
        segments = context.network_segments
        shared_net = network['shared']
        with self.eos_sync_lock:
            if db_lib.is_network_provisioned(tenant_id, network_id):
                try:
                    network_dict = {
                        'network_id': network_id,
                        'segments': segments,
                        'network_name': network_name,
                        'shared': shared_net
                    }
                    self.rpc.create_network(tenant_id, network_dict)
                except arista_exc.AristaRpcError as err:
                    LOG.error(
                        _LE("create_network_postcommit: Did not create "
                            "network %(name)s. Reason: %(err)s"), {
                                'name': network_name,
                                'err': err
                            })
            else:
                LOG.info(
                    _LI('Network %s is not created as it is not found in '
                        'Arista DB'), network_id)
Esempio n. 5
0
 def _sync_required(self):
     try:
         if not self._force_sync and self._region_in_sync():
             LOG.info(_LI('VLANs are in sync!'))
             return False
     except arista_exc.AristaRpcError:
         LOG.warning(EOS_UNREACHABLE_MSG)
         self._force_sync = True
     return True
 def _sync_required(self):
     try:
         if not self._force_sync and self._region_in_sync():
             LOG.info(_LI('VLANs are in sync!'))
             return False
     except arista_exc.AristaRpcError:
         LOG.warning(EOS_UNREACHABLE_MSG)
         self._force_sync = True
     return True
    def update_network_precommit(self, context):
        """At the moment we only support network name change

        Any other change in network is not supported at this time.
        We do not store the network names, therefore, no DB store
        action is performed here.
        """
        new_network = context.current
        orig_network = context.original
        if new_network['name'] != orig_network['name']:
            LOG.info(_LI('Network name changed to %s'), new_network['name'])
    def synchronize(self):
        LOG.info(_LI('Syncing VLANs with EOS'))
        try:
            self._rpc.check_vlan_type_driver_commands()
            vlan_pool = self._rpc.get_vlan_allocation()
        except arista_exc.AristaRpcError:
            LOG.warning(EOS_UNREACHABLE_MSG)
            self._force_sync = True
            return

        self._assigned_vlans = {
            'default': self._parse_vlan_ranges(vlan_pool['assignedVlans'],
                                               return_as_ranges=True),
        }

        assigned_vlans = (
            self._parse_vlan_ranges(vlan_pool['assignedVlans']))
        available_vlans = frozenset(
            self._parse_vlan_ranges(vlan_pool['availableVlans']))
        used_vlans = frozenset(
            self._parse_vlan_ranges(vlan_pool['allocatedVlans']))

        self._force_sync = False

        session = db_api.get_writer_session()
        with session.begin(subtransactions=True):
            allocs = (
                session.query(vlanallocation.VlanAllocation).with_lockmode(
                    'update'))

            for alloc in allocs:
                if alloc.physical_network != 'default':
                    session.delete(alloc)

                try:
                    assigned_vlans.remove(alloc.vlan_id)
                except KeyError:
                    session.delete(alloc)
                    continue

                if alloc.allocated and alloc.vlan_id in available_vlans:
                    alloc.update({"allocated": False})
                elif not alloc.allocated and alloc.vlan_id in used_vlans:
                    alloc.update({"allocated": True})

            for vlan_id in sorted(assigned_vlans):
                allocated = vlan_id in used_vlans
                alloc = vlanallocation.VlanAllocation(
                    physical_network='default',
                    vlan_id=vlan_id,
                    allocated=allocated)
                session.add(alloc)
    def _run_eos_cmds(self, commands, switch):
        """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 switch: 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 = switch.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': switch})
            LOG.exception(msg)
Esempio n. 10
0
    def synchronize(self):
        LOG.info(_LI('Syncing VLANs with EOS'))
        try:
            self._rpc.register_with_eos()
            vlan_pool = self._rpc.get_vlan_allocation()
        except arista_exc.AristaRpcError:
            LOG.warning(EOS_UNREACHABLE_MSG)
            self._force_sync = True
            return

        self._assigned_vlans = {
            'default':
            self._parse_vlan_ranges(vlan_pool['assignedVlans'],
                                    return_as_ranges=True),
        }

        assigned_vlans = (self._parse_vlan_ranges(vlan_pool['assignedVlans']))
        available_vlans = frozenset(
            self._parse_vlan_ranges(vlan_pool['availableVlans']))
        used_vlans = frozenset(
            self._parse_vlan_ranges(vlan_pool['allocatedVlans']))

        self._force_sync = False

        session = db_api.get_writer_session()
        with session.begin(subtransactions=True):
            allocs = (session.query(
                vlanallocation.VlanAllocation).with_lockmode('update'))

            for alloc in allocs:
                if alloc.physical_network != 'default':
                    session.delete(alloc)

                try:
                    assigned_vlans.remove(alloc.vlan_id)
                except KeyError:
                    session.delete(alloc)
                    continue

                if alloc.allocated and alloc.vlan_id in available_vlans:
                    alloc.update({"allocated": False})
                elif not alloc.allocated and alloc.vlan_id in used_vlans:
                    alloc.update({"allocated": True})

            for vlan_id in sorted(assigned_vlans):
                allocated = vlan_id in used_vlans
                alloc = vlanallocation.VlanAllocation(
                    physical_network='default',
                    vlan_id=vlan_id,
                    allocated=allocated)
                session.add(alloc)
Esempio n. 11
0
    def do_synchronize(self):
        """Periodically check whether EOS is in sync with ML2 driver.

           If ML2 database is not in sync with EOS, then compute the diff and
           send it down to EOS.
        """
        # Perform sync of Security Groups unconditionally
        try:
            self._rpc.perform_sync_of_sg()
        except Exception as e:
            LOG.warning(e)

        # Check whether CVX is available before starting the sync.
        if not self._rpc.check_cvx_availability():
            LOG.warning("Not syncing as CVX is unreachable")
            self.force_sync()
            return

        if not self._sync_required():
            return

        LOG.info('Attempting to sync')
        # Send 'sync start' marker.
        if not self._rpc.sync_start():
            LOG.info(_LI('Not starting sync, setting force'))
            self._force_sync = True
            return

        # Perform the actual synchronization.
        self.synchronize()

        # Send 'sync end' marker.
        if not self._rpc.sync_end():
            LOG.info(_LI('Sync end failed, setting force'))
            self._force_sync = True
            return

        self._set_region_updated_time()
Esempio n. 12
0
 def _sync_required(self):
     """"Check whether the sync is required."""
     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._region_in_sync():
             LOG.info(_LI('OpenStack and EOS are in sync!'))
             return False
     except arista_exc.AristaRpcError:
         LOG.warning(constants.EOS_UNREACHABLE_MSG)
         # Force an update incase of an error.
         self._force_sync = True
     return True
 def delete_network_precommit(self, context):
     """Delete the network information from the DB."""
     network = context.current
     network_id = network['id']
     tenant_id = network['tenant_id'] or INTERNAL_TENANT_ID
     with self.eos_sync_lock:
         if db_lib.is_network_provisioned(tenant_id, network_id):
             if db_lib.are_ports_attached_to_network(network_id):
                 LOG.info(
                     _LI('Network %s can not be deleted as it '
                         'has ports attached to it'), network_id)
                 raise ml2_exc.MechanismDriverError(
                     method='delete_network_precommit')
             else:
                 db_lib.forget_network_segment(tenant_id, network_id)
Esempio n. 14
0
    def unplug_port_from_network(self, device_id, device_owner, hostname,
                                 port_id, network_id, tenant_id, sg, vnic_type,
                                 switch_bindings=None, trunk_details=None):
        device_type = ''
        if device_owner == n_const.DEVICE_OWNER_DHCP:
            device_type = const.InstanceType.DHCP
        elif (device_owner.startswith('compute') or
              device_owner.startswith('baremetal') or
              device_owner.startswith('trunk')):
            if vnic_type == 'baremetal':
                device_type = const.InstanceType.BAREMETAL
            else:
                device_type = const.InstanceType.VM
        elif device_owner == n_const.DEVICE_OWNER_DVR_INTERFACE:
            device_type = const.InstanceType.ROUTER
        else:
            LOG.info(_LI('Unsupported device owner: %s'), device_owner)
            return

        if device_type in const.InstanceType.VIRTUAL_INSTANCE_TYPES:
            if trunk_details and trunk_details.get('sub_ports'):
                for subport in trunk_details['sub_ports']:
                    subport_id = subport['port_id']
                    subport_device_owner = 'trunk:subport'
                    self.unbind_port_from_host(subport_id, hostname)
                    self.delete_port(subport_id, device_id, device_type,
                                     subport_device_owner)
            self.unbind_port_from_host(port_id, hostname)
        elif device_type in const.InstanceType.BAREMETAL_INSTANCE_TYPES:
            if trunk_details and trunk_details.get('sub_ports'):
                for subport in trunk_details['sub_ports']:
                    subport_id = subport['port_id']
                    subport_device_owner = 'trunk:subport'
                    self.unbind_port_from_switch_interface(subport_id,
                                                           hostname,
                                                           switch_bindings)
                    self.delete_port(subport_id, device_id, device_type,
                                     subport_device_owner)
            self.unbind_port_from_switch_interface(port_id, hostname,
                                                   switch_bindings)
        self.delete_port(port_id, device_id, device_type, device_owner)
        port = self.get_instance_ports(device_id, device_type)
        if not port:
            # If the last port attached to an instance is deleted, cleanup the
            # instance.
            instances = [device_id]
            self.delete_instance_bulk(tenant_id, instances, device_type)
Esempio n. 15
0
    def synchronize(self):
        """Synchronizes Router DB from Neturon DB with EOS.

        Walks through the Neturon Db and ensures that all the routers
        created in Netuton DB match with EOS. After creating appropriate
        routers, it ensures to add interfaces as well.
        Uses idempotent properties of EOS configuration, which means
        same commands can be repeated.
        """
        LOG.info(_LI('Syncing Neutron Router DB <-> EOS'))
        ctx = nctx.get_admin_context()

        routers = super(AristaL3ServicePlugin, self).get_routers(ctx)
        for r in routers:
            tenant_id = r['tenant_id']
            ports = self.ndb.get_all_ports_for_tenant(tenant_id)

            try:
                self.driver.create_router(self, tenant_id, r)

            except Exception:
                continue

            # Figure out which interfaces are added to this router
            for p in ports:
                if p['device_id'] == r['id']:
                    net_id = p['network_id']
                    subnet_id = p['fixed_ips'][0]['subnet_id']
                    subnet = self.ndb.get_subnet_info(subnet_id)
                    ml2_db = NetworkContext(self, ctx, {'id': net_id})
                    seg_id = ml2_db.network_segments[0]['segmentation_id']

                    r['seg_id'] = seg_id
                    r['cidr'] = subnet['cidr']
                    r['gip'] = subnet['gateway_ip']
                    r['ip_version'] = subnet['ip_version']

                    try:
                        self.driver.add_router_interface(self, r)
                    except Exception:
                        LOG.error(
                            _LE("Error Adding interface %(subnet_id)s "
                                "to router %(router_id)s on Arista HW"), {
                                    'subnet_id': subnet_id,
                                    'router_id': r
                                })
Esempio n. 16
0
    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 commands_to_log : This should be set to the command that is
                                 logged. If it is None, then the commands
                                 param is logged.
        """

        # Always figure out who is master (starting with the last known val)
        try:
            if self._get_eos_master() is None:
                msg = "Failed to identify CVX master"
                self.set_cvx_unavailable()
                raise arista_exc.AristaRpcError(msg=msg)
        except Exception:
            self.set_cvx_unavailable()
            raise

        self.set_cvx_available()
        log_cmds = commands
        if commands_to_log:
            log_cmds = commands_to_log

        LOG.info(_LI('Executing command on Arista EOS: %s'), log_cmds)
        # this returns array of return values for every command in
        # full_command list
        try:
            response = self._send_eapi_req(cmds=commands,
                                           commands_to_log=log_cmds)
            if response is None:
                # Reset the server as we failed communicating with it
                self._server_ip = None
                self.set_cvx_unavailable()
                msg = "Failed to communicate with CVX master"
                raise arista_exc.AristaRpcError(msg=msg)
            return response
        except arista_exc.AristaRpcError:
            raise
Esempio n. 17
0
    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 commands_to_log : This should be set to the command that is
                                 logged. If it is None, then the commands
                                 param is logged.
        """

        # Always figure out who is master (starting with the last known val)
        try:
            if self._get_eos_master() is None:
                msg = "Failed to identify CVX master"
                self.set_cvx_unavailable()
                raise arista_exc.AristaRpcError(msg=msg)
        except Exception:
            self.set_cvx_unavailable()
            raise

        self.set_cvx_available()
        log_cmds = commands
        if commands_to_log:
            log_cmds = commands_to_log

        LOG.info(_LI('Executing command on Arista EOS: %s'), log_cmds)
        # this returns array of return values for every command in
        # full_command list
        try:
            response = self._send_eapi_req(cmds=commands,
                                           commands_to_log=log_cmds)
            if response is None:
                # Reset the server as we failed communicating with it
                self._server_ip = None
                self.set_cvx_unavailable()
                msg = "Failed to communicate with CVX master"
                raise arista_exc.AristaRpcError(msg=msg)
            return response
        except arista_exc.AristaRpcError:
            raise
Esempio n. 18
0
    def synchronize(self):
        """Synchronizes Router DB from Neturon DB with EOS.

        Walks through the Neturon Db and ensures that all the routers
        created in Netuton DB match with EOS. After creating appropriate
        routers, it ensures to add interfaces as well.
        Uses idempotent properties of EOS configuration, which means
        same commands can be repeated.
        """
        LOG.info(_LI('Syncing Neutron Router DB <-> EOS'))
        routers, router_interfaces = self.get_routers_and_interfaces()
        expected_vrfs = set()
        if self._use_vrf:
            expected_vrfs.update(self.driver._arista_router_name(
                r['id'], r['name']) for r in routers)
        expected_vlans = set(r['seg_id'] for r in router_interfaces)
        if self._enable_cleanup:
            self.do_cleanup(expected_vrfs, expected_vlans)
        self.create_routers(routers)
        self.create_router_interfaces(router_interfaces)
    def update_network_postcommit(self, context):
        """At the moment we only support network name change

        If network name is changed, a new network create request is
        sent to the Arista Hardware.
        """
        new_network = context.current
        orig_network = context.original
        if ((new_network['name'] != orig_network['name'])
                or (new_network['shared'] != orig_network['shared'])):
            network_id = new_network['id']
            network_name = new_network['name']
            tenant_id = new_network['tenant_id'] or INTERNAL_TENANT_ID
            shared_net = new_network['shared']
            with self.eos_sync_lock:
                if db_lib.is_network_provisioned(tenant_id, network_id):
                    try:
                        network_dict = {
                            'network_id': network_id,
                            'segments': context.network_segments,
                            'network_name': network_name,
                            'shared': shared_net
                        }
                        self.rpc.create_network(tenant_id, network_dict)
                    except arista_exc.AristaRpcError as err:
                        LOG.error(
                            _LE('update_network_postcommit: Did not '
                                'update network %(name)s. '
                                'Reason: %(err)s'), {
                                    'name': network_name,
                                    'err': err
                                })
                else:
                    LOG.info(
                        _LI('Network %s is not updated as it is not found'
                            ' in Arista DB'), network_id)
Esempio n. 20
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
Esempio n. 21
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
Esempio n. 22
0
 def initialize(self):
     self.rpc.check_supported_features()
     self.rpc.check_vlan_type_driver_commands()
     self._synchronization_thread()
     LOG.info(_LI("AristaVlanTypeDriver initialization complete"))
Esempio n. 23
0
    def execute(self, commands, commands_to_log=None):
        params = {
            'timestamps': False,
            'format': 'json',
            'version': 1,
            'cmds': commands
        }

        data = {
            'id': 'Networking Arista Driver',
            'method': 'runCmds',
            'jsonrpc': '2.0',
            'params': params
        }

        if commands_to_log:
            log_data = dict(data)
            log_data['params'] = dict(params)
            log_data['params']['cmds'] = commands_to_log
        else:
            log_data = data

        LOG.info(
            _LI('EAPI request %(ip)s contains %(data)s'),
            {'ip': self.host, 'data': json.dumps(log_data)}
        )

        # request handling
        try:
            error = None
            response = self.session.post(
                self.url,
                data=json.dumps(data),
                timeout=self.timeout
            )
        except requests_exc.ConnectionError:
            error = _LW('Error while trying to connect to %(ip)s')
        except requests_exc.ConnectTimeout:
            error = _LW('Timed out while trying to connect to %(ip)s')
        except requests_exc.Timeout:
            error = _LW('Timed out during an EAPI request to %(ip)s')
        except requests_exc.InvalidURL:
            error = _LW('Ingoring attempt to connect to invalid URL at %(ip)s')
        except Exception as e:
            with excutils.save_and_reraise_exception():
                LOG.warning(
                    _LW('Error during processing the EAPI request %(error)s'),
                    {'error': e}
                )
        finally:
            if error:
                msg = error % {'ip': self.host}
                # stop processing since we've encountered request error
                LOG.warning(msg)
                raise arista_exc.AristaRpcError(msg=msg)

        if response.status_code != requests.status_codes.codes.OK:
            msg = _LC(
                'Error (%(code)s - %(reason)s) while executing the command')
            LOG.error(msg, {
                'code': response.status_code,
                'reason': response.text})

        # response handling
        try:
            resp_data = response.json()
            return resp_data['result']
        except ValueError as e:
            LOG.info(_LI('Ignoring invalid JSON response'))
        except KeyError:
            if 'error' in resp_data and resp_data['error']['code'] == 1002:
                for d in resp_data['error']['data']:
                    if not isinstance(d, dict):
                        continue
                    elif ERR_CVX_NOT_LEADER in d.get('errors', {})[0]:
                        LOG.info(
                            _LI('%(ip)s is not the CVX leader'),
                            {'ip': self.host}
                        )
                        return
            msg = _LI('Unexpected EAPI error')
            LOG.info(msg)
            raise arista_exc.AristaRpcError(msg=msg)
        except Exception as e:
            with excutils.save_and_reraise_exception():
                LOG.warning(
                    _LW('Error during processing the EAPI response %(error)s'),
                    {'error': e}
                )
    def update_port_postcommit(self, context):
        """Update the name of a given port in EOS.

        At the moment we only support port name change
        Any other change to port is not supported at this time.
        """
        port = context.current
        orig_port = context.original

        device_id = port['device_id']
        device_owner = port['device_owner']
        host = context.host
        is_vm_boot = device_id and device_owner

        vnic_type = port['binding:vnic_type']
        binding_profile = port['binding:profile']
        bindings = []
        if binding_profile:
            bindings = binding_profile['local_link_information']

        port_id = port['id']
        port_name = port['name']
        network_id = port['network_id']
        tenant_id = port['tenant_id'] or INTERNAL_TENANT_ID
        # Ensure that we use tenant Id for the network owner
        tenant_id = self._network_owner_tenant(context, network_id, tenant_id)
        sg = port['security_groups']
        orig_sg = orig_port['security_groups']

        pretty_log("update_port_postcommit: new", port)
        pretty_log("update_port_postcommit: orig", orig_port)

        # Check if it is port migration case
        if self._handle_port_migration_postcommit(context):
            # Return from here as port migration is already handled.
            return

        seg_info = self._bound_segments(context)
        if not seg_info:
            LOG.debug("Ignoring the update as the port is not managed by "
                      "Arista switches.")
            return

        with self.eos_sync_lock:
            hostname = self._host_name(host)
            segmentation_id = seg_info[driver_api.SEGMENTATION_ID]
            port_host_filter = None
            if (port['device_owner'] == n_const.DEVICE_OWNER_DVR_INTERFACE):
                # <port, host> uniquely identifies a DVR port. Other
                # ports are identified by just the port id
                port_host_filter = host

            port_provisioned = db_lib.is_port_provisioned(
                port_id, port_host_filter)
            # If network does not exist under this tenant,
            # it may be a shared network. Get shared network owner Id
            net_provisioned = self._network_provisioned(
                tenant_id, network_id, segmentation_id=segmentation_id)
            segments = []
            if net_provisioned:
                if self.rpc.hpb_supported():
                    for binding_level in context.binding_levels:
                        bound_segment = binding_level.get(
                            driver_api.BOUND_SEGMENT)
                        if bound_segment:
                            segments.append(bound_segment)
                    all_segments = self.ndb.get_all_network_segments(
                        network_id, context=context._plugin_context)
                    try:
                        self.rpc.create_network_segments(
                            tenant_id, network_id,
                            context.network.current['name'], all_segments)
                    except arista_exc.AristaRpcError:
                        LOG.error(_LE("Failed to create network segments"))
                        raise ml2_exc.MechanismDriverError()
                else:
                    # For non HPB cases, the port is bound to the static
                    # segment
                    segments = self.ndb.get_network_segments(network_id)

            try:
                orig_host = context.original_host
                port_down = False
                if (port['device_owner'] == n_const.DEVICE_OWNER_DVR_INTERFACE
                    ):
                    # We care about port status only for DVR ports
                    port_down = context.status == n_const.PORT_STATUS_DOWN

                if orig_host and (port_down or host != orig_host):
                    try:
                        LOG.info("Deleting the port %s" % str(orig_port))
                        # The port moved to a different host or the VM
                        # connected to the port was deleted or its in DOWN
                        # state. So delete the old port on the old host.
                        self._delete_port(orig_port, orig_host, tenant_id)
                    except ml2_exc.MechanismDriverError:
                        # If deleting a port fails, then not much can be done
                        # about it. Log a warning and move on.
                        LOG.warning(UNABLE_TO_DELETE_PORT_MSG)
                if (port_provisioned and net_provisioned and hostname
                        and is_vm_boot and not port_down):
                    LOG.info(_LI("Port plugged into network"))
                    # Plug port into the network only if it exists in the db
                    # and is bound to a host and the port is up.
                    self.rpc.plug_port_into_network(device_id,
                                                    hostname,
                                                    port_id,
                                                    network_id,
                                                    tenant_id,
                                                    port_name,
                                                    device_owner,
                                                    sg,
                                                    orig_sg,
                                                    vnic_type,
                                                    segments=segments,
                                                    switch_bindings=bindings)
                else:
                    LOG.info(_LI("Port not plugged into network"))
            except arista_exc.AristaRpcError as err:
                LOG.error(
                    _LE('update_port_postcommit: Did not update '
                        'port %(port_id)s. Reason: %(err)s'), {
                            'port_id': port_id,
                            'err': err
                        })
    def update_port_precommit(self, context):
        """Update the name of a given port.

        At the moment we only support port name change.
        Any other change to port is not supported at this time.
        We do not store the port names, therefore, no DB store
        action is performed here.
        """
        new_port = context.current
        orig_port = context.original
        if new_port['name'] != orig_port['name']:
            LOG.info(_LI('Port name changed to %s'), new_port['name'])
        device_id = new_port['device_id']
        host = context.host

        pretty_log("update_port_precommit: new", new_port)
        pretty_log("update_port_precommit: orig", orig_port)

        if new_port['device_owner'] == 'compute:probe':
            return

        # Check if it is port migration case
        if self._handle_port_migration_precommit(context):
            return

        # Check if the port is part of managed physical network
        seg_info = self._bound_segments(context)
        if not seg_info:
            # Ignoring the update as the port is not managed by
            # arista mechanism driver.
            return

        # device_id and device_owner are set on VM boot
        port_id = new_port['id']
        network_id = new_port['network_id']
        tenant_id = new_port['tenant_id'] or INTERNAL_TENANT_ID
        # Ensure that we use tenant Id for the network owner
        tenant_id = self._network_owner_tenant(context, network_id, tenant_id)

        if not self._network_provisioned(tenant_id, network_id,
                                         seg_info[driver_api.SEGMENTATION_ID],
                                         seg_info[driver_api.ID]):
            if not self.rpc.hpb_supported():
                LOG.info(
                    _LI("Ignoring port %(port)s conntected to "
                        "%(net_id)s"), {
                            'port': port_id,
                            'net_id': network_id
                        })
                return

            LOG.info(_LI("Adding %s to provisioned network database"),
                     seg_info)
            with self.eos_sync_lock:
                db_lib.remember_tenant(tenant_id)
                db_lib.remember_network_segment(
                    tenant_id, network_id,
                    seg_info[driver_api.SEGMENTATION_ID],
                    seg_info[driver_api.ID])

        with self.eos_sync_lock:
            port_down = False
            if (new_port['device_owner'] == n_const.DEVICE_OWNER_DVR_INTERFACE
                ):
                # We care about port status only for DVR ports because
                # for DVR, a single port exists on multiple hosts. If a port
                # is no longer needed on a host then the driver gets a
                # port_update notification for that <port, host> with the
                # port status as PORT_STATUS_DOWN.
                port_down = context.status == n_const.PORT_STATUS_DOWN

            if host and not port_down:
                port_host_filter = None
                if (new_port['device_owner'] ==
                        n_const.DEVICE_OWNER_DVR_INTERFACE):
                    # <port, host> uniquely identifies a DVR port. Other
                    # ports are identified by just the port id
                    port_host_filter = host

                port_provisioned = db_lib.is_port_provisioned(
                    port_id, port_host_filter)

                if not port_provisioned:
                    LOG.info("Remembering the port")
                    # Create a new port in the DB
                    db_lib.remember_tenant(tenant_id)
                    db_lib.remember_vm(device_id, host, port_id, network_id,
                                       tenant_id)
                else:
                    if (new_port['device_id'] != orig_port['device_id']
                            or context.host != context.original_host or
                            new_port['network_id'] != orig_port['network_id']
                            or
                            new_port['tenant_id'] != orig_port['tenant_id']):
                        LOG.info("Updating the port")
                        # Port exists in the DB. Update it
                        db_lib.update_port(device_id, host, port_id,
                                           network_id, tenant_id)
            else:  # Unbound or down port does not concern us
                orig_host = context.original_host
                LOG.info("Forgetting the port on %s" % str(orig_host))
                db_lib.forget_port(port_id, orig_host)
Esempio n. 26
0
    def plug_port_into_network(self, device_id, host_id, port_id,
                               net_id, tenant_id, port_name, device_owner,
                               sg, orig_sg, vnic_type, segments,
                               switch_bindings=None, trunk_details=None):
        device_type = ''
        if device_owner == n_const.DEVICE_OWNER_DHCP:
            device_type = const.InstanceType.DHCP
        elif (device_owner.startswith('compute')
              or device_owner.startswith('baremetal')
              or device_owner.startswith('trunk')):
            if vnic_type == 'baremetal':
                device_type = const.InstanceType.BAREMETAL
            else:
                device_type = const.InstanceType.VM
        elif device_owner == n_const.DEVICE_OWNER_DVR_INTERFACE:
            device_type = const.InstanceType.ROUTER
        else:
            LOG.info(_LI('Unsupported device owner: %s'), device_owner)
            return

        self._create_tenant_if_needed(tenant_id)
        instance = self._create_instance_data(device_id, host_id)
        port = self._create_port_data(port_id, tenant_id, net_id, device_id,
                                      port_name, device_type, [host_id],
                                      device_owner)
        url = 'region/%(region)s/%(device_type)s?tenantId=%(tenant_id)s' % {
              'region': self.region,
              'device_type': device_type,
              'tenant_id': tenant_id,
        }
        self._send_api_request(url, 'POST', [instance])
        self._send_api_request('region/' + self.region + '/port', 'POST',
                               [port])
        if trunk_details and trunk_details.get('sub_ports'):
            for subport in trunk_details['sub_ports']:
                subport_id = subport['port_id']
                subport_net_id = self._ndb.get_network_id_from_port_id(
                    subport_id)
                subport_name = 'name_%s' % subport_id
                sub_device_owner = 'trunk:subport'
                port = self._create_port_data(subport_id, tenant_id,
                                              subport_net_id, device_id,
                                              subport_name, device_type,
                                              [host_id], sub_device_owner)

                self._send_api_request('region/' + self.region + '/port',
                                       'POST', [port])
        if device_type in const.InstanceType.VIRTUAL_INSTANCE_TYPES:
            self.bind_port_to_host(port_id, host_id, net_id, segments)
            if trunk_details and trunk_details.get('sub_ports'):
                for subport in trunk_details['sub_ports']:
                    subport_id = subport['port_id']
                    subport_net_id = self._ndb.get_network_id_from_port_id(
                        subport_id)
                    sub_segments = db_lib.get_network_segments_by_port_id(
                        subport_id)
                    self.bind_port_to_host(subport_id, host_id,
                                           subport_net_id, sub_segments)
        elif device_type in const.InstanceType.BAREMETAL_INSTANCE_TYPES:
            self.bind_port_to_switch_interface(port_id, host_id, net_id,
                                               switch_bindings, segments)
            if trunk_details and trunk_details.get('sub_ports'):
                for subport in trunk_details['sub_ports']:
                    subport_id = subport['port_id']
                    subport_net_id = self._ndb.get_network_id_from_port_id(
                        subport_id)
                    sub_segments = db_lib.get_network_segments_by_port_id(
                        subport_id)
                    self.bind_port_to_switch_interface(subport_id, host_id,
                                                       subport_net_id,
                                                       switch_bindings,
                                                       sub_segments)
            if sg:
                self.apply_security_group(sg, switch_bindings)
            else:
                # Security group was removed. Clean up the existing security
                # groups.
                if orig_sg:
                    self.remove_security_group(orig_sg, switch_bindings)
Esempio n. 27
0
    def update_port_postcommit(self, context):
        """Update the name of a given port in EOS.

        At the moment we only support port name change
        Any other change to port is not supported at this time.
        """
        port = context.current
        orig_port = context.original

        device_id = port['device_id']
        device_owner = port['device_owner']
        host = context.host
        is_vm_boot = device_id and device_owner

        # When delete a vm, the trunk port context has no device_owner
        # Keep device_owner as in original port
        if not device_owner and orig_port.get('trunk_details'):
            device_owner = orig_port['device_owner']

        if not utils.supported_device_owner(device_owner):
            return

        vnic_type = port['binding:vnic_type']
        binding_profile = port['binding:profile']
        bindings = []
        if binding_profile:
            bindings = binding_profile.get('local_link_information', [])

        port_id = port['id']
        port_name = port['name']
        network_id = port['network_id']
        tenant_id = port['tenant_id'] or constants.INTERNAL_TENANT_ID
        # Ensure that we use tenant Id for the network owner
        tenant_id = self._network_owner_tenant(context, network_id, tenant_id)
        sg = port['security_groups']
        orig_sg = orig_port['security_groups']

        pretty_log("update_port_postcommit: new", port)
        pretty_log("update_port_postcommit: orig", orig_port)

        # Check if it is port migration case
        if self._handle_port_migration_postcommit(context):
            # Return from here as port migration is already handled.
            return

        # Check if it is trunk_port deletion case
        seg_info = []
        if not port.get('trunk_details') or host:
            seg_info = self._bound_segments(context)
            if not seg_info:
                LOG.debug("Ignoring the update as the port is not managed by "
                          "Arista switches.")
                return

        with self.eos_sync_lock:
            hostname = self._host_name(host)
            try:
                orig_host = context.original_host
                port_down = False
                if (port['device_owner'] == n_const.DEVICE_OWNER_DVR_INTERFACE
                        or port.get('trunk_details')):
                    # We care about port status only for DVR ports and
                    # trunk ports
                    port_down = context.status == n_const.PORT_STATUS_DOWN

                if orig_host and (port_down or host != orig_host or device_id
                                  == n_const.DEVICE_ID_RESERVED_DHCP_PORT):
                    LOG.info("Deleting the port %s" % str(orig_port))
                    # The port moved to a different host or the VM
                    # connected to the port was deleted or its in DOWN
                    # state. So delete the old port on the old host.
                    self._delete_port(orig_port, orig_host, tenant_id)
                if (hostname and is_vm_boot and not port_down
                        and device_id != n_const.DEVICE_ID_RESERVED_DHCP_PORT):
                    segments = seg_info
                    all_segments = self.ndb.get_all_network_segments(
                        network_id, context=context._plugin_context)
                    try:
                        self.rpc.create_network_segments(
                            tenant_id, network_id,
                            context.network.current['name'], all_segments)
                    except arista_exc.AristaRpcError:
                        with excutils.save_and_reraise_exception():
                            LOG.error(_LE("Failed to create network segments"))
                    LOG.info(_LI("Port plugged into network"))
                    # Plug port into the network only if it exists in the db
                    # and is bound to a host and the port is up.
                    trunk_details = port.get('trunk_details')
                    self.rpc.plug_port_into_network(
                        device_id,
                        hostname,
                        port_id,
                        network_id,
                        tenant_id,
                        port_name,
                        device_owner,
                        sg,
                        orig_sg,
                        vnic_type,
                        segments=segments,
                        switch_bindings=bindings,
                        trunk_details=trunk_details)
                else:
                    LOG.info(_LI("Port not plugged into network"))
            except arista_exc.AristaRpcError as err:
                LOG.error(
                    _LE('update_port_postcommit: Did not update '
                        'port %(port_id)s. Reason: %(err)s'), {
                            'port_id': port_id,
                            'err': err
                        })
Esempio n. 28
0
    def synchronize(self):
        """Sends data to EOS which differs from neutron DB."""

        LOG.info(_LI('Syncing Neutron <-> EOS'))
        try:
            # Register with EOS to ensure that it has correct credentials
            self._rpc.register_with_eos(sync=True)
            eos_tenants = self._rpc.get_tenants()
        except arista_exc.AristaRpcError:
            LOG.warning(constants.EOS_UNREACHABLE_MSG)
            self._force_sync = True
            return

        db_tenants = db_lib.get_tenants()

        # Delete tenants that are in EOS, but not in the database
        tenants_to_delete = frozenset(eos_tenants.keys()).difference(
            db_tenants)

        if tenants_to_delete:
            try:
                self._rpc.delete_tenant_bulk(tenants_to_delete, sync=True)
            except arista_exc.AristaRpcError:
                LOG.warning(constants.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

        # Get Baremetal port switch_bindings, if any
        port_profiles = db_lib.get_all_portbindings()
        # To support shared networks, split the sync loop in two parts:
        # In first loop, delete unwanted VM and networks and update networks
        # In second loop, update VMs. This is done to ensure that networks for
        # all tenats are updated before VMs are updated
        instances_to_update = {}
        for tenant in db_tenants:
            db_nets = {n['id']: n
                       for n in self._ndb.get_all_networks_for_tenant(tenant)}
            db_instances = db_lib.get_instances(tenant)

            eos_nets = self._get_eos_networks(eos_tenants, tenant)
            eos_vms, eos_bms, eos_routers = self._get_eos_vms(eos_tenants,
                                                              tenant)

            db_nets_key_set = frozenset(db_nets.keys())
            db_instances_key_set = frozenset(db_instances)
            eos_nets_key_set = frozenset(eos_nets.keys())
            eos_vms_key_set = frozenset(eos_vms.keys())
            eos_routers_key_set = frozenset(eos_routers.keys())
            eos_bms_key_set = frozenset(eos_bms.keys())

            # Create a candidate list by incorporating all instances
            eos_instances_key_set = (eos_vms_key_set | eos_routers_key_set |
                                     eos_bms_key_set)

            # 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)

            # Find the VMs that are present on EOS, but not in Neutron DB
            instances_to_delete = eos_instances_key_set.difference(
                db_instances_key_set)

            vms_to_delete = [
                vm for vm in eos_vms_key_set if vm in instances_to_delete]
            routers_to_delete = [
                r for r in eos_routers_key_set if r in instances_to_delete]
            bms_to_delete = [
                b for b in eos_bms_key_set if b in instances_to_delete]

            # 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)

            # Find the VMs that are present in Neutron DB, but not on EOS
            instances_to_update[tenant] = db_instances_key_set.difference(
                eos_instances_key_set)

            try:
                if vms_to_delete:
                    self._rpc.delete_vm_bulk(tenant, vms_to_delete, sync=True)
                if routers_to_delete:
                    self._rpc.delete_instance_bulk(
                        tenant,
                        routers_to_delete,
                        constants.InstanceType.ROUTER,
                        sync=True)
                if bms_to_delete:
                    self._rpc.delete_instance_bulk(
                        tenant,
                        bms_to_delete,
                        constants.InstanceType.BAREMETAL,
                        sync=True)
                if nets_to_delete:
                    self._rpc.delete_network_bulk(tenant, nets_to_delete,
                                                  sync=True)
                if nets_to_update:
                    networks = [{
                        'network_id': net_id,
                        'network_name':
                            db_nets.get(net_id, {'name': ''})['name'],
                        'shared':
                            db_nets.get(net_id,
                                        {'shared': False})['shared'],
                        'segments': self._ndb.get_all_network_segments(net_id),
                        }
                        for net_id in nets_to_update
                    ]
                    self._rpc.create_network_bulk(tenant, networks, sync=True)
            except arista_exc.AristaRpcError:
                LOG.warning(constants.EOS_UNREACHABLE_MSG)
                self._force_sync = True

        # Now update the instances
        for tenant in instances_to_update:
            if not instances_to_update[tenant]:
                continue
            try:
                # Filter the ports to only the vms that we are interested
                # in.
                ports_of_interest = {}
                for port in self._ndb.get_all_ports_for_tenant(tenant):
                    ports_of_interest.update(
                        self._port_dict_representation(port))

                if ports_of_interest:
                    instance_ports = db_lib.get_instance_ports(
                        tenant, self._manage_fabric, self._managed_physnets)
                    if instance_ports:
                        self._rpc.create_instance_bulk(tenant,
                                                       ports_of_interest,
                                                       instance_ports,
                                                       port_profiles,
                                                       sync=True)
            except arista_exc.AristaRpcError:
                LOG.warning(constants.EOS_UNREACHABLE_MSG)
                self._force_sync = True
Esempio n. 29
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
Esempio n. 30
0
    def execute(self, commands, commands_to_log=None):
        params = {
            'timestamps': False,
            'format': 'json',
            'version': 1,
            'cmds': commands
        }

        data = {
            'id': 'Networking Arista Driver',
            'method': 'runCmds',
            'jsonrpc': '2.0',
            'params': params
        }

        if commands_to_log:
            log_data = dict(data)
            log_data['params'] = dict(params)
            log_data['params']['cmds'] = commands_to_log
        else:
            log_data = data

        LOG.info(
            _LI('EAPI request %(ip)s contains %(data)s'),
            {'ip': self.host, 'data': json.dumps(log_data)}
        )

        # request handling
        try:
            error = None
            response = self.session.post(
                self.url,
                data=json.dumps(data),
                timeout=self.timeout
            )
        except requests_exc.ConnectionError:
            error = _LW('Error while trying to connect to %(ip)s')
        except requests_exc.ConnectTimeout:
            error = _LW('Timed out while trying to connect to %(ip)s')
        except requests_exc.Timeout:
            error = _LW('Timed out during an EAPI request to %(ip)s')
        except requests_exc.InvalidURL:
            error = _LW('Ingoring attempt to connect to invalid URL at %(ip)s')
        except Exception as e:
            with excutils.save_and_reraise_exception():
                LOG.warning(
                    _LW('Error during processing the EAPI request %(error)s'),
                    {'error': e}
                )
        finally:
            if error:
                msg = error % {'ip': self.host}
                # stop processing since we've encountered request error
                LOG.warning(msg)
                raise arista_exc.AristaRpcError(msg=msg)

        if response.status_code != requests.status_codes.codes.OK:
            msg = _LC(
                'Error (%(code)s - %(reason)s) while executing the command')
            LOG.error(msg, {
                'code': response.status_code,
                'reason': response.text})

        # response handling
        try:
            resp_data = response.json()
            return resp_data['result']
        except ValueError as e:
            LOG.info(_LI('Ignoring invalid JSON response'))
        except KeyError:
            if 'error' in resp_data:
                for i, d in enumerate(resp_data['error']['data'], 1):
                    if not isinstance(d, dict):
                        continue
                    if 'messages' in d:
                        LOG.info(
                            _LI('Command %(cmd)s returned message %(msg)s'),
                            {'cmd': i, 'msg': d['messages']})
                    if 'errors' in d:
                        LOG.info(
                            _LI('Command %(cmd)s returned error %(err)s'),
                            {'cmd': i, 'err': d['errors']})
                        if ERR_CVX_NOT_LEADER in d['errors'][0]:
                            LOG.info(_LI('%(ip)s is not the CVX leader'),
                                     {'ip': self.host})
                            return
            msg = ('Unexpected EAPI error: %s' %
                   resp_data.get('error', {}).get('message', 'Unknown Error'))
            LOG.info(msg)
            raise arista_exc.AristaRpcError(msg=msg)
        except Exception as e:
            with excutils.save_and_reraise_exception():
                LOG.warning(
                    _LW('Error during processing the EAPI response %(error)s'),
                    {'error': e}
                )
 def initialize(self):
     self.rpc.check_vlan_type_driver_commands()
     self._synchronization_thread()
     LOG.info(_LI("AristaVlanTypeDriver initialization complete"))
Esempio n. 32
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