#!/usr/bin/env python # -*- coding: utf-8 -*- # nolslib.py # Ce script est inspiré de slonlib.py, développé par # Jérémie Dimino en 2008. # Développé pour communiquer en telnet avec une baie MSA 2012a (modèle à # vérifier), il a été adapté pour communiquer avec une MSA P2000, qui sait # retourner des sorties compatibles avec des api. # ---------- # Auteur : Pierre-Elliott Bécue # Licence : GPLv3 '''Bibliothèque pour accéder à la baie de stockage nols, récupère les données formatées en XML''' 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/nols.py") class NolsError(Exception): def __init__(self, msg): Exception.__init__(self, msg) class Nols(object): '''Objet représentant la baie de stockage''' def __init__(self, host="10.231.136.36"): self.tn = telnetlib.Telnet(host) # Identification self.tn.expect(["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 veut des sorties en XML self.cmd("set cli-parameters api-embed") # On se débarasse des "appuie sur un bouton pour voir la suite" self.cmd("set cli-parameters pager off") 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 api-embed\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-map", mode="XML") root = fromstring(XML_map) tree = ElementTree(root) # XML c'est trobyien Objects = tree.findall("OBJECT[@name='volume-view']") for Object in Objects: name = None lun = None name = Object.findall("PROPERTY[@name='volume-name']")[0].text serial = Object.findall("PROPERTY[@name='volume-serial']")[0].text lun = Object.findall("OBJECT/PROPERTY[@name='lun']")[0].text if lun is None: pass else: map[int(lun)] = (name, serial) return map def create_volume(self, name, size, unit="GiB", vdisk="slon1"): '''Crée un nouveau volume. Retourne le lun sur lequel il est mappé. L'unité doit être "B", "K(i)B", "M(i)B", "G(i)B" ou T(i)B. Par défault l'unité est le giga octet binaire : Gib.''' # On récupère le mapping pour chercher un lun de libre map = self.volume_map() lun = 1 while lun in map: lun = lun + 1 # Création du volume result = self.cmd("create volume vdisk %s size %d%s lun %d %s" % (vdisk, size, unit, lun, name)) print "Le volume %s a été créé, son numéro d'identification est %d" %(name, lun) def delete_volume(self, name): '''Supprime un volume''' self.cmd("delete volume %s" % (name)) def expand_volume(self, name, size, unit="GiB"): '''Permet d'étendre un volume, la taille par défaut est le giga octet binaire (GiB), les unités possibles sont B, K(i)B, M(i)B, G(i)B, T(i)B.''' self.cmd("expand volume size %d%s %s" % (size, unit, name)) def rename_volume(self, origin_name, new_name): '''Renomme un volume.''' self.cmd("set volume name %s %s" % (new_name, origin_name))