- Split quickwin services: prereq, snapshot, log services - Add referentiel router and template - QuickWin detail: prereq/snapshot terminal divs for production - Server edit partial updates - QuickWin correspondance and logs templates - Base template updates Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
145 lines
4.9 KiB
Python
145 lines
4.9 KiB
Python
"""Service snapshot QuickWin — prise de snapshots VM via vSphere/pyvmomi
|
|
Ordre de recherche des VM sur les vCenters:
|
|
- Hors-prod: Senlis (vpgesavcs1) → Nanterre (vpmetavcs1) → DR (vpsicavcs1)
|
|
- Prod: Nanterre (vpmetavcs1) → Senlis (vpgesavcs1) → DR (vpsicavcs1)
|
|
Physiques: pas de snapshot, alerte Commvault."""
|
|
import ssl
|
|
import logging
|
|
from datetime import datetime
|
|
|
|
log = logging.getLogger("quickwin.snapshot")
|
|
|
|
try:
|
|
from pyVim.connect import SmartConnect, Disconnect
|
|
from pyVmomi import vim
|
|
PYVMOMI_OK = True
|
|
except ImportError:
|
|
PYVMOMI_OK = False
|
|
log.warning("pyvmomi non disponible — snapshots impossibles")
|
|
|
|
|
|
def _get_secret(db, key):
|
|
try:
|
|
from ..services.secrets_service import get_secret
|
|
return get_secret(db, key)
|
|
except Exception:
|
|
return None
|
|
|
|
|
|
def _connect_vcenter(endpoint, user, password):
|
|
"""Connexion a un vCenter. Retourne un ServiceInstance ou None."""
|
|
try:
|
|
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
|
ctx.check_hostname = False
|
|
ctx.verify_mode = ssl.CERT_NONE
|
|
si = SmartConnect(host=endpoint, user=user, pwd=password, sslContext=ctx)
|
|
return si
|
|
except Exception as e:
|
|
log.warning(f"Connexion vCenter {endpoint} echouee: {e}")
|
|
return None
|
|
|
|
|
|
def _find_vm(si, vm_name):
|
|
"""Cherche une VM par nom dans le vCenter. Retourne l'objet VM ou None."""
|
|
content = si.RetrieveContent()
|
|
container = content.viewManager.CreateContainerView(
|
|
content.rootFolder, [vim.VirtualMachine], True)
|
|
try:
|
|
for vm in container.view:
|
|
if vm.name.lower() == vm_name.lower():
|
|
return vm
|
|
finally:
|
|
container.Destroy()
|
|
return None
|
|
|
|
|
|
def _take_snapshot(vm, snap_name, description=""):
|
|
"""Prend un snapshot de la VM. Retourne (ok, message)."""
|
|
try:
|
|
task = vm.CreateSnapshot_Task(
|
|
name=snap_name,
|
|
description=description,
|
|
memory=False,
|
|
quiesce=True,
|
|
)
|
|
# Attendre la fin du task
|
|
while task.info.state in (vim.TaskInfo.State.queued, vim.TaskInfo.State.running):
|
|
import time
|
|
time.sleep(2)
|
|
if task.info.state == vim.TaskInfo.State.success:
|
|
return True, "Snapshot OK"
|
|
else:
|
|
err = str(task.info.error) if task.info.error else "Echec inconnu"
|
|
return False, f"Snapshot echoue: {err}"
|
|
except Exception as e:
|
|
return False, f"Erreur snapshot: {e}"
|
|
|
|
|
|
def get_vcenter_order(db, branch):
|
|
"""Retourne la liste ordonnee des vCenters selon la branche.
|
|
hprod: Senlis → Nanterre → DR
|
|
prod: Nanterre → Senlis → DR"""
|
|
from sqlalchemy import text
|
|
vcenters = db.execute(text(
|
|
"SELECT id, name, endpoint FROM vcenters WHERE is_active = true ORDER BY id"
|
|
)).fetchall()
|
|
|
|
vc_map = {}
|
|
for vc in vcenters:
|
|
ep = vc.endpoint.lower()
|
|
if "vpgesavcs1" in ep:
|
|
vc_map["senlis"] = vc
|
|
elif "vpmetavcs1" in ep:
|
|
vc_map["nanterre"] = vc
|
|
elif "vpsicavcs1" in ep:
|
|
vc_map["dr"] = vc
|
|
else:
|
|
vc_map.setdefault("other", []).append(vc)
|
|
|
|
if branch == "prod":
|
|
order = [vc_map.get("nanterre"), vc_map.get("senlis"), vc_map.get("dr")]
|
|
else:
|
|
order = [vc_map.get("senlis"), vc_map.get("nanterre"), vc_map.get("dr")]
|
|
|
|
return [v for v in order if v is not None]
|
|
|
|
|
|
def snapshot_server(hostname, vm_name, branch, db, snap_name=None):
|
|
"""Prend un snapshot pour un serveur.
|
|
Cherche la VM sur les vCenters dans l'ordre selon la branche.
|
|
Retourne dict: {ok, vcenter, detail, skipped}"""
|
|
if not PYVMOMI_OK:
|
|
return {"ok": False, "vcenter": "", "detail": "pyvmomi non installe", "skipped": True}
|
|
|
|
vc_user = _get_secret(db, "vcenter_user")
|
|
vc_pass = _get_secret(db, "vcenter_pass")
|
|
if not vc_user or not vc_pass:
|
|
return {"ok": False, "vcenter": "", "detail": "Credentials vCenter manquants (vcenter_user/vcenter_pass dans Settings > Secrets)", "skipped": True}
|
|
|
|
search_name = vm_name or hostname
|
|
if not snap_name:
|
|
snap_name = f"QW_{datetime.now().strftime('%Y%m%d_%H%M')}"
|
|
|
|
vcenters = get_vcenter_order(db, branch)
|
|
if not vcenters:
|
|
return {"ok": False, "vcenter": "", "detail": "Aucun vCenter actif configure", "skipped": True}
|
|
|
|
for vc in vcenters:
|
|
si = _connect_vcenter(vc.endpoint, vc_user, vc_pass)
|
|
if not si:
|
|
continue
|
|
try:
|
|
vm = _find_vm(si, search_name)
|
|
if vm:
|
|
ok, msg = _take_snapshot(vm, snap_name,
|
|
description=f"QuickWin auto-snapshot {hostname}")
|
|
return {"ok": ok, "vcenter": vc.name, "detail": msg}
|
|
finally:
|
|
try:
|
|
Disconnect(si)
|
|
except Exception:
|
|
pass
|
|
|
|
tried = ", ".join(vc.name for vc in vcenters)
|
|
return {"ok": False, "vcenter": "", "detail": f"VM '{search_name}' non trouvee sur: {tried}"}
|