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