#!/usr/bin/env python
# -*- coding: utf-8 -*-

# slonlib.py
# ----------
# Copyright : (c) 2008, Jeremie Dimino <jeremie@dimino.org>
# 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 <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-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
            serial = Object.findall("volume_serial")[0].text
            lun = Object.findall("lun")[0].text

            if lun is None:
                pass
            else:
                map[int(lun)] = (name, serial)
        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")
