Ejemplo n.º 1
0
    def expand_jobs(self, step, phase_config):
        """
        Creates and runs JobSteps for a set of tests, based on a phase config.

        This phase config comes from a tests.json file that the collection
        jobstep should generate. This method is then called by the TestsJsonHandler.
        """
        if not phase_config.get("cmd"):
            raise ArtifactParseError("No cmd attribute")
        if "{test_names}" not in phase_config["cmd"]:
            raise ArtifactParseError("No {test_names} in cmd")
        if "tests" not in phase_config:
            raise ArtifactParseError("No tests attribute")

        num_tests = len(phase_config["tests"])
        test_stats, avg_test_time = TestsExpander.get_test_stats(self.get_test_stats_from() or step.project.slug)

        phase, _ = get_or_create(
            JobPhase,
            where={"job": step.job, "project": step.project, "label": phase_config.get("phase") or "Test"},
            defaults={"status": Status.queued},
        )
        db.session.commit()

        # If there are no tests to run, the phase is done.
        if num_tests == 0:
            phase.status = Status.finished
            phase.result = Result.passed
            db.session.add(phase)
            db.session.commit()
            return

        # Check for whether a previous run of this task has already
        # created JobSteps for us, since doing it again would create a
        # double-sharded build.
        steps = JobStep.query.filter_by(phase_id=phase.id, replacement_id=None).all()
        if steps:
            step_shard_counts = [s.data.get("shard_count", 1) for s in steps]
            if len(set(step_shard_counts)) != 1:
                raise Exception("Mixed shard counts in phase!")
            elif len(steps) != step_shard_counts[0]:
                raise Exception("Shard count incorrect")
        else:
            # Create all of the job steps and commit them together.
            groups = TestsExpander.shard_tests(phase_config["tests"], self.max_shards, test_stats, avg_test_time)
            steps = [
                self._create_jobstep(
                    phase, phase_config["cmd"], phase_config.get("path", ""), weight, test_list, len(groups)
                )
                for weight, test_list in groups
            ]
            if len(steps) != len(groups):
                raise Exception("Didn't create correct number of shards")
            db.session.commit()

        # Now that that database transaction is done, we'll do the slow work of
        # creating jenkins builds.
        for step in steps:
            self._create_jenkins_build(step)
            sync_job_step.delay_if_needed(step_id=step.id.hex, task_id=step.id.hex, parent_task_id=phase.job.id.hex)
Ejemplo n.º 2
0
    def _expand_job(self, phase, job_config):
        label = job_config.get('name') or md5(job_config['cmd']).hexdigest()

        step, created = get_or_create(JobStep,
                                      where={
                                          'job': phase.job,
                                          'project': phase.project,
                                          'phase': phase,
                                          'label': label,
                                      },
                                      defaults={
                                          'data': {
                                              'cmd': job_config['cmd'],
                                              'job_name': self.job_name,
                                              'build_no': None,
                                              'expanded': True,
                                          },
                                          'status': Status.queued,
                                      })

        # TODO(dcramer): due to no unique constraints this section of code
        # presents a race condition when run concurrently
        if not step.data.get('build_no'):
            builder = self.get_builder()
            params = builder.get_job_parameters(step.job,
                                                changes_bid=step.id.hex,
                                                script=step.data['cmd'])

            success = False
            exn = None
            for _ in range(0, 3):
                try:
                    job_data = builder.create_job_from_params(
                        changes_bid=step.id.hex,
                        params=params,
                        job_name=step.data['job_name'],
                    )

                    step.data.update(job_data)
                    db.session.add(step)
                    db.session.commit()
                    success = True
                    break
                except Exception as ex:
                    logging.exception("Failed to create jobstep")
                    exn = ex

            if not success:
                step.status = Status.finished
                step.result = Result.infra_failed
                db.session.add(step)
                db.session.commit()
                if exn:
                    raise exn

        sync_job_step.delay_if_needed(
            step_id=step.id.hex,
            task_id=step.id.hex,
            parent_task_id=phase.job.id.hex,
        )
Ejemplo n.º 3
0
    def sync_step(self, step):
        if step.data.get('job_name') != self.job_name:
            return super(JenkinsFactoryBuilder, self).sync_step(step)

        # for any downstream jobs, pull their results using xpath magic
        for downstream_job_name in self.downstream_job_names:
            downstream_build_nos = self._get_downstream_jobs(step, downstream_job_name)

            if not downstream_build_nos:
                continue

            phase, created = get_or_create(JobPhase, where={
                'job': step.job,
                'label': downstream_job_name,
            }, defaults={
                'project_id': step.job.project_id,
            })
            db.session.commit()

            for build_no in downstream_build_nos:
                # XXX(dcramer): ideally we would grab this with the first query
                # but because we dont want to rely on an XML parser, we're doing
                # a second http request for build details
                downstream_step = self._create_job_step(
                    phase, downstream_job_name, build_no)

                db.session.commit()

                sync_job_step.delay_if_needed(
                    step_id=downstream_step.id.hex,
                    task_id=downstream_step.id.hex,
                    parent_task_id=step.job.id.hex,
                )

        return super(JenkinsFactoryBuilder, self).sync_step(step)
Ejemplo n.º 4
0
    def _expand_job(self, phase, label, cmd, replaces=None):
        where = {
            'job': phase.job,
            'project': phase.project,
            'phase': phase,
            'label': label,
        }
        if replaces:
            # uuid is unique which forces jobstep to be created
            where['id'] = uuid.uuid4()
        step, created = get_or_create(JobStep, where=where, defaults={
            'data': {
                'cmd': cmd,
                'job_name': self.job_name,
                'build_no': None,
                'expanded': True,
            },
            'status': Status.queued,
        })
        assert created or not replaces
        BuildStep.handle_debug_infra_failures(step, self.debug_config, 'expanded')
        if replaces:
            replaces.replacement_id = step.id
            db.session.add(replaces)

        builder = self.get_builder()
        builder.create_jenkins_build(step, job_name=step.data['job_name'], script=step.data['cmd'])

        sync_job_step.delay_if_needed(
            step_id=step.id.hex,
            task_id=step.id.hex,
            parent_task_id=phase.job.id.hex,
        )
        return step
Ejemplo n.º 5
0
    def expand_jobs(self, step, phase_config):
        """
        Creates and runs JobSteps for a set of tests, based on a phase config.

        This phase config comes from a tests.json file that the collection
        jobstep should generate. This method is then called by the TestsJsonHandler.
        """
        assert phase_config['cmd']
        assert '{test_names}' in phase_config['cmd']
        assert 'tests' in phase_config

        num_tests = len(phase_config['tests'])
        test_stats, avg_test_time = TestsExpander.get_test_stats(self.get_test_stats_from() or step.project.slug)

        phase, _ = get_or_create(JobPhase, where={
            'job': step.job,
            'project': step.project,
            'label': phase_config.get('phase') or 'Test',
        }, defaults={
            'status': Status.queued
        })
        db.session.commit()

        # If there are no tests to run, the phase is done.
        if num_tests == 0:
            phase.status = Status.finished
            phase.result = Result.passed
            db.session.add(phase)
            db.session.commit()
            return

        # Check for whether a previous run of this task has already
        # created JobSteps for us, since doing it again would create a
        # double-sharded build.
        steps = JobStep.query.filter_by(phase_id=phase.id, replacement_id=None).all()
        if steps:
            step_shard_counts = [s.data.get('shard_count', 1) for s in steps]
            assert len(set(step_shard_counts)) == 1, "Mixed shard counts in phase!"
            assert len(steps) == step_shard_counts[0]
        else:
            # Create all of the job steps and commit them together.
            groups = TestsExpander.shard_tests(phase_config['tests'], self.max_shards,
                                       test_stats, avg_test_time)
            steps = [
                self._create_jobstep(phase, phase_config['cmd'], phase_config.get('path', ''),
                                     weight, test_list, len(groups))
                for weight, test_list in groups
                ]
            assert len(steps) == len(groups)
            db.session.commit()

        # Now that that database transaction is done, we'll do the slow work of
        # creating jenkins builds.
        for step in steps:
            self._create_jenkins_build(step)
            sync_job_step.delay_if_needed(
                step_id=step.id.hex,
                task_id=step.id.hex,
                parent_task_id=phase.job.id.hex,
            )
Ejemplo n.º 6
0
    def expand_jobs(self, step, phase_config):
        """
        Creates and runs JobSteps for a set of tests, based on a phase config.

        This phase config comes from a tests.json file that the collection
        jobstep should generate. This method is then called by the TestsJsonHandler.
        """
        assert phase_config['cmd']
        assert '{test_names}' in phase_config['cmd']
        assert 'tests' in phase_config

        num_tests = len(phase_config['tests'])
        test_stats, avg_test_time = TestsExpander.get_test_stats(self.get_test_stats_from() or step.project.slug)

        phase, _ = get_or_create(JobPhase, where={
            'job': step.job,
            'project': step.project,
            'label': phase_config.get('phase') or 'Test',
        }, defaults={
            'status': Status.queued
        })
        db.session.commit()

        # If there are no tests to run, the phase is done.
        if num_tests == 0:
            phase.status = Status.finished
            phase.result = Result.passed
            db.session.add(phase)
            db.session.commit()
            return

        # Check for whether a previous run of this task has already
        # created JobSteps for us, since doing it again would create a
        # double-sharded build.
        steps = JobStep.query.filter_by(phase_id=phase.id, replacement_id=None).all()
        if steps:
            step_shard_counts = [s.data.get('shard_count', 1) for s in steps]
            assert len(set(step_shard_counts)) == 1, "Mixed shard counts in phase!"
            assert len(steps) == step_shard_counts[0]
        else:
            # Create all of the job steps and commit them together.
            groups = TestsExpander.shard_tests(phase_config['tests'], self.max_shards,
                                       test_stats, avg_test_time)
            steps = [
                self._create_jobstep(phase, phase_config['cmd'], phase_config.get('path', ''),
                                     weight, test_list, len(groups))
                for weight, test_list in groups
                ]
            assert len(steps) == len(groups)
            db.session.commit()

        # Now that that database transaction is done, we'll do the slow work of
        # creating jenkins builds.
        for step in steps:
            self._create_jenkins_build(step)
            sync_job_step.delay_if_needed(
                step_id=step.id.hex,
                task_id=step.id.hex,
                parent_task_id=phase.job.id.hex,
            )
Ejemplo n.º 7
0
    def _expand_jobs(self, step, artifact):
        builder = self.get_builder()
        artifact_data = builder.fetch_artifact(step, artifact.data)
        phase_config = artifact_data.json()

        assert phase_config['cmd']
        assert '{test_names}' in phase_config['cmd']
        assert 'tests' in phase_config

        num_tests = len(phase_config['tests'])
        test_stats, avg_test_time = self.get_test_stats(step.project)

        phase, created = get_or_create(JobPhase, where={
            'job': step.job,
            'project': step.project,
            'label': phase_config.get('phase') or 'Test',
        }, defaults={
            'status': Status.queued
        })
        db.session.commit()

        # If there are no tests to run, the phase is done.
        if num_tests == 0:
            phase.status = Status.finished
            phase.result = Result.passed
            db.session.add(phase)
            db.session.commit()
            return

        # Check for whether a previous run of this task has already
        # created JobSteps for us, since doing it again would create a
        # double-sharded build.
        steps = JobStep.query.filter_by(phase_id=phase.id).all()
        if steps:
            step_shard_counts = [s.data.get('shard_count', 1) for s in steps]
            assert len(set(step_shard_counts)) == 1, "Mixed shard counts in phase!"
            assert len(steps) == step_shard_counts[0]
        else:
            # Create all of the job steps and commit them together.
            groups = self._shard_tests(phase_config['tests'], self.max_shards,
                                       test_stats, avg_test_time)
            steps = [
                self._create_jobstep(phase, phase_config, weight, test_list, len(groups))
                for weight, test_list in groups
                ]
            assert len(steps) == len(groups)
            db.session.commit()

        # Now that that database transaction is done, we'll do the slow work of
        # creating jenkins builds.
        for step in steps:
            self._create_jenkins_build(phase, step)
            sync_job_step.delay_if_needed(
                step_id=step.id.hex,
                task_id=step.id.hex,
                parent_task_id=phase.job.id.hex,
            )
Ejemplo n.º 8
0
    def _expand_job(self, phase, job_config):
        label = job_config.get('name') or md5(job_config['cmd']).hexdigest()

        step, created = get_or_create(JobStep, where={
            'job': phase.job,
            'project': phase.project,
            'phase': phase,
            'label': label,
        }, defaults={
            'data': {
                'cmd': job_config['cmd'],
                'job_name': self.job_name,
                'build_no': None,
                'expanded': True,
            },
            'status': Status.queued,
        })

        # TODO(dcramer): due to no unique constraints this section of code
        # presents a race condition when run concurrently
        if not step.data.get('build_no'):
            builder = self.get_builder()
            params = builder.get_job_parameters(
                step.job, changes_bid=step.id.hex, script=step.data['cmd'])

            success = False
            exn = None
            for _ in range(0, 3):
                try:
                    job_data = builder.create_job_from_params(
                        changes_bid=step.id.hex,
                        params=params,
                        job_name=step.data['job_name'],
                    )

                    step.data.update(job_data)
                    db.session.add(step)
                    db.session.commit()
                    success = True
                    break
                except Exception as ex:
                    logging.exception("Failed to create jobstep")
                    exn = ex

            if not success:
                step.status = Status.finished
                step.result = Result.infra_failed
                db.session.add(step)
                db.session.commit()
                if exn:
                    raise exn

        sync_job_step.delay_if_needed(
            step_id=step.id.hex,
            task_id=step.id.hex,
            parent_task_id=phase.job.id.hex,
        )
Ejemplo n.º 9
0
    def _expand_jobs(self, step, artifact):
        builder = self.get_builder()
        artifact_data = builder.fetch_artifact(step, artifact.data)
        phase_config = artifact_data.json()

        assert phase_config['cmd']
        assert '{test_names}' in phase_config['cmd']
        assert phase_config['tests']

        test_stats, avg_test_time = self.get_test_stats(step.project)

        phase, created = get_or_create(JobPhase,
                                       where={
                                           'job':
                                           step.job,
                                           'project':
                                           step.project,
                                           'label':
                                           phase_config.get('phase') or 'Test',
                                       },
                                       defaults={
                                           'status': Status.queued,
                                       })
        db.session.commit()

        # Check for whether a previous run of this task has already
        # created JobSteps for us, since doing it again would create a
        # double-sharded build.
        steps = JobStep.query.filter_by(phase_id=phase.id).all()
        if steps:
            step_shard_counts = [s.data.get('shard_count', 1) for s in steps]
            assert len(
                set(step_shard_counts)) == 1, "Mixed shard counts in phase!"
            assert len(steps) == step_shard_counts[0]
        else:
            # Create all of the job steps and commit them together.
            groups = self._shard_tests(phase_config['tests'], self.max_shards,
                                       test_stats, avg_test_time)
            steps = [
                self._create_jobstep(phase, phase_config, weight, test_list,
                                     len(groups))
                for weight, test_list in groups
            ]
            assert len(steps) == len(groups)
            db.session.commit()

        # Now that that database transaction is done, we'll do the slow work of
        # creating jenkins builds.
        for step in steps:
            self._create_jenkins_build(phase, step)
            sync_job_step.delay_if_needed(
                step_id=step.id.hex,
                task_id=step.id.hex,
                parent_task_id=phase.job.id.hex,
            )
Ejemplo n.º 10
0
    def _expand_job(self, phase, job_config):
        assert job_config['tests']

        test_names = ' '.join(job_config['tests'])
        label = md5(test_names).hexdigest()

        step, created = get_or_create(JobStep,
                                      where={
                                          'job': phase.job,
                                          'project': phase.project,
                                          'phase': phase,
                                          'label': label,
                                      },
                                      defaults={
                                          'data': {
                                              'cmd': job_config['cmd'],
                                              'path': job_config['path'],
                                              'tests': job_config['tests'],
                                              'expanded': True,
                                              'job_name': self.job_name,
                                              'build_no': None,
                                              'weight': job_config['weight']
                                          },
                                          'status': Status.queued,
                                      })

        # TODO(dcramer): due to no unique constraints this section of code
        # presents a race condition when run concurrently
        if not step.data.get('build_no'):
            builder = self.get_builder()
            params = builder.get_job_parameters(
                step.job,
                script=step.data['cmd'].format(test_names=test_names, ),
                target_id=step.id.hex,
                path=step.data['path'],
            )

            is_diff = not step.job.source.is_commit()
            job_data = builder.create_job_from_params(
                target_id=step.id.hex,
                params=params,
                job_name=step.data['job_name'],
                is_diff=is_diff)
            step.data.update(job_data)
            db.session.add(step)

        db.session.commit()

        sync_job_step.delay_if_needed(
            step_id=step.id.hex,
            task_id=step.id.hex,
            parent_task_id=phase.job.id.hex,
        )
Ejemplo n.º 11
0
    def _expand_job(self, phase, job_config):
        assert job_config['tests']

        test_names = ' '.join(job_config['tests'])
        label = md5(test_names).hexdigest()

        step, created = get_or_create(JobStep, where={
            'job': phase.job,
            'project': phase.project,
            'phase': phase,
            'label': label,
        }, defaults={
            'data': {
                'cmd': job_config['cmd'],
                'path': job_config['path'],
                'tests': job_config['tests'],
                'expanded': True,
                'job_name': self.job_name,
                'build_no': None,
                'weight': job_config['weight']
            },
            'status': Status.queued,
        })

        # TODO(dcramer): due to no unique constraints this section of code
        # presents a race condition when run concurrently
        if not step.data.get('build_no'):
            builder = self.get_builder()
            params = builder.get_job_parameters(
                step.job,
                script=step.data['cmd'].format(
                    test_names=test_names,
                ),
                target_id=step.id.hex,
                path=step.data['path'],
            )

            job_data = builder.create_job_from_params(
                target_id=step.id.hex,
                params=params,
                job_name=step.data['job_name'],
            )
            step.data.update(job_data)
            db.session.add(step)

        db.session.commit()

        sync_job_step.delay_if_needed(
            step_id=step.id.hex,
            task_id=step.id.hex,
            parent_task_id=phase.job.id.hex,
        )
Ejemplo n.º 12
0
 def create_replacement_jobstep(self, step):
     if not step.data.get("expanded", False):
         return self._setup_jobstep(step.phase, step.job, replaces=step)
     future_commands = map(FutureCommand.from_command, step.commands)
     future_jobstep = FutureJobStep(step.label, commands=future_commands)
     # we skip adding setup and teardown commands because these will already
     # be present in the old, failed JobStep.
     new_jobstep = self.create_expanded_jobstep(step, step.phase, future_jobstep, skip_setup_teardown=True)
     db.session.flush()
     step.replacement_id = new_jobstep.id
     db.session.add(step)
     db.session.commit()
     sync_job_step.delay_if_needed(
         step_id=new_jobstep.id.hex, task_id=new_jobstep.id.hex, parent_task_id=new_jobstep.job.id.hex
     )
     return new_jobstep
Ejemplo n.º 13
0
    def create_replacement_jobstep(self, step):
        if not step.data.get('expanded'):
            return super(JenkinsTestCollectorBuildStep, self).create_replacement_jobstep(step)
        newstep = self._create_jobstep(step.phase, step.data['cmd'], step.data['path'],
                                       step.data['weight'], step.data['tests'],
                                       step.data['shard_count'], force_create=True)
        step.replacement_id = newstep.id
        db.session.add(step)
        db.session.commit()

        self._create_jenkins_build(newstep)
        sync_job_step.delay_if_needed(
            step_id=newstep.id.hex,
            task_id=newstep.id.hex,
            parent_task_id=newstep.phase.job.id.hex,
        )
        return newstep
Ejemplo n.º 14
0
    def sync_step(self, step):
        if step.data.get('job_name') != self.job_name:
            return super(JenkinsFactoryBuilder, self).sync_step(step)

        job = step.job

        # for any downstream jobs, pull their results using xpath magic
        for downstream_job_name in self.downstream_job_names:
            downstream_build_nos = self._get_downstream_jobs(
                step, downstream_job_name)

            if not downstream_build_nos:
                continue

            phase, created = get_or_create(JobPhase,
                                           where={
                                               'job': job,
                                               'label': downstream_job_name,
                                           },
                                           defaults={
                                               'project_id': job.project_id,
                                           })
            db.session.commit()

            for build_no in downstream_build_nos:
                # XXX(dcramer): ideally we would grab this with the first query
                # but because we dont want to rely on an XML parser, we're doing
                # a second http request for build details
                downstream_step = self._create_job_step(
                    phase,
                    data={
                        'job_name': downstream_job_name,
                        'build_no': build_no,
                        'queued': False,
                        'master': step.data['master']
                    })

                db.session.commit()

                sync_job_step.delay_if_needed(
                    step_id=downstream_step.id.hex,
                    task_id=downstream_step.id.hex,
                    parent_task_id=job.id.hex,
                )

        return super(JenkinsFactoryBuilder, self).sync_step(step)
Ejemplo n.º 15
0
    def create_replacement_jobstep(self, step):
        if not step.data.get('expanded'):
            return super(JenkinsTestCollectorBuildStep, self).create_replacement_jobstep(step)
        newstep = self._create_jobstep(step.phase, step.data['cmd'], step.data['path'],
                                       step.data['weight'], step.data['tests'],
                                       step.data['shard_count'], force_create=True)
        step.replacement_id = newstep.id
        db.session.add(step)
        db.session.commit()

        self._create_jenkins_build(newstep)
        sync_job_step.delay_if_needed(
            step_id=newstep.id.hex,
            task_id=newstep.id.hex,
            parent_task_id=newstep.phase.job.id.hex,
        )
        return newstep
Ejemplo n.º 16
0
    def _expand_job(self, phase, job_config):
        label = job_config.get("name") or md5(job_config["cmd"]).hexdigest()

        step, created = get_or_create(
            JobStep,
            where={"job": phase.job, "project": phase.project, "phase": phase, "label": label},
            defaults={
                "data": {"cmd": job_config["cmd"], "job_name": self.job_name, "build_no": None, "expanded": True},
                "status": Status.queued,
            },
        )

        # TODO(dcramer): due to no unique constraints this section of code
        # presents a race condition when run concurrently
        if not step.data.get("build_no"):
            builder = self.get_builder()
            params = builder.get_job_parameters(step.job, changes_bid=step.id.hex, script=step.data["cmd"])

            success = False
            exn = None
            for _ in range(0, 3):
                try:
                    job_data = builder.create_job_from_params(
                        changes_bid=step.id.hex, params=params, job_name=step.data["job_name"]
                    )

                    step.data.update(job_data)
                    db.session.add(step)
                    db.session.commit()
                    success = True
                    break
                except Exception as ex:
                    logging.exception("Failed to create jobstep")
                    exn = ex

            if not success:
                step.status = Status.finished
                step.result = Result.infra_failed
                db.session.add(step)
                db.session.commit()
                if exn:
                    raise exn

        sync_job_step.delay_if_needed(step_id=step.id.hex, task_id=step.id.hex, parent_task_id=phase.job.id.hex)
Ejemplo n.º 17
0
 def create_replacement_jobstep(self, step):
     if not step.data.get('expanded', False):
         return self._setup_jobstep(step.phase, step.job, replaces=step)
     future_commands = map(FutureCommand.from_command, step.commands)
     future_jobstep = FutureJobStep(step.label, commands=future_commands)
     # we skip adding setup and teardown commands because these will already
     # be present in the old, failed JobStep.
     new_jobstep = self.create_expanded_jobstep(step, step.phase, future_jobstep,
                                skip_setup_teardown=True)
     db.session.flush()
     step.replacement_id = new_jobstep.id
     db.session.add(step)
     db.session.commit()
     sync_job_step.delay_if_needed(
         step_id=new_jobstep.id.hex,
         task_id=new_jobstep.id.hex,
         parent_task_id=new_jobstep.job.id.hex,
     )
     return new_jobstep
Ejemplo n.º 18
0
    def expand_command(self, command, expander, data):
        jobstep = command.jobstep
        phase_name = data.get('phase')
        if not phase_name:
            phase_count = db.session.query(
                func.count(),
            ).filter(
                JobPhase.job_id == jobstep.job_id,
            ).scalar()
            phase_name = 'Phase #{}'.format(phase_count)

        new_jobphase = JobPhase(
            job_id=jobstep.job_id,
            project_id=jobstep.project_id,
            label=phase_name,
            status=Status.queued,
        )
        db.session.add(new_jobphase)

        _, buildstep = JobPlan.get_build_step_for_job(jobstep.job_id)

        results = []
        for future_jobstep in expander.expand(max_executors=jobstep.data['max_executors'],
                                              test_stats_from=buildstep.get_test_stats_from()):
            new_jobstep = buildstep.create_expanded_jobstep(jobstep, new_jobphase, future_jobstep)
            results.append(new_jobstep)

        # If there are no tests to run, the phase is done.
        if len(results) == 0:
            new_jobphase.status = Status.finished
            new_jobphase.result = Result.passed
            db.session.add(new_jobphase)

        db.session.flush()

        for new_jobstep in results:
            sync_job_step.delay_if_needed(
                step_id=new_jobstep.id.hex,
                task_id=new_jobstep.id.hex,
                parent_task_id=new_jobphase.job.id.hex,
            )

        return results
Ejemplo n.º 19
0
    def expand_command(self, command, expander, data):
        jobstep = command.jobstep
        phase_name = data.get('phase')
        if not phase_name:
            phase_count = db.session.query(func.count(), ).filter(
                JobPhase.job_id == jobstep.job_id, ).scalar()
            phase_name = 'Phase #{}'.format(phase_count)

        new_jobphase = JobPhase(
            job_id=jobstep.job_id,
            project_id=jobstep.project_id,
            label=phase_name,
            status=Status.queued,
        )
        db.session.add(new_jobphase)

        _, buildstep = JobPlan.get_build_step_for_job(jobstep.job_id)

        results = []
        for future_jobstep in expander.expand(
                max_executors=jobstep.data['max_executors'],
                test_stats_from=buildstep.get_test_stats_from()):
            new_jobstep = buildstep.create_expanded_jobstep(
                jobstep, new_jobphase, future_jobstep)
            results.append(new_jobstep)

        # If there are no tests to run, the phase is done.
        if len(results) == 0:
            new_jobphase.status = Status.finished
            new_jobphase.result = Result.passed
            db.session.add(new_jobphase)

        db.session.flush()

        for new_jobstep in results:
            sync_job_step.delay_if_needed(
                step_id=new_jobstep.id.hex,
                task_id=new_jobstep.id.hex,
                parent_task_id=new_jobphase.job.id.hex,
            )

        return results
Ejemplo n.º 20
0
    def _expand_job(self, phase, job_config):
        label = job_config.get('name') or md5(job_config['cmd']).hexdigest()

        step, created = get_or_create(JobStep,
                                      where={
                                          'job': phase.job,
                                          'project': phase.project,
                                          'phase': phase,
                                          'label': label,
                                      },
                                      defaults={
                                          'data': {
                                              'cmd': job_config['cmd'],
                                              'job_name': self.job_name,
                                              'build_no': None,
                                          },
                                          'status': Status.queued,
                                      })

        # TODO(dcramer): due to no unique constraints this section of code
        # presents a race condition when run concurrently
        if not step.data.get('build_no'):
            builder = self.get_builder()
            params = builder.get_job_parameters(step.job,
                                                script=step.data['cmd'])

            job_data = builder.create_job_from_params(
                job_id=step.id.hex,
                params=params,
                job_name=step.data['job_name'],
            )
            step.data.update(job_data)
            db.session.add(step)
            db.session.commit()

        sync_job_step.delay_if_needed(
            step_id=step.id.hex,
            task_id=step.id.hex,
            parent_task_id=phase.job.id.hex,
        )
Ejemplo n.º 21
0
    def expand_command(self, command, expander, data):
        jobstep = command.jobstep
        phase_name = data.get('phase')
        if not phase_name:
            phase_count = db.session.query(
                func.count(),
            ).filter(
                JobPhase.job_id == jobstep.job_id,
            ).scalar()
            phase_name = 'Phase #{}'.format(phase_count)

        jobstep.data['expanded'] = True
        db.session.add(jobstep)

        new_jobphase = JobPhase(
            job_id=jobstep.job_id,
            project_id=jobstep.project_id,
            label=phase_name,
            status=Status.queued,
        )
        db.session.add(new_jobphase)

        _, buildstep = JobPlan.get_build_step_for_job(jobstep.job_id)

        results = []
        for future_jobstep in expander.expand(max_executors=jobstep.data['max_executors']):
            new_jobstep = buildstep.expand_jobstep(jobstep, new_jobphase, future_jobstep)
            results.append(new_jobstep)

        db.session.flush()

        for new_jobstep in results:
            sync_job_step.delay_if_needed(
                step_id=new_jobstep.id.hex,
                task_id=new_jobstep.id.hex,
                parent_task_id=new_jobphase.job.id.hex,
            )

        return results
Ejemplo n.º 22
0
    def expand_command(self, command, expander, data):
        jobstep = command.jobstep
        phase_name = data.get('phase')
        if not phase_name:
            phase_count = db.session.query(func.count(), ).filter(
                JobPhase.job_id == jobstep.job_id, ).scalar()
            phase_name = 'Phase #{}'.format(phase_count)

        jobstep.data['expanded'] = True
        db.session.add(jobstep)

        new_jobphase = JobPhase(
            job_id=jobstep.job_id,
            project_id=jobstep.project_id,
            label=phase_name,
            status=Status.queued,
        )
        db.session.add(new_jobphase)

        _, buildstep = JobPlan.get_build_step_for_job(jobstep.job_id)

        results = []
        for future_jobstep in expander.expand(
                max_executors=jobstep.data['max_executors']):
            new_jobstep = buildstep.expand_jobstep(jobstep, new_jobphase,
                                                   future_jobstep)
            results.append(new_jobstep)

        db.session.flush()

        for new_jobstep in results:
            sync_job_step.delay_if_needed(
                step_id=new_jobstep.id.hex,
                task_id=new_jobstep.id.hex,
                parent_task_id=new_jobphase.job.id.hex,
            )

        return results
Ejemplo n.º 23
0
    def _expand_jobs(self, step, artifact):
        builder = self.get_builder()
        artifact_data = builder.fetch_artifact(step, artifact.data)
        phase_config = artifact_data.json()

        assert phase_config['cmd']
        assert '{test_names}' in phase_config['cmd']
        assert phase_config['tests']

        test_stats, avg_test_time = self.get_test_stats(step.project)

        groups = self._shard_tests(phase_config['tests'], self.num_shards, test_stats, avg_test_time)

        phase, created = get_or_create(JobPhase, where={
            'job': step.job,
            'project': step.project,
            'label': phase_config.get('phase') or 'Test',
        }, defaults={
            'status': Status.queued,
        })
        db.session.commit()

        assert len(groups) == self.num_shards

        # Create all of the job steps and commit them together.
        steps = [self._create_jobstep(phase, phase_config, weight, test_list)
                 for weight, test_list in groups]
        db.session.commit()

        # Now that that database transaction is done, we'll do the slow work of
        # creating jenkins builds.
        for step in steps:
            self._create_jenkins_build(phase, step)
            sync_job_step.delay_if_needed(
                step_id=step.id.hex,
                task_id=step.id.hex,
                parent_task_id=phase.job.id.hex,
            )
Ejemplo n.º 24
0
    def _sync_phased_results(self, step, artifacts):
        # due to the limitations of Jenkins and our requirement to have more
        # insight into the actual steps a build process takes and unfortunately
        # the best way to do this is to rewrite history within Changes
        job = step.job
        is_diff = not job.source.is_commit()
        project = step.project

        artifacts_by_name = dict((a["fileName"], a) for a in artifacts)
        pending_artifacts = set(artifacts_by_name.keys())

        phase_steps = set()
        phase_step_data = {
            "job_name": step.data["job_name"],
            "build_no": step.data["build_no"],
            "generated": True,
            # TODO: _pick_master here seems very suspicious.
            "master": self._pick_master(step.data["job_name"], is_diff),
        }

        phases = set()

        # fetch each phase and create it immediately (as opposed to async)
        for artifact_data in artifacts:
            artifact_filename = artifact_data["fileName"]

            if not artifact_filename.endswith("phase.json"):
                continue

            pending_artifacts.remove(artifact_filename)

            resp = self.fetch_artifact(step, artifact_data)
            phase_data = resp.json()

            if phase_data["retcode"]:
                result = Result.failed
            else:
                result = Result.passed

            date_started = datetime.utcfromtimestamp(phase_data["startTime"])
            date_finished = datetime.utcfromtimestamp(phase_data["endTime"])

            jobphase, created = get_or_create(
                JobPhase,
                where={"job": job, "label": phase_data["name"]},
                defaults={
                    "project": project,
                    "result": result,
                    "status": Status.finished,
                    "date_started": date_started,
                    "date_finished": date_finished,
                },
            )
            phases.add(jobphase)

            jobstep, created = get_or_create(
                JobStep,
                where={"phase": jobphase, "label": step.label},
                defaults={
                    "job": job,
                    "node": step.node,
                    "project": project,
                    "result": result,
                    "status": Status.finished,
                    "date_started": date_started,
                    "date_finished": date_finished,
                    "data": phase_step_data,
                },
            )
            sync_job_step.delay_if_needed(task_id=jobstep.id.hex, parent_task_id=job.id.hex, step_id=jobstep.id.hex)
            phase_steps.add(jobstep)

            # capture the log if available
            try:
                log_artifact = artifacts_by_name[phase_data["log"]]
            except KeyError:
                self.logger.warning("Unable to find logfile for phase: %s", phase_data)
            else:
                pending_artifacts.remove(log_artifact["fileName"])

                self._handle_generic_artifact(jobstep=jobstep, artifact=log_artifact, skip_checks=True)

        # ideally we don't mark the base step as a failure if any of the phases
        # report more correct results
        if phases and step.result == Result.failed and any(p.result == Result.failed for p in phases):
            step.result = Result.passed
            db.session.add(step)

        if not pending_artifacts:
            return

        # Alias to clarify that this is the JobStep that actually ran on a slave.
        original_step = step
        # all remaining artifacts get bound to the final phase
        final_step = sorted(phase_steps, key=lambda x: x.date_finished, reverse=True)[0]
        for artifact_name in pending_artifacts:
            # Manifest files are associated with the original step so we can validate that the ID is correct.
            responsible_step = original_step if ManifestJsonHandler.can_process(artifact_name) else final_step
            self._handle_generic_artifact(jobstep=responsible_step, artifact=artifacts_by_name[artifact_name])
Ejemplo n.º 25
0
    def _sync_phased_results(self, step, artifacts):
        # due to the limitations of Jenkins and our requirement to have more
        # insight into the actual steps a build process takes and unfortunately
        # the best way to do this is to rewrite history within Changes
        job = step.job
        project = step.project

        artifacts_by_name = dict((a['fileName'], a) for a in artifacts)
        pending_artifacts = set(artifacts_by_name.keys())

        phase_steps = set()
        phase_step_data = {
            'job_name': step.data['job_name'],
            'build_no': step.data['build_no'],
            'generated': True,
        }

        phases = set()

        # fetch each phase and create it immediately (as opposed to async)
        for artifact_data in artifacts:
            artifact_filename = artifact_data['fileName']

            if not artifact_filename.endswith('phase.json'):
                continue

            pending_artifacts.remove(artifact_filename)

            resp = self.fetch_artifact(step, artifact_data)
            phase_data = resp.json()

            if phase_data['retcode']:
                result = Result.failed
            else:
                result = Result.passed

            date_started = datetime.utcfromtimestamp(phase_data['startTime'])
            date_finished = datetime.utcfromtimestamp(phase_data['endTime'])

            jobphase, created = get_or_create(JobPhase,
                                              where={
                                                  'job': job,
                                                  'label': phase_data['name'],
                                              },
                                              defaults={
                                                  'project': project,
                                                  'result': result,
                                                  'status': Status.finished,
                                                  'date_started': date_started,
                                                  'date_finished':
                                                  date_finished,
                                              })
            phases.add(jobphase)

            jobstep, created = get_or_create(JobStep,
                                             where={
                                                 'phase': jobphase,
                                                 'label': step.label,
                                             },
                                             defaults={
                                                 'job': job,
                                                 'node': step.node,
                                                 'project': project,
                                                 'result': result,
                                                 'status': Status.finished,
                                                 'date_started': date_started,
                                                 'date_finished':
                                                 date_finished,
                                                 'data': phase_step_data,
                                             })
            sync_job_step.delay_if_needed(
                task_id=jobstep.id.hex,
                parent_task_id=job.id.hex,
                step_id=jobstep.id.hex,
            )
            phase_steps.add(jobstep)

            # capture the log if available
            try:
                log_artifact = artifacts_by_name[phase_data['log']]
            except KeyError:
                self.logger.warning('Unable to find logfile for phase: %s',
                                    phase_data)
            else:
                pending_artifacts.remove(log_artifact['fileName'])

                self._handle_generic_artifact(
                    jobstep=jobstep,
                    artifact=log_artifact,
                    skip_checks=True,
                )

        # ideally we don't mark the base step as a failure if any of the phases
        # report more correct results
        if phases and step.result == Result.failed and any(
                p.result == Result.failed for p in phases):
            step.result = Result.passed
            db.session.add(step)

        if not pending_artifacts:
            return

        # all remaining artifacts get bound to the final phase
        final_step = sorted(phase_steps,
                            key=lambda x: x.date_finished,
                            reverse=True)[0]
        for artifact_name in pending_artifacts:
            self._handle_generic_artifact(
                jobstep=final_step,
                artifact=artifacts_by_name[artifact_name],
            )
Ejemplo n.º 26
0
    def _sync_phased_results(self, step, artifacts):
        # due to the limitations of Jenkins and our requirement to have more
        # insight into the actual steps a build process takes and unfortunately
        # the best way to do this is to rewrite history within Changes
        job = step.job
        project = step.project

        artifacts_by_name = dict(
            (a['fileName'], a)
            for a in artifacts
        )
        pending_artifacts = set(artifacts_by_name.keys())

        phase_steps = set()
        phase_step_data = {
            'job_name': step.data['job_name'],
            'build_no': step.data['build_no'],
            'generated': True,
            'master': self._pick_master(step.data['job_name']),
        }

        phases = set()

        # fetch each phase and create it immediately (as opposed to async)
        for artifact_data in artifacts:
            artifact_filename = artifact_data['fileName']

            if not artifact_filename.endswith('phase.json'):
                continue

            pending_artifacts.remove(artifact_filename)

            resp = self.fetch_artifact(step, artifact_data)
            phase_data = resp.json()

            if phase_data['retcode']:
                result = Result.failed
            else:
                result = Result.passed

            date_started = datetime.utcfromtimestamp(phase_data['startTime'])
            date_finished = datetime.utcfromtimestamp(phase_data['endTime'])

            jobphase, created = get_or_create(JobPhase, where={
                'job': job,
                'label': phase_data['name'],
            }, defaults={
                'project': project,
                'result': result,
                'status': Status.finished,
                'date_started': date_started,
                'date_finished': date_finished,
            })
            phases.add(jobphase)

            jobstep, created = get_or_create(JobStep, where={
                'phase': jobphase,
                'label': step.label,
            }, defaults={
                'job': job,
                'node': step.node,
                'project': project,
                'result': result,
                'status': Status.finished,
                'date_started': date_started,
                'date_finished': date_finished,
                'data': phase_step_data,
            })
            sync_job_step.delay_if_needed(
                task_id=jobstep.id.hex,
                parent_task_id=job.id.hex,
                step_id=jobstep.id.hex,
            )
            phase_steps.add(jobstep)

            # capture the log if available
            try:
                log_artifact = artifacts_by_name[phase_data['log']]
            except KeyError:
                self.logger.warning('Unable to find logfile for phase: %s', phase_data)
            else:
                pending_artifacts.remove(log_artifact['fileName'])

                self._handle_generic_artifact(
                    jobstep=jobstep,
                    artifact=log_artifact,
                    skip_checks=True,
                )

        # ideally we don't mark the base step as a failure if any of the phases
        # report more correct results
        if phases and step.result == Result.failed and any(p.result == Result.failed for p in phases):
            step.result = Result.passed
            db.session.add(step)

        if not pending_artifacts:
            return

        # all remaining artifacts get bound to the final phase
        final_step = sorted(phase_steps, key=lambda x: x.date_finished, reverse=True)[0]
        for artifact_name in pending_artifacts:
            self._handle_generic_artifact(
                jobstep=final_step,
                artifact=artifacts_by_name[artifact_name],
            )
Ejemplo n.º 27
0
    def expand_jobs(self, step, phase_config):
        """
        Creates and runs JobSteps for a set of tests, based on a phase config.

        This phase config comes from a tests.json file that the collection
        jobstep should generate. This method is then called by the TestsJsonHandler.
        """
        if not phase_config.get('cmd'):
            raise ArtifactParseError('No cmd attribute')
        if '{test_names}' not in phase_config['cmd']:
            raise ArtifactParseError('No {test_names} in cmd')
        if 'tests' not in phase_config:
            raise ArtifactParseError('No tests attribute')

        num_tests = len(phase_config['tests'])
        test_stats, avg_test_time = TestsExpander.get_test_stats(
            self.get_test_stats_from() or step.project.slug)

        phase, _ = get_or_create(JobPhase,
                                 where={
                                     'job': step.job,
                                     'project': step.project,
                                     'label': phase_config.get('phase')
                                     or 'Test',
                                 },
                                 defaults={'status': Status.queued})
        db.session.commit()

        # If there are no tests to run, the phase is done.
        if num_tests == 0:
            phase.status = Status.finished
            phase.result = Result.passed
            db.session.add(phase)
            db.session.commit()
            return

        # Check for whether a previous run of this task has already
        # created JobSteps for us, since doing it again would create a
        # double-sharded build.
        steps = JobStep.query.filter_by(phase_id=phase.id,
                                        replacement_id=None).all()
        if steps:
            step_shard_counts = [s.data.get('shard_count', 1) for s in steps]
            if len(set(step_shard_counts)) != 1:
                raise Exception("Mixed shard counts in phase!")
            elif len(steps) != step_shard_counts[0]:
                raise Exception("Shard count incorrect")
        else:
            # Create all of the job steps and commit them together.
            groups = shard(
                phase_config['tests'],
                self.max_shards,
                test_stats,
                avg_test_time,
                normalize_object_name=TestsExpander._normalize_test_segments)
            steps = [
                self._create_jobstep(phase,
                                     phase_config['cmd'],
                                     phase_config.get('path', ''),
                                     weight,
                                     test_list,
                                     len(groups),
                                     cluster=step.cluster)
                for weight, test_list in groups
            ]
            if len(steps) != len(groups):
                raise Exception("Didn't create correct number of shards")
            db.session.commit()

        # Now that the database transaction is done, we'll do the slow work of
        # creating jenkins builds.
        for step in steps:
            self._create_jenkins_build(step)
            sync_job_step.delay_if_needed(
                step_id=step.id.hex,
                task_id=step.id.hex,
                parent_task_id=phase.job.id.hex,
            )