nolslib.py 6.03 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
#!/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'''

12
13
import telnetlib
import re
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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")

28
29
30
31
32
class NolsError(Exception):
    def __init__(self, msg):
        Exception.__init__(self, msg)


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
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)
92
93
94
        if resp.lower().startswith("error"):
            raise NolsError(resp.replace("Error: ", ""))

95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
        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)
111
112
113
114
115
116

        ## Cf juste en dessous
        Objects = tree.findall("OBJECT")
        #Objects = tree.findall("OBJECT[@name='volume-view']")
        ## Fin cf juste en dessous
        
117
        for Object in Objects:
118
119
            name = None
            lun = None
120
121
122
123
            # 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
124
125
126
127
128
            
            ######## Début merde que j'ai faite juste après ###########
            if not (Object.attrib['name'] == "volume-view"):
                pass

129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
            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
144
145
            ####### Fin merde que j'ai faite juste après #############

146

147
148
149
150
151
152
            if lun is None:
                pass
            else:
                map[int(lun)] = name
        return map

Pierre-Elliott Bécue's avatar
Pierre-Elliott Bécue committed
153
    def create_volume(self, name, size, unit="GiB", vdisk="slon1"):
154
155
156
157
158
159
160
161
162
163
        '''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
164
        result = self.cmd("create volume vdisk %s size %d%s lun %d %s" % (vdisk, size, unit, lun, name))
165

Pierre-Elliott Bécue's avatar
Pierre-Elliott Bécue committed
166
        print "Le volume %s a été créé, son numéro d'identification est %d" %(name, lun)
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182

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