2023-08-28 20:54:24 +00:00
|
|
|
#! /usr/bin/env python3
|
2023-09-28 04:30:48 +00:00
|
|
|
from argparse import ArgumentParser
|
2023-08-28 20:54:24 +00:00
|
|
|
from os import environ
|
|
|
|
from typing import Any
|
|
|
|
from typing import cast
|
|
|
|
|
|
|
|
import requests
|
|
|
|
|
|
|
|
|
|
|
|
NOMAD_ADDR = environ.get("NOMAD_ADDR", "http://127.0.0.1:4646")
|
|
|
|
NOMAD_TOKEN = environ.get("NOMAD_TOKEN")
|
|
|
|
|
|
|
|
|
|
|
|
def nomad_req(
|
|
|
|
*path: str, params: dict[str, Any] | None = None, method="GET"
|
|
|
|
) -> list[dict[str, Any]] | dict[str, Any] | str:
|
|
|
|
headers = {}
|
|
|
|
if NOMAD_TOKEN:
|
|
|
|
headers["X-Nomad-Token"] = NOMAD_TOKEN
|
|
|
|
|
|
|
|
response = requests.request(
|
|
|
|
method,
|
|
|
|
f"{NOMAD_ADDR}/v1/{'/'.join(path)}",
|
|
|
|
params=params,
|
|
|
|
headers=headers,
|
|
|
|
)
|
|
|
|
response.raise_for_status()
|
|
|
|
|
|
|
|
try:
|
|
|
|
return response.json()
|
|
|
|
except requests.exceptions.JSONDecodeError:
|
|
|
|
return response.text
|
|
|
|
|
|
|
|
|
2023-09-28 04:30:48 +00:00
|
|
|
def extract_job_services(job: dict[str, Any]) -> dict[str, str]:
|
|
|
|
services: dict[str, str] = dict()
|
2023-08-28 20:54:24 +00:00
|
|
|
for group in job["TaskGroups"]:
|
|
|
|
for service in group.get("Services") or []:
|
2023-09-28 04:30:48 +00:00
|
|
|
services[service["Name"]] = group["Name"]
|
2023-08-28 20:54:24 +00:00
|
|
|
for task in group["Tasks"]:
|
|
|
|
for service in task.get("Services") or []:
|
2023-09-28 04:30:48 +00:00
|
|
|
services[service["Name"]] = group["Name"]
|
2023-08-28 20:54:24 +00:00
|
|
|
|
|
|
|
return services
|
|
|
|
|
|
|
|
exit_code = 0
|
2023-09-28 04:30:48 +00:00
|
|
|
parser = ArgumentParser(
|
|
|
|
description="Checks for missing services and optionally restarts their allocs.",
|
|
|
|
)
|
|
|
|
parser.add_argument("-r", "--restart", action="store_true", help="Restart allocs for missing services")
|
|
|
|
args = parser.parse_args()
|
2023-08-28 20:54:24 +00:00
|
|
|
|
|
|
|
for job in nomad_req("jobs"):
|
|
|
|
job = cast(dict[str, Any], job)
|
|
|
|
|
|
|
|
if job["Type"] in ("batch", "sysbatch"):
|
|
|
|
continue
|
|
|
|
|
|
|
|
job_detail = nomad_req("job", job["ID"])
|
|
|
|
job_detail = cast(dict[str, Any], job_detail)
|
|
|
|
|
|
|
|
expected_services = extract_job_services(job_detail)
|
|
|
|
|
|
|
|
found_services: set[str] = set()
|
|
|
|
for service in nomad_req("job", job_detail["ID"], "services"):
|
|
|
|
service = cast(dict[str, Any], service)
|
|
|
|
found_services.add(service["ServiceName"])
|
|
|
|
|
2023-09-28 04:30:48 +00:00
|
|
|
missing_services = set(expected_services) - found_services
|
|
|
|
restart_groups: set[str] = set()
|
2023-08-28 20:54:24 +00:00
|
|
|
for missing_service in missing_services:
|
|
|
|
print(f"ERROR: Missing service {missing_service} for job {job_detail['Name']}")
|
2023-09-28 04:30:48 +00:00
|
|
|
# print(job)
|
2023-08-28 20:54:24 +00:00
|
|
|
exit_code = 1
|
|
|
|
|
2023-09-28 04:30:48 +00:00
|
|
|
# Add group associated with missing service to set
|
|
|
|
restart_groups.add(expected_services[missing_service])
|
|
|
|
|
|
|
|
if not restart_groups or not args.restart:
|
|
|
|
continue
|
|
|
|
|
|
|
|
# Get allocts for groups that are missing services
|
|
|
|
restart_allocs: set[str] = set()
|
|
|
|
for allocation in nomad_req("job", job_detail["ID"], "allocations"):
|
|
|
|
allocation = cast(dict[str, Any], allocation)
|
|
|
|
if allocation["TaskGroup"] in restart_groups:
|
|
|
|
restart_allocs.add(allocation["ID"])
|
|
|
|
|
|
|
|
# Restart allocs associated with missing services
|
|
|
|
for allocation in restart_allocs:
|
|
|
|
print(f"INFO: Restarting allocation {allocation}")
|
|
|
|
nomad_req("client", "allocation", allocation, "restart")
|
|
|
|
|
2023-08-28 20:54:24 +00:00
|
|
|
|
|
|
|
exit(exit_code)
|