- 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.
304 lines
12 KiB
Python
304 lines
12 KiB
Python
# 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}")]
|
||
) |