Beispiel #1
0
    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,
        }
Beispiel #3
0
    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.')
Beispiel #5
0
    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.')
Beispiel #6
0
    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()
Beispiel #7
0
    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)