X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=sfa%2Fclient%2Fsfi.py;h=31b8a0530ac144da14d88d080842323ca65769f6;hb=a5fdefe7f034410ab55ba0d739c2b802334418e9;hp=8f9682f7614ecd99df61e9e305b3fb074d80fef6;hpb=a49c0e1ae9001aa0ff22cc7262e5669db0739df5;p=sfa.git diff --git a/sfa/client/sfi.py b/sfa/client/sfi.py index 8f9682f7..31b8a053 100644 --- a/sfa/client/sfi.py +++ b/sfa/client/sfi.py @@ -31,6 +31,7 @@ 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.storage.record import Record @@ -43,33 +44,12 @@ from sfa.client.sfaserverproxy import SfaServerProxy, ServerException from sfa.client.client_helper import pg_users_arg, sfa_users_arg from sfa.client.return_value import ReturnValue from sfa.client.candidates import Candidates +from sfa.client.manifolduploader import ManifoldUploader CM_PORT=12346 -# utility methods here -def optparse_listvalue_callback(option, option_string, value, parser): - setattr(parser.values, option.dest, value.split(',')) - -# a code fragment that could be helpful for argparse which unfortunately is -# available with 2.7 only, so this feels like too strong a requirement for the client side -#class ExtraArgAction (argparse.Action): -# def __call__ (self, parser, namespace, values, option_string=None): -# would need a try/except of course -# (k,v)=values.split('=') -# d=getattr(namespace,self.dest) -# d[k]=v -##### -#parser.add_argument ("-X","--extra",dest='extras', default={}, action=ExtraArgAction, -# help="set extra flags, testbed dependent, e.g. --extra enabled=true") - -def optparse_dictvalue_callback (option, option_string, value, parser): - try: - (k,v)=value.split('=',1) - d=getattr(parser.values, option.dest) - d[k]=v - except: - parser.print_help() - sys.exit(1) +from sfa.client.common import optparse_listvalue_callback, optparse_dictvalue_callback, \ + terminal_render, filter_records # display methods def display_rspec(rspec, format='rspec'): @@ -115,14 +95,15 @@ def filter_records(type, records): return filtered_records -def credential_printable (credential_string): - credential=Credential(string=credential_string) +def credential_printable (cred): + credential = Credential(cred=cred) result="" - result += credential.get_summary_tostring() + result += credential.pretty_cred() result += "\n" rights = credential.get_privileges() - result += "rights=%s"%rights - result += "\n" + result += "type=%s\n" % credential.type + result += "version=%s\n" % credential.version + result += "rights=%s\n" % rights return result def show_credentials (cred_s): @@ -158,7 +139,7 @@ def save_rspec_to_file(rspec, filename): if not filename.endswith(".rspec"): filename = filename + ".rspec" f = open(filename, 'w') - f.write(rspec) + f.write("%s"%rspec) f.close() return @@ -197,54 +178,6 @@ def save_record_to_file(filename, record_dict): f.close() return -# used in sfi list -def terminal_render (records,options): - # sort records by type - grouped_by_type={} - for record in records: - type=record['type'] - if type not in grouped_by_type: grouped_by_type[type]=[] - grouped_by_type[type].append(record) - group_types=grouped_by_type.keys() - group_types.sort() - for type in group_types: - group=grouped_by_type[type] -# print 20 * '-', type - try: renderer=eval('terminal_render_'+type) - except: renderer=terminal_render_default - for record in group: renderer(record,options) - -def render_plural (how_many, name,names=None): - if not names: names="%ss"%name - if how_many<=0: return "No %s"%name - elif how_many==1: return "1 %s"%name - else: return "%d %s"%(how_many,names) - -def terminal_render_default (record,options): - print "%s (%s)" % (record['hrn'], record['type']) -def terminal_render_user (record, options): - print "%s (User)"%record['hrn'], - if record.get('reg-pi-authorities',None): print " [PI at %s]"%(" and ".join(record['reg-pi-authorities'])), - if record.get('reg-slices',None): print " [IN slices %s]"%(" and ".join(record['reg-slices'])), - user_keys=record.get('reg-keys',[]) - if not options.verbose: - print " [has %s]"%(render_plural(len(user_keys),"key")) - else: - print "" - for key in user_keys: print 8*' ',key.strip("\n") - -def terminal_render_slice (record, options): - print "%s (Slice)"%record['hrn'], - if record.get('reg-researchers',None): print " [USERS %s]"%(" and ".join(record['reg-researchers'])), -# print record.keys() - print "" -def terminal_render_authority (record, options): - print "%s (Authority)"%record['hrn'], - if record.get('reg-pis',None): print " [PIS %s]"%(" and ".join(record['reg-pis'])), - print "" -def terminal_render_node (record, options): - print "%s (Node)"%record['hrn'] - # minimally check a key argument def check_ssh_key (key): good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$' @@ -268,15 +201,18 @@ def load_record_from_opts(options): 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] + 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) @@ -293,6 +229,47 @@ def load_record_from_file(filename): import uuid def unique_call_id(): return uuid.uuid4().urn +########## 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 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) + # 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) + 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 @@ -320,110 +297,178 @@ class Sfi: self.authority = None self.logger = sfi_logger self.logger.enable_console() - self.available_names = [ tuple[0] for tuple in Sfi.available ] - self.available_dict = dict (Sfi.available) - - # tuples command-name expected-args in the order in which they should appear in the help - available = [ - ("version", ""), - ("list", "authority"), - ("show", "name"), - ("add", "[record]"), - ("update", "[record]"), - ("remove", "name"), - ("slices", ""), - ("resources", "[slice_hrn]"), - ("create", "slice_hrn rspec"), - ("delete", "slice_hrn"), - ("status", "slice_hrn"), - ("start", "slice_hrn"), - ("stop", "slice_hrn"), - ("reset", "slice_hrn"), - ("renew", "slice_hrn time"), - ("shutdown", "slice_hrn"), - ("get_ticket", "slice_hrn rspec"), - ("redeem_ticket", "ticket"), - ("delegate", "to_hrn"), - ("gid", "[name]"), - ("trusted", "cred"), - ("config", ""), - ] - - def print_command_help (self, options): + ### 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" + format3="%10s %-35s %s" + format3offset=47 line=80*'-' if not verbose: print format3%("command","cmd_args","description") print line else: print line - self.create_parser().print_help() - for command in self.available_names: - args=self.available_dict[command] - method=getattr(self,command,None) - doc="" - if method: doc=getattr(method,'__doc__',"") - if not doc: doc="*** no doc found ***" - doc=doc.strip(" \t\n") - doc=doc.replace("\n","\n"+35*' ') + self.create_parser_global().print_help() + # preserve order from the code + for command in commands_list: + try: + (doc, args_string, example, canonical) = commands_dict[command] + except: + print "Cannot find info on command %s - skipped"%command + continue if verbose: print line - print format3%(command,args,doc) - if verbose: - self.create_command_parser(command).print_help() + 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,canonical)=commands_dict[self.command] + if canonical != self.command: + print "\n==================== NOTE: %s is an alias for genuine %s"%(self.command,canonical) + self.command=canonical + print "\n==================== Purpose of %s"%self.command + print doc + print "\n==================== Specific usage for %s"%self.command + self.command_parser.print_help() + if example: + print "\n==================== %s example(s)"%self.command + print example + + 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))) + parser.add_option("-r", "--registry", dest="registry", + 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") + 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")) + 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()) + parser.add_option("-u", "--user", dest="user", + help="user name", metavar="HRN", default=None) + parser.add_option("-a", "--auth", dest="auth", + help="authority name", metavar="HRN", default=None) + parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0, + 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") + 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") + parser.disable_interspersed_args() - def create_command_parser(self, command): - if command not in self.available_dict: + return parser + + + def create_parser_command(self, command): + if command not in commands_dict: msg="Invalid command\n" msg+="Commands: " - msg += ','.join(self.available_names) + msg += ','.join(commands_list) self.logger.critical(msg) sys.exit(2) - parser = OptionParser(usage="sfi [sfi_options] %s [cmd_options] %s" \ - % (command, self.available_dict[command])) + # retrieve args_string + (_, 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") - if command in ("add", "update"): + if canonical in ("config"): + parser.add_option('-m', '--myslice', dest='myslice', action='store_true', default=False, + help='how myslice config variables as well') + + 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"): + 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', default=None) parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)") -# use --extra instead -# parser.add_option('-u', '--url', dest='url', metavar='', default=None, help="URL, useful for slices") -# parser.add_option('-d', '--description', dest='description', metavar='', -# help='Description, useful for slices', default=None) + 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='slice xrns', + 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='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='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) -# use --extra instead -# parser.add_option('-f', '--firstname', dest='firstname', metavar='', help='user first name') -# parser.add_option('-l', '--lastname', dest='lastname', metavar='', help='user last name') 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","create","add","update","remove","slices","delete","status","renew"): + 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"): + if canonical 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"): + 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"): + 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", + parser.add_option("-r", "--rspec-version", dest="rspec_version", default="GENI 3", help="schema type and version of resulting RSpec") # disable/enable cached rspecs parser.add_option("-c", "--current", dest="current", default=False, @@ -442,12 +487,11 @@ class Sfi: choices=("all", "resources", "leases"), default="resources") - # 'create' does return the new rspec, makes sense to save that too - if command in ("resources", "show", "list", "gid", 'create'): + 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 command in ("show", "list"): + if canonical in ("show", "list"): parser.add_option("-f", "--format", dest="format", type="choice", help="display format ([text]|xml)", default="text", choices=("text", "xml")) @@ -455,12 +499,12 @@ class Sfi: 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': + 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 command in ("delegate"): + 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") @@ -468,87 +512,42 @@ class Sfi: 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^ + # 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^") + 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") - - return parser + 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") - def create_parser(self): - - # Generate command line parser - parser = OptionParser(usage="sfi [sfi_options] command [cmd_options] [cmd_args]", - description="Commands: %s"%(" ".join(self.available_names))) - parser.add_option("-r", "--registry", dest="registry", - 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") - 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")) - 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()) - parser.add_option("-u", "--user", dest="user", - help="user name", metavar="HRN", default=None) - parser.add_option("-a", "--auth", dest="auth", - help="authority name", metavar="HRN", default=None) - parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0, - 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") - parser.add_option("-t", "--timeout", dest="timeout", default=None, - help="Amout of time to wait before timing out the request") - parser.add_option("-?", "--commands", - action="store_true", dest="command_help", default=False, - help="one page summary on commands & exit") - parser.disable_interspersed_args() - return parser - - - def print_help (self): - print "==================== Generic sfi usage" - self.sfi_parser.print_help() - print "==================== Specific command usage" - self.command_parser.print_help() + # # 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 %s"%command + raise SystemExit,"Unknown command %s"%command return method(command_options, command_args) def main(self): - self.sfi_parser = self.create_parser() + self.sfi_parser = self.create_parser_global() (options, args) = self.sfi_parser.parse_args() - if options.command_help: - self.print_command_help(options) + if options.help: + self.print_commands_help(options) sys.exit(1) self.options = options @@ -556,32 +555,37 @@ use this if you mean an authority instead""") if len(args) <= 0: self.logger.critical("No command given. Use -h for help.") - self.print_command_help(options) + self.print_commands_help(options) return -1 # complete / find unique match with command set - command_candidates = Candidates (self.available_names) + command_candidates = Candidates (commands_list) input = args[0] command = command_candidates.only_match(input) if not command: - self.print_command_help(options) + self.print_commands_help(options) sys.exit(1) # second pass options parsing - self.command_parser = self.create_command_parser(command) + 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" % command) + self.logger.debug("Command=%s" % 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) - sys.exit(1) - - return + return 1 + return retcod #################### def read_config(self): @@ -597,7 +601,11 @@ use this if you mean an authority instead""") # 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 config.add_section('sface') + # 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) # back up old config shutil.move(config_file, shell_config_file) @@ -613,6 +621,7 @@ use this if you mean an authority instead""") self.logger.log_exc("Could not read config file %s"%config_file) sys.exit(1) + self.config_instance=config errors = 0 # Set SliceMgr URL if (self.options.sm is not None): @@ -654,17 +663,6 @@ use this if you mean an authority instead""") if errors: sys.exit(1) - def show_config (self): - print "From configuration file %s"%self.config_file - flags=[ - ('SFI_USER','user'), - ('SFI_AUTH','authority'), - ('SFI_SM','sm_url'), - ('SFI_REGISTRY','reg_url'), - ] - for (external_name, internal_name) in flags: - print "%s='%s'"%(external_name,getattr(self,internal_name)) - # # Get various credential and spec files # @@ -680,6 +678,8 @@ use this if you mean an authority instead""") # init self-signed cert, user credentials and gid def bootstrap (self): + if self.options.verbose: + self.logger.info("Initializing SfaClientBootstrap with {}".format(self.reg_url)) client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir, logger=self.logger) # if -k is provided, use this to initialize private key @@ -704,6 +704,9 @@ use this if you mean an authority instead""") # extract what's needed self.private_key = client_bootstrap.private_key() self.my_credential_string = client_bootstrap.my_credential_string () + self.my_credential = {'geni_type': 'geni_sfa', + 'geni_version': '3', + 'geni_value': self.my_credential_string} self.my_gid = client_bootstrap.my_gid () self.client_bootstrap = client_bootstrap @@ -720,6 +723,32 @@ use this if you mean an authority instead""") def slice_credential_string(self, name): return self.client_bootstrap.slice_credential_string (name) + def slice_credential(self, name): + return {'geni_type': 'geni_sfa', + '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_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) + 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_hrn = delegee_gid.get_hrn() + dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile) + return dcred.save_to_string(save_parents=True) + # # Management of the servers # @@ -846,21 +875,60 @@ use this if you mean an authority instead""") 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 # # Registry-related commands #========================================================================== + @declare_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'), + ]), + ] + if options.myslice: + flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) ) + + for (section, tuples) in flags: + print "[%s]"%section + try: + for (external_name, internal_name) in tuples: + print "%-20s = %s"%(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) + # xxx should analyze result + return 0 + + @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 options.version_local: version=version_core() else: - if options.version_registry: + if options.registry_interface: server=self.registry() else: server = self.sliceapi() @@ -871,7 +939,10 @@ or version information about sfi itself else: pprinter = PrettyPrinter(indent=4) pprinter.pprint(version) + # xxx should analyze result + return 0 + @declare_command("authority","") def list(self, options, args): """ list entries in named authority registry (List) @@ -897,8 +968,10 @@ or version information about sfi itself terminal_render (list, options) if options.file: save_records_to_file(options.file, list, options.fileformat) - return + # xxx should analyze result + return 0 + @declare_command("name","") def show(self, options, args): """ show details about named registry record (Resolve) @@ -908,7 +981,9 @@ or version information about sfi itself 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) @@ -928,18 +1003,31 @@ or version information about sfi itself else: print record.save_as_xml() if options.file: save_records_to_file(options.file, record_dicts, options.fileformat) - return + # xxx should analyze result + return 0 - def add(self, options, args): - "add record into registry by using the command options (Recommended) or from xml file (Register)" + # 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""" + auth_cred = self.my_authority_credential_string() if options.show_credential: show_credentials(auth_cred) record_dict = {} - if len(args) > 0: - record_filepath = args[0] - rec_file = self.get_record_file(record_filepath) - record_dict.update(load_record_from_file(rec_file).todict()) + if len(args) > 1: + self.print_help() + sys.exit(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()) + except: + print "Cannot load record file %s"%record_filepath + sys.exit(1) if options: record_dict.update(load_record_from_opts(options).todict()) # we should have a type by now @@ -953,10 +1041,17 @@ or version information about sfi itself 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 = 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 by using the command options (Recommended) or from xml file (Update)" + """update record into registry (Update) + from command line options (recommended) + old-school method involving an xml file still supported""" record_dict = {} if len(args) > 0: record_filepath = args[0] @@ -971,12 +1066,12 @@ or version information about sfi itself # 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: @@ -986,16 +1081,21 @@ or version information about sfi itself cred = self.my_authority_credential_string() else: raise - elif record_dict['type'] in ["authority"]: + 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'] if options.show_credential: show_credentials(cred) - return self.registry().Update(record_dict, cred) + 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)" auth_cred = self.my_authority_credential_string() @@ -1008,46 +1108,28 @@ or version information about sfi itself 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 # ================================================================== - 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 + @declare_command("","") def resources(self, options, args): """ - with no arg, discover available resources, (ListResources) -or with an slice hrn, shows currently provisioned resources + discover available resources (ListResources) """ server = self.sliceapi() # set creds - creds = [] - if args: - the_credential=self.slice_credential_string(args[0]) - creds.append(the_credential) - else: - the_credential=self.my_credential_string - creds.append(the_credential) + creds = [self.my_credential] + if options.delegate: + creds.append(self.delegate_cred(cred, get_authority(self.authority))) if options.show_credential: show_credentials(creds) @@ -1058,9 +1140,6 @@ or with an slice hrn, shows currently provisioned resources api_options ['call_id'] = unique_call_id() # ask for cached value if available api_options ['cached'] = True - if args: - hrn = args[0] - api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice') if options.info: api_options['info'] = options.info if options.list_leases: @@ -1077,36 +1156,112 @@ or with an slice hrn, shows currently provisioned resources # 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'} + api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'} else: - api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'} - result = server.ListResources (creds, api_options) - value = ReturnValue.get_value(result) + api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'} + 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) + + @declare_command("slice_hrn","") + def describe(self, options, args): + """ + shows currently allocated/provisioned resources + of the named slice or set of slivers (Describe) + """ + server = self.sliceapi() + + # set creds + creds = [self.slice_credential(args[0])] + if options.delegate: + creds.append(self.delegate_cred(cred, get_authority(self.authority))) + if options.show_credential: + show_credentials(creds) + + api_options = {'call_id': unique_call_id(), + 'cached': True, + 'info': options.info, + 'list_leases': options.list_leases, + 'geni_rspec_version': {'type': 'geni', 'version': '3'}, + } + if options.info: + api_options['info'] = options.info - return + 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'} + 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(describe, self.options.raw, self.options.rawformat, self.options.rawbanner) + if options.file is not None: + save_rspec_to_file(value['geni_rspec'], options.file) + if (self.options.raw is None) and (options.file is None): + display_rspec(value['geni_rspec'], options.format) + return self.success (describe) - def create(self, options, args): + @declare_command("slice_hrn [...]","") + def delete(self, options, args): """ - create or update named slice with given rspec + de-allocate and de-provision all or named slivers of the named slice (Delete) """ server = self.sliceapi() - # xxx do we need to check usage (len(args)) ? # 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() + if options.show_credential: + show_credentials(creds) + delete = server.Delete(sliver_urns, creds, *self.ois(server, api_options ) ) + value = ReturnValue.get_value(delete) + if self.options.raw: + save_raw_to_file(delete, self.options.raw, self.options.rawformat, self.options.rawbanner) + else: + print value + return self.success (delete) + + @declare_command("slice_hrn rspec","") + def allocate(self, options, args): + """ + allocate resources to the named slice (Allocate) + """ + server = self.sliceapi() + server_version = self.get_cached_server_version(server) + slice_hrn = args[0] + slice_urn = Xrn(slice_hrn, type='slice').get_urn() # credentials - creds = [self.slice_credential_string(slice_hrn)] + creds = [self.slice_credential(slice_hrn)] delegated_cred = None - server_version = self.get_cached_server_version(server) if server_version.get('interface') == 'slicemgr': # delegate our cred to the slice manager # do not delegate cred to slicemgr...not working at the moment @@ -1122,82 +1277,103 @@ or with an slice hrn, shows currently provisioned resources # rspec rspec_file = self.get_rspec_file(args[1]) rspec = open(rspec_file).read() - + api_options = {} + api_options ['call_id'] = unique_call_id() # users - # need to pass along user keys to the aggregate. - # users = [ - # { urn: urn:publicid:IDN+emulab.net+user+alice - # keys: [, ] - # }] - users = [] - # xxx Thierry 2012 sept. 21 - # contrary to what I was first thinking, calling Resolve with details=False does not yet work properly here - # I am turning details=True on again on a - hopefully - temporary basis, just to get this whole thing to work again + sfa_users = [] + geni_users = [] slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string]) - # slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string], {'details':True}) - if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']: + 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) - if 'sfa' not in server_version: - users = pg_users_arg(user_records) - rspec = RSpec(rspec) - rspec.filter({'component_manager_id': server_version['urn']}) - rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request') - else: - users = sfa_users_arg(user_records, slice_record) - - # do not append users, keys, or slice tags. Anything - # not contained in this request will be removed from the slice + api_options['sfa_users'] = sfa_users + api_options['geni_users'] = geni_users - # CreateSliver has supported the options argument for a while now so it should - # be safe to assume this server support it - api_options = {} - api_options ['append'] = False - api_options ['call_id'] = unique_call_id() - result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options)) - value = ReturnValue.get_value(result) + allocate = server.Allocate(slice_urn, creds, rspec, 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 self.success(allocate) - return value - - def delete(self, options, args): + @declare_command("slice_hrn [...]","") + def provision(self, options, args): """ - delete named slice (DeleteSliver) + provision all or named already allocated slivers of the named slice (Provision) """ server = self.sliceapi() - - # slice urn + server_version = self.get_cached_server_version(server) slice_hrn = args[0] - slice_urn = hrn_to_urn(slice_hrn, 'slice') + 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)] + delegated_cred = None + if server_version.get('interface') == 'slicemgr': + # delegate our cred to the slice manager + # do not delegate cred to slicemgr...not working at the moment + pass + #if server_version.get('hrn'): + # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn']) + #elif server_version.get('urn'): + # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn'])) - # creds - slice_cred = self.slice_credential_string(slice_hrn) - creds = [slice_cred] - - # options and call_id when supported - api_options = {} - api_options ['call_id'] = unique_call_id() if options.show_credential: show_credentials(creds) - result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) ) - value = ReturnValue.get_value(result) + + api_options = {} + api_options ['call_id'] = unique_call_id() + + # set the requtested rspec version + version_manager = VersionManager() + rspec_version = version_manager._get_version('geni', '3').to_dict() + api_options['geni_rspec_version'] = rspec_version + + # users + # need to pass along user keys to the aggregate. + # users = [ + # { urn: urn:publicid:IDN+emulab.net+user+alice + # keys: [, ] + # }] + users = [] + 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['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]) + users = pg_users_arg(user_records) + + api_options['geni_users'] = users + 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) - else: + 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['geni_rspec'], options.file) + if (self.options.raw is None) and (options.file is None): print value - return value - + return self.success(provision) + + @declare_command("slice_hrn","") def status(self, options, args): """ - retrieve slice status (SliverStatus) + retrieve the status of the slivers belonging to the named slice (Status) """ server = self.sliceapi() @@ -1206,7 +1382,7 @@ or with an slice hrn, shows currently provisioned resources slice_urn = hrn_to_urn(slice_hrn, 'slice') # creds - slice_cred = self.slice_credential_string(slice_hrn) + slice_cred = self.slice_credential(slice_hrn) creds = [slice_cred] # options and call_id when supported @@ -1214,103 +1390,91 @@ or with an slice hrn, shows currently provisioned resources api_options['call_id']=unique_call_id() if options.show_credential: show_credentials(creds) - result = server.SliverStatus(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 + return self.success (status) - def start(self, options, args): + @declare_command("slice_hrn [...] action","") + def action(self, options, args): """ - start named slice (Start) - """ - server = self.sliceapi() - - # the slice urn - slice_hrn = args[0] - slice_urn = hrn_to_urn(slice_hrn, 'slice') - - # cred - slice_cred = self.slice_credential_string(args[0]) - creds = [slice_cred] - # xxx Thierry - does this not need an api_options as well ? - result = server.Start(slice_urn, creds) - value = ReturnValue.get_value(result) - if self.options.raw: - save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner) - else: - print value - return value - - def stop(self, options, args): - """ - stop named slice (Stop) + Perform the named operational action on all or named slivers of the named slice """ server = self.sliceapi() + api_options = {} # slice urn slice_hrn = args[0] - slice_urn = hrn_to_urn(slice_hrn, 'slice') - # cred - slice_cred = self.slice_credential_string(args[0]) - creds = [slice_cred] - result = server.Stop(slice_urn, creds) - value = ReturnValue.get_value(result) - if self.options.raw: - save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner) + slice_urn = Xrn(slice_hrn, type='slice').get_urn() + if len(args) > 2: + # we have sliver urns + sliver_urns = args[1:-1] else: - print value - return value - - # reset named slice - def reset(self, options, args): - """ - reset named slice (reset_slice) - """ - server = self.sliceapi() - # slice urn - slice_hrn = args[0] - slice_urn = hrn_to_urn(slice_hrn, 'slice') + # we provision all the slivers of the slice + sliver_urns = [slice_urn] + action = args[-1] # cred - slice_cred = self.slice_credential_string(args[0]) + slice_cred = self.slice_credential(args[0]) creds = [slice_cred] - result = server.reset_slice(creds, slice_urn) - value = ReturnValue.get_value(result) + if options.delegate: + delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority)) + creds.append(delegated_cred) + + 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 - + 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') + 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_string(args[0]) + 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.RenewSliver(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 - + return self.success(renew) + @declare_command("slice_hrn","") def shutdown(self, options, args): """ shutdown named slice (Shutdown) @@ -1320,83 +1484,17 @@ or with an slice hrn, shows currently provisioned resources slice_hrn = args[0] slice_urn = hrn_to_urn(slice_hrn, 'slice') # creds - slice_cred = self.slice_credential_string(slice_hrn) + 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 - - - def get_ticket(self, options, args): - """ - get a ticket for the specified slice - """ - server = self.sliceapi() - # slice urn - slice_hrn, rspec_path = args[0], args[1] - slice_urn = hrn_to_urn(slice_hrn, 'slice') - # creds - slice_cred = self.slice_credential_string(slice_hrn) - creds = [slice_cred] - # rspec - rspec_file = self.get_rspec_file(rspec_path) - rspec = open(rspec_file).read() - # options and call_id when supported - api_options = {} - api_options['call_id']=unique_call_id() - # get ticket at the server - ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options)) - # save - file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket") - self.logger.info("writing ticket to %s"%file) - ticket = SfaTicket(string=ticket_string) - ticket.save_to_file(filename=file, save_parents=True) - - def redeem_ticket(self, options, args): - """ - Connects to nodes in a slice and redeems a ticket -(slice hrn is retrieved from the ticket) - """ - ticket_file = args[0] - - # get slice hrn from the ticket - # use this to get the right slice credential - ticket = SfaTicket(filename=ticket_file) - ticket.decode() - ticket_string = ticket.save_to_string(save_parents=True) - - slice_hrn = ticket.gidObject.get_hrn() - slice_urn = hrn_to_urn(slice_hrn, 'slice') - #slice_hrn = ticket.attributes['slivers'][0]['hrn'] - slice_cred = self.slice_credential_string(slice_hrn) - - # get a list of node hostnames from the RSpec - tree = etree.parse(StringIO(ticket.rspec)) - root = tree.getroot() - hostnames = root.xpath("./network/site/node/hostname/text()") - - # create an xmlrpc connection to the component manager at each of these - # components and gall redeem_ticket - connections = {} - for hostname in hostnames: - try: - self.logger.info("Calling redeem_ticket at %(hostname)s " % locals()) - cm_url="http://%s:%s/"%(hostname,CM_PORT) - server = SfaServerProxy(cm_url, self.private_key, self.my_gid) - server = self.server_proxy(hostname, CM_PORT, self.private_key, - timeout=self.options.timeout, verbose=self.options.debug) - server.RedeemTicket(ticket_string, slice_cred) - self.logger.info("Success") - except socket.gaierror: - self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname) - except Exception, e: - self.logger.log_exc(e.message) - return + return self.success (shutdown) + @declare_command("[name]","") def gid(self, options, args): """ Create a GID (CreateGid) @@ -1413,11 +1511,28 @@ or with an slice hrn, shows currently provisioned resources filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn]) self.logger.info("writing %s gid to %s" % (target_hrn, filename)) GID(string=gid).save_to_file(filename) + # xxx should analyze result + return 0 - + #################### + @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 + (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred + as per -u/--user + (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred + as per -p/--pi + (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred + (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred + because of the two -s options + +""") 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 """ if len(args) != 1: self.print_help() @@ -1461,18 +1576,166 @@ or with an slice hrn, shows currently provisioned resources 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)) + #################### + @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 +delegate = ple.upmc.slicebrowser +# platform - this is a myslice concept +platform = ple +# username - as of this writing (May 2013) a simple login name +username = thierry + +$ sfi myslice + will first collect the slices that you are part of, then make sure + all your credentials are up-to-date (read: refresh expired ones) + then compute delegated credentials for user 'ple.upmc.slicebrowser' + and upload them all on myslice backend, using 'platform' and 'user'. + A password will be prompted for the upload part. + +$ sfi -v myslice -- or sfi -vv myslice + same but with more and more verbosity + +$ 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 +""" +) # 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""" + + ########## + if len(args)>0: + self.print_help() + sys.exit(1) + # enable info by default + self.logger.setLevelFromOptVerbose(self.options.verbose+1) + ### the rough sketch goes like this + # (0) produce a p12 file + self.client_bootstrap.my_pkcs12() + + # (a) rain check for sufficient config in sfi_config + myslice_dict={} + myslice_keys=[ 'backend', 'delegate', 'platform', 'username'] + for key in myslice_keys: + 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 %s in [myslice] section of sfi_config"%key + if len(myslice_dict) != len(myslice_keys): + sys.exit(1) + + # (b) figure whether we are PI for the authority where we belong + self.logger.info("Resolving our own id %s"%self.user) + 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] + my_auths_all = my_record['reg-pi-authorities'] + self.logger.info("Found %d authorities that we are PI for"%len(my_auths_all)) + self.logger.debug("They are %s"%(my_auths_all)) + + my_auths = my_auths_all + if options.delegate_auths: + my_auths = list(set(my_auths_all).intersection(set(options.delegate_auths))) + self.logger.debug("Restricted to user-provided auths"%(my_auths)) + + # (c) get the set of slices that we are in + my_slices_all=my_record['reg-slices'] + self.logger.info("Found %d slices that we are member of"%len(my_slices_all)) + self.logger.debug("They are: %s"%(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))) + self.logger.debug("Restricted to user-provided slices: %s"%(my_slices)) + + # (d) make sure we have *valid* credentials for all these + 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),) ) + for slice_hrn in my_slices: + hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (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 + # switch to myslice using an authority instead of a user + 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, + "%s.%s_for_%s.%s.cred"%(hrn,htype,delegatee_hrn,delegatee_type)) + with file(filename,'w') as f: + f.write(delegated_credential) + self.logger.debug("(Over)wrote %s"%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 + self.logger.info("Uploading on backend at %s"%myslice_dict['backend']) + uploader = ManifoldUploader (logger=self.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="%s (%s) [exp:%s]"%(hrn,htype,expire_datetime) + if uploader.upload(delegated_credential,message=message): + count_success+=1 + count_all+=1 + self.logger.info("Successfully uploaded %d/%d credentials"%(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 + if count_success != count_all: sys.exit(1) + # xxx should analyze result + return 0 + + @declare_command("cred","") def trusted(self, options, args): """ - return uhe trusted certs at this interface (get_trusted_certs) + 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 - - def config (self, options, args): - "Display contents of current config" - self.show_config() + print "Certificate:\n%s\n\n"%trusted_cert + # xxx should analyze result + return 0