#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# -------------------------------------------------------------------------- #
#             EC3 command-line interface                                     #
# Copyright 2015, Universitat Politecnica de Valencia                        #
#                                                                            #
# Licensed under the Apache License, Version 2.0 (the "License"); you may    #
# not use this file except in compliance with the License. You may obtain    #
# a copy of the License at                                                   #
#                                                                            #
# http://www.apache.org/licenses/LICENSE-2.0                                 #
#                                                                            #
# Unless required by applicable law or agreed to in writing, software        #
# distributed under the License is distributed on an "AS IS" BASIS,          #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #
# See the License for the specific language governing permissions and        #
# limitations under the License.                                             #
#--------------------------------------------------------------------------- #

import sys, time, logging, os, argparse, tempfile, random, re, io, subprocess
import requests
try:
    # to avoid Warnings 
    import urllib3
    urllib3.disable_warnings()
    urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

    from requests.packages.urllib3.exceptions import InsecureRequestWarning
    requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
except:
    pass

import yaml
try:
    import json
except ImportError:
    import simplejson as json
try:
    unicode("hola")
except NameError:
    unicode = str

from IM2.auth import Authentication
from IM2.radl import parse_radl, dump_radl as dump_radl_future, dump_radl_json, dump_radl_simple
import IM2.radl.radl as IM_RADL
from IM2.radl.radl import RADL, system, network, configure, deploy, Feature, Features, Aspect, FeaturedAspect

# Disable CERTIFICATE VERIFY
try:
    import ssl
    ssl._create_default_https_context = ssl._create_unverified_context
except:
    pass

# EC3 client version
VERSION = '2.1.0'
# Our public IM service
IM_URL = 'https://appsgrycap.i3m.upv.es:31443/im/'
# RADL template path
TEMPLATE_PATHS = ["./templates", "~/.ec3/templates", "/etc/ec3/templates"]
# Path where to store launched clusters
CLUSTER_STORE_PATH = "~/.ec3/clusters"
# Config file path
FILENAME_CONFIG = "~/.ec3/config.yml"
# Max time in seconds needed to deploy the front-end node
TIMEOUT = 6000
# Time in seconds between two consecutive requests to IM service
DELAY = 5
# Max number of errors permitted in the VM status check
STATE_CHECK_RETRIES = 3

def dump_radl(rin, aspect=None, order_deploys=False):
    r = rin.clone()

    if order_deploys:
        # Delete the deploys to add it now in correct order
        for a in (r.gets(deploy)):
            r.delete(a)

        dep_front = None
        other_deps = []
        for dep in rin.gets(deploy):
            if dep.id == "front" and dep.vm_number == 1:
                dep_front = dep
            else:
                other_deps.append(dep)
        if not dep_front:
            raise Exception("There must be at least one deploy with name 'front' and VM number 1")
        else:
            r.add(dep_front)
            for dep in other_deps:
                r.add(dep)

    for a in r.aspects:
        if not isinstance(a, FeaturedAspect): continue
        for p in a.props:
            v = a.getValue(p, iftuple="default")
            # Replace references by their ids
            if isinstance(v, FeaturedAspect):
                a.setValue(p, v.getId())
            # Replace list of strings and numbers by strings
            elif isinstance(v, (list, set)) and v and isinstance(list(v)[0], (str, int, float)):
                a.setValue(p, ",".join(map(str, v)))
    if aspect:
        r = RADL([r.get(aspect)], addaspects=False, check=False)
    return dump_radl_future(r)

def dump_json(d):
    if json:
        return json.dumps(d, indent=4, separators=(",", ": "))
    return str(d)

def dump_table(l, order, align={}):
    widths = dict([ (f, max([ len(str(d[f])) for d in l ] + [len(f)])+2) for f in order ])
    o = [ "".join([ "{0: ^{width}}".format(f, width=widths[f]) for f in order ]) ]
    o.append("-"*sum(widths.values()))
    o.extend([ "".join([ "{0: {a}{width}}".format(d[f], a=align.get(f, "^"), width=widths[f]) for f in order ])
               for d in l ])
    return "\n".join(o)

def dump_list(l, order, width=80, indent=4):
    def break_line(s):
        indented = False; width0 = width
        while s:
            b = s.find("\n", 0, width0)
            if b < 0: b = len(s) if len(s) < width0 else s.rfind(" ", width0-15, width0)
            yield (" "*indent if indented else "") + (s[0:b] if b >= 0 else s[0:width0-1] + "-")
            s = s[(b+1 if b >= 0 else width0-1):]
            indented = True; width0 = width - indent
    return "\n\n".join([ "\n".join([ s for k in order for s in break_line("%s: %s" % (k, d[k].expandtabs()
                                     .decode("string_escape"))) ]) for d in l ])

def getPublicIP(s):
    for i in range(4):
        if not s.hasFeature("net_interface.%d.connection" % i): break
        if s.getValue("net_interface.%d.connection" % i, default=network("")).getValue("outbound") == "yes":
            return s.getValue("net_interface.%d.ip" % i)
    return None

def get_content_from_template_file(filename):
    t = [ os.path.join(os.path.expanduser(base_path), filename) for base_path in TEMPLATE_PATHS
          if os.path.isfile(os.path.join(os.path.expanduser(base_path), filename)) ]
    if not t: raise Exception("File not found in %s" % TEMPLATE_PATHS)
    f = open(t[0], "r")
    c = f.read()
    f.close()
    return c

def ec3_xpath_radl(r, xpath, local_dump_radl=dump_radl, join_sets=True):
    xpath0 = [ a.strip() for a in xpath.split("/") ]
    if len(xpath0) < 3: raise Exception("Invalid path: minimum path /<aspect>/<aspect_id>!")
    if len(xpath0.pop(0)) != 0: raise Exception("Invalid path: expected it stars with '/'")
    class_name = xpath0.pop(0)
    if not hasattr(IM_RADL, class_name):
        raise Exception("Invalid path: '%s' is not an aspect!" % class_name)
    if len(xpath0) == 1 and xpath0[0] == "*":
        return local_dump_radl(RADL(r.gets(getattr(IM_RADL, class_name))))
    aspect = r.get(getattr(IM_RADL, class_name)(xpath0.pop(0)))
    if not aspect: raise Exception("Invalid path: no aspect with that path.")
    if isinstance(aspect, Features): aspect = ec3_xpath_features(aspect, xpath0)
    if isinstance(aspect, (list,set)) and join_sets: aspect = ",".join(map(str, aspect))
    if isinstance(aspect, Aspect):
        ast = len(xpath0) == 1 and xpath0[0] == "*"
        aspect, aspect0 = local_dump_radl(RADL([aspect]), aspect=aspect if ast else None), aspect
        if ast:
            if isinstance(aspect, str):
                s = slice(2, -2) if isinstance(aspect0, configure) else slice(1, -1)
                return "\n".join(aspect.split("\n")[s])
            elif isinstance(aspect, list):
                return [ a for a in aspect if a["id"] == aspect0.getId() and a["class"] == aspect0.__class__.__name__ ][0]
    if not xpath0: return aspect
    raise Exception("Invalid path: attributes can get from featured aspects only")

def ec3_xpath_features(features, xpath):
    if len(xpath) == 0: return features
    if not isinstance(features, Features):
        raise Exception("Expected an aspect with features, not '%s'" % str(features))
    if xpath[0] == "*": return features
    elem = xpath.pop(0)
    if features.hasFeature(elem):
        return ec3_xpath_features(features.getValue(elem), xpath)
    raise Exception("Invalid path: '%s' is not in '%s'" % (elem, str(features)))

def apply_ec3_expressions(r):
    def apply_ec3_append(i):
        if not isinstance(i, dict) or "ec3_append" not in i: return [i]
        try:
            if not isinstance(r.get(configure(i["ec3_append"])).recipe, list): raise Exception
            return r.get(configure(i["ec3_append"])).clone().recipe
        except:
            raise Exception("Configure '%s' does't exist or the recipe is not a list." % i["ec3_append"])
    for conf in r.gets(configure):
        if not isinstance(conf.recipe, list): continue
        conf.recipe = [ l1 for l0 in map(apply_ec3_append, conf.recipe) for l1 in l0 ]
    recipes_prio = [ conf for conf in r.gets(configure) if isinstance(conf.recipe, list) and
                     any([ "ec3_prio" in d for d in conf.recipe if isinstance(d, dict) ]) ]
    def rm_ec3_prio(t):
        t[1].pop("ec3_prio", None); return t[1]
    for conf in recipes_prio:
        conf.recipe = list(map(rm_ec3_prio, sorted([ (d.get("ec3_prio", i), d) for i, d in enumerate(conf.recipe) ])))
    def sanitize(cad):
        return cad.replace("{", "\\x7b").replace("}", "\\x7d")
        #return cad.replace("\\", "\\\\").replace("{", "\\x7b").replace("}", "\\x7d")
    recipes_vars = [ (conf, d["vars"]) for conf in r.gets(configure) if isinstance(conf.recipe, list)
                     for d in conf.recipe if isinstance(d, dict) and "vars" in d and isinstance(d["vars"], dict) ]
    def isrecursive(p):
        p = p.split("/")
        return len(p) < 3 or p[1] == "configure"
    for conf, conf_vars in recipes_vars:
        conf_vars0 = {}
        for k, v in conf_vars.items():
            if not isinstance(v, dict): continue
            try:
                if v.get("ec3_file"):
                    conf_vars0[k] = sanitize(get_content_from_template_file(v["ec3_file"]))
                elif v.get("ec3_xpath") and not isrecursive(v.get("ec3_xpath")):
                    conf_vars0[k] = sanitize(ec3_xpath_radl(r, v["ec3_xpath"]))
                elif v.get("ec3_jpath") and not isrecursive(v.get("ec3_jpath")):
                    conf_vars0[k] = ec3_xpath_radl(r, v["ec3_jpath"], lambda *a,**_: dump_radl_simple(*a), False)
            except Exception as e:
                CLI.display("Error in configure '%s' in variable '%s': %s" % (conf.getId(), k, str(e)), level=logging.ERROR, exception=True)
                sys.exit(1)
        conf_vars.update(conf_vars0)
    recipes_vars = [ (conf, d["vars"]) for conf in r.gets(configure) if isinstance(conf.recipe, list)
                     for d in conf.recipe if isinstance(d, dict) and "vars" in d and isinstance(d["vars"], dict) ]
    for conf, conf_vars in recipes_vars:
        conf_vars0 = {}
        for k, v in conf_vars.items():
            if not isinstance(v, dict): continue
            try:
                if v.get("ec3_xpath"):
                    conf_vars0[k] = sanitize(ec3_xpath_radl(r, v["ec3_xpath"]))
                elif v.get("ec3_jpath"):
                    conf_vars0[k] = ec3_xpath_radl(r, v["ec3_jpath"], lambda *a,**_: dump_radl_simple(*a), False)
            except Exception as e:
                CLI.display("Error in configure '%s' in variable '%s': %s" % (conf.getId(), k, str(e)), level=logging.ERROR, exception=True)
                sys.exit(1)
        conf_vars.update(conf_vars0)

def apply_ec3_features(r):
    for a in list(r.aspects):
        if not isinstance(a, FeaturedAspect) or not a.getValue("ec3_inherit_from"): continue
        s = a.getValue("ec3_inherit_from").clone(); a.delValue("ec3_inherit_from")
        if r.get(configure(s.getId())):
            r.add(configure(a.getId(), [{"ec3_append": s.getId()}]), ifpresent="merge", conflict="me")
        r.delete(a)
        s.merge(a, conflict="other", missing="other")
        s.setId(a.getId())
        r.add(s, ifpresent="merge", conflict="other", missing="other")

def format_rest_auth_data(auth_data):
    rest_auth_data = ""
    if auth_data:
        for item in auth_data:
            for key, value in item.items():
                value = value.replace("\n", "\\\\n")
                rest_auth_data += "%s = %s;" % (key, value)
            rest_auth_data += "\\n"
    return rest_auth_data

def get_out_port(radl, port, protocol="tcp"):
    """
    Get the port from the RADL

    Returns: int with the port
    """
    res = port

    public_net = None
    for net in radl.gets(network):
        if net.isPublic():
            public_net = net

    if public_net:
        outports = public_net.getOutPorts()
        if outports:
            for outport in outports:
                if outport.get_local_port() == port and outport.get_protocol() == protocol:
                    res = outport.get_remote_port()

    return res

class include(IM_RADL.FeaturedAspect):
    def check(self, r):
        self.check_simple(dict(template=(str, lambda x,_: len(x.value.strip()))), r)
IM_RADL.include = include

class description(IM_RADL.FeaturedAspect):
    def check(self, r):
        self.check_simple(dict(short=(str, None), content=(str, None), kind=(str, None)), r)
IM_RADL.description = description

class Display:
    # Private variables, just don't bother them!
    LEN_SCREEN = 90
    BACKGROUND = " "*LEN_SCREEN
    state = 0
    last_msg = None
    please_clean_background = False
    ANIMATION = ['\033[1;32m _____"         \033[m', # cute, isn't it?
                 '\033[1;32m  _/\_"         \033[m',
                 '\033[1;32m  _____"        \033[m',
                 '\033[1;32m   _/\_"        \033[m',
                 '\033[1;32m   _____"       \033[m',
                 '\033[1;32m    _/\_"       \033[m',
                 '\033[1;32m    _____"      \033[m',
                 '\033[1;32m     _/\_"      \033[m',
                 '\033[1;32m     _____"     \033[m',
                 '\033[1;32m      _/\_"     \033[m',
                 '\033[1;32m      _____"    \033[m',
                 '\033[1;32m       _/\_"    \033[m',
                 '\033[1;32m       _____"   \033[m',
                 '\033[1;32m        _/\_"   \033[m',
                 '\033[1;32m        _____"  \033[m',
                 '\033[1;32m         _/\_"  \033[m',
                 '\033[1;32m         _____" \033[m',
                 '\033[1;32m          _/\_" \033[m',
                 '\033[1;32m          _____"\033[m',
                 '\033[1;32m          _____ \033[m',
                 '\033[1;32m         "_____ \033[m',
                 '\033[1;32m         "_/\_  \033[m',
                 '\033[1;32m        "_____  \033[m',
                 '\033[1;32m        "_/\_   \033[m',
                 '\033[1;32m       "_____   \033[m',
                 '\033[1;32m       "_/\_    \033[m',
                 '\033[1;32m      "_____    \033[m',
                 '\033[1;32m      "_/\_     \033[m',
                 '\033[1;32m     "_____     \033[m',
                 '\033[1;32m     "_/\_      \033[m',
                 '\033[1;32m    "_____      \033[m',
                 '\033[1;32m    "_/\_       \033[m',
                 '\033[1;32m   "_____       \033[m',
                 '\033[1;32m   "_/\_        \033[m',
                 '\033[1;32m  "_____        \033[m',
                 '\033[1;32m  "_/\_         \033[m',
                 '\033[1;32m "_____         \033[m',
                 '\033[1;32m "_/\_          \033[m',
                 '\033[1;32m"_____          \033[m',
                 '\033[1;32m _____          \033[m']
    # Make worm fatter
    if sys.stdout.encoding == "UTF-8":
        ANIMATION = list(map(lambda s: s.replace("_", "â–„").replace("/", "â–Ÿ").replace("\\", "â–™")
                                   .replace('"', '¨'), ANIMATION))

    @staticmethod
    def _display_waiting_tty(msg, delay=0.):
        time0 = time.time()
        while time.time() - time0 < delay:
            screen = Display.ANIMATION[Display.state] + msg
            Display.clean()
            sys.stdout.write(screen[0:Display.LEN_SCREEN])
            sys.stdout.flush()
            Display.please_clean_background = True
            Display.state = (Display.state + 1) % len(Display.ANIMATION)
            time.sleep(min(max(delay - (time.time() - time0), 0.), .7))

    @staticmethod
    def _display_notty(msg):
        if msg == Display.last_msg: return
        sys.stdout.write(msg)
        sys.stdout.flush()
        Display.last_msg = msg

    @staticmethod
    def display_waiting(msg, delay=0.):
        # Animations are funny, but only in proper consoles
        if sys.stdout.isatty():
            Display._display_waiting_tty(msg, delay)
        else:
            Display._display_notty(msg)
            time.sleep(delay)

    @staticmethod
    def clean():
        if not Display.please_clean_background: return
        sys.stdout.write("\r" + Display.BACKGROUND + "\r")
        Display.please_clean_background = False

    @staticmethod
    def display(msg):
        Display.clean()
        sys.stdout.write(str(msg) + "\n")
        sys.stdout.flush()

class ClusterStore:
    # Private variables, just don't bother them!
    DIR = os.path.expanduser(CLUSTER_STORE_PATH)

    @staticmethod
    def _check_dir():
        if not os.path.exists(ClusterStore.DIR):
            os.makedirs(ClusterStore.DIR)

    @staticmethod
    def list():
        ClusterStore._check_dir()
        return os.listdir(ClusterStore.DIR)

    @staticmethod
    def save(clustername, r):
        ClusterStore._check_dir()
        f = open(os.path.join(ClusterStore.DIR, clustername), "w")
        f.write(dump_radl(r))
        f.close()
     
    @staticmethod
    def write(clustername, r):
        ClusterStore._check_dir()
        f = open(os.path.join(ClusterStore.DIR, clustername), "w")
        f.write(r)
        f.close()

    @staticmethod
    def read(clustername, refresh=False):
        ClusterStore._check_dir()
        f = open(os.path.join(ClusterStore.DIR, clustername), "r")
        r = f.read()
        f.close()
        return r

    @staticmethod
    def load(clustername, refresh=False):
        ClusterStore._check_dir()
        if clustername not in ClusterStore.list():
            raise Exception("There is no cluster with name '%s'!" % clustername)
        f = open(os.path.join(ClusterStore.DIR, clustername), "r")
        r = parse_radl(f.read())
        f.close()
        r.check()
        s0 = r.get(system("front"))
        if not refresh or not s0 or s0.getValue("state") not in system.IS_ACCESSIBLE: return r
        try:
            new_im_server_url, infrId, vmId, auth = ClusterStore.get_im_server_infrId_and_vmId_and_auth(r)

            headers = {"Authorization": format_rest_auth_data(auth), "Accept": "application/json"}
            url = "%s/infrastructures/%s" % (new_im_server_url, infrId)
            resp = requests.request("GET", url, verify=False, headers=headers)
            if resp.status_code != 200:
                raise Exception(resp.text)
            res = []
            for elem in resp.json()["uri-list"]:
                res.append(os.path.basename(list(elem.values())[0]))

            headers = {"Authorization": format_rest_auth_data(auth), "Accept": "text/*"}
            url = "%s/infrastructures/%s/vms/%s" % (new_im_server_url, infrId, vmId)
            resp = requests.request("GET", url, verify=False, headers=headers)
            if resp.status_code != 200:
                raise Exception(resp.text)
            info = str(resp.text)

            r = parse_radl(info)
            r.check()
            for prop in ("auth", "__im_server", "__infrastructure_id"):
                if s0.hasFeature(prop):
                    r.get(system("front")).setValue(prop, s0.getValue(prop))
            r.get(system("front")).setValue("nodes", len(res)-1)

            url = "%s/infrastructures/%s/contmsg" % (new_im_server_url, infrId)
            resp = requests.request("GET", url, verify=False, headers=headers)
            if resp.status_code != 200:
                raise Exception(resp.text)
            cont_out = resp.text

            r.get(system("front")).setValue("contextualization_output", cont_out)
            ClusterStore.save(clustername, r)
        except Exception as e:
            CLI.display("Error getting information from cluster '%s': %s" % (clustername, str(e)),
                        level=logging.ERROR)
            s0.setValue("state", system.UNKNOWN)
        return r

    @staticmethod
    def remove(clustername):
        ClusterStore._check_dir()
        if clustername not in ClusterStore.list():
            raise Exception("There is no cluster with name '%s'!" % clustername)
        os.unlink(os.path.join(ClusterStore.DIR, clustername))
        try:
            os.unlink(os.path.join(ClusterStore.DIR, clustername + "_ids"))
        except Exception:
            pass

    @staticmethod
    def get_im_server_infrId_and_vmId_and_auth(r):
        s0 = r.get(system("front"))
        im_server_name = s0.getValue("__im_server", default="http://%s:%s" % (getPublicIP(s0), get_out_port(r, 8800)))
        infrId = s0.getValue("__infrastructure_id", default=0)
        vmId = s0.getValue("__vm_id", default="0")
        auth = json.loads(s0.getValue("auth"))
        return im_server_name, infrId, vmId, auth

class argparse_configyaml:
    def __init__(self, *args, **kwargs):
        if "_self" in kwargs:
            for k in ("_self", "_defaults", "_configfile", "_section"):
                setattr(self, k, kwargs.get(k, None))
            return

        configfilename = os.path.expanduser(FILENAME_CONFIG)
        if os.path.isfile(configfilename):
            try:
                self._defaults = yaml.safe_load(open(configfilename, "r").read())
            except Exception as e:
                #CLI.display("Error reading configuration file '%s': %s" % (configfilename, str(e)), level=logging.ERROR, exception=True)
                sys.stderr.write("ERROR: Error reading configuration file '%s': %s. Delete it and try again.\n" % (configfilename, str(e)))
                sys.exit(-1)
            self._configfile = None
        else:
            self._defaults = None
            if not os.path.exists(os.path.dirname(configfilename)):
                os.makedirs(os.path.dirname(configfilename))
            self._configfile = open(configfilename, "w")
            self._configfile.write("""#
# This file is read by ec3 to set default values to the command options. Options
# are formated in YAML. Options related to files, e.g. 'auth_file' in section
# 'launch', admit the next values:
#
# - an scalar: it will be treated as the content of the file, e.g.:
#    auth_file: |
#       type = OpenNebula; host = myone.com:9999; username = user; password = 1234
#       type = EC2; username = AKIAAAAAAAAAAAAAAAAA; password = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
#
# - a mapping with the key 'filename': it will be treated as the file path, e.g.:
#    auth_file:
#       filename: /home/user/auth.txt
#
# - a mapping with the key 'stream': it will select either standard output ('stdout')
#   or standard error ('stderr'), e.g.:
#    log_file:
#       stream: stdout

default:
""")
        self._section = "default"
        self._self = argparse.ArgumentParser(*args, **kwargs)

    def add_subparsers(self, *args, **kw):
        return argparse_configyaml(_self=self._self.add_subparsers(*args, **kw), _defaults=self._defaults,
                                   _configfile=self._configfile)

    def add_parser(self, section, help=None, config=True):
        if not config:
            return self._self.add_parser(section, help=help)
        elif self._configfile:
            self._configfile.write("# Default options for command '%s' (%s)\n%s:\n" % (section, help, section))
            return argparse_configyaml(_self=self._self.add_parser(section, help=help), _configfile=self._configfile)
        else:
            return argparse_configyaml(_self=self._self.add_parser(section, help=help), _section=section,
                                       _defaults=self._defaults.get(section, {}))

    def add_argument(self, *args, **kw):
        prop = sorted(map(lambda s: (-len(s), s), args))[0][1].strip("-").replace("-", "_")
        if (self._configfile and kw.get("config", True) and args[0][0] == "-" and kw.get("action", None) != "append"):
            self._configfile.write("    # %s\n" % kw["help"])
            if "config_default" in kw:
                self._configfile.write(kw["config_default"] + "\n\n")
            elif "default" in kw:
                default = kw["default"][0] if isinstance(kw["default"], list) else kw["default"]
                if default == sys.stderr or default == sys.stdout:
                    default = dict(stream="stderr" if default == sys.stderr else "stdout")
                r = "\n".join(yaml.safe_dump(dict(a={prop: default}), default_flow_style=False, indent=4).splitlines()[1:])
                self._configfile.write(r + "\n\n")
        elif (self._defaults and prop in self._defaults and kw.get("config", True) and args[0][0] == "-" and
              kw.get("action", None) != "append"):
            default = self._defaults[prop]
            if isinstance(kw.get("type", None), argparse.FileType):
                if isinstance(default, str): default = io.StringIO(unicode(default))
                elif "filename" in default: default = open(default["filename"], kw["type"]._mode)
                elif "stream" in default: default = sys.stderr if default["stream"] == "stderr" else sys.stdout
                else:
                    CLI.display("Error in configure file: bad value for property %s in section %s" % (prop, self._section), level=logging.ERROR)
            kw["default"] = [default] if kw.get("nargs", None) == 1 else default
        for k in ("config_default", "config"): kw.pop(k, None)
        return self._self.add_argument(*args, **kw)

    def parse_args(self):
        if self._configfile: self._configfile.close()
        return self._self.parse_args()

    def set_defaults(self, *args, **kw):
        return self._self.set_defaults(*args, **kw)

class CLI:
    # Private variables, just don't bother them!
    options = None

    # Public variables
    logger = None

    @staticmethod
    def run(commands):
        parser = argparse_configyaml(prog="ec3")
        parser.add_argument("-v", "--version", action="version", version='EC3 current version: %s' % VERSION, help='show program\'s version number and exit')
        parser.add_argument("-l", "--log-file", dest="log_file", nargs=1, type=argparse.FileType('w'), default=[sys.stderr], help="log output file")
        parser.add_argument("-ll", "--log-level", dest="log_level", nargs=1, type=int, default=[5], help="log level. 1: debug; 2: info; 3: warning; 4: error")
        parser.add_argument("-q", "--quiet", action="store_true", dest="quiet", default=False, help="only print messages from front-end")
        subparsers = parser.add_subparsers(title="subcommands", description="valid subcommands", help="additional help")
        parser.set_defaults(func=None)
        for cmd in commands:
            cmd.parse(subparsers)
        CLI.options = parser.parse_args()

        # Set log
        logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s', datefmt='%H:%M:%S',
                            stream=CLI.options.log_file[0], level=CLI.options.log_level[0]*10)
        CLI.logger = logging.getLogger('ec3')

        # Run command
        if CLI.options.func:
            CLI.options.func(CLI.options)

    @staticmethod
    def display(msg, alt=None, level=logging.INFO, exception=False):
        if exception: CLI.logger.exception("")
        CLI.logger.log(level, msg)
        if CLI.options.quiet:
            if alt: msg = alt
            else: return
        Display.display(msg)

    @staticmethod
    def display_waiting(msg, delay=0.):
        if CLI.options.quiet:
            time.sleep(delay)
        else:
            Display.display_waiting(msg, delay)

class CmdLaunch:
    @staticmethod
    def parse(subparsers):
        parser = subparsers.add_parser("launch", help="launch a new cluster")
        parser.add_argument("clustername", help="name of the new cluster")
        parser.add_argument("templates", nargs="+", help="filename in `templates` without extension")
        parser.add_argument("--add", action="append", help="add a piece of RADL")
        parser.add_argument("-n", "--not-store", action="store_true", default=False, help="don't store the cluster nor check the cluster name")
        parser.add_argument("-p", "--print", action="store_true", default=False, dest="print_radl", help="print final RADL description of the cluster")
        parser.add_argument("--json", action="store_true", default=False, help="print cluster information in JSON format")
        parser.add_argument("-a", "--auth-file", type=argparse.FileType('r'), dest="auth_file", nargs=1, help="authorization file", config_default="""    # auth_file: |
    #   type = OpenNebula; host = myone.com:9999; username = user; password = 1234
    #   type = EC2; username = AKIAAAAAAAAAAAAAAAAA; password = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa""")
        parser.add_argument("-u", "--restapi-url", dest="restapi", nargs=1, default=[IM_URL], help="URL to IM REST API service")
        parser.add_argument("--dry-run", action="store_true", dest="dry_run", default=False, help="don't launch any cluster", config=False)
        parser.add_argument("--on-error-destroy", action="store_true", dest="destroy", default=False, help="on error try to destroy the infrastructure")
        parser.add_argument("-y", "--yes", action="store_true", dest="yes", default=False, help="say yes to the warning about insecure connections")
        parser.add_argument("-g", "--golden-images", action="store_true", dest="golden_image", default=False, help="generate a VMI from the first deployed node, to accelerate the contextualization process of next node deployments")
        parser.set_defaults(func=CmdLaunch.run)

    @staticmethod
    def run(options):
        # Check cluster name, authentication and IM server
        if not options.not_store and options.clustername in ClusterStore.list():
            CLI.display("Cluster name already exists!", level=logging.ERROR)
            sys.exit(1)
        try:
            if not options.auth_file: raise Exception("option is not set")
            auth_content = [ str(l) for l in options.auth_file[0].readlines() ]
            auth_data = Authentication.read_auth_data(auth_content)
            if not any([ a.get("type", None) == "InfrastructureManager" for a in auth_data ]):
                auth_content.append("type = InfrastructureManager; username = %x; password = %x" %
                                    (random.random()*1e15, random.random()*1e15))
                auth_data = Authentication.read_auth_data(auth_content)
            auth_content = Authentication.dump(auth_data)
        except Exception as e:
            CLI.display("Error in -a/--auth-file: %s" % str(e), level=logging.ERROR)
            sys.exit(1)
        try:
            if not options.restapi: raise Exception("option is not set")
            if not options.dry_run and options.restapi[0].startswith("http:") and not options.restapi[0].startswith("http://localhost"):
                CLI.display("WARNING: you are not using a secure connection and this can compromise the secrecy of the passwords and private keys available in the authorization file.")
                if not options.yes and get_input("Continue [y/N]? ")[0:1].lower() != "y":
                    sys.exit(1)
        except Exception as e:
            CLI.display("Error in -u/--restapi-url: %s" % str(e), level=logging.ERROR)
            sys.exit(1)

        # Instantiate templates
        try:
            radl = CmdLaunch.generate_radl(options.templates, options.add if options.add else [], auth_content)
        except Exception as e:
            CLI.display("Error generating RADL: %s Check the templates used. Have you provided a name for the cluster?." % str(e), level=logging.ERROR)
            sys.exit(1)

        # Consider the use of golden images
        if options.golden_image:
            if radl.get(system("wn")):
                radl.get(system("wn")).setValue("ec3_golden_images", "true")

        # Finish if dry run
        if options.dry_run:
            if options.print_radl:
                output = dump_radl_json(radl) if options.json else dump_radl(radl)
                CLI.display(output + "\n")
            sys.exit(0)

        # Create infrastructure
        CLI.display("Creating infrastructure")
        try:
            headers = {"Authorization": format_rest_auth_data(auth_data), "Accept": "text/*"}
            url = "%s/infrastructures" % options.restapi[0]
            resp = requests.request("POST", url, verify=False, headers=headers, data=dump_radl(radl, order_deploys=True))
            if resp.status_code != 200:
                raise Exception(resp.text)
            infrId = os.path.basename(resp.text)

            headers = {"Authorization": format_rest_auth_data(auth_data), "Accept": "application/json"}
            url = "%s/infrastructures/%s" % (options.restapi[0], infrId)
            resp = requests.request("GET", url, verify=False, headers=headers)
            if resp.status_code != 200:
                raise Exception(resp.text)
            vm_ids = []
            for elem in resp.json()["uri-list"]:
                vm_ids.append(os.path.basename(list(elem.values())[0]))
        except Exception as e:
            CLI.display("Error launching front-end: %s" % str(e), level=logging.ERROR)
            sys.exit(1)
        CLI.display("Infrastructure successfully created with ID: %s" % infrId, alt=infrId)

        vm_id = CmdLaunch.get_front_vm_id(options.restapi[0], infrId, vm_ids, auth_data)

        CmdLaunch.wait_transfer_save(infrId, vm_id, auth_data, options.restapi[0], options.destroy,
                                     options.not_store, options.clustername, options.print_radl, options.json)
        sys.exit(0)

    @staticmethod
    def get_front_vm_id(im_server_url, infrId, vm_ids, auth_data):
        try:
            if len(vm_ids) == 1:
                return vm_ids[0]
            else:
                for vm_id in vm_ids:
                    headers = {"Authorization": format_rest_auth_data(auth_data), "Accept": "text/*"}
                    url = "%s/infrastructures/%s/vms/%s" % (im_server_url, infrId, vm_id)
                    resp = requests.request("GET", url, verify=False, headers=headers)
                    if resp.status_code != 200:
                        raise Exception(resp.text)
                    radl = parse_radl(str(resp.text))
                    if radl.get(system("front")):
                        return vm_id
                CLI.display("Error getting front-end VM ID. Assuming 0.", level=logging.WARNING)
        except Exception as e:
            CLI.display("Error getting front-end VM ID: %s. Assuming 0." % str(e), level=logging.WARNING)
        return "0"

    @staticmethod
    def generate_radl(template_names, pieces_radl, auth_content):
        radl = RADL([system("front", [Feature("ec3_templates_cmd", "=", " ".join(template_names))])])
        templates = set()
        def add_template(r):
            # Process every template only once
            if r in templates: return
            templates.add(r)
            try:
                file_content = get_content_from_template_file(r + ".radl")
                radl.merge(parse_radl(file_content), ifpresent="merge", missing="other", conflict="other")
            except Exception as e:
                CLI.display("Error processing '%s.radl': %s" % (r, str(e)), level=logging.ERROR, exception=True)
                sys.exit(1)
        for r in template_names: add_template(r)
        for r in pieces_radl:
            try:
                radl.merge(parse_radl(r), ifpresent="merge", missing="other", conflict="other")
            except Exception as e:
                CLI.display("Error processing '%s': %s" % (r, str(e)), level=logging.ERROR, exception=True)
                sys.exit(1)
        while radl.gets(include):
            for a in radl.gets(include):
                for r in a.getValue("template").split(" "):
                    if r: add_template(r)
                radl.delete(a)
        for a in radl.gets(description): radl.delete(a)

        # Set auth in system "front"
        if radl.get(system("front")):
            radl.get(system("front")).setValue("auth", "".join(auth_content).replace("'","\047"))

        # Replace "ec3_*" expression in YAML recipes and systems
        try:
            apply_ec3_features(radl)
            apply_ec3_expressions(radl)
        except Exception as e:
            CLI.display(str(e), level=logging.ERROR, exception=True)
            sys.exit(1)

        # Check if the generated RADL is correct
        CmdLaunch.check_radl(radl)

        return radl

    @staticmethod
    def check_radl(radl):
        """
        Check that the RADL generated is "EC3 correct".
        """ 
        radl.check()
        
        if not radl.get(system("front")):
            raise Exception("There must be at least one system with name 'front'")
        else:
            front = radl.get(system("front"))
            if not front.getValue("disk.0.image.url"):
                raise Exception("system 'front' has no image URL.")

        if not radl.get(system("wn")):
            raise Exception("There must be at least one system with name 'wn'")
        else:
            wn = radl.get(system("wn"))
            if not wn.getValue("disk.0.image.url"):
                raise Exception("system 'wn' has no image URL.")

        dep_front = None
        for dep in radl.gets(deploy):
            if dep.id == "front" and dep.vm_number == 1:
                dep_front = dep

        if not dep_front:
            raise Exception("There must be at least one deploy with name 'front' and VM number 1")

        return True

    @staticmethod
    def get_vm_info(im_server_url, infrId, vmId, auth_data):
        """
        Get VM info with a number of retries
        """
        success = False
        cont = 0
        info = "Error getting VM info max number of attempts reached."
        while not success and cont < STATE_CHECK_RETRIES:
            cont += 1
            try:
                headers = {"Authorization": format_rest_auth_data(auth_data), "Accept": "text/*"}
                url = "%s/infrastructures/%s/vms/%s" % (im_server_url, infrId, vmId)
                resp = requests.request("GET", url, verify=False, headers=headers)
                if resp.status_code != 200:
                    raise Exception(resp.text)
                info = str(resp.text)
                success = True
            except Exception as vminfoex:
                info = str(vminfoex)
            if not success:
                CLI.display("WARNING: Error getting VM info: %s (%d/%d)." % (info, cont, STATE_CHECK_RETRIES))
                time.sleep(DELAY)

        return success, info

    @staticmethod
    def wait_transfer_save(infrId, vmId, auth_data, im_server_url, options_destroy, options_not_store,
                           options_clustername, options_print_radl, options_json):
        # Waiting for front-end configured
        vm_ip = None
        CLI.display_waiting("Front-end state: launching")
        time0 = time.time()
        system_front = None
        res = None
        radl = None
        try:
            while True:
                success, info = CmdLaunch.get_vm_info(im_server_url, infrId, vmId, auth_data)
                CLI.logger.debug("Front-end info: %s" % info)
                radl = parse_radl(info)
                radl.check()

                headers = {"Authorization": format_rest_auth_data(auth_data), "Accept": "application/json"}
                url = "%s/infrastructures/%s/state" % (im_server_url, infrId)
                resp = requests.request("GET", url, verify=False, headers=headers)
                if resp.status_code != 200:
                    raise Exception(resp.text)
                state_info = resp.json()['state']

                system_front = radl.get(system("front"))
                vm_ip = str(getPublicIP(system_front))
                #state = system_front.getValue("state")
                state = str(state_info['state'])
                system_front.setValue("__im_server", im_server_url)
                system_front.setValue("__infrastructure_id", infrId)
                system_front.setValue("__vm_id", vmId)
                system_front.setValue("auth", json.dumps(auth_data))
                system_front.setValue("nodes", 0)
                if not options_not_store: ClusterStore.save(options_clustername, radl)
                if state == system.CONFIGURED: break
                if state in [system.FAILED, system.UNCONFIGURED]:
                    headers = {"Authorization": format_rest_auth_data(auth_data), "Accept": "text/*"}
                    url = "%s/infrastructures/%s/contmsg" % (im_server_url, infrId)
                    resp = requests.request("GET", url, verify=False, headers=headers)
                    raise Exception(resp.text)

                if state not in system.IS_ACCESSIBLE: raise Exception("Frontend in incorrect state: %s." % state)
                if time.time() - time0 > TIMEOUT:
                    raise Exception("Time out! Infrastructure is not still configured!")
                Display.display_waiting("Front-end state: %s%s" % (state, ", IP: %s" % vm_ip if vm_ip else ""),
                                        DELAY)
        except Exception as e:
            CLI.display("Error while configuring the infrastructure: %s" % str(e), level=logging.ERROR, exception=True)
            if options_destroy:
                try:
                    headers = {"Authorization": format_rest_auth_data(auth_data), "Accept": "text/*"}
                    url = "%s/infrastructures/%s" % (im_server_url, infrId)
                    resp = requests.request("DELETE", url, verify=False, headers=headers)
                    if resp.status_code != 200:
                        raise Exception(resp.text)
                    CLI.display("Infrastructure successfully destroyed")
                except Exception as e:
                    CLI.display("Error while destroying the infrastructure: %s" % str(e), level=logging.ERROR)
                sys.exit(1)
        else:
            CLI.display("Front-end configured with IP %s" % vm_ip)

            # Transfer infrastructure
            CLI.display("Transferring infrastructure")
            im_port = get_out_port(radl, 8800)
            new_im_server_url = "http://%s:%s" % (vm_ip, im_port)
            try:
                if new_im_server_url != im_server_url:
                    headers = {"Authorization": format_rest_auth_data(auth_data), "Accept": "application/json"}
                    url = "%s/infrastructures/%s/data" % (im_server_url, infrId)
                    resp = requests.request("GET", url, verify=False, headers=headers)
                    if resp.status_code != 200:
                        raise Exception(resp.text)
                    strInf = resp.json()["data"]

                    headers = {"Authorization": format_rest_auth_data(auth_data), "Accept": "text/*"}
                    url = "%s/infrastructures" % new_im_server_url
                    resp = requests.request("PUT", url, verify=False, headers=headers, data=strInf)
                    if resp.status_code != 200:
                        raise Exception(resp.text)
                    newInfrId = os.path.basename(resp.text)
                else:
                    newInfrId = infrId
                system_front.delValue("__im_server")
                system_front.setValue("__infrastructure_id", newInfrId)
                ClusterStore.save(options_clustername, radl)
                if new_im_server_url != im_server_url:
                    headers = {"Authorization": format_rest_auth_data(auth_data)}
                    url = "%s/infrastructures/%s/data?delete=yes" % (im_server_url, infrId)
                    resp = requests.request("GET", url, verify=False, headers=headers)
                    if resp.status_code != 200:
                        raise Exception(resp.text)
                
                headers = {"Authorization": format_rest_auth_data(auth_data)}
                url = "%s/infrastructures/%s" % (new_im_server_url, newInfrId)
                resp = requests.request("GET", url, verify=False, headers=headers)
                if resp.status_code != 200:
                    raise Exception(resp.text)
            except Exception as e:
                CLI.display("Error transferring infrastructure: %s" % str(e), level=logging.ERROR)
            CLI.display("Front-end ready!")
        finally:
            if system_front and not options_not_store:
                system_front.setValue("nodes", len(res if res else [0])-1)
                system_front.setValue("auth", json.dumps(auth_data))
                system_front.setValue("__infrastructure_id", infrId)
                ClusterStore.save(options_clustername, radl)


            # Print cluster
            if radl and options_print_radl:
                output = dump_radl_json(radl) if options_json else dump_radl(radl)
                CLI.display(output + "\n")

class CmdList:
    @staticmethod
    def parse(subparsers):
        parser = subparsers.add_parser("list", help="list launched clusters")
        parser.add_argument("--json", action="store_true", default=False, help="output information in JSON format")
        parser.add_argument("-r", "--refresh", action="store_true", default=False, help="update state of the clusters")
        parser.add_argument("-un", "--username", dest="username", nargs=1, default="", help="indicate the username you want to list deployed clusters")
        parser.set_defaults(func=CmdList.run)

    @staticmethod    
    def run(options):                 
        info = []
        for clustername in ClusterStore.list():
            s = ClusterStore.load(clustername, options.refresh).get(system("front"))
            if options.username and options.username[0] != "":   
                cluster_username = s.getValue("ec3aas.username", "?")
                if cluster_username == options.username[0]:
                    info.append({"name": clustername, "state": s.getValue("state", "?"), "IP": getPublicIP(s),
                                 "nodes": s.getValue("nodes", "?"), "provider": s.getValue("provider.type", "?")})
            else:            
                info.append({"name": clustername, "state": s.getValue("state", "?"), "IP": getPublicIP(s),
                             "nodes": s.getValue("nodes", "?"), "provider": s.getValue("provider.type", "?")})
        output = dump_json(info) if options.json else dump_table(info, ["name", "state", "IP", "nodes", "provider"])
        sys.stdout.write(output + "\n")

class CmdShow:
    @staticmethod
    def parse(subparsers):
        parser = subparsers.add_parser("show", help="print RADL")
        parser.add_argument("clustername", help="name of the cluster to show")
        parser.add_argument("--json", action="store_true", default=False, help="output information in JSON format")
        parser.add_argument("-r", "--refresh", action="store_true", default=False, help="updated state of the clusters")
        parser.set_defaults(func=CmdShow.run)

    @staticmethod
    def run(options):
        try:
            r = ClusterStore.load(options.clustername, options.refresh)
            output = dump_radl_json(r) if options.json else dump_radl(r)
            CLI.display(output + "\n")
        except Exception as e:
            CLI.display("Error showing cluster '%s': %s" % (options.clustername, str(e)),
                        level=logging.ERROR, exception=True)

class CmdSsh:
    @staticmethod
    def parse(subparsers):
        parser = subparsers.add_parser("ssh", help="connect to cluster via SSH", config=False)
        parser.add_argument("clustername", help="name of the cluster to connect")
        parser.add_argument("sshcommand", nargs="*", help="ssh command to be executed on the font-end", default=[])
        parser.add_argument("--show-only", action="store_true", dest="show_only", default=False, help="show the command line invoking SSH, but do nothing")
        parser.set_defaults(func=CmdSsh.run)

    @staticmethod
    def run(options):
        try:
            radl = ClusterStore.load(options.clustername)
            front_system = radl.get(system("front"))
            ssh_port = get_out_port(radl, 22)
            if front_system.getValue("disk.0.os.credentials.private_key"):
                ops = CmdSsh._connect_key(front_system, ssh_port)
            else:
                ops = CmdSsh._connect_password(front_system, ssh_port)
            if options.sshcommand:
                ops.extend(options.sshcommand)
            if options.show_only:
                CLI.display(" ".join(ops))
                sys.exit(0)
            os.execlp(ops[0], *ops)
        except OSError as e:
            CLI.display("Error connecting to cluster '%s': %s\n"
                        "Probably 'sshpass' or 'ssh' program is not installed!" % (options.clustername, str(e)),
                        level=logging.ERROR)
            sys.exit(1)
        except Exception as e:
            CLI.display("Error connecting to cluster '%s': %s" % (options.clustername, str(e)),
                        level=logging.ERROR)
            sys.exit(1)

    @staticmethod
    def _connect_password(s, ssh_port):
        return ["sshpass", "-p%s" % s.getValue("disk.0.os.credentials.password"),
                "ssh", "-o", "UserKnownHostsFile=/dev/null", "-o", "StrictHostKeyChecking=no",
                "%s@%s" % (s.getValue("disk.0.os.credentials.username"), getPublicIP(s)),
                "-p %s" % ssh_port]

    @staticmethod
    def _connect_key(s, ssh_port):
        f = tempfile.NamedTemporaryFile(mode="w", delete=False)
        f.write(s.getValue("disk.0.os.credentials.private_key"))
        f.close()
        return ["ssh", "-i", f.name, "-o", "UserKnownHostsFile=/dev/null",
                "-o", "StrictHostKeyChecking=no",
                "%s@%s" % (s.getValue("disk.0.os.credentials.username"), getPublicIP(s)),
                "-p %s" % ssh_port]

class CmdDestroy:
    @staticmethod
    def parse(subparsers):
        parser = subparsers.add_parser("destroy", help="destroy a launched cluster")
        parser.add_argument("clustername", help="name of the cluster to destroy")
        parser.add_argument("-u", "--restapi-url", dest="restapi", nargs=1, default=[IM_URL], help="URL to IM REST API service. If not indicated, EC3 uses default value (%s)." % IM_URL)
        parser.add_argument("--force", action="store_true", default=False, help="destroy the local information of the cluster anyway")
        parser.add_argument("-y", "--yes", action="store_true", dest="yes", default=False, help="say yes to the warning about deleting the cluster")
        parser.add_argument("-a", "--auth-file", type=argparse.FileType('r'), dest="auth_file", nargs=1, help="authorization file", config_default="""    # auth_file: |
    #   type = OpenNebula; host = myone.com:9999; username = user; password = 1234
    #   type = EC2; username = AKIAAAAAAAAAAAAAAAAA; password = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa""")
        parser.set_defaults(func=CmdDestroy.run)

    @staticmethod
    def run(options):
        # If the user indicates an IM URL, we use it, if not we use the default IM
        if options.restapi:
            im_server_url = options.restapi[0]
        else:
            im_server_url = IM_URL
        try:
            r = ClusterStore.load(options.clustername)
            CLI.display("WARNING: you are going to delete the infrastructure (including frontend and nodes).")
            if not options.yes and get_input("Continue [y/N]? ")[0:1].lower() != "y":
                sys.exit(1)
            new_im_server_url, infrId, _, auth = ClusterStore.get_im_server_infrId_and_vmId_and_auth(r)
            if options.auth_file:
                auth_content = [ str(l) for l in options.auth_file[0].readlines() ]
                auth = Authentication.read_auth_data(auth_content)

            #First, check if the infrastructure still remains in the external IM, due to problems installing internal IM
            try:
                headers = {"Authorization": format_rest_auth_data(auth)}
                url = "%s/infrastructures/%s" % (im_server_url, infrId)
                resp = requests.request("DELETE", url, verify=False, headers=headers)
                if resp.status_code == 200:
                    CLI.display("Success deleting the cluster!")
                    ClusterStore.remove(options.clustername)
                    sys.exit(0)
            except Exception as e:
                CLI.display("Error destroying cluster '%s': %s" % (options.clustername, str(e)),
                        level=logging.ERROR)

            #Import the infrastructure to the external IM for the destroy operation
            try:
                headers = {"Authorization": format_rest_auth_data(auth), "Accept": "application/json"}
                url = "%s/infrastructures/%s/data" % (new_im_server_url, infrId)
                resp = requests.request("GET", url, verify=False, headers=headers)
                if resp.status_code != 200:
                    raise Exception(resp.text)
                strInf = resp.json()["data"]

                headers = {"Authorization": format_rest_auth_data(auth), "Accept": "text/*"}
                url = "%s/infrastructures" % im_server_url
                resp = requests.request("PUT", url, verify=False, headers=headers, data=strInf)
                if resp.status_code != 200:
                    raise Exception(resp.text)
                newInfrId = os.path.basename(resp.text)
            except Exception as e:
                CLI.display("Error transferring infrastructure: %s" % str(e), level=logging.ERROR)
                if options.force:
                    ClusterStore.remove(options.clustername)
                sys.exit(1)
            headers = {"Authorization": format_rest_auth_data(auth)}
            url = "%s/infrastructures/%s" % (im_server_url, newInfrId)
            resp = requests.request("DELETE", url, verify=False, headers=headers)
            if resp.status_code != 200:
                raise Exception(resp.text)
            CLI.display("Success deleting the cluster!")
        except Exception as e:
            CLI.display("Error destroying cluster '%s': %s" % (options.clustername, str(e)),
                        level=logging.ERROR)
            if options.force:
                ClusterStore.remove(options.clustername)
            sys.exit(1)
        ClusterStore.remove(options.clustername)


class CmdReconfigure:
    @staticmethod
    def parse(subparsers):
        parser = subparsers.add_parser("reconfigure", help="reconfigure the cluster", config=False)
        parser.add_argument("clustername", help="name of the cluster")
        parser.add_argument("--add", action="append", help="add a piece of RADL")
        parser.add_argument("-a", "--auth-file", type=argparse.FileType('r'), dest="auth_file", nargs=1, help="append new entries to the authorization file")
        parser.add_argument("-r", "--reload", action="store_true", dest="reload", default=False, help="reload templates used to launch the cluster and reconfigure the cluster with them (useful if templates changed)")
        parser.add_argument("--template", "-t",dest="new_template", help="add a new template/recipe of RADL to the cluster")
        parser.set_defaults(func=CmdReconfigure.run)

    @staticmethod
    def run(options):
        # Check cluster name, authentication and IM server
        try:
            r = ClusterStore.load(options.clustername)
            im_server_url, infrId, vmId, auth_data = ClusterStore.get_im_server_infrId_and_vmId_and_auth(r)
            auth_content = Authentication.dump(auth_data)
        except Exception as e:
            CLI.display(str(e), level=logging.ERROR)
            sys.exit(1)
        try:
            if options.auth_file:
                auth_content = [ str(l) for l in options.auth_file[0].readlines() ]
                auth_data = Authentication.read_auth_data(auth_content)
                if not any([ a.get("type", None) == "InfrastructureManager" for a in auth_data ]):
                    auth_content.append("type = InfrastructureManager; username = %x; password = %x" %
                                    (random.random()*1e15, random.random()*1e15))
                auth_data = Authentication.read_auth_data(auth_content)
        except Exception as e:
            CLI.display("Error in -a/--auth-file: %s" % str(e), level=logging.ERROR)
            sys.exit(1)

        # Instantiate templates
        if options.auth_file or options.reload or options.add or options.new_template:
            templates = r.get(system("front")).getValue("ec3_templates_cmd", "").split(" ")
            if options.new_template:
                templates.append(options.new_template)
            radl = CmdLaunch.generate_radl(templates, options.add if options.add else [], auth_content)
            for a in radl.gets(deploy): radl.delete(a)
            radl = dump_radl(radl)
        else:
            radl = ""

        # Reconfigure infrastructure
        CLI.display("Reconfiguring infrastructure")
        try:
            headers = {"Authorization": format_rest_auth_data(auth_data)}
            url = "%s/infrastructures/%s/reconfigure" % (im_server_url, infrId)
            resp = requests.request("PUT", url, verify=False, headers=headers, data=radl)
            if resp.status_code != 200:
                raise Exception(resp.text)
        except Exception as e:
            CLI.display("Error reconfiguring front-end: %s" % str(e), level=logging.ERROR)
            sys.exit(1)

        CmdLaunch.wait_transfer_save(infrId, vmId, auth_data, im_server_url, False, False,
                                     options.clustername, False, False)
        sys.exit(0)

class CmdTemplates:
    @staticmethod
    def parse(subparsers):
        parser = subparsers.add_parser("templates", help="list available templates")
        parser.add_argument("-s", "--search", dest="search", nargs=1, default=[None], help="show only templates in which the expression appears", config=False)
        parser.add_argument("-n", "--name", dest="name", nargs=1, default=[None], help="show only the template with this name", config=False)
        parser.add_argument("--json", action="store_true", default=False, help="output information in JSON format")
        parser.add_argument("-f", "--full-description", dest="full", action="store_true", default=False, help="show full description for every template")
        parser.set_defaults(func=CmdTemplates.run)

    @staticmethod
    def run(options):
        info = []
        templates = set()
        search = re.compile(options.search[0], re.IGNORECASE) if options.search[0] else None
        for t in [ f.rsplit(".")[0] for p in TEMPLATE_PATHS if os.path.isdir(os.path.expanduser(p)) for f in os.listdir(os.path.expanduser(p)) if os.path.isfile(os.path.join(os.path.expanduser(p), f)) and f.endswith("radl") ]:
            if t in templates: continue
            if options.name[0] and options.name[0] != t: continue
            templates.add(t)
            try:
                r = parse_radl(get_content_from_template_file(t + ".radl"))
            except Exception as e:
                CLI.display("Error processing '%s': %s" % (t, str(e)), level=logging.ERROR, exception=True)
                sys.exit(1)
            for d in r.gets(description):
                if (search and not re.search(search, " ".join([ t, d.getValue("kind", ""), d.getValue("short", ""),
                                                                d.getValue("content", "") ]))): break
                info.append(dict(name=t, kind=d.getValue("kind", ""), summary=d.getValue("short", "")[0:80],
                                 description=d.getValue("content", "")))
        info = sorted(info, key=lambda x: x["name"])
        if options.json:
            output = dump_json(info)
        elif options.full:
            output = dump_list(info, ["name", "kind", "summary", "description"])
        else:
            output = dump_table(info, ["name", "kind", "summary"], {"summary": "<"})
        sys.stdout.write(output + "\n")

class CmdClone:
    @staticmethod
    def parse(subparsers):
        parser = subparsers.add_parser("clone", help="clone a launched cluster in another Cloud provider")
        parser.add_argument("clustername", help="name of the cluster you want to clone")
        parser.add_argument("-u", "--restapi-url", dest="restapi", nargs=1, default=[IM_URL], help="URL to IM REST API service. If not indicated, EC3 uses default value (%s)." % IM_URL)
        parser.add_argument("-d", "--destination", dest="destination", nargs=1, help="Cloud infrastructure destination for the cloned cluster")
        parser.add_argument("-a", "--auth-file", type=argparse.FileType('r'), dest="auth_file", nargs=1, help="Authorization file")
        parser.add_argument("-e", "--eliminate", action="store_true", dest="eliminate", default=False, help="Force to destroy the original cluster after its clonation")
        parser.set_defaults(func=CmdClone.run)

    # TODO: De momento clonar la infraestructura y luego ver que pasa con las aplicaciones
    @staticmethod
    def run(options):
        # If the user indicates an IM URL, we use it, if not we use the default value
        if options.restapi:
            im_server_url = options.restapi[0]
        else:
            im_server_url = IM_URL  

        # Check cluster name, authentication and IM server
        try:
            r = ClusterStore.load(options.clustername)
            im_server_url, infrId_old, _, auth_data = ClusterStore.get_im_server_infrId_and_vmId_and_auth(r)
        except Exception as e:
            CLI.display(str(e), level=logging.ERROR)
            sys.exit(1)

        # Para decirle al IM en que cloud lanzar el cluster, se usa la primera linea del auth file (a no ser que se indique lo contrario en el deploy).
        # Si el usuario no nos da un nuevo auth file ya ordenado, reordenar el auth_data
        try:
            if options.auth_file:
                auth_content = [ str(l) for l in options.auth_file[0].readlines() ]
                auth_data = Authentication.read_auth_data(auth_content)
                if not any([ a.get("type", None) == "InfrastructureManager" for a in auth_data ]):
                    auth_content.append("type = InfrastructureManager; username = %x; password = %x" %
                                    (random.random()*1e15, random.random()*1e15))
                auth_data = Authentication.read_auth_data(auth_content)
            elif options.destination:
                for auth in auth_data:
                    if auth.has_key('id') and auth['id'] == options.destination[0]:
                        dest = auth_data.pop(auth_data.index(auth))
                        auth_data.insert(0, dest)
            else:
                auth_data.reverse()
        except Exception as e:
            CLI.display("Error in -a/--auth-file: %s" % str(e), level=logging.ERROR)
            sys.exit(1)

        # Recover RADL
        radl = ""
        try:
            headers = {"Authorization": format_rest_auth_data(auth_data), "Accept": "text/*"}
            url = "%s/infrastructures/%s/radl" % (im_server_url, infrId_old)
            resp = requests.request("GET", url, verify=False, headers=headers)
            if resp.status_code != 200:
                raise Exception(resp.text)
            radl = resp.text
        except Exception as e:
            CLI.display("Error obtaining RADL info: %s" % str(e), level=logging.ERROR)
            sys.exit(1)

        # Modificamos el deploy del RADL para no indique en que infraestructura lanzar (y lance en la primera del auth file)
        radl_obj = parse_radl(radl)
        for d in radl_obj.gets(deploy):
                radl_obj.delete(d)
        new_deploy = deploy('front', 1)
        radl_obj.add(new_deploy)

        # Tambien hay que eliminar la informacion de deploys, configures y systems referente a posibles nodos desplegados en la infraestructura origen
        # ec3 clone solo clona el frontend, los nodos se encargara CLUES de volver a encenderlos cuando migremos las tareas
        for s in radl_obj.gets(system):
            if s.getValue("ec3_class") is not None:
                radl_obj.delete(s)
                for c in radl_obj.gets(configure):
                    if c.getId() == s.getId():
                        radl_obj.delete(c)

        radl = dump_radl(radl_obj)

        # Clone infrastructure
        CLI.display("Cloning infrastructure")

        try:
            #print auth_data
            headers = {"Authorization": format_rest_auth_data(auth_data)}
            url = "%s/infrastructures" % im_server_url
            resp = requests.request("POST", url, verify=False, headers=headers, data=radl)
            if resp.status_code != 200:
                raise Exception(resp.text)
            infrId = os.path.basename(resp.text)

            headers = {"Authorization": format_rest_auth_data(auth_data), "Accept": "application/json"}
            CLI.display("Success creating infrastructure " + str(infrId))
            url = "%s/infrastructures/%s" % (im_server_url, infrId)
            resp = requests.request("GET", url, verify=False, headers=headers)
            if resp.status_code != 200:
                raise Exception(resp.text)
            vm_ids = []
            for elem in resp.json()["uri-list"]:
                vm_ids.append(os.path.basename(list(elem.values())[0]))
        except Exception as e:
            CLI.display("Error cloning front-end: %s" % str(e), level=logging.ERROR)
            sys.exit(1)
        new_clustername = options.clustername + "_cloned"

        vm_id = CmdLaunch.get_front_vm_id(im_server_url, infrId, vm_ids, auth_data)

        CmdLaunch.wait_transfer_save(infrId, vm_id, auth_data, im_server_url, False, False, new_clustername, False, False)
        CLI.display("Infrastructure successfully cloned with ID: " + str(infrId) + ". The cluster name now is: " + new_clustername)

        # Destroy the old cluster if the user has requested it
        if options.eliminate:
            CLI.display("Finally, let's delete the old cluster")
            headers = {"Authorization": format_rest_auth_data(auth_data)}
            url = "%s/infrastructures/%s" % (im_server_url, infrId_old)
            resp = requests.request("DELETE", url, verify=False, headers=headers)
            ClusterStore.remove(options.clustername)
            if resp.status_code != 200:
                CLI.display("Error deleting the old cluster!")
                raise Exception(resp.text)
            else:
                CLI.display("Success deleting the old cluster!")
        
        CLI.display("Clone process completed")
        sys.exit(0)

class CmdMigrate:
    @staticmethod
    def parse(subparsers):
        parser = subparsers.add_parser("migrate", help="migrate a launched cluster together with its workload to another Cloud provider")
        parser.add_argument("clustername", help="name of the cluster you want to clone")
        parser.add_argument("-u", "--restapi-url", dest="restapi", nargs=1, default=[IM_URL], help="URL to IM REST API service. If not indicated, EC3 uses default value (%s)." % IM_URL)
        parser.add_argument("-d", "--destination", dest="destination", nargs=1, help="Cloud infrastructure destination for the cloned cluster")
        parser.add_argument("-a", "--auth-file", type=argparse.FileType('r'), dest="auth_file", nargs=1, help="Authorization file")
        parser.add_argument("-b", "--bucket", dest="bucket", nargs=1, help="Name of the already created bucket where to upload ckpt files")
        parser.add_argument("-e", "--eliminate", action="store_true", dest="eliminate", default=False, help="Force to destroy the original cluster after its migration")
        parser.set_defaults(func=CmdMigrate.run)

    @staticmethod
    def run(options):
        # If the user indicates an IM URL, we use it, if not we use the default value
        if options.restapi:
            im_server_url = options.restapi[0]
        else:
            im_server_url = IM_URL  

        # Check if the user has given a bucket name
        if options.bucket:
            CLI.display("The S3 bucket used to copy ckpt files is: " + str(options.bucket[0]))
        else:
            CLI.display("Error getting S3 bucket: please indicate an already created S3 bucket to perform the migration operation", level=logging.ERROR)
            sys.exit(1)  

        # Check cluster name, authentication and IM server
        try:
            r = ClusterStore.load(options.clustername)
            im_server_url, infrId, _, auth_data = ClusterStore.get_im_server_infrId_and_vmId_and_auth(r)
        except Exception as e:
            CLI.display(str(e), level=logging.ERROR)
            sys.exit(1)

        # Para decirle al IM en que cloud lanzar el cluster, se usa la primera linea del auth file (a no ser que se indique lo contrario en el deploy).
        # Si el usuario no nos da un nuevo auth file ya ordenado, reordenar el auth_data
        access_key = ""
        secret_key = ""
        try:
            if options.auth_file:
                auth_content = [ str(l) for l in options.auth_file[0].readlines() ]
                auth_data = Authentication.read_auth_data(auth_content)
                if not any([ a.get("type", None) == "EC2" for a in auth_data ]):
                    CLI.display("Error obtaining AWS credentials. Impossible to connect with S3 to upload ckpt files. Check your auth file", level=logging.ERROR)
                    sys.exit(1)
                else:
                    for a in auth_data:
                        if a.has_key('id'):
                            if a['id'] == "ec2" or a['id'] == "s3":
                                access_key = a['username']
                                secret_key = a['password']
                if not any([ a.get("type", None) == "InfrastructureManager" for a in auth_data ]):
                    auth_content.append("type = InfrastructureManager; username = %x; password = %x" %
                                    (random.random()*1e15, random.random()*1e15))
                auth_data = Authentication.read_auth_data(auth_content)
            elif options.destination:
                for auth in auth_data:
                    if auth.has_key('id') and auth['id'] == options.destination[0]:
                        dest = auth_data.pop(auth_data.index(auth))
                        auth_data.insert(0, dest)
            else:
                auth_data.reverse()
        except Exception as e:
            CLI.display("Error in -a/--auth-file: %s" % str(e), level=logging.ERROR)
            sys.exit(1)

        # Recover RADL
        radl = ""
        try:
            headers = {"Authorization": format_rest_auth_data(auth_data)}
            url = "%s/infrastructures/%s/radl" % (im_server_url, infrId)
            resp = requests.request("GET", url, verify=False, headers=headers)
            if resp.status_code != 200:
                raise Exception(resp.text)
            radl = resp.text
        except Exception as e:
            CLI.display("Error obtaining RADL info: %s" % str(e), level=logging.ERROR)
            sys.exit(1)

        # Comprobar que el cluster ha sido lanzado con blcr y slurm, en otro caso emitir un error y acabar ejecucion
        radl_obj = parse_radl(radl)
        for s in radl_obj.gets(system):
            if s.getId() == "front":
                if "blcr" not in s.getValue("ec3_templates_cmd") or "slurm" not in s.getValue("ec3_templates_cmd"):
                    CLI.display("Error migrating the cluster: Cluster %s is not a SLURM cluster with BLCR installed, so the migration process can't be performed." % str(options.clustername), level=logging.ERROR)
                    sys.exit(1)

        # Modificamos el deploy del RADL para no indique en que infraestructura lanzar (y lance en la primera del auth file)
        radl_obj = parse_radl(radl)
        for d in radl_obj.gets(deploy):
                radl_obj.delete(d)
        new_deploy = deploy('front', 1)
        radl_obj.add(new_deploy)

        # Tambien hay que eliminar la informacion de deploys, configures y systems referente a posibles nodos desplegados en la infraestructura origen
        # ec3 clone solo clona el frontend, los nodos se encargara CLUES de volver a encenderlos cuando migremos las tareas
        for s in radl_obj.gets(system):
            if s.getValue("ec3_class") is not None:
                radl_obj.delete(s)
                for c in radl_obj.gets(configure):
                    if c.getId() == s.getId():
                        radl_obj.delete(c)

        radl = dump_radl(radl_obj)

        # Clone infrastructure
        CLI.display("Cloning infrastructure")

        try:
            headers = {"Authorization": format_rest_auth_data(auth_data)}
            url = "%s/infrastructures" % im_server_url
            resp = requests.request("POST", url, verify=False, headers=headers, data=radl)
            if resp.status_code != 200:
                raise Exception(resp.text)
            infrId2 = os.path.basename(resp.text)
            CLI.display("Success creating infrastructure " + str(infrId2))

            headers = {"Authorization": format_rest_auth_data(auth_data), "Accept": "application/json"}
            url = "%s/infrastructures/%s" % (im_server_url, infrId2)
            resp = requests.request("GET", url, verify=False, headers=headers)
            if resp.status_code != 200:
                raise Exception(resp.text)
            vm_ids = []
            for elem in resp.json()["uri-list"]:
                vm_ids.append(os.path.basename(list(elem.values())[0]))
        except Exception as e:
            CLI.display("Error cloning front-end: %s" % str(e), level=logging.ERROR)
            sys.exit(1)
        new_clustername = options.clustername + "_cloned"

        vm_id = CmdLaunch.get_front_vm_id(im_server_url, infrId2, vm_ids, auth_data)

        CmdLaunch.wait_transfer_save(infrId2, vm_id, auth_data, im_server_url, False, False, new_clustername, False, False)
        CLI.display("Infrastructure successfully cloned with ID: " + str(infrId2) + ". The cluster name now is: " + new_clustername)

        # Obtain the job list running in the cluster
        front_system = ClusterStore.load(options.clustername).get(system("front"))
        ip = getPublicIP(front_system)
        user = front_system.getValue("disk.0.os.credentials.username")
        key_pem = ""
        if front_system.getValue("disk.0.os.credentials.private_key"):
            f = tempfile.NamedTemporaryFile(mode="w", delete=False)
            f.write(front_system.getValue("disk.0.os.credentials.private_key"))
            f.close()
            key_pem = f.name
        else:
            passwd = front_system.getValue("disk.0.os.credentials.password")
        
        job_list = CmdMigrate.get_job_info(ip, user, passwd, key_pem)
        
        if len(job_list) > 0:
            # Checkpoint all the jobs running in the cluster
            for key in job_list.keys():
                try:
                    CLI.display("Performing a checkpoint to the job "+ key)
                    if key_pem == "":
                        run_command("sshpass -p '" + str(passwd) +"' ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no " + str(user) + "@" + str(ip) + " scontrol checkpoint create " + key)
                    else:
                        run_command("ssh -i '"+ str(key_pem) +"' -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no " + str(user) + "@" + str(ip) + " scontrol checkpoint create " + key)
                    CLI.display("Checkpointing performed successfully.")
                except: 
                    CLI.display("Error checkpointing the job: %s" % str(key), level=logging.ERROR)
            
            # Create the credentials file with the credentials of the user to connect with S3
            try:
                if key_pem == "":
                    run_command("sshpass -p '" + str(passwd) +"' ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no " + str(user) + "@" + str(ip) + " 'echo -e \"[default]\naws_access_key_id=" + access_key + "\naws_secret_access_key=" + secret_key + "\" > /home/ubuntu/.aws/credentials'")
                else:
                    run_command("ssh -i '"+ str(key_pem) +"' -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no " + str(user) + "@" + str(ip) + " 'echo -e \"[default]\naws_access_key_id=" + access_key + "\naws_secret_access_key=" + secret_key + "\" > /home/ubuntu/.aws/credentials'")
            except: 
                CLI.display("Error creating credentials file for awscli", level=logging.ERROR)

            # Copy the checkpoint files to a bucket in Amazon S3
            for key in job_list.keys():
                try:
                    CLI.display("Uploading checkpoint files to S3")
                    if key_pem == "":
                        run_command("sshpass -p '" + str(passwd) +"' ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no " + str(user) + "@" + str(ip) + " 'sudo aws s3 cp /var/slurm/checkpoint/" + key + ".ckpt s3://" + str(options.bucket[0]) + "/" + key + ".ckpt'")
                    else:
                        run_command("ssh -i '"+ str(key_pem) +"' -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no " + str(user) + "@" + str(ip) + " sudo aws s3 cp /var/slurm/checkpoint/" + key + ".ckpt s3://" + str(options.bucket[0]) + "/" + key + ".ckpt")
                    CLI.display("Upload performed succesfully")
                except:
                    CLI.display("Error while uploading checkpoint files to S3", level=logging.ERROR)

            # Obtain the IP of the front-end of the cloned cluster
            front_system_cloned = ClusterStore.load(new_clustername).get(system("front"))
            ip_cloned = getPublicIP(front_system_cloned)
            
            # Create the credentials file with the credentials of the user to connect with S3
            try:
                if key_pem == "":
                    run_command("sshpass -p '" + str(passwd) +"' ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no " + str(user) + "@" + str(ip_cloned) + " 'echo -e \"[default]\naws_access_key_id=" + access_key + "\naws_secret_access_key=" + secret_key + "\" > /home/ubuntu/.aws/credentials'")
                else:
                    run_command("ssh -i '"+ str(key_pem) +"' -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no " + str(user) + "@" + str(ip_cloned) + " 'echo -e \"[default]\naws_access_key_id=" + access_key + "\naws_secret_access_key=" + secret_key + "\" > /home/ubuntu/.aws/credentials'")
            except: 
                CLI.display("Error creating credentials file for awscli", level=logging.ERROR)
            
            # Download checkpoint files from the bucket in Amazon S3 to the cloned cluster
            for key in job_list.keys():
                try:
                    CLI.display("Downloading checkpoint files to S3")
                    if key_pem == "":
                        run_command("sshpass -p '" + str(passwd) +"' ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no " + str(user) + "@" + str(ip_cloned) + " 'sudo aws s3 cp s3://" + str(options.bucket[0]) + "/" + key + ".ckpt /var/slurm/checkpoint/" + key + ".ckpt'")
                    else:
                        run_command("ssh -i '"+ str(key_pem) +"' -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no " + str(user) + "@" + str(ip_cloned) + " 'sudo aws s3 cp s3://" + str(options.bucket[0]) + "/" + key + ".ckpt /var/slurm/checkpoint/" + key + ".ckpt'")
                    CLI.display("Download performed succesfully")
                except:
                    CLI.display("Error while downloading checkpoint files to S3", level=logging.ERROR)
                
            # Restart the jobs in the cloned cluster
            for key in job_list.keys():
                try:
                    CLI.display("Restarting the job "+ key)
                    if key_pem == "":
                        run_command("sshpass -p '" + str(passwd) +"' ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no " + str(user) + "@" + str(ip_cloned) + " scontrol checkpoint restart " + key)
                    else:
                        run_command("ssh -i '"+ str(key_pem) +"' -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no " + str(user) + "@" + str(ip_cloned) + " scontrol checkpoint restart " + key)
                    CLI.display("Restart performed successfully.")
                except DownNodeError: 
                    CLI.display("Success restarting the job %s from the checkpointing file" % str(key))
                except CommandError: 
                    CLI.display("Error restarting the job: %s" % str(key), level=logging.ERROR)
        else:
            CLI.display("There are no jobs running in the cluster.")
    
        # Destroy the old cluster if the user has requested it
        if options.eliminate:
            CLI.display("Finally, let's delete the old cluster")
            headers = {"Authorization": format_rest_auth_data(auth_data)}
            url = "%s/infrastructures/%s" % (im_server_url, infrId)
            resp = requests.request("DELETE", url, verify=False, headers=headers)
            ClusterStore.remove(options.clustername)
            if resp.status_code != 200:
                CLI.display("Error deleting the old cluster!")
                raise Exception(resp.text)
                sys.exit(1)
            CLI.display("Success deleting the old cluster!")
        
        CLI.display("Migration process completed")
        sys.exit(0)


    # Parse the exit of the scontrol SLURM command
    @staticmethod
    def parse_scontrol(out):
    #and exit != "No jobs in the system"
        if out.find("=") < 0: return []
        r = []
        for line in out.split("\n"):
            line = line.strip()
            if not line: continue
            d = {}; r.append(d); s = False; f = None
            for k in [ j for i in line.split("=") for j in i.rsplit(" ", 1) ]:
                if s: d[f] = k
                else: f = k
                s = not s
        return r

    # Obtains the list of jobs that are in execution in SLURM
    @staticmethod
    def get_job_info(ip, user, passwd, key_pem):
        if key_pem == "":
            output = CmdMigrate.parse_scontrol(run_command("sshpass -p '" + str(passwd) +"' ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no " + str(user) + "@" + str(ip) + " scontrol -o show jobs -a"))
        else:
            output = CmdMigrate.parse_scontrol(run_command("ssh -i '"+ str(key_pem) +"' -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no " + str(user) + "@" + str(ip) + " scontrol -o show jobs -a"))
        job_list = {}
        if output:
            for key in output:
                if key["JobState"] == "RUNNING": 
                    job_list [str(key["JobId"])] = str(key["BatchHost"])
        return job_list

class CmdStop:
    @staticmethod
    def parse(subparsers):
        parser = subparsers.add_parser("stop", help="stop a launched cluster")
        parser.add_argument("clustername", help="name of the cluster to stop")
        parser.add_argument("-a", "--auth-file", type=argparse.FileType('r'), dest="auth_file", nargs=1, help="authorization file")
        parser.add_argument("-y", "--yes", action="store_true", dest="yes", default=False, help="say yes to the warning about stopping the cluster")
        parser.add_argument("-u", "--restapi-url", dest="restapi", nargs=1, default=[IM_URL], help="URL to IM REST API service that is outside the cluster")
        parser.set_defaults(func=CmdStop.run)

    @staticmethod
    def run(options):
        # If the user indicates an IM URL, we use it, if not we use the default IM
        if options.restapi:
            im_server_url = options.restapi[0]
        else:
            im_server_url = IM_URL

        try:
            r = ClusterStore.load(options.clustername)
            CLI.display("WARNING: you are going to stop the infrastructure (including frontend and nodes).")
            if not options.yes and get_input("Continue [y/N]? ")[0:1].lower() != "y":
                sys.exit(1)
            cluster_im_server_url, infrId, _, auth = ClusterStore.get_im_server_infrId_and_vmId_and_auth(r)

            if options.auth_file:
                auth_content = [ str(l) for l in options.auth_file[0].readlines() ]
                auth = Authentication.read_auth_data(auth_content)

            #Import the infrastructure for the restart operation
            try:
                headers = {"Authorization": format_rest_auth_data(auth), "Accept": "application/json"}
                url = "%s/infrastructures/%s/data" % (cluster_im_server_url, infrId)
                resp = requests.request("GET", url, verify=False, headers=headers)
                if resp.status_code != 200:
                    raise Exception(resp.text)
                strInf = resp.json()["data"]

                headers = {"Authorization": format_rest_auth_data(auth), "Accept": "text/*"}
                url = "%s/infrastructures" % im_server_url
                resp = requests.request("PUT", url, verify=False, headers=headers, data=strInf)
                if resp.status_code != 200:
                    raise Exception(resp.text)
                newInfrId = os.path.basename(resp.text)
                #ids = infrId + "#" + newInfrId
                #ClusterStore.write(options.clustername + "_ids", ids)
            except Exception as e:
                CLI.display("Error transferring infrastructure: %s" % str(e), level=logging.ERROR)

            #Stop the infrastructure
            #success, info = cluster_im_server.StopInfrastructure(infrId, auth)
            headers = {"Authorization": format_rest_auth_data(auth)}
            url = "%s/infrastructures/%s/stop" % (im_server_url, newInfrId)
            resp = requests.request("PUT", url, verify=False, headers=headers)
            if resp.status_code != 200:
                raise Exception(resp.text)
            CLI.display("Success stopping the cluster!")
            sys.exit(0)
        except Exception as e:
            CLI.display("Error stopping cluster '%s': %s" % (options.clustername, str(e)),
                        level=logging.ERROR)
            sys.exit(1)


class CmdRestart:
    @staticmethod
    def parse(subparsers):
        parser = subparsers.add_parser("restart", help="restart a previously stopped cluster")
        parser.add_argument("clustername", help="name of the cluster to restart")
        parser.add_argument("-u", "--restapi-url", dest="restapi", nargs=1, default=[IM_URL], help="URL to IM REST API service that is outside the cluster")
        parser.add_argument("-a", "--auth-file", type=argparse.FileType('r'), dest="auth_file", nargs=1, help="authorization file")
        parser.set_defaults(func=CmdRestart.run)

    @staticmethod
    def run(options):
        # If the user indicates an IM URL, we use it, if not we use the default IM
        if options.restapi:
            im_server_url = options.restapi[0]
        else:
            im_server_url = IM_URL

        try:
            r = ClusterStore.load(options.clustername)
            _, cluster_infrId, _, auth = ClusterStore.get_im_server_infrId_and_vmId_and_auth(r)

            # obtengo infrId del im externo
            #ids = ClusterStore.read(options.clustername + "_ids")

            if options.auth_file:
                auth_content = [ str(l) for l in options.auth_file[0].readlines() ]
                auth = Authentication.read_auth_data(auth_content)

            headers = {"Authorization": format_rest_auth_data(auth)}
            url = "%s/infrastructures/%s/start" % (im_server_url, cluster_infrId)
            resp = requests.request("PUT", url, verify=False, headers=headers)
            if resp.status_code != 200:
                raise Exception(resp.text)

            url = "%s/infrastructures/%s/data?delete=yes" % (im_server_url, cluster_infrId)
            resp = requests.request("GET", url, verify=False, headers=headers)
            if resp.status_code != 200:
                raise Exception(resp.text)
            CLI.display("Success restarting the cluster!")
            sys.exit(0)
        except Exception as e:
            CLI.display("Error restarting cluster '%s': %s" % (options.clustername, str(e)),
                        level=logging.ERROR)
            sys.exit(1)


class CmdTransfer:
    @staticmethod
    def parse(subparsers):
        parser = subparsers.add_parser("transfer", help="transfer a previously launched cluster")
        parser.add_argument("clustername", help="name of the cluster to restart")
        parser.add_argument("-u", "--restapi-url", dest="restapi", nargs=1, default=[IM_URL], help="URL to IM REST API service that is outside the cluster")
        parser.add_argument("-a", "--auth-file", type=argparse.FileType('r'), dest="auth_file", nargs=1, help="authorization file")
        parser.set_defaults(func=CmdTransfer.run)

    @staticmethod
    def run(options):
        # If the user indicates an IM URL, we use it, if not we use the default IM
        if options.restapi:
            im_server_url = options.restapi[0]
        else:
            im_server_url = IM_URL
        try:
            r = ClusterStore.load(options.clustername)
            new_im_server_url, infrId, vmId, auth = ClusterStore.get_im_server_infrId_and_vmId_and_auth(r)
            state = r.get(system("front")).getValue("state", default=system.UNKNOWN)

            if options.auth_file:
                auth_content = [ str(l) for l in options.auth_file[0].readlines() ]
                auth = Authentication.read_auth_data(auth_content)

            if state == system.CONFIGURED and new_im_server_url and new_im_server_url != im_server_url:
                CLI.display("Cluster already transferred.")
                sys.exit(0)
            else:
                CmdLaunch.wait_transfer_save(infrId, vmId, auth, im_server_url, False, False,
                                             options.clustername, False, False)
        except Exception as e:
            CLI.display("Error transfering cluster '%s': %s" % (options.clustername, str(e)),
                        level=logging.ERROR)
            sys.exit(1)


# TODO: muy probablemente no haga falta esto del run_command y se pueda hacer como hace eloy con os.execlp
class CommandError(Exception):pass

class DownNodeError(Exception):pass

def run_command(command, shell=False):
    string = " "
    try:
        #CLI.display("executing: %s" % command)
        p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
    except:
        if type(command)==list: command = string.join(command)
        CLI.display('Could not execute command "%s"' %command, level=logging.ERROR)
        raise
    
    (output, err) = p.communicate()
    if p.returncode != 0:
        if err == "scontrol_checkpoint error: Required node not available (down, drained or reserved)\n":
            raise DownNodeError()
        else:
            if type(command)==list: command = string.join(command)
            CLI.display(' Error in command "%s"' % command, level=logging.ERROR)
            CLI.display(' Return code was: %s' % p.returncode, level=logging.ERROR)
            CLI.display(' Error output was:\n%s' % err, level=logging.ERROR)
            raise CommandError()
    else:
        return output

if __name__ == "__main__":
    # Support Python 2 and 3 input
    get_input = input
    if sys.version_info[:2] <= (2, 7):
        get_input = raw_input

    commands = [CmdLaunch, CmdList, CmdShow, CmdTemplates, CmdSsh, CmdReconfigure, CmdDestroy, CmdClone, CmdMigrate, CmdStop, CmdRestart, CmdTransfer]
    try:
        CLI.run(commands)
    except (OSError, IOError) as e:
        if e.errno != 32: raise
    except KeyboardInterrupt:
        CLI.display("Execution interrupted! Operation may not have finished properly!")
        sys.exit(1)