def _invoke(self, timeout) -> bool: """ invokes the command on the remote server Returns ------- bool: True if command ended up gracefully. False otherwise Raises ------ TimeoutError: raised when timeout reached while waiting the response back from the remote server """ ctx = None if not has_app_context(): ctx = self._app.app_context() ctx.push() try: self.__dict__['_server'] = Server.query.get(self._server) # set a timeout if none to avoid infinite wait in event if timeout is None: timeout = defaults.TIMEOUT_REMOTE_COMMAND if not self._command._cp: auth = HTTPBearerAuth( create_access_token(self._command.var_context.env['executor_id'], datetime.timedelta(seconds=15))) start = time.time() data = dict(operation=base64.b64encode(pickle.dumps(self._command.implementation)).decode('ascii'), var_context=base64.b64encode(pickle.dumps(self._command.var_context)).decode('ascii'), params=base64.b64encode(pickle.dumps(self._command.params)).decode('ascii'), timeout=timeout, step_id=str(self.id[1]), orch_execution=self._command.register.json_orch_execution, event_id=str(uuid.uuid4())) resp = post(server=self.server, view_or_url='api_1_0.launch_operation', json=data, auth=auth, timeout=timeout) if resp.code == 204: current_app.events.register(data['event_id'], self.callback_completion_event) event = self._completion_event.wait(timeout=timeout - (time.time() - start)) if event is not True: self._command._cp = CompletedProcess(success=False, stdout='', stderr=f'Timeout of {timeout} reached waiting ' f'server operation completion') elif resp.code == 200: self.callback_completion_event(Event(None, data=resp.msg)) elif resp.code: if isinstance(resp.msg, dict): msg = json.dumps(resp.msg) else: msg = str(resp.msg) self._command._cp = CompletedProcess(success=False, stdout='', stderr=msg, rc=resp.code) finally: if ctx: ctx.pop() return self.success
def pre_process(self): if self.pre_process_code: try: local = dict(vc=self.var_context, cp=self._cp) exec_safe(self.pre_process_code, local) except Exception as e: self._cp = CompletedProcess(success=False, stderr=f"Pre-Process error: {format_exception(e)}")
def _invoke(self, timeout): if not self._cp: try: self._cp = self.implementation.execute(self.params, timeout=timeout, context=self.var_context) except Exception as e: self._cp = CompletedProcess(success=False, stderr=f"Execution error: {e}") logger.exception(f"Exception on execution {self.id}")
def test_command_post_process(self): cp = CompletedProcess(success=True, stdout='{"output": "this is a message"}', stderr='', rc=0, start_time=datetime(2019, 4, 1)) self.mock_implementation.execute.return_value = cp c = Command(implementation=self.mock_implementation, var_context=self.mock_context, undo_on_error=False, post_process= "import json\nvc.set('response', json.loads(cp.stdout))", id_=1) c.invoke() self.mock_context.set.assert_called_once_with( 'response', {"output": "this is a message"}) c = Command(implementation=self.mock_implementation, var_context=self.mock_context, undo_on_error=False, post_process="raise RuntimeError()", id_=1) c.invoke() self.assertIn("Post-Process error", cp.stderr) self.assertIn("RuntimeError", cp.stderr) self.assertFalse(cp.success)
def operation_execute(self, params, timeout=None, context=None): cp = CompletedProcess() cp.set_start_time() cp.stdout = self.rpl_params(**params, env=context.env) cp.stderr = None cp.rc = 0 cp.success = True self.evaluate_result(cp, context) return cp
def callback_completion_event(self, event: Event): """callback executed on response to the invoke command on remote server """ if 'step_execution' in event.data: step_exec_json = event.data.get('step_execution') self._command._cp = CompletedProcess(success=step_exec_json.get('success'), stdout=step_exec_json.get('stdout'), stderr=step_exec_json.get('stderr'), rc=step_exec_json.get('rc'), start_time=datetime.datetime.strptime(step_exec_json.get('start_time'), defaults.DATETIME_FORMAT) if step_exec_json.get( 'start_time') else None, end_time=datetime.datetime.strptime(step_exec_json.get('end_time'), defaults.DATETIME_FORMAT) if step_exec_json.get( 'end_time') else None) else: self._command._cp = CompletedProcess(success=False, stdout=str(event.data), stderr=f'Unknown message got on completion event.') self._completion_event.set()
def run_command_and_callback(operation: 'IOperationEncapsulation', params, context: Context, source: Server, step_execution: StepExecution, event_id, identity, timeout=None): execution: StepExecution = db.session.merge(step_execution) exec_id = execution.id source = db.session.merge(source) start = get_now() try: cp = operation.execute(params, timeout=timeout, context=context) except Exception as e: cp = CompletedProcess( success=False, stderr=f"Error while executing operation. {format_exception(e)}", start_time=start, end_time=get_now()) finally: execution.load_completed_result(cp) data = dict(step_execution=execution.to_json()) if execution.child_orch_execution: data['step_execution'].update( orch_execution=execution.child_orch_execution.to_json( add_step_exec=True)) # commit after data is dumped try: db.session.commit() except Exception as e: current_app.logger.exception( f"Error on commit for execution {exec_id}") resp, code = ntwrk.post(server=source, view_or_url='api_1_0.events', view_data={'event_id': event_id}, json=data, identity=identity) if code != 202: current_app.logger.error( f"Error while sending result for execution {exec_id}: {code}, {resp}" ) return data
def test_command_no_undo(self): cp = CompletedProcess(success=True, stdout='output: var', stderr='', rc=0, start_time=datetime(2019, 4, 1)) self.mock_implementation.execute.return_value = cp c = Command(implementation=self.mock_implementation, var_context=self.mock_context, undo_on_error=False, id_=1) r = c.invoke() self.assertTrue(r) r = c.undo() self.assertTrue(r) self.assertDictEqual({1: cp}, c.result)
def test_command_register(self): cp = CompletedProcess(success=True, stdout='{"output": "this is a message"}', stderr='', rc=0, start_time=datetime(2019, 4, 1)) self.mock_implementation.execute.return_value = cp mock_register = mock.Mock() c = Command(implementation=self.mock_implementation, var_context=self.mock_context, register=mock_register, undo_on_error=False, post_process="", id_=1) c.invoke() mock_register.save_step_execution.assert_called_once()
def extract_params(self): """ Turns over only params from var context to params :return: """ if not self._cp: try: if self.signature: # get required variables required = DictSet() for r in self.signature.get('required', []): container_name, var = extract_container_var(r) required[container_name].add(var) # del container_name # containers in signature container_names = [k for k in self.signature.keys() if k not in reserved_words] # add containers to turn over params from required container_names = set(container_names) | set(required.keys()) container_names -= {'env'} # remove env container cause is going to be passed explicitly # resolve mapping values for dest, value in self.signature.get('mapping', {}).items(): if isinstance(value, dict) and len(value) == 1 and 'from' in value: action, source = tuple(value.items())[0] container_name, var = extract_container_var(source) try: self.params['input'].update({dest: getattr(self.var_context, container_name, {})[var]}) except KeyError: if dest in required.get('input', []): se = StepExecution.query.get(self.step_execution_id) raise errors.MissingParameters([source], se.step, se.server) else: self.params['input'][dest] = value schema2validate = {'type': 'object', 'properties': {}} for container_name in container_names: schema2validate['properties'].update( {container_name: dict(type="object", properties=self.signature.get(container_name, {}), required=list(required[container_name]))}) for var, value in self.signature.get(container_name, {}).items(): if var not in self.params[container_name]: try: self.params[container_name].update( {var: getattr(self.var_context, container_name, {})[var]}) except KeyError: if 'default' in value: self.params[container_name][var] = value['default'] elif var in required.get(container_name, []): if has_app_context(): raise errors.MissingParameters([f"{container_name}.{var}"], Step.query.get(self.id[1]), Server.query.get(self.id[0])) else: raise errors.MissingParameters([f"{container_name}.{var}"]) # add variables specified in required for var in required.get(container_name, []): if var not in self.params[container_name]: try: self.params[container_name].update( {var: getattr(self.var_context, container_name, {})[var]}) except KeyError: raise errors.MissingParameters([f"{container_name}.{var}"], Step.query.get(self.id[1]), Server.query.get(self.id[0])) if schema2validate: jsonschema.validate(self.params, schema2validate) else: self.params = self.var_context.dict() except KeyError as e: self._cp = CompletedProcess(success=False, stderr=f"Variable {e} not found") except jsonschema.ValidationError as e: self._cp = CompletedProcess(success=False, stderr=f"Param validation error: {e}") except errors.MissingParameters as e: self._cp = CompletedProcess(success=False, stderr=f"{e}") except Exception as e: self._cp = CompletedProcess(success=False, stderr=f"{format_exception(e)}")