kailash 0.8.4__py3-none-any.whl → 0.8.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kailash/__init__.py +1 -7
- kailash/cli/__init__.py +11 -1
- kailash/cli/validation_audit.py +570 -0
- kailash/core/actors/supervisor.py +1 -1
- kailash/core/resilience/circuit_breaker.py +71 -1
- kailash/core/resilience/health_monitor.py +172 -0
- kailash/edge/compliance.py +33 -0
- kailash/edge/consistency.py +609 -0
- kailash/edge/coordination/__init__.py +30 -0
- kailash/edge/coordination/global_ordering.py +355 -0
- kailash/edge/coordination/leader_election.py +217 -0
- kailash/edge/coordination/partition_detector.py +296 -0
- kailash/edge/coordination/raft.py +485 -0
- kailash/edge/discovery.py +63 -1
- kailash/edge/migration/__init__.py +19 -0
- kailash/edge/migration/edge_migrator.py +832 -0
- kailash/edge/monitoring/__init__.py +21 -0
- kailash/edge/monitoring/edge_monitor.py +736 -0
- kailash/edge/prediction/__init__.py +10 -0
- kailash/edge/prediction/predictive_warmer.py +591 -0
- kailash/edge/resource/__init__.py +102 -0
- kailash/edge/resource/cloud_integration.py +796 -0
- kailash/edge/resource/cost_optimizer.py +949 -0
- kailash/edge/resource/docker_integration.py +919 -0
- kailash/edge/resource/kubernetes_integration.py +893 -0
- kailash/edge/resource/platform_integration.py +913 -0
- kailash/edge/resource/predictive_scaler.py +959 -0
- kailash/edge/resource/resource_analyzer.py +824 -0
- kailash/edge/resource/resource_pools.py +610 -0
- kailash/integrations/dataflow_edge.py +261 -0
- kailash/mcp_server/registry_integration.py +1 -1
- kailash/monitoring/__init__.py +18 -0
- kailash/monitoring/alerts.py +646 -0
- kailash/monitoring/metrics.py +677 -0
- kailash/nodes/__init__.py +2 -0
- kailash/nodes/ai/semantic_memory.py +2 -2
- kailash/nodes/base.py +545 -0
- kailash/nodes/edge/__init__.py +36 -0
- kailash/nodes/edge/base.py +240 -0
- kailash/nodes/edge/cloud_node.py +710 -0
- kailash/nodes/edge/coordination.py +239 -0
- kailash/nodes/edge/docker_node.py +825 -0
- kailash/nodes/edge/edge_data.py +582 -0
- kailash/nodes/edge/edge_migration_node.py +392 -0
- kailash/nodes/edge/edge_monitoring_node.py +421 -0
- kailash/nodes/edge/edge_state.py +673 -0
- kailash/nodes/edge/edge_warming_node.py +393 -0
- kailash/nodes/edge/kubernetes_node.py +652 -0
- kailash/nodes/edge/platform_node.py +766 -0
- kailash/nodes/edge/resource_analyzer_node.py +378 -0
- kailash/nodes/edge/resource_optimizer_node.py +501 -0
- kailash/nodes/edge/resource_scaler_node.py +397 -0
- kailash/nodes/ports.py +676 -0
- kailash/runtime/local.py +344 -1
- kailash/runtime/validation/__init__.py +20 -0
- kailash/runtime/validation/connection_context.py +119 -0
- kailash/runtime/validation/enhanced_error_formatter.py +202 -0
- kailash/runtime/validation/error_categorizer.py +164 -0
- kailash/runtime/validation/metrics.py +380 -0
- kailash/runtime/validation/performance.py +615 -0
- kailash/runtime/validation/suggestion_engine.py +212 -0
- kailash/testing/fixtures.py +2 -2
- kailash/workflow/builder.py +230 -4
- kailash/workflow/contracts.py +418 -0
- kailash/workflow/edge_infrastructure.py +369 -0
- kailash/workflow/migration.py +3 -3
- kailash/workflow/type_inference.py +669 -0
- {kailash-0.8.4.dist-info → kailash-0.8.5.dist-info}/METADATA +43 -27
- {kailash-0.8.4.dist-info → kailash-0.8.5.dist-info}/RECORD +73 -27
- kailash/nexus/__init__.py +0 -21
- kailash/nexus/cli/__init__.py +0 -5
- kailash/nexus/cli/__main__.py +0 -6
- kailash/nexus/cli/main.py +0 -176
- kailash/nexus/factory.py +0 -413
- kailash/nexus/gateway.py +0 -545
- {kailash-0.8.4.dist-info → kailash-0.8.5.dist-info}/WHEEL +0 -0
- {kailash-0.8.4.dist-info → kailash-0.8.5.dist-info}/entry_points.txt +0 -0
- {kailash-0.8.4.dist-info → kailash-0.8.5.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.8.4.dist-info → kailash-0.8.5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,796 @@
|
|
1
|
+
"""Cloud API integration for edge resource management."""
|
2
|
+
|
3
|
+
import asyncio
|
4
|
+
import base64
|
5
|
+
import json
|
6
|
+
from dataclasses import asdict, dataclass
|
7
|
+
from datetime import datetime, timedelta
|
8
|
+
from enum import Enum
|
9
|
+
from typing import Any, Dict, List, Optional, Union
|
10
|
+
|
11
|
+
try:
|
12
|
+
import boto3
|
13
|
+
from botocore.exceptions import ClientError, NoCredentialsError
|
14
|
+
|
15
|
+
AWS_AVAILABLE = True
|
16
|
+
except ImportError:
|
17
|
+
AWS_AVAILABLE = False
|
18
|
+
|
19
|
+
try:
|
20
|
+
from google.cloud import compute_v1
|
21
|
+
from google.oauth2 import service_account
|
22
|
+
|
23
|
+
GCP_AVAILABLE = True
|
24
|
+
except ImportError:
|
25
|
+
GCP_AVAILABLE = False
|
26
|
+
|
27
|
+
try:
|
28
|
+
from azure.identity import DefaultAzureCredential
|
29
|
+
from azure.mgmt.compute import ComputeManagementClient
|
30
|
+
from azure.mgmt.resource import ResourceManagementClient
|
31
|
+
|
32
|
+
AZURE_AVAILABLE = True
|
33
|
+
except ImportError:
|
34
|
+
AZURE_AVAILABLE = False
|
35
|
+
|
36
|
+
|
37
|
+
class CloudProvider(Enum):
|
38
|
+
"""Cloud providers."""
|
39
|
+
|
40
|
+
AWS = "aws"
|
41
|
+
GCP = "gcp"
|
42
|
+
AZURE = "azure"
|
43
|
+
ALIBABA_CLOUD = "alibaba_cloud"
|
44
|
+
DIGITAL_OCEAN = "digital_ocean"
|
45
|
+
|
46
|
+
|
47
|
+
class InstanceState(Enum):
|
48
|
+
"""Instance states."""
|
49
|
+
|
50
|
+
PENDING = "pending"
|
51
|
+
RUNNING = "running"
|
52
|
+
STOPPING = "stopping"
|
53
|
+
STOPPED = "stopped"
|
54
|
+
TERMINATED = "terminated"
|
55
|
+
UNKNOWN = "unknown"
|
56
|
+
|
57
|
+
|
58
|
+
class InstanceType(Enum):
|
59
|
+
"""Instance types."""
|
60
|
+
|
61
|
+
MICRO = "micro"
|
62
|
+
SMALL = "small"
|
63
|
+
MEDIUM = "medium"
|
64
|
+
LARGE = "large"
|
65
|
+
XLARGE = "xlarge"
|
66
|
+
CUSTOM = "custom"
|
67
|
+
|
68
|
+
|
69
|
+
@dataclass
|
70
|
+
class CloudInstance:
|
71
|
+
"""Cloud instance specification."""
|
72
|
+
|
73
|
+
instance_id: str
|
74
|
+
name: str
|
75
|
+
provider: CloudProvider
|
76
|
+
instance_type: str
|
77
|
+
image_id: str
|
78
|
+
region: str
|
79
|
+
zone: Optional[str] = None
|
80
|
+
state: InstanceState = InstanceState.PENDING
|
81
|
+
public_ip: Optional[str] = None
|
82
|
+
private_ip: Optional[str] = None
|
83
|
+
security_groups: Optional[List[str]] = None
|
84
|
+
tags: Optional[Dict[str, str]] = None
|
85
|
+
edge_node: Optional[str] = None
|
86
|
+
created_at: Optional[datetime] = None
|
87
|
+
launched_at: Optional[datetime] = None
|
88
|
+
|
89
|
+
def __post_init__(self):
|
90
|
+
if self.created_at is None:
|
91
|
+
self.created_at = datetime.now()
|
92
|
+
if self.security_groups is None:
|
93
|
+
self.security_groups = []
|
94
|
+
if self.tags is None:
|
95
|
+
self.tags = {}
|
96
|
+
|
97
|
+
def to_dict(self) -> Dict[str, Any]:
|
98
|
+
"""Convert to dictionary."""
|
99
|
+
data = asdict(self)
|
100
|
+
data["provider"] = self.provider.value
|
101
|
+
data["state"] = self.state.value
|
102
|
+
data["created_at"] = self.created_at.isoformat()
|
103
|
+
if self.launched_at:
|
104
|
+
data["launched_at"] = self.launched_at.isoformat()
|
105
|
+
return data
|
106
|
+
|
107
|
+
|
108
|
+
@dataclass
|
109
|
+
class InstanceSpec:
|
110
|
+
"""Instance creation specification."""
|
111
|
+
|
112
|
+
name: str
|
113
|
+
provider: CloudProvider
|
114
|
+
instance_type: str
|
115
|
+
image_id: str
|
116
|
+
region: str
|
117
|
+
zone: Optional[str] = None
|
118
|
+
subnet_id: Optional[str] = None
|
119
|
+
security_group_ids: Optional[List[str]] = None
|
120
|
+
key_name: Optional[str] = None
|
121
|
+
user_data: Optional[str] = None
|
122
|
+
tags: Optional[Dict[str, str]] = None
|
123
|
+
edge_node: Optional[str] = None
|
124
|
+
min_count: int = 1
|
125
|
+
max_count: int = 1
|
126
|
+
|
127
|
+
def __post_init__(self):
|
128
|
+
if self.security_group_ids is None:
|
129
|
+
self.security_group_ids = []
|
130
|
+
if self.tags is None:
|
131
|
+
self.tags = {}
|
132
|
+
|
133
|
+
def to_dict(self) -> Dict[str, Any]:
|
134
|
+
"""Convert to dictionary."""
|
135
|
+
data = asdict(self)
|
136
|
+
data["provider"] = self.provider.value
|
137
|
+
return data
|
138
|
+
|
139
|
+
|
140
|
+
@dataclass
|
141
|
+
class CloudMetrics:
|
142
|
+
"""Cloud instance metrics."""
|
143
|
+
|
144
|
+
instance_id: str
|
145
|
+
provider: CloudProvider
|
146
|
+
timestamp: datetime
|
147
|
+
cpu_utilization: float
|
148
|
+
memory_utilization: Optional[float] = None
|
149
|
+
network_in: Optional[float] = None
|
150
|
+
network_out: Optional[float] = None
|
151
|
+
disk_read_ops: Optional[float] = None
|
152
|
+
disk_write_ops: Optional[float] = None
|
153
|
+
disk_read_bytes: Optional[float] = None
|
154
|
+
disk_write_bytes: Optional[float] = None
|
155
|
+
|
156
|
+
def to_dict(self) -> Dict[str, Any]:
|
157
|
+
"""Convert to dictionary."""
|
158
|
+
data = asdict(self)
|
159
|
+
data["provider"] = self.provider.value
|
160
|
+
data["timestamp"] = self.timestamp.isoformat()
|
161
|
+
return data
|
162
|
+
|
163
|
+
|
164
|
+
class AWSIntegration:
|
165
|
+
"""AWS cloud integration."""
|
166
|
+
|
167
|
+
def __init__(self, region: str = "us-west-2", profile_name: Optional[str] = None):
|
168
|
+
if not AWS_AVAILABLE:
|
169
|
+
raise ImportError("AWS SDK not available. Install with: pip install boto3")
|
170
|
+
|
171
|
+
self.region = region
|
172
|
+
self.profile_name = profile_name
|
173
|
+
|
174
|
+
# AWS clients
|
175
|
+
if profile_name:
|
176
|
+
session = boto3.Session(profile_name=profile_name)
|
177
|
+
self.ec2_client = session.client("ec2", region_name=region)
|
178
|
+
self.cloudwatch_client = session.client("cloudwatch", region_name=region)
|
179
|
+
else:
|
180
|
+
self.ec2_client = boto3.client("ec2", region_name=region)
|
181
|
+
self.cloudwatch_client = boto3.client("cloudwatch", region_name=region)
|
182
|
+
|
183
|
+
async def create_instance(self, spec: InstanceSpec) -> Dict[str, Any]:
|
184
|
+
"""Create AWS EC2 instance."""
|
185
|
+
try:
|
186
|
+
# Prepare launch parameters
|
187
|
+
launch_params = {
|
188
|
+
"ImageId": spec.image_id,
|
189
|
+
"MinCount": spec.min_count,
|
190
|
+
"MaxCount": spec.max_count,
|
191
|
+
"InstanceType": spec.instance_type,
|
192
|
+
"TagSpecifications": [
|
193
|
+
{
|
194
|
+
"ResourceType": "instance",
|
195
|
+
"Tags": [{"Key": k, "Value": v} for k, v in spec.tags.items()]
|
196
|
+
+ [{"Key": "Name", "Value": spec.name}],
|
197
|
+
}
|
198
|
+
],
|
199
|
+
}
|
200
|
+
|
201
|
+
# Add optional parameters
|
202
|
+
if spec.key_name:
|
203
|
+
launch_params["KeyName"] = spec.key_name
|
204
|
+
if spec.security_group_ids:
|
205
|
+
launch_params["SecurityGroupIds"] = spec.security_group_ids
|
206
|
+
if spec.subnet_id:
|
207
|
+
launch_params["SubnetId"] = spec.subnet_id
|
208
|
+
if spec.user_data:
|
209
|
+
launch_params["UserData"] = base64.b64encode(
|
210
|
+
spec.user_data.encode()
|
211
|
+
).decode()
|
212
|
+
if spec.zone:
|
213
|
+
launch_params["Placement"] = {"AvailabilityZone": spec.zone}
|
214
|
+
|
215
|
+
# Add edge node tag
|
216
|
+
if spec.edge_node:
|
217
|
+
launch_params["TagSpecifications"][0]["Tags"].append(
|
218
|
+
{"Key": "edge-node", "Value": spec.edge_node}
|
219
|
+
)
|
220
|
+
|
221
|
+
response = await asyncio.to_thread(
|
222
|
+
self.ec2_client.run_instances, **launch_params
|
223
|
+
)
|
224
|
+
|
225
|
+
instance_data = response["Instances"][0]
|
226
|
+
|
227
|
+
return {
|
228
|
+
"status": "created",
|
229
|
+
"instance_id": instance_data["InstanceId"],
|
230
|
+
"instance_type": instance_data["InstanceType"],
|
231
|
+
"image_id": instance_data["ImageId"],
|
232
|
+
"state": instance_data["State"]["Name"],
|
233
|
+
"launch_time": instance_data["LaunchTime"].isoformat(),
|
234
|
+
"availability_zone": instance_data["Placement"]["AvailabilityZone"],
|
235
|
+
}
|
236
|
+
|
237
|
+
except ClientError as e:
|
238
|
+
return {
|
239
|
+
"status": "error",
|
240
|
+
"error": f"AWS API error: {e.response['Error']['Message']}",
|
241
|
+
"error_code": e.response["Error"]["Code"],
|
242
|
+
}
|
243
|
+
except Exception as e:
|
244
|
+
return {"status": "error", "error": f"Failed to create instance: {str(e)}"}
|
245
|
+
|
246
|
+
async def get_instance_status(self, instance_id: str) -> Dict[str, Any]:
|
247
|
+
"""Get AWS EC2 instance status."""
|
248
|
+
try:
|
249
|
+
response = await asyncio.to_thread(
|
250
|
+
self.ec2_client.describe_instances, InstanceIds=[instance_id]
|
251
|
+
)
|
252
|
+
|
253
|
+
if not response["Reservations"]:
|
254
|
+
return {"status": "error", "error": "Instance not found"}
|
255
|
+
|
256
|
+
instance = response["Reservations"][0]["Instances"][0]
|
257
|
+
|
258
|
+
return {
|
259
|
+
"instance_id": instance["InstanceId"],
|
260
|
+
"state": instance["State"]["Name"],
|
261
|
+
"instance_type": instance["InstanceType"],
|
262
|
+
"public_ip": instance.get("PublicIpAddress"),
|
263
|
+
"private_ip": instance.get("PrivateIpAddress"),
|
264
|
+
"launch_time": instance["LaunchTime"].isoformat(),
|
265
|
+
"availability_zone": instance["Placement"]["AvailabilityZone"],
|
266
|
+
"tags": {tag["Key"]: tag["Value"] for tag in instance.get("Tags", [])},
|
267
|
+
}
|
268
|
+
|
269
|
+
except ClientError as e:
|
270
|
+
return {
|
271
|
+
"status": "error",
|
272
|
+
"error": f"AWS API error: {e.response['Error']['Message']}",
|
273
|
+
"error_code": e.response["Error"]["Code"],
|
274
|
+
}
|
275
|
+
except Exception as e:
|
276
|
+
return {
|
277
|
+
"status": "error",
|
278
|
+
"error": f"Failed to get instance status: {str(e)}",
|
279
|
+
}
|
280
|
+
|
281
|
+
async def terminate_instance(self, instance_id: str) -> Dict[str, Any]:
|
282
|
+
"""Terminate AWS EC2 instance."""
|
283
|
+
try:
|
284
|
+
response = await asyncio.to_thread(
|
285
|
+
self.ec2_client.terminate_instances, InstanceIds=[instance_id]
|
286
|
+
)
|
287
|
+
|
288
|
+
instance_data = response["TerminatingInstances"][0]
|
289
|
+
|
290
|
+
return {
|
291
|
+
"status": "terminating",
|
292
|
+
"instance_id": instance_data["InstanceId"],
|
293
|
+
"current_state": instance_data["CurrentState"]["Name"],
|
294
|
+
"previous_state": instance_data["PreviousState"]["Name"],
|
295
|
+
}
|
296
|
+
|
297
|
+
except ClientError as e:
|
298
|
+
return {
|
299
|
+
"status": "error",
|
300
|
+
"error": f"AWS API error: {e.response['Error']['Message']}",
|
301
|
+
"error_code": e.response["Error"]["Code"],
|
302
|
+
}
|
303
|
+
except Exception as e:
|
304
|
+
return {
|
305
|
+
"status": "error",
|
306
|
+
"error": f"Failed to terminate instance: {str(e)}",
|
307
|
+
}
|
308
|
+
|
309
|
+
async def list_instances(
|
310
|
+
self, filters: Optional[Dict[str, List[str]]] = None
|
311
|
+
) -> List[Dict[str, Any]]:
|
312
|
+
"""List AWS EC2 instances."""
|
313
|
+
try:
|
314
|
+
params = {}
|
315
|
+
if filters:
|
316
|
+
params["Filters"] = [
|
317
|
+
{"Name": name, "Values": values} for name, values in filters.items()
|
318
|
+
]
|
319
|
+
|
320
|
+
response = await asyncio.to_thread(
|
321
|
+
self.ec2_client.describe_instances, **params
|
322
|
+
)
|
323
|
+
|
324
|
+
instances = []
|
325
|
+
for reservation in response["Reservations"]:
|
326
|
+
for instance in reservation["Instances"]:
|
327
|
+
instances.append(
|
328
|
+
{
|
329
|
+
"instance_id": instance["InstanceId"],
|
330
|
+
"state": instance["State"]["Name"],
|
331
|
+
"instance_type": instance["InstanceType"],
|
332
|
+
"public_ip": instance.get("PublicIpAddress"),
|
333
|
+
"private_ip": instance.get("PrivateIpAddress"),
|
334
|
+
"launch_time": instance["LaunchTime"].isoformat(),
|
335
|
+
"availability_zone": instance["Placement"][
|
336
|
+
"AvailabilityZone"
|
337
|
+
],
|
338
|
+
"tags": {
|
339
|
+
tag["Key"]: tag["Value"]
|
340
|
+
for tag in instance.get("Tags", [])
|
341
|
+
},
|
342
|
+
}
|
343
|
+
)
|
344
|
+
|
345
|
+
return instances
|
346
|
+
|
347
|
+
except ClientError as e:
|
348
|
+
raise RuntimeError(f"AWS API error: {e.response['Error']['Message']}")
|
349
|
+
except Exception as e:
|
350
|
+
raise RuntimeError(f"Failed to list instances: {str(e)}")
|
351
|
+
|
352
|
+
async def get_instance_metrics(
|
353
|
+
self, instance_id: str, start_time: datetime, end_time: datetime
|
354
|
+
) -> List[CloudMetrics]:
|
355
|
+
"""Get AWS CloudWatch metrics for instance."""
|
356
|
+
try:
|
357
|
+
# Get CPU utilization
|
358
|
+
cpu_response = await asyncio.to_thread(
|
359
|
+
self.cloudwatch_client.get_metric_statistics,
|
360
|
+
Namespace="AWS/EC2",
|
361
|
+
MetricName="CPUUtilization",
|
362
|
+
Dimensions=[{"Name": "InstanceId", "Value": instance_id}],
|
363
|
+
StartTime=start_time,
|
364
|
+
EndTime=end_time,
|
365
|
+
Period=300, # 5 minutes
|
366
|
+
Statistics=["Average"],
|
367
|
+
)
|
368
|
+
|
369
|
+
metrics = []
|
370
|
+
for datapoint in cpu_response["Datapoints"]:
|
371
|
+
metrics.append(
|
372
|
+
CloudMetrics(
|
373
|
+
instance_id=instance_id,
|
374
|
+
provider=CloudProvider.AWS,
|
375
|
+
timestamp=datapoint["Timestamp"],
|
376
|
+
cpu_utilization=datapoint["Average"],
|
377
|
+
)
|
378
|
+
)
|
379
|
+
|
380
|
+
return sorted(metrics, key=lambda x: x.timestamp)
|
381
|
+
|
382
|
+
except ClientError as e:
|
383
|
+
raise RuntimeError(f"AWS API error: {e.response['Error']['Message']}")
|
384
|
+
except Exception as e:
|
385
|
+
raise RuntimeError(f"Failed to get metrics: {str(e)}")
|
386
|
+
|
387
|
+
|
388
|
+
class GCPIntegration:
|
389
|
+
"""Google Cloud Platform integration."""
|
390
|
+
|
391
|
+
def __init__(
|
392
|
+
self,
|
393
|
+
project_id: str,
|
394
|
+
zone: str = "us-central1-a",
|
395
|
+
credentials_path: Optional[str] = None,
|
396
|
+
):
|
397
|
+
if not GCP_AVAILABLE:
|
398
|
+
raise ImportError(
|
399
|
+
"GCP SDK not available. Install with: pip install google-cloud-compute"
|
400
|
+
)
|
401
|
+
|
402
|
+
self.project_id = project_id
|
403
|
+
self.zone = zone
|
404
|
+
|
405
|
+
# Initialize credentials
|
406
|
+
if credentials_path:
|
407
|
+
credentials = service_account.Credentials.from_service_account_file(
|
408
|
+
credentials_path
|
409
|
+
)
|
410
|
+
self.instances_client = compute_v1.InstancesClient(credentials=credentials)
|
411
|
+
else:
|
412
|
+
self.instances_client = compute_v1.InstancesClient()
|
413
|
+
|
414
|
+
async def create_instance(self, spec: InstanceSpec) -> Dict[str, Any]:
|
415
|
+
"""Create GCP Compute Engine instance."""
|
416
|
+
try:
|
417
|
+
# Prepare instance configuration
|
418
|
+
instance_body = {
|
419
|
+
"name": spec.name,
|
420
|
+
"machine_type": f"zones/{spec.zone or self.zone}/machineTypes/{spec.instance_type}",
|
421
|
+
"disks": [
|
422
|
+
{
|
423
|
+
"boot": True,
|
424
|
+
"auto_delete": True,
|
425
|
+
"initialize_params": {"source_image": spec.image_id},
|
426
|
+
}
|
427
|
+
],
|
428
|
+
"network_interfaces": [
|
429
|
+
{
|
430
|
+
"network": "global/networks/default",
|
431
|
+
"access_configs": [
|
432
|
+
{"type": "ONE_TO_ONE_NAT", "name": "External NAT"}
|
433
|
+
],
|
434
|
+
}
|
435
|
+
],
|
436
|
+
"metadata": {"items": []},
|
437
|
+
"labels": spec.tags.copy(),
|
438
|
+
}
|
439
|
+
|
440
|
+
# Add edge node label
|
441
|
+
if spec.edge_node:
|
442
|
+
instance_body["labels"]["edge-node"] = spec.edge_node.replace("_", "-")
|
443
|
+
|
444
|
+
# Add user data if provided
|
445
|
+
if spec.user_data:
|
446
|
+
instance_body["metadata"]["items"].append(
|
447
|
+
{"key": "startup-script", "value": spec.user_data}
|
448
|
+
)
|
449
|
+
|
450
|
+
request = compute_v1.InsertInstanceRequest(
|
451
|
+
project=self.project_id,
|
452
|
+
zone=spec.zone or self.zone,
|
453
|
+
instance_resource=instance_body,
|
454
|
+
)
|
455
|
+
|
456
|
+
operation = await asyncio.to_thread(
|
457
|
+
self.instances_client.insert, request=request
|
458
|
+
)
|
459
|
+
|
460
|
+
return {
|
461
|
+
"status": "created",
|
462
|
+
"instance_name": spec.name,
|
463
|
+
"operation_id": operation.name,
|
464
|
+
"zone": spec.zone or self.zone,
|
465
|
+
"created_at": datetime.now().isoformat(),
|
466
|
+
}
|
467
|
+
|
468
|
+
except Exception as e:
|
469
|
+
return {
|
470
|
+
"status": "error",
|
471
|
+
"error": f"Failed to create GCP instance: {str(e)}",
|
472
|
+
}
|
473
|
+
|
474
|
+
async def get_instance_status(
|
475
|
+
self, instance_name: str, zone: Optional[str] = None
|
476
|
+
) -> Dict[str, Any]:
|
477
|
+
"""Get GCP Compute Engine instance status."""
|
478
|
+
try:
|
479
|
+
request = compute_v1.GetInstanceRequest(
|
480
|
+
project=self.project_id, zone=zone or self.zone, instance=instance_name
|
481
|
+
)
|
482
|
+
|
483
|
+
instance = await asyncio.to_thread(
|
484
|
+
self.instances_client.get, request=request
|
485
|
+
)
|
486
|
+
|
487
|
+
return {
|
488
|
+
"instance_name": instance.name,
|
489
|
+
"status": instance.status,
|
490
|
+
"machine_type": instance.machine_type.split("/")[-1],
|
491
|
+
"zone": instance.zone.split("/")[-1],
|
492
|
+
"creation_timestamp": instance.creation_timestamp,
|
493
|
+
"labels": dict(instance.labels) if instance.labels else {},
|
494
|
+
"network_interfaces": [
|
495
|
+
{
|
496
|
+
"network_ip": interface.network_i_p,
|
497
|
+
"access_configs": [
|
498
|
+
{"nat_ip": config.nat_i_p}
|
499
|
+
for config in interface.access_configs or []
|
500
|
+
],
|
501
|
+
}
|
502
|
+
for interface in instance.network_interfaces or []
|
503
|
+
],
|
504
|
+
}
|
505
|
+
|
506
|
+
except Exception as e:
|
507
|
+
return {
|
508
|
+
"status": "error",
|
509
|
+
"error": f"Failed to get GCP instance status: {str(e)}",
|
510
|
+
}
|
511
|
+
|
512
|
+
async def delete_instance(
|
513
|
+
self, instance_name: str, zone: Optional[str] = None
|
514
|
+
) -> Dict[str, Any]:
|
515
|
+
"""Delete GCP Compute Engine instance."""
|
516
|
+
try:
|
517
|
+
request = compute_v1.DeleteInstanceRequest(
|
518
|
+
project=self.project_id, zone=zone or self.zone, instance=instance_name
|
519
|
+
)
|
520
|
+
|
521
|
+
operation = await asyncio.to_thread(
|
522
|
+
self.instances_client.delete, request=request
|
523
|
+
)
|
524
|
+
|
525
|
+
return {
|
526
|
+
"status": "deleting",
|
527
|
+
"instance_name": instance_name,
|
528
|
+
"operation_id": operation.name,
|
529
|
+
"zone": zone or self.zone,
|
530
|
+
}
|
531
|
+
|
532
|
+
except Exception as e:
|
533
|
+
return {
|
534
|
+
"status": "error",
|
535
|
+
"error": f"Failed to delete GCP instance: {str(e)}",
|
536
|
+
}
|
537
|
+
|
538
|
+
|
539
|
+
class CloudIntegration:
|
540
|
+
"""Unified cloud integration for edge resource management."""
|
541
|
+
|
542
|
+
def __init__(self):
|
543
|
+
self.integrations: Dict[CloudProvider, Any] = {}
|
544
|
+
self.instances: Dict[str, CloudInstance] = {}
|
545
|
+
|
546
|
+
# Background tasks
|
547
|
+
self._monitoring_task: Optional[asyncio.Task] = None
|
548
|
+
|
549
|
+
# Configuration
|
550
|
+
self.monitoring_interval = 60 # seconds
|
551
|
+
|
552
|
+
def register_aws(
|
553
|
+
self, region: str = "us-west-2", profile_name: Optional[str] = None
|
554
|
+
) -> None:
|
555
|
+
"""Register AWS integration."""
|
556
|
+
try:
|
557
|
+
self.integrations[CloudProvider.AWS] = AWSIntegration(region, profile_name)
|
558
|
+
except ImportError as e:
|
559
|
+
raise RuntimeError(f"Failed to register AWS integration: {e}")
|
560
|
+
|
561
|
+
def register_gcp(
|
562
|
+
self,
|
563
|
+
project_id: str,
|
564
|
+
zone: str = "us-central1-a",
|
565
|
+
credentials_path: Optional[str] = None,
|
566
|
+
) -> None:
|
567
|
+
"""Register GCP integration."""
|
568
|
+
try:
|
569
|
+
self.integrations[CloudProvider.GCP] = GCPIntegration(
|
570
|
+
project_id, zone, credentials_path
|
571
|
+
)
|
572
|
+
except ImportError as e:
|
573
|
+
raise RuntimeError(f"Failed to register GCP integration: {e}")
|
574
|
+
|
575
|
+
def register_azure(self, subscription_id: str, resource_group: str) -> None:
|
576
|
+
"""Register Azure integration."""
|
577
|
+
if not AZURE_AVAILABLE:
|
578
|
+
raise ImportError(
|
579
|
+
"Azure SDK not available. Install with: pip install azure-mgmt-compute azure-identity"
|
580
|
+
)
|
581
|
+
# Azure integration would be implemented here
|
582
|
+
raise NotImplementedError("Azure integration not yet implemented")
|
583
|
+
|
584
|
+
async def create_instance(self, spec: InstanceSpec) -> Dict[str, Any]:
|
585
|
+
"""Create cloud instance."""
|
586
|
+
if spec.provider not in self.integrations:
|
587
|
+
return {
|
588
|
+
"status": "error",
|
589
|
+
"error": f"Provider {spec.provider.value} not registered",
|
590
|
+
}
|
591
|
+
|
592
|
+
integration = self.integrations[spec.provider]
|
593
|
+
result = await integration.create_instance(spec)
|
594
|
+
|
595
|
+
if result.get("status") == "created":
|
596
|
+
# Create instance tracking object
|
597
|
+
instance = CloudInstance(
|
598
|
+
instance_id=result.get("instance_id") or result.get("instance_name"),
|
599
|
+
name=spec.name,
|
600
|
+
provider=spec.provider,
|
601
|
+
instance_type=spec.instance_type,
|
602
|
+
image_id=spec.image_id,
|
603
|
+
region=spec.region,
|
604
|
+
zone=spec.zone,
|
605
|
+
tags=spec.tags,
|
606
|
+
edge_node=spec.edge_node,
|
607
|
+
)
|
608
|
+
|
609
|
+
self.instances[instance.instance_id] = instance
|
610
|
+
|
611
|
+
return result
|
612
|
+
|
613
|
+
async def get_instance_status(
|
614
|
+
self, provider: CloudProvider, instance_id: str, zone: Optional[str] = None
|
615
|
+
) -> Dict[str, Any]:
|
616
|
+
"""Get cloud instance status."""
|
617
|
+
if provider not in self.integrations:
|
618
|
+
return {
|
619
|
+
"status": "error",
|
620
|
+
"error": f"Provider {provider.value} not registered",
|
621
|
+
}
|
622
|
+
|
623
|
+
integration = self.integrations[provider]
|
624
|
+
|
625
|
+
if provider == CloudProvider.AWS:
|
626
|
+
return await integration.get_instance_status(instance_id)
|
627
|
+
elif provider == CloudProvider.GCP:
|
628
|
+
return await integration.get_instance_status(instance_id, zone)
|
629
|
+
else:
|
630
|
+
return {
|
631
|
+
"status": "error",
|
632
|
+
"error": f"Status operation not implemented for {provider.value}",
|
633
|
+
}
|
634
|
+
|
635
|
+
async def terminate_instance(
|
636
|
+
self, provider: CloudProvider, instance_id: str, zone: Optional[str] = None
|
637
|
+
) -> Dict[str, Any]:
|
638
|
+
"""Terminate cloud instance."""
|
639
|
+
if provider not in self.integrations:
|
640
|
+
return {
|
641
|
+
"status": "error",
|
642
|
+
"error": f"Provider {provider.value} not registered",
|
643
|
+
}
|
644
|
+
|
645
|
+
integration = self.integrations[provider]
|
646
|
+
|
647
|
+
if provider == CloudProvider.AWS:
|
648
|
+
result = await integration.terminate_instance(instance_id)
|
649
|
+
elif provider == CloudProvider.GCP:
|
650
|
+
result = await integration.delete_instance(instance_id, zone)
|
651
|
+
else:
|
652
|
+
return {
|
653
|
+
"status": "error",
|
654
|
+
"error": f"Terminate operation not implemented for {provider.value}",
|
655
|
+
}
|
656
|
+
|
657
|
+
# Remove from tracking
|
658
|
+
if result.get("status") in ["terminating", "deleting"]:
|
659
|
+
self.instances.pop(instance_id, None)
|
660
|
+
|
661
|
+
return result
|
662
|
+
|
663
|
+
async def list_instances(
|
664
|
+
self, provider: CloudProvider, filters: Optional[Dict[str, Any]] = None
|
665
|
+
) -> List[Dict[str, Any]]:
|
666
|
+
"""List cloud instances."""
|
667
|
+
if provider not in self.integrations:
|
668
|
+
raise RuntimeError(f"Provider {provider.value} not registered")
|
669
|
+
|
670
|
+
integration = self.integrations[provider]
|
671
|
+
|
672
|
+
if provider == CloudProvider.AWS:
|
673
|
+
return await integration.list_instances(filters)
|
674
|
+
else:
|
675
|
+
raise NotImplementedError(
|
676
|
+
f"List operation not implemented for {provider.value}"
|
677
|
+
)
|
678
|
+
|
679
|
+
async def get_instance_metrics(
|
680
|
+
self,
|
681
|
+
provider: CloudProvider,
|
682
|
+
instance_id: str,
|
683
|
+
start_time: datetime,
|
684
|
+
end_time: datetime,
|
685
|
+
) -> List[CloudMetrics]:
|
686
|
+
"""Get cloud instance metrics."""
|
687
|
+
if provider not in self.integrations:
|
688
|
+
raise RuntimeError(f"Provider {provider.value} not registered")
|
689
|
+
|
690
|
+
integration = self.integrations[provider]
|
691
|
+
|
692
|
+
if provider == CloudProvider.AWS:
|
693
|
+
return await integration.get_instance_metrics(
|
694
|
+
instance_id, start_time, end_time
|
695
|
+
)
|
696
|
+
else:
|
697
|
+
raise NotImplementedError(
|
698
|
+
f"Metrics operation not implemented for {provider.value}"
|
699
|
+
)
|
700
|
+
|
701
|
+
async def get_supported_providers(self) -> List[str]:
|
702
|
+
"""Get list of registered cloud providers."""
|
703
|
+
return [provider.value for provider in self.integrations.keys()]
|
704
|
+
|
705
|
+
async def get_provider_info(self, provider: CloudProvider) -> Dict[str, Any]:
|
706
|
+
"""Get cloud provider information."""
|
707
|
+
if provider not in self.integrations:
|
708
|
+
return {
|
709
|
+
"status": "error",
|
710
|
+
"error": f"Provider {provider.value} not registered",
|
711
|
+
}
|
712
|
+
|
713
|
+
provider_info = {"provider": provider.value, "registered": True, "features": []}
|
714
|
+
|
715
|
+
if provider == CloudProvider.AWS:
|
716
|
+
provider_info["features"] = [
|
717
|
+
"create_instance",
|
718
|
+
"get_instance_status",
|
719
|
+
"terminate_instance",
|
720
|
+
"list_instances",
|
721
|
+
"get_instance_metrics",
|
722
|
+
]
|
723
|
+
provider_info["region"] = self.integrations[provider].region
|
724
|
+
elif provider == CloudProvider.GCP:
|
725
|
+
provider_info["features"] = [
|
726
|
+
"create_instance",
|
727
|
+
"get_instance_status",
|
728
|
+
"delete_instance",
|
729
|
+
]
|
730
|
+
provider_info["project_id"] = self.integrations[provider].project_id
|
731
|
+
provider_info["zone"] = self.integrations[provider].zone
|
732
|
+
|
733
|
+
return provider_info
|
734
|
+
|
735
|
+
async def start_monitoring(self) -> None:
|
736
|
+
"""Start cloud instance monitoring."""
|
737
|
+
if self._monitoring_task and not self._monitoring_task.done():
|
738
|
+
return
|
739
|
+
|
740
|
+
self._monitoring_task = asyncio.create_task(self._monitor_instances())
|
741
|
+
|
742
|
+
async def stop_monitoring(self) -> None:
|
743
|
+
"""Stop cloud instance monitoring."""
|
744
|
+
if self._monitoring_task and not self._monitoring_task.done():
|
745
|
+
self._monitoring_task.cancel()
|
746
|
+
try:
|
747
|
+
await self._monitoring_task
|
748
|
+
except asyncio.CancelledError:
|
749
|
+
pass
|
750
|
+
|
751
|
+
async def _monitor_instances(self) -> None:
|
752
|
+
"""Monitor cloud instances continuously."""
|
753
|
+
while True:
|
754
|
+
try:
|
755
|
+
# Update status for all tracked instances
|
756
|
+
for instance_id, instance in list(self.instances.items()):
|
757
|
+
try:
|
758
|
+
status = await self.get_instance_status(
|
759
|
+
instance.provider, instance_id, instance.zone
|
760
|
+
)
|
761
|
+
|
762
|
+
# Update instance state based on status
|
763
|
+
if status.get("state"):
|
764
|
+
if status["state"] in ["running", "RUNNING"]:
|
765
|
+
instance.state = InstanceState.RUNNING
|
766
|
+
elif status["state"] in ["stopped", "TERMINATED"]:
|
767
|
+
instance.state = InstanceState.STOPPED
|
768
|
+
elif status["state"] in ["stopping", "STOPPING"]:
|
769
|
+
instance.state = InstanceState.STOPPING
|
770
|
+
elif status["state"] in ["pending", "PROVISIONING"]:
|
771
|
+
instance.state = InstanceState.PENDING
|
772
|
+
elif status["state"] in ["terminated", "TERMINATED"]:
|
773
|
+
instance.state = InstanceState.TERMINATED
|
774
|
+
# Remove terminated instances
|
775
|
+
del self.instances[instance_id]
|
776
|
+
else:
|
777
|
+
instance.state = InstanceState.UNKNOWN
|
778
|
+
|
779
|
+
# Update IP addresses
|
780
|
+
if status.get("public_ip"):
|
781
|
+
instance.public_ip = status["public_ip"]
|
782
|
+
if status.get("private_ip"):
|
783
|
+
instance.private_ip = status["private_ip"]
|
784
|
+
|
785
|
+
except Exception:
|
786
|
+
# Instance might have been deleted
|
787
|
+
pass
|
788
|
+
|
789
|
+
await asyncio.sleep(self.monitoring_interval)
|
790
|
+
|
791
|
+
except asyncio.CancelledError:
|
792
|
+
break
|
793
|
+
except Exception as e:
|
794
|
+
# Log error and continue monitoring
|
795
|
+
print(f"Cloud monitoring error: {e}")
|
796
|
+
await asyncio.sleep(self.monitoring_interval)
|