isa-model 0.4.0__py3-none-any.whl → 0.4.3__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.
- isa_model/client.py +466 -43
- isa_model/core/cache/redis_cache.py +12 -3
- isa_model/core/config/config_manager.py +230 -3
- isa_model/core/config.py +90 -0
- isa_model/core/database/direct_db_client.py +114 -0
- isa_model/core/database/migration_manager.py +563 -0
- isa_model/core/database/migrations.py +21 -1
- isa_model/core/database/supabase_client.py +154 -19
- isa_model/core/dependencies.py +316 -0
- isa_model/core/discovery/__init__.py +19 -0
- isa_model/core/discovery/consul_discovery.py +190 -0
- isa_model/core/logging/__init__.py +54 -0
- isa_model/core/logging/influx_logger.py +523 -0
- isa_model/core/logging/loki_logger.py +160 -0
- isa_model/core/models/__init__.py +27 -18
- isa_model/core/models/config_models.py +625 -0
- isa_model/core/models/deployment_billing_tracker.py +430 -0
- isa_model/core/models/model_manager.py +40 -17
- isa_model/core/models/model_metadata.py +690 -0
- isa_model/core/models/model_repo.py +174 -18
- isa_model/core/models/system_models.py +857 -0
- isa_model/core/repositories/__init__.py +9 -0
- isa_model/core/repositories/config_repository.py +912 -0
- isa_model/core/services/intelligent_model_selector.py +399 -21
- isa_model/core/storage/hf_storage.py +1 -1
- isa_model/core/types.py +1 -0
- isa_model/deployment/__init__.py +5 -48
- isa_model/deployment/core/__init__.py +2 -31
- isa_model/deployment/core/deployment_manager.py +1278 -370
- isa_model/deployment/local/__init__.py +31 -0
- isa_model/deployment/local/config.py +248 -0
- isa_model/deployment/local/gpu_gateway.py +607 -0
- isa_model/deployment/local/health_checker.py +428 -0
- isa_model/deployment/local/provider.py +586 -0
- isa_model/deployment/local/tensorrt_service.py +621 -0
- isa_model/deployment/local/transformers_service.py +644 -0
- isa_model/deployment/local/vllm_service.py +527 -0
- isa_model/deployment/modal/__init__.py +8 -0
- isa_model/deployment/modal/config.py +136 -0
- isa_model/deployment/{services/auto_hf_modal_deployer.py → modal/deployer.py} +1 -1
- isa_model/deployment/modal/services/__init__.py +3 -0
- isa_model/deployment/modal/services/audio/__init__.py +1 -0
- isa_model/deployment/modal/services/embedding/__init__.py +1 -0
- isa_model/deployment/modal/services/llm/__init__.py +1 -0
- isa_model/deployment/modal/services/llm/isa_llm_service.py +424 -0
- isa_model/deployment/modal/services/video/__init__.py +1 -0
- isa_model/deployment/modal/services/vision/__init__.py +1 -0
- isa_model/deployment/models/org-org-acme-corp-tenant-a-service-llm-20250825-225822/tenant-a-service_modal_service.py +48 -0
- isa_model/deployment/models/org-test-org-123-prefix-test-service-llm-20250825-225822/prefix-test-service_modal_service.py +48 -0
- isa_model/deployment/models/test-llm-service-llm-20250825-204442/test-llm-service_modal_service.py +48 -0
- isa_model/deployment/models/test-monitoring-gpt2-llm-20250825-212906/test-monitoring-gpt2_modal_service.py +48 -0
- isa_model/deployment/models/test-monitoring-gpt2-llm-20250825-213009/test-monitoring-gpt2_modal_service.py +48 -0
- isa_model/deployment/storage/__init__.py +5 -0
- isa_model/deployment/storage/deployment_repository.py +824 -0
- isa_model/deployment/triton/__init__.py +10 -0
- isa_model/deployment/triton/config.py +196 -0
- isa_model/deployment/triton/configs/__init__.py +1 -0
- isa_model/deployment/triton/provider.py +512 -0
- isa_model/deployment/triton/scripts/__init__.py +1 -0
- isa_model/deployment/triton/templates/__init__.py +1 -0
- isa_model/inference/__init__.py +47 -1
- isa_model/inference/ai_factory.py +137 -10
- isa_model/inference/legacy_services/__init__.py +21 -0
- isa_model/inference/legacy_services/model_evaluation.py +637 -0
- isa_model/inference/legacy_services/model_service.py +573 -0
- isa_model/inference/legacy_services/model_serving.py +717 -0
- isa_model/inference/legacy_services/model_training.py +561 -0
- isa_model/inference/models/__init__.py +21 -0
- isa_model/inference/models/inference_config.py +551 -0
- isa_model/inference/models/inference_record.py +675 -0
- isa_model/inference/models/performance_models.py +714 -0
- isa_model/inference/repositories/__init__.py +9 -0
- isa_model/inference/repositories/inference_repository.py +828 -0
- isa_model/inference/services/audio/base_stt_service.py +184 -11
- isa_model/inference/services/audio/openai_stt_service.py +22 -6
- isa_model/inference/services/custom_model_manager.py +277 -0
- isa_model/inference/services/embedding/ollama_embed_service.py +15 -3
- isa_model/inference/services/embedding/resilient_embed_service.py +285 -0
- isa_model/inference/services/llm/__init__.py +10 -2
- isa_model/inference/services/llm/base_llm_service.py +335 -24
- isa_model/inference/services/llm/cerebras_llm_service.py +628 -0
- isa_model/inference/services/llm/helpers/llm_adapter.py +9 -4
- isa_model/inference/services/llm/helpers/llm_prompts.py +342 -0
- isa_model/inference/services/llm/helpers/llm_utils.py +321 -23
- isa_model/inference/services/llm/huggingface_llm_service.py +581 -0
- isa_model/inference/services/llm/local_llm_service.py +747 -0
- isa_model/inference/services/llm/ollama_llm_service.py +9 -2
- isa_model/inference/services/llm/openai_llm_service.py +33 -16
- isa_model/inference/services/llm/yyds_llm_service.py +8 -2
- isa_model/inference/services/vision/__init__.py +22 -1
- isa_model/inference/services/vision/blip_vision_service.py +359 -0
- isa_model/inference/services/vision/helpers/image_utils.py +8 -5
- isa_model/inference/services/vision/isa_vision_service.py +65 -4
- isa_model/inference/services/vision/openai_vision_service.py +19 -10
- isa_model/inference/services/vision/vgg16_vision_service.py +257 -0
- isa_model/serving/api/cache_manager.py +245 -0
- isa_model/serving/api/dependencies/__init__.py +1 -0
- isa_model/serving/api/dependencies/auth.py +194 -0
- isa_model/serving/api/dependencies/database.py +139 -0
- isa_model/serving/api/error_handlers.py +284 -0
- isa_model/serving/api/fastapi_server.py +172 -22
- isa_model/serving/api/middleware/auth.py +8 -2
- isa_model/serving/api/middleware/security.py +23 -33
- isa_model/serving/api/middleware/tenant_context.py +414 -0
- isa_model/serving/api/routes/analytics.py +4 -1
- isa_model/serving/api/routes/config.py +645 -0
- isa_model/serving/api/routes/deployment_billing.py +315 -0
- isa_model/serving/api/routes/deployments.py +138 -2
- isa_model/serving/api/routes/gpu_gateway.py +440 -0
- isa_model/serving/api/routes/health.py +32 -12
- isa_model/serving/api/routes/inference_monitoring.py +486 -0
- isa_model/serving/api/routes/local_deployments.py +448 -0
- isa_model/serving/api/routes/tenants.py +575 -0
- isa_model/serving/api/routes/unified.py +680 -18
- isa_model/serving/api/routes/webhooks.py +479 -0
- isa_model/serving/api/startup.py +68 -54
- isa_model/utils/gpu_utils.py +311 -0
- {isa_model-0.4.0.dist-info → isa_model-0.4.3.dist-info}/METADATA +66 -24
- isa_model-0.4.3.dist-info/RECORD +193 -0
- isa_model/core/storage/minio_storage.py +0 -0
- isa_model/deployment/cloud/__init__.py +0 -9
- isa_model/deployment/cloud/modal/__init__.py +0 -10
- isa_model/deployment/core/deployment_config.py +0 -356
- isa_model/deployment/core/isa_deployment_service.py +0 -401
- isa_model/deployment/gpu_int8_ds8/app/server.py +0 -66
- isa_model/deployment/gpu_int8_ds8/scripts/test_client.py +0 -43
- isa_model/deployment/gpu_int8_ds8/scripts/test_client_os.py +0 -35
- isa_model/deployment/runtime/deployed_service.py +0 -338
- isa_model/deployment/services/__init__.py +0 -9
- isa_model/deployment/services/auto_deploy_vision_service.py +0 -538
- isa_model/deployment/services/model_service.py +0 -332
- isa_model/deployment/services/service_monitor.py +0 -356
- isa_model/deployment/services/service_registry.py +0 -527
- isa_model/eval/__init__.py +0 -92
- isa_model/eval/benchmarks/__init__.py +0 -27
- isa_model/eval/benchmarks/multimodal_datasets.py +0 -460
- isa_model/eval/benchmarks.py +0 -701
- isa_model/eval/config/__init__.py +0 -10
- isa_model/eval/config/evaluation_config.py +0 -108
- isa_model/eval/evaluators/__init__.py +0 -24
- isa_model/eval/evaluators/audio_evaluator.py +0 -727
- isa_model/eval/evaluators/base_evaluator.py +0 -503
- isa_model/eval/evaluators/embedding_evaluator.py +0 -742
- isa_model/eval/evaluators/llm_evaluator.py +0 -472
- isa_model/eval/evaluators/vision_evaluator.py +0 -564
- isa_model/eval/example_evaluation.py +0 -395
- isa_model/eval/factory.py +0 -798
- isa_model/eval/infrastructure/__init__.py +0 -24
- isa_model/eval/infrastructure/experiment_tracker.py +0 -466
- isa_model/eval/isa_benchmarks.py +0 -700
- isa_model/eval/isa_integration.py +0 -582
- isa_model/eval/metrics.py +0 -951
- isa_model/eval/tests/unit/test_basic.py +0 -396
- isa_model/serving/api/routes/evaluations.py +0 -579
- isa_model/training/__init__.py +0 -168
- isa_model/training/annotation/annotation_schema.py +0 -47
- isa_model/training/annotation/processors/annotation_processor.py +0 -126
- isa_model/training/annotation/storage/dataset_manager.py +0 -131
- isa_model/training/annotation/storage/dataset_schema.py +0 -44
- isa_model/training/annotation/tests/test_annotation_flow.py +0 -109
- isa_model/training/annotation/tests/test_minio copy.py +0 -113
- isa_model/training/annotation/tests/test_minio_upload.py +0 -43
- isa_model/training/annotation/views/annotation_controller.py +0 -158
- isa_model/training/cloud/__init__.py +0 -22
- isa_model/training/cloud/job_orchestrator.py +0 -402
- isa_model/training/cloud/runpod_trainer.py +0 -454
- isa_model/training/cloud/storage_manager.py +0 -482
- isa_model/training/core/__init__.py +0 -26
- isa_model/training/core/config.py +0 -181
- isa_model/training/core/dataset.py +0 -222
- isa_model/training/core/trainer.py +0 -720
- isa_model/training/core/utils.py +0 -213
- isa_model/training/examples/intelligent_training_example.py +0 -281
- isa_model/training/factory.py +0 -424
- isa_model/training/intelligent/__init__.py +0 -25
- isa_model/training/intelligent/decision_engine.py +0 -643
- isa_model/training/intelligent/intelligent_factory.py +0 -888
- isa_model/training/intelligent/knowledge_base.py +0 -751
- isa_model/training/intelligent/resource_optimizer.py +0 -839
- isa_model/training/intelligent/task_classifier.py +0 -576
- isa_model/training/storage/__init__.py +0 -24
- isa_model/training/storage/core_integration.py +0 -439
- isa_model/training/storage/training_repository.py +0 -552
- isa_model/training/storage/training_storage.py +0 -628
- isa_model-0.4.0.dist-info/RECORD +0 -182
- /isa_model/deployment/{cloud/modal → modal/services/audio}/isa_audio_chatTTS_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/audio}/isa_audio_fish_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/audio}/isa_audio_openvoice_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/audio}/isa_audio_service_v2.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/embedding}/isa_embed_rerank_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/video}/isa_video_hunyuan_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/vision}/isa_vision_ocr_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/vision}/isa_vision_qwen25_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/vision}/isa_vision_table_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/vision}/isa_vision_ui_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/vision}/isa_vision_ui_service_optimized.py +0 -0
- /isa_model/deployment/{services → modal/services/vision}/simple_auto_deploy_vision_service.py +0 -0
- {isa_model-0.4.0.dist-info → isa_model-0.4.3.dist-info}/WHEEL +0 -0
- {isa_model-0.4.0.dist-info → isa_model-0.4.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,824 @@
|
|
1
|
+
"""
|
2
|
+
Deployment Repository - Data persistence layer for deployment operations
|
3
|
+
|
4
|
+
Provides standardized data access for deployment records, configurations, and metrics
|
5
|
+
following the ISA Model architecture pattern.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import logging
|
9
|
+
import json
|
10
|
+
import uuid
|
11
|
+
from datetime import datetime, timezone
|
12
|
+
from typing import Dict, List, Optional, Any, Union
|
13
|
+
from pathlib import Path
|
14
|
+
from dataclasses import dataclass, asdict
|
15
|
+
from enum import Enum
|
16
|
+
|
17
|
+
try:
|
18
|
+
# Try to import Supabase for centralized data storage
|
19
|
+
from ...core.database.supabase_client import get_supabase_client, SupabaseClient
|
20
|
+
SUPABASE_AVAILABLE = True
|
21
|
+
except ImportError:
|
22
|
+
SUPABASE_AVAILABLE = False
|
23
|
+
SupabaseClient = None
|
24
|
+
|
25
|
+
logger = logging.getLogger(__name__)
|
26
|
+
|
27
|
+
class DeploymentStatus(str, Enum):
|
28
|
+
"""Deployment status enumeration"""
|
29
|
+
PENDING = "pending"
|
30
|
+
DEPLOYING = "deploying"
|
31
|
+
RUNNING = "running"
|
32
|
+
FAILED = "failed"
|
33
|
+
STOPPED = "stopped"
|
34
|
+
UPDATING = "updating"
|
35
|
+
SCALING = "scaling"
|
36
|
+
|
37
|
+
class DeploymentPlatform(str, Enum):
|
38
|
+
"""Deployment platform enumeration"""
|
39
|
+
HUGGINGFACE = "huggingface"
|
40
|
+
MODAL = "modal"
|
41
|
+
AWS = "aws"
|
42
|
+
GCP = "gcp"
|
43
|
+
AZURE = "azure"
|
44
|
+
LOCAL = "local"
|
45
|
+
DOCKER = "docker"
|
46
|
+
KUBERNETES = "kubernetes"
|
47
|
+
|
48
|
+
@dataclass
|
49
|
+
class DeploymentRecord:
|
50
|
+
"""Deployment record model"""
|
51
|
+
deployment_id: str
|
52
|
+
model_id: str
|
53
|
+
deployment_name: str
|
54
|
+
platform: str
|
55
|
+
status: str = DeploymentStatus.PENDING
|
56
|
+
endpoint_url: Optional[str] = None
|
57
|
+
config: Optional[Dict[str, Any]] = None
|
58
|
+
environment: str = "production"
|
59
|
+
created_at: datetime = None
|
60
|
+
updated_at: datetime = None
|
61
|
+
deployed_at: Optional[datetime] = None
|
62
|
+
stopped_at: Optional[datetime] = None
|
63
|
+
user_id: Optional[str] = None
|
64
|
+
project_name: Optional[str] = None
|
65
|
+
tags: Optional[Dict[str, str]] = None
|
66
|
+
error_message: Optional[str] = None
|
67
|
+
|
68
|
+
def __post_init__(self):
|
69
|
+
if self.created_at is None:
|
70
|
+
self.created_at = datetime.now(timezone.utc)
|
71
|
+
if self.updated_at is None:
|
72
|
+
self.updated_at = self.created_at
|
73
|
+
|
74
|
+
@dataclass
|
75
|
+
class DeploymentMetrics:
|
76
|
+
"""Deployment metrics record"""
|
77
|
+
metric_id: str
|
78
|
+
deployment_id: str
|
79
|
+
timestamp: datetime
|
80
|
+
requests_per_minute: Optional[int] = None
|
81
|
+
avg_response_time_ms: Optional[float] = None
|
82
|
+
error_rate: Optional[float] = None
|
83
|
+
cpu_usage: Optional[float] = None
|
84
|
+
memory_usage: Optional[float] = None
|
85
|
+
disk_usage: Optional[float] = None
|
86
|
+
cost_per_hour: Optional[float] = None
|
87
|
+
uptime_percentage: Optional[float] = None
|
88
|
+
custom_metrics: Optional[Dict[str, Any]] = None
|
89
|
+
|
90
|
+
def __post_init__(self):
|
91
|
+
if self.timestamp is None:
|
92
|
+
self.timestamp = datetime.now(timezone.utc)
|
93
|
+
|
94
|
+
@dataclass
|
95
|
+
class ServiceRegistryEntry:
|
96
|
+
"""Service registry entry for tracking deployed services"""
|
97
|
+
service_id: str
|
98
|
+
deployment_id: str
|
99
|
+
service_name: str
|
100
|
+
service_type: str
|
101
|
+
endpoint_url: str
|
102
|
+
health_check_url: Optional[str] = None
|
103
|
+
is_active: bool = True
|
104
|
+
last_health_check: Optional[datetime] = None
|
105
|
+
health_status: str = "unknown" # healthy, unhealthy, unknown
|
106
|
+
metadata: Optional[Dict[str, Any]] = None
|
107
|
+
created_at: datetime = None
|
108
|
+
|
109
|
+
def __post_init__(self):
|
110
|
+
if self.created_at is None:
|
111
|
+
self.created_at = datetime.now(timezone.utc)
|
112
|
+
|
113
|
+
class DeploymentRepository:
|
114
|
+
"""
|
115
|
+
Repository for deployment data persistence
|
116
|
+
|
117
|
+
Supports multiple backend storage options:
|
118
|
+
1. Supabase (preferred for centralized storage)
|
119
|
+
2. Local file system (fallback for development)
|
120
|
+
3. In-memory storage (for testing)
|
121
|
+
"""
|
122
|
+
|
123
|
+
def __init__(self, storage_backend: str = "auto", **kwargs):
|
124
|
+
"""
|
125
|
+
Initialize deployment repository
|
126
|
+
|
127
|
+
Args:
|
128
|
+
storage_backend: "supabase", "file", "memory", or "auto"
|
129
|
+
**kwargs: Backend-specific configuration
|
130
|
+
"""
|
131
|
+
self.storage_backend = self._determine_backend(storage_backend)
|
132
|
+
self.config = kwargs
|
133
|
+
|
134
|
+
# Initialize storage backend
|
135
|
+
if self.storage_backend == "supabase":
|
136
|
+
self._init_supabase()
|
137
|
+
elif self.storage_backend == "memory":
|
138
|
+
self._init_memory()
|
139
|
+
else: # file system fallback
|
140
|
+
self._init_file_system()
|
141
|
+
|
142
|
+
logger.info(f"Deployment repository initialized with {self.storage_backend} backend")
|
143
|
+
|
144
|
+
def _determine_backend(self, preference: str) -> str:
|
145
|
+
"""Determine the best available storage backend"""
|
146
|
+
if preference == "supabase" and SUPABASE_AVAILABLE:
|
147
|
+
return "supabase"
|
148
|
+
elif preference in ["supabase", "file", "memory"]:
|
149
|
+
return preference
|
150
|
+
|
151
|
+
# Auto-select best available backend
|
152
|
+
if SUPABASE_AVAILABLE:
|
153
|
+
return "supabase"
|
154
|
+
else:
|
155
|
+
return "file"
|
156
|
+
|
157
|
+
def _init_supabase(self):
|
158
|
+
"""Initialize Supabase backend"""
|
159
|
+
try:
|
160
|
+
self.supabase_client = get_supabase_client()
|
161
|
+
self._ensure_supabase_tables()
|
162
|
+
logger.info("Supabase backend initialized for deployments")
|
163
|
+
except Exception as e:
|
164
|
+
logger.error(f"Failed to initialize Supabase backend: {e}")
|
165
|
+
self.storage_backend = "file"
|
166
|
+
self._init_file_system()
|
167
|
+
|
168
|
+
def _init_file_system(self):
|
169
|
+
"""Initialize file system backend"""
|
170
|
+
self.data_dir = Path(self.config.get("data_dir", "./deployment_data"))
|
171
|
+
self.data_dir.mkdir(parents=True, exist_ok=True)
|
172
|
+
|
173
|
+
# Create subdirectories
|
174
|
+
(self.data_dir / "deployments").mkdir(exist_ok=True)
|
175
|
+
(self.data_dir / "metrics").mkdir(exist_ok=True)
|
176
|
+
(self.data_dir / "services").mkdir(exist_ok=True)
|
177
|
+
|
178
|
+
logger.info(f"File system backend initialized: {self.data_dir}")
|
179
|
+
|
180
|
+
def _init_memory(self):
|
181
|
+
"""Initialize in-memory backend for testing"""
|
182
|
+
self.deployments = {}
|
183
|
+
self.metrics = {}
|
184
|
+
self.services = {}
|
185
|
+
logger.info("In-memory backend initialized for deployments")
|
186
|
+
|
187
|
+
def _ensure_supabase_tables(self):
|
188
|
+
"""Ensure required Supabase tables exist"""
|
189
|
+
try:
|
190
|
+
self.supabase_client.table("deployment_records").select("deployment_id").limit(1).execute()
|
191
|
+
self.supabase_client.table("deployment_metrics").select("metric_id").limit(1).execute()
|
192
|
+
self.supabase_client.table("service_registry").select("service_id").limit(1).execute()
|
193
|
+
except Exception as e:
|
194
|
+
logger.warning(f"Some deployment tables may not exist in Supabase: {e}")
|
195
|
+
|
196
|
+
# Deployment Management Methods
|
197
|
+
|
198
|
+
def create_deployment_record(
|
199
|
+
self,
|
200
|
+
model_id: str,
|
201
|
+
deployment_name: str,
|
202
|
+
platform: str,
|
203
|
+
config: Optional[Dict[str, Any]] = None,
|
204
|
+
environment: str = "production",
|
205
|
+
user_id: Optional[str] = None,
|
206
|
+
project_name: Optional[str] = None,
|
207
|
+
tags: Optional[Dict[str, str]] = None
|
208
|
+
) -> str:
|
209
|
+
"""Create a new deployment record"""
|
210
|
+
deployment_id = f"deploy_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{uuid.uuid4().hex[:8]}"
|
211
|
+
|
212
|
+
deployment = DeploymentRecord(
|
213
|
+
deployment_id=deployment_id,
|
214
|
+
model_id=model_id,
|
215
|
+
deployment_name=deployment_name,
|
216
|
+
platform=platform,
|
217
|
+
config=config or {},
|
218
|
+
environment=environment,
|
219
|
+
user_id=user_id,
|
220
|
+
project_name=project_name,
|
221
|
+
tags=tags
|
222
|
+
)
|
223
|
+
|
224
|
+
if self.storage_backend == "supabase":
|
225
|
+
return self._create_deployment_supabase(deployment)
|
226
|
+
elif self.storage_backend == "memory":
|
227
|
+
return self._create_deployment_memory(deployment)
|
228
|
+
else:
|
229
|
+
return self._create_deployment_file(deployment)
|
230
|
+
|
231
|
+
def update_deployment_status(
|
232
|
+
self,
|
233
|
+
deployment_id: str,
|
234
|
+
status: str,
|
235
|
+
endpoint_url: Optional[str] = None,
|
236
|
+
error_message: Optional[str] = None,
|
237
|
+
additional_updates: Optional[Dict[str, Any]] = None
|
238
|
+
) -> bool:
|
239
|
+
"""Update deployment status and information"""
|
240
|
+
updates = {
|
241
|
+
"status": status,
|
242
|
+
"updated_at": datetime.now(timezone.utc).isoformat()
|
243
|
+
}
|
244
|
+
|
245
|
+
if status == DeploymentStatus.RUNNING and endpoint_url:
|
246
|
+
updates["endpoint_url"] = endpoint_url
|
247
|
+
updates["deployed_at"] = datetime.now(timezone.utc).isoformat()
|
248
|
+
elif status == DeploymentStatus.STOPPED:
|
249
|
+
updates["stopped_at"] = datetime.now(timezone.utc).isoformat()
|
250
|
+
|
251
|
+
if error_message:
|
252
|
+
updates["error_message"] = error_message
|
253
|
+
|
254
|
+
if additional_updates:
|
255
|
+
updates.update(additional_updates)
|
256
|
+
|
257
|
+
if self.storage_backend == "supabase":
|
258
|
+
return self._update_deployment_supabase(deployment_id, updates)
|
259
|
+
elif self.storage_backend == "memory":
|
260
|
+
return self._update_deployment_memory(deployment_id, updates)
|
261
|
+
else:
|
262
|
+
return self._update_deployment_file(deployment_id, updates)
|
263
|
+
|
264
|
+
def get_deployment_info(self, deployment_id: str) -> Optional[DeploymentRecord]:
|
265
|
+
"""Get deployment information by ID"""
|
266
|
+
if self.storage_backend == "supabase":
|
267
|
+
return self._get_deployment_supabase(deployment_id)
|
268
|
+
elif self.storage_backend == "memory":
|
269
|
+
return self._get_deployment_memory(deployment_id)
|
270
|
+
else:
|
271
|
+
return self._get_deployment_file(deployment_id)
|
272
|
+
|
273
|
+
def list_active_deployments(
|
274
|
+
self,
|
275
|
+
platform: Optional[str] = None,
|
276
|
+
environment: Optional[str] = None,
|
277
|
+
user_id: Optional[str] = None,
|
278
|
+
project_name: Optional[str] = None,
|
279
|
+
limit: int = 100
|
280
|
+
) -> List[DeploymentRecord]:
|
281
|
+
"""List active deployments with optional filtering"""
|
282
|
+
if self.storage_backend == "supabase":
|
283
|
+
return self._list_deployments_supabase(platform, environment, user_id, project_name, limit)
|
284
|
+
elif self.storage_backend == "memory":
|
285
|
+
return self._list_deployments_memory(platform, environment, user_id, project_name, limit)
|
286
|
+
else:
|
287
|
+
return self._list_deployments_file(platform, environment, user_id, project_name, limit)
|
288
|
+
|
289
|
+
def delete_deployment_record(self, deployment_id: str) -> bool:
|
290
|
+
"""Delete deployment record and associated data"""
|
291
|
+
if self.storage_backend == "supabase":
|
292
|
+
return self._delete_deployment_supabase(deployment_id)
|
293
|
+
elif self.storage_backend == "memory":
|
294
|
+
return self._delete_deployment_memory(deployment_id)
|
295
|
+
else:
|
296
|
+
return self._delete_deployment_file(deployment_id)
|
297
|
+
|
298
|
+
# Metrics Management Methods
|
299
|
+
|
300
|
+
def record_deployment_metrics(
|
301
|
+
self,
|
302
|
+
deployment_id: str,
|
303
|
+
requests_per_minute: Optional[int] = None,
|
304
|
+
avg_response_time_ms: Optional[float] = None,
|
305
|
+
error_rate: Optional[float] = None,
|
306
|
+
cpu_usage: Optional[float] = None,
|
307
|
+
memory_usage: Optional[float] = None,
|
308
|
+
disk_usage: Optional[float] = None,
|
309
|
+
cost_per_hour: Optional[float] = None,
|
310
|
+
uptime_percentage: Optional[float] = None,
|
311
|
+
custom_metrics: Optional[Dict[str, Any]] = None
|
312
|
+
) -> str:
|
313
|
+
"""Record deployment metrics"""
|
314
|
+
metric_id = f"metric_{deployment_id}_{uuid.uuid4().hex[:8]}"
|
315
|
+
|
316
|
+
metrics = DeploymentMetrics(
|
317
|
+
metric_id=metric_id,
|
318
|
+
deployment_id=deployment_id,
|
319
|
+
timestamp=datetime.now(timezone.utc),
|
320
|
+
requests_per_minute=requests_per_minute,
|
321
|
+
avg_response_time_ms=avg_response_time_ms,
|
322
|
+
error_rate=error_rate,
|
323
|
+
cpu_usage=cpu_usage,
|
324
|
+
memory_usage=memory_usage,
|
325
|
+
disk_usage=disk_usage,
|
326
|
+
cost_per_hour=cost_per_hour,
|
327
|
+
uptime_percentage=uptime_percentage,
|
328
|
+
custom_metrics=custom_metrics
|
329
|
+
)
|
330
|
+
|
331
|
+
if self.storage_backend == "supabase":
|
332
|
+
return self._record_metrics_supabase(metrics)
|
333
|
+
elif self.storage_backend == "memory":
|
334
|
+
return self._record_metrics_memory(metrics)
|
335
|
+
else:
|
336
|
+
return self._record_metrics_file(metrics)
|
337
|
+
|
338
|
+
def get_deployment_metrics(
|
339
|
+
self,
|
340
|
+
deployment_id: str,
|
341
|
+
hours: int = 24,
|
342
|
+
limit: int = 1000
|
343
|
+
) -> List[DeploymentMetrics]:
|
344
|
+
"""Get deployment metrics for specified time period"""
|
345
|
+
if self.storage_backend == "supabase":
|
346
|
+
return self._get_metrics_supabase(deployment_id, hours, limit)
|
347
|
+
elif self.storage_backend == "memory":
|
348
|
+
return self._get_metrics_memory(deployment_id, hours, limit)
|
349
|
+
else:
|
350
|
+
return self._get_metrics_file(deployment_id, hours, limit)
|
351
|
+
|
352
|
+
def get_deployment_statistics(self, deployment_id: str) -> Dict[str, Any]:
|
353
|
+
"""Get aggregated deployment statistics"""
|
354
|
+
metrics = self.get_deployment_metrics(deployment_id, hours=24)
|
355
|
+
|
356
|
+
if not metrics:
|
357
|
+
return {"total_metrics": 0, "period_hours": 24}
|
358
|
+
|
359
|
+
# Calculate basic statistics
|
360
|
+
response_times = [m.avg_response_time_ms for m in metrics if m.avg_response_time_ms is not None]
|
361
|
+
error_rates = [m.error_rate for m in metrics if m.error_rate is not None]
|
362
|
+
cpu_usage = [m.cpu_usage for m in metrics if m.cpu_usage is not None]
|
363
|
+
|
364
|
+
stats = {
|
365
|
+
"total_metrics": len(metrics),
|
366
|
+
"period_hours": 24,
|
367
|
+
"latest_timestamp": max(m.timestamp for m in metrics).isoformat(),
|
368
|
+
"avg_response_time_ms": sum(response_times) / len(response_times) if response_times else None,
|
369
|
+
"avg_error_rate": sum(error_rates) / len(error_rates) if error_rates else None,
|
370
|
+
"avg_cpu_usage": sum(cpu_usage) / len(cpu_usage) if cpu_usage else None,
|
371
|
+
"total_requests": sum(m.requests_per_minute or 0 for m in metrics),
|
372
|
+
"uptime_percentage": metrics[-1].uptime_percentage if metrics and metrics[-1].uptime_percentage else None
|
373
|
+
}
|
374
|
+
|
375
|
+
return stats
|
376
|
+
|
377
|
+
# Service Registry Methods
|
378
|
+
|
379
|
+
def register_service(
|
380
|
+
self,
|
381
|
+
deployment_id: str,
|
382
|
+
service_name: str,
|
383
|
+
service_type: str,
|
384
|
+
endpoint_url: str,
|
385
|
+
health_check_url: Optional[str] = None,
|
386
|
+
metadata: Optional[Dict[str, Any]] = None
|
387
|
+
) -> str:
|
388
|
+
"""Register a service in the service registry"""
|
389
|
+
service_id = f"service_{deployment_id}_{uuid.uuid4().hex[:6]}"
|
390
|
+
|
391
|
+
service = ServiceRegistryEntry(
|
392
|
+
service_id=service_id,
|
393
|
+
deployment_id=deployment_id,
|
394
|
+
service_name=service_name,
|
395
|
+
service_type=service_type,
|
396
|
+
endpoint_url=endpoint_url,
|
397
|
+
health_check_url=health_check_url,
|
398
|
+
metadata=metadata
|
399
|
+
)
|
400
|
+
|
401
|
+
if self.storage_backend == "supabase":
|
402
|
+
return self._register_service_supabase(service)
|
403
|
+
elif self.storage_backend == "memory":
|
404
|
+
return self._register_service_memory(service)
|
405
|
+
else:
|
406
|
+
return self._register_service_file(service)
|
407
|
+
|
408
|
+
def update_service_health(
|
409
|
+
self,
|
410
|
+
service_id: str,
|
411
|
+
health_status: str,
|
412
|
+
last_check_time: Optional[datetime] = None
|
413
|
+
) -> bool:
|
414
|
+
"""Update service health status"""
|
415
|
+
updates = {
|
416
|
+
"health_status": health_status,
|
417
|
+
"last_health_check": (last_check_time or datetime.now(timezone.utc)).isoformat()
|
418
|
+
}
|
419
|
+
|
420
|
+
if self.storage_backend == "supabase":
|
421
|
+
return self._update_service_supabase(service_id, updates)
|
422
|
+
elif self.storage_backend == "memory":
|
423
|
+
return self._update_service_memory(service_id, updates)
|
424
|
+
else:
|
425
|
+
return self._update_service_file(service_id, updates)
|
426
|
+
|
427
|
+
def list_services(
|
428
|
+
self,
|
429
|
+
deployment_id: Optional[str] = None,
|
430
|
+
service_type: Optional[str] = None,
|
431
|
+
is_active: bool = True
|
432
|
+
) -> List[ServiceRegistryEntry]:
|
433
|
+
"""List registered services"""
|
434
|
+
if self.storage_backend == "supabase":
|
435
|
+
return self._list_services_supabase(deployment_id, service_type, is_active)
|
436
|
+
elif self.storage_backend == "memory":
|
437
|
+
return self._list_services_memory(deployment_id, service_type, is_active)
|
438
|
+
else:
|
439
|
+
return self._list_services_file(deployment_id, service_type, is_active)
|
440
|
+
|
441
|
+
def deregister_service(self, service_id: str) -> bool:
|
442
|
+
"""Deregister a service"""
|
443
|
+
if self.storage_backend == "supabase":
|
444
|
+
return self._deregister_service_supabase(service_id)
|
445
|
+
elif self.storage_backend == "memory":
|
446
|
+
return self._deregister_service_memory(service_id)
|
447
|
+
else:
|
448
|
+
return self._deregister_service_file(service_id)
|
449
|
+
|
450
|
+
# Backend-specific implementations
|
451
|
+
|
452
|
+
def _create_deployment_file(self, deployment: DeploymentRecord) -> str:
|
453
|
+
"""Create deployment in file system"""
|
454
|
+
try:
|
455
|
+
deployment_file = self.data_dir / "deployments" / f"{deployment.deployment_id}.json"
|
456
|
+
deployment_data = asdict(deployment)
|
457
|
+
|
458
|
+
# Convert datetime objects to ISO strings
|
459
|
+
for key in ['created_at', 'updated_at', 'deployed_at', 'stopped_at']:
|
460
|
+
if deployment_data[key] and isinstance(deployment_data[key], datetime):
|
461
|
+
deployment_data[key] = deployment_data[key].isoformat()
|
462
|
+
|
463
|
+
with open(deployment_file, 'w') as f:
|
464
|
+
json.dump(deployment_data, f, indent=2, ensure_ascii=False)
|
465
|
+
|
466
|
+
return deployment.deployment_id
|
467
|
+
except Exception as e:
|
468
|
+
logger.error(f"Failed to create deployment in file system: {e}")
|
469
|
+
raise
|
470
|
+
|
471
|
+
def _create_deployment_memory(self, deployment: DeploymentRecord) -> str:
|
472
|
+
"""Create deployment in memory"""
|
473
|
+
self.deployments[deployment.deployment_id] = deployment
|
474
|
+
return deployment.deployment_id
|
475
|
+
|
476
|
+
def _update_deployment_file(self, deployment_id: str, updates: Dict[str, Any]) -> bool:
|
477
|
+
"""Update deployment in file system"""
|
478
|
+
try:
|
479
|
+
deployment_file = self.data_dir / "deployments" / f"{deployment_id}.json"
|
480
|
+
if not deployment_file.exists():
|
481
|
+
return False
|
482
|
+
|
483
|
+
with open(deployment_file, 'r') as f:
|
484
|
+
deployment_data = json.load(f)
|
485
|
+
|
486
|
+
deployment_data.update(updates)
|
487
|
+
|
488
|
+
with open(deployment_file, 'w') as f:
|
489
|
+
json.dump(deployment_data, f, indent=2, ensure_ascii=False)
|
490
|
+
|
491
|
+
return True
|
492
|
+
except Exception as e:
|
493
|
+
logger.error(f"Failed to update deployment in file system: {e}")
|
494
|
+
return False
|
495
|
+
|
496
|
+
def _update_deployment_memory(self, deployment_id: str, updates: Dict[str, Any]) -> bool:
|
497
|
+
"""Update deployment in memory"""
|
498
|
+
if deployment_id not in self.deployments:
|
499
|
+
return False
|
500
|
+
|
501
|
+
deployment_dict = asdict(self.deployments[deployment_id])
|
502
|
+
deployment_dict.update(updates)
|
503
|
+
|
504
|
+
# Convert datetime strings back to datetime objects if needed
|
505
|
+
for key in ['created_at', 'updated_at', 'deployed_at', 'stopped_at']:
|
506
|
+
if key in deployment_dict and isinstance(deployment_dict[key], str):
|
507
|
+
deployment_dict[key] = datetime.fromisoformat(deployment_dict[key])
|
508
|
+
|
509
|
+
self.deployments[deployment_id] = DeploymentRecord(**deployment_dict)
|
510
|
+
return True
|
511
|
+
|
512
|
+
def _get_deployment_file(self, deployment_id: str) -> Optional[DeploymentRecord]:
|
513
|
+
"""Get deployment from file system"""
|
514
|
+
try:
|
515
|
+
deployment_file = self.data_dir / "deployments" / f"{deployment_id}.json"
|
516
|
+
if not deployment_file.exists():
|
517
|
+
return None
|
518
|
+
|
519
|
+
with open(deployment_file, 'r') as f:
|
520
|
+
deployment_data = json.load(f)
|
521
|
+
|
522
|
+
# Convert ISO strings back to datetime objects
|
523
|
+
for key in ['created_at', 'updated_at', 'deployed_at', 'stopped_at']:
|
524
|
+
if deployment_data[key]:
|
525
|
+
deployment_data[key] = datetime.fromisoformat(deployment_data[key])
|
526
|
+
|
527
|
+
return DeploymentRecord(**deployment_data)
|
528
|
+
except Exception as e:
|
529
|
+
logger.error(f"Failed to get deployment from file system: {e}")
|
530
|
+
return None
|
531
|
+
|
532
|
+
def _get_deployment_memory(self, deployment_id: str) -> Optional[DeploymentRecord]:
|
533
|
+
"""Get deployment from memory"""
|
534
|
+
return self.deployments.get(deployment_id)
|
535
|
+
|
536
|
+
def _list_deployments_file(
|
537
|
+
self, platform: Optional[str], environment: Optional[str],
|
538
|
+
user_id: Optional[str], project_name: Optional[str], limit: int
|
539
|
+
) -> List[DeploymentRecord]:
|
540
|
+
"""List deployments from file system"""
|
541
|
+
try:
|
542
|
+
deployments = []
|
543
|
+
deployments_dir = self.data_dir / "deployments"
|
544
|
+
|
545
|
+
for deployment_file in deployments_dir.glob("*.json"):
|
546
|
+
with open(deployment_file, 'r') as f:
|
547
|
+
deployment_data = json.load(f)
|
548
|
+
|
549
|
+
# Apply filters
|
550
|
+
if platform and deployment_data.get('platform') != platform:
|
551
|
+
continue
|
552
|
+
if environment and deployment_data.get('environment') != environment:
|
553
|
+
continue
|
554
|
+
if user_id and deployment_data.get('user_id') != user_id:
|
555
|
+
continue
|
556
|
+
if project_name and deployment_data.get('project_name') != project_name:
|
557
|
+
continue
|
558
|
+
|
559
|
+
# Convert datetime fields
|
560
|
+
for key in ['created_at', 'updated_at', 'deployed_at', 'stopped_at']:
|
561
|
+
if deployment_data[key]:
|
562
|
+
deployment_data[key] = datetime.fromisoformat(deployment_data[key])
|
563
|
+
|
564
|
+
deployments.append(DeploymentRecord(**deployment_data))
|
565
|
+
|
566
|
+
if len(deployments) >= limit:
|
567
|
+
break
|
568
|
+
|
569
|
+
return sorted(deployments, key=lambda x: x.created_at, reverse=True)
|
570
|
+
except Exception as e:
|
571
|
+
logger.error(f"Failed to list deployments from file system: {e}")
|
572
|
+
return []
|
573
|
+
|
574
|
+
def _list_deployments_memory(
|
575
|
+
self, platform: Optional[str], environment: Optional[str],
|
576
|
+
user_id: Optional[str], project_name: Optional[str], limit: int
|
577
|
+
) -> List[DeploymentRecord]:
|
578
|
+
"""List deployments from memory"""
|
579
|
+
deployments = []
|
580
|
+
|
581
|
+
for deployment in self.deployments.values():
|
582
|
+
# Apply filters
|
583
|
+
if platform and deployment.platform != platform:
|
584
|
+
continue
|
585
|
+
if environment and deployment.environment != environment:
|
586
|
+
continue
|
587
|
+
if user_id and deployment.user_id != user_id:
|
588
|
+
continue
|
589
|
+
if project_name and deployment.project_name != project_name:
|
590
|
+
continue
|
591
|
+
|
592
|
+
deployments.append(deployment)
|
593
|
+
|
594
|
+
if len(deployments) >= limit:
|
595
|
+
break
|
596
|
+
|
597
|
+
return sorted(deployments, key=lambda x: x.created_at, reverse=True)
|
598
|
+
|
599
|
+
def _record_metrics_file(self, metrics: DeploymentMetrics) -> str:
|
600
|
+
"""Record metrics in file system"""
|
601
|
+
try:
|
602
|
+
metrics_file = self.data_dir / "metrics" / f"{metrics.metric_id}.json"
|
603
|
+
metrics_data = asdict(metrics)
|
604
|
+
|
605
|
+
if metrics_data['timestamp'] and isinstance(metrics_data['timestamp'], datetime):
|
606
|
+
metrics_data['timestamp'] = metrics_data['timestamp'].isoformat()
|
607
|
+
|
608
|
+
with open(metrics_file, 'w') as f:
|
609
|
+
json.dump(metrics_data, f, indent=2, ensure_ascii=False)
|
610
|
+
|
611
|
+
return metrics.metric_id
|
612
|
+
except Exception as e:
|
613
|
+
logger.error(f"Failed to record metrics in file system: {e}")
|
614
|
+
raise
|
615
|
+
|
616
|
+
def _record_metrics_memory(self, metrics: DeploymentMetrics) -> str:
|
617
|
+
"""Record metrics in memory"""
|
618
|
+
if metrics.deployment_id not in self.metrics:
|
619
|
+
self.metrics[metrics.deployment_id] = []
|
620
|
+
self.metrics[metrics.deployment_id].append(metrics)
|
621
|
+
return metrics.metric_id
|
622
|
+
|
623
|
+
def _get_metrics_file(self, deployment_id: str, hours: int, limit: int) -> List[DeploymentMetrics]:
|
624
|
+
"""Get metrics from file system"""
|
625
|
+
try:
|
626
|
+
metrics = []
|
627
|
+
metrics_dir = self.data_dir / "metrics"
|
628
|
+
cutoff_time = datetime.now(timezone.utc) - timedelta(hours=hours)
|
629
|
+
|
630
|
+
for metrics_file in metrics_dir.glob("*.json"):
|
631
|
+
with open(metrics_file, 'r') as f:
|
632
|
+
metrics_data = json.load(f)
|
633
|
+
|
634
|
+
if metrics_data.get('deployment_id') != deployment_id:
|
635
|
+
continue
|
636
|
+
|
637
|
+
if metrics_data['timestamp']:
|
638
|
+
timestamp = datetime.fromisoformat(metrics_data['timestamp'])
|
639
|
+
if timestamp < cutoff_time:
|
640
|
+
continue
|
641
|
+
metrics_data['timestamp'] = timestamp
|
642
|
+
|
643
|
+
metrics.append(DeploymentMetrics(**metrics_data))
|
644
|
+
|
645
|
+
if len(metrics) >= limit:
|
646
|
+
break
|
647
|
+
|
648
|
+
return sorted(metrics, key=lambda x: x.timestamp, reverse=True)
|
649
|
+
except Exception as e:
|
650
|
+
logger.error(f"Failed to get metrics from file system: {e}")
|
651
|
+
return []
|
652
|
+
|
653
|
+
def _get_metrics_memory(self, deployment_id: str, hours: int, limit: int) -> List[DeploymentMetrics]:
|
654
|
+
"""Get metrics from memory"""
|
655
|
+
from datetime import timedelta
|
656
|
+
cutoff_time = datetime.now(timezone.utc) - timedelta(hours=hours)
|
657
|
+
|
658
|
+
deployment_metrics = self.metrics.get(deployment_id, [])
|
659
|
+
filtered_metrics = [
|
660
|
+
m for m in deployment_metrics
|
661
|
+
if m.timestamp >= cutoff_time
|
662
|
+
]
|
663
|
+
|
664
|
+
return sorted(filtered_metrics[:limit], key=lambda x: x.timestamp, reverse=True)
|
665
|
+
|
666
|
+
# Service registry implementations (simplified)
|
667
|
+
def _register_service_file(self, service: ServiceRegistryEntry) -> str:
|
668
|
+
"""Register service in file system"""
|
669
|
+
try:
|
670
|
+
service_file = self.data_dir / "services" / f"{service.service_id}.json"
|
671
|
+
service_data = asdict(service)
|
672
|
+
|
673
|
+
if service_data['created_at'] and isinstance(service_data['created_at'], datetime):
|
674
|
+
service_data['created_at'] = service_data['created_at'].isoformat()
|
675
|
+
if service_data['last_health_check'] and isinstance(service_data['last_health_check'], datetime):
|
676
|
+
service_data['last_health_check'] = service_data['last_health_check'].isoformat()
|
677
|
+
|
678
|
+
with open(service_file, 'w') as f:
|
679
|
+
json.dump(service_data, f, indent=2, ensure_ascii=False)
|
680
|
+
|
681
|
+
return service.service_id
|
682
|
+
except Exception as e:
|
683
|
+
logger.error(f"Failed to register service in file system: {e}")
|
684
|
+
raise
|
685
|
+
|
686
|
+
def _register_service_memory(self, service: ServiceRegistryEntry) -> str:
|
687
|
+
"""Register service in memory"""
|
688
|
+
self.services[service.service_id] = service
|
689
|
+
return service.service_id
|
690
|
+
|
691
|
+
# Placeholder implementations for Supabase backend
|
692
|
+
def _create_deployment_supabase(self, deployment: DeploymentRecord) -> str:
|
693
|
+
return deployment.deployment_id # Implementation needed
|
694
|
+
|
695
|
+
def _update_deployment_supabase(self, deployment_id: str, updates: Dict[str, Any]) -> bool:
|
696
|
+
return False # Implementation needed
|
697
|
+
|
698
|
+
def _get_deployment_supabase(self, deployment_id: str) -> Optional[DeploymentRecord]:
|
699
|
+
return None # Implementation needed
|
700
|
+
|
701
|
+
def _list_deployments_supabase(self, platform, environment, user_id, project_name, limit) -> List[DeploymentRecord]:
|
702
|
+
return [] # Implementation needed
|
703
|
+
|
704
|
+
def _delete_deployment_supabase(self, deployment_id: str) -> bool:
|
705
|
+
return False # Implementation needed
|
706
|
+
|
707
|
+
def _delete_deployment_file(self, deployment_id: str) -> bool:
|
708
|
+
try:
|
709
|
+
deployment_file = self.data_dir / "deployments" / f"{deployment_id}.json"
|
710
|
+
if deployment_file.exists():
|
711
|
+
deployment_file.unlink()
|
712
|
+
return True
|
713
|
+
except Exception as e:
|
714
|
+
logger.error(f"Failed to delete deployment from file system: {e}")
|
715
|
+
return False
|
716
|
+
|
717
|
+
def _delete_deployment_memory(self, deployment_id: str) -> bool:
|
718
|
+
return self.deployments.pop(deployment_id, None) is not None
|
719
|
+
|
720
|
+
# Additional placeholder implementations
|
721
|
+
def _record_metrics_supabase(self, metrics: DeploymentMetrics) -> str:
|
722
|
+
return metrics.metric_id
|
723
|
+
|
724
|
+
def _get_metrics_supabase(self, deployment_id: str, hours: int, limit: int) -> List[DeploymentMetrics]:
|
725
|
+
return []
|
726
|
+
|
727
|
+
def _register_service_supabase(self, service: ServiceRegistryEntry) -> str:
|
728
|
+
return service.service_id
|
729
|
+
|
730
|
+
def _update_service_supabase(self, service_id: str, updates: Dict[str, Any]) -> bool:
|
731
|
+
return False
|
732
|
+
|
733
|
+
def _update_service_memory(self, service_id: str, updates: Dict[str, Any]) -> bool:
|
734
|
+
if service_id not in self.services:
|
735
|
+
return False
|
736
|
+
|
737
|
+
service_dict = asdict(self.services[service_id])
|
738
|
+
service_dict.update(updates)
|
739
|
+
|
740
|
+
# Convert datetime strings back if needed
|
741
|
+
for key in ['created_at', 'last_health_check']:
|
742
|
+
if key in service_dict and isinstance(service_dict[key], str):
|
743
|
+
service_dict[key] = datetime.fromisoformat(service_dict[key])
|
744
|
+
|
745
|
+
self.services[service_id] = ServiceRegistryEntry(**service_dict)
|
746
|
+
return True
|
747
|
+
|
748
|
+
def _update_service_file(self, service_id: str, updates: Dict[str, Any]) -> bool:
|
749
|
+
try:
|
750
|
+
service_file = self.data_dir / "services" / f"{service_id}.json"
|
751
|
+
if not service_file.exists():
|
752
|
+
return False
|
753
|
+
|
754
|
+
with open(service_file, 'r') as f:
|
755
|
+
service_data = json.load(f)
|
756
|
+
|
757
|
+
service_data.update(updates)
|
758
|
+
|
759
|
+
with open(service_file, 'w') as f:
|
760
|
+
json.dump(service_data, f, indent=2, ensure_ascii=False)
|
761
|
+
|
762
|
+
return True
|
763
|
+
except Exception as e:
|
764
|
+
logger.error(f"Failed to update service in file system: {e}")
|
765
|
+
return False
|
766
|
+
|
767
|
+
def _list_services_supabase(self, deployment_id, service_type, is_active) -> List[ServiceRegistryEntry]:
|
768
|
+
return []
|
769
|
+
|
770
|
+
def _list_services_memory(self, deployment_id, service_type, is_active) -> List[ServiceRegistryEntry]:
|
771
|
+
services = []
|
772
|
+
for service in self.services.values():
|
773
|
+
if deployment_id and service.deployment_id != deployment_id:
|
774
|
+
continue
|
775
|
+
if service_type and service.service_type != service_type:
|
776
|
+
continue
|
777
|
+
if service.is_active != is_active:
|
778
|
+
continue
|
779
|
+
services.append(service)
|
780
|
+
return services
|
781
|
+
|
782
|
+
def _list_services_file(self, deployment_id, service_type, is_active) -> List[ServiceRegistryEntry]:
|
783
|
+
try:
|
784
|
+
services = []
|
785
|
+
services_dir = self.data_dir / "services"
|
786
|
+
|
787
|
+
for service_file in services_dir.glob("*.json"):
|
788
|
+
with open(service_file, 'r') as f:
|
789
|
+
service_data = json.load(f)
|
790
|
+
|
791
|
+
if deployment_id and service_data.get('deployment_id') != deployment_id:
|
792
|
+
continue
|
793
|
+
if service_type and service_data.get('service_type') != service_type:
|
794
|
+
continue
|
795
|
+
if service_data.get('is_active') != is_active:
|
796
|
+
continue
|
797
|
+
|
798
|
+
# Convert datetime fields
|
799
|
+
for key in ['created_at', 'last_health_check']:
|
800
|
+
if service_data[key]:
|
801
|
+
service_data[key] = datetime.fromisoformat(service_data[key])
|
802
|
+
|
803
|
+
services.append(ServiceRegistryEntry(**service_data))
|
804
|
+
|
805
|
+
return services
|
806
|
+
except Exception as e:
|
807
|
+
logger.error(f"Failed to list services from file system: {e}")
|
808
|
+
return []
|
809
|
+
|
810
|
+
def _deregister_service_supabase(self, service_id: str) -> bool:
|
811
|
+
return False
|
812
|
+
|
813
|
+
def _deregister_service_memory(self, service_id: str) -> bool:
|
814
|
+
return self.services.pop(service_id, None) is not None
|
815
|
+
|
816
|
+
def _deregister_service_file(self, service_id: str) -> bool:
|
817
|
+
try:
|
818
|
+
service_file = self.data_dir / "services" / f"{service_id}.json"
|
819
|
+
if service_file.exists():
|
820
|
+
service_file.unlink()
|
821
|
+
return True
|
822
|
+
except Exception as e:
|
823
|
+
logger.error(f"Failed to deregister service from file system: {e}")
|
824
|
+
return False
|