X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=sfa%2Fclient%2Fsfi.py;h=20b878edbe0155205e29c2377d9a67ad57c70f04;hb=fd395e1944dcd49f10a4d5b27ce4983ad389fb96;hp=b01f8a2e3f5bb147768d8b73a25126cddae12085;hpb=6da5aead24c6cfc1a9e77d6debddf7d7bab4649c;p=sfa.git diff --git a/sfa/client/sfi.py b/sfa/client/sfi.py index b01f8a2e..20b878ed 100644 --- a/sfa/client/sfi.py +++ b/sfa/client/sfi.py @@ -1,13 +1,17 @@ -# -# sfi.py - basic SFA command-line client -# this module is also used in sfascan -# +""" +sfi.py - basic SFA command-line client +this module is also used in sfascan +""" + +# pylint: disable=c0111, c0413 + +from __future__ import print_function import sys sys.path.append('.') -import os, os.path -import socket +import os +import os.path import re import datetime import codecs @@ -15,7 +19,6 @@ import pickle import json import shutil from lxml import etree -from StringIO import StringIO from optparse import OptionParser from pprint import PrettyPrinter from tempfile import mkstemp @@ -26,11 +29,13 @@ from sfa.trust.credential import Credential from sfa.trust.sfaticket import SfaTicket from sfa.util.faults import SfaInvalidArgument -from sfa.util.sfalogging import sfi_logger +from sfa.util.sfalogging import init_logger, logger from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn, Xrn from sfa.util.config import Config from sfa.util.version import version_core from sfa.util.cache import Cache +from sfa.util.printable import printable +from sfa.util.py23 import StringIO from sfa.storage.record import Record @@ -45,12 +50,15 @@ from sfa.client.return_value import ReturnValue from sfa.client.candidates import Candidates from sfa.client.manifolduploader import ManifoldUploader -CM_PORT=12346 +CM_PORT = 12346 +DEFAULT_RSPEC_VERSION = "GENI 3" from sfa.client.common import optparse_listvalue_callback, optparse_dictvalue_callback, \ - terminal_render, filter_records + terminal_render, filter_records # display methods + + def display_rspec(rspec, format='rspec'): if format in ['dns']: tree = etree.parse(StringIO(rspec)) @@ -65,24 +73,27 @@ def display_rspec(rspec, format='rspec'): else: result = rspec - print result + print(result) return + def display_list(results): for result in results: - print result + print(result) + def display_records(recordList, dump=False): ''' Print all fields in the record''' for record in recordList: display_record(record, dump) + def display_record(record, dump=False): if dump: record.dump(sort=True) else: info = record.getdict() - print "%s (%s)" % (info['hrn'], info['type']) + print("{} ({})".format(info['hrn'], info['type'])) return @@ -94,95 +105,125 @@ def filter_records(type, records): return filtered_records -def credential_printable (cred): - credential=Credential(cred=cred) - result="" - result += credential.get_summary_tostring() +def credential_printable(cred): + credential = Credential(cred=cred) + result = "" + result += credential.pretty_cred() result += "\n" rights = credential.get_privileges() - result += "type=%s\n" % credential.type - result += "version=%s\n" % credential.version - result += "rights=%s\n"%rights + result += "type={}\n".format(credential.type) + result += "version={}\n".format(credential.version) + result += "rights={}\n".format(rights) return result -def show_credentials (cred_s): - if not isinstance (cred_s,list): cred_s = [cred_s] + +def show_credentials(cred_s): + if not isinstance(cred_s, list): + cred_s = [cred_s] for cred in cred_s: - print "Using Credential %s"%credential_printable(cred) + print("Using Credential {}".format(credential_printable(cred))) # save methods -def save_raw_to_file(var, filename, format="text", banner=None): - if filename == "-": - # if filename is "-", send it to stdout - f = sys.stdout + +# raw + + +def save_raw_to_file(var, filename, format='text', banner=None): + if filename == '-': + _save_raw_to_file(var, sys.stdout, format, banner) else: - f = open(filename, "w") - if banner: - f.write(banner+"\n") + with open(filename, w) as fileobj: + _save_raw_to_file(var, fileobj, format, banner) + print("(Over)wrote {}".format(filename)) + + +def _save_raw_to_file(var, f, format, banner): if format == "text": - f.write(str(var)) + if banner: + f.write(banner + "\n") + f.write("{}".format(var)) + if banner: + f.write('\n' + banner + "\n") elif format == "pickled": f.write(pickle.dumps(var)) elif format == "json": - if hasattr(json, "dumps"): - f.write(json.dumps(var)) # python 2.6 - else: - f.write(json.write(var)) # python 2.5 + f.write(json.dumps(var)) # python 2.6 else: # this should never happen - print "unknown output format", format - if banner: - f.write('\n'+banner+"\n") + print("unknown output format", format) + +### + def save_rspec_to_file(rspec, filename): if not filename.endswith(".rspec"): filename = filename + ".rspec" - f = open(filename, 'w') - f.write(rspec) - f.close() - return + with open(filename, 'w') as f: + f.write("{}".format(rspec)) + print("(Over)wrote {}".format(filename)) + + +def save_record_to_file(filename, record_dict): + record = Record(dict=record_dict) + xml = record.save_as_xml() + with codecs.open(filename, encoding='utf-8', mode="w") as f: + f.write(xml) + print("(Over)wrote {}".format(filename)) + def save_records_to_file(filename, record_dicts, format="xml"): if format == "xml": - index = 0 - for record_dict in record_dicts: - if index > 0: - save_record_to_file(filename + "." + str(index), record_dict) - else: - save_record_to_file(filename, record_dict) - index = index + 1 + for index, record_dict in enumerate(record_dicts): + save_record_to_file(filename + "." + str(index), record_dict) elif format == "xmllist": - f = open(filename, "w") - f.write("\n") - for record_dict in record_dicts: - record_obj=Record(dict=record_dict) - f.write('\n') - f.write("\n") - f.close() + with open(filename, "w") as f: + f.write("\n") + for record_dict in record_dicts: + record_obj = Record(dict=record_dict) + f.write('\n') + f.write("\n") + print("(Over)wrote {}".format(filename)) + elif format == "hrnlist": - f = open(filename, "w") - for record_dict in record_dicts: - record_obj=Record(dict=record_dict) - f.write(record_obj.hrn + "\n") - f.close() + with open(filename, "w") as f: + for record_dict in record_dicts: + record_obj = Record(dict=record_dict) + f.write(record_obj.hrn + "\n") + print("(Over)wrote {}".format(filename)) + else: # this should never happen - print "unknown output format", format - -def save_record_to_file(filename, record_dict): - record = Record(dict=record_dict) - xml = record.save_as_xml() - f=codecs.open(filename, encoding='utf-8',mode="w") - f.write(xml) - f.close() - return + print("unknown output format", format) # minimally check a key argument -def check_ssh_key (key): + + +def check_ssh_key(key): good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$' return re.match(good_ssh_key, key, re.IGNORECASE) # load methods + + +def normalize_type(type): + if type.startswith('au'): + return 'authority' + elif type.startswith('us'): + return 'user' + elif type.startswith('sl'): + return 'slice' + elif type.startswith('no'): + return 'node' + elif type.startswith('ag'): + return 'aggregate' + elif type.startswith('al'): + return 'all' + else: + print('unknown type {} - should start with one of au|us|sl|no|ag|al'.format(type)) + return None + + def load_record_from_opts(options): record_dict = {} if hasattr(options, 'xrn') and options.xrn: @@ -198,71 +239,93 @@ def load_record_from_opts(options): pubkey = open(options.key, 'r').read() except IOError: pubkey = options.key - if not check_ssh_key (pubkey): - raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format") - record_dict['keys'] = [pubkey] + if not check_ssh_key(pubkey): + raise SfaInvalidArgument( + name='key', msg="Could not find file, or wrong key format") + record_dict['reg-keys'] = [pubkey] if hasattr(options, 'slices') and options.slices: record_dict['slices'] = options.slices - if hasattr(options, 'researchers') and options.researchers: - record_dict['researcher'] = options.researchers + if hasattr(options, 'reg_researchers') and options.reg_researchers is not None: + record_dict['reg-researchers'] = options.reg_researchers if hasattr(options, 'email') and options.email: record_dict['email'] = options.email - if hasattr(options, 'pis') and options.pis: - record_dict['pi'] = options.pis + # authorities can have a name for standalone deployment + if hasattr(options, 'name') and options.name: + record_dict['name'] = options.name + if hasattr(options, 'reg_pis') and options.reg_pis: + record_dict['reg-pis'] = options.reg_pis # handle extra settings record_dict.update(options.extras) - + return Record(dict=record_dict) -def load_record_from_file(filename): - f=codecs.open(filename, encoding="utf-8", mode="r") - xml_string = f.read() - f.close() - return Record(xml=xml_string) +def load_record_from_file(filename): + with codecs.open(filename, encoding="utf-8", mode="r") as f: + xml_str = f.read() + return Record(xml=xml_str) import uuid + + def unique_call_id(): return uuid.uuid4().urn -########## a simple model for maintaing 3 doc attributes per command (instead of just one) +# a simple model for maintaing 3 doc attributes per command (instead of just one) # essentially for the methods that implement a subcommand like sfi list # we need to keep track of # (*) doc a few lines that tell what it does, still located in __doc__ # (*) args_string a simple one-liner that describes mandatory arguments # (*) example well, one or several releant examples -# +# # since __doc__ only accounts for one, we use this simple mechanism below # however we keep doc in place for easier migration from functools import wraps # we use a list as well as a dict so we can keep track of the order -commands_list=[] -commands_dict={} - -def register_command (args_string, example): - def wrap(m): - name=getattr(m,'__name__') - doc=getattr(m,'__doc__',"-- missing doc --") - doc=doc.strip(" \t\n") +commands_list = [] +commands_dict = {} + + +def declare_command(args_string, example, aliases=None): + def wrap(m): + name = getattr(m, '__name__') + doc = getattr(m, '__doc__', "-- missing doc --") + doc = doc.strip(" \t\n") commands_list.append(name) - commands_dict[name]=(doc, args_string, example) + # last item is 'canonical' name, so we can know which commands are + # aliases + command_tuple = (doc, args_string, example, name) + commands_dict[name] = command_tuple + if aliases is not None: + for alias in aliases: + commands_list.append(alias) + commands_dict[alias] = command_tuple + @wraps(m) - def new_method (*args, **kwds): return m(*args, **kwds) + def new_method(*args, **kwds): return m(*args, **kwds) return new_method return wrap + +def remove_none_fields(record): + none_fields = [k for (k, v) in record.items() if v is None] + for k in none_fields: + del record[k] + ########## + class Sfi: - + # dirty hack to make this class usable from the outside - required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key'] + required_options = ['verbose', 'debug', 'registry', + 'sm', 'auth', 'user', 'user_private_key'] @staticmethod - def default_sfi_dir (): - if os.path.isfile("./sfi_config"): + def default_sfi_dir(): + if os.path.isfile("./sfi_config"): return os.getcwd() else: return os.path.expanduser("~/.sfi/") @@ -272,294 +335,345 @@ class Sfi: class DummyOptions: pass - def __init__ (self,options=None): - if options is None: options=Sfi.DummyOptions() + def __init__(self, options=None): + if options is None: + options = Sfi.DummyOptions() for opt in Sfi.required_options: - if not hasattr(options,opt): setattr(options,opt,None) - if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir() + if not hasattr(options, opt): + setattr(options, opt, None) + if not hasattr(options, 'sfi_dir'): + options.sfi_dir = Sfi.default_sfi_dir() self.options = options self.user = None self.authority = None - self.logger = sfi_logger - self.logger.enable_console() - ### various auxiliary material that we keep at hand - self.command=None - # need to call this other than just 'config' as we have a command/method with that name - self.config_instance=None - self.config_file=None - self.client_bootstrap=None - - ### suitable if no reasonable command has been provided - def print_commands_help (self, options): - verbose=getattr(options,'verbose') - format3="%18s %-15s %s" - line=80*'-' + logger.enable_console() + # various auxiliary material that we keep at hand + self.command = None + # need to call this other than just 'config' as we have a + # command/method with that name + self.config_instance = None + self.config_file = None + self.client_bootstrap = None + + # suitable if no reasonable command has been provided + def print_commands_help(self, options): + verbose = getattr(options, 'verbose') + format3 = "%10s %-35s %s" + format3offset = 47 + line = 80 * '-' if not verbose: - print format3%("command","cmd_args","description") - print line + print(format3 % ("command", "cmd_args", "description")) + print(line) else: - print line - self.create_global_parser().print_help() + print(line) + self.create_parser_global().print_help() # preserve order from the code for command in commands_list: - (doc, args_string, example) = commands_dict[command] - if verbose: - print line - doc=doc.replace("\n","\n"+35*' ') - print format3%(command,args_string,doc) + try: + (doc, args_string, example, canonical) = commands_dict[command] + except: + print("Cannot find info on command %s - skipped" % command) + continue if verbose: - self.create_command_parser(command).print_help() - - ### now if a known command was found we can be more verbose on that one - def print_help (self): - print "==================== Generic sfi usage" + print(line) + if command == canonical: + doc = doc.replace("\n", "\n" + format3offset * ' ') + print(format3 % (command, args_string, doc)) + if verbose: + self.create_parser_command(command).print_help() + else: + print(format3 % (command, "<>" % canonical, "")) + + # now if a known command was found we can be more verbose on that one + def print_help(self): + print("==================== Generic sfi usage") self.sfi_parser.print_help() - (doc,_,example)=commands_dict[self.command] - print "\n==================== Purpose of %s"%self.command - print doc - print "\n==================== Specific usage for %s"%self.command + (doc, _, example, canonical) = commands_dict[self.command] + if canonical != self.command: + print("\n==================== NOTE: {} is an alias for genuine {}" + .format(self.command, canonical)) + self.command = canonical + print("\n==================== Purpose of {}".format(self.command)) + print(doc) + print("\n==================== Specific usage for {}".format(self.command)) self.command_parser.print_help() if example: - print "\n==================== %s example(s)"%self.command - print example + print("\n==================== {} example(s)".format(self.command)) + print(example) - def create_global_parser(self): + def create_parser_global(self): # Generate command line parser parser = OptionParser(add_help_option=False, usage="sfi [sfi_options] command [cmd_options] [cmd_args]", - description="Commands: %s"%(" ".join(commands_list))) + description="Commands: {}".format(" ".join(commands_list))) parser.add_option("-r", "--registry", dest="registry", - help="root registry", metavar="URL", default=None) + help="root registry", metavar="URL", default=None) parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL", - help="slice API - in general a SM URL, but can be used to talk to an aggregate") + help="slice API - in general a SM URL, but can be used to talk to an aggregate") parser.add_option("-R", "--raw", dest="raw", default=None, help="Save raw, unparsed server response to a file") parser.add_option("", "--rawformat", dest="rawformat", type="choice", help="raw file format ([text]|pickled|json)", default="text", - choices=("text","pickled","json")) + choices=("text", "pickled", "json")) parser.add_option("", "--rawbanner", dest="rawbanner", default=None, help="text string to write before and after raw output") parser.add_option("-d", "--dir", dest="sfi_dir", - help="config & working directory - default is %default", - metavar="PATH", default=Sfi.default_sfi_dir()) + help="config & working directory - default is %default", + metavar="PATH", default=Sfi.default_sfi_dir()) parser.add_option("-u", "--user", dest="user", - help="user name", metavar="HRN", default=None) + help="user name", metavar="HRN", default=None) parser.add_option("-a", "--auth", dest="auth", - help="authority name", metavar="HRN", default=None) + help="authority name", metavar="HRN", default=None) parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0, - help="verbose mode - cumulative") + help="verbose mode - cumulative") parser.add_option("-D", "--debug", action="store_true", dest="debug", default=False, help="Debug (xml-rpc) protocol messages") # would it make sense to use ~/.ssh/id_rsa as a default here ? parser.add_option("-k", "--private-key", - action="store", dest="user_private_key", default=None, - help="point to the private key file to use if not yet installed in sfi_dir") + action="store", dest="user_private_key", default=None, + help="point to the private key file to use if not yet installed in sfi_dir") parser.add_option("-t", "--timeout", dest="timeout", default=None, - help="Amout of time to wait before timing out the request") - parser.add_option("-h", "--help", - action="store_true", dest="help", default=False, - help="one page summary on commands & exit") + help="Amout of time to wait before timing out the request") + parser.add_option("-h", "--help", + action="store_true", dest="help", default=False, + help="one page summary on commands & exit") parser.disable_interspersed_args() return parser - - def create_command_parser(self, command): + def create_parser_command(self, command): if command not in commands_dict: - msg="Invalid command\n" - msg+="Commands: " - msg += ','.join(commands_list) - self.logger.critical(msg) + msg = "Invalid command\n" + msg += "Commands: " + msg += ','.join(commands_list) + logger.critical(msg) sys.exit(2) # retrieve args_string - (_, args_string, __) = commands_dict[command] + (_, args_string, __, canonical) = commands_dict[command] parser = OptionParser(add_help_option=False, - usage="sfi [sfi_options] %s [cmd_options] %s" - % (command, args_string)) - parser.add_option ("-h","--help",dest='help',action='store_true',default=False, - help="Summary of one command usage") + usage="sfi [sfi_options] {} [cmd_options] {}" + .format(command, args_string)) + parser.add_option("-h", "--help", dest='help', action='store_true', default=False, + help="Summary of one command usage") - if command in ("config"): + if canonical in ("config"): parser.add_option('-m', '--myslice', dest='myslice', action='store_true', default=False, help='how myslice config variables as well') - if command in ("add", "update"): - parser.add_option('-x', '--xrn', dest='xrn', metavar='', help='object hrn/urn (mandatory)') - parser.add_option('-t', '--type', dest='type', metavar='', help='object type', default=None) - parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)") - parser.add_option('-k', '--key', dest='key', metavar='', help='public key string or file', + if canonical in ("version"): + parser.add_option("-l", "--local", + action="store_true", dest="version_local", default=False, + help="display version of the local client") + + if canonical in ("version", "trusted", "introspect"): + parser.add_option("-R", "--registry_interface", + action="store_true", dest="registry_interface", default=False, + help="target the registry interface instead of slice interface") + + if canonical in ("register", "update"): + parser.add_option('-x', '--xrn', dest='xrn', + metavar='', help='object hrn/urn (mandatory)') + parser.add_option('-t', '--type', dest='type', metavar='', + help='object type (2 first chars is enough)', default=None) + parser.add_option('-e', '--email', dest='email', + default="", help="email (mandatory for users)") + parser.add_option('-n', '--name', dest='name', + default="", help="name (optional for authorities)") + parser.add_option('-k', '--key', dest='key', metavar='', help='public key string or file', default=None) parser.add_option('-s', '--slices', dest='slices', metavar='', help='Set/replace slice xrns', default='', type="str", action='callback', callback=optparse_listvalue_callback) - parser.add_option('-r', '--researchers', dest='researchers', metavar='', - help='Set/replace slice researchers', default='', type="str", action='callback', + parser.add_option('-r', '--researchers', dest='reg_researchers', metavar='', + help='Set/replace slice researchers - use -r none to reset', default=None, type="str", action='callback', callback=optparse_listvalue_callback) - parser.add_option('-p', '--pis', dest='pis', metavar='', help='Set/replace Principal Investigators/Project Managers', + parser.add_option('-p', '--pis', dest='reg_pis', metavar='', help='Set/replace Principal Investigators/Project Managers', default='', type="str", action='callback', callback=optparse_listvalue_callback) - parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="", - action="callback", callback=optparse_dictvalue_callback, nargs=1, - help="set extra/testbed-dependent flags, e.g. --extra enabled=true") - - # user specifies remote aggregate/sm/component - if command in ("resources", "describe", "allocate", "provision", "delete", "allocate", "provision", - "action", "shutdown", "renew", "status"): - parser.add_option("-d", "--delegate", dest="delegate", default=None, - action="store_true", - help="Include a credential delegated to the user's root"+\ - "authority in set of credentials for this call") + parser.add_option('-X', '--extra', dest='extras', default={}, type='str', metavar="", + action="callback", callback=optparse_dictvalue_callback, nargs=1, + help="set extra/testbed-dependent flags, e.g. --extra enabled=true") + + # user specifies remote aggregate/sm/component + if canonical in ("resources", "describe", "allocate", "provision", "delete", "allocate", "provision", + "action", "shutdown", "renew", "status"): + parser.add_option("-d", "--delegate", dest="delegate", default=None, + action="store_true", + help="Include a credential delegated to the user's root" + + "authority in set of credentials for this call") # show_credential option - if command in ("list","resources", "describe", "provision", "allocate", "add","update","remove","slices","delete","status","renew"): - parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False, + if canonical in ("list", "resources", "describe", "provision", "allocate", "register", + "update", "remove", "delete", "status", "renew"): + parser.add_option("-C", "--credential", dest='show_credential', action='store_true', default=False, help="show credential(s) used in human-readable form") + if canonical in ("renew"): + parser.add_option("-l", "--as-long-as-possible", dest='alap', action='store_true', default=False, + help="renew as long as possible") # registy filter option - if command in ("list", "show", "remove"): - parser.add_option("-t", "--type", dest="type", type="choice", - help="type filter ([all]|user|slice|authority|node|aggregate)", - choices=("all", "user", "slice", "authority", "node", "aggregate"), - default="all") - if command in ("show"): - parser.add_option("-k","--key",dest="keys",action="append",default=[], + if canonical in ("list", "show", "remove"): + parser.add_option("-t", "--type", dest="type", metavar="", + default="all", + help="type filter - 2 first chars is enough ([all]|user|slice|authority|node|aggregate)") + if canonical in ("show"): + parser.add_option("-k", "--key", dest="keys", action="append", default=[], help="specify specific keys to be displayed from record") - if command in ("resources", "describe"): + parser.add_option("-n", "--no-details", dest="no_details", action="store_true", default=False, + help="call Resolve without the 'details' option") + if canonical in ("resources", "describe"): # rspec version - parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1", - help="schema type and version of resulting RSpec") + parser.add_option("-r", "--rspec-version", dest="rspec_version", default=DEFAULT_RSPEC_VERSION, + help="schema type and version of resulting RSpec (default:{})".format(DEFAULT_RSPEC_VERSION)) # disable/enable cached rspecs parser.add_option("-c", "--current", dest="current", default=False, - action="store_true", + action="store_true", help="Request the current rspec bypassing the cache. Cached rspecs are returned by default") # display formats parser.add_option("-f", "--format", dest="format", type="choice", - help="display format ([xml]|dns|ip)", default="xml", - choices=("xml", "dns", "ip")) - #panos: a new option to define the type of information about resources a user is interested in + help="display format ([xml]|dns|ip)", default="xml", + choices=("xml", "dns", "ip")) + # panos: a new option to define the type of information about + # resources a user is interested in parser.add_option("-i", "--info", dest="info", - help="optional component information", default=None) - # a new option to retreive or not reservation-oriented RSpecs (leases) + help="optional component information", default=None) + # a new option to retrieve or not reservation-oriented RSpecs + # (leases) parser.add_option("-l", "--list_leases", dest="list_leases", type="choice", - help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )", - choices=("all", "resources", "leases"), default="resources") - - - if command in ("resources", "describe", "allocate", "provision", "show", "list", "gid"): - parser.add_option("-o", "--output", dest="file", - help="output XML to file", metavar="FILE", default=None) - - if command in ("show", "list"): - parser.add_option("-f", "--format", dest="format", type="choice", - help="display format ([text]|xml)", default="text", - choices=("text", "xml")) - - parser.add_option("-F", "--fileformat", dest="fileformat", type="choice", - help="output file format ([xml]|xmllist|hrnlist)", default="xml", - choices=("xml", "xmllist", "hrnlist")) - if command == 'list': - parser.add_option("-r", "--recursive", dest="recursive", action='store_true', - help="list all child records", default=False) - parser.add_option("-v", "--verbose", dest="verbose", action='store_true', - help="gives details, like user keys", default=False) - if command in ("delegate"): - parser.add_option("-u", "--user", - action="store_true", dest="delegate_user", default=False, - help="delegate your own credentials; default if no other option is provided") - parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[], - metavar="slice_hrn", help="delegate cred. for slice HRN") - parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[], - metavar='auth_hrn', help="delegate cred for auth HRN") - # this primarily is a shorthand for -a my_hrn - parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true', - help="delegate your PI credentials, so s.t. like -a your_hrn^") - parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False, - help="""by default the mandatory argument is expected to be a user, + help="Retrieve or not reservation-oriented RSpecs ([resources]|leases|all)", + choices=("all", "resources", "leases"), default="resources") + + if canonical in ("resources", "describe", "allocate", "provision", "show", "list", "gid"): + parser.add_option("-o", "--output", dest="file", + help="output XML to file", metavar="FILE", default=None) + + if canonical in ("show", "list"): + parser.add_option("-f", "--format", dest="format", type="choice", + help="display format ([text]|xml)", default="text", + choices=("text", "xml")) + + parser.add_option("-F", "--fileformat", dest="fileformat", type="choice", + help="output file format ([xml]|xmllist|hrnlist)", default="xml", + choices=("xml", "xmllist", "hrnlist")) + if canonical == 'list': + parser.add_option("-r", "--recursive", dest="recursive", action='store_true', + help="list all child records", default=False) + parser.add_option("-v", "--verbose", dest="verbose", action='store_true', + help="gives details, like user keys", default=False) + if canonical in ("delegate"): + parser.add_option("-u", "--user", + action="store_true", dest="delegate_user", default=False, + help="delegate your own credentials; default if no other option is provided") + parser.add_option("-s", "--slice", dest="delegate_slices", action='append', default=[], + metavar="slice_hrn", help="delegate cred. for slice HRN") + parser.add_option("-a", "--auths", dest='delegate_auths', action='append', default=[], + metavar='auth_hrn', help="delegate cred for auth HRN") + # this primarily is a shorthand for -A my_hrn^ + parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true', + help="delegate your PI credentials, so s.t. like -A your_hrn^") + parser.add_option("-A", "--to-authority", dest='delegate_to_authority', action='store_true', default=False, + help="""by default the mandatory argument is expected to be a user, use this if you mean an authority instead""") - - if command in ("version"): - parser.add_option("-R","--registry-version", - action="store_true", dest="version_registry", default=False, - help="probe registry version instead of sliceapi") - parser.add_option("-l","--local", - action="store_true", dest="version_local", default=False, - help="display version of the local client") + + if canonical in ("myslice"): + parser.add_option("-p", "--password", dest='password', action='store', default=None, + help="specify mainfold password on the command line") + parser.add_option("-s", "--slice", dest="delegate_slices", action='append', default=[], + metavar="slice_hrn", help="delegate cred. for slice HRN") + parser.add_option("-a", "--auths", dest='delegate_auths', action='append', default=[], + metavar='auth_hrn', help="delegate PI cred for auth HRN") + parser.add_option('-d', '--delegate', dest='delegate', + help="Override 'delegate' from the config file") + parser.add_option('-b', '--backend', dest='backend', + help="Override 'backend' from the config file") return parser - # # Main: parse arguments and dispatch to command # def dispatch(self, command, command_options, command_args): - method=getattr(self, command, None) + (doc, args_string, example, canonical) = commands_dict[command] + method = getattr(self, canonical, None) if not method: - print "Unknown command %s"%command - return + print("sfi: unknown command {}".format(command)) + raise SystemExit("Unknown command {}".format(command)) + for arg in command_args: + if 'help' in arg or arg == '-h': + self.print_help() + sys.exit(1) return method(command_options, command_args) def main(self): - self.sfi_parser = self.create_global_parser() + init_logger('cli') + self.sfi_parser = self.create_parser_global() (options, args) = self.sfi_parser.parse_args() - if options.help: + if options.help: self.print_commands_help(options) sys.exit(1) self.options = options - self.logger.setLevelFromOptVerbose(self.options.verbose) + logger.setLevelFromOptVerbose(self.options.verbose) if len(args) <= 0: - self.logger.critical("No command given. Use -h for help.") + logger.critical("No command given. Use -h for help.") self.print_commands_help(options) return -1 - + # complete / find unique match with command set - command_candidates = Candidates (commands_list) + command_candidates = Candidates(commands_list) input = args[0] command = command_candidates.only_match(input) if not command: self.print_commands_help(options) sys.exit(1) # second pass options parsing - self.command=command - self.command_parser = self.create_command_parser(command) - (command_options, command_args) = self.command_parser.parse_args(args[1:]) + self.command = command + self.command_parser = self.create_parser_command(command) + (command_options, command_args) = self.command_parser.parse_args( + args[1:]) if command_options.help: self.print_help() sys.exit(1) self.command_options = command_options - self.read_config () - self.bootstrap () - self.logger.debug("Command=%s" % self.command) + # allow incoming types on 2 characters only + if hasattr(command_options, 'type'): + command_options.type = normalize_type(command_options.type) + if not command_options.type: + sys.exit(1) + + self.read_config() + self.bootstrap() + logger.debug("Command={}".format(self.command)) try: - self.dispatch(command, command_options, command_args) + retcod = self.dispatch(command, command_options, command_args) except SystemExit: return 1 except: - self.logger.log_exc ("sfi command %s failed"%command) + logger.log_exc("sfi command {} failed".format(command)) return 1 + return retcod - return 0 - #################### def read_config(self): - config_file = os.path.join(self.options.sfi_dir,"sfi_config") - shell_config_file = os.path.join(self.options.sfi_dir,"sfi_config.sh") + config_file = os.path.join(self.options.sfi_dir, "sfi_config") + shell_config_file = os.path.join(self.options.sfi_dir, "sfi_config.sh") try: if Config.is_ini(config_file): - config = Config (config_file) + config = Config(config_file) else: # try upgrading from shell config format - fp, fn = mkstemp(suffix='sfi_config', text=True) + fp, fn = mkstemp(suffix='sfi_config', text=True) config = Config(fn) - # we need to preload the sections we want parsed + # we need to preload the sections we want parsed # from the shell config config.add_section('sfi') - # sface users should be able to use this same file to configure their stuff + # sface users should be able to use this same file to configure + # their stuff config.add_section('sface') - # manifold users should be able to specify the details + # manifold users should be able to specify the details # of their backend server here for 'sfi myslice' config.add_section('myslice') config.load(config_file) @@ -567,57 +681,64 @@ use this if you mean an authority instead""") shutil.move(config_file, shell_config_file) # write new config config.save(config_file) - + except: - self.logger.critical("Failed to read configuration file %s"%config_file) - self.logger.info("Make sure to remove the export clauses and to add quotes") - if self.options.verbose==0: - self.logger.info("Re-run with -v for more details") + logger.critical( + "Failed to read configuration file {}".format(config_file)) + logger.info( + "Make sure to remove the export clauses and to add quotes") + if self.options.verbose == 0: + logger.info("Re-run with -v for more details") else: - self.logger.log_exc("Could not read config file %s"%config_file) + logger.log_exc( + "Could not read config file {}".format(config_file)) sys.exit(1) - - self.config_instance=config + + self.config_instance = config errors = 0 # Set SliceMgr URL if (self.options.sm is not None): - self.sm_url = self.options.sm + self.sm_url = self.options.sm elif hasattr(config, "SFI_SM"): - self.sm_url = config.SFI_SM + self.sm_url = config.SFI_SM else: - self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file) - errors += 1 + logger.error( + "You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in {}".format(config_file)) + errors += 1 # Set Registry URL if (self.options.registry is not None): - self.reg_url = self.options.registry + self.reg_url = self.options.registry elif hasattr(config, "SFI_REGISTRY"): - self.reg_url = config.SFI_REGISTRY + self.reg_url = config.SFI_REGISTRY else: - self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file) - errors += 1 + logger.error( + "You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in {}".format(config_file)) + errors += 1 # Set user HRN if (self.options.user is not None): - self.user = self.options.user + self.user = self.options.user elif hasattr(config, "SFI_USER"): - self.user = config.SFI_USER + self.user = config.SFI_USER else: - self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file) - errors += 1 + logger.error( + "You need to set e.g. SFI_USER='plc.princeton.username' in {}".format(config_file)) + errors += 1 # Set authority HRN if (self.options.auth is not None): - self.authority = self.options.auth + self.authority = self.options.auth elif hasattr(config, "SFI_AUTH"): - self.authority = config.SFI_AUTH + self.authority = config.SFI_AUTH else: - self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file) - errors += 1 + logger.error( + "You need to set e.g. SFI_AUTH='plc.princeton' in {}".format(config_file)) + errors += 1 - self.config_file=config_file + self.config_file = config_file if errors: - sys.exit(1) + sys.exit(1) # # Get various credential and spec files @@ -631,153 +752,170 @@ use this if you mean an authority instead""") # - bootstrap authority credential from user credential # - bootstrap slice credential from user credential # - + # init self-signed cert, user credentials and gid - def bootstrap (self): - client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir, - logger=self.logger) + def bootstrap(self): + if self.options.verbose: + logger.info( + "Initializing SfaClientBootstrap with {}".format(self.reg_url)) + client_bootstrap = SfaClientBootstrap(self.user, self.reg_url, self.options.sfi_dir, + logger=logger) # if -k is provided, use this to initialize private key if self.options.user_private_key: - client_bootstrap.init_private_key_if_missing (self.options.user_private_key) + client_bootstrap.init_private_key_if_missing( + self.options.user_private_key) else: - # trigger legacy compat code if needed + # trigger legacy compat code if needed # the name has changed from just .pkey to .pkey if not os.path.isfile(client_bootstrap.private_key_filename()): - self.logger.info ("private key not found, trying legacy name") + logger.info("private key not found, trying legacy name") try: - legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user))) - self.logger.debug("legacy_private_key=%s"%legacy_private_key) - client_bootstrap.init_private_key_if_missing (legacy_private_key) - self.logger.info("Copied private key from legacy location %s"%legacy_private_key) + legacy_private_key = os.path.join(self.options.sfi_dir, "{}.pkey" + .format(Xrn.unescape(get_leaf(self.user)))) + logger.debug("legacy_private_key={}" + .format(legacy_private_key)) + client_bootstrap.init_private_key_if_missing( + legacy_private_key) + logger.info("Copied private key from legacy location {}" + .format(legacy_private_key)) except: - self.logger.log_exc("Can't find private key ") + logger.log_exc("Can't find private key ") sys.exit(1) - + # make it bootstrap client_bootstrap.bootstrap_my_gid() # extract what's needed self.private_key = client_bootstrap.private_key() - self.my_credential_string = client_bootstrap.my_credential_string () + self.my_credential_string = client_bootstrap.my_credential_string() self.my_credential = {'geni_type': 'geni_sfa', - 'geni_version': '3.0', + 'geni_version': '3', 'geni_value': self.my_credential_string} - self.my_gid = client_bootstrap.my_gid () + self.my_gid = client_bootstrap.my_gid() self.client_bootstrap = client_bootstrap - def my_authority_credential_string(self): if not self.authority: - self.logger.critical("no authority specified. Use -a or set SF_AUTH") + logger.critical( + "no authority specified. Use -a or set SF_AUTH") sys.exit(-1) - return self.client_bootstrap.authority_credential_string (self.authority) + return self.client_bootstrap.authority_credential_string(self.authority) def authority_credential_string(self, auth_hrn): - return self.client_bootstrap.authority_credential_string (auth_hrn) + return self.client_bootstrap.authority_credential_string(auth_hrn) def slice_credential_string(self, name): - return self.client_bootstrap.slice_credential_string (name) + return self.client_bootstrap.slice_credential_string(name) def slice_credential(self, name): return {'geni_type': 'geni_sfa', - 'geni_version': '3.0', - 'geni_value': self.slice_credential_string(name)} + 'geni_version': '3', + 'geni_value': self.slice_credential_string(name)} # xxx should be supported by sfaclientbootstrap as well def delegate_cred(self, object_cred, hrn, type='authority'): # the gid and hrn of the object we are delegating if isinstance(object_cred, str): - object_cred = Credential(string=object_cred) + object_cred = Credential(string=object_cred) object_gid = object_cred.get_gid_object() object_hrn = object_gid.get_hrn() - + if not object_cred.get_privileges().get_all_delegate(): - self.logger.error("Object credential %s does not have delegate bit set"%object_hrn) + logger.error("Object credential {} does not have delegate bit set" + .format(object_hrn)) return # the delegating user's gid caller_gidfile = self.my_gid() - + # the gid of the user who will be delegated to - delegee_gid = self.client_bootstrap.gid(hrn,type) + delegee_gid = self.client_bootstrap.gid(hrn, type) delegee_hrn = delegee_gid.get_hrn() - dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile) + dcred = object_cred.delegate( + delegee_gid, self.private_key, caller_gidfile) return dcred.save_to_string(save_parents=True) - + # # Management of the servers - # + # - def registry (self): + def registry(self): # cache the result - if not hasattr (self, 'registry_proxy'): - self.logger.info("Contacting Registry at: %s"%self.reg_url) - self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid, - timeout=self.options.timeout, verbose=self.options.debug) + if not hasattr(self, 'registry_proxy'): + logger.info("Contacting Registry at: {}".format(self.reg_url)) + self.registry_proxy \ + = SfaServerProxy(self.reg_url, self.private_key, self.my_gid, + timeout=self.options.timeout, verbose=self.options.debug) return self.registry_proxy - def sliceapi (self): + def sliceapi(self): # cache the result - if not hasattr (self, 'sliceapi_proxy'): - # if the command exposes the --component option, figure it's hostname and connect at CM_PORT - if hasattr(self.command_options,'component') and self.command_options.component: + if not hasattr(self, 'sliceapi_proxy'): + # if the command exposes the --component option, figure it's + # hostname and connect at CM_PORT + if hasattr(self.command_options, 'component') and self.command_options.component: # resolve the hrn at the registry node_hrn = self.command_options.component records = self.registry().Resolve(node_hrn, self.my_credential_string) records = filter_records('node', records) if not records: - self.logger.warning("No such component:%r"% opts.component) + logger.warning( + "No such component:{}".format(opts.component)) record = records[0] - cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT) - self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid) + cm_url = "http://{}:{}/".format(record['hostname'], CM_PORT) + self.sliceapi_proxy = SfaServerProxy( + cm_url, self.private_key, self.my_gid) else: - # otherwise use what was provided as --sliceapi, or SFI_SM in the config + # otherwise use what was provided as --sliceapi, or SFI_SM in + # the config if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'): self.sm_url = 'http://' + self.sm_url - self.logger.info("Contacting Slice Manager at: %s"%self.sm_url) - self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid, - timeout=self.options.timeout, verbose=self.options.debug) + logger.info( + "Contacting Slice Manager at: {}".format(self.sm_url)) + self.sliceapi_proxy \ + = SfaServerProxy(self.sm_url, self.private_key, self.my_gid, + timeout=self.options.timeout, verbose=self.options.debug) return self.sliceapi_proxy def get_cached_server_version(self, server): # check local cache first cache = None - version = None - cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat') + version = None + cache_file = os.path.join(self.options.sfi_dir, 'sfi_cache.dat') cache_key = server.url + "-version" try: cache = Cache(cache_file) except IOError: cache = Cache() - self.logger.info("Local cache not found at: %s" % cache_file) + logger.info("Local cache not found at: {}".format(cache_file)) if cache: version = cache.get(cache_key) - if not version: + if not version: result = server.GetVersion() - version= ReturnValue.get_value(result) + version = ReturnValue.get_value(result) # cache version for 20 minutes - cache.add(cache_key, version, ttl= 60*20) - self.logger.info("Updating cache file %s" % cache_file) + cache.add(cache_key, version, ttl=60 * 20) + logger.info("Updating cache file {}".format(cache_file)) cache.save_to_file(cache_file) - return version - - ### resurrect this temporarily so we can support V1 aggregates for a while + return version + + # resurrect this temporarily so we can support V1 aggregates for a while def server_supports_options_arg(self, server): """ - Returns true if server support the optional call_id arg, false otherwise. + Returns true if server support the optional call_id arg, false otherwise. """ server_version = self.get_cached_server_version(server) result = False - # xxx need to rewrite this + # xxx need to rewrite this if int(server_version.get('geni_api')) >= 2: result = True return result def server_supports_call_id_arg(self, server): server_version = self.get_cached_server_version(server) - result = False + result = False if 'sfa' in server_version and 'code_tag' in server_version: code_tag = server_version['code_tag'] code_tag_parts = code_tag.split("-") @@ -786,48 +924,62 @@ use this if you mean an authority instead""") rev = code_tag_parts[1] if int(major) == 1 and minor == 0 and build >= 22: result = True - return result + return result - ### ois = options if supported - # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options)) - def ois (self, server, option_dict): - if self.server_supports_options_arg (server): + # ois = options if supported + # to be used in something like serverproxy.Method(arg1, arg2, + # *self.ois(api_options)) + def ois(self, server, option_dict): + if self.server_supports_options_arg(server): return [option_dict] - elif self.server_supports_call_id_arg (server): - return [ unique_call_id () ] - else: + elif self.server_supports_call_id_arg(server): + return [unique_call_id()] + else: return [] - ### cis = call_id if supported - like ois - def cis (self, server): - if self.server_supports_call_id_arg (server): - return [ unique_call_id ] + # cis = call_id if supported - like ois + def cis(self, server): + if self.server_supports_call_id_arg(server): + return [unique_call_id] else: return [] - ######################################## miscell utilities + # miscell utilities def get_rspec_file(self, rspec): - if (os.path.isabs(rspec)): - file = rspec - else: - file = os.path.join(self.options.sfi_dir, rspec) - if (os.path.isfile(file)): - return file - else: - self.logger.critical("No such rspec file %s"%rspec) - sys.exit(1) - + if (os.path.isabs(rspec)): + file = rspec + else: + file = os.path.join(self.options.sfi_dir, rspec) + if (os.path.isfile(file)): + return file + else: + logger.critical("No such rspec file {}".format(rspec)) + sys.exit(1) + def get_record_file(self, record): - if (os.path.isabs(record)): - file = record - else: - file = os.path.join(self.options.sfi_dir, record) - if (os.path.isfile(file)): - return file - else: - self.logger.critical("No such registry record file %s"%record) - sys.exit(1) + if (os.path.isabs(record)): + file = record + else: + file = os.path.join(self.options.sfi_dir, record) + if (os.path.isfile(file)): + return file + else: + logger.critical( + "No such registry record file {}".format(record)) + sys.exit(1) + # helper function to analyze raw output + # for main : return 0 if everything is fine, something else otherwise + # (mostly 1 for now) + def success(self, raw): + return_value = ReturnValue(raw) + output = ReturnValue.get_output(return_value) + # means everything is fine + if not output: + return 0 + # something went wrong + print('ERROR:', output) + return 1 #========================================================================== # Following functions implement the commands @@ -835,136 +987,172 @@ use this if you mean an authority instead""") # Registry-related commands #========================================================================== - @register_command("","") - def config (self, options, args): - "Display contents of current config" - print "# From configuration file %s"%self.config_file - flags=[ ('sfi', [ ('registry','reg_url'), - ('auth','authority'), - ('user','user'), - ('sm','sm_url'), + @declare_command("", "") + def config(self, options, args): + """ + Display contents of current config + """ + if len(args) != 0: + self.print_help() + sys.exit(1) + + print("# From configuration file {}".format(self.config_file)) + flags = [('sfi', [('registry', 'reg_url'), + ('auth', 'authority'), + ('user', 'user'), + ('sm', 'sm_url'), ]), - ] + ] if options.myslice: - flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) ) + flags.append( + ('myslice', ['backend', 'delegate', 'platform', 'username'])) for (section, tuples) in flags: - print "[%s]"%section + print("[{}]".format(section)) try: - for (external_name, internal_name) in tuples: - print "%-20s = %s"%(external_name,getattr(self,internal_name)) + for external_name, internal_name in tuples: + print("{:<20} = {}".format( + external_name, getattr(self, internal_name))) except: - for name in tuples: - varname="%s_%s"%(section.upper(),name.upper()) - value=getattr(self.config_instance,varname) - print "%-20s = %s"%(name,value) + for external_name, internal_name in tuples: + varname = "{}_{}".format( + section.upper(), external_name.upper()) + value = getattr(self.config_instance, varname) + print("{:<20} = {}".format(external_name, value)) + # xxx should analyze result + return 0 - @register_command("","") + @declare_command("", "") def version(self, options, args): """ display an SFA server version (GetVersion) - or version information about sfi itself + or version information about sfi itself """ + if len(args) != 0: + self.print_help() + sys.exit(1) + if options.version_local: - version=version_core() + version = version_core() else: - if options.version_registry: - server=self.registry() + if options.registry_interface: + server = self.registry() else: server = self.sliceapi() result = server.GetVersion() version = ReturnValue.get_value(result) if self.options.raw: - save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner) + save_raw_to_file(result, self.options.raw, + self.options.rawformat, self.options.rawbanner) else: pprinter = PrettyPrinter(indent=4) pprinter.pprint(version) + # xxx should analyze result + return 0 - @register_command("authority","") + @declare_command("authority", "") def list(self, options, args): """ list entries in named authority registry (List) """ - if len(args)!= 1: + if len(args) != 1: self.print_help() sys.exit(1) + hrn = args[0] opts = {} if options.recursive: opts['recursive'] = options.recursive - + if options.show_credential: show_credentials(self.my_credential_string) try: list = self.registry().List(hrn, self.my_credential_string, options) except IndexError: - raise Exception, "Not enough parameters for the 'list' command" + raise Exception("Not enough parameters for the 'list' command") # filter on person, slice, site, node, etc. # This really should be in the self.filter_records funct def comment... list = filter_records(options.type, list) - terminal_render (list, options) + terminal_render(list, options) if options.file: save_records_to_file(options.file, list, options.fileformat) - return - - @register_command("name","") + # xxx should analyze result + return 0 + + @declare_command("name", "") def show(self, options, args): """ show details about named registry record (Resolve) """ - if len(args)!= 1: + if len(args) != 1: self.print_help() sys.exit(1) + hrn = args[0] # explicitly require Resolve to run in details mode - record_dicts = self.registry().Resolve(hrn, self.my_credential_string, {'details':True}) + resolve_options = {} + if not options.no_details: + resolve_options['details'] = True + record_dicts = self.registry().Resolve( + hrn, self.my_credential_string, resolve_options) record_dicts = filter_records(options.type, record_dicts) if not record_dicts: - self.logger.error("No record of type %s"% options.type) + logger.error("No record of type {}".format(options.type)) return # user has required to focus on some keys if options.keys: - def project (record): - projected={} + def project(record): + projected = {} for key in options.keys: - try: projected[key]=record[key] - except: pass + try: + projected[key] = record[key] + except: + pass return projected - record_dicts = [ project (record) for record in record_dicts ] - records = [ Record(dict=record_dict) for record_dict in record_dicts ] + record_dicts = [project(record) for record in record_dicts] + records = [Record(dict=record_dict) for record_dict in record_dicts] for record in records: - if (options.format == "text"): record.dump(sort=True) - else: print record.save_as_xml() + if (options.format == "text"): + record.dump(sort=True) + else: + print(record.save_as_xml()) if options.file: - save_records_to_file(options.file, record_dicts, options.fileformat) - return - - @register_command("[xml-filename]","") - def add(self, options, args): - """add record into registry (Register) - from command line options (recommended) - old-school method involving an xml file still supported""" + save_records_to_file( + options.file, record_dicts, options.fileformat) + # xxx should analyze result + return 0 + + # this historically was named 'add', it is now 'register' with an alias + # for legacy + @declare_command("[xml-filename]", "", ['add']) + def register(self, options, args): + """ + create new record in registry (Register) + from command line options (recommended) + old-school method involving an xml file still supported + """ + if len(args) > 1: + self.print_help() + sys.exit(1) auth_cred = self.my_authority_credential_string() if options.show_credential: show_credentials(auth_cred) record_dict = {} - if len(args) > 1: - self.print_help() - sys.exit(1) - if len(args)==1: + if len(args) == 1: try: record_filepath = args[0] rec_file = self.get_record_file(record_filepath) - record_dict.update(load_record_from_file(rec_file).todict()) + record_dict.update(load_record_from_file( + rec_file).record_to_dict()) except: - print "Cannot load record file %s"%record_filepath + print("Cannot load record file {}".format(record_filepath)) sys.exit(1) if options: - record_dict.update(load_record_from_opts(options).todict()) + record_dict.update(load_record_from_opts(options).record_to_dict()) # we should have a type by now - if 'type' not in record_dict : + if 'type' not in record_dict: self.print_help() sys.exit(1) # this is still planetlab dependent.. as plc will whine without that @@ -973,103 +1161,111 @@ use this if you mean an authority instead""") if not 'first_name' in record_dict: record_dict['first_name'] = record_dict['hrn'] if 'last_name' not in record_dict: - record_dict['last_name'] = record_dict['hrn'] - return self.registry().Register(record_dict, auth_cred) - - @register_command("[xml-filename]","") + record_dict['last_name'] = record_dict['hrn'] + register = self.registry().Register(record_dict, auth_cred) + # xxx looks like the result here is not ReturnValue-compatible + # return self.success (register) + # xxx should analyze result + return 0 + + @declare_command("[xml-filename]", "") def update(self, options, args): - """update record into registry (Update) - from command line options (recommended) - old-school method involving an xml file still supported""" + """ + update record into registry (Update) + from command line options (recommended) + old-school method involving an xml file still supported + """ + if len(args) > 1: + self.print_help() + sys.exit(1) + record_dict = {} - if len(args) > 0: + if len(args) == 1: record_filepath = args[0] rec_file = self.get_record_file(record_filepath) - record_dict.update(load_record_from_file(rec_file).todict()) + record_dict.update(load_record_from_file( + rec_file).record_to_dict()) if options: - record_dict.update(load_record_from_opts(options).todict()) + record_dict.update(load_record_from_opts(options).record_to_dict()) # at the very least we need 'type' here - if 'type' not in record_dict: + if 'type' not in record_dict or record_dict['type'] is None: self.print_help() sys.exit(1) # don't translate into an object, as this would possibly distort # user-provided data; e.g. add an 'email' field to Users - if record_dict['type'] == "user": + if record_dict['type'] in ['user']: if record_dict['hrn'] == self.user: cred = self.my_credential_string else: cred = self.my_authority_credential_string() - elif record_dict['type'] in ["slice"]: + elif record_dict['type'] in ['slice']: try: cred = self.slice_credential_string(record_dict['hrn']) - except ServerException, e: - # XXX smbaker -- once we have better error return codes, update this - # to do something better than a string compare - if "Permission error" in e.args[0]: - cred = self.my_authority_credential_string() - else: - raise - elif record_dict['type'] in ["authority"]: + except ServerException as e: + # XXX smbaker -- once we have better error return codes, update this + # to do something better than a string compare + if "Permission error" in e.args[0]: + cred = self.my_authority_credential_string() + else: + raise + elif record_dict['type'] in ['authority']: cred = self.my_authority_credential_string() - elif record_dict['type'] == 'node': + elif record_dict['type'] in ['node']: cred = self.my_authority_credential_string() else: - raise "unknown record type" + record_dict['type'] + raise Exception( + "unknown record type {}".format(record_dict['type'])) if options.show_credential: show_credentials(cred) - return self.registry().Update(record_dict, cred) - - @register_command("hrn","") + update = self.registry().Update(record_dict, cred) + # xxx looks like the result here is not ReturnValue-compatible + # return self.success(update) + # xxx should analyze result + return 0 + + @declare_command("hrn", "") def remove(self, options, args): - "remove registry record by name (Remove)" + """ + remove registry record by name (Remove) + """ auth_cred = self.my_authority_credential_string() - if len(args)!=1: + if len(args) != 1: self.print_help() sys.exit(1) + hrn = args[0] - type = options.type + type = options.type if type in ['all']: type = '*' if options.show_credential: show_credentials(auth_cred) - return self.registry().Remove(hrn, auth_cred, type) - + remove = self.registry().Remove(hrn, auth_cred, type) + # xxx looks like the result here is not ReturnValue-compatible + # return self.success (remove) + # xxx should analyze result + return 0 + # ================================================================== # Slice-related commands # ================================================================== - @register_command("","") - def slices(self, options, args): - "list instantiated slices (ListSlices) - returns urn's" - server = self.sliceapi() - # creds - creds = [self.my_credential_string] - # options and call_id when supported - api_options = {} - api_options['call_id']=unique_call_id() - if options.show_credential: - show_credentials(creds) - result = server.ListSlices(creds, *self.ois(server,api_options)) - value = ReturnValue.get_value(result) - if self.options.raw: - save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner) - else: - display_list(value) - return - # show rspec for named slice - @register_command("","") + @declare_command("", "", ['discover']) def resources(self, options, args): """ discover available resources (ListResources) """ - server = self.sliceapi() + if len(args) != 0: + self.print_help() + sys.exit(1) + server = self.sliceapi() # set creds - creds = [self.my_credential] + creds = [self.my_credential_string] if options.delegate: - creds.append(self.delegate_cred(cred, get_authority(self.authority))) + creds.append(self.delegate_cred( + cred, get_authority(self.authority))) if options.show_credential: show_credentials(creds) @@ -1077,9 +1273,9 @@ use this if you mean an authority instead""") # been a required argument since v1 API api_options = {} # always send call_id to v2 servers - api_options ['call_id'] = unique_call_id() + api_options['call_id'] = unique_call_id() # ask for cached value if available - api_options ['cached'] = True + api_options['cached'] = True if options.info: api_options['info'] = options.info if options.list_leases: @@ -1089,39 +1285,37 @@ use this if you mean an authority instead""") api_options['cached'] = False else: api_options['cached'] = True - if options.rspec_version: - version_manager = VersionManager() - server_version = self.get_cached_server_version(server) - if 'sfa' in server_version: - # just request the version the client wants - api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict() - else: - api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'} - else: - api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'} - result = server.ListResources (creds, api_options) - value = ReturnValue.get_value(result) + version_manager = VersionManager() + api_options['geni_rspec_version'] = version_manager.get_version( + options.rspec_version).to_dict() + + list_resources = server.ListResources(creds, api_options) + value = ReturnValue.get_value(list_resources) if self.options.raw: - save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner) + save_raw_to_file(list_resources, self.options.raw, + self.options.rawformat, self.options.rawbanner) if options.file is not None: save_rspec_to_file(value, options.file) if (self.options.raw is None) and (options.file is None): display_rspec(value, options.format) + return self.success(list_resources) - return - - @register_command("slice_hrn","") + @declare_command("slice_hrn", "") def describe(self, options, args): """ - shows currently allocated/provisioned resources - of the named slice or set of slivers (Describe) + shows currently allocated/provisioned resources + of the named slice or set of slivers (Describe) """ - server = self.sliceapi() + if len(args) != 1: + self.print_help() + sys.exit(1) + server = self.sliceapi() # set creds creds = [self.slice_credential(args[0])] if options.delegate: - creds.append(self.delegate_cred(cred, get_authority(self.authority))) + creds.append(self.delegate_cred( + cred, get_authority(self.authority))) if options.show_credential: show_credentials(creds) @@ -1129,64 +1323,88 @@ use this if you mean an authority instead""") 'cached': True, 'info': options.info, 'list_leases': options.list_leases, - 'geni_rspec_version': {'type': 'geni', 'version': '3.0'}, - } + 'geni_rspec_version': {'type': 'geni', 'version': '3'}, + } + if options.info: + api_options['info'] = options.info + if options.rspec_version: version_manager = VersionManager() server_version = self.get_cached_server_version(server) if 'sfa' in server_version: # just request the version the client wants - api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict() + api_options['geni_rspec_version'] = version_manager.get_version( + options.rspec_version).to_dict() else: - api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'} - urn = Xrn(args[0], type='slice').get_urn() - result = server.Describe([urn], creds, api_options) - value = ReturnValue.get_value(result) + api_options['geni_rspec_version'] = { + 'type': 'geni', 'version': '3'} + urn = Xrn(args[0], type='slice').get_urn() + remove_none_fields(api_options) + describe = server.Describe([urn], creds, api_options) + value = ReturnValue.get_value(describe) if self.options.raw: - save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner) + save_raw_to_file(describe, self.options.raw, + self.options.rawformat, self.options.rawbanner) if options.file is not None: - save_rspec_to_file(value, options.file) + save_rspec_to_file(value['geni_rspec'], options.file) if (self.options.raw is None) and (options.file is None): - display_rspec(value, options.format) - - return + display_rspec(value['geni_rspec'], options.format) + return self.success(describe) - @register_command("slice_hrn","") + @declare_command("slice_hrn [...]", "") def delete(self, options, args): """ - de-allocate and de-provision all or named slivers of the slice (Delete) + de-allocate and de-provision all or named slivers of the named slice (Delete) """ - server = self.sliceapi() + if len(args) == 0: + self.print_help() + sys.exit(1) + server = self.sliceapi() # slice urn slice_hrn = args[0] - slice_urn = hrn_to_urn(slice_hrn, 'slice') + slice_urn = hrn_to_urn(slice_hrn, 'slice') + + if len(args) > 1: + # we have sliver urns + sliver_urns = args[1:] + else: + # we provision all the slivers of the slice + sliver_urns = [slice_urn] # creds slice_cred = self.slice_credential(slice_hrn) creds = [slice_cred] - + # options and call_id when supported api_options = {} - api_options ['call_id'] = unique_call_id() + api_options['call_id'] = unique_call_id() if options.show_credential: show_credentials(creds) - result = server.Delete([slice_urn], creds, *self.ois(server, api_options ) ) - value = ReturnValue.get_value(result) + delete = server.Delete(sliver_urns, creds, * + self.ois(server, api_options)) + value = ReturnValue.get_value(delete) if self.options.raw: - save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner) + save_raw_to_file(delete, self.options.raw, + self.options.rawformat, self.options.rawbanner) else: - print value - return value + print(value) + return self.success(delete) - @register_command("slice_hrn rspec","") + @declare_command("slice_hrn rspec", "") def allocate(self, options, args): """ allocate resources to the named slice (Allocate) """ + if len(args) != 2: + self.print_help() + sys.exit(1) + server = self.sliceapi() server_version = self.get_cached_server_version(server) slice_hrn = args[0] + rspec_file = self.get_rspec_file(args[1]) + slice_urn = Xrn(slice_hrn, type='slice').get_urn() # credentials @@ -1197,39 +1415,68 @@ use this if you mean an authority instead""") # delegate our cred to the slice manager # do not delegate cred to slicemgr...not working at the moment pass - #if server_version.get('hrn'): + # if server_version.get('hrn'): # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn']) - #elif server_version.get('urn'): + # elif server_version.get('urn'): # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn'])) if options.show_credential: show_credentials(creds) # rspec - rspec_file = self.get_rspec_file(args[1]) - rspec = open(rspec_file).read() api_options = {} - api_options ['call_id'] = unique_call_id() - result = server.Allocate(slice_urn, creds, rspec, api_options) - value = ReturnValue.get_value(result) + api_options['call_id'] = unique_call_id() + # users + sfa_users = [] + geni_users = [] + slice_records = self.registry().Resolve( + slice_urn, [self.my_credential_string]) + remove_none_fields(slice_records[0]) + if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers'] != []: + slice_record = slice_records[0] + user_hrns = slice_record['reg-researchers'] + user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns] + user_records = self.registry().Resolve( + user_urns, [self.my_credential_string]) + sfa_users = sfa_users_arg(user_records, slice_record) + geni_users = pg_users_arg(user_records) + + api_options['sfa_users'] = sfa_users + api_options['geni_users'] = geni_users + + with open(rspec_file) as rspec: + rspec_xml = rspec.read() + allocate = server.Allocate( + slice_urn, creds, rspec_xml, api_options) + value = ReturnValue.get_value(allocate) if self.options.raw: - save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner) + save_raw_to_file(allocate, self.options.raw, + self.options.rawformat, self.options.rawbanner) if options.file is not None: - save_rspec_to_file (value, options.file) + save_rspec_to_file(value['geni_rspec'], options.file) if (self.options.raw is None) and (options.file is None): - print value - return value - + print(value) + return self.success(allocate) - @register_command("slice_hrn","") + @declare_command("slice_hrn [...]", "") def provision(self, options, args): """ - provision already allocated resources of named slice (Provision) + provision all or named already allocated slivers of the named slice (Provision) """ + if len(args) == 0: + self.print_help() + sys.exit(1) + server = self.sliceapi() server_version = self.get_cached_server_version(server) slice_hrn = args[0] slice_urn = Xrn(slice_hrn, type='slice').get_urn() + if len(args) > 1: + # we have sliver urns + sliver_urns = args[1:] + else: + # we provision all the slivers of the slice + sliver_urns = [slice_urn] # credentials creds = [self.slice_credential(slice_hrn)] @@ -1238,20 +1485,20 @@ use this if you mean an authority instead""") # delegate our cred to the slice manager # do not delegate cred to slicemgr...not working at the moment pass - #if server_version.get('hrn'): + # if server_version.get('hrn'): # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn']) - #elif server_version.get('urn'): + # elif server_version.get('urn'): # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn'])) if options.show_credential: show_credentials(creds) api_options = {} - api_options ['call_id'] = unique_call_id() + api_options['call_id'] = unique_call_id() # set the requtested rspec version version_manager = VersionManager() - rspec_version = version_manager._get_version('geni', '3.0').to_dict() + rspec_version = version_manager._get_version('geni', '3').to_dict() api_options['geni_rspec_version'] = rspec_version # users @@ -1261,132 +1508,173 @@ use this if you mean an authority instead""") # keys: [, ] # }] users = [] - slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string]) - if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]: + slice_records = self.registry().Resolve( + slice_urn, [self.my_credential_string]) + if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers'] != []: slice_record = slice_records[0] - user_hrns = slice_record['researcher'] + user_hrns = slice_record['reg-researchers'] user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns] - user_records = self.registry().Resolve(user_urns, [self.my_credential_string]) + user_records = self.registry().Resolve( + user_urns, [self.my_credential_string]) users = pg_users_arg(user_records) - + api_options['geni_users'] = users - result = server.Provision([slice_urn], creds, api_options) - value = ReturnValue.get_value(result) + provision = server.Provision(sliver_urns, creds, api_options) + value = ReturnValue.get_value(provision) if self.options.raw: - save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner) + save_raw_to_file(provision, self.options.raw, + self.options.rawformat, self.options.rawbanner) if options.file is not None: - save_rspec_to_file (value, options.file) + save_rspec_to_file(value['geni_rspec'], options.file) if (self.options.raw is None) and (options.file is None): - print value - return value + print(value) + return self.success(provision) - @register_command("slice_hrn","") + @declare_command("slice_hrn", "") def status(self, options, args): """ - retrieve the status of the slivers belonging to tne named slice (Status) + retrieve the status of the slivers belonging to the named slice (Status) """ - server = self.sliceapi() + if len(args) != 1: + self.print_help() + sys.exit(1) + server = self.sliceapi() # slice urn slice_hrn = args[0] - slice_urn = hrn_to_urn(slice_hrn, 'slice') + slice_urn = hrn_to_urn(slice_hrn, 'slice') - # creds + # creds slice_cred = self.slice_credential(slice_hrn) creds = [slice_cred] # options and call_id when supported api_options = {} - api_options['call_id']=unique_call_id() + api_options['call_id'] = unique_call_id() if options.show_credential: show_credentials(creds) - result = server.Status([slice_urn], creds, *self.ois(server,api_options)) - value = ReturnValue.get_value(result) + status = server.Status([slice_urn], creds, * + self.ois(server, api_options)) + value = ReturnValue.get_value(status) if self.options.raw: - save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner) + save_raw_to_file(status, self.options.raw, + self.options.rawformat, self.options.rawbanner) else: - print value - # Thierry: seemed to be missing - return value + print(value) + return self.success(status) - @register_command("slice_hrn action","") + @declare_command("slice_hrn [...] action", "") def action(self, options, args): """ - Perform the named operational action on these slivers + Perform the named operational action on all or named slivers of the named slice """ + if len(args) == 0: + self.print_help() + sys.exit(1) + server = self.sliceapi() api_options = {} # slice urn slice_hrn = args[0] - action = args[1] - slice_urn = Xrn(slice_hrn, type='slice').get_urn() + slice_urn = Xrn(slice_hrn, type='slice').get_urn() + if len(args) > 2: + # we have sliver urns + sliver_urns = args[1:-1] + else: + # we provision all the slivers of the slice + sliver_urns = [slice_urn] + action = args[-1] # cred slice_cred = self.slice_credential(args[0]) creds = [slice_cred] if options.delegate: - delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority)) + delegated_cred = self.delegate_cred( + slice_cred, get_authority(self.authority)) creds.append(delegated_cred) - - result = server.PerformOperationalAction([slice_urn], creds, action , api_options) - value = ReturnValue.get_value(result) + + perform_action = server.PerformOperationalAction( + sliver_urns, creds, action, api_options) + value = ReturnValue.get_value(perform_action) if self.options.raw: - save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner) + save_raw_to_file(perform_action, self.options.raw, + self.options.rawformat, self.options.rawbanner) else: - print value - return value - - @register_command("slice_hrn time","") + print(value) + return self.success(perform_action) + + @declare_command("slice_hrn [...] time", + "\n".join(["sfi renew onelab.ple.heartbeat 2015-04-31", + "sfi renew onelab.ple.heartbeat 2015-04-31T14:00:00Z", + "sfi renew onelab.ple.heartbeat +5d", + "sfi renew onelab.ple.heartbeat +3w", + "sfi renew onelab.ple.heartbeat +2m", ])) def renew(self, options, args): """ - renew slice (RenewSliver) + renew slice(Renew) """ - server = self.sliceapi() - if len(args) != 2: + if len(args) < 2: self.print_help() sys.exit(1) - [ slice_hrn, input_time ] = args - # slice urn - slice_urn = hrn_to_urn(slice_hrn, 'slice') + + server = self.sliceapi() + slice_hrn = args[0] + slice_urn = Xrn(slice_hrn, type='slice').get_urn() + + if len(args) > 2: + # we have sliver urns + sliver_urns = args[1:-1] + else: + # we provision all the slivers of the slice + sliver_urns = [slice_urn] + input_time = args[-1] + # time: don't try to be smart on the time format, server-side will # creds slice_cred = self.slice_credential(args[0]) creds = [slice_cred] # options and call_id when supported api_options = {} - api_options['call_id']=unique_call_id() + api_options['call_id'] = unique_call_id() + if options.alap: + api_options['geni_extend_alap'] = True if options.show_credential: show_credentials(creds) - result = server.Renew([slice_urn], creds, input_time, *self.ois(server,api_options)) - value = ReturnValue.get_value(result) + renew = server.Renew(sliver_urns, creds, input_time, + *self.ois(server, api_options)) + value = ReturnValue.get_value(renew) if self.options.raw: - save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner) + save_raw_to_file(renew, self.options.raw, + self.options.rawformat, self.options.rawbanner) else: - print value - return value + print(value) + return self.success(renew) - - @register_command("slice_hrn","") + @declare_command("slice_hrn", "") def shutdown(self, options, args): """ shutdown named slice (Shutdown) """ + if len(args) != 1: + self.print_help() + sys.exit(1) + server = self.sliceapi() # slice urn slice_hrn = args[0] - slice_urn = hrn_to_urn(slice_hrn, 'slice') + slice_urn = hrn_to_urn(slice_hrn, 'slice') # creds slice_cred = self.slice_credential(slice_hrn) creds = [slice_cred] - result = server.Shutdown(slice_urn, creds) - value = ReturnValue.get_value(result) + shutdown = server.Shutdown(slice_urn, creds) + value = ReturnValue.get_value(shutdown) if self.options.raw: - save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner) + save_raw_to_file(shutdown, self.options.raw, + self.options.rawformat, self.options.rawbanner) else: - print value - return value - + print(value) + return self.success(shutdown) - @register_command("[name]","") + @declare_command("[name]", "") def gid(self, options, args): """ Create a GID (CreateGid) @@ -1394,18 +1682,22 @@ use this if you mean an authority instead""") if len(args) < 1: self.print_help() sys.exit(1) + target_hrn = args[0] - my_gid_string = open(self.client_bootstrap.my_gid()).read() + my_gid_string = open(self.client_bootstrap.my_gid()).read() gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string) if options.file: filename = options.file else: - filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn]) - self.logger.info("writing %s gid to %s" % (target_hrn, filename)) + filename = os.sep.join( + [self.options.sfi_dir, '{}.gid'.format(target_hrn)]) + logger.info("writing {} gid to {}".format(target_hrn, filename)) GID(string=gid).save_to_file(filename) - + # xxx should analyze result + return 0 + #################### - @register_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser + @declare_command("to_hrn", """$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser the set of credentials in the scope for this call would be @@ -1418,56 +1710,59 @@ use this if you mean an authority instead""") because of the two -s options """) - def delegate (self, options, args): + def delegate(self, options, args): """ (locally) create delegate credential for use by given hrn - make sure to check for 'sfi myslice' instead if you plan - on using MySlice + make sure to check for 'sfi myslice' instead if you plan + on using MySlice """ if len(args) != 1: self.print_help() sys.exit(1) + to_hrn = args[0] # support for several delegations in the same call # so first we gather the things to do - tuples=[] + tuples = [] for slice_hrn in options.delegate_slices: - message="%s.slice"%slice_hrn + message = "{}.slice".format(slice_hrn) original = self.slice_credential_string(slice_hrn) - tuples.append ( (message, original,) ) + tuples.append((message, original,)) if options.delegate_pi: - my_authority=self.authority - message="%s.pi"%my_authority + my_authority = self.authority + message = "{}.pi".format(my_authority) original = self.my_authority_credential_string() - tuples.append ( (message, original,) ) + tuples.append((message, original,)) for auth_hrn in options.delegate_auths: - message="%s.auth"%auth_hrn - original=self.authority_credential_string(auth_hrn) - tuples.append ( (message, original, ) ) + message = "{}.auth".format(auth_hrn) + original = self.authority_credential_string(auth_hrn) + tuples.append((message, original, )) # if nothing was specified at all at this point, let's assume -u - if not tuples: options.delegate_user=True + if not tuples: + options.delegate_user = True # this user cred if options.delegate_user: - message="%s.user"%self.user + message = "{}.user".format(self.user) original = self.my_credential_string - tuples.append ( (message, original, ) ) + tuples.append((message, original, )) # default type for beneficial is user unless -A - if options.delegate_to_authority: to_type='authority' - else: to_type='user' + to_type = 'authority' if options.delegate_to_authority else 'user' # let's now handle all this # it's all in the filenaming scheme - for (message,original) in tuples: - delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type) - delegated_credential = Credential (string=delegated_string) - filename = os.path.join ( self.options.sfi_dir, - "%s_for_%s.%s.cred"%(message,to_hrn,to_type)) + for (message, original) in tuples: + delegated_string = self.client_bootstrap.delegate_credential_string( + original, to_hrn, to_type) + delegated_credential = Credential(string=delegated_string) + filename = os.path.join(self.options.sfi_dir, + "{}_for_{}.{}.cred".format(message, to_hrn, to_type)) delegated_credential.save_to_file(filename, save_parents=True) - self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename)) - + logger.info("delegated credential for {} to {} and wrote to {}" + .format(message, to_hrn, filename)) + #################### - @register_command("","""$ less +/myslice sfi_config + @declare_command("", """$ less +/myslice sfi_config [myslice] backend = http://manifold.pl.sophia.inria.fr:7080 # the HRN that myslice uses, so that we are delegating to @@ -1487,95 +1782,196 @@ $ sfi myslice $ sfi -v myslice -- or sfi -vv myslice same but with more and more verbosity -$ sfi m +$ sfi m -b http://mymanifold.foo.com:7080/ is synonym to sfi myslice as no other command starts with an 'm' + and uses a custom backend for this one call """ -) # register_command - def myslice (self, options, args): - + ) # declare_command + def myslice(self, options, args): """ This helper is for refreshing your credentials at myslice; it will - * compute all the slices that you currently have credentials on - * refresh all your credentials (you as a user and pi, your slices) - * upload them to the manifold backend server - for last phase, sfi_config is read to look for the [myslice] section, - and namely the 'backend', 'delegate' and 'user' settings""" + * compute all the slices that you currently have credentials on + * refresh all your credentials (you as a user and pi, your slices) + * upload them to the manifold backend server + for last phase, sfi_config is read to look for the [myslice] section, + and namely the 'backend', 'delegate' and 'user' settings + """ ########## - if len(args)>0: + if len(args) > 0: self.print_help() sys.exit(1) + # enable info by default + logger.setLevelFromOptVerbose(self.options.verbose + 1) + # the rough sketch goes like this + # (0) produce a p12 file + self.client_bootstrap.my_pkcs12() - ### the rough sketch goes like this # (a) rain check for sufficient config in sfi_config - # we don't allow to override these settings for now - myslice_dict={} - myslice_keys=['backend', 'delegate', 'platform', 'username'] + myslice_dict = {} + myslice_keys = ['backend', 'delegate', 'platform', 'username'] for key in myslice_keys: - full_key="MYSLICE_" + key.upper() - value=getattr(self.config_instance,full_key,None) - if value: myslice_dict[key]=value - else: print "Unsufficient config, missing key %s in [myslice] section of sfi_config"%key + value = None + # oct 2013 - I'm finding myself juggling with config files + # so a couple of command-line options can now override config + if hasattr(options, key) and getattr(options, key) is not None: + value = getattr(options, key) + else: + full_key = "MYSLICE_" + key.upper() + value = getattr(self.config_instance, full_key, None) + if value: + myslice_dict[key] = value + else: + print("Unsufficient config, missing key {} in [myslice] section of sfi_config" + .format(key)) if len(myslice_dict) != len(myslice_keys): sys.exit(1) # (b) figure whether we are PI for the authority where we belong - sfi_logger.info("Resolving our own id") - my_records=self.registry().Resolve(self.user,self.my_credential_string) - if len(my_records)!=1: print "Cannot Resolve %s -- exiting"%self.user; sys.exit(1) - my_record=my_records[0] - sfi_logger.info("Checking for authorities that we are PI for") - my_auths = my_record['reg-pi-authorities'] - sfi_logger.debug("Found %d authorities: %s"%(len(my_auths),my_auths)) + logger.info("Resolving our own id {}".format(self.user)) + my_records = self.registry().Resolve(self.user, self.my_credential_string) + if len(my_records) != 1: + print("Cannot Resolve {} -- exiting".format(self.user)) + sys.exit(1) + my_record = my_records[0] + my_auths_all = my_record['reg-pi-authorities'] + logger.info( + "Found {} authorities that we are PI for".format(len(my_auths_all))) + logger.debug("They are {}".format(my_auths_all)) + + my_auths = my_auths_all + if options.delegate_auths: + my_auths = list(set(my_auths_all).intersection( + set(options.delegate_auths))) + logger.debug( + "Restricted to user-provided auths {}".format(my_auths)) # (c) get the set of slices that we are in - sfi_logger.info("Checking for slices that we are member of") - my_slices=my_record['reg-slices'] - sfi_logger.debug("Found %d slices: %s"%(len(my_slices),my_slices)) + my_slices_all = my_record['reg-slices'] + logger.info( + "Found {} slices that we are member of".format(len(my_slices_all))) + logger.debug("They are: {}".format(my_slices_all)) + + my_slices = my_slices_all + # if user provided slices, deal only with these - if they are found + if options.delegate_slices: + my_slices = list(set(my_slices_all).intersection( + set(options.delegate_slices))) + logger.debug( + "Restricted to user-provided slices: {}".format(my_slices)) # (d) make sure we have *valid* credentials for all these - hrn_credentials=[] - hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) ) + hrn_credentials = [] + hrn_credentials.append((self.user, 'user', self.my_credential_string,)) for auth_hrn in my_auths: - hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) ) + hrn_credentials.append( + (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),)) for slice_hrn in my_slices: - hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) ) + try: + hrn_credentials.append( + (slice_hrn, 'slice', self.slice_credential_string(slice_hrn),)) + except: + print("WARNING: could not get slice credential for slice {}" + .format(slice_hrn)) # (e) check for the delegated version of these - # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever + # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever # switch to myslice using an authority instead of a user - delegatee_type='user' - delegatee_hrn=myslice_dict['delegate'] - hrn_delegated_credentials = [ - (hrn, htype, self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type),) - for (hrn, htype, credential) in hrn_credentials ] + delegatee_type = 'user' + delegatee_hrn = myslice_dict['delegate'] + hrn_delegated_credentials = [] + for (hrn, htype, credential) in hrn_credentials: + delegated_credential = self.client_bootstrap.delegate_credential_string( + credential, delegatee_hrn, delegatee_type) + # save these so user can monitor what she's uploaded + filename = os.path.join(self.options.sfi_dir, + "{}.{}_for_{}.{}.cred" + .format(hrn, htype, delegatee_hrn, delegatee_type)) + with open(filename, 'w') as f: + f.write(delegated_credential) + logger.debug("(Over)wrote {}".format(filename)) + hrn_delegated_credentials.append( + (hrn, htype, delegated_credential, filename, )) # (f) and finally upload them to manifold server # xxx todo add an option so the password can be set on the command line # (but *NOT* in the config file) so other apps can leverage this - uploader = ManifoldUploader (logger=sfi_logger, - url=myslice_dict['backend'], - platform=myslice_dict['platform'], - username=myslice_dict['username']) - for (hrn,htype,delegated_credential) in hrn_delegated_credentials: - sfi_logger.info("Uploading delegated credential for %s (%s)"%(hrn,htype)) - uploader.upload(delegated_credential,message=hrn) + logger.info("Uploading on backend at {}".format( + myslice_dict['backend'])) + uploader = ManifoldUploader(logger=logger, + url=myslice_dict['backend'], + platform=myslice_dict['platform'], + username=myslice_dict['username'], + password=options.password) + uploader.prompt_all() + (count_all, count_success) = (0, 0) + for (hrn, htype, delegated_credential, filename) in hrn_delegated_credentials: + # inspect + inspect = Credential(string=delegated_credential) + expire_datetime = inspect.get_expiration() + message = "{} ({}) [exp:{}]".format(hrn, htype, expire_datetime) + if uploader.upload(delegated_credential, message=message): + count_success += 1 + count_all += 1 + logger.info("Successfully uploaded {}/{} credentials" + .format(count_success, count_all)) + # at first I thought we would want to save these, # like 'sfi delegate does' but on second thought # it is probably not helpful as people would not # need to run 'sfi delegate' at all anymore - return + if count_success != count_all: + sys.exit(1) + # xxx should analyze result + return 0 -# Thierry: I'm turning this off as a command, no idea what it's used for -# @register_command("cred","") + @declare_command("cred", "") def trusted(self, options, args): """ return the trusted certs at this interface (get_trusted_certs) - """ - trusted_certs = self.registry().get_trusted_certs() + """ + if options.registry_interface: + server = self.registry() + else: + server = self.sliceapi() + cred = self.my_authority_credential_string() + trusted_certs = server.get_trusted_certs(cred) + if not options.registry_interface: + trusted_certs = ReturnValue.get_value(trusted_certs) + for trusted_cert in trusted_certs: + print("\n===========================================================\n") gid = GID(string=trusted_cert) gid.dump() cert = Certificate(string=trusted_cert) - self.logger.debug('Sfi.trusted -> %r'%cert.get_subject()) - return + logger.debug('Sfi.trusted -> {}'.format(cert.get_subject())) + print("Certificate:\n{}\n\n".format(trusted_cert)) + # xxx should analyze result + return 0 + @declare_command("", "") + def introspect(self, options, args): + """ + If remote server supports XML-RPC instrospection API, allows + to list supported methods + """ + if options.registry_interface: + server = self.registry() + else: + server = self.sliceapi() + results = server.serverproxy.system.listMethods() + # at first sight a list here means it's fine, + # and a dict suggests an error (no support for introspection?) + if isinstance(results, list): + results = [name for name in results if 'system.' not in name] + results.sort() + print("== methods supported at {}".format(server.url)) + if 'Discover' in results: + print("== has support for 'Discover' - most likely a v3") + else: + print("== has no support for 'Discover' - most likely a v2") + for name in results: + print(name) + else: + print("Got return of type {}, expected a list".format(type(results))) + print("This suggests the remote end does not support introspection") + print(results)