diff --git a/backend/kernelCI_app/management/commands/update_hardware_registry.py b/backend/kernelCI_app/management/commands/update_hardware_registry.py new file mode 100644 index 000000000..6087a8366 --- /dev/null +++ b/backend/kernelCI_app/management/commands/update_hardware_registry.py @@ -0,0 +1,122 @@ +"""Parse and display KernelCI hardware registry YAML files.""" + +from __future__ import annotations + +from pathlib import Path +from typing import Any + +import yaml +from django.core.management.base import BaseCommand, CommandError + +from kernelCI_app.models import ( + HardwareRegistryPlatform, + HardwareRegistryPlatformVendor, + HardwareRegistryProcessor, + HardwareRegistrySiliconVendor, + HardwareRegistrySystemModule, +) + + +def get_update_fields(model): + return [ + f.column if f.is_relation else f.name + for f in model._meta.concrete_fields + if not f.primary_key + ] + + +EXPECTED_SECTIONS = ( + "silicon_vendors", + "platform_vendors", + "processors", + "system_modules", + "platforms", +) + + +class Command(BaseCommand): + help = ( + "Parse a KernelCI hardware registry YAML file (same shape as " + "kernelci-pipeline config/hardware_registry/*.yaml) and print its data." + ) + + def add_arguments(self, parser): + parser.add_argument( + "registry_file", + type=str, + help="Path to the hardware registry YAML file.", + ) + + def load_yaml(self, path: str) -> dict: + if not path.is_file(): + raise CommandError(f"Registry file not found: {path}") + try: + raw = path.read_text(encoding="utf-8") + except OSError as exc: + raise CommandError(f"Cannot read registry file: {exc}") from exc + + try: + data = yaml.safe_load(raw) + return data + except yaml.YAMLError as exc: + raise CommandError(f"Invalid YAML: {exc}") from exc + + def handle(self, *args: Any, registry_file: str, **options: Any) -> None: + path = Path(registry_file).expanduser().resolve() + data = self.load_yaml(path) + + if data is None: + raise CommandError("Registry file is empty or parses to null.") + if not isinstance(data, dict): + raise CommandError( + f"Expected a YAML mapping at root, got {type(data).__name__}." + ) + + missing = [k for k in EXPECTED_SECTIONS if k not in data] + if missing: + self.stderr.write( + self.style.WARNING( + f"Missing typical hardware-registry sections : {', '.join(missing)}" + ) + ) + + silicon_vendors = data["silicon_vendors"] or [] + platform_vendors = data["platform_vendors"] or [] + processors = data["processors"] or [] + system_modules = data["system_modules"] or [] + platforms = data["platforms"] or [] + + HardwareRegistrySiliconVendor.objects.bulk_create( + [HardwareRegistrySiliconVendor(**sv) for sv in silicon_vendors.values()], + update_conflicts=True, + unique_fields=["id"], + update_fields=get_update_fields(HardwareRegistrySiliconVendor), + ) + + HardwareRegistryProcessor.objects.bulk_create( + [HardwareRegistryProcessor(**p) for p in processors.values()], + update_conflicts=True, + unique_fields=["id"], + update_fields=get_update_fields(HardwareRegistryProcessor), + ) + + HardwareRegistryPlatformVendor.objects.bulk_create( + [HardwareRegistryPlatformVendor(**pv) for pv in platform_vendors.values()], + update_conflicts=True, + unique_fields=["id"], + update_fields=get_update_fields(HardwareRegistryPlatformVendor), + ) + + HardwareRegistrySystemModule.objects.bulk_create( + [HardwareRegistrySystemModule(**sm) for sm in system_modules.values()], + update_conflicts=True, + unique_fields=["id"], + update_fields=get_update_fields(HardwareRegistrySystemModule), + ) + + HardwareRegistryPlatform.objects.bulk_create( + [HardwareRegistryPlatform(**p) for p in platforms.values()], + update_conflicts=True, + unique_fields=["id"], + update_fields=get_update_fields(HardwareRegistryPlatform), + ) diff --git a/backend/kernelCI_app/migrations/0018_hardwareregistryplatformvendor_and_more.py b/backend/kernelCI_app/migrations/0018_hardwareregistryplatformvendor_and_more.py new file mode 100644 index 000000000..1c3baf34f --- /dev/null +++ b/backend/kernelCI_app/migrations/0018_hardwareregistryplatformvendor_and_more.py @@ -0,0 +1,134 @@ +# Generated by Django 5.2.11 on 2026-05-18 12:07 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("kernelCI_app", "0017_add_tree_tests_rollup_table"), + ] + + operations = [ + migrations.CreateModel( + name="HardwareRegistryPlatformVendor", + fields=[ + ("id", models.TextField(primary_key=True, serialize=False)), + ("type", models.CharField(default="platform_vendor", max_length=64)), + ("url", models.TextField(blank=True, null=True)), + ("details", models.TextField(blank=True, null=True)), + ], + options={ + "db_table": "hardware_registry_platform_vendors", + }, + ), + migrations.CreateModel( + name="HardwareRegistrySiliconVendor", + fields=[ + ("id", models.TextField(primary_key=True, serialize=False)), + ("type", models.CharField(blank=True, max_length=64)), + ("url", models.TextField(blank=True, null=True)), + ("details", models.TextField(blank=True, null=True)), + ], + options={ + "db_table": "hardware_registry_silicon_vendors", + }, + ), + migrations.CreateModel( + name="HardwareRegistryProcessor", + fields=[ + ("id", models.TextField(primary_key=True, serialize=False)), + ("type", models.CharField(default="soc", max_length=64)), + ("url", models.TextField(blank=True, null=True)), + ("details", models.TextField(blank=True, null=True)), + ("architecture", models.TextField(blank=True, null=True)), + ("cores", models.IntegerField(blank=True, null=True)), + ("max_clock_speed_mhz", models.IntegerField(blank=True, null=True)), + ( + "vendor", + models.ForeignKey( + db_column="vendor_id", + on_delete=django.db.models.deletion.CASCADE, + related_name="hardware_registry_processors", + to="kernelCI_app.hardwareregistrysiliconvendor", + ), + ), + ], + options={ + "db_table": "hardware_registry_processors", + }, + ), + migrations.CreateModel( + name="HardwareRegistrySystemModule", + fields=[ + ("id", models.TextField(primary_key=True, serialize=False)), + ("type", models.CharField(max_length=64)), + ("url", models.TextField(blank=True, null=True)), + ("details", models.TextField(blank=True, null=True)), + ("form_factor", models.TextField(blank=True, null=True)), + ( + "processor", + models.ForeignKey( + db_column="processor_id", + on_delete=django.db.models.deletion.CASCADE, + related_name="hardware_registry_system_modules", + to="kernelCI_app.hardwareregistryprocessor", + ), + ), + ( + "vendor", + models.ForeignKey( + db_column="vendor_id", + on_delete=django.db.models.deletion.CASCADE, + related_name="hardware_registry_system_modules", + to="kernelCI_app.hardwareregistryplatformvendor", + ), + ), + ], + options={ + "db_table": "hardware_registry_system_modules", + }, + ), + migrations.CreateModel( + name="HardwareRegistryPlatform", + fields=[ + ("id", models.TextField(primary_key=True, serialize=False)), + ("type", models.CharField(max_length=64)), + ("url", models.TextField(blank=True, null=True)), + ("details", models.TextField(blank=True, null=True)), + ("form_factor", models.TextField(blank=True, null=True)), + ( + "vendor", + models.ForeignKey( + db_column="vendor_id", + on_delete=django.db.models.deletion.CASCADE, + related_name="hardware_registry_platforms", + to="kernelCI_app.hardwareregistryplatformvendor", + ), + ), + ( + "processor", + models.ForeignKey( + db_column="processor_id", + on_delete=django.db.models.deletion.CASCADE, + related_name="hardware_registry_platforms", + to="kernelCI_app.hardwareregistryprocessor", + ), + ), + ( + "system_module", + models.ForeignKey( + blank=True, + db_column="system_module_id", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="hardware_registry_platforms", + to="kernelCI_app.hardwareregistrysystemmodule", + ), + ), + ], + options={ + "db_table": "hardware_registry_platforms", + }, + ), + ] diff --git a/backend/kernelCI_app/models.py b/backend/kernelCI_app/models.py index d680a792d..7df0796a2 100644 --- a/backend/kernelCI_app/models.py +++ b/backend/kernelCI_app/models.py @@ -499,3 +499,111 @@ class Meta: name="tree_tests_rollup_group_total", ), ] + + +class HardwareRegistrySiliconVendor(models.Model): + id = models.TextField(primary_key=True) + type = models.CharField(max_length=64, blank=True) + url = models.TextField(blank=True, null=True) + details = models.TextField(blank=True, null=True) + + class Meta: + db_table = "hardware_registry_silicon_vendors" + + def __str__(self) -> str: + return self.id + + +class HardwareRegistryPlatformVendor(models.Model): + id = models.TextField(primary_key=True) + type = models.CharField(max_length=64, default="platform_vendor") + url = models.TextField(blank=True, null=True) + details = models.TextField(blank=True, null=True) + + class Meta: + db_table = "hardware_registry_platform_vendors" + + def __str__(self) -> str: + return self.id + + +class HardwareRegistryProcessor(models.Model): + id = models.TextField(primary_key=True) + type = models.CharField(max_length=64, default="soc") + vendor = models.ForeignKey( + HardwareRegistrySiliconVendor, + db_column="vendor_id", + on_delete=models.CASCADE, + related_name="hardware_registry_processors", + ) + url = models.TextField(blank=True, null=True) + details = models.TextField(blank=True, null=True) + architecture = models.TextField(blank=True, null=True) + cores = models.IntegerField(blank=True, null=True) + max_clock_speed_mhz = models.IntegerField(blank=True, null=True) + + class Meta: + db_table = "hardware_registry_processors" + + def __str__(self) -> str: + return self.id + + +class HardwareRegistrySystemModule(models.Model): + id = models.TextField(primary_key=True) + type = models.CharField(max_length=64) + vendor = models.ForeignKey( + HardwareRegistryPlatformVendor, + db_column="vendor_id", + on_delete=models.CASCADE, + related_name="hardware_registry_system_modules", + ) + processor = models.ForeignKey( + HardwareRegistryProcessor, + db_column="processor_id", + on_delete=models.CASCADE, + related_name="hardware_registry_system_modules", + ) + url = models.TextField(blank=True, null=True) + details = models.TextField(blank=True, null=True) + form_factor = models.TextField(blank=True, null=True) + + class Meta: + db_table = "hardware_registry_system_modules" + + def __str__(self) -> str: + return self.id + + +class HardwareRegistryPlatform(models.Model): + id = models.TextField(primary_key=True) + type = models.CharField(max_length=64) + vendor = models.ForeignKey( + HardwareRegistryPlatformVendor, + db_column="vendor_id", + on_delete=models.CASCADE, + related_name="hardware_registry_platforms", + ) + processor = models.ForeignKey( + HardwareRegistryProcessor, + db_column="processor_id", + on_delete=models.CASCADE, + related_name="hardware_registry_platforms", + ) + system_module = models.ForeignKey( + HardwareRegistrySystemModule, + db_column="system_module_id", + on_delete=models.CASCADE, + related_name="hardware_registry_platforms", + null=True, + blank=True, + ) + url = models.TextField(blank=True, null=True) + details = models.TextField(blank=True, null=True) + form_factor = models.TextField(blank=True, null=True) + + class Meta: + db_table = "hardware_registry_platforms" + + def __str__(self) -> str: + return self.id