#!/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, re # 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éressses: 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="slon.adm.crans.org"): 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("stty rows 32767") # 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): '''Exécute une commande et renvoie le résultat. Lance l'exception Error si la commande échoue''' self.tn.write(cmd + "\r\n") resp = "" # Lecture de la réponse, c'est terminé quand on atteint un # nouveau prompt: while not resp.endswith("# "): resp = resp + self.tn.read_some() # Parfois le serveur attend que l'on appuie sur une touche # pour continuer à envoyer les données: if resp.endswith("Press any key to continue (Q to quit)"): self.tn.write(" ") # On retire les messages parasites resp = junk_regexp.sub("", resp) # On retire le prompt [resp, _] = resp.rsplit("\r\n", 1) # Remplace les fins de ligne dos pas des fin de lignes unix resp = crlf_regexp.sub("\n", resp) 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 = {} for m in volume_map_regexp.finditer(self.cmd("show volume-maps")): map[int(m.group(2))] = m.group(1) return map def create_volume(self, name, size, unit="GB", vdisk="slon1"): '''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")