nolslib.py 5.82 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# baie_lib.py
# ----------
# Type à taper si ça chie : Pierre-Elliott Bécue <peb@crans.org> 
# Licence   : WTFPL

'''Bibliothèque pour accéder à la baie de stockage nols, récupère les données
formatées en XML'''

import telnetlib, 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 Nols(object):
    '''Objet représentant la baie de stockage'''

    def __init__(self, host="nols.adm.crans.org"):
        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)
        return resp

    def show(self, what):
        '''Raccourci pour: print slon.cmd("show <what>")'''
        print self.cmd("show " + what)

    def help(self, what = ""):
        '''Raccourci pour: print slon.cmd("help <what>")'''
        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)
102 103 104 105 106 107

        ## Cf juste en dessous
        Objects = tree.findall("OBJECT")
        #Objects = tree.findall("OBJECT[@name='volume-view']")
        ## Fin cf juste en dessous
        
108
        for Object in Objects:
109 110
            name = None
            lun = None
111 112 113 114
            # Quand on passera à wheezy, décommenter ces lignes, et virer
            # la merde que j'ai fait juste après.
            #name = Object.findall("PROPERTY[@name='volume-name']")[0].text
            #lun = Object.findall("OBJECT/PROPERTY[@name='lun']")[0].text
115 116 117 118 119
            
            ######## Début merde que j'ai faite juste après ###########
            if not (Object.attrib['name'] == "volume-view"):
                pass

120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
            properties = Object.findall("PROPERTY")
            for property in properties:
                if property.attrib['name'] == "volume-name":
                    name = property.text
                else:
                    pass

            subObjects = Object.findall("OBJECT")
            for subObject in subObjects:
                properties = subObject.findall("PROPERTY")
                for property in properties:
                    if property.attrib['name'] == "lun":
                        lun = property.text
                    else:
                        pass
135 136
            ####### Fin merde que j'ai faite juste après #############

137

138 139 140 141 142 143
            if lun is None:
                pass
            else:
                map[int(lun)] = name
        return map

Pierre-Elliott Bécue's avatar
Pierre-Elliott Bécue committed
144
    def create_volume(self, name, size, unit="GiB", vdisk="slon1"):
145 146 147 148 149 150 151 152 153 154 155 156
        '''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 = 0
        while lun in map: lun = lun + 1

        # Création du volume
        self.cmd("create volume vdisk %s size %d%s lun %d %s" % (vdisk, size, unit, lun, name))

Pierre-Elliott Bécue's avatar
Pierre-Elliott Bécue committed
157
        print "Le volume %s a été créé, son numéro d'identification est %d" %(name, lun)
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173

    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, orig_name))