Batch 1 : configs plages horaires + blackout + check disponibilité dans delegate

- config/blackout_hours.json : plage maintenance nuit (02:00-05:00)
- config/reports_schedule.json : horaires sollicitation rapports par agent
- config/tasks_schedule.json : tâches planifiées (vide pour l'instant)
- agents_registry.json : ajout work_hours par agent
- delegate.py : vérification blackout + work_hours avant délégation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-08 15:28:24 +00:00
parent 9a55dafd17
commit a95cb0127a
5 changed files with 102 additions and 11 deletions
+12 -3
View File
@@ -3,18 +3,27 @@
"jid": "agent2_debian13@xmpp.ovh", "jid": "agent2_debian13@xmpp.ovh",
"mqtt_inbox": "agents/agent2_debian13/inbox", "mqtt_inbox": "agents/agent2_debian13/inbox",
"mqtt_outbox": "agents/agent1/inbox", "mqtt_outbox": "agents/agent1/inbox",
"speciality": "Administration Debian : apt, dpkg, systemd, conteneurs LXC/Docker, KVM, réseau, sécurité système" "speciality": "Administration Debian : apt, dpkg, systemd, conteneurs LXC/Docker, KVM, réseau, sécurité système",
"work_hours": { "start": "07:00", "end": "23:00", "days": ["mon","tue","wed","thu","fri","sat","sun"], "enabled": true }
}, },
"agent2_ansible": { "agent2_ansible": {
"jid": "agent2_ansible@xmpp.ovh", "jid": "agent2_ansible@xmpp.ovh",
"mqtt_inbox": "agents/agent2_ansible/inbox", "mqtt_inbox": "agents/agent2_ansible/inbox",
"mqtt_outbox": "agents/agent1/inbox", "mqtt_outbox": "agents/agent1/inbox",
"speciality": "Automatisation infrastructure via Ansible : playbooks, commandes ad-hoc, déploiement multi-hôtes, gestion de configuration sur le réseau local" "speciality": "Automatisation infrastructure via Ansible : playbooks, commandes ad-hoc, déploiement multi-hôtes, gestion de configuration sur le réseau local",
"work_hours": { "start": "07:00", "end": "23:00", "days": ["mon","tue","wed","thu","fri","sat","sun"], "enabled": true }
}, },
"agent2_deploy": { "agent2_deploy": {
"jid": "agent2_deploy@xmpp.ovh", "jid": "agent2_deploy@xmpp.ovh",
"mqtt_inbox": "agents/agent2_deploy/inbox", "mqtt_inbox": "agents/agent2_deploy/inbox",
"mqtt_outbox": "agents/agent1/inbox", "mqtt_outbox": "agents/agent1/inbox",
"speciality": "Déploiement d'agents : installe et configure d'autres agents sur des machines distantes ou locales via SSH" "speciality": "Déploiement d'agents : installe et configure d'autres agents sur des machines distantes ou locales via SSH",
"work_hours": { "start": "08:00", "end": "20:00", "days": ["mon","tue","wed","thu","fri"], "enabled": true }
},
"agent2_test": {
"jid": "agent2_test@xmpp.ovh",
"mqtt_inbox": "agents/agent2_test/inbox",
"mqtt_outbox": "agents/agent1/inbox",
"speciality": "Spécialiste Debian : apt, systemd, conteneurs LXC/Docker, KVM, réseau, sécurité"
} }
} }
+8
View File
@@ -0,0 +1,8 @@
[
{
"start": "02:00",
"end": "05:00",
"label": "maintenance nuit",
"enabled": true
}
]
+18
View File
@@ -0,0 +1,18 @@
{
"agents": {
"agent2_debian13": {
"report_time": "22:00",
"enabled": true
},
"agent2_ansible": {
"report_time": "22:05",
"enabled": true
},
"agent2_deploy": {
"report_time": "22:10",
"enabled": true
}
},
"daily_report_time": "22:30",
"daily_report_enabled": true
}
+3
View File
@@ -0,0 +1,3 @@
{
"tasks": []
}
+54 -1
View File
@@ -2,6 +2,7 @@
Skill : DELEGATE Skill : DELEGATE
Délègue une tâche à un agent spécialisé via MQTT et attend sa réponse. Délègue une tâche à un agent spécialisé via MQTT et attend sa réponse.
Log le résultat et notifie agent1 en cas d'erreur. Log le résultat et notifie agent1 en cas d'erreur.
Vérifie les plages horaires (work_hours) et le blackout avant délégation.
Commande : Commande :
DELEGATE: <agent_name> | <tâche> DELEGATE: <agent_name> | <tâche>
@@ -9,6 +10,7 @@ Commande :
import json import json
import time import time
import threading import threading
from datetime import datetime
from pathlib import Path from pathlib import Path
import paho.mqtt.client as mqtt import paho.mqtt.client as mqtt
@@ -17,9 +19,9 @@ TRIGGER = "DELEGATE:"
CONFIG_FILE = Path("/opt/agent/config/config.json") CONFIG_FILE = Path("/opt/agent/config/config.json")
REGISTRY_FILE = Path("/opt/agent/config/agents_registry.json") REGISTRY_FILE = Path("/opt/agent/config/agents_registry.json")
BLACKOUT_FILE = Path("/opt/agent/config/blackout_hours.json")
TIMEOUT = 120 TIMEOUT = 120
# Mots-clés indiquant une erreur dans la réponse d'un agent
ERROR_KEYWORDS = ["erreur", "error", "timeout", "échec", "failed", "cannot", "permission denied", ERROR_KEYWORDS = ["erreur", "error", "timeout", "échec", "failed", "cannot", "permission denied",
"command not found", "no such file", "connexion refusée"] "command not found", "no such file", "connexion refusée"]
@@ -34,6 +36,51 @@ def _is_error(result: str) -> bool:
return True return True
return any(kw in lower for kw in ERROR_KEYWORDS) return any(kw in lower for kw in ERROR_KEYWORDS)
def _check_availability(agent_name: str, registry: dict) -> tuple:
"""Retourne (disponible: bool, message: str)."""
now = datetime.now()
# Vérifier blackout_hours.json
try:
blackouts = json.loads(BLACKOUT_FILE.read_text())
for b in blackouts:
if not b.get("enabled", True):
continue
start = datetime.strptime(b["start"], "%H:%M").replace(
year=now.year, month=now.month, day=now.day)
end = datetime.strptime(b["end"], "%H:%M").replace(
year=now.year, month=now.month, day=now.day)
if start <= now <= end:
return False, "Blackout actif ({}) : {}-{}. Tâche non exécutée.".format(
b.get("label", "maintenance"), b["start"], b["end"])
except Exception:
pass
# Vérifier work_hours de l'agent
agent = registry.get(agent_name, {})
wh = agent.get("work_hours")
if wh and wh.get("enabled", True):
start_str = wh.get("start", "00:00")
end_str = wh.get("end", "23:59")
start = datetime.strptime(start_str, "%H:%M").replace(
year=now.year, month=now.month, day=now.day)
end = datetime.strptime(end_str, "%H:%M").replace(
year=now.year, month=now.month, day=now.day)
# Vérifier le jour
days = wh.get("days")
if days:
day_map = ["mon","tue","wed","thu","fri","sat","sun"]
if day_map[now.weekday()] not in days:
return False, "Agent {} ne travaille pas ce jour. Jours actifs : {}.".format(
agent_name, ", ".join(days))
if not (start <= now <= end):
return False, "Agent {} hors plage horaire ({}-{}).".format(
agent_name, start_str, end_str)
return True, ""
def _notify_error(host: str, port: int, agent: str, task: str, result: str): def _notify_error(host: str, port: int, agent: str, task: str, result: str):
"""Publie l'erreur sur le topic d'erreurs pour que agent1 notifie l'utilisateur.""" """Publie l'erreur sur le topic d'erreurs pour que agent1 notifie l'utilisateur."""
try: try:
@@ -62,6 +109,12 @@ def execute(args: str) -> str:
available = ", ".join(registry.keys()) available = ", ".join(registry.keys())
return "Agent inconnu : «{}». Agents disponibles : {}".format(agent_name, available) return "Agent inconnu : «{}». Agents disponibles : {}".format(agent_name, available)
# Vérifier disponibilité (plages horaires + blackout)
available, reason = _check_availability(agent_name, registry)
if not available:
print("[DELEGATE] {} indisponible : {}".format(agent_name, reason))
return "[INDISPONIBLE] {} : {}".format(agent_name, reason)
agent = registry[agent_name] agent = registry[agent_name]
inbox = agent["mqtt_inbox"] inbox = agent["mqtt_inbox"]
outbox = agent["mqtt_outbox"] outbox = agent["mqtt_outbox"]