예제 #1
0
class Landing(db.Model):
    """DEPRECATED

    This model has been replaced by landoapi.models.transplant.Transplant
    and has been kept around as part of data migration. In the future when
    migration has been completed this may been cleaned up and dropped from
    the database with another migration.
    """
    __tablename__ = "landings"

    id = db.Column(db.Integer, primary_key=True)
    request_id = db.Column(db.Integer, unique=True)
    revision_id = db.Column(db.Integer)
    diff_id = db.Column(db.Integer)
    active_diff_id = db.Column(db.Integer)
    status = db.Column(db.Enum(LandingStatus),
                       nullable=False,
                       default=LandingStatus.aborted)
    error = db.Column(db.Text(), default='')
    result = db.Column(db.Text(), default='')
    requester_email = db.Column(db.String(254))
    tree = db.Column(db.String(128))
    created_at = db.Column(db.DateTime(timezone=True),
                           nullable=False,
                           default=db.func.now())
    updated_at = db.Column(db.DateTime(timezone=True),
                           nullable=False,
                           default=db.func.now(),
                           onupdate=db.func.now())
예제 #2
0
파일: base.py 프로젝트: zzzeid/lando-api
class Base(db.Model):
    """An abstract base model that provides common methods and columns.
    """

    __abstract__ = True

    id = db.Column(db.Integer, primary_key=True)
    created_at = db.Column(db.DateTime(timezone=True),
                           nullable=False,
                           default=db.func.now())
    updated_at = db.Column(
        db.DateTime(timezone=True),
        nullable=False,
        default=db.func.now(),
        onupdate=db.func.now(),
    )

    @declared_attr
    def __tablename__(self):
        """Return a snake-case version of the class name as the table name.

        To override __tablename__, define this attribute as needed on your
        model.
        """
        return table_name_re.sub(r"_\1", self.__name__).lower()

    def __repr__(self):
        """Return a human-readable representation of the instance.

        For example, `<Transplant: 1235>`.
        """
        return f"<{self.__class__.__name__}: {self.id}>"
class Transplant(db.Model):
    """Represents a request to Autoland Transplant."""

    __tablename__ = "transplants"

    # Internal request ID.
    id = db.Column(db.Integer, primary_key=True)

    # Autoland Transplant request ID.
    request_id = db.Column(db.Integer, unique=True)

    status = db.Column(db.Enum(TransplantStatus),
                       nullable=False,
                       default=TransplantStatus.aborted)
    created_at = db.Column(db.DateTime(timezone=True),
                           nullable=False,
                           default=db.func.now())
    updated_at = db.Column(
        db.DateTime(timezone=True),
        nullable=False,
        default=db.func.now(),
        onupdate=db.func.now(),
    )

    # JSON object mapping string revision id of the form "<int>" (used because
    # json keys may not be integers) to integer diff id. This is used to
    # record the diff id used with each revision and make searching for
    # Transplants that match a set of revisions easy (such as those
    # in a stack).
    # e.g.
    #     {
    #         "1001": 1221,
    #         "1002": 1246,
    #         "1003": 1412
    #     }
    revision_to_diff_id = db.Column(JSONB, nullable=False)

    # JSON array of string revision ids of the form "<int>" (used to match
    # the string type of revision_to_diff_id keys) listing the order
    # of the revisions in the request from most ancestral to most
    # descendant.
    # e.g.
    #     ["1001", "1002", "1003"]
    revision_order = db.Column(JSONB, nullable=False)

    # Text describing errors when not landed.
    error = db.Column(db.Text(), default="")

    # Revision (sha) of the head of the push.
    result = db.Column(db.Text(), default="")

    # LDAP email of the user who requested transplant.
    requester_email = db.Column(db.String(254))

    # URL of the repository revisions are to land to.
    repository_url = db.Column(db.Text(), default="")

    # Treestatus tree name the revisions are to land to.
    tree = db.Column(db.String(128))

    def __repr__(self):
        return "<Transplant: %s>" % self.id

    def update_from_transplant(self, landed, error="", result=""):
        """Set the status from pingback request."""
        self.error = error
        self.result = result
        if not landed:
            self.status = (TransplantStatus.failed
                           if error else TransplantStatus.submitted)
        else:
            self.status = TransplantStatus.landed

    @property
    def landing_path(self):
        return [(int(r), self.revision_to_diff_id[r])
                for r in self.revision_order]

    @property
    def head_revision(self):
        """Human-readable representation of the branch head's Phabricator revision ID.
        """
        assert (
            self.revision_order
        ), "head_revision should never be called without setting self.revision_order!"
        return "D" + self.revision_order[-1]

    @classmethod
    def revisions_query(cls, revisions):
        revisions = [str(int(r)) for r in revisions]
        return cls.query.filter(
            cls.revision_to_diff_id.has_any(array(revisions)))

    def serialize(self):
        """Return a JSON compatible dictionary."""
        return {
            "id":
            self.id,
            "request_id":
            self.request_id,
            "status":
            self.status.value,
            "landing_path": [{
                "revision_id": "D{}".format(r),
                "diff_id": self.revision_to_diff_id[r]
            } for r in self.revision_order],
            "details":
            (self.error or self.result if self.status
             in (TransplantStatus.failed,
                 TransplantStatus.aborted) else self.result or self.error),
            "requester_email":
            self.requester_email,
            "tree":
            self.tree,
            "repository_url":
            self.repository_url,
            "created_at":
            (self.created_at.astimezone(datetime.timezone.utc).isoformat()),
            "updated_at":
            (self.updated_at.astimezone(datetime.timezone.utc).isoformat()),
        }
예제 #4
0
class Transplant(db.Model):
    """Represents a request to Autoland Transplant."""
    __tablename__ = "transplants"

    # Internal request ID.
    id = db.Column(db.Integer, primary_key=True)

    # Autoland Transplant request ID.
    request_id = db.Column(db.Integer, unique=True)

    status = db.Column(db.Enum(TransplantStatus),
                       nullable=False,
                       default=TransplantStatus.aborted)
    created_at = db.Column(db.DateTime(timezone=True),
                           nullable=False,
                           default=db.func.now())
    updated_at = db.Column(db.DateTime(timezone=True),
                           nullable=False,
                           default=db.func.now(),
                           onupdate=db.func.now())

    # JSON object mapping string revision id of the form "<int>" (used because
    # json keys may not be integers) to integer diff id. This is used to
    # record the diff id used with each revision and make searching for
    # Transplants that match a set of revisions easy (such as those
    # in a stack).
    # e.g.
    #     {
    #         "1001": 1221,
    #         "1002": 1246,
    #         "1003": 1412
    #     }
    revision_to_diff_id = db.Column(JSONB, nullable=False)

    # JSON array of string revision ids of the form "<int>" (used to match
    # the string type of revision_to_diff_id keys) listing the order
    # of the revisions in the request from most ancestral to most
    # descendant.
    # e.g.
    #     ["1001", "1002", "1003"]
    revision_order = db.Column(JSONB, nullable=False)

    # Text describing errors when not landed.
    error = db.Column(db.Text(), default='')

    # Revision (sha) of the head of the push.
    result = db.Column(db.Text(), default='')

    # LDAP email of the user who requested transplant.
    requester_email = db.Column(db.String(254))

    # URL of the repository revisions are to land to.
    repository_url = db.Column(db.Text(), default='')

    # Treestatus tree name the revisions are to land to.
    tree = db.Column(db.String(128))

    def __repr__(self):
        return '<Transplant: %s>' % self.id

    def update_from_transplant(self, landed, error='', result=''):
        """Set the status from pingback request."""
        self.error = error
        self.result = result
        if not landed:
            self.status = (TransplantStatus.failed
                           if error else TransplantStatus.submitted)
        else:
            self.status = TransplantStatus.landed

    @property
    def landing_path(self):
        return [(int(r), self.revision_to_diff_id[r])
                for r in self.revision_order]

    @classmethod
    def revisions_query(cls, revisions):
        revisions = [str(int(r)) for r in revisions]
        return cls.query.filter(
            cls.revision_to_diff_id.has_any(array(revisions)))

    @classmethod
    def is_revision_submitted(cls, revision_id):
        """Check if revision is successfully submitted.

        Args:
            revision_id: The integer id of the revision.

        Returns:
            Transplant object or False if not submitted.
        """
        transplants = cls.revisions_query(
            [revision_id]).filter_by(status=TransplantStatus.submitted).all()

        if not transplants:
            return False

        return transplants[0]

    @classmethod
    def legacy_latest_landed(cls, revision_id):
        """DEPRECATED Return the latest Landing that is landed, or None.

        Args:
            revision_id: The integer id of the revision.

        Returns:
            Latest transplant object with status landed, or None if
            none exist.
        """
        return cls.revisions_query(
            [revision_id]).filter_by(status=TransplantStatus.landed).order_by(
                cls.updated_at.desc()).first()

    def serialize(self):
        """Return a JSON compatible dictionary."""
        return {
            'id': self.id,
            'request_id': self.request_id,
            'status': self.status.value,
            'landing_path': [
                {
                    'revision_id': 'D{}'.format(r),
                    'diff_id': self.revision_to_diff_id[r],
                } for r in self.revision_order
            ],
            'details': (
                self.error or self.result
                if self.status in (
                    TransplantStatus.failed,
                    TransplantStatus.aborted
                )
                else self.result or self.error
            ),
            'requester_email': self.requester_email,
            'tree': self.tree,
            'repository_url': self.repository_url,
            'created_at': (
                self.created_at.astimezone(datetime.timezone.utc).isoformat()
            ),
            'updated_at': (
                self.updated_at.astimezone(datetime.timezone.utc).isoformat()
            ),
        }  # yapf: disable

    def legacy_serialize(self):
        """DEPRECATED Serialize to JSON compatible dictionary."""

        revision_id = None
        diff_id = None
        if self.revision_order is not None:
            revision_id = self.revision_order[-1]

        if (revision_id is not None and self.revision_to_diff_id is not None):
            diff_id = self.revision_to_diff_id.get(revision_id)

        return {
            'id': self.id,
            'revision_id': 'D{}'.format(revision_id),
            'request_id': self.request_id,
            'diff_id': diff_id,
            'active_diff_id': diff_id,
            'status': self.status.value,
            'error_msg': self.error,
            'result': self.result,
            'requester_email': self.requester_email,
            'tree': self.tree,
            'tree_url': self.repository_url or '',
            'created_at': (
                self.created_at.astimezone(datetime.timezone.utc).isoformat()
            ),
            'updated_at': (
                self.updated_at.astimezone(datetime.timezone.utc).isoformat()
            ),
        }  # yapf: disable
예제 #5
0
class Landing(db.Model):
    """Represents the landing process in Autoland.

    Landing is communicating with Autoland via TransplantClient.
    Landing is communicating with Phabricator via PhabricatorClient.
    Landing object might be saved to database without creation of the actual
    landing in Autoland. It is done before landing request to construct
    required "pingback URL" and save related Patch objects.
    To update the Landing status Transplant is calling provided pingback URL.
    Active Diff Id is stored on creation if it is different than diff_id.

    Attributes:
        id: Primary Key
        request_id: Id of the request in Autoland
        revision_id: Phabricator id of the revision to be landed
        diff_id: Phabricator id of the diff to be landed
        active_diff_id: Phabricator id of the diff active at the moment of
            landing
        status: Status of the landing. Modified by `update` API
        error: Text describing the error if not landed
        result: Revision (sha) of push
        requester_email: The email address of the requester of the landing.
        tree: The treestatus tree name the revision is to land to.
        created_at: DateTime of the creation
        updated_at: DateTime of the last save
    """
    __tablename__ = "landings"

    id = db.Column(db.Integer, primary_key=True)
    request_id = db.Column(db.Integer, unique=True)
    revision_id = db.Column(db.Integer)
    diff_id = db.Column(db.Integer)
    active_diff_id = db.Column(db.Integer)
    status = db.Column(db.Enum(LandingStatus),
                       nullable=False,
                       default=LandingStatus.aborted)
    error = db.Column(db.Text(), default='')
    result = db.Column(db.Text(), default='')
    requester_email = db.Column(db.String(254))
    tree = db.Column(db.String(128))
    created_at = db.Column(db.DateTime(timezone=True),
                           nullable=False,
                           default=db.func.now())
    updated_at = db.Column(db.DateTime(timezone=True),
                           nullable=False,
                           default=db.func.now(),
                           onupdate=db.func.now())

    @classmethod
    def is_revision_submitted(cls, revision_id):
        """Check if revision is successfully submitted.

        Args:
            revision_id: The integer id of the revision.

        Returns:
            Landed Revision object or False if not submitted.
        """
        landings = cls.query.filter(
            cls.revision_id == revision_id,
            cls.status == LandingStatus.submitted).all()
        if not landings:
            return False

        return landings[0]

    @classmethod
    def latest_landed(cls, revision_id):
        """Return the latest Landing that is landed, or None.

        Args:
            revision_id: The integer id of the revision.

        Returns:
            Latest landing object with status landed, or None if
            none exist.
        """
        return cls.query.filter_by(revision_id=revision_id,
                                   status=LandingStatus.landed).order_by(
                                       cls.updated_at.desc()).first()

    def __repr__(self):
        return '<Landing: %s>' % self.id

    def serialize(self):
        """Serialize to JSON compatible dictionary."""
        return {
            'id': self.id,
            'revision_id': 'D{}'.format(self.revision_id),
            'request_id': self.request_id,
            'diff_id': self.diff_id,
            'active_diff_id': self.active_diff_id,
            'status': self.status.value,
            'error_msg': self.error,
            'result': self.result,
            'requester_email': self.requester_email,
            'tree': self.tree,
            'created_at': (
                self.created_at.astimezone(datetime.timezone.utc).isoformat()
            ),
            'updated_at': (
                self.updated_at.astimezone(datetime.timezone.utc).isoformat()
            ),
        }  # yapf: disable

    def update_from_transplant(self, landed, error='', result=''):
        """Set the status from pingback request."""
        self.error = error
        self.result = result
        if not landed:
            self.status = (LandingStatus.failed
                           if error else LandingStatus.submitted)
        else:
            self.status = LandingStatus.landed