2 # sfi.py - basic SFA command-line client
3 # this module is also used in sfascan
17 from lxml import etree
18 from StringIO import StringIO
19 from optparse import OptionParser
20 from pprint import PrettyPrinter
21 from tempfile import mkstemp
23 from sfa.trust.certificate import Keypair, Certificate
24 from sfa.trust.gid import GID
25 from sfa.trust.credential import Credential
26 from sfa.trust.sfaticket import SfaTicket
28 from sfa.util.faults import SfaInvalidArgument
29 from sfa.util.sfalogging import sfi_logger
30 from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn, Xrn
31 from sfa.util.config import Config
32 from sfa.util.version import version_core
33 from sfa.util.cache import Cache
34 from sfa.util.printable import printable
36 from sfa.storage.record import Record
38 from sfa.rspecs.rspec import RSpec
39 from sfa.rspecs.rspec_converter import RSpecConverter
40 from sfa.rspecs.version_manager import VersionManager
42 from sfa.client.sfaclientlib import SfaClientBootstrap
43 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
44 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
45 from sfa.client.return_value import ReturnValue
46 from sfa.client.candidates import Candidates
47 from sfa.client.manifolduploader import ManifoldUploader
51 from sfa.client.common import optparse_listvalue_callback, optparse_dictvalue_callback, \
52 terminal_render, filter_records
55 def display_rspec(rspec, format='rspec'):
57 tree = etree.parse(StringIO(rspec))
59 result = root.xpath("./network/site/node/hostname/text()")
60 elif format in ['ip']:
61 # The IP address is not yet part of the new RSpec
62 # so this doesn't do anything yet.
63 tree = etree.parse(StringIO(rspec))
65 result = root.xpath("./network/site/node/ipv4/text()")
72 def display_list(results):
73 for result in results:
76 def display_records(recordList, dump=False):
77 ''' Print all fields in the record'''
78 for record in recordList:
79 display_record(record, dump)
81 def display_record(record, dump=False):
83 record.dump(sort=True)
85 info = record.getdict()
86 print "%s (%s)" % (info['hrn'], info['type'])
90 def filter_records(type, records):
92 for record in records:
93 if (record['type'] == type) or (type == "all"):
94 filtered_records.append(record)
95 return filtered_records
98 def credential_printable (cred):
99 credential=Credential(cred=cred)
101 result += credential.get_summary_tostring()
103 rights = credential.get_privileges()
104 result += "type=%s\n" % credential.type
105 result += "version=%s\n" % credential.version
106 result += "rights=%s\n"%rights
109 def show_credentials (cred_s):
110 if not isinstance (cred_s,list): cred_s = [cred_s]
112 print "Using Credential %s"%credential_printable(cred)
115 def save_raw_to_file(var, filename, format="text", banner=None):
117 # if filename is "-", send it to stdout
120 f = open(filename, "w")
125 elif format == "pickled":
126 f.write(pickle.dumps(var))
127 elif format == "json":
128 if hasattr(json, "dumps"):
129 f.write(json.dumps(var)) # python 2.6
131 f.write(json.write(var)) # python 2.5
133 # this should never happen
134 print "unknown output format", format
136 f.write('\n'+banner+"\n")
138 def save_rspec_to_file(rspec, filename):
139 if not filename.endswith(".rspec"):
140 filename = filename + ".rspec"
141 f = open(filename, 'w')
146 def save_records_to_file(filename, record_dicts, format="xml"):
149 for record_dict in record_dicts:
151 save_record_to_file(filename + "." + str(index), record_dict)
153 save_record_to_file(filename, record_dict)
155 elif format == "xmllist":
156 f = open(filename, "w")
157 f.write("<recordlist>\n")
158 for record_dict in record_dicts:
159 record_obj=Record(dict=record_dict)
160 f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
161 f.write("</recordlist>\n")
163 elif format == "hrnlist":
164 f = open(filename, "w")
165 for record_dict in record_dicts:
166 record_obj=Record(dict=record_dict)
167 f.write(record_obj.hrn + "\n")
170 # this should never happen
171 print "unknown output format", format
173 def save_record_to_file(filename, record_dict):
174 record = Record(dict=record_dict)
175 xml = record.save_as_xml()
176 f=codecs.open(filename, encoding='utf-8',mode="w")
181 # minimally check a key argument
182 def check_ssh_key (key):
183 good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
184 return re.match(good_ssh_key, key, re.IGNORECASE)
187 def load_record_from_opts(options):
189 if hasattr(options, 'xrn') and options.xrn:
190 if hasattr(options, 'type') and options.type:
191 xrn = Xrn(options.xrn, options.type)
193 xrn = Xrn(options.xrn)
194 record_dict['urn'] = xrn.get_urn()
195 record_dict['hrn'] = xrn.get_hrn()
196 record_dict['type'] = xrn.get_type()
197 if hasattr(options, 'key') and options.key:
199 pubkey = open(options.key, 'r').read()
202 if not check_ssh_key (pubkey):
203 raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format")
204 record_dict['reg-keys'] = [pubkey]
205 if hasattr(options, 'slices') and options.slices:
206 record_dict['slices'] = options.slices
207 if hasattr(options, 'reg_researchers') and options.reg_researchers is not None:
208 record_dict['reg-researchers'] = options.reg_researchers
209 if hasattr(options, 'email') and options.email:
210 record_dict['email'] = options.email
211 if hasattr(options, 'reg_pis') and options.reg_pis:
212 record_dict['reg-pis'] = options.reg_pis
214 # handle extra settings
215 record_dict.update(options.extras)
217 return Record(dict=record_dict)
219 def load_record_from_file(filename):
220 f=codecs.open(filename, encoding="utf-8", mode="r")
221 xml_string = f.read()
223 return Record(xml=xml_string)
227 def unique_call_id(): return uuid.uuid4().urn
229 ########## a simple model for maintaing 3 doc attributes per command (instead of just one)
230 # essentially for the methods that implement a subcommand like sfi list
231 # we need to keep track of
232 # (*) doc a few lines that tell what it does, still located in __doc__
233 # (*) args_string a simple one-liner that describes mandatory arguments
234 # (*) example well, one or several releant examples
236 # since __doc__ only accounts for one, we use this simple mechanism below
237 # however we keep doc in place for easier migration
239 from functools import wraps
241 # we use a list as well as a dict so we can keep track of the order
245 def declare_command (args_string, example,aliases=None):
247 name=getattr(m,'__name__')
248 doc=getattr(m,'__doc__',"-- missing doc --")
249 doc=doc.strip(" \t\n")
250 commands_list.append(name)
251 # last item is 'canonical' name, so we can know which commands are aliases
252 command_tuple=(doc, args_string, example,name)
253 commands_dict[name]=command_tuple
254 if aliases is not None:
255 for alias in aliases:
256 commands_list.append(alias)
257 commands_dict[alias]=command_tuple
259 def new_method (*args, **kwds): return m(*args, **kwds)
264 def remove_none_fields (record):
265 none_fields=[ k for (k,v) in record.items() if v is None ]
266 for k in none_fields: del record[k]
272 # dirty hack to make this class usable from the outside
273 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
276 def default_sfi_dir ():
277 if os.path.isfile("./sfi_config"):
280 return os.path.expanduser("~/.sfi/")
282 # dummy to meet Sfi's expectations for its 'options' field
283 # i.e. s/t we can do setattr on
287 def __init__ (self,options=None):
288 if options is None: options=Sfi.DummyOptions()
289 for opt in Sfi.required_options:
290 if not hasattr(options,opt): setattr(options,opt,None)
291 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
292 self.options = options
294 self.authority = None
295 self.logger = sfi_logger
296 self.logger.enable_console()
297 ### various auxiliary material that we keep at hand
299 # need to call this other than just 'config' as we have a command/method with that name
300 self.config_instance=None
301 self.config_file=None
302 self.client_bootstrap=None
304 ### suitable if no reasonable command has been provided
305 def print_commands_help (self, options):
306 verbose=getattr(options,'verbose')
307 format3="%10s %-35s %s"
311 print format3%("command","cmd_args","description")
315 self.create_parser_global().print_help()
316 # preserve order from the code
317 for command in commands_list:
319 (doc, args_string, example, canonical) = commands_dict[command]
321 print "Cannot find info on command %s - skipped"%command
325 if command==canonical:
326 doc=doc.replace("\n","\n"+format3offset*' ')
327 print format3%(command,args_string,doc)
329 self.create_parser_command(command).print_help()
331 print format3%(command,"<<alias for %s>>"%canonical,"")
333 ### now if a known command was found we can be more verbose on that one
334 def print_help (self):
335 print "==================== Generic sfi usage"
336 self.sfi_parser.print_help()
337 (doc,_,example,canonical)=commands_dict[self.command]
338 if canonical != self.command:
339 print "\n==================== NOTE: %s is an alias for genuine %s"%(self.command,canonical)
340 self.command=canonical
341 print "\n==================== Purpose of %s"%self.command
343 print "\n==================== Specific usage for %s"%self.command
344 self.command_parser.print_help()
346 print "\n==================== %s example(s)"%self.command
349 def create_parser_global(self):
350 # Generate command line parser
351 parser = OptionParser(add_help_option=False,
352 usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
353 description="Commands: %s"%(" ".join(commands_list)))
354 parser.add_option("-r", "--registry", dest="registry",
355 help="root registry", metavar="URL", default=None)
356 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
357 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
358 parser.add_option("-R", "--raw", dest="raw", default=None,
359 help="Save raw, unparsed server response to a file")
360 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
361 help="raw file format ([text]|pickled|json)", default="text",
362 choices=("text","pickled","json"))
363 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
364 help="text string to write before and after raw output")
365 parser.add_option("-d", "--dir", dest="sfi_dir",
366 help="config & working directory - default is %default",
367 metavar="PATH", default=Sfi.default_sfi_dir())
368 parser.add_option("-u", "--user", dest="user",
369 help="user name", metavar="HRN", default=None)
370 parser.add_option("-a", "--auth", dest="auth",
371 help="authority name", metavar="HRN", default=None)
372 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
373 help="verbose mode - cumulative")
374 parser.add_option("-D", "--debug",
375 action="store_true", dest="debug", default=False,
376 help="Debug (xml-rpc) protocol messages")
377 # would it make sense to use ~/.ssh/id_rsa as a default here ?
378 parser.add_option("-k", "--private-key",
379 action="store", dest="user_private_key", default=None,
380 help="point to the private key file to use if not yet installed in sfi_dir")
381 parser.add_option("-t", "--timeout", dest="timeout", default=None,
382 help="Amout of time to wait before timing out the request")
383 parser.add_option("-h", "--help",
384 action="store_true", dest="help", default=False,
385 help="one page summary on commands & exit")
386 parser.disable_interspersed_args()
391 def create_parser_command(self, command):
392 if command not in commands_dict:
393 msg="Invalid command\n"
395 msg += ','.join(commands_list)
396 self.logger.critical(msg)
399 # retrieve args_string
400 (_, args_string, __,canonical) = commands_dict[command]
402 parser = OptionParser(add_help_option=False,
403 usage="sfi [sfi_options] %s [cmd_options] %s"
404 % (command, args_string))
405 parser.add_option ("-h","--help",dest='help',action='store_true',default=False,
406 help="Summary of one command usage")
408 if canonical in ("config"):
409 parser.add_option('-m', '--myslice', dest='myslice', action='store_true', default=False,
410 help='how myslice config variables as well')
412 if canonical in ("version"):
413 parser.add_option("-l","--local",
414 action="store_true", dest="version_local", default=False,
415 help="display version of the local client")
417 if canonical in ("version", "trusted"):
418 parser.add_option("-R","--registry_interface",
419 action="store_true", dest="registry_interface", default=False,
420 help="target the registry interface instead of slice interface")
422 if canonical in ("register", "update"):
423 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
424 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
425 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
426 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
428 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
429 default='', type="str", action='callback', callback=optparse_listvalue_callback)
430 parser.add_option('-r', '--researchers', dest='reg_researchers', metavar='<researchers>',
431 help='Set/replace slice researchers - use -r none to reset', default=None, type="str", action='callback',
432 callback=optparse_listvalue_callback)
433 parser.add_option('-p', '--pis', dest='reg_pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
434 default='', type="str", action='callback', callback=optparse_listvalue_callback)
435 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
436 action="callback", callback=optparse_dictvalue_callback, nargs=1,
437 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
439 # user specifies remote aggregate/sm/component
440 if canonical in ("resources", "describe", "allocate", "provision", "delete", "allocate", "provision",
441 "action", "shutdown", "renew", "status"):
442 parser.add_option("-d", "--delegate", dest="delegate", default=None,
444 help="Include a credential delegated to the user's root"+\
445 "authority in set of credentials for this call")
447 # show_credential option
448 if canonical in ("list","resources", "describe", "provision", "allocate", "register","update","remove","delete","status","renew"):
449 parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
450 help="show credential(s) used in human-readable form")
451 if canonical in ("renew"):
452 parser.add_option("-l","--as-long-as-possible",dest='alap',action='store_true',default=False,
453 help="renew as long as possible")
454 # registy filter option
455 if canonical in ("list", "show", "remove"):
456 parser.add_option("-t", "--type", dest="type", type="choice",
457 help="type filter ([all]|user|slice|authority|node|aggregate)",
458 choices=("all", "user", "slice", "authority", "node", "aggregate"),
460 if canonical in ("show"):
461 parser.add_option("-k","--key",dest="keys",action="append",default=[],
462 help="specify specific keys to be displayed from record")
463 parser.add_option("-n","--no-details",dest="no_details",action="store_true",default=False,
464 help="call Resolve without the 'details' option")
465 if canonical in ("resources", "describe"):
467 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="GENI 3",
468 help="schema type and version of resulting RSpec")
469 # disable/enable cached rspecs
470 parser.add_option("-c", "--current", dest="current", default=False,
472 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
474 parser.add_option("-f", "--format", dest="format", type="choice",
475 help="display format ([xml]|dns|ip)", default="xml",
476 choices=("xml", "dns", "ip"))
477 #panos: a new option to define the type of information about resources a user is interested in
478 parser.add_option("-i", "--info", dest="info",
479 help="optional component information", default=None)
480 # a new option to retreive or not reservation-oriented RSpecs (leases)
481 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
482 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
483 choices=("all", "resources", "leases"), default="resources")
486 if canonical in ("resources", "describe", "allocate", "provision", "show", "list", "gid"):
487 parser.add_option("-o", "--output", dest="file",
488 help="output XML to file", metavar="FILE", default=None)
490 if canonical in ("show", "list"):
491 parser.add_option("-f", "--format", dest="format", type="choice",
492 help="display format ([text]|xml)", default="text",
493 choices=("text", "xml"))
495 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
496 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
497 choices=("xml", "xmllist", "hrnlist"))
498 if canonical == 'list':
499 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
500 help="list all child records", default=False)
501 parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
502 help="gives details, like user keys", default=False)
503 if canonical in ("delegate"):
504 parser.add_option("-u", "--user",
505 action="store_true", dest="delegate_user", default=False,
506 help="delegate your own credentials; default if no other option is provided")
507 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
508 metavar="slice_hrn", help="delegate cred. for slice HRN")
509 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
510 metavar='auth_hrn', help="delegate cred for auth HRN")
511 # this primarily is a shorthand for -A my_hrn^
512 parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
513 help="delegate your PI credentials, so s.t. like -A your_hrn^")
514 parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
515 help="""by default the mandatory argument is expected to be a user,
516 use this if you mean an authority instead""")
518 if canonical in ("myslice"):
519 parser.add_option("-p","--password",dest='password',action='store',default=None,
520 help="specify mainfold password on the command line")
521 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
522 metavar="slice_hrn", help="delegate cred. for slice HRN")
523 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
524 metavar='auth_hrn', help="delegate PI cred for auth HRN")
525 parser.add_option('-d', '--delegate', dest='delegate', help="Override 'delegate' from the config file")
526 parser.add_option('-b', '--backend', dest='backend', help="Override 'backend' from the config file")
532 # Main: parse arguments and dispatch to command
534 def dispatch(self, command, command_options, command_args):
535 (doc, args_string, example, canonical) = commands_dict[command]
536 method=getattr(self, canonical, None)
538 print "sfi: unknown command %s"%command
539 raise SystemExit,"Unknown command %s"%command
540 return method(command_options, command_args)
543 self.sfi_parser = self.create_parser_global()
544 (options, args) = self.sfi_parser.parse_args()
546 self.print_commands_help(options)
548 self.options = options
550 self.logger.setLevelFromOptVerbose(self.options.verbose)
553 self.logger.critical("No command given. Use -h for help.")
554 self.print_commands_help(options)
557 # complete / find unique match with command set
558 command_candidates = Candidates (commands_list)
560 command = command_candidates.only_match(input)
562 self.print_commands_help(options)
564 # second pass options parsing
566 self.command_parser = self.create_parser_command(command)
567 (command_options, command_args) = self.command_parser.parse_args(args[1:])
568 if command_options.help:
571 self.command_options = command_options
575 self.logger.debug("Command=%s" % self.command)
578 retcod = self.dispatch(command, command_options, command_args)
582 self.logger.log_exc ("sfi command %s failed"%command)
587 def read_config(self):
588 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
589 shell_config_file = os.path.join(self.options.sfi_dir,"sfi_config.sh")
591 if Config.is_ini(config_file):
592 config = Config (config_file)
594 # try upgrading from shell config format
595 fp, fn = mkstemp(suffix='sfi_config', text=True)
597 # we need to preload the sections we want parsed
598 # from the shell config
599 config.add_section('sfi')
600 # sface users should be able to use this same file to configure their stuff
601 config.add_section('sface')
602 # manifold users should be able to specify the details
603 # of their backend server here for 'sfi myslice'
604 config.add_section('myslice')
605 config.load(config_file)
607 shutil.move(config_file, shell_config_file)
609 config.save(config_file)
612 self.logger.critical("Failed to read configuration file %s"%config_file)
613 self.logger.info("Make sure to remove the export clauses and to add quotes")
614 if self.options.verbose==0:
615 self.logger.info("Re-run with -v for more details")
617 self.logger.log_exc("Could not read config file %s"%config_file)
620 self.config_instance=config
623 if (self.options.sm is not None):
624 self.sm_url = self.options.sm
625 elif hasattr(config, "SFI_SM"):
626 self.sm_url = config.SFI_SM
628 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
632 if (self.options.registry is not None):
633 self.reg_url = self.options.registry
634 elif hasattr(config, "SFI_REGISTRY"):
635 self.reg_url = config.SFI_REGISTRY
637 self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
641 if (self.options.user is not None):
642 self.user = self.options.user
643 elif hasattr(config, "SFI_USER"):
644 self.user = config.SFI_USER
646 self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
650 if (self.options.auth is not None):
651 self.authority = self.options.auth
652 elif hasattr(config, "SFI_AUTH"):
653 self.authority = config.SFI_AUTH
655 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
658 self.config_file=config_file
663 # Get various credential and spec files
665 # Establishes limiting conventions
666 # - conflates MAs and SAs
667 # - assumes last token in slice name is unique
669 # Bootstraps credentials
670 # - bootstrap user credential from self-signed certificate
671 # - bootstrap authority credential from user credential
672 # - bootstrap slice credential from user credential
675 # init self-signed cert, user credentials and gid
676 def bootstrap (self):
677 if self.options.verbose:
678 self.logger.info("Initializing SfaClientBootstrap with {}".format(self.reg_url))
679 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
681 # if -k is provided, use this to initialize private key
682 if self.options.user_private_key:
683 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
685 # trigger legacy compat code if needed
686 # the name has changed from just <leaf>.pkey to <hrn>.pkey
687 if not os.path.isfile(client_bootstrap.private_key_filename()):
688 self.logger.info ("private key not found, trying legacy name")
690 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
691 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
692 client_bootstrap.init_private_key_if_missing (legacy_private_key)
693 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
695 self.logger.log_exc("Can't find private key ")
699 client_bootstrap.bootstrap_my_gid()
700 # extract what's needed
701 self.private_key = client_bootstrap.private_key()
702 self.my_credential_string = client_bootstrap.my_credential_string ()
703 self.my_credential = {'geni_type': 'geni_sfa',
705 'geni_value': self.my_credential_string}
706 self.my_gid = client_bootstrap.my_gid ()
707 self.client_bootstrap = client_bootstrap
710 def my_authority_credential_string(self):
711 if not self.authority:
712 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
714 return self.client_bootstrap.authority_credential_string (self.authority)
716 def authority_credential_string(self, auth_hrn):
717 return self.client_bootstrap.authority_credential_string (auth_hrn)
719 def slice_credential_string(self, name):
720 return self.client_bootstrap.slice_credential_string (name)
722 def slice_credential(self, name):
723 return {'geni_type': 'geni_sfa',
725 'geni_value': self.slice_credential_string(name)}
727 # xxx should be supported by sfaclientbootstrap as well
728 def delegate_cred(self, object_cred, hrn, type='authority'):
729 # the gid and hrn of the object we are delegating
730 if isinstance(object_cred, str):
731 object_cred = Credential(string=object_cred)
732 object_gid = object_cred.get_gid_object()
733 object_hrn = object_gid.get_hrn()
735 if not object_cred.get_privileges().get_all_delegate():
736 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
739 # the delegating user's gid
740 caller_gidfile = self.my_gid()
742 # the gid of the user who will be delegated to
743 delegee_gid = self.client_bootstrap.gid(hrn,type)
744 delegee_hrn = delegee_gid.get_hrn()
745 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
746 return dcred.save_to_string(save_parents=True)
749 # Management of the servers
754 if not hasattr (self, 'registry_proxy'):
755 self.logger.info("Contacting Registry at: %s"%self.reg_url)
756 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
757 timeout=self.options.timeout, verbose=self.options.debug)
758 return self.registry_proxy
762 if not hasattr (self, 'sliceapi_proxy'):
763 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
764 if hasattr(self.command_options,'component') and self.command_options.component:
765 # resolve the hrn at the registry
766 node_hrn = self.command_options.component
767 records = self.registry().Resolve(node_hrn, self.my_credential_string)
768 records = filter_records('node', records)
770 self.logger.warning("No such component:%r"% opts.component)
772 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
773 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
775 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
776 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
777 self.sm_url = 'http://' + self.sm_url
778 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
779 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
780 timeout=self.options.timeout, verbose=self.options.debug)
781 return self.sliceapi_proxy
783 def get_cached_server_version(self, server):
784 # check local cache first
787 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
788 cache_key = server.url + "-version"
790 cache = Cache(cache_file)
793 self.logger.info("Local cache not found at: %s" % cache_file)
796 version = cache.get(cache_key)
799 result = server.GetVersion()
800 version= ReturnValue.get_value(result)
801 # cache version for 20 minutes
802 cache.add(cache_key, version, ttl= 60*20)
803 self.logger.info("Updating cache file %s" % cache_file)
804 cache.save_to_file(cache_file)
808 ### resurrect this temporarily so we can support V1 aggregates for a while
809 def server_supports_options_arg(self, server):
811 Returns true if server support the optional call_id arg, false otherwise.
813 server_version = self.get_cached_server_version(server)
815 # xxx need to rewrite this
816 if int(server_version.get('geni_api')) >= 2:
820 def server_supports_call_id_arg(self, server):
821 server_version = self.get_cached_server_version(server)
823 if 'sfa' in server_version and 'code_tag' in server_version:
824 code_tag = server_version['code_tag']
825 code_tag_parts = code_tag.split("-")
826 version_parts = code_tag_parts[0].split(".")
827 major, minor = version_parts[0], version_parts[1]
828 rev = code_tag_parts[1]
829 if int(major) == 1 and minor == 0 and build >= 22:
833 ### ois = options if supported
834 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
835 def ois (self, server, option_dict):
836 if self.server_supports_options_arg (server):
838 elif self.server_supports_call_id_arg (server):
839 return [ unique_call_id () ]
843 ### cis = call_id if supported - like ois
844 def cis (self, server):
845 if self.server_supports_call_id_arg (server):
846 return [ unique_call_id ]
850 ######################################## miscell utilities
851 def get_rspec_file(self, rspec):
852 if (os.path.isabs(rspec)):
855 file = os.path.join(self.options.sfi_dir, rspec)
856 if (os.path.isfile(file)):
859 self.logger.critical("No such rspec file %s"%rspec)
862 def get_record_file(self, record):
863 if (os.path.isabs(record)):
866 file = os.path.join(self.options.sfi_dir, record)
867 if (os.path.isfile(file)):
870 self.logger.critical("No such registry record file %s"%record)
874 # helper function to analyze raw output
875 # for main : return 0 if everything is fine, something else otherwise (mostly 1 for now)
876 def success (self, raw):
877 return_value=ReturnValue (raw)
878 output=ReturnValue.get_output(return_value)
879 # means everything is fine
882 # something went wrong
883 print 'ERROR:',output
886 #==========================================================================
887 # Following functions implement the commands
889 # Registry-related commands
890 #==========================================================================
892 @declare_command("","")
893 def config (self, options, args):
894 "Display contents of current config"
895 print "# From configuration file %s"%self.config_file
896 flags=[ ('sfi', [ ('registry','reg_url'),
897 ('auth','authority'),
903 flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
905 for (section, tuples) in flags:
908 for (external_name, internal_name) in tuples:
909 print "%-20s = %s"%(external_name,getattr(self,internal_name))
912 varname="%s_%s"%(section.upper(),name.upper())
913 value=getattr(self.config_instance,varname)
914 print "%-20s = %s"%(name,value)
915 # xxx should analyze result
918 @declare_command("","")
919 def version(self, options, args):
921 display an SFA server version (GetVersion)
922 or version information about sfi itself
924 if options.version_local:
925 version=version_core()
927 if options.registry_interface:
928 server=self.registry()
930 server = self.sliceapi()
931 result = server.GetVersion()
932 version = ReturnValue.get_value(result)
934 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
936 pprinter = PrettyPrinter(indent=4)
937 pprinter.pprint(version)
938 # xxx should analyze result
941 @declare_command("authority","")
942 def list(self, options, args):
944 list entries in named authority registry (List)
951 if options.recursive:
952 opts['recursive'] = options.recursive
954 if options.show_credential:
955 show_credentials(self.my_credential_string)
957 list = self.registry().List(hrn, self.my_credential_string, options)
959 raise Exception, "Not enough parameters for the 'list' command"
961 # filter on person, slice, site, node, etc.
962 # This really should be in the self.filter_records funct def comment...
963 list = filter_records(options.type, list)
964 terminal_render (list, options)
966 save_records_to_file(options.file, list, options.fileformat)
967 # xxx should analyze result
970 @declare_command("name","")
971 def show(self, options, args):
973 show details about named registry record (Resolve)
979 # explicitly require Resolve to run in details mode
981 if not options.no_details: resolve_options['details']=True
982 record_dicts = self.registry().Resolve(hrn, self.my_credential_string, resolve_options)
983 record_dicts = filter_records(options.type, record_dicts)
985 self.logger.error("No record of type %s"% options.type)
987 # user has required to focus on some keys
989 def project (record):
991 for key in options.keys:
992 try: projected[key]=record[key]
995 record_dicts = [ project (record) for record in record_dicts ]
996 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
997 for record in records:
998 if (options.format == "text"): record.dump(sort=True)
999 else: print record.save_as_xml()
1001 save_records_to_file(options.file, record_dicts, options.fileformat)
1002 # xxx should analyze result
1005 # this historically was named 'add', it is now 'register' with an alias for legacy
1006 @declare_command("[xml-filename]","",['add'])
1007 def register(self, options, args):
1008 """create new record in registry (Register)
1009 from command line options (recommended)
1010 old-school method involving an xml file still supported"""
1012 auth_cred = self.my_authority_credential_string()
1013 if options.show_credential:
1014 show_credentials(auth_cred)
1021 record_filepath = args[0]
1022 rec_file = self.get_record_file(record_filepath)
1023 record_dict.update(load_record_from_file(rec_file).todict())
1025 print "Cannot load record file %s"%record_filepath
1028 record_dict.update(load_record_from_opts(options).todict())
1029 # we should have a type by now
1030 if 'type' not in record_dict :
1033 # this is still planetlab dependent.. as plc will whine without that
1034 # also, it's only for adding
1035 if record_dict['type'] == 'user':
1036 if not 'first_name' in record_dict:
1037 record_dict['first_name'] = record_dict['hrn']
1038 if 'last_name' not in record_dict:
1039 record_dict['last_name'] = record_dict['hrn']
1040 register = self.registry().Register(record_dict, auth_cred)
1041 # xxx looks like the result here is not ReturnValue-compatible
1042 #return self.success (register)
1043 # xxx should analyze result
1046 @declare_command("[xml-filename]","")
1047 def update(self, options, args):
1048 """update record into registry (Update)
1049 from command line options (recommended)
1050 old-school method involving an xml file still supported"""
1053 record_filepath = args[0]
1054 rec_file = self.get_record_file(record_filepath)
1055 record_dict.update(load_record_from_file(rec_file).todict())
1057 record_dict.update(load_record_from_opts(options).todict())
1058 # at the very least we need 'type' here
1059 if 'type' not in record_dict:
1063 # don't translate into an object, as this would possibly distort
1064 # user-provided data; e.g. add an 'email' field to Users
1065 if record_dict['type'] in ['user']:
1066 if record_dict['hrn'] == self.user:
1067 cred = self.my_credential_string
1069 cred = self.my_authority_credential_string()
1070 elif record_dict['type'] in ['slice']:
1072 cred = self.slice_credential_string(record_dict['hrn'])
1073 except ServerException, e:
1074 # XXX smbaker -- once we have better error return codes, update this
1075 # to do something better than a string compare
1076 if "Permission error" in e.args[0]:
1077 cred = self.my_authority_credential_string()
1080 elif record_dict['type'] in ['authority']:
1081 cred = self.my_authority_credential_string()
1082 elif record_dict['type'] in ['node']:
1083 cred = self.my_authority_credential_string()
1085 raise "unknown record type" + record_dict['type']
1086 if options.show_credential:
1087 show_credentials(cred)
1088 update = self.registry().Update(record_dict, cred)
1089 # xxx looks like the result here is not ReturnValue-compatible
1090 #return self.success(update)
1091 # xxx should analyze result
1094 @declare_command("hrn","")
1095 def remove(self, options, args):
1096 "remove registry record by name (Remove)"
1097 auth_cred = self.my_authority_credential_string()
1105 if options.show_credential:
1106 show_credentials(auth_cred)
1107 remove = self.registry().Remove(hrn, auth_cred, type)
1108 # xxx looks like the result here is not ReturnValue-compatible
1109 #return self.success (remove)
1110 # xxx should analyze result
1113 # ==================================================================
1114 # Slice-related commands
1115 # ==================================================================
1117 # show rspec for named slice
1118 @declare_command("","")
1119 def resources(self, options, args):
1121 discover available resources (ListResources)
1123 server = self.sliceapi()
1126 creds = [self.my_credential]
1127 if options.delegate:
1128 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1129 if options.show_credential:
1130 show_credentials(creds)
1132 # no need to check if server accepts the options argument since the options has
1133 # been a required argument since v1 API
1135 # always send call_id to v2 servers
1136 api_options ['call_id'] = unique_call_id()
1137 # ask for cached value if available
1138 api_options ['cached'] = True
1140 api_options['info'] = options.info
1141 if options.list_leases:
1142 api_options['list_leases'] = options.list_leases
1144 if options.current == True:
1145 api_options['cached'] = False
1147 api_options['cached'] = True
1148 if options.rspec_version:
1149 version_manager = VersionManager()
1150 server_version = self.get_cached_server_version(server)
1151 if 'sfa' in server_version:
1152 # just request the version the client wants
1153 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1155 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1157 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1158 list_resources = server.ListResources (creds, api_options)
1159 value = ReturnValue.get_value(list_resources)
1160 if self.options.raw:
1161 save_raw_to_file(list_resources, self.options.raw, self.options.rawformat, self.options.rawbanner)
1162 if options.file is not None:
1163 save_rspec_to_file(value, options.file)
1164 if (self.options.raw is None) and (options.file is None):
1165 display_rspec(value, options.format)
1166 return self.success(list_resources)
1168 @declare_command("slice_hrn","")
1169 def describe(self, options, args):
1171 shows currently allocated/provisioned resources
1172 of the named slice or set of slivers (Describe)
1174 server = self.sliceapi()
1177 creds = [self.slice_credential(args[0])]
1178 if options.delegate:
1179 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1180 if options.show_credential:
1181 show_credentials(creds)
1183 api_options = {'call_id': unique_call_id(),
1185 'info': options.info,
1186 'list_leases': options.list_leases,
1187 'geni_rspec_version': {'type': 'geni', 'version': '3'},
1190 api_options['info'] = options.info
1192 if options.rspec_version:
1193 version_manager = VersionManager()
1194 server_version = self.get_cached_server_version(server)
1195 if 'sfa' in server_version:
1196 # just request the version the client wants
1197 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1199 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1200 urn = Xrn(args[0], type='slice').get_urn()
1201 remove_none_fields(api_options)
1202 describe = server.Describe([urn], creds, api_options)
1203 value = ReturnValue.get_value(describe)
1204 if self.options.raw:
1205 save_raw_to_file(describe, self.options.raw, self.options.rawformat, self.options.rawbanner)
1206 if options.file is not None:
1207 save_rspec_to_file(value['geni_rspec'], options.file)
1208 if (self.options.raw is None) and (options.file is None):
1209 display_rspec(value['geni_rspec'], options.format)
1210 return self.success (describe)
1212 @declare_command("slice_hrn [<sliver_urn>...]","")
1213 def delete(self, options, args):
1215 de-allocate and de-provision all or named slivers of the named slice (Delete)
1217 server = self.sliceapi()
1221 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1224 # we have sliver urns
1225 sliver_urns = args[1:]
1227 # we provision all the slivers of the slice
1228 sliver_urns = [slice_urn]
1231 slice_cred = self.slice_credential(slice_hrn)
1232 creds = [slice_cred]
1234 # options and call_id when supported
1236 api_options ['call_id'] = unique_call_id()
1237 if options.show_credential:
1238 show_credentials(creds)
1239 delete = server.Delete(sliver_urns, creds, *self.ois(server, api_options ) )
1240 value = ReturnValue.get_value(delete)
1241 if self.options.raw:
1242 save_raw_to_file(delete, self.options.raw, self.options.rawformat, self.options.rawbanner)
1245 return self.success (delete)
1247 @declare_command("slice_hrn rspec","")
1248 def allocate(self, options, args):
1250 allocate resources to the named slice (Allocate)
1252 server = self.sliceapi()
1253 server_version = self.get_cached_server_version(server)
1255 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1258 creds = [self.slice_credential(slice_hrn)]
1260 delegated_cred = None
1261 if server_version.get('interface') == 'slicemgr':
1262 # delegate our cred to the slice manager
1263 # do not delegate cred to slicemgr...not working at the moment
1265 #if server_version.get('hrn'):
1266 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1267 #elif server_version.get('urn'):
1268 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1270 if options.show_credential:
1271 show_credentials(creds)
1274 rspec_file = self.get_rspec_file(args[1])
1275 rspec = open(rspec_file).read()
1277 api_options ['call_id'] = unique_call_id()
1281 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1282 remove_none_fields(slice_records[0])
1283 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1284 slice_record = slice_records[0]
1285 user_hrns = slice_record['reg-researchers']
1286 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1287 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1288 sfa_users = sfa_users_arg(user_records, slice_record)
1289 geni_users = pg_users_arg(user_records)
1291 api_options['sfa_users'] = sfa_users
1292 api_options['geni_users'] = geni_users
1294 allocate = server.Allocate(slice_urn, creds, rspec, api_options)
1295 value = ReturnValue.get_value(allocate)
1296 if self.options.raw:
1297 save_raw_to_file(allocate, self.options.raw, self.options.rawformat, self.options.rawbanner)
1298 if options.file is not None:
1299 save_rspec_to_file (value['geni_rspec'], options.file)
1300 if (self.options.raw is None) and (options.file is None):
1302 return self.success(allocate)
1304 @declare_command("slice_hrn [<sliver_urn>...]","")
1305 def provision(self, options, args):
1307 provision all or named already allocated slivers of the named slice (Provision)
1309 server = self.sliceapi()
1310 server_version = self.get_cached_server_version(server)
1312 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1314 # we have sliver urns
1315 sliver_urns = args[1:]
1317 # we provision all the slivers of the slice
1318 sliver_urns = [slice_urn]
1321 creds = [self.slice_credential(slice_hrn)]
1322 delegated_cred = None
1323 if server_version.get('interface') == 'slicemgr':
1324 # delegate our cred to the slice manager
1325 # do not delegate cred to slicemgr...not working at the moment
1327 #if server_version.get('hrn'):
1328 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1329 #elif server_version.get('urn'):
1330 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1332 if options.show_credential:
1333 show_credentials(creds)
1336 api_options ['call_id'] = unique_call_id()
1338 # set the requtested rspec version
1339 version_manager = VersionManager()
1340 rspec_version = version_manager._get_version('geni', '3').to_dict()
1341 api_options['geni_rspec_version'] = rspec_version
1344 # need to pass along user keys to the aggregate.
1346 # { urn: urn:publicid:IDN+emulab.net+user+alice
1347 # keys: [<ssh key A>, <ssh key B>]
1350 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1351 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1352 slice_record = slice_records[0]
1353 user_hrns = slice_record['reg-researchers']
1354 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1355 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1356 users = pg_users_arg(user_records)
1358 api_options['geni_users'] = users
1359 provision = server.Provision(sliver_urns, creds, api_options)
1360 value = ReturnValue.get_value(provision)
1361 if self.options.raw:
1362 save_raw_to_file(provision, self.options.raw, self.options.rawformat, self.options.rawbanner)
1363 if options.file is not None:
1364 save_rspec_to_file (value['geni_rspec'], options.file)
1365 if (self.options.raw is None) and (options.file is None):
1367 return self.success(provision)
1369 @declare_command("slice_hrn","")
1370 def status(self, options, args):
1372 retrieve the status of the slivers belonging to the named slice (Status)
1374 server = self.sliceapi()
1378 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1381 slice_cred = self.slice_credential(slice_hrn)
1382 creds = [slice_cred]
1384 # options and call_id when supported
1386 api_options['call_id']=unique_call_id()
1387 if options.show_credential:
1388 show_credentials(creds)
1389 status = server.Status([slice_urn], creds, *self.ois(server,api_options))
1390 value = ReturnValue.get_value(status)
1391 if self.options.raw:
1392 save_raw_to_file(status, self.options.raw, self.options.rawformat, self.options.rawbanner)
1395 return self.success (status)
1397 @declare_command("slice_hrn [<sliver_urn>...] action","")
1398 def action(self, options, args):
1400 Perform the named operational action on all or named slivers of the named slice
1402 server = self.sliceapi()
1406 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1408 # we have sliver urns
1409 sliver_urns = args[1:-1]
1411 # we provision all the slivers of the slice
1412 sliver_urns = [slice_urn]
1415 slice_cred = self.slice_credential(args[0])
1416 creds = [slice_cred]
1417 if options.delegate:
1418 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1419 creds.append(delegated_cred)
1421 perform_action = server.PerformOperationalAction(sliver_urns, creds, action , api_options)
1422 value = ReturnValue.get_value(perform_action)
1423 if self.options.raw:
1424 save_raw_to_file(perform_action, self.options.raw, self.options.rawformat, self.options.rawbanner)
1427 return self.success (perform_action)
1429 @declare_command("slice_hrn [<sliver_urn>...] time",
1430 "\n".join(["sfi renew onelab.ple.heartbeat 2015-04-31",
1431 "sfi renew onelab.ple.heartbeat 2015-04-31T14:00:00Z",
1432 "sfi renew onelab.ple.heartbeat +5d",
1433 "sfi renew onelab.ple.heartbeat +3w",
1434 "sfi renew onelab.ple.heartbeat +2m",]))
1435 def renew(self, options, args):
1439 server = self.sliceapi()
1444 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1447 # we have sliver urns
1448 sliver_urns = args[1:-1]
1450 # we provision all the slivers of the slice
1451 sliver_urns = [slice_urn]
1452 input_time = args[-1]
1454 # time: don't try to be smart on the time format, server-side will
1456 slice_cred = self.slice_credential(args[0])
1457 creds = [slice_cred]
1458 # options and call_id when supported
1460 api_options['call_id']=unique_call_id()
1462 api_options['geni_extend_alap']=True
1463 if options.show_credential:
1464 show_credentials(creds)
1465 renew = server.Renew(sliver_urns, creds, input_time, *self.ois(server,api_options))
1466 value = ReturnValue.get_value(renew)
1467 if self.options.raw:
1468 save_raw_to_file(renew, self.options.raw, self.options.rawformat, self.options.rawbanner)
1471 return self.success(renew)
1473 @declare_command("slice_hrn","")
1474 def shutdown(self, options, args):
1476 shutdown named slice (Shutdown)
1478 server = self.sliceapi()
1481 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1483 slice_cred = self.slice_credential(slice_hrn)
1484 creds = [slice_cred]
1485 shutdown = server.Shutdown(slice_urn, creds)
1486 value = ReturnValue.get_value(shutdown)
1487 if self.options.raw:
1488 save_raw_to_file(shutdown, self.options.raw, self.options.rawformat, self.options.rawbanner)
1491 return self.success (shutdown)
1493 @declare_command("[name]","")
1494 def gid(self, options, args):
1496 Create a GID (CreateGid)
1501 target_hrn = args[0]
1502 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1503 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1505 filename = options.file
1507 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1508 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1509 GID(string=gid).save_to_file(filename)
1510 # xxx should analyze result
1513 ####################
1514 @declare_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1516 will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1517 the set of credentials in the scope for this call would be
1518 (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1520 (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1522 (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1523 (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1524 because of the two -s options
1527 def delegate (self, options, args):
1529 (locally) create delegate credential for use by given hrn
1530 make sure to check for 'sfi myslice' instead if you plan
1537 # support for several delegations in the same call
1538 # so first we gather the things to do
1540 for slice_hrn in options.delegate_slices:
1541 message="%s.slice"%slice_hrn
1542 original = self.slice_credential_string(slice_hrn)
1543 tuples.append ( (message, original,) )
1544 if options.delegate_pi:
1545 my_authority=self.authority
1546 message="%s.pi"%my_authority
1547 original = self.my_authority_credential_string()
1548 tuples.append ( (message, original,) )
1549 for auth_hrn in options.delegate_auths:
1550 message="%s.auth"%auth_hrn
1551 original=self.authority_credential_string(auth_hrn)
1552 tuples.append ( (message, original, ) )
1553 # if nothing was specified at all at this point, let's assume -u
1554 if not tuples: options.delegate_user=True
1556 if options.delegate_user:
1557 message="%s.user"%self.user
1558 original = self.my_credential_string
1559 tuples.append ( (message, original, ) )
1561 # default type for beneficial is user unless -A
1562 if options.delegate_to_authority: to_type='authority'
1563 else: to_type='user'
1565 # let's now handle all this
1566 # it's all in the filenaming scheme
1567 for (message,original) in tuples:
1568 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1569 delegated_credential = Credential (string=delegated_string)
1570 filename = os.path.join ( self.options.sfi_dir,
1571 "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1572 delegated_credential.save_to_file(filename, save_parents=True)
1573 self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1575 ####################
1576 @declare_command("","""$ less +/myslice sfi_config
1578 backend = http://manifold.pl.sophia.inria.fr:7080
1579 # the HRN that myslice uses, so that we are delegating to
1580 delegate = ple.upmc.slicebrowser
1581 # platform - this is a myslice concept
1583 # username - as of this writing (May 2013) a simple login name
1587 will first collect the slices that you are part of, then make sure
1588 all your credentials are up-to-date (read: refresh expired ones)
1589 then compute delegated credentials for user 'ple.upmc.slicebrowser'
1590 and upload them all on myslice backend, using 'platform' and 'user'.
1591 A password will be prompted for the upload part.
1593 $ sfi -v myslice -- or sfi -vv myslice
1594 same but with more and more verbosity
1596 $ sfi m -b http://mymanifold.foo.com:7080/
1597 is synonym to sfi myslice as no other command starts with an 'm'
1598 and uses a custom backend for this one call
1601 def myslice (self, options, args):
1603 """ This helper is for refreshing your credentials at myslice; it will
1604 * compute all the slices that you currently have credentials on
1605 * refresh all your credentials (you as a user and pi, your slices)
1606 * upload them to the manifold backend server
1607 for last phase, sfi_config is read to look for the [myslice] section,
1608 and namely the 'backend', 'delegate' and 'user' settings"""
1614 # enable info by default
1615 self.logger.setLevelFromOptVerbose(self.options.verbose+1)
1616 ### the rough sketch goes like this
1617 # (0) produce a p12 file
1618 self.client_bootstrap.my_pkcs12()
1620 # (a) rain check for sufficient config in sfi_config
1622 myslice_keys=[ 'backend', 'delegate', 'platform', 'username']
1623 for key in myslice_keys:
1625 # oct 2013 - I'm finding myself juggling with config files
1626 # so a couple of command-line options can now override config
1627 if hasattr(options,key) and getattr(options,key) is not None:
1628 value=getattr(options,key)
1630 full_key="MYSLICE_" + key.upper()
1631 value=getattr(self.config_instance,full_key,None)
1632 if value: myslice_dict[key]=value
1633 else: print "Unsufficient config, missing key %s in [myslice] section of sfi_config"%key
1634 if len(myslice_dict) != len(myslice_keys):
1637 # (b) figure whether we are PI for the authority where we belong
1638 self.logger.info("Resolving our own id %s"%self.user)
1639 my_records=self.registry().Resolve(self.user,self.my_credential_string)
1640 if len(my_records)!=1: print "Cannot Resolve %s -- exiting"%self.user; sys.exit(1)
1641 my_record=my_records[0]
1642 my_auths_all = my_record['reg-pi-authorities']
1643 self.logger.info("Found %d authorities that we are PI for"%len(my_auths_all))
1644 self.logger.debug("They are %s"%(my_auths_all))
1646 my_auths = my_auths_all
1647 if options.delegate_auths:
1648 my_auths = list(set(my_auths_all).intersection(set(options.delegate_auths)))
1649 self.logger.debug("Restricted to user-provided auths"%(my_auths))
1651 # (c) get the set of slices that we are in
1652 my_slices_all=my_record['reg-slices']
1653 self.logger.info("Found %d slices that we are member of"%len(my_slices_all))
1654 self.logger.debug("They are: %s"%(my_slices_all))
1656 my_slices = my_slices_all
1657 # if user provided slices, deal only with these - if they are found
1658 if options.delegate_slices:
1659 my_slices = list(set(my_slices_all).intersection(set(options.delegate_slices)))
1660 self.logger.debug("Restricted to user-provided slices: %s"%(my_slices))
1662 # (d) make sure we have *valid* credentials for all these
1664 hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1665 for auth_hrn in my_auths:
1666 hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1667 for slice_hrn in my_slices:
1668 hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1670 # (e) check for the delegated version of these
1671 # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever
1672 # switch to myslice using an authority instead of a user
1673 delegatee_type='user'
1674 delegatee_hrn=myslice_dict['delegate']
1675 hrn_delegated_credentials = []
1676 for (hrn, htype, credential) in hrn_credentials:
1677 delegated_credential = self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type)
1678 # save these so user can monitor what she's uploaded
1679 filename = os.path.join ( self.options.sfi_dir,
1680 "%s.%s_for_%s.%s.cred"%(hrn,htype,delegatee_hrn,delegatee_type))
1681 with file(filename,'w') as f:
1682 f.write(delegated_credential)
1683 self.logger.debug("(Over)wrote %s"%filename)
1684 hrn_delegated_credentials.append ((hrn, htype, delegated_credential, filename, ))
1686 # (f) and finally upload them to manifold server
1687 # xxx todo add an option so the password can be set on the command line
1688 # (but *NOT* in the config file) so other apps can leverage this
1689 self.logger.info("Uploading on backend at %s"%myslice_dict['backend'])
1690 uploader = ManifoldUploader (logger=self.logger,
1691 url=myslice_dict['backend'],
1692 platform=myslice_dict['platform'],
1693 username=myslice_dict['username'],
1694 password=options.password)
1695 uploader.prompt_all()
1696 (count_all,count_success)=(0,0)
1697 for (hrn,htype,delegated_credential,filename) in hrn_delegated_credentials:
1699 inspect=Credential(string=delegated_credential)
1700 expire_datetime=inspect.get_expiration()
1701 message="%s (%s) [exp:%s]"%(hrn,htype,expire_datetime)
1702 if uploader.upload(delegated_credential,message=message):
1705 self.logger.info("Successfully uploaded %d/%d credentials"%(count_success,count_all))
1707 # at first I thought we would want to save these,
1708 # like 'sfi delegate does' but on second thought
1709 # it is probably not helpful as people would not
1710 # need to run 'sfi delegate' at all anymore
1711 if count_success != count_all: sys.exit(1)
1712 # xxx should analyze result
1715 @declare_command("cred","")
1716 def trusted(self, options, args):
1718 return the trusted certs at this interface (get_trusted_certs)
1720 if options.registry_interface:
1721 server=self.registry()
1723 server = self.sliceapi()
1724 cred = self.my_authority_credential_string()
1725 trusted_certs = server.get_trusted_certs(cred)
1726 if not options.registry_interface:
1727 trusted_certs = ReturnValue.get_value(trusted_certs)
1729 for trusted_cert in trusted_certs:
1730 print "\n===========================================================\n"
1731 gid = GID(string=trusted_cert)
1733 cert = Certificate(string=trusted_cert)
1734 self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1735 print "Certificate:\n%s\n\n"%trusted_cert
1736 # xxx should analyze result