#!/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 <peb@crans.org>
# 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 <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)

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