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. """ if self._server is None: self._server_ip = self._get_eos_master() if self._server_ip is None or self._server is None: msg = "Failed to identify EOS master" raise arista_exc.AristaRpcError(msg=msg) log_cmds = commands if commands_to_log: log_cmds = commands_to_log LOG.info(_LI('Executing command on Arista EOS: %s'), log_cmds) try: # this returns array of return values for every command in # full_command list ret = self._server.runCmds(version=1, cmds=commands) except Exception as error: error_msg_str = unicode(error) if commands_to_log: # The command might contain sensitive information. If the # command to log is different from the actual command, use # that in the error message. for cmd, log_cmd in itertools.izip(commands, log_cmds): error_msg_str = error_msg_str.replace(cmd, log_cmd) msg = (_('Error %(err)s while trying to execute ' 'commands %(cmd)s on EOS %(host)s') % {'err': error_msg_str, 'cmd': commands_to_log, 'host': self._server_ip}) # Reset the server as we failed communicating with it; # there might just be another master self._server = None self._server_ip = None # Logging exception here can reveal passwords as the exception # contains the CLI command which contains the credentials. LOG.error(msg) raise arista_exc.AristaRpcError(msg=msg) return ret
def _send_api_request(self, path, method, data=None, sanitized_data=None): host = self._get_eos_master() if not host: msg = six.text_type("Could not find CVX leader") LOG.info(msg) self.set_cvx_unavailable() raise arista_exc.AristaRpcError(msg=msg) self.set_cvx_available() return self._send_request(host, path, method, data, sanitized_data)
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
def __init__(self, rpc=None): self.ndb = db_lib.NeutronNets() self.db_nets = db.AristaProvisionedNets() self.db_vms = db.AristaProvisionedVms() self.db_tenants = db.AristaProvisionedTenants() confg = cfg.CONF.ml2_arista self.segmentation_type = db_lib.VLAN_SEGMENTATION self.timer = None self.sync_timeout = confg['sync_interval'] self.managed_physnets = confg['managed_physnets'] self.eos_sync_lock = threading.Lock() self.eapi = None if rpc: LOG.info("Using passed in parameter for RPC") self.rpc = rpc self.eapi = rpc else: self.eapi = arista_ml2.AristaRPCWrapperEapi(self.ndb) api_type = confg['api_type'].upper() if api_type == 'EAPI': LOG.info("Using EAPI for RPC") self.rpc = arista_ml2.AristaRPCWrapperEapi(self.ndb) elif api_type == 'JSON': LOG.info("Using JSON for RPC") self.rpc = arista_ml2.AristaRPCWrapperJSON(self.ndb) else: msg = "RPC mechanism %s not recognized" % api_type LOG.error(msg) raise arista_exc.AristaRpcError(msg=msg) self.sync_service = arista_ml2.SyncService(self.rpc, self.ndb) self.rpc.sync_service = self.sync_service
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
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 _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