Skip to content

Commit

Permalink
Add status update endpoint to receive updates from Testflinger
Browse files Browse the repository at this point in the history
  • Loading branch information
val500 committed Jun 7, 2024
1 parent f69008a commit b8e4172
Show file tree
Hide file tree
Showing 8 changed files with 379 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""Create test status update tables
Revision ID: 00fe81a03705
Revises: 33c0383ea9ca
Create Date: 2024-06-03 20:38:47.346523+00:00
"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = "00fe81a03705"
down_revision = "33c0383ea9ca"
branch_labels = None
depends_on = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"test_status_update",
sa.Column("agent_id", sa.String(), nullable=False),
sa.Column("job_queue", sa.String(), nullable=False),
sa.Column("test_execution_id", sa.Integer(), nullable=False),
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.Column("updated_at", sa.DateTime(), nullable=False),
sa.ForeignKeyConstraint(
["test_execution_id"],
["test_execution.id"],
name=op.f("test_status_update_test_execution_id_fkey"),
ondelete="CASCADE",
),
sa.PrimaryKeyConstraint("id", name=op.f("test_status_update_pkey")),
)
op.create_index(
op.f("test_status_update_test_execution_id_ix"),
"test_status_update",
["test_execution_id"],
unique=False,
)
op.create_table(
"test_event",
sa.Column(
"event_name",
sa.Enum(
"STARTED_SETUP",
"STARTED_PROVISION",
"STARTED_FIRMWARE_UPDATE",
"STARTED_TEST",
"STARTED_ALLOCATE",
"STARTED_RESERVE",
"STARTED_CLEANUP",
"ENDED_SETUP",
"ENDED_PROVISION",
"ENDED_FIRMWARE_UPDATE",
"ENDED_TEST",
"ENDED_ALLOCATE",
"ENDED_RESERVE",
"ENDED_CLEANUP",
"CANCELLED",
"GLOBAL_TIMEOUT",
"OUTPUT_TIMEOUT",
"RECOVERY_FAILED",
"FAILED",
name="testeventenum",
),
nullable=False,
),
sa.Column("timestamp", sa.DateTime(), nullable=False),
sa.Column("detail_msg", sa.String(), nullable=False),
sa.Column("test_status_update_id", sa.Integer(), nullable=False),
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.Column("updated_at", sa.DateTime(), nullable=False),
sa.ForeignKeyConstraint(
["test_status_update_id"],
["test_status_update.id"],
name=op.f("test_event_test_status_update_id_fkey"),
ondelete="CASCADE",
),
sa.PrimaryKeyConstraint("id", name=op.f("test_event_pkey")),
)
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table("test_event")
op.drop_index(
op.f("test_status_update_test_execution_id_ix"), table_name="test_status_update"
)
op.drop_table("test_status_update")
# ### end Alembic commands ###
op.execute("DROP TYPE IF EXISTS testeventenum")
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@

from fastapi import APIRouter

from . import end_test, get_test_results, patch, reruns, start_test
from . import end_test, get_test_results, patch, reruns, start_test, status_update

router = APIRouter(tags=["test-executions"])
router.include_router(start_test.router)
router.include_router(get_test_results.router)
router.include_router(end_test.router)
router.include_router(patch.router)
router.include_router(reruns.router)
router.include_router(status_update.router)
13 changes: 13 additions & 0 deletions backend/test_observer/controllers/test_executions/logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
ArtefactBuild,
TestExecution,
TestResult,
TestStatusUpdate,
)
from test_observer.data_access.models_enums import TestExecutionStatus

Expand Down Expand Up @@ -55,6 +56,18 @@ def delete_previous_results(
db.commit()


def delete_previous_status_update(
db: Session,
test_execution: TestExecution,
):
db.execute(
delete(TestStatusUpdate).where(
TestStatusUpdate.test_execution_id == test_execution.id
)
)
db.commit()


def get_previous_artefact_builds_query(
session: Session,
artefact: Artefact,
Expand Down
14 changes: 14 additions & 0 deletions backend/test_observer/controllers/test_executions/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from enum import Enum
from typing import Annotated

from datetime import datetime
from pydantic import (
AliasPath,
BaseModel,
Expand All @@ -35,6 +36,7 @@
from test_observer.common.constants import PREVIOUS_TEST_RESULT_COUNT
from test_observer.data_access.models_enums import (
FamilyName,
TestEventEnum,
TestExecutionReviewDecision,
TestExecutionStatus,
TestResultStatus,
Expand Down Expand Up @@ -172,3 +174,15 @@ class PendingRerun(BaseModel):

class DeleteReruns(BaseModel):
test_execution_ids: set[int]


class JobEvent(BaseModel):
event_name: TestEventEnum
timestamp: datetime
detail_msg: str


class StatusUpdateRequest(BaseModel):
agent_id: str
job_queue: str
events: list[JobEvent]
63 changes: 63 additions & 0 deletions backend/test_observer/controllers/test_executions/status_update.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Copyright 2024 Canonical Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session, joinedload

from test_observer.data_access.models import (
TestStatusUpdate,
TestEvent,
TestExecution,
)

from test_observer.data_access.setup import get_db

from .logic import delete_previous_status_update
from .models import StatusUpdateRequest

router = APIRouter()


@router.post("/{id}/status_update")
def status_update(id: int, request: StatusUpdateRequest, db: Session = Depends(get_db)):
test_execution = db.get(
TestExecution,
id,
options=[
joinedload(TestExecution.test_status_update).joinedload(
TestStatusUpdate.events
),
],
)
if test_execution is None:
raise HTTPException(status_code=404, detail="TestExecution not found")

delete_previous_status_update(db, test_execution)

test_status_update = TestStatusUpdate(
agent_id=request.agent_id,
job_queue=request.job_queue,
test_execution=test_execution,
)
for event in request.events:
test_event = TestEvent(
event_name=event.event_name,
timestamp=event.timestamp,
detail_msg=event.detail_msg,
)
test_status_update.events.append(test_event)
db.add(test_status_update)
db.commit()
42 changes: 42 additions & 0 deletions backend/test_observer/data_access/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
TestExecutionReviewDecision,
TestExecutionStatus,
TestResultStatus,
TestEventEnum,
)


Expand Down Expand Up @@ -313,6 +314,9 @@ class TestExecution(Base):
test_results: Mapped[list["TestResult"]] = relationship(
back_populates="test_execution", cascade="all, delete"
)
test_status_update: Mapped["TestStatusUpdate"] = relationship(
back_populates="test_execution", cascade="all, delete"
)
rerun_request: Mapped[TestExecutionRerunRequest | None] = relationship(
back_populates="test_execution", cascade="all, delete"
)
Expand Down Expand Up @@ -402,3 +406,41 @@ def __repr__(self) -> str:
"test_execution_id",
"test_case_id",
)


class TestStatusUpdate(Base):
"""
A table to represent status updates for the test runs
"""

__tablename__ = "test_status_update"

agent_id: Mapped[str]
job_queue: Mapped[str]
events: Mapped[list["TestEvent"]] = relationship(
back_populates="test_status_update", cascade="all, delete"
)
test_execution_id: Mapped[int] = mapped_column(
ForeignKey("test_execution.id", ondelete="CASCADE"), index=True
)
test_execution: Mapped["TestExecution"] = relationship(
back_populates="test_status_update"
)


class TestEvent(Base):
"""
A table to represent test events that have ocurred during a job
"""

__tablename__ = "test_event"

event_name: Mapped[TestEventEnum]
timestamp: Mapped[datetime]
detail_msg: Mapped[str]
test_status_update_id: Mapped[int] = mapped_column(
ForeignKey("test_status_update.id", ondelete="CASCADE")
)
test_status_update: Mapped["TestStatusUpdate"] = relationship(
back_populates="events"
)
22 changes: 22 additions & 0 deletions backend/test_observer/data_access/models_enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,25 @@ class TestResultStatus(str, Enum):
PASSED = "PASSED"
FAILED = "FAILED"
SKIPPED = "SKIPPED"


class TestEventEnum(str, Enum):
STARTED_SETUP = "started_setup"
STARTED_PROVISION = "started_provision"
STARTED_FIRMWARE_UPDATE = "started_firmware_update"
STARTED_TEST = "started_test"
STARTED_ALLOCATE = "started_allocate"
STARTED_RESERVE = "started_reserve"
STARTED_CLEANUP = "started_cleanup"
ENDED_SETUP = "ended_setup"
ENDED_PROVISION = "ended_provision"
ENDED_FIRMWARE_UPDATE = "ended_firmware_update"
ENDED_TEST = "ended_test"
ENDED_ALLOCATE = "ended_allocate"
ENDED_RESERVE = "ended_reserve"
ENDED_CLEANUP = "ended_cleanup"
CANCELLED = "cancelled"
GLOBAL_TIMEOUT = "global_timeout"
OUTPUT_TIMEOUT = "output_timeout"
RECOVERY_FAILED = "recovery_failed"
FAILED = "failed"
Loading

0 comments on commit b8e4172

Please sign in to comment.