Skip to content

Commit 5dda31e

Browse files
author
ikemilian-lewis
committed
fix: address PR comments
1 parent f00a428 commit 5dda31e

7 files changed

Lines changed: 362 additions & 51 deletions

File tree

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""Provider fulfilment contract value objects."""
2+
3+
from __future__ import annotations
4+
5+
from collections.abc import Mapping
6+
from dataclasses import dataclass
7+
from typing import Any, Literal
8+
9+
FulfilmentState = Literal["fulfilled", "in_progress", "partial", "failed"]
10+
11+
12+
@dataclass(frozen=True)
13+
class ProviderFulfilment:
14+
"""Provider-computed verdict on whether an acquire request is fulfilled."""
15+
16+
state: FulfilmentState
17+
message: str
18+
target_units: int | None = None
19+
fulfilled_units: int | None = None
20+
running_count: int | None = None
21+
pending_count: int | None = None
22+
failed_count: int | None = None
23+
24+
25+
@dataclass(frozen=True)
26+
class CheckHostsStatusResult:
27+
"""Combined status details and provider fulfilment verdict."""
28+
29+
instances: list[Mapping[str, Any]]
30+
fulfilment: ProviderFulfilment

src/orb/providers/azure/services/inventory_service.py

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -516,16 +516,22 @@ async def _build_instance_status_result(
516516
AzureProviderApi.VMSS,
517517
AzureProviderApi.VMSS_UNIFORM,
518518
)
519+
metadata: dict[str, Any] = {
520+
"operation": "get_instance_status",
521+
"instance_ids": read_context.instance_ids,
522+
"method": "handler",
523+
}
524+
status_result = self._resource_metadata_service.attach_provider_fulfilment(
525+
metadata,
526+
instances=handler_machines,
527+
target_units=len(read_context.instance_ids),
528+
)
519529
result = ProviderResult.success_result(
520530
{
521-
"instances": handler_machines,
531+
"instances": status_result.instances,
522532
"queried_count": len(read_context.instance_ids),
523533
},
524-
{
525-
"operation": "get_instance_status",
526-
"instance_ids": read_context.instance_ids,
527-
"method": "handler",
528-
},
534+
metadata,
529535
)
530536
if is_vmss and read_context.resource_group and read_context.resource_ids:
531537
await self._vmss_cleanup_coordinator.reconcile(
@@ -748,7 +754,19 @@ async def _build_describe_instances_result(
748754
resource_group=resource_group,
749755
deployment_service=deployment_service,
750756
)
751-
return ProviderResult.success_result({"instances": []}, metadata)
757+
status_result = self._resource_metadata_service.attach_provider_fulfilment(
758+
metadata,
759+
instances=[],
760+
target_units=(
761+
len(resource_ids)
762+
if provider_api == AzureProviderApi.SINGLE_VM
763+
else None
764+
),
765+
)
766+
return ProviderResult.success_result(
767+
{"instances": status_result.instances},
768+
metadata,
769+
)
752770

753771
fleet_errors = self._collect_instance_fleet_errors(instance_details)
754772
if fleet_errors:
@@ -762,7 +780,16 @@ async def _build_describe_instances_result(
762780
)
763781
if include_shortfall_metadata:
764782
self._resource_metadata_service.augment_shortfall_metadata(metadata)
783+
status_result = self._resource_metadata_service.attach_provider_fulfilment(
784+
metadata,
785+
instances=instance_details,
786+
target_units=(
787+
len(resource_ids)
788+
if provider_api == AzureProviderApi.SINGLE_VM
789+
else None
790+
),
791+
)
765792
return ProviderResult.success_result(
766-
data={"instances": instance_details},
793+
data={"instances": status_result.instances},
767794
metadata=metadata,
768795
)

src/orb/providers/azure/services/resource_metadata_service.py

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,20 @@
33
from __future__ import annotations
44

55
import asyncio
6+
from collections.abc import Mapping, Sequence
67
from dataclasses import dataclass
78
from typing import Any, Optional, Protocol, TypedDict
89

910
from orb.domain.base.ports import LoggingPort
11+
from orb.domain.base.provider_fulfilment import (
12+
CheckHostsStatusResult,
13+
FulfilmentState,
14+
ProviderFulfilment,
15+
)
16+
17+
_RUNNING_STATES = frozenset({"running"})
18+
_PENDING_STATES = frozenset({"pending", "creating", "starting", "updating", "unknown"})
19+
_FAILED_STATES = frozenset({"failed", "terminated", "stopped", "deallocated"})
1020

1121

1222
@dataclass
@@ -271,3 +281,123 @@ def augment_shortfall_metadata(metadata: dict[str, Any]) -> None:
271281
+ (f"; causes={', '.join(likely_causes)}" if likely_causes else "")
272282
),
273283
}
284+
285+
def attach_provider_fulfilment(
286+
self,
287+
metadata: dict[str, Any],
288+
*,
289+
instances: Sequence[Mapping[str, Any]],
290+
target_units: int | None,
291+
) -> CheckHostsStatusResult:
292+
"""Attach and return the canonical provider status contract."""
293+
status_result = CheckHostsStatusResult(
294+
instances=list(instances),
295+
fulfilment=_build_provider_fulfilment(
296+
metadata=metadata,
297+
instances=instances,
298+
target_units=target_units,
299+
),
300+
)
301+
metadata["provider_fulfilment"] = status_result.fulfilment
302+
return status_result
303+
304+
305+
def _build_provider_fulfilment(
306+
*,
307+
metadata: dict[str, Any],
308+
instances: Sequence[Mapping[str, Any]],
309+
target_units: int | None,
310+
) -> ProviderFulfilment:
311+
fleet_errors = metadata.get("fleet_errors") or []
312+
# ProviderResult.metadata is the core provider result bag typed as dict[str, Any].
313+
# Azure owns this key, but reading it back from the core metadata boundary
314+
# requires validating the shape before trusting the capacity fields.
315+
capacity = metadata.get("fleet_capacity_fulfilment")
316+
if isinstance(capacity, dict):
317+
target = capacity.get("target_capacity_units")
318+
fulfilled = capacity.get("fulfilled_capacity_units")
319+
if isinstance(target, int) and isinstance(fulfilled, int):
320+
capacity_state = str(capacity.get("state") or "").lower()
321+
if fulfilled >= target and target > 0 and not fleet_errors:
322+
return ProviderFulfilment(
323+
state="fulfilled",
324+
message=f"Azure capacity fulfilled: {fulfilled}/{target}",
325+
target_units=target,
326+
fulfilled_units=fulfilled,
327+
)
328+
329+
if capacity_state == "failed" or fleet_errors:
330+
capacity_fulfilment_state: FulfilmentState = (
331+
"partial" if fulfilled > 0 else "failed"
332+
)
333+
return ProviderFulfilment(
334+
state=capacity_fulfilment_state,
335+
message=f"Azure capacity shortfall: {fulfilled}/{target}",
336+
target_units=target,
337+
fulfilled_units=fulfilled,
338+
)
339+
340+
return ProviderFulfilment(
341+
state="in_progress",
342+
message=f"Azure capacity provisioning: {fulfilled}/{target}",
343+
target_units=target,
344+
fulfilled_units=fulfilled,
345+
)
346+
347+
# Handler-backed status, SingleVM, CycleCloud, and dry-run results do not
348+
# have VMSS capacity metadata; derive their verdict from observed statuses.
349+
target = target_units
350+
running_count = _count_statuses(instances, _RUNNING_STATES)
351+
pending_count = _count_statuses(instances, _PENDING_STATES)
352+
failed_count = _count_statuses(instances, _FAILED_STATES)
353+
354+
if fleet_errors and running_count == 0:
355+
return ProviderFulfilment(
356+
state="failed",
357+
message="Azure provisioning failed before capacity became available",
358+
target_units=target,
359+
fulfilled_units=running_count,
360+
)
361+
362+
if target is not None and target > 0 and running_count >= target and failed_count == 0:
363+
return ProviderFulfilment(
364+
state="fulfilled",
365+
message=f"Azure instances running: {running_count}/{target}",
366+
target_units=target,
367+
fulfilled_units=running_count,
368+
)
369+
370+
if failed_count > 0 and pending_count == 0:
371+
state: FulfilmentState = "partial" if running_count > 0 else "failed"
372+
return ProviderFulfilment(
373+
state=state,
374+
message=(
375+
f"Azure instance shortfall: {running_count}/{target}"
376+
if target is not None
377+
else f"Azure instance failure: {running_count} running"
378+
),
379+
target_units=target,
380+
fulfilled_units=running_count,
381+
)
382+
383+
return ProviderFulfilment(
384+
state="in_progress",
385+
message=(
386+
f"Azure instances provisioning: {running_count}/{target}"
387+
if target is not None
388+
else f"Azure instances provisioning: {running_count} running"
389+
),
390+
target_units=target,
391+
fulfilled_units=running_count,
392+
)
393+
394+
395+
def _count_statuses(
396+
instances: Sequence[Mapping[str, Any]],
397+
statuses: frozenset[str],
398+
) -> int:
399+
return sum(
400+
1
401+
for instance in instances
402+
if str(instance.get("status") or "").lower() in statuses
403+
)

0 commit comments

Comments
 (0)