Skip to content

Commit 0395164

Browse files
author
ikemilian-lewis
committed
fix: rebase error handling and registration
1 parent b1dacaf commit 0395164

21 files changed

Lines changed: 502 additions & 105 deletions

src/orb/domain/template/factory.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,9 @@ def create_template(
113113
except Exception as e:
114114
if self._logger:
115115
self._logger.error("Failed to create %s template: %s", provider_type, e)
116-
# Fall back to core template
116+
raise
117117

118-
# Fall back to core template
118+
# Use the core template only when no provider-specific class is registered.
119119
try:
120120
template = Template(**template_data)
121121

src/orb/domain/template/template_aggregate.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ class Template(BaseModel):
7474
# Tags and metadata
7575
tags: dict[str, Any] = Field(default_factory=dict)
7676
metadata: dict[str, Any] = Field(default_factory=dict)
77+
provider_data: dict[str, Any] = Field(default_factory=dict)
7778

7879
# Provider configuration (multi-provider support)
7980
provider_type: Optional[str] = None

src/orb/providers/azure/auth/azure_auth_strategy.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@
77
import asyncio
88
from typing import Any, Optional
99

10-
from orb.domain.base.dependency_injection import injectable
1110
from orb.domain.base.ports import LoggingPort
1211
from orb.infrastructure.adapters.ports.auth import (
1312
AuthContext,
1413
AuthPort,
1514
AuthResult,
1615
AuthStatus,
1716
)
17+
from orb.infrastructure.di.injectable import injectable
1818
from orb.providers.azure.infrastructure.credential_factory import (
1919
AsyncAzureAccessTokenProviderProtocol,
2020
AsyncDefaultAzureAccessTokenProvider,

src/orb/providers/azure/exceptions/azure_exceptions.py

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,37 @@
55

66
from typing import Any, Optional
77

8-
from orb.domain.base.exceptions import InfrastructureError
8+
from orb.providers.base.exceptions import (
9+
ProviderAuthError,
10+
ProviderConfigError,
11+
ProviderError,
12+
ProviderPermanentError,
13+
ProviderQuotaError,
14+
ProviderTransientError,
15+
)
916

1017

11-
class AzureError(InfrastructureError):
18+
class AzureError(ProviderError):
1219
"""Base class for Azure-related errors."""
1320

1421
def __init__(
1522
self,
1623
message: str,
1724
details: Optional[dict[str, Any]] = None,
1825
error_code: Optional[str] = None,
26+
*,
27+
provider_name: Optional[str] = None,
28+
underlying_exception: Optional[BaseException] = None,
29+
is_retryable: Optional[bool] = None,
1930
) -> None:
20-
super().__init__(message, error_code or self.__class__.__name__, details)
31+
super().__init__(
32+
message,
33+
provider_type="azure",
34+
provider_name=provider_name,
35+
underlying_exception=underlying_exception,
36+
details=details,
37+
is_retryable=is_retryable,
38+
)
2139
self.error_code = error_code or self.__class__.__name__
2240

2341
def to_dict(self) -> dict[str, Any]:
@@ -27,13 +45,20 @@ def to_dict(self) -> dict[str, Any]:
2745
result["error_code"] = self.error_code
2846
return result
2947

48+
def safe_to_dict(self) -> dict[str, Any]:
49+
"""Serialize the exception without exposing its underlying exception."""
50+
result = super().safe_to_dict()
51+
if self.error_code and self.error_code != self.__class__.__name__:
52+
result["error_code"] = self.error_code
53+
return result
54+
3055

3156
# ---------------------------------------------------------------------------
3257
# Validation
3358
# ---------------------------------------------------------------------------
3459

3560

36-
class AzureValidationError(AzureError):
61+
class AzureValidationError(AzureError, ProviderPermanentError):
3762
"""Raised when Azure resource validation fails."""
3863

3964

@@ -66,7 +91,7 @@ def __init__(self, message: str, image_ref: str, details: Optional[dict[str, Any
6691
# ---------------------------------------------------------------------------
6792

6893

69-
class AzureEntityNotFoundError(AzureError):
94+
class AzureEntityNotFoundError(AzureError, ProviderPermanentError):
7095
"""Raised when an Azure resource is not found."""
7196

7297

@@ -91,7 +116,7 @@ def __init__(self, message: str, vmss_name: str, details: Optional[dict[str, Any
91116
# ---------------------------------------------------------------------------
92117

93118

94-
class QuotaExceededError(AzureError):
119+
class QuotaExceededError(AzureError, ProviderQuotaError):
95120
"""Raised when Azure service quotas would be exceeded."""
96121

97122

@@ -128,11 +153,11 @@ def __init__(
128153
# ---------------------------------------------------------------------------
129154

130155

131-
class ResourceInUseError(AzureError):
156+
class ResourceInUseError(AzureError, ProviderTransientError):
132157
"""Raised when an Azure resource is already in use."""
133158

134159

135-
class ResourceStateError(AzureError):
160+
class ResourceStateError(AzureError, ProviderTransientError):
136161
"""Raised when a resource is in an invalid state for the operation."""
137162

138163
def __init__(
@@ -162,11 +187,11 @@ def __init__(
162187
# ---------------------------------------------------------------------------
163188

164189

165-
class AuthenticationError(AzureError):
190+
class AuthenticationError(AzureError, ProviderAuthError):
166191
"""Raised when Azure authentication fails."""
167192

168193

169-
class AuthorizationError(AzureError):
194+
class AuthorizationError(AzureError, ProviderAuthError):
170195
"""Raised when there are insufficient permissions."""
171196

172197

@@ -193,11 +218,11 @@ def __init__(
193218
# ---------------------------------------------------------------------------
194219

195220

196-
class RateLimitError(AzureError):
221+
class RateLimitError(AzureError, ProviderQuotaError):
197222
"""Raised when Azure API rate limits are exceeded."""
198223

199224

200-
class NetworkError(AzureError):
225+
class NetworkError(AzureError, ProviderTransientError):
201226
"""Raised when there are network-related issues."""
202227

203228

@@ -206,11 +231,11 @@ class NetworkError(AzureError):
206231
# ---------------------------------------------------------------------------
207232

208233

209-
class AzureInfrastructureError(AzureError):
234+
class AzureInfrastructureError(AzureError, ProviderTransientError):
210235
"""Raised for general Azure infrastructure errors."""
211236

212237

213-
class AzureConfigurationError(AzureError):
238+
class AzureConfigurationError(AzureError, ProviderConfigError):
214239
"""Raised when Azure configuration is invalid."""
215240

216241

@@ -219,7 +244,7 @@ class AzureConfigurationError(AzureError):
219244
# ---------------------------------------------------------------------------
220245

221246

222-
class LaunchError(AzureError):
247+
class LaunchError(AzureError, ProviderTransientError):
223248
"""Instance / VMSS launch failed."""
224249

225250
def __init__(
@@ -265,7 +290,7 @@ def __init__(
265290
self.vmss_name = vmss_name
266291

267292

268-
class TerminationError(AzureError):
293+
class TerminationError(AzureError, ProviderTransientError):
269294
"""Instance termination failed."""
270295

271296
def __init__(
@@ -281,7 +306,7 @@ def __init__(
281306
self.resource_ids = resource_ids
282307

283308

284-
class ResourceCleanupError(AzureError):
309+
class ResourceCleanupError(AzureError, ProviderTransientError):
285310
"""Failed to clean up Azure resources."""
286311

287312
def __init__(
@@ -303,7 +328,7 @@ def __init__(
303328
self.resource_type = resource_type
304329

305330

306-
class TaggingError(AzureError):
331+
class TaggingError(AzureError, ProviderTransientError):
307332
"""Failed to tag Azure resources."""
308333

309334
def __init__(
@@ -330,7 +355,7 @@ class CycleCloudError(AzureError):
330355
"""Base class for CycleCloud-related errors."""
331356

332357

333-
class CycleCloudConnectionError(CycleCloudError):
358+
class CycleCloudConnectionError(CycleCloudError, ProviderTransientError):
334359
"""Failed to connect to the CycleCloud REST API."""
335360

336361
def __init__(
@@ -346,7 +371,7 @@ def __init__(
346371
self.url = url
347372

348373

349-
class CycleCloudClusterNotFoundError(CycleCloudError):
374+
class CycleCloudClusterNotFoundError(CycleCloudError, ProviderPermanentError):
350375
"""The specified CycleCloud cluster was not found."""
351376

352377
def __init__(
@@ -362,7 +387,7 @@ def __init__(
362387
self.cluster_name = cluster_name
363388

364389

365-
class CycleCloudNodeError(CycleCloudError):
390+
class CycleCloudNodeError(CycleCloudError, ProviderTransientError):
366391
"""Failed to add or manage CycleCloud nodes."""
367392

368393
def __init__(

src/orb/providers/azure/infrastructure/adapters/azure_validation_adapter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
from typing import Any
44

5-
from orb.domain.base.dependency_injection import injectable
65
from orb.domain.base.ports.logging_port import LoggingPort
76
from orb.domain.base.ports.provider_validation_port import BaseProviderValidationAdapter
7+
from orb.infrastructure.di.injectable import injectable
88
from orb.providers.azure.capabilities import get_supported_api_capabilities, get_supported_apis
99
from orb.providers.azure.configuration.config import AzureProviderConfig
1010
from orb.providers.azure.configuration.validator import validate_azure_template

src/orb/providers/azure/infrastructure/adapters/template_adapter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66

77
from typing import Any
88

9-
from orb.domain.base.dependency_injection import injectable
109
from orb.domain.base.ports import LoggingPort
10+
from orb.infrastructure.di.injectable import injectable
1111
from orb.providers.azure.domain.template.azure_template_aggregate import AzureTemplate
1212

1313

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""Azure adapter for provider-owned template example generation."""
2+
3+
from typing import Any, Optional
4+
5+
from orb.providers.azure.infrastructure.azure_handler_factory import (
6+
generate_azure_example_templates,
7+
)
8+
9+
10+
class AzureTemplateExampleGeneratorAdapter:
11+
"""Expose Azure handler examples through the shared generator port."""
12+
13+
def generate_example_templates(
14+
self,
15+
provider_name: str,
16+
provider_api: Optional[str] = None,
17+
) -> list[Any]:
18+
"""Return Azure examples, optionally restricted to one provider API."""
19+
examples = generate_azure_example_templates()
20+
if provider_api is None:
21+
return examples
22+
return [example for example in examples if example.get("provider_api") == provider_api]

src/orb/providers/azure/infrastructure/azure_client.py

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
- Lazy initialization of Azure service clients
55
- Explicit lifecycle cleanup for owned Azure SDK resources
66
- Explicit runtime config assembly before client construction
7-
- Optional metrics instrumentation
87
98
Azure SDK clients wrapped:
109
- ComputeManagementClient (VMs, VMSS, Disks, Images, Galleries)
@@ -27,9 +26,8 @@
2726
from typing import TYPE_CHECKING, Any, Callable, Optional
2827

2928
from orb.config import PerformanceConfig
30-
from orb.domain.base.dependency_injection import injectable
3129
from orb.domain.base.ports import LoggingPort
32-
from orb.monitoring.metrics import MetricsCollector
30+
from orb.infrastructure.di.injectable import injectable
3331
from orb.providers.azure.configuration.config import AzureProviderConfig
3432
from orb.providers.azure.exceptions.azure_exceptions import (
3533
AuthenticationError,
@@ -76,22 +74,18 @@ class AzureClient:
7674
and passed in as a typed infrastructure object.
7775
* Azure SDK management clients are created **lazily** on first access
7876
through ``@property`` accessors, keeping startup cost near zero.
79-
* An optional :class:`MetricsCollector` can be injected for API-call
80-
instrumentation (hooks are left as extension points for now).
8177
"""
8278

8379
def __init__(
8480
self,
8581
runtime_config: AzureClientRuntimeConfig,
8682
logger: LoggingPort,
87-
metrics: Optional[MetricsCollector] = None,
8883
) -> None:
8984
"""Initialise the Azure client wrapper.
9085
9186
Args:
9287
runtime_config: Fully resolved Azure client runtime settings.
9388
logger: Logger for diagnostic and operational messages.
94-
metrics: Optional metrics collector for Azure API instrumentation.
9589
"""
9690
self._runtime_config = runtime_config
9791
self._logger = logger
@@ -148,13 +142,6 @@ def __init__(
148142
network_lookup_error_types=self._network_lookup_error_types,
149143
)
150144

151-
# Metrics instrumentation (extension point)
152-
self._metrics = metrics
153-
if metrics:
154-
logger.info("Azure API metrics collection enabled")
155-
else:
156-
logger.debug("Azure API metrics collection disabled - no MetricsCollector provided")
157-
158145
self._logger.info(
159146
"Azure client initialised: region=%s, subscription=%s, resource_group=%s, "
160147
"retries=%d, connect_timeout=%ds, read_timeout=%ds",
@@ -711,16 +698,3 @@ async def resolve_network_identity_from_nic_refs_async(
711698
) -> AzureNetworkIdentity:
712699
"""Async variant of NIC-ref network identity resolution."""
713700
return await self._get_network_identity_resolver().resolve_from_nic_refs_async(nic_refs)
714-
715-
# ------------------------------------------------------------------
716-
# Metrics / observability
717-
# ------------------------------------------------------------------
718-
719-
def get_metrics_stats(self) -> dict[str, Any]:
720-
"""Return metrics collection statistics.
721-
722-
Returns:
723-
Dictionary with metrics status; currently a placeholder for
724-
future instrumentation parity with the AWS client.
725-
"""
726-
return {"metrics_enabled": self._metrics is not None}

0 commit comments

Comments
 (0)