Files
ocdp/ocdp/daos/orchestration/application_dao.py
jackyliu c7f8e69d61 feat: Add orchestration models and services for Kubernetes cluster management
- Implemented Pydantic models for Kubernetes cluster state representation in `cluster.py`.
- Created a `Resource` class for converting JSON/dict to Python objects in `resource.py`.
- Established user models and services for user management, including password hashing and JWT token generation.
- Developed application orchestration services for managing Kubernetes applications, including installation and uninstallation.
- Added cluster service for retrieving cluster status and health reports.
- Introduced node service for fetching node resource details and health status.
- Implemented user service for handling user authentication and management.
2025-09-02 02:54:26 +00:00

304 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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}")]
)