From c35a240ec3f7260d5c610e9ca21d946fd413b455 Mon Sep 17 00:00:00 2001 From: Tellsanguis Date: Thu, 27 Nov 2025 19:43:53 +0100 Subject: [PATCH] Ajout script gestion ressources LINSTOR - Ajouter script bash pour gestion automatique ressources LINSTOR - Documenter utilisation et options script - Inclure exemples cas d'usage --- manage_linstor_resources.py | 399 ++++++++++++++++++++++++++++++++++++ 1 file changed, 399 insertions(+) create mode 100644 manage_linstor_resources.py diff --git a/manage_linstor_resources.py b/manage_linstor_resources.py new file mode 100644 index 0000000..b24de0f --- /dev/null +++ b/manage_linstor_resources.py @@ -0,0 +1,399 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# Auteur: BENE Maël +# Version: 1.0 +# Date de création: 27-11-2025 +""" +Script de gestion des ressources DRBD Linstor pour les VMs K3s. +Exécuté avant le déploiement Terraform pour s'assurer que les ressources +de stockage DRBD sont créées et dimensionnées correctement. +""" + +import subprocess +import sys +import json +import argparse +import os +from typing import Dict, Optional, Tuple + + +# Configuration SSH et Linstor (via variables d'environnement) +SSH_KEY_PATH = os.environ.get("SSH_KEY_PATH") +LINSTOR_CONTROLLER_IP = os.environ.get("LINSTOR_CONTROLLER_IP", "192.168.100.30") +LINSTOR_CONTROLLER_USER = os.environ.get("LINSTOR_CONTROLLER_USER", "root") + +# Noms de ressources DRBD pour chaque VM +# Format attendu par Proxmox: vm-{VMID}-disk-0 +RESOURCE_NAMES = { + 1000: "vm-1000-disk-0", # acemagician - k3s-server-1 + 1001: "vm-1001-disk-0", # elitedesk - k3s-server-2 +} + +# Configuration des nœuds Proxmox +NODE_CONFIG = { + 1000: {"node": "acemagician", "vm_name": "k3s-server-1"}, + 1001: {"node": "elitedesk", "vm_name": "k3s-server-2"}, +} + + +def run_ssh_command(command: str, ssh_key: Optional[str] = None) -> Tuple[int, str, str]: + """ + Exécute une commande SSH sur le contrôleur Linstor. + + Args: + command: Commande à exécuter + ssh_key: Chemin vers la clé SSH privée (optionnel) + + Returns: + Tuple (code_retour, stdout, stderr) + """ + ssh_cmd = ["ssh", "-o", "StrictHostKeyChecking=no"] + + # Ajouter la clé SSH si spécifiée + if ssh_key: + ssh_cmd.extend(["-i", ssh_key]) + + ssh_cmd.extend([f"{LINSTOR_CONTROLLER_USER}@{LINSTOR_CONTROLLER_IP}", command]) + + try: + result = subprocess.run( + ssh_cmd, + capture_output=True, + text=True, + timeout=30 + ) + return result.returncode, result.stdout, result.stderr + except subprocess.TimeoutExpired: + return 1, "", "Timeout lors de l'exécution de la commande SSH" + except Exception as e: + return 1, "", f"Erreur lors de l'exécution SSH: {str(e)}" + + +def check_resource_exists(resource_name: str, ssh_key: Optional[str] = None) -> bool: + """ + Vérifie si une ressource DRBD existe. + + Args: + resource_name: Nom de la ressource à vérifier + ssh_key: Chemin vers la clé SSH privée (optionnel) + + Returns: + True si la ressource existe, False sinon + """ + # Méthode 1: Vérifier avec resource-definition list + returncode, stdout, stderr = run_ssh_command( + f"linstor resource-definition list", + ssh_key=ssh_key + ) + + if returncode == 0: + # Cherche le nom exact de la ressource dans la sortie + lines = stdout.strip().split('\n') + for line in lines: + # Ignore les lignes d'en-tête et de séparation + if line.startswith('+-') or line.startswith('| ResourceName'): + continue + # Cherche la ressource dans les lignes de données + if f"| {resource_name} " in line or f"|{resource_name}|" in line: + print(f" → Ressource trouvée dans la liste des définitions") + return True + + # Méthode 2: Vérifier avec volume-definition (si resource-definition existe, volume existe aussi) + returncode, stdout, stderr = run_ssh_command( + f"linstor volume-definition list --resource {resource_name}", + ssh_key=ssh_key + ) + + if returncode == 0 and stdout.strip() and "VolumeNr" in stdout: + print(f" → Volume trouvé pour la ressource") + return True + + print(f" → Ressource non trouvée") + return False + + +def get_resource_size(resource_name: str, ssh_key: Optional[str] = None) -> Optional[int]: + """ + Récupère la taille actuelle d'une ressource DRBD en GiB. + + Args: + resource_name: Nom de la ressource + ssh_key: Chemin vers la clé SSH privée (optionnel) + + Returns: + Taille en GiB ou None si erreur + """ + # Essayer d'abord avec machine-readable (JSON) + returncode, stdout, stderr = run_ssh_command( + f"linstor volume-definition list --resource {resource_name} --machine-readable", + ssh_key=ssh_key + ) + + if returncode == 0 and stdout.strip(): + try: + # Parse la sortie JSON de Linstor + data = json.loads(stdout) + if data and len(data) > 0: + volume_defs = data[0].get("volume_definitions", []) + if volume_defs and len(volume_defs) > 0: + # Taille en KiB, conversion en GiB + size_kib = volume_defs[0].get("size_kib", 0) + size_gib = size_kib // (1024 * 1024) + print(f" → Taille récupérée via JSON: {size_gib}GiB") + return size_gib + except (json.JSONDecodeError, KeyError, IndexError) as e: + print(f" ⚠ Erreur parsing JSON, essai avec format texte: {e}") + + # Fallback: parser la sortie texte normale + returncode, stdout, stderr = run_ssh_command( + f"linstor volume-definition list --resource {resource_name}", + ssh_key=ssh_key + ) + + if returncode == 0 and stdout.strip(): + lines = stdout.strip().split('\n') + for line in lines: + if '|' in line and 'GiB' in line and not line.startswith('| VolumeNr'): + # Extrait la taille en GiB + parts = [p.strip() for p in line.split('|')] + for part in parts: + if 'GiB' in part: + try: + size_str = part.replace('GiB', '').strip() + size_gib = int(float(size_str)) + print(f" → Taille récupérée via texte: {size_gib}GiB") + return size_gib + except ValueError: + continue + + print(f" ⚠ Impossible de récupérer la taille, sortie: {stdout[:200]}") + return None + + +def create_resource(resource_name: str, size_gib: int, nodes: list, ssh_key: Optional[str] = None) -> bool: + """ + Crée une nouvelle ressource DRBD avec réplication. + + Args: + resource_name: Nom de la ressource à créer + size_gib: Taille en GiB + nodes: Liste des nœuds pour la réplication + ssh_key: Chemin vers la clé SSH privée (optionnel) + + Returns: + True si succès, False sinon + """ + print(f"Création de la ressource {resource_name} avec {size_gib}GiB...") + + # 1. Créer la définition de ressource + print(f" [1/3] Création de la définition de ressource...") + returncode, stdout, stderr = run_ssh_command( + f"linstor resource-definition create {resource_name}", + ssh_key=ssh_key + ) + + if returncode != 0: + # Si la ressource existe déjà, ce n'est pas une erreur fatale + if "already exists" in stdout or "already exists" in stderr: + print(f" ⚠ La définition de ressource existe déjà, passage à l'étape suivante") + else: + print(f"Erreur lors de la création de la définition: {stderr}", file=sys.stderr) + if stdout: + print(f"Sortie standard: {stdout}", file=sys.stderr) + return False + else: + print(f" ✓ Définition de ressource créée") + + # 2. Créer la définition de volume + print(f" [2/3] Création de la définition de volume...") + returncode, stdout, stderr = run_ssh_command( + f"linstor volume-definition create {resource_name} {size_gib}GiB", + ssh_key=ssh_key + ) + + if returncode != 0: + # Si le volume existe déjà, ce n'est pas une erreur fatale + if "already exists" in stdout or "already exists" in stderr: + print(f" ⚠ La définition de volume existe déjà, passage à l'étape suivante") + else: + print(f"Erreur lors de la création du volume: {stderr}", file=sys.stderr) + if stdout: + print(f"Sortie standard: {stdout}", file=sys.stderr) + return False + else: + print(f" ✓ Définition de volume créée") + + # 3. Déployer la ressource sur les nœuds avec réplication + print(f" [3/3] Déploiement de la ressource sur les nœuds...") + deployed_count = 0 + for node in nodes: + print(f" → Déploiement sur {node}...") + returncode, stdout, stderr = run_ssh_command( + f"linstor resource create {node} {resource_name} --storage-pool linstor_storage --resource-group pve-rg", + ssh_key=ssh_key + ) + + if returncode != 0: + # Si la ressource existe déjà sur ce nœud, ce n'est pas une erreur + if "already exists" in stdout or "already exists" in stderr or "already deployed" in stdout: + print(f" ⚠ Ressource déjà déployée sur {node}") + deployed_count += 1 + else: + print(f"Erreur lors du déploiement sur {node}: {stderr}", file=sys.stderr) + if stdout: + print(f"Sortie standard: {stdout}", file=sys.stderr) + # Continue avec les autres nœuds même en cas d'erreur + continue + else: + print(f" ✓ Ressource déployée sur {node}") + deployed_count += 1 + + print(f"✓ Ressource {resource_name} déployée sur {deployed_count}/{len(nodes)} nœuds") + return True + + +def resize_resource(resource_name: str, new_size_gib: int, ssh_key: Optional[str] = None) -> bool: + """ + Augmente la taille d'une ressource DRBD existante. + + Args: + resource_name: Nom de la ressource à redimensionner + new_size_gib: Nouvelle taille en GiB + ssh_key: Chemin vers la clé SSH privée (optionnel) + + Returns: + True si succès, False sinon + """ + print(f"Redimensionnement de la ressource {resource_name} à {new_size_gib}GiB...") + + returncode, stdout, stderr = run_ssh_command( + f"linstor volume-definition set-size {resource_name} 0 {new_size_gib}GiB", + ssh_key=ssh_key + ) + + if returncode != 0: + print(f"Erreur lors du redimensionnement: {stderr}", file=sys.stderr) + return False + + print(f"✓ Ressource {resource_name} redimensionnée avec succès") + return True + + +def manage_vm_resource(vmid: int, size_gib: int, dry_run: bool = False, ssh_key: Optional[str] = None) -> bool: + """ + Gère la ressource DRBD pour une VM spécifique. + + Args: + vmid: ID de la VM + size_gib: Taille souhaitée en GiB + dry_run: Si True, affiche les actions sans les exécuter + ssh_key: Chemin vers la clé SSH privée (optionnel) + + Returns: + True si succès, False sinon + """ + if vmid not in RESOURCE_NAMES: + print(f"VMID {vmid} non configuré", file=sys.stderr) + return False + + resource_name = RESOURCE_NAMES[vmid] + node_info = NODE_CONFIG[vmid] + + print(f"\n{'='*60}") + print(f"Gestion de la ressource pour VM {vmid} ({node_info['vm_name']})") + print(f"Ressource DRBD: {resource_name}") + print(f"Nœud Proxmox: {node_info['node']}") + print(f"Taille souhaitée: {size_gib}GiB") + print(f"{'='*60}\n") + + resource_exists = check_resource_exists(resource_name, ssh_key=ssh_key) + + if not resource_exists: + print(f"La ressource {resource_name} n'existe pas.") + + if dry_run: + print(f"[DRY-RUN] Créerait la ressource {resource_name} avec {size_gib}GiB") + return True + + # Créer la ressource sur les 2 nœuds avec stockage (thinkpad = contrôleur uniquement) + nodes = ["acemagician", "elitedesk"] + return create_resource(resource_name, size_gib, nodes, ssh_key=ssh_key) + + else: + print(f"La ressource {resource_name} existe déjà.") + + current_size = get_resource_size(resource_name, ssh_key=ssh_key) + + if current_size is None: + print("⚠ Impossible de récupérer la taille actuelle") + print("La ressource existe mais le volume peut ne pas être complètement configuré") + print("Tentative de création/configuration du volume...") + + if dry_run: + print(f"[DRY-RUN] Tenterait de créer/configurer le volume avec {size_gib}GiB") + return True + + # Tente de créer le volume (sera ignoré s'il existe déjà) + nodes = ["acemagician", "elitedesk"] + return create_resource(resource_name, size_gib, nodes, ssh_key=ssh_key) + + print(f"Taille actuelle: {current_size}GiB") + + if current_size == size_gib: + print(f"✓ La taille correspond déjà ({size_gib}GiB), aucune action nécessaire") + return True + + elif current_size < size_gib: + print(f"La taille doit être augmentée de {current_size}GiB à {size_gib}GiB") + + if dry_run: + print(f"[DRY-RUN] Redimensionnerait {resource_name} à {size_gib}GiB") + return True + + return resize_resource(resource_name, size_gib, ssh_key=ssh_key) + + else: + print(f"⚠ La taille actuelle ({current_size}GiB) est supérieure à la taille souhaitée ({size_gib}GiB)") + print("La réduction de taille n'est pas supportée, conservation de la taille actuelle") + return True + + +def main(): + """Point d'entrée principal du script.""" + parser = argparse.ArgumentParser( + description="Gestion des ressources DRBD Linstor pour les VMs K3s" + ) + parser.add_argument( + "--vmid", + type=int, + required=True, + choices=[1000, 1001], + help="ID de la VM (1000=acemagician, 1001=elitedesk)" + ) + parser.add_argument( + "--size", + type=int, + required=True, + help="Taille du disque en GiB" + ) + parser.add_argument( + "--dry-run", + action="store_true", + help="Affiche les actions sans les exécuter" + ) + parser.add_argument( + "--ssh-key", + type=str, + default=SSH_KEY_PATH, + help="Chemin vers la clé SSH privée (défaut: variable d'environnement SSH_KEY_PATH)" + ) + + args = parser.parse_args() + + success = manage_vm_resource(args.vmid, args.size, args.dry_run, ssh_key=args.ssh_key) + + sys.exit(0 if success else 1) + + +if __name__ == "__main__": + main()