# dao.py import os import yaml import json import time from pydantic import ValidationError from ocdp.orchestration.cluster import Cluster from ocdp.models.orchestration.application import (ApplicationTemplate, ApplicationMetadata, InstallReceipt, ApplicationStatus, UninstallReceipt, PodStatusDetail, InstalledApplicationInstance, NamespaceDeleteReceipt) # <-- 导入更新后的模型 # ... (list_application_templates, list_application_instances, _deep_merge 保持不变) ... def list_application_templates(cluster: Cluster) -> list[ApplicationTemplate]: app_dirs = cluster.list_applications(); template_list = [] for app_dir in app_dirs: try: metadata_dict = cluster.get_application_metadata(app_dir) validated_metadata = ApplicationMetadata(**metadata_dict) template_list.append(ApplicationTemplate(name=app_dir,metadata=validated_metadata)) except Exception as e: print(f"⚠️ Warning: Could not load or validate metadata for '{app_dir}': {e}") return template_list def list_application_instances(cluster: Cluster, user_id: str) -> list[InstalledApplicationInstance]: prefix = f"{user_id}-"; try: ns_json_str = cluster.get("namespaces"); all_ns_data = json.loads(ns_json_str).get("items", []) user_namespaces = [ns['metadata']['name'] for ns in all_ns_data if ns['metadata']['name'].startswith(prefix)] if not user_namespaces: return [] releases_json_str = cluster.list_releases(all_namespaces=True, output="json"); all_releases = json.loads(releases_json_str) instances = [] for rel in all_releases: if rel.get("namespace") in user_namespaces: ns_parts = rel.get("namespace").split('-'); app_name = ns_parts[1] if len(ns_parts) > 2 else "unknown" instances.append(InstalledApplicationInstance( application_name=app_name, release_name=rel.get("name"), namespace=rel.get("namespace"), chart=rel.get("chart"), status=rel.get("status") )) return instances except (RuntimeError, json.JSONDecodeError) as e: print(f"❌ Error listing application instances: {e}"); return [] def _deep_merge(source: dict, destination: dict) -> dict: for key, value in source.items(): if isinstance(value, dict) and key in destination and isinstance(destination[key], dict): destination[key] = _deep_merge(value, destination[key]) else: destination[key] = value return destination def install_application( cluster, namespace, app_template_name, mode, user_overrides=None ) -> InstallReceipt: metadata = cluster.get_application_metadata(app_template_name) print(f"Metadata for '{app_template_name}': {metadata}") app_meta = ApplicationMetadata(**metadata) deployment_mode = getattr(app_meta, mode, None) if not deployment_mode: raise ValueError(f"Mode '{mode}' not found.") release_name = deployment_mode.release_name chart_source = deployment_mode.chart values_to_set = deployment_mode.sets if user_overrides: values_to_set = _deep_merge(user_overrides, values_to_set) temp_values_path = f"/tmp/temp-values-{namespace}.yaml" with open(temp_values_path, 'w') as f: yaml.dump(values_to_set, f) try: output = cluster.install_release( release_name=release_name, chart_source=chart_source, namespace=namespace, config_file=temp_values_path, create_namespace=True ) print(output) return InstallReceipt( application_name=app_meta.application_name, release_name=release_name, namespace=namespace, message=f"Installation triggered successfully. Raw output: {output.strip()}" ) finally: if os.path.exists(temp_values_path): os.remove(temp_values_path) def uninstall_application_release(cluster: Cluster, namespace: str, app_name: str, mode: str) -> UninstallReceipt: try: # 1. 获取并验证元数据 metadata = cluster.get_application_metadata(app_name) app_meta = ApplicationMetadata(**metadata) deployment_mode = getattr(app_meta, mode, None) if not deployment_mode: raise ValueError(f"Mode '{mode}' not found in metadata.") release_name = deployment_mode.release_name # 2. 卸载 Helm Release output = cluster.uninstall_release(release_name, namespace=namespace, wait=True) uninstalled_successfully = True # 3. 验证卸载是否成功 verification_message = "Verification successful: Release is no longer listed by Helm." is_clean = True try: time.sleep(2) releases_json_str = cluster.list_releases(namespace=namespace, output="json") releases = json.loads(releases_json_str) release_found = any(r['name'] == release_name for r in releases) if release_found: is_clean = False verification_message = "Verification failed: Release is still present in Helm's list." except Exception as e: verification_message = f"Verification check failed: {e}" except (ValidationError, ValueError, RuntimeError) as e: # 捕获所有已知的预处理和运行时错误 return UninstallReceipt( application_name=app_name, release_name=release_name if 'release_name' in locals() else 'unknown', namespace=namespace, uninstalled_successfully=False, is_clean=False, message=f"Operation failed due to an error: {e}" ) except Exception as e: # 捕获所有其他意外错误 return UninstallReceipt( application_name=app_name, release_name=release_name if 'release_name' in locals() else 'unknown', namespace=namespace, uninstalled_successfully=False, is_clean=False, message=f"An unexpected error occurred: {e}" ) return UninstallReceipt( application_name=app_name, release_name=release_name, namespace=namespace, uninstalled_successfully=uninstalled_successfully, is_clean=is_clean, message=f"{output.strip()}. {verification_message}" ) def delete_namespace(cluster: Cluster, namespace: str) -> NamespaceDeleteReceipt: app_name = "unknown" try: # 尝试从命名空间中提取应用名称 ns_parts = namespace.split('-') if len(ns_parts) > 2: app_name = ns_parts[1] except Exception: pass # 如果解析失败,app_name 保持为 'unknown' try: # 1. 提交删除命名空间的命令 output = cluster.delete(resource_type="namespace", name=namespace) deleted_successfully = True # 2. 验证命名空间是否已被删除 is_clean = False verification_message = "Delete command submitted. Namespace is terminating." try: # 循环检查命名空间直到它不存在 timeout = 60 start_time = time.time() while time.time() - start_time < timeout: try: cluster.get("namespace", name=namespace) time.sleep(5) except RuntimeError as e: if "not found" in str(e).lower(): is_clean = True verification_message = "Verification successful: Namespace not found." break else: raise e # 重新抛出其他运行时错误 if not is_clean: verification_message = "Verification failed: Namespace still exists after timeout." except Exception as e: verification_message = f"Verification check failed: {e}" except RuntimeError as e: # 如果 delete 命令本身失败 return NamespaceDeleteReceipt( application_name=app_name, namespace=namespace, deleted_successfully=False, is_clean=False, message=f"Delete namespace command failed: {e}" ) return NamespaceDeleteReceipt( application_name=app_name, namespace=namespace, deleted_successfully=deleted_successfully, is_clean=is_clean, message=f"{output.strip()}. {verification_message}" ) def get_application_status( cluster, namespace: str, app_template_name: str, mode: str ): app_name = "Unknown" base_access_url = None paths = None try: metadata_dict = cluster.get_application_metadata(app_template_name) app_meta = ApplicationMetadata(**metadata_dict) deployment_mode = getattr(app_meta, mode, None) if not deployment_mode: raise ValueError(f"Mode '{mode}' not found.") app_name = app_meta.application_name if not deployment_mode.pod or not deployment_mode.pod.name: raise ValueError("Pod name pattern is not defined.") pod_name_pattern = deployment_mode.pod.name if deployment_mode.svc: base_access_url = deployment_mode.svc.url paths = deployment_mode.svc.paths pods_json_str = cluster.get("pods", namespace=namespace) all_pods = json.loads(pods_json_str).get("items", []) target_pods = [p for p in all_pods if p.get('metadata', {}).get('name', '').startswith(pod_name_pattern)] if not target_pods: return ApplicationStatus( application_name=app_name, namespace=namespace, is_ready=False, base_access_url=base_access_url, paths=paths, details=[] ) all_ready = True pod_details = [] for pod in target_pods: pod_name = pod['metadata']['name'] container_statuses = pod.get('status', {}).get('containerStatuses', []) pod_phase = pod.get('status', {}).get('phase', '') ready_count = sum(1 for s in container_statuses if s.get('ready')) total_count = len(container_statuses) pod_is_ready = (pod_phase == 'Running') and (ready_count == total_count) if not pod_is_ready: all_ready = False pod_details.append( PodStatusDetail( pod_name=pod_name, is_ready=pod_is_ready, ready_status=f"{ready_count}/{total_count}", status_phase=pod_phase ) ) return ApplicationStatus( application_name=app_name, namespace=namespace, is_ready=all_ready, base_access_url=base_access_url, paths=paths, details=pod_details ) except (ValidationError, json.JSONDecodeError, KeyError, ValueError, AttributeError) as e: return ApplicationStatus( application_name=app_name, namespace=namespace, is_ready=False, base_access_url=base_access_url, paths=paths, details=[PodStatusDetail(pod_name="Error", is_ready=False, ready_status="0/0", status_phase=f"Error: {e}")] ) except Exception as e: return ApplicationStatus( application_name=app_name, namespace=namespace, is_ready=False, base_access_url=base_access_url, paths=paths, details=[PodStatusDetail(pod_name="Unexpected Error", is_ready=False, ready_status="0/0", status_phase=f"Error: {e}")] )