API Reference

Tests

Services to handle tests runs and submissions. They define their own router so they can be integrated into any app.

from app.ap1.v1.endpoints import tests
from fastapi import FastAPI

app = FastAPI()
app.include_router(tests.router)

run_tests(tests_data, session, request) async

Schedules tests in a runner. We only scheduled tests that have not been run for before for the build.

Parameters:
  • tests_data (TestSuite) –

    Tests to schedule

  • session (SessionDep) –

    Database session

  • request (Request) –

    Request object, allows us to obtain a URL from a function name

Source code in app/api/v1/endpoints/tests.py
@router.post("/run", status_code=204)
async def run_tests(tests_data: TestSuite, session: SessionDep, request: Request):
    """
    Schedules tests in a runner.
    We only scheduled tests that have not been run for before for the build.

    :param tests_data: Tests to schedule
    :param session: Database session
    :param request: Request object, allows us to obtain a URL from a function name
    """
    tests_to_run = []
    for test in tests_data.tests:
        tests_has_been_run = session.exec(select(func.count(RunTest.build_id))
                                        .where(RunTest.build_id == tests_data.build_id)
                                        .where(RunTest.test == test)).one()
        if tests_has_been_run > 0:
            logging.info(f"Test {test} from {tests_data.collection} has already been run for {tests_data.build_id}")
            continue
        tests_to_run.append(test)

    if len(tests_to_run) == 0:
        logging.info(f"No tests to run for build: {tests_data.build_id}")
        return

    tests_runner = get_test_runner(tests_data.runner)

    test_uid = tests_runner(tests_data.kernel_image_url, tests_data.modules_url,
                      tests_to_run, "qemu-riscv64",
                      str(request.url_for(get_test_callback_funcname(tests_data.runner))))
    scheduled_test = ScheduledTest(test_uid=test_uid, build_id=tests_data.build_id, test_collection=tests_data.collection,
                                   tests=tests_to_run, runner=tests_data.runner)
    session.add(scheduled_test)
    for test in tests_to_run:
        run_test = RunTest(build_id=tests_data.build_id, test=test)
        session.add(run_test)
    # Both queries are run in a transaction
    session.commit()

submit_results(results, session) async

Submit test results to KCIDB. In this case test runner is not relevant.

Parameters:
  • results (RunnerTestsResults) –

    Test results passed as the body of the request

  • session (SessionDep) –

    Database session

Source code in app/api/v1/endpoints/tests.py
@router.post("/submit", status_code=204)
async def submit_results(results: RunnerTestsResults, session: SessionDep):
    """
    Submit test results to KCIDB. In this case test runner is not relevant.

    :param results: Test results passed as the body of the request
    :param session: Database session
    """
    logging.info(f"Received results for {results.test_uid}")
    parsed_results = parse_results2kcidb(results)
    json_results = [item.to_json() for item in parsed_results]

    try:
        submit_kcidb(json_results)
    except KCIDBSubmitionException:
        test_row = TestResults(test_uid=results.test_uid, build_id=results.build_id, results=json_results)
        session.add(test_row)
        session.commit() 

Builds

Services to handle build runs. They define their own router so they can be integrated into any app.

from app.ap1.v1.endpoints import builds
from fastapi import FastAPI

app = FastAPI()
app.include_router(builds.router)

run_builds(build_data, session, request)

Submit a build request. The requests stores all the information requireed for the builds along with an identifier.

Parameters:
  • build_data (BuildData) –

    Data required by runners to perform a build

  • session (SessionDep) –

    Database session

  • request (Request) –

    Request object

Source code in app/api/v1/endpoints/builds.py
@router.post("/run", status_code=204)
def run_builds(build_data: BuildData, session: SessionDep, request: Request):
    """
    Submit a build request.
    The requests stores all the information requireed for the builds along with an identifier.

    :param build_data: Data required by runners to perform a build
    :param session: Database session
    :param request: Request object
    """
    build_runner = get_build_runner(build_data.runner)
    # Schedule the build
    build_uid = build_runner(build_data.toolchain, "riscv", build_data.tree, build_data.branch,
                             build_data.kconfig, build_data.fragments,
                             str(request.url_for(get_build_callback_funcname(build_data.runner))))
    # Store it in the database
    scheduled_build = ScheduledBuild(build_uid=build_uid,
                                    toolchain=build_data.toolchain,
                                    tree=build_data.tree,
                                    branch=build_data.branch,
                                    kconfig=build_data.kconfig,
                                    fragments=build_data.fragments,
                                    runner=build_data.runner)
    session.add(scheduled_build)
    session.commit()

Boot testing

:: app.api.v1.endpoints.boot

Sync

Services that handle re-trying failed submsissions. They define their own router so they can be integrated into any app.

from app.ap1.v1.endpoints import sync
from fastapi import FastAPI

app = FastAPI()
app.include_router(sync.router)

sync_builds(session) async

Looks for unsubmitted builds and tries to submit them again. If the re try fails the results are kept stored, so that they can be submitted later.

Parameters:
  • session (SessionDep) –

    Database session. Used to access stored results

Source code in app/api/v1/endpoints/sync.py
@router.post("/builds", status_code=204)
async def sync_builds(session: SessionDep):
    """
    Looks for unsubmitted builds and tries to submit them again.
    If the re try fails the results are kept stored, so that they can be submitted later.

    :param session: Database session. Used to access stored results
    """
    non_submitted_builds = session.exec(select(RunBuild).where(RunBuild.submitted == False)).all()

    for build in non_submitted_builds:
        build_uid = build.build_uid
        submission = build.submission
        # Only submit results with submitted false
        logging.info(f"Submitting build for build uid {build_uid}")
        try:
            submit_kcidb([submission])
            mark_build_as_submitted(build_uid, session)

        except:
            logging.warning(f"Could not submit build with uid {build_uid}")
    session.commit()

sync_results(session) async

Looks for unsubmitted test results and tries to submit them again. If the re try fails the results are kept stored, so that they can be submitted later.

Parameters:
  • session (SessionDep) –

    Database session. Used to access stored results

Source code in app/api/v1/endpoints/sync.py
@router.post("/results", status_code=204)
async def sync_results(session: SessionDep):
    """
    Looks for unsubmitted test results and tries to submit them again.
    If the re try fails the results are kept stored, so that they can be submitted later.

    :param session: Database session. Used to access stored results
    """
    non_submitted_tests = session.exec(select(TestResults)).all()

    for test in non_submitted_tests:
        test_uid = test.test_uid
        results = test.results
        # Only submit results with submitted false
        logging.info(f"Submitting results for test uid {test_uid}")
        try:
            submit_kcidb(results)
            session.delete(test)
            mark_tests_as_submitted(test_uid, session)

        except:
            logging.warning(f"Could not submit results for test uid {test_uid}")
    session.commit()

TuxSuite Callbacks

TuxSuite runner callbacks. In their specific case they send a header that allows verifying the source of the request. Sadly, we have no way of using that header (at least for the moment), since we cannot obtain a signature in the community project. They define their own router so they can be integrated into any app.

from app.ap1.v1.endpoints import tuxsuite_callbacks
from fastapi import FastAPI

app = FastAPI()
app.include_router(tuxsuite_callbacks.router)

tuxsuite_boot_callback(x_tux_payload_signature, request, session) async

Callback for tuxsuite boot test. It obtains the test from the database (to get its build id), stores the results in the TestResults table and marks the test as finished.

Parameters:
  • x_tux_payload_signature (Annotated[str | None, Header()]) –

    Payload signature used to verify the origin of the request

  • request (TuxSuiteTestRequest) –

    Test evaluation result

  • session (SessionDep) –

    Database session

Source code in app/api/v1/endpoints/tuxsuite_callbacks.py
@router.post("/boot", status_code=204)
async def tuxsuite_boot_callback(x_tux_payload_signature: Annotated[str | None, Header()], request: TuxSuiteTestRequest,
                         session: SessionDep):
    """
    Callback for tuxsuite boot test.
    It obtains the test from the database (to get its build id), stores the results in 
    the TestResults table and marks the test as finished.

    :param x_tux_payload_signature: Payload signature used to verify the origin of the request
    :param request: Test evaluation result
    :param session: Database session

    """
    # TODO: add payload signature check (when available)
    tests_results = request.status
    logging.info(f"Received results for {tests_results.uid}")
    test = session.exec(select(ScheduledTest).where(ScheduledTest.test_uid == tests_results.uid)).one()
    build_id = test.build_id
    # We mark the all tests from that test suit as received
    # TODO: Check how many of these are left as non-received and for how long
    submitted_tests = get_already_submitted_tests(build_id, tests_results.tests, session)
    parsed_test_results = await parse_tuxsuite_boot2kcidb(tests_results, test, submitted_tests)
    results = [item.to_json() for item in parsed_test_results]
    mark_as_received_tests_results([item.test for item in parsed_test_results], build_id, session)

    try:
        # Only submit results with submitted false
        submit_kcidb(results)
        mark_tests_as_submitted([item.test for item in parsed_test_results], build_id, session)
    except KCIDBSubmitionException:
        test_row = TestResults(test_uid=tests_results.uid, build_id=build_id ,results=results)
        session.add(test_row)
        session.commit() 

tuxsuite_build_callback(x_tux_payload_signature, request, session) async

Callback for tuxsuite build. It obtains the build from the database, and marks its completion state. If it passed the build we submit it to KCIDB

Parameters:
  • x_tux_payload_signature (Annotated[str | None, Header()]) –

    Payload signature used to verify the origin of the request

  • request (TuxSuiteBuildRequest) –

    Build result

  • session (SessionDep) –

    Database session

Source code in app/api/v1/endpoints/tuxsuite_callbacks.py
@router.post("/build", status_code=204)
async def tuxsuite_build_callback(x_tux_payload_signature: Annotated[str | None, Header()], request: TuxSuiteBuildRequest,
                         session: SessionDep):
    """
    Callback for tuxsuite build.
    It obtains the build from the database, and marks its completion state.
    If it passed the build we submit it to KCIDB

    :param x_tux_payload_signature: Payload signature used to verify the origin of the request
    :param request: Build result
    :param session: Database session
    """
    # TODO: add payload signature check
    build_results = request.status
    logging.info(f"Received build results for {build_results.uid}")
    try:
        build = session.exec(select(ScheduledBuild).where(ScheduledBuild.build_uid == build_results.uid)).one()
    except sqlalchemy.exc.NoResultFound:
        logging.warning(f"Received unexpected build uid: {build_results.uid}")
        raise HTTPException(status_code=500, detail=f"Invalid build uid {build_results.uid}")

    parsed_build_result = await parse_tuxsuite_build2kcidb(build_results, build)
    store_build_result(build_results, parsed_build_result, session)

    try:
        submit_kcidb([parsed_build_result.to_json()])
        mark_build_as_submitted(build_uid=build_results.uid, session=session)
    except KCIDBSubmitionException:
        logging.warning(f"Build {build_results.uid} couldn't be submitted")

tuxsuite_test_callback(x_tux_payload_signature, request, session) async

Callback for tuxsuite test. It obtains the test from the database (to get its build id), stores the results in the TestResults table and marks the test as finished.

Parameters:
  • x_tux_payload_signature (Annotated[str | None, Header()]) –

    Payload signature used to verify the origin of the request

  • request (TuxSuiteTestRequest) –

    Test evaluation result

  • session (SessionDep) –

    Database session

Source code in app/api/v1/endpoints/tuxsuite_callbacks.py
@router.post("/test", status_code=204)
async def tuxsuite_test_callback(x_tux_payload_signature: Annotated[str | None, Header()], request: TuxSuiteTestRequest,
                         session: SessionDep):
    """
    Callback for tuxsuite test.
    It obtains the test from the database (to get its build id), stores the results in 
    the TestResults table and marks the test as finished.

    :param x_tux_payload_signature: Payload signature used to verify the origin of the request
    :param request: Test evaluation result
    :param session: Database session

    """
    # TODO: add payload signature check (when available)
    tests_results = request.status
    logging.info(f"Received results for {tests_results.uid}")
    test = session.exec(select(ScheduledTest).where(ScheduledTest.test_uid == tests_results.uid)).one()
    build_id = test.build_id
    # We mark the all tests from that test suit as received
    # TODO: Check how many of these are left as non-received and for how long
    submitted_tests = get_already_submitted_tests(build_id, tests_results.tests, session)
    parsed_test_results = await parse_tuxsuite_test2kcidb(tests_results, test, submitted_tests)
    results = [item.to_json() for item in parsed_test_results]
    mark_as_received_tests_results([item.test for item in parsed_test_results], build_id, session)

    try:
        # Only submit results with submitted false
        submit_kcidb(results)
        mark_tests_as_submitted([item.test for item in parsed_test_results], build_id, session)
    except KCIDBSubmitionException:
        test_row = TestResults(test_uid=tests_results.uid, build_id=build_id ,results=results)
        session.add(test_row)
        session.commit()