From 1a1750a0b9b0b147d6b3ac683a364774291c63eb Mon Sep 17 00:00:00 2001 From: 1yam Date: Thu, 4 Dec 2025 16:38:52 +0100 Subject: [PATCH 01/13] Feature: filter crn can with based on vm resources --- src/aleph/sdk/client/services/crn.py | 69 ++++++++++++++++++++++++++++ src/aleph/sdk/types.py | 6 +++ 2 files changed, 75 insertions(+) diff --git a/src/aleph/sdk/client/services/crn.py b/src/aleph/sdk/client/services/crn.py index bca54176..4120bcc4 100644 --- a/src/aleph/sdk/client/services/crn.py +++ b/src/aleph/sdk/client/services/crn.py @@ -1,3 +1,4 @@ +from datetime import datetime from typing import TYPE_CHECKING, Dict, List, Optional, Union import aiohttp @@ -13,6 +14,7 @@ CrnV1List, CrnV2List, DictLikeModel, + VmResources, ) from aleph.sdk.utils import extract_valid_eth_address, sanitize_url @@ -20,6 +22,48 @@ from aleph.sdk.client.http import AlephHttpClient +class CpuLoad(BaseModel): + load1: float + load5: float + load15: float + + +class CoreFrequencies(BaseModel): + min: float + max: float + + +class CpuInfo(BaseModel): + count: int + load_average: CpuLoad + core_frequencies: CoreFrequencies + + +class MemoryInfo(BaseModel): + total_kB: int + available_kB: int + + +class DiskInfo(BaseModel): + total_kB: int + available_kB: int + + +class UsagePeriod(BaseModel): + start_timestamp: datetime + duration_seconds: int + + +class SystemUsage(BaseModel): + cpu: CpuInfo + mem: MemoryInfo + disk: DiskInfo + period: UsagePeriod + properties: dict + gpu: dict + active: bool + + class GPU(BaseModel): vendor: str model: str @@ -47,6 +91,7 @@ class CRN(DictLikeModel): gpu_support: Optional[bool] = False confidential_support: Optional[bool] = False qemu_support: Optional[bool] = False + system_usage: Optional[SystemUsage] = None version: Optional[str] = "0.0.0" payment_receiver_address: Optional[str] # Can be None if not configured @@ -102,6 +147,7 @@ def filter_crn( stream_address: bool = False, confidential: bool = False, gpu: bool = False, + vm_resources: Optional[VmResources] = None, ) -> list[CRN]: """Filter compute resource node list, unfiltered by default. Args: @@ -110,6 +156,7 @@ def filter_crn( stream_address (bool): Filter invalid payment receiver address. confidential (bool): Filter by confidential computing support. gpu (bool): Filter by GPU support. + vm_resources (VmResources): Filter by VM need, vcpus, memory, disk. Returns: list[CRN]: List of compute resource nodes. (if no filter applied, return all) """ @@ -140,6 +187,28 @@ def filter_crn( if gpu and (not crn_.gpu_support or not available_gpu): continue + # Filter VM resources + if vm_resources: + sys = crn_.system_usage + if not sys: + continue + + # Check CPU count + if sys.cpu.count < vm_resources.vcpus: + continue + + # Convert MiB to kB (1 MiB = 1024 kB) for proper comparison + memory_kb_required = vm_resources.memory * 1024 + disk_kb_required = vm_resources.disk_mib * 1024 + + # Check free memory + if sys.mem.available_kB < memory_kb_required: + continue + + # Check free disk + if sys.disk.available_kB < disk_kb_required: + continue + filtered_crn.append(crn_) return filtered_crn diff --git a/src/aleph/sdk/types.py b/src/aleph/sdk/types.py index 8d952b18..12fc78ad 100644 --- a/src/aleph/sdk/types.py +++ b/src/aleph/sdk/types.py @@ -399,3 +399,9 @@ class Voucher(BaseModel): image: str icon: str attributes: list[VoucherAttribute] + + +class VmResources(BaseModel): + vcpus: int + memory: int + disk_mib: int From 33ac0bea54f53206fc749e8cc02e1e3601df264b Mon Sep 17 00:00:00 2001 From: 1yam Date: Wed, 24 Dec 2025 14:45:28 +0100 Subject: [PATCH 02/13] fix: `total_kB`and `available_kB` in MemoryInfo should always be positive --- src/aleph/sdk/client/services/crn.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/aleph/sdk/client/services/crn.py b/src/aleph/sdk/client/services/crn.py index 4120bcc4..cd334f7a 100644 --- a/src/aleph/sdk/client/services/crn.py +++ b/src/aleph/sdk/client/services/crn.py @@ -4,7 +4,7 @@ import aiohttp from aiohttp.client_exceptions import ClientResponseError from aleph_message.models import ItemHash -from pydantic import BaseModel +from pydantic import BaseModel, PositiveInt from aleph.sdk.conf import settings from aleph.sdk.exceptions import MethodNotAvailableOnCRN, VmNotFoundOnHost @@ -40,8 +40,8 @@ class CpuInfo(BaseModel): class MemoryInfo(BaseModel): - total_kB: int - available_kB: int + total_kB: PositiveInt + available_kB: PositiveInt class DiskInfo(BaseModel): From b5ff6fd68ca09005fc7d5af66d7525dc124390dc Mon Sep 17 00:00:00 2001 From: 1yam Date: Wed, 24 Dec 2025 14:46:33 +0100 Subject: [PATCH 03/13] fix: `properties` and `gpu` in SystemUsage should be typed instead of dict --- src/aleph/sdk/client/services/crn.py | 32 +++++++++++++++++++++------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/aleph/sdk/client/services/crn.py b/src/aleph/sdk/client/services/crn.py index cd334f7a..98cdb5d8 100644 --- a/src/aleph/sdk/client/services/crn.py +++ b/src/aleph/sdk/client/services/crn.py @@ -39,6 +39,12 @@ class CpuInfo(BaseModel): core_frequencies: CoreFrequencies +class CpuProperties(BaseModel): + architecture: str + vendor: str + features: List[str] = [] + + class MemoryInfo(BaseModel): total_kB: PositiveInt available_kB: PositiveInt @@ -54,14 +60,8 @@ class UsagePeriod(BaseModel): duration_seconds: int -class SystemUsage(BaseModel): - cpu: CpuInfo - mem: MemoryInfo - disk: DiskInfo - period: UsagePeriod - properties: dict - gpu: dict - active: bool +class Properties(BaseModel): + cpu: CpuProperties class GPU(BaseModel): @@ -70,9 +70,25 @@ class GPU(BaseModel): device_name: str device_class: str pci_host: str + device_id: str compatible: bool +class GpuUsages(BaseModel): + devices: List[GPU] = [] + available_devices: List[GPU] = [] + + +class SystemUsage(BaseModel): + cpu: CpuInfo + mem: MemoryInfo + disk: DiskInfo + period: UsagePeriod + properties: Properties + gpu: GpuUsages + active: bool + + class NetworkGPUS(BaseModel): total_gpu_count: int available_gpu_count: int From 3931c787c7146a6ac77767c03804063cad440ab3 Mon Sep 17 00:00:00 2001 From: 1yam Date: Wed, 24 Dec 2025 14:47:18 +0100 Subject: [PATCH 04/13] fix: `crn_` renamed to `crn` in CRN services --- src/aleph/sdk/client/services/crn.py | 46 ++++++++++++++-------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/aleph/sdk/client/services/crn.py b/src/aleph/sdk/client/services/crn.py index 98cdb5d8..bb9359e7 100644 --- a/src/aleph/sdk/client/services/crn.py +++ b/src/aleph/sdk/client/services/crn.py @@ -132,20 +132,20 @@ def find_gpu_on_network(self): compatible_gpu: Dict[str, List[GPU]] = {} available_compatible_gpu: Dict[str, List[GPU]] = {} - for crn_ in self.crns: - if not crn_.gpu_support: + for crn in self.crns: + if not crn.gpu_support: continue # Extracts used GPU - compatible_gpu[crn_.address] = [] - for gpu in crn_.get("compatible_gpus", []): - compatible_gpu[crn_.address].append(GPU.model_validate(gpu)) + compatible_gpu[crn.address] = [] + for gpu in crn.get("compatible_gpus", []): + compatible_gpu[crn.address].append(GPU.model_validate(gpu)) gpu_count += 1 # Extracts available GPU - available_compatible_gpu[crn_.address] = [] - for gpu in crn_.get("compatible_available_gpus", []): - available_compatible_gpu[crn_.address].append(GPU.model_validate(gpu)) + available_compatible_gpu[crn.address] = [] + for gpu in crn.get("compatible_available_gpus", []): + available_compatible_gpu[crn.address].append(GPU.model_validate(gpu)) gpu_count += 1 available_gpu_count += 1 @@ -178,34 +178,34 @@ def filter_crn( """ filtered_crn: list[CRN] = [] - for crn_ in self.crns: + for crn in self.crns: # Check crn version - if crn_version and (crn_.version or "0.0.0") < crn_version: + if crn_version and (crn.version or "0.0.0") < crn_version: continue # Filter with ipv6 check if ipv6: - ipv6_check = crn_.get("ipv6_check") + ipv6_check = crn.get("ipv6_check") if not ipv6_check or not all(ipv6_check.values()): continue if stream_address and not extract_valid_eth_address( - crn_.payment_receiver_address or "" + crn.payment_receiver_address or "" ): continue # Confidential Filter - if confidential and not crn_.confidential_support: + if confidential and not crn.confidential_support: continue # Filter with GPU / Available GPU - available_gpu = crn_.get("compatible_available_gpus") - if gpu and (not crn_.gpu_support or not available_gpu): + available_gpu = crn.get("compatible_available_gpus") + if gpu and (not crn.gpu_support or not available_gpu): continue # Filter VM resources if vm_resources: - sys = crn_.system_usage + sys = crn.system_usage if not sys: continue @@ -225,21 +225,21 @@ def filter_crn( if sys.disk.available_kB < disk_kb_required: continue - filtered_crn.append(crn_) + filtered_crn.append(crn) return filtered_crn # Find CRN by address def find_crn_by_address(self, address: str) -> Optional[CRN]: - for crn_ in self.crns: - if crn_.address == sanitize_url(address): - return crn_ + for crn in self.crns: + if crn.address == sanitize_url(address): + return crn return None # Find CRN by hash def find_crn_by_hash(self, crn_hash: str) -> Optional[CRN]: - for crn_ in self.crns: - if crn_.hash == crn_hash: - return crn_ + for crn in self.crns: + if crn.hash == crn_hash: + return crn return None def find_crn( From 6950a910b1279a63d68b9d57ba3fd7feb9066834 Mon Sep 17 00:00:00 2001 From: 1yam Date: Mon, 5 Jan 2026 14:43:53 +0100 Subject: [PATCH 05/13] fix: ipv6_check filter only check that the field is here (so that the vm got ran) --- src/aleph/sdk/client/services/crn.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/aleph/sdk/client/services/crn.py b/src/aleph/sdk/client/services/crn.py index bb9359e7..5a1937a2 100644 --- a/src/aleph/sdk/client/services/crn.py +++ b/src/aleph/sdk/client/services/crn.py @@ -186,7 +186,13 @@ def filter_crn( # Filter with ipv6 check if ipv6: ipv6_check = crn.get("ipv6_check") - if not ipv6_check or not all(ipv6_check.values()): + + """ + The diagnostic VM has an issue where it can fail even when it is working correctly. + To avoid ending up with only a few working CRNs, we only ensure that the + `ipv6_check` field exists, which means the VM ran, even if the test failed. + """ + if not ipv6_check: # or not all(ipv6_check.values()) continue if stream_address and not extract_valid_eth_address( From 0a2a343c2a59e986c5c1129b00b88c847ab4eafe Mon Sep 17 00:00:00 2001 From: 1yam Date: Tue, 6 Jan 2026 16:34:06 +0100 Subject: [PATCH 06/13] fix: cpu count, disk total kb and avaiable kb should be PositiveInt instead of regular int --- src/aleph/sdk/client/services/crn.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/aleph/sdk/client/services/crn.py b/src/aleph/sdk/client/services/crn.py index 5a1937a2..a204aa38 100644 --- a/src/aleph/sdk/client/services/crn.py +++ b/src/aleph/sdk/client/services/crn.py @@ -34,7 +34,7 @@ class CoreFrequencies(BaseModel): class CpuInfo(BaseModel): - count: int + count: PositiveInt load_average: CpuLoad core_frequencies: CoreFrequencies @@ -51,8 +51,8 @@ class MemoryInfo(BaseModel): class DiskInfo(BaseModel): - total_kB: int - available_kB: int + total_kB: PositiveInt + available_kB: PositiveInt class UsagePeriod(BaseModel): From 7e8714261794da6e0a3cbc962e1106264c7ffdf3 Mon Sep 17 00:00:00 2001 From: 1yam Date: Tue, 6 Jan 2026 16:34:32 +0100 Subject: [PATCH 07/13] fix: use regular comment instead of docstring --- src/aleph/sdk/client/services/crn.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/aleph/sdk/client/services/crn.py b/src/aleph/sdk/client/services/crn.py index a204aa38..9e61edfa 100644 --- a/src/aleph/sdk/client/services/crn.py +++ b/src/aleph/sdk/client/services/crn.py @@ -187,11 +187,10 @@ def filter_crn( if ipv6: ipv6_check = crn.get("ipv6_check") - """ - The diagnostic VM has an issue where it can fail even when it is working correctly. - To avoid ending up with only a few working CRNs, we only ensure that the - `ipv6_check` field exists, which means the VM ran, even if the test failed. - """ + # The diagnostic VM has an issue where it can fail even when it is working correctly. + # To avoid ending up with only a few working CRNs, we only ensure that the + # `ipv6_check` field exists, which means the VM ran, even if the test failed. + if not ipv6_check: # or not all(ipv6_check.values()) continue From 550c6c8dd0f2dcdfdaf3eb4bd1f74d7c07e401a7 Mon Sep 17 00:00:00 2001 From: 1yam Date: Tue, 6 Jan 2026 16:45:22 +0100 Subject: [PATCH 08/13] fix: available_kb can be 0 --- src/aleph/sdk/client/services/crn.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aleph/sdk/client/services/crn.py b/src/aleph/sdk/client/services/crn.py index 9e61edfa..20486c43 100644 --- a/src/aleph/sdk/client/services/crn.py +++ b/src/aleph/sdk/client/services/crn.py @@ -47,12 +47,12 @@ class CpuProperties(BaseModel): class MemoryInfo(BaseModel): total_kB: PositiveInt - available_kB: PositiveInt + available_kB: int class DiskInfo(BaseModel): total_kB: PositiveInt - available_kB: PositiveInt + available_kB: int class UsagePeriod(BaseModel): From 9006fc9eaa3b6d6b51baaf6f54adba4f6858d591 Mon Sep 17 00:00:00 2001 From: 1yam Date: Tue, 6 Jan 2026 16:54:51 +0100 Subject: [PATCH 09/13] fix: use NonNegativeInt for available_kB instead of regular int since it's cant be negative --- src/aleph/sdk/client/services/crn.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/aleph/sdk/client/services/crn.py b/src/aleph/sdk/client/services/crn.py index 20486c43..d2e41b85 100644 --- a/src/aleph/sdk/client/services/crn.py +++ b/src/aleph/sdk/client/services/crn.py @@ -4,7 +4,7 @@ import aiohttp from aiohttp.client_exceptions import ClientResponseError from aleph_message.models import ItemHash -from pydantic import BaseModel, PositiveInt +from pydantic import BaseModel, PositiveInt, NonNegativeInt from aleph.sdk.conf import settings from aleph.sdk.exceptions import MethodNotAvailableOnCRN, VmNotFoundOnHost @@ -47,12 +47,12 @@ class CpuProperties(BaseModel): class MemoryInfo(BaseModel): total_kB: PositiveInt - available_kB: int + available_kB: NonNegativeInt class DiskInfo(BaseModel): total_kB: PositiveInt - available_kB: int + available_kB: NonNegativeInt class UsagePeriod(BaseModel): From a57000e03c9a5656e81a57741d91aec7fa9f04e8 Mon Sep 17 00:00:00 2001 From: 1yam Date: Tue, 6 Jan 2026 17:36:03 +0100 Subject: [PATCH 10/13] fix: isort issue --- src/aleph/sdk/client/services/crn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aleph/sdk/client/services/crn.py b/src/aleph/sdk/client/services/crn.py index d2e41b85..1d3fe317 100644 --- a/src/aleph/sdk/client/services/crn.py +++ b/src/aleph/sdk/client/services/crn.py @@ -4,7 +4,7 @@ import aiohttp from aiohttp.client_exceptions import ClientResponseError from aleph_message.models import ItemHash -from pydantic import BaseModel, PositiveInt, NonNegativeInt +from pydantic import BaseModel, NonNegativeInt, PositiveInt from aleph.sdk.conf import settings from aleph.sdk.exceptions import MethodNotAvailableOnCRN, VmNotFoundOnHost From 60f6d8c5c05f15cb7a6c2176916395b1a73ce4d7 Mon Sep 17 00:00:00 2001 From: 1yam Date: Wed, 14 Jan 2026 14:12:51 +0100 Subject: [PATCH 11/13] fix: enforce ipv6 check when filtering (diagnostic vm was updated) --- src/aleph/sdk/client/services/crn.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/aleph/sdk/client/services/crn.py b/src/aleph/sdk/client/services/crn.py index 1d3fe317..0311b238 100644 --- a/src/aleph/sdk/client/services/crn.py +++ b/src/aleph/sdk/client/services/crn.py @@ -187,11 +187,7 @@ def filter_crn( if ipv6: ipv6_check = crn.get("ipv6_check") - # The diagnostic VM has an issue where it can fail even when it is working correctly. - # To avoid ending up with only a few working CRNs, we only ensure that the - # `ipv6_check` field exists, which means the VM ran, even if the test failed. - - if not ipv6_check: # or not all(ipv6_check.values()) + if not ipv6_check or not all(ipv6_check.values()): continue if stream_address and not extract_valid_eth_address( From c4bc4e1b834a415191dd6e4bd2f0f292b2932a06 Mon Sep 17 00:00:00 2001 From: Olivier Desenfans Date: Wed, 14 Jan 2026 17:28:05 +0100 Subject: [PATCH 12/13] review fixes --- src/aleph/sdk/client/services/crn.py | 14 +++++++------- src/aleph/sdk/types.py | 7 ++++--- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/aleph/sdk/client/services/crn.py b/src/aleph/sdk/client/services/crn.py index 0311b238..33c39e60 100644 --- a/src/aleph/sdk/client/services/crn.py +++ b/src/aleph/sdk/client/services/crn.py @@ -57,7 +57,7 @@ class DiskInfo(BaseModel): class UsagePeriod(BaseModel): start_timestamp: datetime - duration_seconds: int + duration_seconds: NonNegativeInt class Properties(BaseModel): @@ -206,24 +206,24 @@ def filter_crn( # Filter VM resources if vm_resources: - sys = crn.system_usage - if not sys: + crn_usage = crn.system_usage + if not crn_usage: continue # Check CPU count - if sys.cpu.count < vm_resources.vcpus: + if crn_usage.cpu.count < vm_resources.vcpus: continue # Convert MiB to kB (1 MiB = 1024 kB) for proper comparison - memory_kb_required = vm_resources.memory * 1024 + memory_kb_required = vm_resources.memory_mib * 1024 disk_kb_required = vm_resources.disk_mib * 1024 # Check free memory - if sys.mem.available_kB < memory_kb_required: + if crn_usage.mem.available_kB < memory_kb_required: continue # Check free disk - if sys.disk.available_kB < disk_kb_required: + if crn_usage.disk.available_kB < disk_kb_required: continue filtered_crn.append(crn) diff --git a/src/aleph/sdk/types.py b/src/aleph/sdk/types.py index 12fc78ad..3949cd2e 100644 --- a/src/aleph/sdk/types.py +++ b/src/aleph/sdk/types.py @@ -19,6 +19,7 @@ BaseModel, ConfigDict, Field, + PositiveInt, RootModel, TypeAdapter, field_validator, @@ -402,6 +403,6 @@ class Voucher(BaseModel): class VmResources(BaseModel): - vcpus: int - memory: int - disk_mib: int + vcpus: PositiveInt + memory_mib: PositiveInt + disk_mib: PositiveInt From b944f7bb0556abd7a1bd2e0fe2e5d34b366ca0d1 Mon Sep 17 00:00:00 2001 From: Olivier Desenfans Date: Wed, 14 Jan 2026 17:30:33 +0100 Subject: [PATCH 13/13] undo rename --- src/aleph/sdk/client/services/crn.py | 2 +- src/aleph/sdk/types.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aleph/sdk/client/services/crn.py b/src/aleph/sdk/client/services/crn.py index 33c39e60..19477cb4 100644 --- a/src/aleph/sdk/client/services/crn.py +++ b/src/aleph/sdk/client/services/crn.py @@ -215,7 +215,7 @@ def filter_crn( continue # Convert MiB to kB (1 MiB = 1024 kB) for proper comparison - memory_kb_required = vm_resources.memory_mib * 1024 + memory_kb_required = vm_resources.memory * 1024 disk_kb_required = vm_resources.disk_mib * 1024 # Check free memory diff --git a/src/aleph/sdk/types.py b/src/aleph/sdk/types.py index 3949cd2e..ed2524cb 100644 --- a/src/aleph/sdk/types.py +++ b/src/aleph/sdk/types.py @@ -404,5 +404,5 @@ class Voucher(BaseModel): class VmResources(BaseModel): vcpus: PositiveInt - memory_mib: PositiveInt + memory: PositiveInt disk_mib: PositiveInt