def get_console_output( self, build_id: int, subjob_id: int, atom_id: int, result_root: str, max_lines: int = 50, offset_line: Optional[int] = None, ): """ Return the console output if it exists, raises an ItemNotFound error if not. On success, the response contains keys: offset_line, num_lines, total_num_lines, and content. e.g.: { 'offset_line': 0, 'num_lines': 50, 'total_num_lines': 167, 'content': 'Lorem ipsum dolor sit amet,\nconsectetur adipiscing elit,\n...', } :param build_id: build id :param subjob_id: subjob id :param atom_id: atom id :param result_root: the sys path to either the results or artifacts directory where results are stored. :param max_lines: The maximum total number of lines to return. If this max_lines + offset_line lines do not exist in the output file, just return what there is. :param offset_line: The line number (0-indexed) to start reading content for. If none is specified, we will return the console output starting from the end of the file. """ if offset_line is not None and offset_line < 0: raise BadRequestError( '\'offset_line\' must be greater than or equal to zero.') if max_lines <= 0: raise BadRequestError('\'max_lines\' must be greater than zero.') segment = BuildArtifact.get_console_output(build_id, subjob_id, atom_id, result_root, max_lines, offset_line) if not segment: raise ItemNotFoundError( 'Console output does not exist on this host for ' 'build {}, subjob {}, atom {}.'.format(build_id, subjob_id, atom_id)) return { 'offset_line': segment.offset_line, 'num_lines': segment.num_lines, 'total_num_lines': segment.total_num_lines, 'content': segment.content, }
def get_console_output(self, build_id, subjob_id, atom_id, result_root, max_lines=50, offset_line=None): """ Return the console output if it exists, raises an ItemNotFound error if not. On success, the response contains keys: offset_line, num_lines, total_num_lines, and content. e.g.: { 'offset_line': 0, 'num_lines': 50, 'total_num_lines': 167, 'content': 'Lorem ipsum dolor sit amet,\nconsectetur adipiscing elit,\n...', } :type build_id: int :type subjob_id: int :type atom_id: int :param result_root: the sys path to either the results or artifacts directory where results are stored. :type result_root: str :param max_lines: The maximum total number of lines to return. If this max_lines + offset_line lines do not exist in the output file, just return what there is. :type max_lines: int :param offset_line: The line number (0-indexed) to start reading content for. If none is specified, we will return the console output starting from the end of the file. :type offset_line: int | None """ if offset_line is not None and offset_line < 0: raise BadRequestError('\'offset_line\' must be greater than or equal to zero.') if max_lines <= 0: raise BadRequestError('\'max_lines\' must be greater than zero.') artifact_dir = BuildArtifact.atom_artifact_directory(build_id, subjob_id, atom_id, result_root=result_root) output_file = os.path.join(artifact_dir, BuildArtifact.OUTPUT_FILE) if not os.path.isfile(output_file): raise ItemNotFoundError('Output file doesn\'t exist for build_id: {} subjob_id: {} atom_id: {}'.format( build_id, subjob_id, atom_id)) try: console_output = ConsoleOutput(output_file) segment = console_output.segment(max_lines, offset_line) except ValueError as e: raise BadRequestError(e) return { 'offset_line': segment.offset_line, 'num_lines': segment.num_lines, 'total_num_lines': segment.total_num_lines, 'content': segment.content, }
def start_working_on_subjob(self, build_id, subjob_id, subjob_artifact_dir, atomic_commands): """ Begin working on a subjob with the given build id and subjob id. This just starts the subjob execution asynchronously on a separate thread. :type build_id: int :type subjob_id: int :type subjob_artifact_dir: str :type atomic_commands: list[str] :return: The text to return in the API response. :rtype: dict[str, int] """ if build_id != self._current_build_id: raise BadRequestError('Attempted to start subjob {} for build {}, ' 'but current build id is {}.'.format(subjob_id, build_id, self._current_build_id)) # get idle executor from queue to claim it as in-use (or block until one is available) executor = self._idle_executors.get() # Start a thread to execute the job (after waiting for setup to complete) SafeThread( target=self._execute_subjob, args=(build_id, subjob_id, executor, subjob_artifact_dir, atomic_commands), name='Bld{}-Sub{}'.format(build_id, subjob_id), ).start() self._logger.info('Slave ({}:{}) has received subjob. (Build {}, Subjob {})', self.host, self.port, build_id, subjob_id) return {'executor_id': executor.id}
def _handle_setup_failure_on_slave(self, slave): """ Respond to failed build setup on a slave. This should put the slave back into a usable state. :type slave: Slave """ raise BadRequestError('Setup failure handling on the master is not yet implemented.')
def _handle_setup_failure_on_slave(self, slave): """ Respond to failed build setup on a slave. This should put the slave back into a usable state. :type slave: Slave """ internal_errors.labels(ErrorType.SetupBuildFailure).inc() # pylint: disable=no-member raise BadRequestError('Setup failure handling on the master is not yet implemented.')
def teardown_build(self, build_id=None): """ Called at the end of each build on each slave before it reports back to the master that it is idle again. :param build_id: The build id to teardown -- this parameter is used solely for correctness checking of the master, to make sure that the master is not erroneously sending teardown commands for other builds. :type build_id: int | None """ if self._current_build_id is None: raise BadRequestError('Tried to teardown a build but no build is active on this slave.') if build_id is not None and build_id != self._current_build_id: raise BadRequestError('Tried to teardown build {}, ' 'but slave is running build {}!'.format(build_id, self._current_build_id)) SafeThread( target=self._async_teardown_build, name='Bld{}-Teardwn'.format(build_id) ).start()
def prepare(self): """ Called at the beginning of a request before `get`/`post`/etc. """ # Decode an encoded body, if present. Otherwise fall back to decoding the raw request body. See the comments in # the util.network.Network class for more information about why we're doing this. try: self.encoded_body = self.get_argument(ENCODED_BODY, default=self.request.body) self.decoded_body = tornado.escape.json_decode(self.encoded_body) if self.encoded_body else {} except ValueError as ex: raise BadRequestError('Invalid JSON in request body.') from ex
def handle_slave_state_update(self, slave, new_slave_state): """ Execute logic to transition the specified slave to the given state. :type slave: Slave :type new_slave_state: SlaveState """ slave_transition_functions = { SlaveState.DISCONNECTED: self._disconnect_slave, SlaveState.SHUTDOWN: self._graceful_shutdown_slave, SlaveState.IDLE: self._slave_allocator.add_idle_slave, SlaveState.SETUP_COMPLETED: self._handle_setup_success_on_slave, SlaveState.SETUP_FAILED: self._handle_setup_failure_on_slave, } if new_slave_state not in slave_transition_functions: raise BadRequestError('Invalid slave state "{}". Valid states are: {}.' .format(new_slave_state, ', '.join(slave_transition_functions.keys()))) do_transition = slave_transition_functions.get(new_slave_state) do_transition(slave)