feat(cicd): Use Proxmox API instead of SSH for LINSTOR management
Some checks failed
CD - Deploy Infrastructure / Terraform Validation (push) Successful in 16s
CD - Deploy Infrastructure / Deploy on pve1 (push) Failing after 16s
CD - Deploy Infrastructure / Deploy on pve2 (push) Failing after 14s
CD - Deploy Infrastructure / Deploy on pve3 (push) Successful in 1m56s
CD - Deploy Infrastructure / Validate K3s Cluster (push) Has been skipped
CD - Deploy Infrastructure / Deployment Notification (push) Failing after 1s
Some checks failed
CD - Deploy Infrastructure / Terraform Validation (push) Successful in 16s
CD - Deploy Infrastructure / Deploy on pve1 (push) Failing after 16s
CD - Deploy Infrastructure / Deploy on pve2 (push) Failing after 14s
CD - Deploy Infrastructure / Deploy on pve3 (push) Successful in 1m56s
CD - Deploy Infrastructure / Validate K3s Cluster (push) Has been skipped
CD - Deploy Infrastructure / Deployment Notification (push) Failing after 1s
Version 2.0 du script de gestion LINSTOR Changements majeurs: - Remplace les commandes SSH/LINSTOR par l'API Proxmox REST - Ajoute une classe ProxmoxAPI pour gérer les appels API - Utilise les endpoints /cluster/linstor/* de l'API Proxmox - Installe les dépendances Python (requests, urllib3) dans le pipeline - Passe les credentials API via variables d'environnement/secrets - Plus sécurisé: pas besoin de clés SSH, utilise les tokens API existants - Support des certificats auto-signés (verify_ssl=False) Auteur: BENE Maël
This commit is contained in:
parent
287410732f
commit
4628fc266f
2 changed files with 205 additions and 134 deletions
|
|
@ -81,14 +81,19 @@ jobs:
|
||||||
if ! command -v tofu &> /dev/null; then
|
if ! command -v tofu &> /dev/null; then
|
||||||
curl -fsSL https://get.opentofu.org/install-opentofu.sh | bash -s -- --install-method standalone --opentofu-version 1.10.7
|
curl -fsSL https://get.opentofu.org/install-opentofu.sh | bash -s -- --install-method standalone --opentofu-version 1.10.7
|
||||||
fi
|
fi
|
||||||
- name: Setup Python
|
- name: Setup Python and dependencies
|
||||||
run: |
|
run: |
|
||||||
apt-get update && apt-get install -y python3
|
apt-get update && apt-get install -y python3 python3-pip
|
||||||
|
pip3 install --break-system-packages requests urllib3
|
||||||
- name: Prepare LINSTOR resources for pve1
|
- name: Prepare LINSTOR resources for pve1
|
||||||
run: |
|
run: |
|
||||||
# Exécute le script dans le container, qui utilisera SSH pour communiquer avec LINSTOR
|
# Exécute le script dans le container, qui utilisera l'API Proxmox pour gérer LINSTOR
|
||||||
# Utilise l'IP au lieu du hostname car le container Docker ne peut pas résoudre les noms locaux
|
python3 scripts/manage_linstor_resources.py \
|
||||||
python3 scripts/manage_linstor_resources.py --terraform-dir terraform --remote-host 192.168.100.30 --verbose
|
--terraform-dir terraform \
|
||||||
|
--api-url "${{ secrets.PROXMOX_API_URL || 'https://192.168.100.10:8006/api2/json' }}" \
|
||||||
|
--token-id "${{ secrets.PROXMOX_TOKEN_ID }}" \
|
||||||
|
--token-secret "${{ secrets.PROXMOX_TOKEN_SECRET }}" \
|
||||||
|
--verbose
|
||||||
- name: Terraform Apply on pve1
|
- name: Terraform Apply on pve1
|
||||||
run: |
|
run: |
|
||||||
cd terraform/pve1
|
cd terraform/pve1
|
||||||
|
|
@ -124,14 +129,19 @@ jobs:
|
||||||
if ! command -v tofu &> /dev/null; then
|
if ! command -v tofu &> /dev/null; then
|
||||||
curl -fsSL https://get.opentofu.org/install-opentofu.sh | bash -s -- --install-method standalone --opentofu-version 1.10.7
|
curl -fsSL https://get.opentofu.org/install-opentofu.sh | bash -s -- --install-method standalone --opentofu-version 1.10.7
|
||||||
fi
|
fi
|
||||||
- name: Setup Python
|
- name: Setup Python and dependencies
|
||||||
run: |
|
run: |
|
||||||
apt-get update && apt-get install -y python3
|
apt-get update && apt-get install -y python3 python3-pip
|
||||||
|
pip3 install --break-system-packages requests urllib3
|
||||||
- name: Prepare LINSTOR resources for pve2
|
- name: Prepare LINSTOR resources for pve2
|
||||||
run: |
|
run: |
|
||||||
# Exécute le script dans le container, qui utilisera SSH pour communiquer avec LINSTOR
|
# Exécute le script dans le container, qui utilisera l'API Proxmox pour gérer LINSTOR
|
||||||
# Utilise l'IP au lieu du hostname car le container Docker ne peut pas résoudre les noms locaux
|
python3 scripts/manage_linstor_resources.py \
|
||||||
python3 scripts/manage_linstor_resources.py --terraform-dir terraform --remote-host 192.168.100.30 --verbose
|
--terraform-dir terraform \
|
||||||
|
--api-url "${{ secrets.PROXMOX_API_URL || 'https://192.168.100.10:8006/api2/json' }}" \
|
||||||
|
--token-id "${{ secrets.PROXMOX_TOKEN_ID }}" \
|
||||||
|
--token-secret "${{ secrets.PROXMOX_TOKEN_SECRET }}" \
|
||||||
|
--verbose
|
||||||
- name: Terraform Apply on pve2
|
- name: Terraform Apply on pve2
|
||||||
run: |
|
run: |
|
||||||
cd terraform/pve2
|
cd terraform/pve2
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,35 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
Script de Gestion des Ressources LINSTOR pour Proxmox
|
Script de Gestion des Ressources LINSTOR pour Proxmox via API
|
||||||
|
|
||||||
Auteur: BENE Maël
|
Auteur: BENE Maël
|
||||||
Version: 1.0
|
Version: 2.0
|
||||||
Date: 2025-11-27
|
Date: 2025-11-27
|
||||||
|
|
||||||
Description:
|
Description:
|
||||||
Ce script gère automatiquement les ressources LINSTOR pour les VMs Proxmox.
|
Ce script gère automatiquement les ressources LINSTOR pour les VMs Proxmox
|
||||||
|
en utilisant l'API Proxmox au lieu de commandes SSH directes.
|
||||||
Il assure que les ressources existent avec la taille correcte avant le déploiement.
|
Il assure que les ressources existent avec la taille correcte avant le déploiement.
|
||||||
|
|
||||||
Fonctionnalités:
|
Fonctionnalités:
|
||||||
- Vérifie l'existence d'une ressource
|
- Vérifie l'existence d'une ressource via API Proxmox
|
||||||
- Crée la ressource si elle n'existe pas
|
- Crée la ressource si elle n'existe pas
|
||||||
- Redimensionne la ressource si la taille ne correspond pas (uniquement augmentation)
|
- Redimensionne la ressource si la taille ne correspond pas (uniquement augmentation)
|
||||||
- Opérations idempotentes (peut être exécuté plusieurs fois en toute sécurité)
|
- Opérations idempotentes (peut être exécuté plusieurs fois en toute sécurité)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import subprocess
|
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
import argparse
|
import argparse
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
|
import urllib3
|
||||||
|
import requests
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Désactive les avertissements SSL pour les certificats auto-signés
|
||||||
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||||
|
|
||||||
|
|
||||||
# Configuration des ressources par défaut
|
# Configuration des ressources par défaut
|
||||||
# Ces valeurs peuvent être modifiées selon vos besoins
|
# Ces valeurs peuvent être modifiées selon vos besoins
|
||||||
|
|
@ -47,50 +52,119 @@ RESOURCE_CONFIG = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class LinstorManager:
|
class ProxmoxAPI:
|
||||||
"""Gestionnaire des ressources LINSTOR"""
|
"""Client pour l'API Proxmox"""
|
||||||
|
|
||||||
def __init__(self, verbose=False, remote_host=None):
|
def __init__(self, api_url, token_id, token_secret, verify_ssl=False, verbose=False):
|
||||||
|
self.api_url = api_url.rstrip('/')
|
||||||
|
self.token_id = token_id
|
||||||
|
self.token_secret = token_secret
|
||||||
|
self.verify_ssl = verify_ssl
|
||||||
self.verbose = verbose
|
self.verbose = verbose
|
||||||
self.remote_host = remote_host # Hôte sur lequel exécuter les commandes LINSTOR via SSH
|
self.headers = {
|
||||||
|
'Authorization': f'PVEAPIToken={token_id}={token_secret}'
|
||||||
|
}
|
||||||
|
|
||||||
def log(self, message):
|
def log(self, message):
|
||||||
"""Affiche un message de log si le mode verbose est activé"""
|
"""Affiche un message de log si le mode verbose est activé"""
|
||||||
if self.verbose:
|
if self.verbose:
|
||||||
print(f"[INFO] {message}")
|
print(f"[INFO] {message}")
|
||||||
|
|
||||||
def run_command(self, command, remote_host=None):
|
def _request(self, method, endpoint, data=None):
|
||||||
"""Exécute une commande shell et retourne la sortie
|
"""Effectue une requête HTTP vers l'API Proxmox"""
|
||||||
|
url = f"{self.api_url}{endpoint}"
|
||||||
Args:
|
self.log(f"{method} {endpoint}")
|
||||||
command: Liste de commandes à exécuter
|
|
||||||
remote_host: Si spécifié, exécute la commande via SSH sur cet hôte
|
|
||||||
"""
|
|
||||||
if remote_host:
|
|
||||||
# Construit la commande SSH
|
|
||||||
ssh_command = ['ssh', f'root@{remote_host}'] + command
|
|
||||||
self.log(f"Exécution SSH sur {remote_host}: {' '.join(command)}")
|
|
||||||
command = ssh_command
|
|
||||||
else:
|
|
||||||
self.log(f"Exécution locale: {' '.join(command)}")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(
|
if method == 'GET':
|
||||||
command,
|
response = requests.get(url, headers=self.headers, verify=self.verify_ssl)
|
||||||
capture_output=True,
|
elif method == 'POST':
|
||||||
text=True,
|
response = requests.post(url, headers=self.headers, data=data, verify=self.verify_ssl)
|
||||||
check=True
|
elif method == 'PUT':
|
||||||
)
|
response = requests.put(url, headers=self.headers, data=data, verify=self.verify_ssl)
|
||||||
return result.stdout.strip()
|
elif method == 'DELETE':
|
||||||
except subprocess.CalledProcessError as e:
|
response = requests.delete(url, headers=self.headers, verify=self.verify_ssl)
|
||||||
print(f"[ERREUR] Échec de la commande: {' '.join(command)}", file=sys.stderr)
|
else:
|
||||||
print(f"[ERREUR] Code de sortie: {e.returncode}", file=sys.stderr)
|
raise ValueError(f"Méthode HTTP non supportée: {method}")
|
||||||
print(f"[ERREUR] Stdout: {e.stdout}", file=sys.stderr)
|
|
||||||
print(f"[ERREUR] Stderr: {e.stderr}", file=sys.stderr)
|
response.raise_for_status()
|
||||||
|
return response.json()
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"[ERREUR] Requête API échouée: {e}", file=sys.stderr)
|
||||||
|
if hasattr(e.response, 'text'):
|
||||||
|
print(f"[ERREUR] Réponse: {e.response.text}", file=sys.stderr)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def linstor_resource_definitions_list(self):
|
||||||
|
"""Liste toutes les définitions de ressources LINSTOR"""
|
||||||
|
result = self._request('GET', '/cluster/linstor/resource-definitions')
|
||||||
|
if result and 'data' in result:
|
||||||
|
return result['data']
|
||||||
|
return []
|
||||||
|
|
||||||
|
def linstor_resource_definition_create(self, resource_name):
|
||||||
|
"""Crée une définition de ressource LINSTOR"""
|
||||||
|
data = {'id': resource_name}
|
||||||
|
result = self._request('POST', '/cluster/linstor/resource-definitions', data)
|
||||||
|
return result is not None
|
||||||
|
|
||||||
|
def linstor_volume_definitions_list(self, resource_name):
|
||||||
|
"""Liste les définitions de volumes pour une ressource"""
|
||||||
|
result = self._request('GET', f'/cluster/linstor/resource-definitions/{resource_name}/volumes')
|
||||||
|
if result and 'data' in result:
|
||||||
|
return result['data']
|
||||||
|
return []
|
||||||
|
|
||||||
|
def linstor_volume_definition_create(self, resource_name, size_kib):
|
||||||
|
"""Crée une définition de volume LINSTOR"""
|
||||||
|
data = {
|
||||||
|
'volume-number': '0',
|
||||||
|
'size-kib': str(size_kib)
|
||||||
|
}
|
||||||
|
result = self._request('POST', f'/cluster/linstor/resource-definitions/{resource_name}/volumes', data)
|
||||||
|
return result is not None
|
||||||
|
|
||||||
|
def linstor_volume_definition_resize(self, resource_name, volume_number, size_kib):
|
||||||
|
"""Redimensionne un volume LINSTOR"""
|
||||||
|
data = {'size-kib': str(size_kib)}
|
||||||
|
result = self._request('PUT', f'/cluster/linstor/resource-definitions/{resource_name}/volumes/{volume_number}', data)
|
||||||
|
return result is not None
|
||||||
|
|
||||||
|
def linstor_resources_list(self, resource_name=None):
|
||||||
|
"""Liste les ressources LINSTOR"""
|
||||||
|
if resource_name:
|
||||||
|
result = self._request('GET', f'/cluster/linstor/resources/{resource_name}')
|
||||||
|
else:
|
||||||
|
result = self._request('GET', '/cluster/linstor/resources')
|
||||||
|
|
||||||
|
if result and 'data' in result:
|
||||||
|
return result['data']
|
||||||
|
return []
|
||||||
|
|
||||||
|
def linstor_resource_create(self, node, resource_name, storage_pool):
|
||||||
|
"""Crée une ressource LINSTOR sur un nœud"""
|
||||||
|
data = {
|
||||||
|
'node': node,
|
||||||
|
'storage-pool': storage_pool
|
||||||
|
}
|
||||||
|
result = self._request('POST', f'/cluster/linstor/resources/{resource_name}', data)
|
||||||
|
return result is not None
|
||||||
|
|
||||||
|
|
||||||
|
class LinstorManager:
|
||||||
|
"""Gestionnaire des ressources LINSTOR via API Proxmox"""
|
||||||
|
|
||||||
|
def __init__(self, proxmox_api, verbose=False):
|
||||||
|
self.api = proxmox_api
|
||||||
|
self.verbose = verbose
|
||||||
|
|
||||||
|
def log(self, message):
|
||||||
|
"""Affiche un message de log si le mode verbose est activé"""
|
||||||
|
if self.verbose:
|
||||||
|
print(f"[INFO] {message}")
|
||||||
|
|
||||||
def parse_size(self, size_str):
|
def parse_size(self, size_str):
|
||||||
"""Convertit une chaîne de taille (ex: '100G', '1024M') en octets"""
|
"""Convertit une chaîne de taille (ex: '100G', '1024M') en KiB"""
|
||||||
size_str = size_str.strip().upper()
|
size_str = size_str.strip().upper()
|
||||||
|
|
||||||
# Correspond au nombre et à l'unité
|
# Correspond au nombre et à l'unité
|
||||||
|
|
@ -101,18 +175,20 @@ class LinstorManager:
|
||||||
number, unit = match.groups()
|
number, unit = match.groups()
|
||||||
number = float(number)
|
number = float(number)
|
||||||
|
|
||||||
|
# Convertit en KiB
|
||||||
multipliers = {
|
multipliers = {
|
||||||
'': 1,
|
'': 1 / 1024, # Bytes vers KiB
|
||||||
'K': 1024,
|
'K': 1, # KiB
|
||||||
'M': 1024**2,
|
'M': 1024, # MiB vers KiB
|
||||||
'G': 1024**3,
|
'G': 1024**2, # GiB vers KiB
|
||||||
'T': 1024**4,
|
'T': 1024**3, # TiB vers KiB
|
||||||
}
|
}
|
||||||
|
|
||||||
return int(number * multipliers.get(unit, 1))
|
return int(number * multipliers.get(unit, 1))
|
||||||
|
|
||||||
def format_size(self, bytes_value):
|
def format_size(self, kib_value):
|
||||||
"""Formate les octets en taille lisible"""
|
"""Formate les KiB en taille lisible"""
|
||||||
|
bytes_value = kib_value * 1024
|
||||||
for unit in ['', 'K', 'M', 'G', 'T']:
|
for unit in ['', 'K', 'M', 'G', 'T']:
|
||||||
if bytes_value < 1024.0:
|
if bytes_value < 1024.0:
|
||||||
return f"{bytes_value:.0f}{unit}iB" if unit else f"{bytes_value:.0f}B"
|
return f"{bytes_value:.0f}{unit}iB" if unit else f"{bytes_value:.0f}B"
|
||||||
|
|
@ -121,103 +197,66 @@ class LinstorManager:
|
||||||
|
|
||||||
def resource_exists(self, resource_name):
|
def resource_exists(self, resource_name):
|
||||||
"""Vérifie si une définition de ressource LINSTOR existe"""
|
"""Vérifie si une définition de ressource LINSTOR existe"""
|
||||||
output = self.run_command(['linstor', 'resource-definition', 'list', '--machine-readable'], self.remote_host)
|
resources = self.api.linstor_resource_definitions_list()
|
||||||
if output is None:
|
for res in resources:
|
||||||
return False
|
if res.get('name') == resource_name:
|
||||||
|
|
||||||
try:
|
|
||||||
data = json.loads(output)
|
|
||||||
for item in data:
|
|
||||||
if isinstance(item, dict) and item.get('name') == resource_name:
|
|
||||||
self.log(f"La définition de ressource '{resource_name}' existe")
|
self.log(f"La définition de ressource '{resource_name}' existe")
|
||||||
return True
|
return True
|
||||||
except json.JSONDecodeError:
|
|
||||||
self.log("Échec de l'analyse de la sortie resource-definition list")
|
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_resource_size(self, resource_name):
|
def get_resource_size(self, resource_name):
|
||||||
"""Récupère la taille actuelle d'un volume de ressource (en octets)"""
|
"""Récupère la taille actuelle d'un volume de ressource (en KiB)"""
|
||||||
output = self.run_command(['linstor', 'volume-definition', 'list', '--machine-readable'], self.remote_host)
|
volumes = self.api.linstor_volume_definitions_list(resource_name)
|
||||||
if output is None:
|
for vol in volumes:
|
||||||
return None
|
if vol.get('volume_number') == 0 or vol.get('volume-number') == 0:
|
||||||
|
size_kib = vol.get('size_kib') or vol.get('size-kib', 0)
|
||||||
try:
|
self.log(f"Taille actuelle de '{resource_name}': {self.format_size(size_kib)}")
|
||||||
data = json.loads(output)
|
return size_kib
|
||||||
for item in data:
|
|
||||||
if isinstance(item, dict):
|
|
||||||
if item.get('resource_name') == resource_name and item.get('volume_number') == 0:
|
|
||||||
# La taille est en KiB dans LINSTOR
|
|
||||||
size_kib = item.get('size_kib', 0)
|
|
||||||
size_bytes = size_kib * 1024
|
|
||||||
self.log(f"Taille actuelle de '{resource_name}': {self.format_size(size_bytes)}")
|
|
||||||
return size_bytes
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
self.log("Échec de l'analyse de la sortie volume-definition list")
|
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_resource_nodes(self, resource_name):
|
def get_resource_nodes(self, resource_name):
|
||||||
"""Récupère la liste des nœuds où la ressource est déployée"""
|
"""Récupère la liste des nœuds où la ressource est déployée"""
|
||||||
output = self.run_command(['linstor', 'resource', 'list', '--machine-readable'], self.remote_host)
|
resources = self.api.linstor_resources_list(resource_name)
|
||||||
if output is None:
|
|
||||||
return []
|
|
||||||
|
|
||||||
nodes = set()
|
nodes = set()
|
||||||
try:
|
for res in resources:
|
||||||
data = json.loads(output)
|
node = res.get('node_name') or res.get('node-name')
|
||||||
for item in data:
|
|
||||||
if isinstance(item, dict) and item.get('name') == resource_name:
|
|
||||||
node = item.get('node_name')
|
|
||||||
if node:
|
if node:
|
||||||
nodes.add(node)
|
nodes.add(node)
|
||||||
except json.JSONDecodeError:
|
|
||||||
self.log("Échec de l'analyse de la sortie resource list")
|
|
||||||
|
|
||||||
return list(nodes)
|
return list(nodes)
|
||||||
|
|
||||||
def create_resource_definition(self, resource_name):
|
def create_resource_definition(self, resource_name):
|
||||||
"""Crée une définition de ressource LINSTOR"""
|
"""Crée une définition de ressource LINSTOR"""
|
||||||
self.log(f"Création de la définition de ressource '{resource_name}'")
|
self.log(f"Création de la définition de ressource '{resource_name}'")
|
||||||
output = self.run_command(['linstor', 'resource-definition', 'create', resource_name], self.remote_host)
|
if self.api.linstor_resource_definition_create(resource_name):
|
||||||
if output is None:
|
|
||||||
return False
|
|
||||||
print(f"✓ Définition de ressource '{resource_name}' créée")
|
print(f"✓ Définition de ressource '{resource_name}' créée")
|
||||||
return True
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def create_volume_definition(self, resource_name, size):
|
def create_volume_definition(self, resource_name, size):
|
||||||
"""Crée une définition de volume LINSTOR"""
|
"""Crée une définition de volume LINSTOR"""
|
||||||
|
size_kib = self.parse_size(size)
|
||||||
self.log(f"Création de la définition de volume pour '{resource_name}' avec taille {size}")
|
self.log(f"Création de la définition de volume pour '{resource_name}' avec taille {size}")
|
||||||
output = self.run_command(['linstor', 'volume-definition', 'create', resource_name, size], self.remote_host)
|
if self.api.linstor_volume_definition_create(resource_name, size_kib):
|
||||||
if output is None:
|
|
||||||
return False
|
|
||||||
print(f"✓ Définition de volume créée pour '{resource_name}' avec taille {size}")
|
print(f"✓ Définition de volume créée pour '{resource_name}' avec taille {size}")
|
||||||
return True
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def create_resource(self, node, resource_name, storage_pool):
|
def create_resource(self, node, resource_name, storage_pool):
|
||||||
"""Crée une ressource LINSTOR sur un nœud spécifique"""
|
"""Crée une ressource LINSTOR sur un nœud spécifique"""
|
||||||
self.log(f"Création de la ressource '{resource_name}' sur le nœud '{node}' avec le pool de stockage '{storage_pool}'")
|
self.log(f"Création de la ressource '{resource_name}' sur le nœud '{node}' avec le pool de stockage '{storage_pool}'")
|
||||||
output = self.run_command([
|
if self.api.linstor_resource_create(node, resource_name, storage_pool):
|
||||||
'linstor', 'resource', 'create',
|
|
||||||
node, resource_name,
|
|
||||||
'--storage-pool', storage_pool
|
|
||||||
], self.remote_host)
|
|
||||||
if output is None:
|
|
||||||
return False
|
|
||||||
print(f"✓ Ressource '{resource_name}' créée sur le nœud '{node}'")
|
print(f"✓ Ressource '{resource_name}' créée sur le nœud '{node}'")
|
||||||
return True
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def resize_volume(self, resource_name, new_size):
|
def resize_volume(self, resource_name, new_size):
|
||||||
"""Redimensionne un volume LINSTOR (uniquement augmentation)"""
|
"""Redimensionne un volume LINSTOR (uniquement augmentation)"""
|
||||||
|
new_size_kib = self.parse_size(new_size)
|
||||||
self.log(f"Redimensionnement du volume '{resource_name}' à {new_size}")
|
self.log(f"Redimensionnement du volume '{resource_name}' à {new_size}")
|
||||||
output = self.run_command([
|
if self.api.linstor_volume_definition_resize(resource_name, 0, new_size_kib):
|
||||||
'linstor', 'volume-definition', 'set-size',
|
|
||||||
resource_name, '0', new_size
|
|
||||||
], self.remote_host)
|
|
||||||
if output is None:
|
|
||||||
return False
|
|
||||||
print(f"✓ Volume '{resource_name}' redimensionné à {new_size}")
|
print(f"✓ Volume '{resource_name}' redimensionné à {new_size}")
|
||||||
return True
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def ensure_resource(self, resource_name, node, size, storage_pool):
|
def ensure_resource(self, resource_name, node, size, storage_pool):
|
||||||
"""
|
"""
|
||||||
|
|
@ -256,19 +295,19 @@ class LinstorManager:
|
||||||
# La ressource existe - vérifier la taille
|
# La ressource existe - vérifier la taille
|
||||||
print(f"La ressource '{resource_name}' existe déjà. Vérification de la taille...")
|
print(f"La ressource '{resource_name}' existe déjà. Vérification de la taille...")
|
||||||
current_size = self.get_resource_size(resource_name)
|
current_size = self.get_resource_size(resource_name)
|
||||||
desired_size_bytes = self.parse_size(size)
|
desired_size_kib = self.parse_size(size)
|
||||||
|
|
||||||
if current_size is None:
|
if current_size is None:
|
||||||
print(f"[ATTENTION] Impossible de déterminer la taille actuelle de '{resource_name}'")
|
print(f"[ATTENTION] Impossible de déterminer la taille actuelle de '{resource_name}'")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if current_size < desired_size_bytes:
|
if current_size < desired_size_kib:
|
||||||
print(f"Taille actuelle ({self.format_size(current_size)}) inférieure à la taille désirée ({size})")
|
print(f"Taille actuelle ({self.format_size(current_size)}) inférieure à la taille désirée ({size})")
|
||||||
print(f"Redimensionnement de la ressource '{resource_name}' à {size}...")
|
print(f"Redimensionnement de la ressource '{resource_name}' à {size}...")
|
||||||
if not self.resize_volume(resource_name, size):
|
if not self.resize_volume(resource_name, size):
|
||||||
return False
|
return False
|
||||||
print(f"✓ Ressource '{resource_name}' redimensionnée avec succès")
|
print(f"✓ Ressource '{resource_name}' redimensionnée avec succès")
|
||||||
elif current_size > desired_size_bytes:
|
elif current_size > desired_size_kib:
|
||||||
print(f"[ATTENTION] Taille actuelle ({self.format_size(current_size)}) supérieure à la taille désirée ({size})")
|
print(f"[ATTENTION] Taille actuelle ({self.format_size(current_size)}) supérieure à la taille désirée ({size})")
|
||||||
print(f"[ATTENTION] LINSTOR ne supporte pas la réduction de volumes. Conservation de la taille actuelle.")
|
print(f"[ATTENTION] LINSTOR ne supporte pas la réduction de volumes. Conservation de la taille actuelle.")
|
||||||
else:
|
else:
|
||||||
|
|
@ -345,9 +384,9 @@ def parse_terraform_config(terraform_dir):
|
||||||
if 'etcd' in storage_var.lower():
|
if 'etcd' in storage_var.lower():
|
||||||
storage_pool = 'local-lvm'
|
storage_pool = 'local-lvm'
|
||||||
else:
|
else:
|
||||||
storage_pool = 'linstor_storage'
|
storage_pool = 'pve-storage'
|
||||||
else:
|
else:
|
||||||
storage_pool = 'linstor_storage'
|
storage_pool = 'pve-storage'
|
||||||
|
|
||||||
resource_name = f"vm-{vmid}-disk-0"
|
resource_name = f"vm-{vmid}-disk-0"
|
||||||
config[resource_name] = {
|
config[resource_name] = {
|
||||||
|
|
@ -365,7 +404,7 @@ def parse_terraform_config(terraform_dir):
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description='Gestion des ressources LINSTOR pour les VMs Proxmox',
|
description='Gestion des ressources LINSTOR pour les VMs Proxmox via API',
|
||||||
formatter_class=argparse.RawDescriptionHelpFormatter
|
formatter_class=argparse.RawDescriptionHelpFormatter
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -375,13 +414,35 @@ def main():
|
||||||
parser.add_argument('--storage-pool', help='Nom du pool de stockage (ex: pve-storage)')
|
parser.add_argument('--storage-pool', help='Nom du pool de stockage (ex: pve-storage)')
|
||||||
parser.add_argument('--terraform-dir', help='Chemin vers le répertoire Terraform pour lecture automatique de la config')
|
parser.add_argument('--terraform-dir', help='Chemin vers le répertoire Terraform pour lecture automatique de la config')
|
||||||
parser.add_argument('--all', action='store_true', help='Traiter toutes les ressources configurées')
|
parser.add_argument('--all', action='store_true', help='Traiter toutes les ressources configurées')
|
||||||
parser.add_argument('--remote-host', help='Hôte sur lequel exécuter les commandes LINSTOR via SSH (ex: thinkpad)')
|
|
||||||
|
# Paramètres API Proxmox
|
||||||
|
parser.add_argument('--api-url', help='URL de l\'API Proxmox (ex: https://192.168.100.10:8006/api2/json)',
|
||||||
|
default=os.environ.get('PROXMOX_API_URL'))
|
||||||
|
parser.add_argument('--token-id', help='ID du token API Proxmox',
|
||||||
|
default=os.environ.get('PROXMOX_TOKEN_ID'))
|
||||||
|
parser.add_argument('--token-secret', help='Secret du token API Proxmox',
|
||||||
|
default=os.environ.get('PROXMOX_TOKEN_SECRET'))
|
||||||
|
|
||||||
parser.add_argument('--verbose', '-v', action='store_true', help='Active la sortie détaillée')
|
parser.add_argument('--verbose', '-v', action='store_true', help='Active la sortie détaillée')
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
# Utilise --remote-host si spécifié, sinon utilise le premier noeud trouvé dans la config
|
# Vérifie que les credentials API sont fournis
|
||||||
manager = LinstorManager(verbose=args.verbose, remote_host=args.remote_host)
|
if not args.api_url or not args.token_id or not args.token_secret:
|
||||||
|
print("[ERREUR] Les credentials API Proxmox sont requis (--api-url, --token-id, --token-secret)", file=sys.stderr)
|
||||||
|
print("[INFO] Vous pouvez aussi les définir via les variables d'environnement PROXMOX_API_URL, PROXMOX_TOKEN_ID, PROXMOX_TOKEN_SECRET", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Initialise le client API
|
||||||
|
api = ProxmoxAPI(
|
||||||
|
api_url=args.api_url,
|
||||||
|
token_id=args.token_id,
|
||||||
|
token_secret=args.token_secret,
|
||||||
|
verify_ssl=False,
|
||||||
|
verbose=args.verbose
|
||||||
|
)
|
||||||
|
|
||||||
|
manager = LinstorManager(proxmox_api=api, verbose=args.verbose)
|
||||||
|
|
||||||
# Mode 1: Lecture automatique depuis Terraform
|
# Mode 1: Lecture automatique depuis Terraform
|
||||||
if args.terraform_dir:
|
if args.terraform_dir:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue