Example #1
0
    def _just_run_command(self, job, job_state, config):
        """
        Internal method of JobRunner.

        Runs the command embedded in the job and returns a JobResultBuilder
        that describes the result.
        """
        try:
            ctrl = self._get_ctrl_for_job(job)
        except LookupError:
            return JobResultBuilder(
                outcome=IJobResult.OUTCOME_NOT_SUPPORTED,
                comments=_('No suitable execution controller is available)'))
        # Run the embedded command
        start_time = time.time()
        return_code, record_path = self._run_command(
            job, job_state, config, ctrl)
        execution_duration = time.time() - start_time
        # Convert the return of the command to the outcome of the job
        if return_code == 0:
            outcome = IJobResult.OUTCOME_PASS
        elif return_code < 0:
            outcome = IJobResult.OUTCOME_CRASH
        else:
            outcome = IJobResult.OUTCOME_FAIL
        # Create a result object and return it
        return JobResultBuilder(
            outcome=outcome,
            return_code=return_code,
            io_log_filename=record_path,
            execution_duration=execution_duration)
Example #2
0
 def skipped_builder(*args, **kwargs):
     result_builder = JobResultBuilder(
         outcome=IJobResult.OUTCOME_SKIP,
         comments=_("Explicitly skipped before execution"))
     if self._current_comments != "":
         result_builder.comments = self._current_comments
     return result_builder
Example #3
0
 def run_single_job_with_ui(self, job, ui):
     job_start_time = time.time()
     job_state = self.state.job_state_map[job.id]
     ui.considering_job(job, job_state)
     if job_state.can_start():
         ui.about_to_start_running(job, job_state)
         self.metadata.running_job_name = job.id
         self.manager.checkpoint()
         ui.started_running(job, job_state)
         result_builder = self._run_single_job_with_ui_loop(
             job, job_state, ui)
         assert result_builder is not None
         result_builder.execution_duration = time.time() - job_start_time
         job_result = result_builder.get_result()
         self.metadata.running_job_name = None
         self.manager.checkpoint()
         ui.finished_running(job, job_state, job_result)
     else:
         result_builder = JobResultBuilder(
             outcome=IJobResult.OUTCOME_NOT_SUPPORTED,
             comments=job_state.get_readiness_description(),
             execution_duration=time.time() - job_start_time)
         job_result = result_builder.get_result()
         ui.job_cannot_start(job, job_state, job_result)
     self.state.update_job_result(job, job_result)
     ui.finished(job, job_state, job_result)
Example #4
0
    def run_job(self, job, job_state, environ=None, ui=None):
        logger.info(_("Running %r"), job)
        if job.plugin not in supported_plugins:
            print(Colorizer().RED("Unsupported plugin type: {}".format(
                job.plugin)))
            return JobResultBuilder(
                outcome=IJobResult.OUTCOME_SKIP,
                comments=_("Unsupported plugin type: {}".format(job.plugin))
            ).get_result()

        # resource and attachment jobs are always run (even in dry runs)
        if self._dry_run and job.plugin not in ('resource', 'attachment'):
            return JobResultBuilder(
                outcome=IJobResult.OUTCOME_SKIP,
                comments=_("Job skipped in dry-run mode")
            ).get_result()
        self._job_runner_ui_delegate.ui = ui

        # for cached resource jobs we get the result using cache
        # if it's not in the cache, ordinary "_run_command" will be run
        if job.plugin == 'resource' and 'cachable' in job.get_flag_set():
            from_cache, result = self._resource_cache.get(
                job.checksum, lambda: self._run_command(
                    job, environ).get_result())
            if from_cache:
                print(Colorizer().header(_("Using cached data!")))
                jrud = self._job_runner_ui_delegate
                jrud.on_begin('', dict())
                for io_log_entry in result.io_log:
                    jrud.on_chunk(io_log_entry.stream_name, io_log_entry.data)
                jrud.on_end(result.return_code)
            return result

        # manual jobs don't require running anything so we just return
        # the 'undecided' outcome
        if job.plugin == 'manual':
            return JobResultBuilder(
                outcome=IJobResult.OUTCOME_UNDECIDED).get_result()

        # all other kinds of jobs at this point need to run their command
        if not job.command:
            print(Colorizer().RED("No command to run!"))
            return JobResultBuilder(
                outcome=IJobResult.OUTCOME_FAIL,
                comments=_("No command to run!")
            ).get_result()
        result_builder = self._run_command(job, environ)

        # for user-interact-verify and user-verify jobs the operator chooses
        # the final outcome, so we need to reset the outcome to undecided
        # (from what command's return code would have set)
        if job.plugin in ('user-interact-verify', 'user-verify'):
            result_builder.outcome = IJobResult.OUTCOME_UNDECIDED

        # by this point the result_builder should have all the info needed
        # to yield appropriate result
        return result_builder.get_result()
 def _run_single_job_with_ui_loop(self, job, job_state, ui):
     comments = ""
     while True:
         if job.plugin in ('user-interact', 'user-interact-verify',
                           'user-verify', 'manual'):
             ui.notify_about_purpose(job)
             if (self.is_interactive
                     and job.plugin in ('user-interact',
                                        'user-interact-verify', 'manual')):
                 ui.notify_about_steps(job)
                 if job.plugin == 'manual':
                     cmd = 'run'
                 else:
                     cmd = ui.wait_for_interaction_prompt(job)
                 if cmd == 'run' or cmd is None:
                     result_builder = self.runner.run_job(
                         job, job_state, self.config,
                         ui).get_builder(comments=comments)
                 elif cmd == 'comment':
                     new_comment = input(
                         self.C.BLUE(
                             _('Please enter your comments:') + '\n'))
                     if new_comment:
                         comments += new_comment + '\n'
                     continue
                 elif cmd == 'skip':
                     result_builder = JobResultBuilder(
                         outcome=IJobResult.OUTCOME_SKIP,
                         comments=_("Explicitly skipped before"
                                    " execution"))
                     if comments != "":
                         result_builder.comments = comments
                     break
                 elif cmd == 'quit':
                     raise SystemExit()
             else:
                 result_builder = self.runner.run_job(
                     job, job_state, self.config, ui).get_builder()
         else:
             if 'noreturn' in job.get_flag_set():
                 ui.noreturn_job()
             result_builder = self.runner.run_job(job, job_state,
                                                  self.config,
                                                  ui).get_builder()
         if (self.is_interactive and result_builder.outcome
                 == IJobResult.OUTCOME_UNDECIDED):
             try:
                 if comments != "":
                     result_builder.comments = comments
                 ui.notify_about_verification(job)
                 self._interaction_callback(self.runner, job,
                                            result_builder, self.config)
             except ReRunJob:
                 continue
         break
     return result_builder
 def _make_result_for(self, job):
     builder = JobResultBuilder(outcome='pass')
     if job.plugin == 'local':
         pass
     elif job.plugin == 'resource':
         pass
     else:
         builder.io_log = [(0, 'stdout', b'IO-LOG-STDOUT\n'),
                           (1, 'stderr', b'IO-LOG-STDERR\n')]
     return builder.get_result()
Example #7
0
 def _run_command(self, job, environ):
     start_time = time.time()
     slug = slugify(job.id)
     output_writer = CommandOutputWriter(
         stdout_path=os.path.join(self._jobs_io_log_dir,
                                  "{}.stdout".format(slug)),
         stderr_path=os.path.join(self._jobs_io_log_dir,
                                  "{}.stderr".format(slug)))
     io_log_gen = IOLogRecordGenerator()
     log = os.path.join(self._jobs_io_log_dir, "{}.record.gz".format(slug))
     with gzip.open(log, mode='wb') as gzip_stream, io.TextIOWrapper(
             gzip_stream, encoding='UTF-8') as record_stream:
         writer = IOLogRecordWriter(record_stream)
         io_log_gen.on_new_record.connect(writer.write_record)
         delegate = extcmd.Chain([
             self._job_runner_ui_delegate, io_log_gen,
             self._command_io_delegate, output_writer
         ])
         ecmd = extcmd.ExternalCommandWithDelegate(delegate)
         return_code = self.execute_job(job, environ, ecmd, self._stdin)
         io_log_gen.on_new_record.disconnect(writer.write_record)
     if return_code == 0:
         outcome = IJobResult.OUTCOME_PASS
     elif return_code < 0:
         outcome = IJobResult.OUTCOME_CRASH
     else:
         outcome = IJobResult.OUTCOME_FAIL
     return JobResultBuilder(outcome=outcome,
                             return_code=return_code,
                             io_log_filename=log,
                             execution_duration=time.time() - start_time)
Example #8
0
    def run_manual_job(self, job, job_state, config):
        """
        Method called to run a job with plugin field equal to 'manual'.

        The 'manual' job implements the following scenario:

        * Display the description to the user
        * Ask the user to perform some operation
        * Ask the user to decide on the outcome

        .. note::
            Technically this method almost always returns a result with
            OUTCOME_UNDECIDED to indicate that it could not determine if the
            test passed or not. Manual jobs are basically fully human driven
            and could totally ignore the job runner. This method is provided
            for completeness.

        .. warning::
            Before the interaction callback is fully removed and deprecated it
            may also return other values through that callback.
        """
        if job.plugin != "manual":
            # TRANSLATORS: please keep 'plugin' untranslated
            raise ValueError(_("bad job plugin value"))
        return JobResultBuilder(
            outcome=IJobResult.OUTCOME_UNDECIDED).get_result()
Example #9
0
    def _get_dry_run_result(self, job):
        """
        Internal method of JobRunner.

        Returns a result that is used when running in dry-run mode (where we
        don't really test anything)
        """
        return JobResultBuilder(
            outcome=IJobResult.OUTCOME_SKIP,
            comments=_("Job skipped in dry-run mode")).get_result()
 def run_single_job_with_ui(self, job, ui):
     job_start_time = time.time()
     job_state = self.state.job_state_map[job.id]
     ui.considering_job(job, job_state)
     if job_state.can_start():
         ui.about_to_start_running(job, job_state)
         self.metadata.running_job_name = job.id
         self.manager.checkpoint()
         ui.started_running(job, job_state)
         result_builder = self._run_single_job_with_ui_loop(
             job, job_state, ui)
         assert result_builder is not None
         result_builder.execution_duration = time.time() - job_start_time
         job_result = result_builder.get_result()
         self.metadata.running_job_name = None
         self.manager.checkpoint()
         ui.finished_running(job, job_state, job_result)
     else:
         # Set the outcome of jobs that cannot start to
         # OUTCOME_NOT_SUPPORTED _except_ if any of the inhibitors point to
         # a job with an OUTCOME_SKIP outcome, if that is the case mirror
         # that outcome. This makes 'skip' stronger than 'not-supported'
         outcome = IJobResult.OUTCOME_NOT_SUPPORTED
         for inhibitor in job_state.readiness_inhibitor_list:
             if (inhibitor.cause == InhibitionCause.FAILED_RESOURCE
                     and 'fail-on-resource' in job.get_flag_set()):
                 outcome = IJobResult.OUTCOME_FAIL
                 break
             elif inhibitor.cause != InhibitionCause.FAILED_DEP:
                 continue
             related_job_state = self.state.job_state_map[
                 inhibitor.related_job.id]
             if related_job_state.result.outcome == IJobResult.OUTCOME_SKIP:
                 outcome = IJobResult.OUTCOME_SKIP
         result_builder = JobResultBuilder(
             outcome=outcome,
             comments=job_state.get_readiness_description(),
             execution_duration=time.time() - job_start_time)
         job_result = result_builder.get_result()
         ui.job_cannot_start(job, job_state, job_result)
     self.state.update_job_result(job, job_result)
     ui.finished(job, job_state, job_result)
Example #11
0
    def run_job(self, job, job_state, config=None, ui=None):
        """
        Run the specified job an return the result.

        :param job:
            A JobDefinition to run
        :param job_state:
            The JobState associated to the job to execute.
        :param config:
            A PlainBoxConfig that may influence how this job is executed. This
            is only used for the environment variables (that should be
            specified in the environment but, for simplicity in certain setups,
            can be pulled from a special section of the configuration file.
        :param ui:
            A IJobRunnerUI object (optional) which will be used do relay
            external process interaction events during the execution of this
            job.
        :returns:
            A IJobResult subclass that describes the result
        :raises ValueError:
            In the future, this method will not run jobs that don't themselves
            validate correctly. Right now this is not enforced.

        This method is the entry point for running all kinds of jobs. Typically
        execution blocks while a command, embeded in many jobs, is running in
        another process. How a job is executed depends mostly on the value of
        the :attr:`plainbox.abc.IJobDefinition.plugin` field.

        The result of a job may in some cases be OUTCOME_UNDECIDED, in which
        case the application should ask the user what the outcome is (and
        present sufficient information to make that choice, typically this is
        the job description and the output of the command)
        """
        # TRANSLATORS: %r is the name of the job
        logger.info(_("Running %r"), job)
        func_name = "run_{}_job".format(job.plugin.replace('-', '_'))
        try:
            runner = getattr(self, func_name)
        except AttributeError:
            return JobResultBuilder(
                outcome=IJobResult.OUTCOME_NOT_IMPLEMENTED,
                comments=_('This type of job is not supported')
            ).get_result()
        else:
            if self._dry_run and job.plugin not in self._DRY_RUN_PLUGINS:
                return self._get_dry_run_result(job)
            else:
                self._job_runner_ui_delegate.ui = ui
                try:
                    return runner(job, job_state, config)
                finally:
                    self._job_runner_ui_delegate.ui = None
 def test_smoke_disk(self):
     builder = JobResultBuilder()
     builder.comments = 'it works'
     builder.execution_duration = 0.1
     builder.io_log_filename = 'log'
     builder.outcome = 'pass'
     builder.return_code = 0
     result = builder.get_result()
     self.assertEqual(result.comments, "it works")
     self.assertEqual(result.execution_duration, 0.1)
     self.assertEqual(result.io_log_filename, 'log')
     self.assertEqual(result.outcome, "pass")
     self.assertEqual(result.return_code, 0)
     # Sanity check: the builder we can re-create is identical
     builder2 = result.get_builder()
     self.assertEqual(builder, builder2)
 def _make_cert_attachments(self):
     state = self.manager.default_device_context.state
     partial_id_list = [
         'dmi_attachment', 'sysfs_attachment', 'udev_attachment'
     ]
     for partial_id in partial_id_list:
         job = JobDefinition({
             'id': CERTIFICATION_NS + partial_id,
             'plugin': 'attachment'
         })
         result = JobResultBuilder(io_log=[(
             0, 'stdout', 'STDOUT-{}\n'.format(partial_id).encode('utf-8')
         ), (1, 'stderr', 'STDERR-{}\n'.format(partial_id).encode('utf-8')
             )]).get_result()
         state.add_unit(job)
         state.update_job_result(job, result)
 def test_smoke_memory(self):
     builder = JobResultBuilder()
     builder.comments = 'it works'
     builder.execution_duration = 0.1
     builder.io_log = [(0, 'stdout', b'ok\n')]
     builder.outcome = 'pass'
     builder.return_code = 0
     result = builder.get_result()
     self.assertEqual(result.comments, "it works")
     self.assertEqual(result.execution_duration, 0.1)
     self.assertEqual(
         result.io_log,
         (IOLogRecord(delay=0, stream_name='stdout', data=b'ok\n'), ))
     self.assertEqual(result.outcome, "pass")
     self.assertEqual(result.return_code, 0)
     # Sanity check: the builder we can re-create is identical
     builder2 = result.get_builder()
     self.assertEqual(builder, builder2)
Example #15
0
 def run_job(self, job, job_state, environ=None, ui=None):
     """
     Only one resouce object is created from this runner.
     Exception: 'graphics_card' resource job creates two objects to
     simulate hybrid graphics.
     """
     if job.plugin != 'resource':
         return super().run_job(job, job_state, environ, ui)
     builder = JobResultBuilder()
     if job.partial_id == 'graphics_card':
         builder.io_log = [(0, 'stdout', b'a: b\n'), (1, 'stdout', b'\n'),
                           (2, 'stdout', b'a: c\n')]
     else:
         builder.io_log = [(0, 'stdout', b'a: b\n')]
     builder.outcome = 'pass'
     builder.return_code = 0
     return builder.get_result()
Example #16
0
 def register_test_result(self, test):
     """Registers outcome of a test."""
     _logger.info("Storing test result: %s", test)
     job_id = test['id']
     builder_kwargs = {
         'outcome': test['outcome'],
         'comments': test.get('comments', pod.UNSET),
         'execution_duration': time.time() - test['start_time'],
     }
     if 'result' in test:
         # if we're registering skipped test as an outcome of resuming
         # session, the result field of the test object will be missing
         builder_kwargs['return_code'] = test['result'].return_code
         builder_kwargs['io_log_filename'] = test['result'].io_log_filename
         builder_kwargs['io_log'] = test['result'].io_log
     else:
         builder_kwargs['return_code'] = 0
     result = JobResultBuilder(**builder_kwargs).get_result()
     self.assistant.use_job_result(job_id, result)
     self.index += 1
     self.assistant.update_app_blob(self._get_app_blob())
Example #17
0
 def cant_start_builder(*args, **kwargs):
     result_builder = JobResultBuilder(
         outcome=outcome,
         comments=job_state.get_readiness_description())
     return result_builder
 def test_get_builder_kwargs(self):
     result = JobResultBuilder(outcome='pass').get_result()
     self.assertEqual(result.get_builder(outcome='fail').outcome, 'fail')
 def test_add_comment(self):
     builder = JobResultBuilder()
     builder.add_comment('first comment')  # ;-)
     self.assertEqual(builder.comments, 'first comment')
     builder.add_comment('second comment')
     self.assertEqual(builder.comments, 'first comment\nsecond comment')
 def test_io_log_clash(self):
     builder = JobResultBuilder()
     builder.io_log = [(0, 'stout', b'hi')]
     builder.io_log_filename = 'log'
     with self.assertRaises(ValueError):
         builder.get_result()
 def test_smoke_hollow(self):
     self.assertTrue(JobResultBuilder().get_result().is_hollow)
Example #22
0
    def run_qml_job(self, job, job_state, config):
        """
        Method called to run a job with plugin field equal to 'qml'.

        The 'qml' job implements the following scenario:

        * Maybe display the description to the user
        * Run qmlscene with provided test and wait for it to finish
        * Decide on the outcome based on the result object returned by qml
          shell
        * The method ends here

        .. note::
            QML jobs are fully manual jobs with graphical user interface
            implemented in QML. They implement proposal described in CEP-5.
        """
        if job.plugin != "qml":
            # TRANSLATORS: please keep 'plugin' untranslated
            raise ValueError(_("bad job plugin value"))
        try:
            ctrl = self._get_ctrl_for_job(job)
        except LookupError:
            return JobResultBuilder(
                outcome=IJobResult.OUTCOME_NOT_SUPPORTED,
                comments=_('No suitable execution controller is available)')
            ).get_result()
        # Run the embedded command
        start_time = time.time()
        delegate, io_log_gen = self._prepare_io_handling(job, config)
        # Create a subprocess.Popen() like object that uses the delegate
        # system to observe all IO as it occurs in real time.
        delegate_cls = extcmd.ExternalCommandWithDelegate
        extcmd_popen = delegate_cls(delegate)
        # Stream all IOLogRecord entries to disk
        record_path = self.get_record_path_for_job(job)
        with gzip.open(record_path, mode='wb') as gzip_stream, \
                io.TextIOWrapper(
                    gzip_stream, encoding='UTF-8') as record_stream:
            writer = IOLogRecordWriter(record_stream)
            io_log_gen.on_new_record.connect(writer.write_record)
            try:
                # Start the process and wait for it to finish getting the
                # result code. This will actually call a number of callbacks
                # while the process is running. It will also spawn a few
                # threads although all callbacks will be fired from a single
                # thread (which is _not_ the main thread)
                logger.debug(
                    _("job[%s] starting qml shell: %s"), job.id, job.qml_file)
                # Run the job command using extcmd
                ctrl.on_leftover_files.connect(self.on_leftover_files)
                try:
                    return_code, result = ctrl.execute_job_with_result(
                        job, job_state, config, self._session_dir,
                        extcmd_popen)
                finally:
                    ctrl.on_leftover_files.disconnect(self.on_leftover_files)
                logger.debug(
                    _("job[%s] shell return code: %r"), job.id, return_code)
            finally:
                io_log_gen.on_new_record.disconnect(writer.write_record)
        execution_duration = time.time() - start_time
        if return_code != 0 or result is None:
            outcome = IJobResult.OUTCOME_FAIL
        else:
            outcome = result['outcome']
        # Create a result object and return it
        return JobResultBuilder(
            outcome=outcome,
            return_code=return_code,
            io_log_filename=record_path,
            execution_duration=execution_duration
        ).get_result()
Example #23
0
 def _run_single_job_with_ui_loop(self, job, ui):
     print(self.C.header(job.tr_summary(), fill='-'))
     print(_("ID: {0}").format(job.id))
     print(
         _("Category: {0}").format(
             self.sa.get_job_state(job.id).effective_category_id))
     comments = ""
     while True:
         if job.plugin in ('user-interact', 'user-interact-verify',
                           'user-verify', 'manual'):
             job_state = self.sa.get_job_state(job.id)
             if (not self.is_interactive
                     and job.plugin in ('user-interact',
                                        'user-interact-verify', 'manual')):
                 result_builder = JobResultBuilder(
                     outcome=IJobResult.OUTCOME_SKIP,
                     comments=_("Trying to run interactive job in a silent"
                                " session"))
                 return result_builder
             if job_state.can_start():
                 ui.notify_about_purpose(job)
             if (self.is_interactive
                     and job.plugin in ('user-interact',
                                        'user-interact-verify', 'manual')):
                 if job_state.can_start():
                     ui.notify_about_steps(job)
                 if job.plugin == 'manual':
                     cmd = 'run'
                 else:
                     if job_state.can_start():
                         cmd = ui.wait_for_interaction_prompt(job)
                     else:
                         # 'running' the job will make it marked as skipped
                         # because of the failed dependency
                         cmd = 'run'
                 if cmd == 'run' or cmd is None:
                     result_builder = self.sa.run_job(job.id, ui, False)
                 elif cmd == 'comment':
                     new_comment = input(
                         self.C.BLUE(
                             _('Please enter your comments:') + '\n'))
                     if new_comment:
                         comments += new_comment + '\n'
                     continue
                 elif cmd == 'skip':
                     result_builder = JobResultBuilder(
                         outcome=IJobResult.OUTCOME_SKIP,
                         comments=_("Explicitly skipped before"
                                    " execution"))
                     if comments != "":
                         result_builder.comments = comments
                     break
                 elif cmd == 'quit':
                     raise SystemExit()
             else:
                 result_builder = self.sa.run_job(job.id, ui, False)
         else:
             if 'noreturn' in job.get_flag_set():
                 ui.noreturn_job()
             result_builder = self.sa.run_job(job.id, ui, False)
         if (self.is_interactive and result_builder.outcome
                 == IJobResult.OUTCOME_UNDECIDED):
             try:
                 if comments != "":
                     result_builder.comments = comments
                 ui.notify_about_verification(job)
                 self._interaction_callback(job, result_builder)
             except ReRunJob:
                 self.sa.use_job_result(job.id, result_builder.get_result())
                 continue
         break
     return result_builder
Example #24
0
 def undecided_builder(*args, **kwargs):
     return JobResultBuilder(outcome=IJobResult.OUTCOME_UNDECIDED)