Beispiel #1
0
class EventDependency(FreshmakerBase):
    __tablename__ = "event_dependencies"
    id = db.Column(db.Integer, primary_key=True)
    event_id = db.Column(db.Integer,
                         db.ForeignKey('events.id'),
                         nullable=False)
    event_dependency_id = db.Column(db.Integer,
                                    db.ForeignKey('events.id'),
                                    nullable=False)
Beispiel #2
0
class ArtifactBuildCompose(FreshmakerBase):
    __tablename__ = 'artifact_build_composes'

    build_id = db.Column(db.Integer,
                         db.ForeignKey('artifact_builds.id'),
                         primary_key=True)

    compose_id = db.Column(db.Integer,
                           db.ForeignKey('composes.id'),
                           primary_key=True)

    build = db.relationship('ArtifactBuild', back_populates='composes')
    compose = db.relationship('Compose', back_populates='builds')
Beispiel #3
0
class ArtifactBuild(FreshmakerBase):
    __tablename__ = "artifact_builds"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String, nullable=False)
    original_nvr = db.Column(db.String, nullable=True)
    rebuilt_nvr = db.Column(db.String, nullable=True)
    type = db.Column(db.Integer)
    state = db.Column(db.Integer, nullable=False)
    state_reason = db.Column(db.String, nullable=True)
    time_submitted = db.Column(db.DateTime, nullable=False)
    time_completed = db.Column(db.DateTime)

    # Link to the Artifact on which this one depends and which triggered
    # the rebuild of this Artifact.
    dep_on_id = db.Column(db.Integer, db.ForeignKey('artifact_builds.id'))
    dep_on = relationship('ArtifactBuild', remote_side=[id])

    # Event associated with this Build
    event_id = db.Column(db.Integer, db.ForeignKey('events.id'))
    event = relationship("Event", back_populates="builds")

    # Id of corresponding real build in external build system. Currently, it
    # could be ID of a build in MBS or Koji, maybe others in the future.
    # build_id may be NULL, which means this build has not been built in
    # external build system.
    build_id = db.Column(db.Integer)

    # Build args in json format.
    build_args = db.Column(db.String, nullable=True)

    # The reason why this artifact is rebuilt. Set according to
    # `freshmaker.types.RebuildReason`.
    rebuild_reason = db.Column(db.Integer, nullable=True)

    # pullspec overrides
    _bundle_pullspec_overrides = db.Column("bundle_pullspec_overrides",
                                           db.Text,
                                           nullable=True)

    composes = db.relationship('ArtifactBuildCompose', back_populates='build')

    @classmethod
    def create(cls,
               session,
               event,
               name,
               type,
               build_id=None,
               dep_on=None,
               state=None,
               original_nvr=None,
               rebuilt_nvr=None,
               rebuild_reason=0):

        now = datetime.utcnow()
        build = cls(name=name,
                    original_nvr=original_nvr,
                    rebuilt_nvr=rebuilt_nvr,
                    type=type,
                    event=event,
                    state=state or ArtifactBuildState.BUILD.value,
                    build_id=build_id,
                    time_submitted=now,
                    dep_on=dep_on,
                    rebuild_reason=rebuild_reason)
        session.add(build)
        return build

    @validates('state')
    def validate_state(self, key, field):
        if field in [s.value for s in list(ArtifactBuildState)]:
            return field
        if field in [s.name.lower() for s in list(ArtifactBuildState)]:
            return ArtifactBuildState[field.upper()].value
        if isinstance(field, ArtifactBuildState):
            return field.value
        raise ValueError("%s: %s, not in %r" %
                         (key, field, list(ArtifactBuildState)))

    @validates('type')
    def validate_type(self, key, field):
        if field in [t.value for t in list(ArtifactType)]:
            return field
        if field in [t.name.lower() for t in list(ArtifactType)]:
            return ArtifactType[field.upper()].value
        if isinstance(field, ArtifactType):
            return field.value
        raise ValueError("%s: %s, not in %r" %
                         (key, field, list(ArtifactType)))

    @classmethod
    def get_lowest_build_id(cls, session):
        """
        Returns the lowest build_id. If there is no build so far,
        returns 0.
        """
        build = (
            session.query(ArtifactBuild).filter(cls.build_id != None)  # noqa
            .order_by(ArtifactBuild.build_id.asc()).first())
        if not build:
            return 0
        return build.build_id

    @property
    def bundle_pullspec_overrides(self):
        """Return the Python representation of the JSON bundle_pullspec_overrides."""
        return (json.loads(self._bundle_pullspec_overrides)
                if self._bundle_pullspec_overrides else None)

    @bundle_pullspec_overrides.setter
    def bundle_pullspec_overrides(self, bundle_pullspec_overrides):
        """
        Set the bundle_pullspec_overrides column to the input bundle_pullspec_overrides as a JSON string.
        If ``None`` is provided, it will be simply set to ``None`` and not be converted to JSON.
        :param dict bundle_pullspec_overrides: the dictionary of the bundle_pullspec_overrides or ``None``
        """
        self._bundle_pullspec_overrides = (json.dumps(
            bundle_pullspec_overrides,
            sort_keys=True) if bundle_pullspec_overrides is not None else None)

    def depending_artifact_builds(self):
        """
        Returns list of artifact builds depending on this one.
        """
        return ArtifactBuild.query.filter_by(dep_on_id=self.id).all()

    def transition(self, state, state_reason):
        """
        Sets the state and state_reason of this ArtifactBuild.

        :param state: ArtifactBuildState value
        :param state_reason: Reason why this state has been set.
        :return: True/False, whether state was changed
        """
        # Convert state from its possible representation to number.
        state = self.validate_state("state", state)

        # Log the state and state_reason
        if state == ArtifactBuildState.FAILED.value:
            log_fnc = log.error
        else:
            log_fnc = log.info
        log_fnc("Artifact build %r moved to state %s, %r" %
                (self, ArtifactBuildState(state).name, state_reason))

        if self.state == state:
            return False

        self.state = state
        if ArtifactBuildState(state).counter:
            ArtifactBuildState(state).counter.inc()

        self.state_reason = state_reason
        if self.state in [
                ArtifactBuildState.DONE.value, ArtifactBuildState.FAILED.value,
                ArtifactBuildState.CANCELED.value
        ]:
            self.time_completed = datetime.utcnow()

        # For FAILED/CANCELED states, move also all the artifacts depending
        # on this one to FAILED/CANCELED state, because there is no way we
        # can rebuild them.
        if self.state in [
                ArtifactBuildState.FAILED.value,
                ArtifactBuildState.CANCELED.value
        ]:
            for build in self.depending_artifact_builds():
                build.transition(
                    self.state, "Cannot build artifact, because its "
                    "dependency cannot be built.")

        messaging.publish('build.state.changed', self.json())

        return True

    def __repr__(self):
        return "<ArtifactBuild %s, type %s, state %s, event %s>" % (
            self.name, ArtifactType(self.type).name,
            ArtifactBuildState(self.state).name, self.event.message_id)

    def json(self):
        build_args = {}
        if self.build_args:
            build_args = json.loads(self.build_args)

        build_url = get_url_for('build', id=self.id)
        db.session.add(self)
        return {
            "id": self.id,
            "name": self.name,
            "original_nvr": self.original_nvr,
            "rebuilt_nvr": self.rebuilt_nvr,
            "type": self.type,
            "type_name": ArtifactType(self.type).name,
            "state": self.state,
            "state_name": ArtifactBuildState(self.state).name,
            "state_reason": self.state_reason,
            "dep_on": self.dep_on.name if self.dep_on else None,
            "dep_on_id": self.dep_on.id if self.dep_on else None,
            "time_submitted": _utc_datetime_to_iso(self.time_submitted),
            "time_completed": _utc_datetime_to_iso(self.time_completed),
            "event_id": self.event_id,
            "build_id": self.build_id,
            "url": build_url,
            "build_args": build_args,
            "odcs_composes":
            [rel.compose.odcs_compose_id for rel in self.composes],
            "rebuild_reason": RebuildReason(self.rebuild_reason
                                            or 0).name.lower()
        }

    def get_root_dep_on(self):
        dep_on = self.dep_on
        while dep_on:
            dep = dep_on.dep_on
            if dep:
                dep_on = dep
            else:
                break
        return dep_on

    def add_composes(self, session, composes):
        """Add an ODCS compose to this build"""
        for compose in composes:
            session.add(
                ArtifactBuildCompose(build_id=self.id, compose_id=compose.id))

    @property
    def composes_ready(self):
        """Check if composes this build has have been done in ODCS"""
        return all((rel.compose.finished for rel in self.composes))