def _command(self, node, method, params, wait=False): url = self._get_command_url(node) body = self._get_command_body(method, params) request_params = {'wait': str(wait).lower()} LOG.debug('Executing agent command %(method)s for node %(node)s', { 'node': node.uuid, 'method': method }) try: response = self.session.post(url, params=request_params, data=body) except requests.RequestException as e: msg = (_('Error invoking agent command %(method)s for node ' '%(node)s. Error: %(error)s') % { 'method': method, 'node': node.uuid, 'error': e }) LOG.error(msg) raise exception.IronicException(msg) # TODO(russellhaering): real error handling try: result = response.json() except ValueError: msg = _('Unable to decode response as JSON.\n' 'Request URL: %(url)s\nRequest body: "%(body)s"\n' 'Response status code: %(code)s\n' 'Response: "%(response)s"') % ({ 'response': response.text, 'body': body, 'url': url, 'code': response.status_code }) LOG.error(msg) raise exception.IronicException(msg) LOG.debug( 'Agent command %(method)s for node %(node)s returned ' 'result %(res)s, error %(error)s, HTTP status code %(code)d', { 'node': node.uuid, 'method': method, 'res': result.get('command_result'), 'error': result.get('command_error'), 'code': response.status_code }) if response.status_code >= http_client.BAD_REQUEST: LOG.error( 'Agent command %(method)s for node %(node)s failed. ' 'Expected 2xx HTTP status code, got %(code)d.', { 'method': method, 'node': node.uuid, 'code': response.status_code }) raise exception.AgentAPIError(node=node.uuid, status=response.status_code, error=result.get('faultstring')) return result
def _raise_if_typeerror(self, result, node, method): error = result.get('command_error') if error and error.get('type') == 'TypeError': LOG.error( 'Agent command %(method)s for node %(node)s failed. ' 'Internal TypeError detected: Error %(error)s', { 'method': method, 'node': node.uuid, 'error': error }) raise exception.AgentAPIError(node=node.uuid, status=error.get('code'), error=get_command_error(result))
def test_finalize_rescue_fallback_restricted(self): self.config(require_rescue_password_hashed=True, group="conductor") self.client._command = mock.MagicMock(spec_set=[]) self.node.instance_info['rescue_password'] = '******' self.node.instance_info['hashed_rescue_password'] = '******' self.client._command.side_effect = exception.AgentAPIError('blah') self.assertRaises(exception.InstanceRescueFailure, self.client.finalize_rescue, self.node) self.client._command.assert_has_calls([ mock.call(node=mock.ANY, method='rescue.finalize_rescue', params={ 'rescue_password': '******', 'hashed': True }) ])
def _command(self, node, method, params, wait=False): """Sends command to agent. :param node: A Node object. :param method: A string represents the command to be executed by agent. :param params: A dictionary containing params used to form the request body. :param wait: True to wait for the command to finish executing, False otherwise. :raises: IronicException when failed to issue the request or there was a malformed response from the agent. :raises: AgentAPIError when agent failed to execute specified command. :returns: A dict containing command result from agent, see get_commands_status for a sample. """ url = self._get_command_url(node) body = self._get_command_body(method, params) request_params = {'wait': str(wait).lower()} LOG.debug('Executing agent command %(method)s for node %(node)s', { 'node': node.uuid, 'method': method }) try: response = self.session.post(url, params=request_params, data=body, timeout=CONF.agent.command_timeout) except (requests.ConnectionError, requests.Timeout) as e: msg = (_('Failed to connect to the agent running on node %(node)s ' 'for invoking command %(method)s. Error: %(error)s') % { 'node': node.uuid, 'method': method, 'error': e }) LOG.error(msg) raise exception.AgentConnectionFailed(reason=msg) except requests.RequestException as e: msg = (_('Error invoking agent command %(method)s for node ' '%(node)s. Error: %(error)s') % { 'method': method, 'node': node.uuid, 'error': e }) LOG.error(msg) raise exception.IronicException(msg) # TODO(russellhaering): real error handling try: result = response.json() except ValueError: msg = _('Unable to decode response as JSON.\n' 'Request URL: %(url)s\nRequest body: "%(body)s"\n' 'Response status code: %(code)s\n' 'Response: "%(response)s"') % ({ 'response': response.text, 'body': body, 'url': url, 'code': response.status_code }) LOG.error(msg) raise exception.IronicException(msg) LOG.debug( 'Agent command %(method)s for node %(node)s returned ' 'result %(res)s, error %(error)s, HTTP status code %(code)d', { 'node': node.uuid, 'method': method, 'res': result.get('command_result'), 'error': result.get('command_error'), 'code': response.status_code }) if response.status_code >= http_client.BAD_REQUEST: LOG.error( 'Agent command %(method)s for node %(node)s failed. ' 'Expected 2xx HTTP status code, got %(code)d.', { 'method': method, 'node': node.uuid, 'code': response.status_code }) raise exception.AgentAPIError(node=node.uuid, status=response.status_code, error=result.get('faultstring')) return result
def _command(self, node, method, params, wait=False, poll=False): """Sends command to agent. :param node: A Node object. :param method: A string represents the command to be executed by agent. :param params: A dictionary containing params used to form the request body. :param wait: True to wait for the command to finish executing, False otherwise. :param poll: Whether to poll the command until completion. Provides a better alternative to `wait` for long-running commands. :raises: IronicException when failed to issue the request or there was a malformed response from the agent. :raises: AgentAPIError when agent failed to execute specified command. :returns: A dict containing command result from agent, see get_commands_status for a sample. """ assert not (wait and poll) url = self._get_command_url(node) body = self._get_command_body(method, params) request_params = {'wait': str(wait).lower()} agent_token = node.driver_internal_info.get('agent_secret_token') if agent_token: request_params['agent_token'] = agent_token LOG.debug('Executing agent command %(method)s for node %(node)s', { 'node': node.uuid, 'method': method }) try: response = self.session.post(url, params=request_params, data=body, verify=self._get_verify(node), timeout=CONF.agent.command_timeout) except (requests.ConnectionError, requests.Timeout) as e: msg = (_('Failed to connect to the agent running on node %(node)s ' 'for invoking command %(method)s. Error: %(error)s') % { 'node': node.uuid, 'method': method, 'error': e }) LOG.error(msg) raise exception.AgentConnectionFailed(reason=msg) except requests.RequestException as e: msg = (_('Error invoking agent command %(method)s for node ' '%(node)s. Error: %(error)s') % { 'method': method, 'node': node.uuid, 'error': e }) LOG.error(msg) raise exception.IronicException(msg) # TODO(russellhaering): real error handling try: result = response.json() except ValueError: msg = _('Unable to decode response as JSON.\n' 'Request URL: %(url)s\nRequest body: "%(body)s"\n' 'Response status code: %(code)s\n' 'Response: "%(response)s"') % ({ 'response': response.text, 'body': body, 'url': url, 'code': response.status_code }) LOG.error(msg) raise exception.IronicException(msg) error = result.get('command_error') LOG.debug( 'Agent command %(method)s for node %(node)s returned ' 'result %(res)s, error %(error)s, HTTP status code %(code)d', { 'node': node.uuid, 'method': method, 'res': result.get('command_result'), 'error': error, 'code': response.status_code }) if response.status_code >= http_client.BAD_REQUEST: faultstring = result.get('faultstring') if 'agent_token' in faultstring: LOG.error( 'Agent command %(method)s for node %(node)s ' 'failed. Expected 2xx HTTP status code, got ' '%(code)d. Error suggests an older ramdisk ' 'which does not support ``agent_token``. ' 'This is a fatal error.', { 'method': method, 'node': node.uuid, 'code': response.status_code }) else: LOG.error( 'Agent command %(method)s for node %(node)s failed. ' 'Expected 2xx HTTP status code, got %(code)d.', { 'method': method, 'node': node.uuid, 'code': response.status_code }) raise exception.AgentAPIError(node=node.uuid, status=response.status_code, error=faultstring) self._raise_if_typeerror(result, node, method) if poll: result = self._wait_for_command(node, method) return result
def _command(self, node, method, params, wait=False, command_timeout_factor=1): """Sends command to agent. :param node: A Node object. :param method: A string represents the command to be executed by agent. :param params: A dictionary containing params used to form the request body. :param wait: True to wait for the command to finish executing, False otherwise. :param command_timeout_factor: An integer, default 1, by which to multiply the [agent]command_timeout value. This is intended for use with extremely long running commands to the agent ramdisk where a general timeout value should not be extended in all cases. :raises: IronicException when failed to issue the request or there was a malformed response from the agent. :raises: AgentAPIError when agent failed to execute specified command. :returns: A dict containing command result from agent, see get_commands_status for a sample. """ url = self._get_command_url(node) body = self._get_command_body(method, params) request_params = { 'wait': str(wait).lower() } agent_token = node.driver_internal_info.get('agent_secret_token') if agent_token: request_params['agent_token'] = agent_token LOG.debug('Executing agent command %(method)s for node %(node)s', {'node': node.uuid, 'method': method}) try: response = self.session.post( url, params=request_params, data=body, timeout=CONF.agent.command_timeout * command_timeout_factor) except (requests.ConnectionError, requests.Timeout) as e: msg = (_('Failed to connect to the agent running on node %(node)s ' 'for invoking command %(method)s. Error: %(error)s') % {'node': node.uuid, 'method': method, 'error': e}) LOG.error(msg) raise exception.AgentConnectionFailed(reason=msg) except requests.RequestException as e: msg = (_('Error invoking agent command %(method)s for node ' '%(node)s. Error: %(error)s') % {'method': method, 'node': node.uuid, 'error': e}) LOG.error(msg) raise exception.IronicException(msg) # TODO(russellhaering): real error handling try: result = response.json() except ValueError: msg = _( 'Unable to decode response as JSON.\n' 'Request URL: %(url)s\nRequest body: "%(body)s"\n' 'Response status code: %(code)s\n' 'Response: "%(response)s"' ) % ({'response': response.text, 'body': body, 'url': url, 'code': response.status_code}) LOG.error(msg) raise exception.IronicException(msg) error = result.get('command_error') exc_type = None if error: # if an error, we should see if a type field exists. This type # field may signal an exception that is compatability based. exc_type = error.get('type') LOG.debug('Agent command %(method)s for node %(node)s returned ' 'result %(res)s, error %(error)s, HTTP status code %(code)d', {'node': node.uuid, 'method': method, 'res': result.get('command_result'), 'error': error, 'code': response.status_code}) if response.status_code >= http_client.BAD_REQUEST: LOG.error('Agent command %(method)s for node %(node)s failed. ' 'Expected 2xx HTTP status code, got %(code)d.', {'method': method, 'node': node.uuid, 'code': response.status_code}) raise exception.AgentAPIError(node=node.uuid, status=response.status_code, error=result.get('faultstring')) if exc_type == 'TypeError': LOG.error('Agent command %(method)s for node %(node)s failed. ' 'Internal %(exc_type)s error detected: Error %(error)s', {'method': method, 'node': node.uuid, 'exc_type': exc_type, 'error': error}) raise exception.AgentAPIError(node=node.uuid, status=error.get('code'), error=result.get('faultstring')) return result