|
1 | 1 | # SPDX-License-Identifier: Apache-2.0 |
2 | 2 |
|
| 3 | +import fcntl |
| 4 | +import json |
3 | 5 | import os |
4 | 6 | import re |
5 | 7 | import subprocess |
| 8 | +import yaml |
| 9 | +from datetime import datetime, timezone |
| 10 | +from pathlib import Path |
6 | 11 |
|
7 | 12 | from loguru import logger |
8 | 13 |
|
@@ -30,6 +35,103 @@ class Config: |
30 | 35 | } |
31 | 36 |
|
32 | 37 |
|
| 38 | +def get_container_version(worker): |
| 39 | + """Read container version from YAML version file. |
| 40 | +
|
| 41 | + Args: |
| 42 | + worker: The runtime container name (osism-ansible, kolla-ansible, ceph-ansible, osism-kubernetes) |
| 43 | +
|
| 44 | + Returns: |
| 45 | + str: The container version, "latest" if empty, or "unknown" if not found |
| 46 | +
|
| 47 | + Examples: |
| 48 | + >>> get_container_version("osism-ansible") |
| 49 | + "7.0.5a" |
| 50 | + >>> get_container_version("kolla-ansible") |
| 51 | + "18.1.0" |
| 52 | + >>> get_container_version("osism-kubernetes") |
| 53 | + "1.29.0" |
| 54 | +
|
| 55 | + Note: |
| 56 | + If the version parameter in the YAML file is an empty string (""), |
| 57 | + the function returns "latest" as the default value. |
| 58 | + """ |
| 59 | + version_file = Path(f"/interface/versions/{worker}.yml") |
| 60 | + |
| 61 | + try: |
| 62 | + if not version_file.exists(): |
| 63 | + logger.debug(f"Version file not found: {version_file}") |
| 64 | + return "unknown" |
| 65 | + |
| 66 | + with open(version_file, "r") as f: |
| 67 | + version_data = yaml.safe_load(f) |
| 68 | + |
| 69 | + # Convert worker name to version parameter name |
| 70 | + # osism-ansible -> osism_ansible_version |
| 71 | + # kolla-ansible -> kolla_ansible_version |
| 72 | + # ceph-ansible -> ceph_ansible_version |
| 73 | + version_key = f"{worker.replace('-', '_')}_version" |
| 74 | + |
| 75 | + version = version_data.get(version_key, "unknown") |
| 76 | + |
| 77 | + # If version is empty string, use "latest" as default |
| 78 | + if version == "": |
| 79 | + version = "latest" |
| 80 | + logger.debug(f"Version parameter empty for {worker}, using 'latest'") |
| 81 | + |
| 82 | + logger.debug(f"Read version {version} for {worker} from {version_file}") |
| 83 | + return version |
| 84 | + |
| 85 | + except Exception as e: |
| 86 | + logger.warning(f"Failed to read version from {version_file}: {e}") |
| 87 | + return "unknown" |
| 88 | + |
| 89 | + |
| 90 | +def log_play_execution( |
| 91 | + request_id, worker, environment, role, hosts=None, result="started" |
| 92 | +): |
| 93 | + """Log Ansible play execution to central tracking file. |
| 94 | +
|
| 95 | + Args: |
| 96 | + request_id: The Celery task request ID for correlation |
| 97 | + worker: The runtime container (osism-ansible, kolla-ansible, ceph-ansible, osism-kubernetes) |
| 98 | + environment: The environment parameter |
| 99 | + role: The playbook/role that was executed |
| 100 | + hosts: List of hosts the play was executed against (default: empty list) |
| 101 | + result: Execution result - "started", "success", or "failure" |
| 102 | + """ |
| 103 | + log_file = Path("/share/ansible-execution-history.json") |
| 104 | + |
| 105 | + # Get runtime version from YAML version file |
| 106 | + runtime_version = get_container_version(worker) |
| 107 | + |
| 108 | + execution_record = { |
| 109 | + "timestamp": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"), |
| 110 | + "request_id": request_id, |
| 111 | + "worker": worker, |
| 112 | + "worker_version": runtime_version, |
| 113 | + "environment": environment, |
| 114 | + "role": role, |
| 115 | + "hosts": hosts if isinstance(hosts, list) else [], |
| 116 | + "result": result, |
| 117 | + } |
| 118 | + |
| 119 | + try: |
| 120 | + # Create directory if it doesn't exist |
| 121 | + log_file.parent.mkdir(parents=True, exist_ok=True) |
| 122 | + |
| 123 | + # Append with file locking for thread safety |
| 124 | + with open(log_file, "a") as f: |
| 125 | + fcntl.flock(f.fileno(), fcntl.LOCK_EX) |
| 126 | + try: |
| 127 | + f.write(json.dumps(execution_record) + "\n") |
| 128 | + finally: |
| 129 | + fcntl.flock(f.fileno(), fcntl.LOCK_UN) |
| 130 | + except Exception as e: |
| 131 | + # Log warning but don't fail the execution |
| 132 | + logger.warning(f"Failed to log play execution to {log_file}: {e}") |
| 133 | + |
| 134 | + |
33 | 135 | def run_ansible_in_environment( |
34 | 136 | request_id, |
35 | 137 | worker, |
@@ -73,6 +175,16 @@ def run_ansible_in_environment( |
73 | 175 | if ansible_vault_password: |
74 | 176 | env["VAULT"] = "/ansible-vault.py" |
75 | 177 |
|
| 178 | + # Log play execution start |
| 179 | + log_play_execution( |
| 180 | + request_id=request_id, |
| 181 | + worker=worker, |
| 182 | + environment=environment, |
| 183 | + role=role, |
| 184 | + hosts=None, # Host extraction would require inventory parsing |
| 185 | + result="started", |
| 186 | + ) |
| 187 | + |
76 | 188 | # NOTE: Consider arguments in the future |
77 | 189 | if locking: |
78 | 190 | lock = utils.create_redlock( |
@@ -110,8 +222,8 @@ def run_ansible_in_environment( |
110 | 222 | env=env, |
111 | 223 | ) |
112 | 224 |
|
113 | | - # execute roles from kubernetes |
114 | | - elif worker == "kubernetes": |
| 225 | + # execute roles from osism-kubernetes |
| 226 | + elif worker == "osism-kubernetes": |
115 | 227 | if locking: |
116 | 228 | lock.acquire() |
117 | 229 |
|
@@ -163,6 +275,16 @@ def run_ansible_in_environment( |
163 | 275 |
|
164 | 276 | rc = p.wait(timeout=60) |
165 | 277 |
|
| 278 | + # Log play execution result |
| 279 | + log_play_execution( |
| 280 | + request_id=request_id, |
| 281 | + worker=worker, |
| 282 | + environment=environment, |
| 283 | + role=role, |
| 284 | + hosts=None, # Host extraction would require inventory parsing |
| 285 | + result="success" if rc == 0 else "failure", |
| 286 | + ) |
| 287 | + |
166 | 288 | if publish: |
167 | 289 | utils.finish_task_output(request_id, rc=rc) |
168 | 290 |
|
|
0 commit comments