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['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 self.dispatch(command, command_options, command_args)
582 self.logger.log_exc ("sfi command %s failed"%command)
588 def read_config(self):
589 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
590 shell_config_file = os.path.join(self.options.sfi_dir,"sfi_config.sh")
592 if Config.is_ini(config_file):
593 config = Config (config_file)
595 # try upgrading from shell config format
596 fp, fn = mkstemp(suffix='sfi_config', text=True)
598 # we need to preload the sections we want parsed
599 # from the shell config
600 config.add_section('sfi')
601 # sface users should be able to use this same file to configure their stuff
602 config.add_section('sface')
603 # manifold users should be able to specify the details
604 # of their backend server here for 'sfi myslice'
605 config.add_section('myslice')
606 config.load(config_file)
608 shutil.move(config_file, shell_config_file)
610 config.save(config_file)
613 self.logger.critical("Failed to read configuration file %s"%config_file)
614 self.logger.info("Make sure to remove the export clauses and to add quotes")
615 if self.options.verbose==0:
616 self.logger.info("Re-run with -v for more details")
618 self.logger.log_exc("Could not read config file %s"%config_file)
621 self.config_instance=config
624 if (self.options.sm is not None):
625 self.sm_url = self.options.sm
626 elif hasattr(config, "SFI_SM"):
627 self.sm_url = config.SFI_SM
629 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
633 if (self.options.registry is not None):
634 self.reg_url = self.options.registry
635 elif hasattr(config, "SFI_REGISTRY"):
636 self.reg_url = config.SFI_REGISTRY
638 self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
642 if (self.options.user is not None):
643 self.user = self.options.user
644 elif hasattr(config, "SFI_USER"):
645 self.user = config.SFI_USER
647 self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
651 if (self.options.auth is not None):
652 self.authority = self.options.auth
653 elif hasattr(config, "SFI_AUTH"):
654 self.authority = config.SFI_AUTH
656 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
659 self.config_file=config_file
664 # Get various credential and spec files
666 # Establishes limiting conventions
667 # - conflates MAs and SAs
668 # - assumes last token in slice name is unique
670 # Bootstraps credentials
671 # - bootstrap user credential from self-signed certificate
672 # - bootstrap authority credential from user credential
673 # - bootstrap slice credential from user credential
676 # init self-signed cert, user credentials and gid
677 def bootstrap (self):
678 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
680 # if -k is provided, use this to initialize private key
681 if self.options.user_private_key:
682 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
684 # trigger legacy compat code if needed
685 # the name has changed from just <leaf>.pkey to <hrn>.pkey
686 if not os.path.isfile(client_bootstrap.private_key_filename()):
687 self.logger.info ("private key not found, trying legacy name")
689 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
690 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
691 client_bootstrap.init_private_key_if_missing (legacy_private_key)
692 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
694 self.logger.log_exc("Can't find private key ")
698 client_bootstrap.bootstrap_my_gid()
699 # extract what's needed
700 self.private_key = client_bootstrap.private_key()
701 self.my_credential_string = client_bootstrap.my_credential_string ()
702 self.my_credential = {'geni_type': 'geni_sfa',
704 'geni_value': self.my_credential_string}
705 self.my_gid = client_bootstrap.my_gid ()
706 self.client_bootstrap = client_bootstrap
709 def my_authority_credential_string(self):
710 if not self.authority:
711 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
713 return self.client_bootstrap.authority_credential_string (self.authority)
715 def authority_credential_string(self, auth_hrn):
716 return self.client_bootstrap.authority_credential_string (auth_hrn)
718 def slice_credential_string(self, name):
719 return self.client_bootstrap.slice_credential_string (name)
721 def slice_credential(self, name):
722 return {'geni_type': 'geni_sfa',
724 'geni_value': self.slice_credential_string(name)}
726 # xxx should be supported by sfaclientbootstrap as well
727 def delegate_cred(self, object_cred, hrn, type='authority'):
728 # the gid and hrn of the object we are delegating
729 if isinstance(object_cred, str):
730 object_cred = Credential(string=object_cred)
731 object_gid = object_cred.get_gid_object()
732 object_hrn = object_gid.get_hrn()
734 if not object_cred.get_privileges().get_all_delegate():
735 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
738 # the delegating user's gid
739 caller_gidfile = self.my_gid()
741 # the gid of the user who will be delegated to
742 delegee_gid = self.client_bootstrap.gid(hrn,type)
743 delegee_hrn = delegee_gid.get_hrn()
744 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
745 return dcred.save_to_string(save_parents=True)
748 # Management of the servers
753 if not hasattr (self, 'registry_proxy'):
754 self.logger.info("Contacting Registry at: %s"%self.reg_url)
755 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
756 timeout=self.options.timeout, verbose=self.options.debug)
757 return self.registry_proxy
761 if not hasattr (self, 'sliceapi_proxy'):
762 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
763 if hasattr(self.command_options,'component') and self.command_options.component:
764 # resolve the hrn at the registry
765 node_hrn = self.command_options.component
766 records = self.registry().Resolve(node_hrn, self.my_credential_string)
767 records = filter_records('node', records)
769 self.logger.warning("No such component:%r"% opts.component)
771 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
772 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
774 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
775 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
776 self.sm_url = 'http://' + self.sm_url
777 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
778 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
779 timeout=self.options.timeout, verbose=self.options.debug)
780 return self.sliceapi_proxy
782 def get_cached_server_version(self, server):
783 # check local cache first
786 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
787 cache_key = server.url + "-version"
789 cache = Cache(cache_file)
792 self.logger.info("Local cache not found at: %s" % cache_file)
795 version = cache.get(cache_key)
798 result = server.GetVersion()
799 version= ReturnValue.get_value(result)
800 # cache version for 20 minutes
801 cache.add(cache_key, version, ttl= 60*20)
802 self.logger.info("Updating cache file %s" % cache_file)
803 cache.save_to_file(cache_file)
807 ### resurrect this temporarily so we can support V1 aggregates for a while
808 def server_supports_options_arg(self, server):
810 Returns true if server support the optional call_id arg, false otherwise.
812 server_version = self.get_cached_server_version(server)
814 # xxx need to rewrite this
815 if int(server_version.get('geni_api')) >= 2:
819 def server_supports_call_id_arg(self, server):
820 server_version = self.get_cached_server_version(server)
822 if 'sfa' in server_version and 'code_tag' in server_version:
823 code_tag = server_version['code_tag']
824 code_tag_parts = code_tag.split("-")
825 version_parts = code_tag_parts[0].split(".")
826 major, minor = version_parts[0], version_parts[1]
827 rev = code_tag_parts[1]
828 if int(major) == 1 and minor == 0 and build >= 22:
832 ### ois = options if supported
833 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
834 def ois (self, server, option_dict):
835 if self.server_supports_options_arg (server):
837 elif self.server_supports_call_id_arg (server):
838 return [ unique_call_id () ]
842 ### cis = call_id if supported - like ois
843 def cis (self, server):
844 if self.server_supports_call_id_arg (server):
845 return [ unique_call_id ]
849 ######################################## miscell utilities
850 def get_rspec_file(self, rspec):
851 if (os.path.isabs(rspec)):
854 file = os.path.join(self.options.sfi_dir, rspec)
855 if (os.path.isfile(file)):
858 self.logger.critical("No such rspec file %s"%rspec)
861 def get_record_file(self, record):
862 if (os.path.isabs(record)):
865 file = os.path.join(self.options.sfi_dir, record)
866 if (os.path.isfile(file)):
869 self.logger.critical("No such registry record file %s"%record)
873 #==========================================================================
874 # Following functions implement the commands
876 # Registry-related commands
877 #==========================================================================
879 @declare_command("","")
880 def config (self, options, args):
881 "Display contents of current config"
882 print "# From configuration file %s"%self.config_file
883 flags=[ ('sfi', [ ('registry','reg_url'),
884 ('auth','authority'),
890 flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
892 for (section, tuples) in flags:
895 for (external_name, internal_name) in tuples:
896 print "%-20s = %s"%(external_name,getattr(self,internal_name))
899 varname="%s_%s"%(section.upper(),name.upper())
900 value=getattr(self.config_instance,varname)
901 print "%-20s = %s"%(name,value)
903 @declare_command("","")
904 def version(self, options, args):
906 display an SFA server version (GetVersion)
907 or version information about sfi itself
909 if options.version_local:
910 version=version_core()
912 if options.registry_interface:
913 server=self.registry()
915 server = self.sliceapi()
916 result = server.GetVersion()
917 version = ReturnValue.get_value(result)
919 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
921 pprinter = PrettyPrinter(indent=4)
922 pprinter.pprint(version)
924 @declare_command("authority","")
925 def list(self, options, args):
927 list entries in named authority registry (List)
934 if options.recursive:
935 opts['recursive'] = options.recursive
937 if options.show_credential:
938 show_credentials(self.my_credential_string)
940 list = self.registry().List(hrn, self.my_credential_string, options)
942 raise Exception, "Not enough parameters for the 'list' command"
944 # filter on person, slice, site, node, etc.
945 # This really should be in the self.filter_records funct def comment...
946 list = filter_records(options.type, list)
947 terminal_render (list, options)
949 save_records_to_file(options.file, list, options.fileformat)
952 @declare_command("name","")
953 def show(self, options, args):
955 show details about named registry record (Resolve)
961 # explicitly require Resolve to run in details mode
963 if not options.no_details: resolve_options['details']=True
964 record_dicts = self.registry().Resolve(hrn, self.my_credential_string, resolve_options)
965 record_dicts = filter_records(options.type, record_dicts)
967 self.logger.error("No record of type %s"% options.type)
969 # user has required to focus on some keys
971 def project (record):
973 for key in options.keys:
974 try: projected[key]=record[key]
977 record_dicts = [ project (record) for record in record_dicts ]
978 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
979 for record in records:
980 if (options.format == "text"): record.dump(sort=True)
981 else: print record.save_as_xml()
983 save_records_to_file(options.file, record_dicts, options.fileformat)
986 # this historically was named 'add', it is now 'register' with an alias for legacy
987 @declare_command("[xml-filename]","",['add'])
988 def register(self, options, args):
989 """create new record in registry (Register)
990 from command line options (recommended)
991 old-school method involving an xml file still supported"""
993 auth_cred = self.my_authority_credential_string()
994 if options.show_credential:
995 show_credentials(auth_cred)
1002 record_filepath = args[0]
1003 rec_file = self.get_record_file(record_filepath)
1004 record_dict.update(load_record_from_file(rec_file).todict())
1006 print "Cannot load record file %s"%record_filepath
1009 record_dict.update(load_record_from_opts(options).todict())
1010 # we should have a type by now
1011 if 'type' not in record_dict :
1014 # this is still planetlab dependent.. as plc will whine without that
1015 # also, it's only for adding
1016 if record_dict['type'] == 'user':
1017 if not 'first_name' in record_dict:
1018 record_dict['first_name'] = record_dict['hrn']
1019 if 'last_name' not in record_dict:
1020 record_dict['last_name'] = record_dict['hrn']
1021 return self.registry().Register(record_dict, auth_cred)
1023 @declare_command("[xml-filename]","")
1024 def update(self, options, args):
1025 """update record into registry (Update)
1026 from command line options (recommended)
1027 old-school method involving an xml file still supported"""
1030 record_filepath = args[0]
1031 rec_file = self.get_record_file(record_filepath)
1032 record_dict.update(load_record_from_file(rec_file).todict())
1034 record_dict.update(load_record_from_opts(options).todict())
1035 # at the very least we need 'type' here
1036 if 'type' not in record_dict:
1040 # don't translate into an object, as this would possibly distort
1041 # user-provided data; e.g. add an 'email' field to Users
1042 if record_dict['type'] in ['user']:
1043 if record_dict['hrn'] == self.user:
1044 cred = self.my_credential_string
1046 cred = self.my_authority_credential_string()
1047 elif record_dict['type'] in ['slice']:
1049 cred = self.slice_credential_string(record_dict['hrn'])
1050 except ServerException, e:
1051 # XXX smbaker -- once we have better error return codes, update this
1052 # to do something better than a string compare
1053 if "Permission error" in e.args[0]:
1054 cred = self.my_authority_credential_string()
1057 elif record_dict['type'] in ['authority']:
1058 cred = self.my_authority_credential_string()
1059 elif record_dict['type'] in ['node']:
1060 cred = self.my_authority_credential_string()
1062 raise "unknown record type" + record_dict['type']
1063 if options.show_credential:
1064 show_credentials(cred)
1065 return self.registry().Update(record_dict, cred)
1067 @declare_command("hrn","")
1068 def remove(self, options, args):
1069 "remove registry record by name (Remove)"
1070 auth_cred = self.my_authority_credential_string()
1078 if options.show_credential:
1079 show_credentials(auth_cred)
1080 return self.registry().Remove(hrn, auth_cred, type)
1082 # ==================================================================
1083 # Slice-related commands
1084 # ==================================================================
1086 # show rspec for named slice
1087 @declare_command("","")
1088 def resources(self, options, args):
1090 discover available resources (ListResources)
1092 server = self.sliceapi()
1095 creds = [self.my_credential]
1096 if options.delegate:
1097 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1098 if options.show_credential:
1099 show_credentials(creds)
1101 # no need to check if server accepts the options argument since the options has
1102 # been a required argument since v1 API
1104 # always send call_id to v2 servers
1105 api_options ['call_id'] = unique_call_id()
1106 # ask for cached value if available
1107 api_options ['cached'] = True
1109 api_options['info'] = options.info
1110 if options.list_leases:
1111 api_options['list_leases'] = options.list_leases
1113 if options.current == True:
1114 api_options['cached'] = False
1116 api_options['cached'] = True
1117 if options.rspec_version:
1118 version_manager = VersionManager()
1119 server_version = self.get_cached_server_version(server)
1120 if 'sfa' in server_version:
1121 # just request the version the client wants
1122 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1124 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1126 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1127 result = server.ListResources (creds, api_options)
1128 value = ReturnValue.get_value(result)
1129 if self.options.raw:
1130 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1131 if options.file is not None:
1132 save_rspec_to_file(value, options.file)
1133 if (self.options.raw is None) and (options.file is None):
1134 display_rspec(value, options.format)
1138 @declare_command("slice_hrn","")
1139 def describe(self, options, args):
1141 shows currently allocated/provisioned resources
1142 of the named slice or set of slivers (Describe)
1144 server = self.sliceapi()
1147 creds = [self.slice_credential(args[0])]
1148 if options.delegate:
1149 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1150 if options.show_credential:
1151 show_credentials(creds)
1153 api_options = {'call_id': unique_call_id(),
1155 'info': options.info,
1156 'list_leases': options.list_leases,
1157 'geni_rspec_version': {'type': 'geni', 'version': '3'},
1160 api_options['info'] = options.info
1162 if options.rspec_version:
1163 version_manager = VersionManager()
1164 server_version = self.get_cached_server_version(server)
1165 if 'sfa' in server_version:
1166 # just request the version the client wants
1167 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1169 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1170 urn = Xrn(args[0], type='slice').get_urn()
1171 remove_none_fields(api_options)
1172 result = server.Describe([urn], creds, api_options)
1173 value = ReturnValue.get_value(result)
1174 if self.options.raw:
1175 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1176 if options.file is not None:
1177 save_rspec_to_file(value['geni_rspec'], options.file)
1178 if (self.options.raw is None) and (options.file is None):
1179 display_rspec(value['geni_rspec'], options.format)
1183 @declare_command("slice_hrn [<sliver_urn>...]","")
1184 def delete(self, options, args):
1186 de-allocate and de-provision all or named slivers of the named slice (Delete)
1188 server = self.sliceapi()
1192 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1195 # we have sliver urns
1196 sliver_urns = args[1:]
1198 # we provision all the slivers of the slice
1199 sliver_urns = [slice_urn]
1202 slice_cred = self.slice_credential(slice_hrn)
1203 creds = [slice_cred]
1205 # options and call_id when supported
1207 api_options ['call_id'] = unique_call_id()
1208 if options.show_credential:
1209 show_credentials(creds)
1210 result = server.Delete(sliver_urns, creds, *self.ois(server, api_options ) )
1211 value = ReturnValue.get_value(result)
1212 if self.options.raw:
1213 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1218 @declare_command("slice_hrn rspec","")
1219 def allocate(self, options, args):
1221 allocate resources to the named slice (Allocate)
1223 server = self.sliceapi()
1224 server_version = self.get_cached_server_version(server)
1226 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1229 creds = [self.slice_credential(slice_hrn)]
1231 delegated_cred = None
1232 if server_version.get('interface') == 'slicemgr':
1233 # delegate our cred to the slice manager
1234 # do not delegate cred to slicemgr...not working at the moment
1236 #if server_version.get('hrn'):
1237 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1238 #elif server_version.get('urn'):
1239 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1241 if options.show_credential:
1242 show_credentials(creds)
1245 rspec_file = self.get_rspec_file(args[1])
1246 rspec = open(rspec_file).read()
1248 api_options ['call_id'] = unique_call_id()
1252 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1253 remove_none_fields(slice_records[0])
1254 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1255 slice_record = slice_records[0]
1256 user_hrns = slice_record['reg-researchers']
1257 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1258 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1259 sfa_users = sfa_users_arg(user_records, slice_record)
1260 geni_users = pg_users_arg(user_records)
1262 api_options['sfa_users'] = sfa_users
1263 api_options['geni_users'] = geni_users
1265 result = server.Allocate(slice_urn, creds, rspec, api_options)
1266 value = ReturnValue.get_value(result)
1267 if self.options.raw:
1268 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1269 if options.file is not None:
1270 save_rspec_to_file (value['geni_rspec'], options.file)
1271 if (self.options.raw is None) and (options.file is None):
1276 @declare_command("slice_hrn [<sliver_urn>...]","")
1277 def provision(self, options, args):
1279 provision all or named already allocated slivers of the named slice (Provision)
1281 server = self.sliceapi()
1282 server_version = self.get_cached_server_version(server)
1284 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1286 # we have sliver urns
1287 sliver_urns = args[1:]
1289 # we provision all the slivers of the slice
1290 sliver_urns = [slice_urn]
1293 creds = [self.slice_credential(slice_hrn)]
1294 delegated_cred = None
1295 if server_version.get('interface') == 'slicemgr':
1296 # delegate our cred to the slice manager
1297 # do not delegate cred to slicemgr...not working at the moment
1299 #if server_version.get('hrn'):
1300 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1301 #elif server_version.get('urn'):
1302 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1304 if options.show_credential:
1305 show_credentials(creds)
1308 api_options ['call_id'] = unique_call_id()
1310 # set the requtested rspec version
1311 version_manager = VersionManager()
1312 rspec_version = version_manager._get_version('geni', '3').to_dict()
1313 api_options['geni_rspec_version'] = rspec_version
1316 # need to pass along user keys to the aggregate.
1318 # { urn: urn:publicid:IDN+emulab.net+user+alice
1319 # keys: [<ssh key A>, <ssh key B>]
1322 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1323 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1324 slice_record = slice_records[0]
1325 user_hrns = slice_record['reg-researchers']
1326 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1327 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1328 users = pg_users_arg(user_records)
1330 api_options['geni_users'] = users
1331 result = server.Provision(sliver_urns, creds, api_options)
1332 value = ReturnValue.get_value(result)
1333 if self.options.raw:
1334 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1335 if options.file is not None:
1336 save_rspec_to_file (value['geni_rspec'], options.file)
1337 if (self.options.raw is None) and (options.file is None):
1341 @declare_command("slice_hrn","")
1342 def status(self, options, args):
1344 retrieve the status of the slivers belonging to the named slice (Status)
1346 server = self.sliceapi()
1350 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1353 slice_cred = self.slice_credential(slice_hrn)
1354 creds = [slice_cred]
1356 # options and call_id when supported
1358 api_options['call_id']=unique_call_id()
1359 if options.show_credential:
1360 show_credentials(creds)
1361 result = server.Status([slice_urn], creds, *self.ois(server,api_options))
1362 value = ReturnValue.get_value(result)
1363 if self.options.raw:
1364 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1367 # Thierry: seemed to be missing
1370 @declare_command("slice_hrn [<sliver_urn>...] action","")
1371 def action(self, options, args):
1373 Perform the named operational action on all or named slivers of the named slice
1375 server = self.sliceapi()
1379 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1381 # we have sliver urns
1382 sliver_urns = args[1:-1]
1384 # we provision all the slivers of the slice
1385 sliver_urns = [slice_urn]
1388 slice_cred = self.slice_credential(args[0])
1389 creds = [slice_cred]
1390 if options.delegate:
1391 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1392 creds.append(delegated_cred)
1394 result = server.PerformOperationalAction(sliver_urns, creds, action , api_options)
1395 value = ReturnValue.get_value(result)
1396 if self.options.raw:
1397 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1402 @declare_command("slice_hrn [<sliver_urn>...] time",
1403 "\n".join(["sfi renew onelab.ple.heartbeat 2015-04-31",
1404 "sfi renew onelab.ple.heartbeat 2015-04-31T14:00:00Z",
1405 "sfi renew onelab.ple.heartbeat +5d",
1406 "sfi renew onelab.ple.heartbeat +3w",
1407 "sfi renew onelab.ple.heartbeat +2m",]))
1408 def renew(self, options, args):
1412 server = self.sliceapi()
1417 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1420 # we have sliver urns
1421 sliver_urns = args[1:-1]
1423 # we provision all the slivers of the slice
1424 sliver_urns = [slice_urn]
1425 input_time = args[-1]
1427 # time: don't try to be smart on the time format, server-side will
1429 slice_cred = self.slice_credential(args[0])
1430 creds = [slice_cred]
1431 # options and call_id when supported
1433 api_options['call_id']=unique_call_id()
1435 api_options['geni_extend_alap']=True
1436 if options.show_credential:
1437 show_credentials(creds)
1438 result = server.Renew(sliver_urns, creds, input_time, *self.ois(server,api_options))
1439 value = ReturnValue.get_value(result)
1440 if self.options.raw:
1441 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1447 @declare_command("slice_hrn","")
1448 def shutdown(self, options, args):
1450 shutdown named slice (Shutdown)
1452 server = self.sliceapi()
1455 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1457 slice_cred = self.slice_credential(slice_hrn)
1458 creds = [slice_cred]
1459 result = server.Shutdown(slice_urn, creds)
1460 value = ReturnValue.get_value(result)
1461 if self.options.raw:
1462 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1468 @declare_command("[name]","")
1469 def gid(self, options, args):
1471 Create a GID (CreateGid)
1476 target_hrn = args[0]
1477 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1478 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1480 filename = options.file
1482 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1483 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1484 GID(string=gid).save_to_file(filename)
1486 ####################
1487 @declare_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1489 will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1490 the set of credentials in the scope for this call would be
1491 (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1493 (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1495 (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1496 (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1497 because of the two -s options
1500 def delegate (self, options, args):
1502 (locally) create delegate credential for use by given hrn
1503 make sure to check for 'sfi myslice' instead if you plan
1510 # support for several delegations in the same call
1511 # so first we gather the things to do
1513 for slice_hrn in options.delegate_slices:
1514 message="%s.slice"%slice_hrn
1515 original = self.slice_credential_string(slice_hrn)
1516 tuples.append ( (message, original,) )
1517 if options.delegate_pi:
1518 my_authority=self.authority
1519 message="%s.pi"%my_authority
1520 original = self.my_authority_credential_string()
1521 tuples.append ( (message, original,) )
1522 for auth_hrn in options.delegate_auths:
1523 message="%s.auth"%auth_hrn
1524 original=self.authority_credential_string(auth_hrn)
1525 tuples.append ( (message, original, ) )
1526 # if nothing was specified at all at this point, let's assume -u
1527 if not tuples: options.delegate_user=True
1529 if options.delegate_user:
1530 message="%s.user"%self.user
1531 original = self.my_credential_string
1532 tuples.append ( (message, original, ) )
1534 # default type for beneficial is user unless -A
1535 if options.delegate_to_authority: to_type='authority'
1536 else: to_type='user'
1538 # let's now handle all this
1539 # it's all in the filenaming scheme
1540 for (message,original) in tuples:
1541 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1542 delegated_credential = Credential (string=delegated_string)
1543 filename = os.path.join ( self.options.sfi_dir,
1544 "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1545 delegated_credential.save_to_file(filename, save_parents=True)
1546 self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1548 ####################
1549 @declare_command("","""$ less +/myslice sfi_config
1551 backend = http://manifold.pl.sophia.inria.fr:7080
1552 # the HRN that myslice uses, so that we are delegating to
1553 delegate = ple.upmc.slicebrowser
1554 # platform - this is a myslice concept
1556 # username - as of this writing (May 2013) a simple login name
1560 will first collect the slices that you are part of, then make sure
1561 all your credentials are up-to-date (read: refresh expired ones)
1562 then compute delegated credentials for user 'ple.upmc.slicebrowser'
1563 and upload them all on myslice backend, using 'platform' and 'user'.
1564 A password will be prompted for the upload part.
1566 $ sfi -v myslice -- or sfi -vv myslice
1567 same but with more and more verbosity
1569 $ sfi m -b http://mymanifold.foo.com:7080/
1570 is synonym to sfi myslice as no other command starts with an 'm'
1571 and uses a custom backend for this one call
1574 def myslice (self, options, args):
1576 """ This helper is for refreshing your credentials at myslice; it will
1577 * compute all the slices that you currently have credentials on
1578 * refresh all your credentials (you as a user and pi, your slices)
1579 * upload them to the manifold backend server
1580 for last phase, sfi_config is read to look for the [myslice] section,
1581 and namely the 'backend', 'delegate' and 'user' settings"""
1587 # enable info by default
1588 self.logger.setLevelFromOptVerbose(self.options.verbose+1)
1589 ### the rough sketch goes like this
1590 # (0) produce a p12 file
1591 self.client_bootstrap.my_pkcs12()
1593 # (a) rain check for sufficient config in sfi_config
1595 myslice_keys=[ 'backend', 'delegate', 'platform', 'username']
1596 for key in myslice_keys:
1598 # oct 2013 - I'm finding myself juggling with config files
1599 # so a couple of command-line options can now override config
1600 if hasattr(options,key) and getattr(options,key) is not None:
1601 value=getattr(options,key)
1603 full_key="MYSLICE_" + key.upper()
1604 value=getattr(self.config_instance,full_key,None)
1605 if value: myslice_dict[key]=value
1606 else: print "Unsufficient config, missing key %s in [myslice] section of sfi_config"%key
1607 if len(myslice_dict) != len(myslice_keys):
1610 # (b) figure whether we are PI for the authority where we belong
1611 self.logger.info("Resolving our own id %s"%self.user)
1612 my_records=self.registry().Resolve(self.user,self.my_credential_string)
1613 if len(my_records)!=1: print "Cannot Resolve %s -- exiting"%self.user; sys.exit(1)
1614 my_record=my_records[0]
1615 my_auths_all = my_record['reg-pi-authorities']
1616 self.logger.info("Found %d authorities that we are PI for"%len(my_auths_all))
1617 self.logger.debug("They are %s"%(my_auths_all))
1619 my_auths = my_auths_all
1620 if options.delegate_auths:
1621 my_auths = list(set(my_auths_all).intersection(set(options.delegate_auths)))
1622 self.logger.debug("Restricted to user-provided auths"%(my_auths))
1624 # (c) get the set of slices that we are in
1625 my_slices_all=my_record['reg-slices']
1626 self.logger.info("Found %d slices that we are member of"%len(my_slices_all))
1627 self.logger.debug("They are: %s"%(my_slices_all))
1629 my_slices = my_slices_all
1630 # if user provided slices, deal only with these - if they are found
1631 if options.delegate_slices:
1632 my_slices = list(set(my_slices_all).intersection(set(options.delegate_slices)))
1633 self.logger.debug("Restricted to user-provided slices: %s"%(my_slices))
1635 # (d) make sure we have *valid* credentials for all these
1637 hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1638 for auth_hrn in my_auths:
1639 hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1640 for slice_hrn in my_slices:
1641 hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1643 # (e) check for the delegated version of these
1644 # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever
1645 # switch to myslice using an authority instead of a user
1646 delegatee_type='user'
1647 delegatee_hrn=myslice_dict['delegate']
1648 hrn_delegated_credentials = []
1649 for (hrn, htype, credential) in hrn_credentials:
1650 delegated_credential = self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type)
1651 # save these so user can monitor what she's uploaded
1652 filename = os.path.join ( self.options.sfi_dir,
1653 "%s.%s_for_%s.%s.cred"%(hrn,htype,delegatee_hrn,delegatee_type))
1654 with file(filename,'w') as f:
1655 f.write(delegated_credential)
1656 self.logger.debug("(Over)wrote %s"%filename)
1657 hrn_delegated_credentials.append ((hrn, htype, delegated_credential, filename, ))
1659 # (f) and finally upload them to manifold server
1660 # xxx todo add an option so the password can be set on the command line
1661 # (but *NOT* in the config file) so other apps can leverage this
1662 self.logger.info("Uploading on backend at %s"%myslice_dict['backend'])
1663 uploader = ManifoldUploader (logger=self.logger,
1664 url=myslice_dict['backend'],
1665 platform=myslice_dict['platform'],
1666 username=myslice_dict['username'],
1667 password=options.password)
1668 uploader.prompt_all()
1669 (count_all,count_success)=(0,0)
1670 for (hrn,htype,delegated_credential,filename) in hrn_delegated_credentials:
1672 inspect=Credential(string=delegated_credential)
1673 expire_datetime=inspect.get_expiration()
1674 message="%s (%s) [exp:%s]"%(hrn,htype,expire_datetime)
1675 if uploader.upload(delegated_credential,message=message):
1678 self.logger.info("Successfully uploaded %d/%d credentials"%(count_success,count_all))
1680 # at first I thought we would want to save these,
1681 # like 'sfi delegate does' but on second thought
1682 # it is probably not helpful as people would not
1683 # need to run 'sfi delegate' at all anymore
1684 if count_success != count_all: sys.exit(1)
1687 @declare_command("cred","")
1688 def trusted(self, options, args):
1690 return the trusted certs at this interface (get_trusted_certs)
1692 if options.registry_interface:
1693 server=self.registry()
1695 server = self.sliceapi()
1696 cred = self.my_authority_credential_string()
1697 trusted_certs = server.get_trusted_certs(cred)
1698 if not options.registry_interface:
1699 trusted_certs = ReturnValue.get_value(trusted_certs)
1701 for trusted_cert in trusted_certs:
1702 print "\n===========================================================\n"
1703 gid = GID(string=trusted_cert)
1705 cert = Certificate(string=trusted_cert)
1706 self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1707 print "Certificate:\n%s\n\n"%trusted_cert