Skip to content

Commit d9426e3

Browse files
committed
Add latest crn version and compatible_available_gpus filters
1 parent 7d9ebd6 commit d9426e3

File tree

5 files changed

+67
-19
lines changed

5 files changed

+67
-19
lines changed

src/aleph_client/commands/instance/__init__.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ async def create(
374374
raise typer.Exit(1) from e
375375

376376
echo("Fetching compute resource node's list...")
377-
await fetch_crn_list(ipv6=False, stream_address=False) # Precache complete unfiltered CRN list
377+
await fetch_crn_list() # Precache complete unfiltered CRN list
378378

379379
if crn_url or crn_hash:
380380
try:
@@ -395,7 +395,11 @@ async def create(
395395

396396
while not crn:
397397
crn_table = CRNTable(
398-
only_reward_address=is_stream, only_qemu=is_qemu, only_confidentials=confidential, only_gpu=gpu
398+
only_latest_crn_version=True,
399+
only_reward_address=is_stream,
400+
only_qemu=is_qemu,
401+
only_confidentials=confidential,
402+
only_gpu=gpu,
399403
)
400404
crn = await crn_table.run_async()
401405
if not crn:
@@ -761,7 +765,7 @@ async def _show_instances(messages: builtins.list[InstanceMessage]):
761765
table.add_column("Specifications", style="blue")
762766
table.add_column("Logs", style="blue", overflow="fold")
763767

764-
await fetch_crn_list() # Precache CRN list
768+
await fetch_crn_list() # Precache complete unfiltered CRN list
765769
scheduler_responses = dict(await asyncio.gather(*[fetch_vm_info(message) for message in messages]))
766770
uninitialized_confidential_found = False
767771
for message in messages:

src/aleph_client/commands/instance/display.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@
1111
from textual.widgets import DataTable, Footer, Label, ProgressBar
1212
from textual.widgets._data_table import RowKey
1313

14-
from aleph_client.commands.instance.network import fetch_crn_list
14+
from aleph_client.commands.instance.network import (
15+
fetch_crn_list,
16+
fetch_latest_crn_version,
17+
)
1518
from aleph_client.commands.node import _format_score
1619
from aleph_client.models import CRNInfo
1720

@@ -22,6 +25,7 @@ class CRNTable(App[CRNInfo]):
2225
table: DataTable
2326
tasks: set[asyncio.Task] = set()
2427
crns: dict[RowKey, CRNInfo] = {}
28+
last_crn_version: str
2529
total_crns: int
2630
active_crns: int = 0
2731
filtered_crns: int = 0
@@ -50,12 +54,14 @@ class CRNTable(App[CRNInfo]):
5054

5155
def __init__(
5256
self,
57+
only_latest_crn_version: bool = False,
5358
only_reward_address: bool = False,
5459
only_qemu: bool = False,
5560
only_confidentials: bool = False,
5661
only_gpu: bool = False,
5762
):
5863
super().__init__()
64+
self.only_latest_crn_version = only_latest_crn_version
5965
self.only_reward_address = only_reward_address
6066
self.only_qemu = only_qemu
6167
self.only_confidentials = only_confidentials
@@ -94,8 +100,8 @@ async def on_mount(self):
94100
task.add_done_callback(self.tasks.discard)
95101

96102
async def fetch_node_list(self):
97-
crn_list: list[CRNInfo] = await fetch_crn_list(ipv6=False, stream_address=False)
98-
self.crns: dict[RowKey, CRNInfo] = {RowKey(crn.hash): crn for crn in crn_list}
103+
self.crns: dict[RowKey, CRNInfo] = {RowKey(crn.hash): crn for crn in await fetch_crn_list()}
104+
self.last_crn_version = await fetch_latest_crn_version()
99105

100106
# Initialize the progress bar
101107
self.total_crns = len(self.crns)
@@ -112,6 +118,10 @@ async def fetch_node_list(self):
112118

113119
async def add_crn_info(self, crn: CRNInfo):
114120
self.active_crns += 1
121+
# Skip CRNs with legacy version
122+
if self.only_latest_crn_version and crn.version < self.last_crn_version:
123+
logger.debug(f"Skipping CRN {crn.hash}, legacy version")
124+
return
115125
# Skip CRNs without machine usage
116126
if not crn.machine_usage:
117127
logger.debug(f"Skipping CRN {crn.hash}, no machine usage")
@@ -133,11 +143,7 @@ async def add_crn_info(self, crn: CRNInfo):
133143
logger.debug(f"Skipping CRN {crn.hash}, no confidential support")
134144
return
135145
# Skip non-gpu CRNs if only-gpu is set
136-
if (
137-
self.only_gpu
138-
and not crn.gpu_support
139-
and not (crn.machine_usage.gpu and len(crn.machine_usage.gpu.available_devices) < 1)
140-
):
146+
if self.only_gpu and not (crn.gpu_support and crn.compatible_available_gpus):
141147
logger.debug(f"Skipping CRN {crn.hash}, no GPU support or without GPU available")
142148
return
143149
self.filtered_crns += 1

src/aleph_client/commands/instance/network.py

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,11 @@
3232

3333
logger = logging.getLogger(__name__)
3434

35+
latest_crn_version_link = "https://api.github.com/repos/aleph-im/aleph-vm/releases/latest"
36+
3537
settings_link = (
36-
f"{sanitize_url(settings.API_HOST)}/api/v0/aggregates/0xFba561a84A537fCaa567bb7A2257e7142701ae2A.json?keys=settings"
38+
f"{sanitize_url(settings.API_HOST)}/api/v0/"
39+
"aggregates/0xFba561a84A537fCaa567bb7A2257e7142701ae2A.json?keys=settings"
3740
)
3841

3942
crn_list_link = (
@@ -83,33 +86,58 @@ async def call_program_crn_list() -> Optional[dict]:
8386
raise Exception(error)
8487

8588

89+
@async_lru_cache
90+
async def fetch_latest_crn_version() -> str:
91+
"""Fetch the latest crn version.
92+
93+
Returns:
94+
str: Latest crn version as x.x.x.
95+
"""
96+
97+
async with ClientSession() as session:
98+
try:
99+
data = await fetch_json(session, latest_crn_version_link)
100+
return data.get("tag_name")
101+
except Exception as e:
102+
logger.error(f"Error while fetching latest crn version: {e}")
103+
raise Exit(code=1) from e
104+
105+
86106
@async_lru_cache
87107
async def fetch_crn_list(
88-
ipv6: bool = True, stream_address: bool = True, confidential: Optional[bool] = None, gpu: Optional[bool] = None
108+
latest_crn_version: bool = False,
109+
ipv6: bool = False,
110+
stream_address: bool = False,
111+
confidential: bool = False,
112+
gpu: bool = False,
89113
) -> list[CRNInfo]:
90-
"""Fetch compute resource node list.
114+
"""Fetch compute resource node list, unfiltered by default.
91115
92116
Args:
117+
latest_crn_version (bool): Filter by latest crn version.
93118
ipv6 (bool): Filter invalid IPv6 configuration.
94119
stream_address (bool): Filter invalid payment receiver address.
95-
confidential (Optional[bool]): Filter by confidential computing support.
96-
gpu (Optional[bool]): Filter by GPU support.
120+
confidential (bool): Filter by confidential computing support.
121+
gpu (bool): Filter by GPU support.
97122
Returns:
98123
list[CRNInfo]: List of compute resource nodes.
99124
"""
100125

101126
data = await call_program_crn_list()
127+
last_crn_version = await fetch_latest_crn_version()
102128
crns = []
103129
for crn in data.get("crns"):
130+
if latest_crn_version and crn.get("version") >= last_crn_version:
131+
continue
104132
if ipv6:
105133
ipv6_check = crn.get("ipv6_check")
106134
if not ipv6_check or not all(ipv6_check.values()):
107135
continue
108136
if stream_address and not crn.get("payment_receiver_address"):
109137
continue
110-
if confidential is not None and confidential != bool(crn.get("confidential_support")):
138+
if confidential and not crn.get("confidential_support"):
111139
continue
112-
if gpu is not None and gpu != bool(crn.get("gpu_support")):
140+
if gpu and not (crn.get("gpu_support") and crn.get("compatible_available_gpus")):
113141
continue
114142
try:
115143
crns.append(CRNInfo.from_unsanitized_input(crn))

src/aleph_client/models.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,12 @@ class MachineProperties(BaseModel):
5555

5656
class GpuDevice(BaseModel):
5757
vendor: str
58+
model: str
5859
device_name: str
5960
device_class: GpuDeviceClass
6061
pci_host: str
6162
device_id: str
63+
compatible: bool
6264

6365

6466
class GPUProperties(BaseModel):

tests/unit/test_instance.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,12 @@
6363
def dummy_gpu_device() -> GpuDevice:
6464
return GpuDevice(
6565
vendor="NVIDIA",
66+
model="RTX 4090",
6667
device_name="RTX 4090",
6768
device_class=GpuDeviceClass.VGA_COMPATIBLE_CONTROLLER,
6869
pci_host="01:00.0",
6970
device_id="abcd:1234",
71+
compatible=True,
7072
)
7173

7274

@@ -228,6 +230,10 @@ def create_mock_validate_ssh_pubkey_file():
228230
)
229231

230232

233+
def mock_fetch_latest_crn_version():
234+
return AsyncMock(return_value="v420.69")
235+
236+
231237
def create_mock_fetch_crn_info():
232238
mock_machine_info = dummy_machine_info()
233239
return AsyncMock(
@@ -248,7 +254,7 @@ def create_mock_fetch_crn_info():
248254
confidential_computing=True,
249255
gpu_support=True,
250256
terms_and_conditions=FAKE_STORE_HASH,
251-
compatible_available_gpus=[],
257+
compatible_available_gpus=[dummy_gpu_device()],
252258
)
253259
)
254260

@@ -456,6 +462,7 @@ async def test_create_instance(args, expected):
456462
@patch("aleph_client.commands.instance.get_balance", mock_get_balance)
457463
@patch("aleph_client.commands.instance.AlephHttpClient", mock_client_class)
458464
@patch("aleph_client.commands.instance.AuthenticatedAlephHttpClient", mock_auth_client_class)
465+
@patch("aleph_client.commands.instance.network.fetch_latest_crn_version", mock_fetch_latest_crn_version())
459466
@patch("aleph_client.commands.instance.fetch_crn_info", mock_fetch_crn_info)
460467
@patch("aleph_client.commands.instance.validated_int_prompt", mock_validated_int_prompt)
461468
@patch("aleph_client.commands.instance.wait_for_processed_instance", mock_wait_for_processed_instance)
@@ -518,6 +525,7 @@ async def test_list_instances():
518525
)
519526

520527
@patch("aleph_client.commands.instance._load_account", mock_load_account)
528+
@patch("aleph_client.commands.instance.network.fetch_latest_crn_version", mock_fetch_latest_crn_version())
521529
@patch("aleph_client.commands.files.AlephHttpClient", mock_client_class)
522530
@patch("aleph_client.commands.instance.AlephHttpClient", mock_auth_client_class)
523531
@patch("aleph_client.commands.instance.filter_only_valid_messages", mock_instance_messages)

0 commit comments

Comments
 (0)