#!/usr/bin/env python # -*- coding: utf-8 -*- # slonlib.py # ---------- # Copyright : (c) 2008, Jeremie Dimino # Licence : BSD3 '''Bibliothèque pour accéder à la baie de stockage''' import telnetlib import re from xml.etree.ElementTree import ElementTree, fromstring # Message envoyé par le serveur pour attendre l'appuie sur touche junk_regexp = re.compile("Press any key to continue \(Q to quit\)\r *\r") # Matching des fin de lignes avec \r\n crlf_regexp = re.compile("\r\n") username = "" password = "" # Récupére des identifiants execfile("/etc/crans/secrets/slon.py") # La réponse à la commande "show volume-maps" est une séquence # d'éléments de la forme: # # nom du volume # | # v # > Volume [SN 00c0ffd5e51800004ca1454901000000, Name (o2_slash)] mapping view: # > CH ID LUN Access Host-Port-Identifier Nickname # > ------------------------------------------------------------------------------- # > 0,1 0 4 rw all other hosts # ^ # | # lun # # On utilise cette expression un peu barbare pour récupérer les deux # informations qui nous intéresssent : volume_map_regexp = re.compile("Volume \[SN [0-9a-f]+, Name \(([^)]*)\)\][^\n]*\n[^\n]*\n[^\n]*\n[0-9,-]+ *[0-9]+ *([0-9]+)[^\n]*\n") class Slon(object): '''Objet représentant la baie de stockage''' def __init__(self, host="10.231.136.85"): self.tn = telnetlib.Telnet(host) # Identification self.tn.read_until("Login: ") self.tn.write(username + "\r\n") self.tn.read_until("Password: ") self.tn.write(password + "\r\n") # Ça c'est pour attendre l'invite de commande initiale: self.tn.read_until("# ") # On met un nombre de lignes le plus élévé possible pour # éviter que le serveur ne se mette en attente de l'appuie sur # une touche self.cmd("set cli-parameters pager off") # Au delà de cette valeur il y a un overflow def logout(self): '''Déconnexion de la baie''' self.tn.write("exit\r\n") self.tn.read_all() self.tn.close() print ("Si vous avez effectué des modifications pensez à exécuter:\n" + "/usr/scripts/gestion/iscsi/update.sh sur chacun des dom0\n") def cmd(self, cmd, mode='text'): '''Exécute une commande et renvoie le résultat. Lève l'exception Error si la commande échoue''' # Si c'est le script qui bosse, on utilise le mode XML, sinon # on peut aussi lancer des commandes en mode texte if mode == 'XML': self.tn.write("set cli-parameters xml\r\n") self.tn.read_until("# ") else: self.tn.write("set cli-parameters console\r\n") self.tn.read_until("# ") self.tn.write(cmd + "\r\n") resp = "" # Lecture de la réponse, c'est terminé quand on atteint un # nouveau prompt: resp = self.tn.read_until("# ") # On retire les messages parasites s'il en reste par hasard resp = junk_regexp.sub("", resp) # On vire la commande qui est là et dont on veut pas [_, resp] = resp.split(cmd+"\r\n", 1) # On retire le prompt [resp, _] = resp.rsplit("\r\n", 1) # Remplace les fins de ligne dos par des fin de lignes unix resp = crlf_regexp.sub("\n", resp) if resp.lower().startswith("error"): raise NolsError(resp.replace("Error: ", "")) return resp def show(self, what): '''Raccourci pour: print slon.cmd("show ")''' print self.cmd("show " + what) def help(self, what = ""): '''Raccourci pour: print slon.cmd("help ")''' print self.cmd("help " + what) def volume_map(self): '''Retourne le mapping lun<->nom de volume''' map = {} XML_map = self.cmd("show volume-maps", mode="XML") root = fromstring(XML_map) tree = ElementTree(root) # XML c'est trobyien tree = tree.findall("volume-view")[0] Objects = tree.findall("volume_view") for Object in Objects: name = None lun = None name = Object.findall("volume_name")[0].text lun = Object.findall("lun")[0].text if lun is None: pass else: map[int(lun)] = name return map def create_volume(self, name, size, unit="GB", vdisk="slon2"): '''Créé un nouveau volume. Retourne le lun sur lequel il est mappé. La taille est en Giga-octet. L'unité doit être "KB", "MB" ou "GB". Par défault c'est "GB".''' # Création du volume self.cmd("create volume vdisk %s size %d%s %s" % (vdisk, size, unit, name)) # On récupère le mapping pour chercher un lun de libre map = self.volume_map() lun = 0 while lun in map: lun = lun + 1 # Mapping du volume self.cmd("map volume %s lun %d" % (name, lun)) return lun def delete_volume(self, name): '''Supprime un volume''' self.cmd("delete volume %s")