Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions backend/kernelCI_app/helpers/hardwareRegistry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
from typing import Iterable, Optional

from kernelCI_app.models import HardwareRegistryPlatform
from kernelCI_app.typeModels.hardwareRegistry import (
HardwareRegistryInfo,
HardwareRegistryNamedLink,
HardwareRegistryProcessorInfo,
)


def serialize_hardware_registry_platform(
platform: HardwareRegistryPlatform,
) -> HardwareRegistryInfo:
processor = platform.processor
system_module = platform.system_module

return HardwareRegistryInfo(
platform_id=platform.id,
board_type=platform.type,
form_factor=platform.form_factor,
description=platform.details,
url=platform.url,
vendor=HardwareRegistryNamedLink(
id=platform.vendor.id, url=platform.vendor.url
),
silicon_vendor=HardwareRegistryNamedLink(
id=processor.vendor.id, url=processor.vendor.url
),
system_module=(
HardwareRegistryNamedLink(
id=system_module.id,
url=system_module.url,
form_factor=system_module.form_factor,
)
if system_module
else None
),
processor=HardwareRegistryProcessorInfo(
id=processor.id,
architecture=processor.architecture,
cores=processor.cores,
max_clock_speed_mhz=processor.max_clock_speed_mhz,
url=processor.url,
description=processor.details,
),
)


def _platform_ids(values: Iterable[object]) -> list[str]:
"""Unique, non-empty platform ids. environment_misc values can be anything."""
return list(dict.fromkeys(v for v in values if isinstance(v, str) and v))


def get_hardware_registry_by_ids(
platform_ids: Iterable[object],
) -> dict[str, HardwareRegistryInfo]:
ids = _platform_ids(platform_ids)
if not ids:
return {}

platforms = HardwareRegistryPlatform.objects.select_related(
"vendor",
"processor",
"processor__vendor",
"system_module",
).filter(id__in=ids)

return {
platform.id: serialize_hardware_registry_platform(platform)
for platform in platforms
}


def get_first_hardware_registry(
platform_ids: Iterable[object],
) -> Optional[HardwareRegistryInfo]:
"""Registry info for the first id that the registry knows about."""
ids = _platform_ids(platform_ids)
by_id = get_hardware_registry_by_ids(ids)
return next((by_id[pid] for pid in ids if pid in by_id), None)
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from types import SimpleNamespace
from unittest.mock import patch

from kernelCI_app.helpers.hardwareRegistry import (
get_first_hardware_registry,
get_hardware_registry_by_ids,
serialize_hardware_registry_platform,
)


def _minimal_platform(platform_id: str):
silicon = SimpleNamespace(id="ti", url="https://www.ti.com")
processor = SimpleNamespace(
id="am3358",
architecture="arm",
cores=1,
max_clock_speed_mhz=800,
url=None,
details=None,
vendor=silicon,
)
return SimpleNamespace(
id=platform_id,
type="board",
form_factor=None,
details=None,
url=None,
vendor=SimpleNamespace(id="ti", url=None),
processor=processor,
system_module=None,
)


class TestGetHardwareRegistryByIds:
def test_skips_query_when_no_usable_ids(self):
with patch(
"kernelCI_app.helpers.hardwareRegistry.HardwareRegistryPlatform.objects"
) as mock_objects:
assert get_hardware_registry_by_ids([None, "", 1, {"a": 1}]) == {}
mock_objects.select_related.assert_not_called()


class TestGetFirstHardwareRegistry:
def test_returns_first_matching_id_in_order(self):
platform = _minimal_platform("second")
with patch(
"kernelCI_app.helpers.hardwareRegistry.get_hardware_registry_by_ids"
) as mock_by_ids:
mock_by_ids.return_value = {
"second": serialize_hardware_registry_platform(platform)
}
info = get_first_hardware_registry([None, "missing", "second", "third"])

assert info is not None
assert info.platform_id == "second"
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,14 @@ def _body(self, **overrides):
return body

def _assert_query_count(self, body, n):
with patch(HEADS_PATCH) as mock_heads, patch(QUERY_PATCH) as mock_query:
with (
patch(HEADS_PATCH) as mock_heads,
patch(QUERY_PATCH) as mock_query,
patch(
"kernelCI_app.views.hardwareDetailsSummaryView.get_first_hardware_registry",
return_value=None,
),
):
mock_heads.return_value = self.heads
mock_query.return_value = [SUMMARY_ROW]
response = self._post(body)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ def setUp(self):
self.factory = APIRequestFactory()
self.view = HardwareView()
self.url = "/hardware"
registry_patcher = patch(
"kernelCI_app.views.hardwareView.get_hardware_registry_by_ids",
return_value={},
)
self.addCleanup(registry_patcher.stop)
registry_patcher.start()

@patch(
"kernelCI_app.views.hardwareView.get_hardware_listing_data_from_status_table"
Expand Down
2 changes: 2 additions & 0 deletions backend/kernelCI_app/typeModels/hardwareDetails.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
Origin,
StatusValues,
)
from kernelCI_app.typeModels.hardwareRegistry import HardwareRegistryInfo


class HardwareDetailsQueryParameters(BaseModel):
Expand Down Expand Up @@ -94,6 +95,7 @@ class Tree(BaseModel):
class HardwareCommon(BaseModel):
trees: List[Tree]
compatibles: List[str]
registry: Optional[HardwareRegistryInfo] = None


class HardwareTestLocalFilters(LocalFilters):
Expand Down
3 changes: 3 additions & 0 deletions backend/kernelCI_app/typeModels/hardwareListing.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from kernelCI_app.constants.localization import DocStrings
from kernelCI_app.typeModels.common import StatusCount
from kernelCI_app.typeModels.commonListing import ListingStatusCount
from kernelCI_app.typeModels.hardwareRegistry import HardwareRegistryInfo


def _normalize_commits_list(value: object) -> Optional[list[str]]:
Expand All @@ -24,6 +25,7 @@ class HardwareItem(BaseModel):
test_status_summary: StatusCount
boot_status_summary: StatusCount
build_status_summary: StatusCount
registry: Optional[HardwareRegistryInfo] = None


class HardwareListingItem(BaseModel):
Expand All @@ -32,6 +34,7 @@ class HardwareListingItem(BaseModel):
test_status_summary: ListingStatusCount
boot_status_summary: ListingStatusCount
build_status_summary: ListingStatusCount
registry: Optional[HardwareRegistryInfo] = None


class HardwareListingResponse(BaseModel):
Expand Down
30 changes: 30 additions & 0 deletions backend/kernelCI_app/typeModels/hardwareRegistry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from typing import Optional

from pydantic import BaseModel


class HardwareRegistryNamedLink(BaseModel):
id: str
url: Optional[str] = None
form_factor: Optional[str] = None


class HardwareRegistryProcessorInfo(BaseModel):
id: str
architecture: Optional[str] = None
cores: Optional[int] = None
max_clock_speed_mhz: Optional[int] = None
url: Optional[str] = None
description: Optional[str] = None


class HardwareRegistryInfo(BaseModel):
platform_id: str
board_type: Optional[str] = None
form_factor: Optional[str] = None
description: Optional[str] = None
url: Optional[str] = None
vendor: Optional[HardwareRegistryNamedLink] = None
silicon_vendor: Optional[HardwareRegistryNamedLink] = None
system_module: Optional[HardwareRegistryNamedLink] = None
processor: Optional[HardwareRegistryProcessorInfo] = None
2 changes: 2 additions & 0 deletions backend/kernelCI_app/typeModels/testDetails.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
Test__StartTime,
Timestamp,
)
from kernelCI_app.typeModels.hardwareRegistry import HardwareRegistryInfo
from kernelCI_app.utils import validate_str_to_dict


Expand Down Expand Up @@ -63,6 +64,7 @@ class TestDetailsResponse(BaseModel):
tree_name: Checkout__TreeName
origin: Optional[Origin]
test_origin: Origin
registry: Optional[HardwareRegistryInfo] = None


type PossibleRegressionType = Literal["regression", "fixed", "unstable", "pass", "fail"]
Expand Down
5 changes: 5 additions & 0 deletions backend/kernelCI_app/views/hardwareByRevisionView.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from rest_framework.response import Response
from rest_framework.views import APIView

from kernelCI_app.helpers.hardwareRegistry import get_hardware_registry_by_ids
from kernelCI_app.queries.hardware import get_hardware_listing_data_by_revision
from kernelCI_app.typeModels.hardwareListing import (
HardwareItem,
Expand All @@ -19,6 +20,9 @@

class HardwareByRevisionView(APIView):
def _sanitize_records(self, hardwares_raw: list[dict]) -> list[HardwareItem]:
registry_by_id = get_hardware_registry_by_ids(
hardware["platform"] for hardware in hardwares_raw
)
hardwares = []
for hardware in hardwares_raw:
hardwares.append(
Expand Down Expand Up @@ -52,6 +56,7 @@ def _sanitize_records(self, hardwares_raw: list[dict]) -> list[HardwareItem]:
"DONE": hardware["done_tests"],
"SKIP": hardware["skip_tests"],
},
registry=registry_by_id.get(hardware["platform"]),
)
)

Expand Down
7 changes: 6 additions & 1 deletion backend/kernelCI_app/views/hardwareDetailsSummaryView.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
generate_test_summary_typed,
unstable_parse_post_body,
)
from kernelCI_app.helpers.hardwareRegistry import get_first_hardware_registry
from kernelCI_app.helpers.issueExtras import parse_issue
from kernelCI_app.queries.hardware import (
get_hardware_details_summary,
Expand Down Expand Up @@ -614,7 +615,11 @@ def post(self, request, hardware_id) -> Response:
summary = Summary(
builds=builds_summary, boots=boots_summary, tests=tests_summary
)
commons = HardwareCommon(trees=all_trees, compatibles=all_compatibles)
commons = HardwareCommon(
trees=all_trees,
compatibles=all_compatibles,
registry=get_first_hardware_registry([hardware_id, *all_compatibles]),
)
filters = HardwareDetailsFilters(
all=all_filters,
builds=builds_filters,
Expand Down
4 changes: 4 additions & 0 deletions backend/kernelCI_app/views/hardwareDetailsView.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
set_trees_status_summary,
unstable_parse_post_body,
)
from kernelCI_app.helpers.hardwareRegistry import get_first_hardware_registry
from kernelCI_app.queries.hardware import (
get_hardware_details_data,
get_hardware_trees_data,
Expand Down Expand Up @@ -405,6 +406,9 @@ def post(self, request, hardware_id) -> Response:
common=HardwareCommon(
trees=trees,
compatibles=list(self.processed_compatibles - {hardware_id}),
registry=get_first_hardware_registry(
[hardware_id, *self.processed_compatibles]
),
),
)
except ValidationError as e:
Expand Down
5 changes: 5 additions & 0 deletions backend/kernelCI_app/views/hardwareView.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from kernelCI_app.constants.localization import ClientStrings
from kernelCI_app.helpers.errorHandling import create_api_error_response
from kernelCI_app.helpers.hardwareRegistry import get_hardware_registry_by_ids
from kernelCI_app.queries.hardware import get_hardware_listing_data_from_status_table
from kernelCI_app.typeModels.commonListing import ListingStatusCount
from kernelCI_app.typeModels.hardwareListing import (
Expand All @@ -23,6 +24,9 @@ class HardwareView(APIView):
def _sanitize_records(
self, hardwares_raw: list[tuple]
) -> list[HardwareListingItem]:
registry_by_id = get_hardware_registry_by_ids(
hardware[0] for hardware in hardwares_raw
)
hardwares = []
for hardware in hardwares_raw:
hardwares.append(
Expand All @@ -44,6 +48,7 @@ def _sanitize_records(
FAIL=hardware[9],
INCONCLUSIVE=hardware[10],
),
registry=registry_by_id.get(hardware[0]),
)
)

Expand Down
8 changes: 8 additions & 0 deletions backend/kernelCI_app/views/testDetailsView.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from kernelCI_app.constants.localization import ClientStrings
from kernelCI_app.helpers.errorHandling import create_api_error_response
from kernelCI_app.helpers.hardwareRegistry import get_first_hardware_registry
from kernelCI_app.queries.test import get_test_details_data
from kernelCI_app.typeModels.commonOpenApiParameters import TEST_ID_PATH_PARAM
from kernelCI_app.typeModels.testDetails import (
Expand All @@ -29,6 +30,13 @@ def get(self, _request, test_id: str) -> Response:

try:
valid_response = TestDetailsResponse(**response[0])
environment_misc = valid_response.environment_misc or {}
valid_response.registry = get_first_hardware_registry(
[
environment_misc.get("platform"),
*(valid_response.environment_compatible or []),
]
)
except ValidationError as e:
return Response(data=e.json(), status=HTTPStatus.INTERNAL_SERVER_ERROR)

Expand Down
Loading