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, 'pis') and options.pis:
212 record_dict['pi'] = options.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)
267 # dirty hack to make this class usable from the outside
268 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
271 def default_sfi_dir ():
272 if os.path.isfile("./sfi_config"):
275 return os.path.expanduser("~/.sfi/")
277 # dummy to meet Sfi's expectations for its 'options' field
278 # i.e. s/t we can do setattr on
282 def __init__ (self,options=None):
283 if options is None: options=Sfi.DummyOptions()
284 for opt in Sfi.required_options:
285 if not hasattr(options,opt): setattr(options,opt,None)
286 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
287 self.options = options
289 self.authority = None
290 self.logger = sfi_logger
291 self.logger.enable_console()
292 ### various auxiliary material that we keep at hand
294 # need to call this other than just 'config' as we have a command/method with that name
295 self.config_instance=None
296 self.config_file=None
297 self.client_bootstrap=None
299 ### suitable if no reasonable command has been provided
300 def print_commands_help (self, options):
301 verbose=getattr(options,'verbose')
302 format3="%10s %-35s %s"
306 print format3%("command","cmd_args","description")
310 self.create_parser_global().print_help()
311 # preserve order from the code
312 for command in commands_list:
314 (doc, args_string, example, canonical) = commands_dict[command]
316 print "Cannot find info on command %s - skipped"%command
320 if command==canonical:
321 doc=doc.replace("\n","\n"+format3offset*' ')
322 print format3%(command,args_string,doc)
324 self.create_parser_command(command).print_help()
326 print format3%(command,"<<alias for %s>>"%canonical,"")
328 ### now if a known command was found we can be more verbose on that one
329 def print_help (self):
330 print "==================== Generic sfi usage"
331 self.sfi_parser.print_help()
332 (doc,_,example,canonical)=commands_dict[self.command]
333 if canonical != self.command:
334 print "\n==================== NOTE: %s is an alias for genuine %s"%(self.command,canonical)
335 self.command=canonical
336 print "\n==================== Purpose of %s"%self.command
338 print "\n==================== Specific usage for %s"%self.command
339 self.command_parser.print_help()
341 print "\n==================== %s example(s)"%self.command
344 def create_parser_global(self):
345 # Generate command line parser
346 parser = OptionParser(add_help_option=False,
347 usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
348 description="Commands: %s"%(" ".join(commands_list)))
349 parser.add_option("-r", "--registry", dest="registry",
350 help="root registry", metavar="URL", default=None)
351 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
352 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
353 parser.add_option("-R", "--raw", dest="raw", default=None,
354 help="Save raw, unparsed server response to a file")
355 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
356 help="raw file format ([text]|pickled|json)", default="text",
357 choices=("text","pickled","json"))
358 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
359 help="text string to write before and after raw output")
360 parser.add_option("-d", "--dir", dest="sfi_dir",
361 help="config & working directory - default is %default",
362 metavar="PATH", default=Sfi.default_sfi_dir())
363 parser.add_option("-u", "--user", dest="user",
364 help="user name", metavar="HRN", default=None)
365 parser.add_option("-a", "--auth", dest="auth",
366 help="authority name", metavar="HRN", default=None)
367 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
368 help="verbose mode - cumulative")
369 parser.add_option("-D", "--debug",
370 action="store_true", dest="debug", default=False,
371 help="Debug (xml-rpc) protocol messages")
372 # would it make sense to use ~/.ssh/id_rsa as a default here ?
373 parser.add_option("-k", "--private-key",
374 action="store", dest="user_private_key", default=None,
375 help="point to the private key file to use if not yet installed in sfi_dir")
376 parser.add_option("-t", "--timeout", dest="timeout", default=None,
377 help="Amout of time to wait before timing out the request")
378 parser.add_option("-h", "--help",
379 action="store_true", dest="help", default=False,
380 help="one page summary on commands & exit")
381 parser.disable_interspersed_args()
386 def create_parser_command(self, command):
387 if command not in commands_dict:
388 msg="Invalid command\n"
390 msg += ','.join(commands_list)
391 self.logger.critical(msg)
394 # retrieve args_string
395 (_, args_string, __,canonical) = commands_dict[command]
397 parser = OptionParser(add_help_option=False,
398 usage="sfi [sfi_options] %s [cmd_options] %s"
399 % (command, args_string))
400 parser.add_option ("-h","--help",dest='help',action='store_true',default=False,
401 help="Summary of one command usage")
403 if canonical in ("config"):
404 parser.add_option('-m', '--myslice', dest='myslice', action='store_true', default=False,
405 help='how myslice config variables as well')
407 if canonical in ("version"):
408 parser.add_option("-l","--local",
409 action="store_true", dest="version_local", default=False,
410 help="display version of the local client")
412 if canonical in ("version", "trusted"):
413 parser.add_option("-R","--registry_interface",
414 action="store_true", dest="registry_interface", default=False,
415 help="target the registry interface instead of slice interface")
417 if canonical in ("register", "update"):
418 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
419 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
420 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
421 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
423 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
424 default='', type="str", action='callback', callback=optparse_listvalue_callback)
425 parser.add_option('-r', '--researchers', dest='reg_researchers', metavar='<researchers>',
426 help='Set/replace slice researchers - use -r none to reset', default=None, type="str", action='callback',
427 callback=optparse_listvalue_callback)
428 parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
429 default='', type="str", action='callback', callback=optparse_listvalue_callback)
430 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
431 action="callback", callback=optparse_dictvalue_callback, nargs=1,
432 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
434 # user specifies remote aggregate/sm/component
435 if canonical in ("resources", "describe", "allocate", "provision", "delete", "allocate", "provision",
436 "action", "shutdown", "renew", "status"):
437 parser.add_option("-d", "--delegate", dest="delegate", default=None,
439 help="Include a credential delegated to the user's root"+\
440 "authority in set of credentials for this call")
442 # show_credential option
443 if canonical in ("list","resources", "describe", "provision", "allocate", "register","update","remove","delete","status","renew"):
444 parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
445 help="show credential(s) used in human-readable form")
446 # registy filter option
447 if canonical in ("list", "show", "remove"):
448 parser.add_option("-t", "--type", dest="type", type="choice",
449 help="type filter ([all]|user|slice|authority|node|aggregate)",
450 choices=("all", "user", "slice", "authority", "node", "aggregate"),
452 if canonical in ("show"):
453 parser.add_option("-k","--key",dest="keys",action="append",default=[],
454 help="specify specific keys to be displayed from record")
455 parser.add_option("-n","--no-details",dest="no_details",action="store_true",default=False,
456 help="call Resolve without the 'details' option")
457 if canonical in ("resources", "describe"):
459 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="GENI 3",
460 help="schema type and version of resulting RSpec")
461 # disable/enable cached rspecs
462 parser.add_option("-c", "--current", dest="current", default=False,
464 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
466 parser.add_option("-f", "--format", dest="format", type="choice",
467 help="display format ([xml]|dns|ip)", default="xml",
468 choices=("xml", "dns", "ip"))
469 #panos: a new option to define the type of information about resources a user is interested in
470 parser.add_option("-i", "--info", dest="info",
471 help="optional component information", default=None)
472 # a new option to retreive or not reservation-oriented RSpecs (leases)
473 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
474 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
475 choices=("all", "resources", "leases"), default="resources")
478 if canonical in ("resources", "describe", "allocate", "provision", "show", "list", "gid"):
479 parser.add_option("-o", "--output", dest="file",
480 help="output XML to file", metavar="FILE", default=None)
482 if canonical in ("show", "list"):
483 parser.add_option("-f", "--format", dest="format", type="choice",
484 help="display format ([text]|xml)", default="text",
485 choices=("text", "xml"))
487 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
488 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
489 choices=("xml", "xmllist", "hrnlist"))
490 if canonical == 'list':
491 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
492 help="list all child records", default=False)
493 parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
494 help="gives details, like user keys", default=False)
495 if canonical in ("delegate"):
496 parser.add_option("-u", "--user",
497 action="store_true", dest="delegate_user", default=False,
498 help="delegate your own credentials; default if no other option is provided")
499 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
500 metavar="slice_hrn", help="delegate cred. for slice HRN")
501 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
502 metavar='auth_hrn', help="delegate cred for auth HRN")
503 # this primarily is a shorthand for -a my_hrn
504 parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
505 help="delegate your PI credentials, so s.t. like -a your_hrn^")
506 parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
507 help="""by default the mandatory argument is expected to be a user,
508 use this if you mean an authority instead""")
510 if canonical in ("myslice"):
511 parser.add_option("-p","--password",dest='password',action='store',default=None,
512 help="specify mainfold password on the command line")
513 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
514 metavar="slice_hrn", help="delegate cred. for slice HRN")
515 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
516 metavar='auth_hrn', help="delegate PI cred for auth HRN")
517 parser.add_option('-d', '--delegate', dest='delegate', help="Override 'delegate' from the config file")
518 parser.add_option('-b', '--backend', dest='backend', help="Override 'backend' from the config file")
524 # Main: parse arguments and dispatch to command
526 def dispatch(self, command, command_options, command_args):
527 method=getattr(self, command, None)
529 print "sfi: unknown command %s"%command
530 raise SystemExit,"Unknown command %s"%command
531 return method(command_options, command_args)
534 self.sfi_parser = self.create_parser_global()
535 (options, args) = self.sfi_parser.parse_args()
537 self.print_commands_help(options)
539 self.options = options
541 self.logger.setLevelFromOptVerbose(self.options.verbose)
544 self.logger.critical("No command given. Use -h for help.")
545 self.print_commands_help(options)
548 # complete / find unique match with command set
549 command_candidates = Candidates (commands_list)
551 command = command_candidates.only_match(input)
553 self.print_commands_help(options)
555 # second pass options parsing
557 self.command_parser = self.create_parser_command(command)
558 (command_options, command_args) = self.command_parser.parse_args(args[1:])
559 if command_options.help:
562 self.command_options = command_options
566 self.logger.debug("Command=%s" % self.command)
569 self.dispatch(command, command_options, command_args)
573 self.logger.log_exc ("sfi command %s failed"%command)
579 def read_config(self):
580 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
581 shell_config_file = os.path.join(self.options.sfi_dir,"sfi_config.sh")
583 if Config.is_ini(config_file):
584 config = Config (config_file)
586 # try upgrading from shell config format
587 fp, fn = mkstemp(suffix='sfi_config', text=True)
589 # we need to preload the sections we want parsed
590 # from the shell config
591 config.add_section('sfi')
592 # sface users should be able to use this same file to configure their stuff
593 config.add_section('sface')
594 # manifold users should be able to specify the details
595 # of their backend server here for 'sfi myslice'
596 config.add_section('myslice')
597 config.load(config_file)
599 shutil.move(config_file, shell_config_file)
601 config.save(config_file)
604 self.logger.critical("Failed to read configuration file %s"%config_file)
605 self.logger.info("Make sure to remove the export clauses and to add quotes")
606 if self.options.verbose==0:
607 self.logger.info("Re-run with -v for more details")
609 self.logger.log_exc("Could not read config file %s"%config_file)
612 self.config_instance=config
615 if (self.options.sm is not None):
616 self.sm_url = self.options.sm
617 elif hasattr(config, "SFI_SM"):
618 self.sm_url = config.SFI_SM
620 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
624 if (self.options.registry is not None):
625 self.reg_url = self.options.registry
626 elif hasattr(config, "SFI_REGISTRY"):
627 self.reg_url = config.SFI_REGISTRY
629 self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
633 if (self.options.user is not None):
634 self.user = self.options.user
635 elif hasattr(config, "SFI_USER"):
636 self.user = config.SFI_USER
638 self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
642 if (self.options.auth is not None):
643 self.authority = self.options.auth
644 elif hasattr(config, "SFI_AUTH"):
645 self.authority = config.SFI_AUTH
647 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
650 self.config_file=config_file
655 # Get various credential and spec files
657 # Establishes limiting conventions
658 # - conflates MAs and SAs
659 # - assumes last token in slice name is unique
661 # Bootstraps credentials
662 # - bootstrap user credential from self-signed certificate
663 # - bootstrap authority credential from user credential
664 # - bootstrap slice credential from user credential
667 # init self-signed cert, user credentials and gid
668 def bootstrap (self):
669 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
671 # if -k is provided, use this to initialize private key
672 if self.options.user_private_key:
673 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
675 # trigger legacy compat code if needed
676 # the name has changed from just <leaf>.pkey to <hrn>.pkey
677 if not os.path.isfile(client_bootstrap.private_key_filename()):
678 self.logger.info ("private key not found, trying legacy name")
680 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
681 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
682 client_bootstrap.init_private_key_if_missing (legacy_private_key)
683 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
685 self.logger.log_exc("Can't find private key ")
689 client_bootstrap.bootstrap_my_gid()
690 # extract what's needed
691 self.private_key = client_bootstrap.private_key()
692 self.my_credential_string = client_bootstrap.my_credential_string ()
693 self.my_credential = {'geni_type': 'geni_sfa',
695 'geni_value': self.my_credential_string}
696 self.my_gid = client_bootstrap.my_gid ()
697 self.client_bootstrap = client_bootstrap
700 def my_authority_credential_string(self):
701 if not self.authority:
702 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
704 return self.client_bootstrap.authority_credential_string (self.authority)
706 def authority_credential_string(self, auth_hrn):
707 return self.client_bootstrap.authority_credential_string (auth_hrn)
709 def slice_credential_string(self, name):
710 return self.client_bootstrap.slice_credential_string (name)
712 def slice_credential(self, name):
713 return {'geni_type': 'geni_sfa',
715 'geni_value': self.slice_credential_string(name)}
717 # xxx should be supported by sfaclientbootstrap as well
718 def delegate_cred(self, object_cred, hrn, type='authority'):
719 # the gid and hrn of the object we are delegating
720 if isinstance(object_cred, str):
721 object_cred = Credential(string=object_cred)
722 object_gid = object_cred.get_gid_object()
723 object_hrn = object_gid.get_hrn()
725 if not object_cred.get_privileges().get_all_delegate():
726 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
729 # the delegating user's gid
730 caller_gidfile = self.my_gid()
732 # the gid of the user who will be delegated to
733 delegee_gid = self.client_bootstrap.gid(hrn,type)
734 delegee_hrn = delegee_gid.get_hrn()
735 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
736 return dcred.save_to_string(save_parents=True)
739 # Management of the servers
744 if not hasattr (self, 'registry_proxy'):
745 self.logger.info("Contacting Registry at: %s"%self.reg_url)
746 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
747 timeout=self.options.timeout, verbose=self.options.debug)
748 return self.registry_proxy
752 if not hasattr (self, 'sliceapi_proxy'):
753 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
754 if hasattr(self.command_options,'component') and self.command_options.component:
755 # resolve the hrn at the registry
756 node_hrn = self.command_options.component
757 records = self.registry().Resolve(node_hrn, self.my_credential_string)
758 records = filter_records('node', records)
760 self.logger.warning("No such component:%r"% opts.component)
762 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
763 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
765 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
766 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
767 self.sm_url = 'http://' + self.sm_url
768 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
769 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
770 timeout=self.options.timeout, verbose=self.options.debug)
771 return self.sliceapi_proxy
773 def get_cached_server_version(self, server):
774 # check local cache first
777 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
778 cache_key = server.url + "-version"
780 cache = Cache(cache_file)
783 self.logger.info("Local cache not found at: %s" % cache_file)
786 version = cache.get(cache_key)
789 result = server.GetVersion()
790 version= ReturnValue.get_value(result)
791 # cache version for 20 minutes
792 cache.add(cache_key, version, ttl= 60*20)
793 self.logger.info("Updating cache file %s" % cache_file)
794 cache.save_to_file(cache_file)
798 ### resurrect this temporarily so we can support V1 aggregates for a while
799 def server_supports_options_arg(self, server):
801 Returns true if server support the optional call_id arg, false otherwise.
803 server_version = self.get_cached_server_version(server)
805 # xxx need to rewrite this
806 if int(server_version.get('geni_api')) >= 2:
810 def server_supports_call_id_arg(self, server):
811 server_version = self.get_cached_server_version(server)
813 if 'sfa' in server_version and 'code_tag' in server_version:
814 code_tag = server_version['code_tag']
815 code_tag_parts = code_tag.split("-")
816 version_parts = code_tag_parts[0].split(".")
817 major, minor = version_parts[0], version_parts[1]
818 rev = code_tag_parts[1]
819 if int(major) == 1 and minor == 0 and build >= 22:
823 ### ois = options if supported
824 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
825 def ois (self, server, option_dict):
826 if self.server_supports_options_arg (server):
828 elif self.server_supports_call_id_arg (server):
829 return [ unique_call_id () ]
833 ### cis = call_id if supported - like ois
834 def cis (self, server):
835 if self.server_supports_call_id_arg (server):
836 return [ unique_call_id ]
840 ######################################## miscell utilities
841 def get_rspec_file(self, rspec):
842 if (os.path.isabs(rspec)):
845 file = os.path.join(self.options.sfi_dir, rspec)
846 if (os.path.isfile(file)):
849 self.logger.critical("No such rspec file %s"%rspec)
852 def get_record_file(self, record):
853 if (os.path.isabs(record)):
856 file = os.path.join(self.options.sfi_dir, record)
857 if (os.path.isfile(file)):
860 self.logger.critical("No such registry record file %s"%record)
864 #==========================================================================
865 # Following functions implement the commands
867 # Registry-related commands
868 #==========================================================================
870 @declare_command("","")
871 def config (self, options, args):
872 "Display contents of current config"
873 print "# From configuration file %s"%self.config_file
874 flags=[ ('sfi', [ ('registry','reg_url'),
875 ('auth','authority'),
881 flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
883 for (section, tuples) in flags:
886 for (external_name, internal_name) in tuples:
887 print "%-20s = %s"%(external_name,getattr(self,internal_name))
890 varname="%s_%s"%(section.upper(),name.upper())
891 value=getattr(self.config_instance,varname)
892 print "%-20s = %s"%(name,value)
894 @declare_command("","")
895 def version(self, options, args):
897 display an SFA server version (GetVersion)
898 or version information about sfi itself
900 if options.version_local:
901 version=version_core()
903 if options.registry_interface:
904 server=self.registry()
906 server = self.sliceapi()
907 result = server.GetVersion()
908 version = ReturnValue.get_value(result)
910 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
912 pprinter = PrettyPrinter(indent=4)
913 pprinter.pprint(version)
915 @declare_command("authority","")
916 def list(self, options, args):
918 list entries in named authority registry (List)
925 if options.recursive:
926 opts['recursive'] = options.recursive
928 if options.show_credential:
929 show_credentials(self.my_credential_string)
931 list = self.registry().List(hrn, self.my_credential_string, options)
933 raise Exception, "Not enough parameters for the 'list' command"
935 # filter on person, slice, site, node, etc.
936 # This really should be in the self.filter_records funct def comment...
937 list = filter_records(options.type, list)
938 terminal_render (list, options)
940 save_records_to_file(options.file, list, options.fileformat)
943 @declare_command("name","")
944 def show(self, options, args):
946 show details about named registry record (Resolve)
952 # explicitly require Resolve to run in details mode
954 if not options.no_details: resolve_options['details']=True
955 record_dicts = self.registry().Resolve(hrn, self.my_credential_string, resolve_options)
956 record_dicts = filter_records(options.type, record_dicts)
958 self.logger.error("No record of type %s"% options.type)
960 # user has required to focus on some keys
962 def project (record):
964 for key in options.keys:
965 try: projected[key]=record[key]
968 record_dicts = [ project (record) for record in record_dicts ]
969 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
970 for record in records:
971 if (options.format == "text"): record.dump(sort=True)
972 else: print record.save_as_xml()
974 save_records_to_file(options.file, record_dicts, options.fileformat)
977 # this historically was named 'add', it is now 'register' with an alias for legacy
978 @declare_command("[xml-filename]","",['add'])
979 def register(self, options, args):
980 """create new record in registry (Register)
981 from command line options (recommended)
982 old-school method involving an xml file still supported"""
984 auth_cred = self.my_authority_credential_string()
985 if options.show_credential:
986 show_credentials(auth_cred)
993 record_filepath = args[0]
994 rec_file = self.get_record_file(record_filepath)
995 record_dict.update(load_record_from_file(rec_file).todict())
997 print "Cannot load record file %s"%record_filepath
1000 record_dict.update(load_record_from_opts(options).todict())
1001 # we should have a type by now
1002 if 'type' not in record_dict :
1005 # this is still planetlab dependent.. as plc will whine without that
1006 # also, it's only for adding
1007 if record_dict['type'] == 'user':
1008 if not 'first_name' in record_dict:
1009 record_dict['first_name'] = record_dict['hrn']
1010 if 'last_name' not in record_dict:
1011 record_dict['last_name'] = record_dict['hrn']
1012 return self.registry().Register(record_dict, auth_cred)
1014 @declare_command("[xml-filename]","")
1015 def update(self, options, args):
1016 """update record into registry (Update)
1017 from command line options (recommended)
1018 old-school method involving an xml file still supported"""
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 record_dict.update(load_record_from_opts(options).todict())
1026 # at the very least we need 'type' here
1027 if 'type' not in record_dict:
1031 # don't translate into an object, as this would possibly distort
1032 # user-provided data; e.g. add an 'email' field to Users
1033 if record_dict['type'] == "user":
1034 if record_dict['hrn'] == self.user:
1035 cred = self.my_credential_string
1037 cred = self.my_authority_credential_string()
1038 elif record_dict['type'] in ["slice"]:
1040 cred = self.slice_credential_string(record_dict['hrn'])
1041 except ServerException, e:
1042 # XXX smbaker -- once we have better error return codes, update this
1043 # to do something better than a string compare
1044 if "Permission error" in e.args[0]:
1045 cred = self.my_authority_credential_string()
1048 elif record_dict['type'] in ["authority"]:
1049 cred = self.my_authority_credential_string()
1050 elif record_dict['type'] == 'node':
1051 cred = self.my_authority_credential_string()
1053 raise "unknown record type" + record_dict['type']
1054 if options.show_credential:
1055 show_credentials(cred)
1056 return self.registry().Update(record_dict, cred)
1058 @declare_command("hrn","")
1059 def remove(self, options, args):
1060 "remove registry record by name (Remove)"
1061 auth_cred = self.my_authority_credential_string()
1069 if options.show_credential:
1070 show_credentials(auth_cred)
1071 return self.registry().Remove(hrn, auth_cred, type)
1073 # ==================================================================
1074 # Slice-related commands
1075 # ==================================================================
1077 # show rspec for named slice
1078 @declare_command("","")
1079 def resources(self, options, args):
1081 discover available resources (ListResources)
1083 server = self.sliceapi()
1086 creds = [self.my_credential]
1087 if options.delegate:
1088 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1089 if options.show_credential:
1090 show_credentials(creds)
1092 # no need to check if server accepts the options argument since the options has
1093 # been a required argument since v1 API
1095 # always send call_id to v2 servers
1096 api_options ['call_id'] = unique_call_id()
1097 # ask for cached value if available
1098 api_options ['cached'] = True
1100 api_options['info'] = options.info
1101 if options.list_leases:
1102 api_options['list_leases'] = options.list_leases
1104 if options.current == True:
1105 api_options['cached'] = False
1107 api_options['cached'] = True
1108 if options.rspec_version:
1109 version_manager = VersionManager()
1110 server_version = self.get_cached_server_version(server)
1111 if 'sfa' in server_version:
1112 # just request the version the client wants
1113 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1115 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1117 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1118 result = server.ListResources (creds, api_options)
1119 value = ReturnValue.get_value(result)
1120 if self.options.raw:
1121 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1122 if options.file is not None:
1123 save_rspec_to_file(value, options.file)
1124 if (self.options.raw is None) and (options.file is None):
1125 display_rspec(value, options.format)
1129 @declare_command("slice_hrn","")
1130 def describe(self, options, args):
1132 shows currently allocated/provisioned resources
1133 of the named slice or set of slivers (Describe)
1135 server = self.sliceapi()
1138 creds = [self.slice_credential(args[0])]
1139 if options.delegate:
1140 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1141 if options.show_credential:
1142 show_credentials(creds)
1144 api_options = {'call_id': unique_call_id(),
1146 #'info': options.info,
1147 'list_leases': options.list_leases,
1148 'geni_rspec_version': {'type': 'geni', 'version': '3'},
1151 api_options['info'] = options.info
1153 if options.rspec_version:
1154 version_manager = VersionManager()
1155 server_version = self.get_cached_server_version(server)
1156 if 'sfa' in server_version:
1157 # just request the version the client wants
1158 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1160 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1161 urn = Xrn(args[0], type='slice').get_urn()
1162 result = server.Describe([urn], creds, api_options)
1163 value = ReturnValue.get_value(result)
1164 if self.options.raw:
1165 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1166 if options.file is not None:
1167 save_rspec_to_file(value['geni_rspec'], options.file)
1168 if (self.options.raw is None) and (options.file is None):
1169 display_rspec(value, options.format)
1173 @declare_command("slice_hrn [<sliver_urn>...]","")
1174 def delete(self, options, args):
1176 de-allocate and de-provision all or named slivers of the named slice (Delete)
1178 server = self.sliceapi()
1182 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1185 # we have sliver urns
1186 sliver_urns = args[1:]
1188 # we provision all the slivers of the slice
1189 sliver_urns = [slice_urn]
1192 slice_cred = self.slice_credential(slice_hrn)
1193 creds = [slice_cred]
1195 # options and call_id when supported
1197 api_options ['call_id'] = unique_call_id()
1198 if options.show_credential:
1199 show_credentials(creds)
1200 result = server.Delete(sliver_urns, creds, *self.ois(server, api_options ) )
1201 value = ReturnValue.get_value(result)
1202 if self.options.raw:
1203 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1208 @declare_command("slice_hrn rspec","")
1209 def allocate(self, options, args):
1211 allocate resources to the named slice (Allocate)
1213 server = self.sliceapi()
1214 server_version = self.get_cached_server_version(server)
1216 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1219 creds = [self.slice_credential(slice_hrn)]
1221 delegated_cred = None
1222 if server_version.get('interface') == 'slicemgr':
1223 # delegate our cred to the slice manager
1224 # do not delegate cred to slicemgr...not working at the moment
1226 #if server_version.get('hrn'):
1227 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1228 #elif server_version.get('urn'):
1229 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1231 if options.show_credential:
1232 show_credentials(creds)
1235 rspec_file = self.get_rspec_file(args[1])
1236 rspec = open(rspec_file).read()
1238 api_options ['call_id'] = unique_call_id()
1242 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1243 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1244 slice_record = slice_records[0]
1245 user_hrns = slice_record['reg-researchers']
1246 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1247 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1248 sfa_users = sfa_users_arg(user_records, slice_record)
1249 geni_users = pg_users_arg(user_records)
1251 api_options['sfa_users'] = sfa_users
1252 api_options['geni_users'] = geni_users
1254 result = server.Allocate(slice_urn, creds, rspec, api_options)
1255 value = ReturnValue.get_value(result)
1256 if self.options.raw:
1257 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1258 if options.file is not None:
1259 save_rspec_to_file (value['geni_rspec'], options.file)
1260 if (self.options.raw is None) and (options.file is None):
1265 @declare_command("slice_hrn [<sliver_urn>...]","")
1266 def provision(self, options, args):
1268 provision all or named already allocated slivers of the named slice (Provision)
1270 server = self.sliceapi()
1271 server_version = self.get_cached_server_version(server)
1273 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1275 # we have sliver urns
1276 sliver_urns = args[1:]
1278 # we provision all the slivers of the slice
1279 sliver_urns = [slice_urn]
1282 creds = [self.slice_credential(slice_hrn)]
1283 delegated_cred = None
1284 if server_version.get('interface') == 'slicemgr':
1285 # delegate our cred to the slice manager
1286 # do not delegate cred to slicemgr...not working at the moment
1288 #if server_version.get('hrn'):
1289 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1290 #elif server_version.get('urn'):
1291 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1293 if options.show_credential:
1294 show_credentials(creds)
1297 api_options ['call_id'] = unique_call_id()
1299 # set the requtested rspec version
1300 version_manager = VersionManager()
1301 rspec_version = version_manager._get_version('geni', '3').to_dict()
1302 api_options['geni_rspec_version'] = rspec_version
1305 # need to pass along user keys to the aggregate.
1307 # { urn: urn:publicid:IDN+emulab.net+user+alice
1308 # keys: [<ssh key A>, <ssh key B>]
1311 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1312 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1313 slice_record = slice_records[0]
1314 user_hrns = slice_record['reg-researchers']
1315 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1316 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1317 users = pg_users_arg(user_records)
1319 api_options['geni_users'] = users
1320 result = server.Provision(sliver_urns, creds, api_options)
1321 value = ReturnValue.get_value(result)
1322 if self.options.raw:
1323 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1324 if options.file is not None:
1325 save_rspec_to_file (value['geni_rspec'], options.file)
1326 if (self.options.raw is None) and (options.file is None):
1330 @declare_command("slice_hrn","")
1331 def status(self, options, args):
1333 retrieve the status of the slivers belonging to the named slice (Status)
1335 server = self.sliceapi()
1339 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1342 slice_cred = self.slice_credential(slice_hrn)
1343 creds = [slice_cred]
1345 # options and call_id when supported
1347 api_options['call_id']=unique_call_id()
1348 if options.show_credential:
1349 show_credentials(creds)
1350 result = server.Status([slice_urn], creds, *self.ois(server,api_options))
1351 value = ReturnValue.get_value(result)
1352 if self.options.raw:
1353 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1356 # Thierry: seemed to be missing
1359 @declare_command("slice_hrn [<sliver_urn>...] action","")
1360 def action(self, options, args):
1362 Perform the named operational action on all or named slivers of the named slice
1364 server = self.sliceapi()
1368 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1370 # we have sliver urns
1371 sliver_urns = args[1:-1]
1373 # we provision all the slivers of the slice
1374 sliver_urns = [slice_urn]
1377 slice_cred = self.slice_credential(args[0])
1378 creds = [slice_cred]
1379 if options.delegate:
1380 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1381 creds.append(delegated_cred)
1383 result = server.PerformOperationalAction(sliver_urns, creds, action , api_options)
1384 value = ReturnValue.get_value(result)
1385 if self.options.raw:
1386 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1391 @declare_command("slice_hrn [<sliver_urn>...] time","")
1392 def renew(self, options, args):
1396 server = self.sliceapi()
1401 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1404 # we have sliver urns
1405 sliver_urns = args[1:-1]
1407 # we provision all the slivers of the slice
1408 sliver_urns = [slice_urn]
1409 input_time = args[-1]
1411 # time: don't try to be smart on the time format, server-side will
1413 slice_cred = self.slice_credential(args[0])
1414 creds = [slice_cred]
1415 # options and call_id when supported
1417 api_options['call_id']=unique_call_id()
1418 if options.show_credential:
1419 show_credentials(creds)
1420 result = server.Renew(sliver_urns, creds, input_time, *self.ois(server,api_options))
1421 value = ReturnValue.get_value(result)
1422 if self.options.raw:
1423 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1429 @declare_command("slice_hrn","")
1430 def shutdown(self, options, args):
1432 shutdown named slice (Shutdown)
1434 server = self.sliceapi()
1437 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1439 slice_cred = self.slice_credential(slice_hrn)
1440 creds = [slice_cred]
1441 result = server.Shutdown(slice_urn, creds)
1442 value = ReturnValue.get_value(result)
1443 if self.options.raw:
1444 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1450 @declare_command("[name]","")
1451 def gid(self, options, args):
1453 Create a GID (CreateGid)
1458 target_hrn = args[0]
1459 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1460 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1462 filename = options.file
1464 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1465 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1466 GID(string=gid).save_to_file(filename)
1468 ####################
1469 @declare_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1471 will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1472 the set of credentials in the scope for this call would be
1473 (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1475 (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1477 (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1478 (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1479 because of the two -s options
1482 def delegate (self, options, args):
1484 (locally) create delegate credential for use by given hrn
1485 make sure to check for 'sfi myslice' instead if you plan
1492 # support for several delegations in the same call
1493 # so first we gather the things to do
1495 for slice_hrn in options.delegate_slices:
1496 message="%s.slice"%slice_hrn
1497 original = self.slice_credential_string(slice_hrn)
1498 tuples.append ( (message, original,) )
1499 if options.delegate_pi:
1500 my_authority=self.authority
1501 message="%s.pi"%my_authority
1502 original = self.my_authority_credential_string()
1503 tuples.append ( (message, original,) )
1504 for auth_hrn in options.delegate_auths:
1505 message="%s.auth"%auth_hrn
1506 original=self.authority_credential_string(auth_hrn)
1507 tuples.append ( (message, original, ) )
1508 # if nothing was specified at all at this point, let's assume -u
1509 if not tuples: options.delegate_user=True
1511 if options.delegate_user:
1512 message="%s.user"%self.user
1513 original = self.my_credential_string
1514 tuples.append ( (message, original, ) )
1516 # default type for beneficial is user unless -A
1517 if options.delegate_to_authority: to_type='authority'
1518 else: to_type='user'
1520 # let's now handle all this
1521 # it's all in the filenaming scheme
1522 for (message,original) in tuples:
1523 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1524 delegated_credential = Credential (string=delegated_string)
1525 filename = os.path.join ( self.options.sfi_dir,
1526 "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1527 delegated_credential.save_to_file(filename, save_parents=True)
1528 self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1530 ####################
1531 @declare_command("","""$ less +/myslice sfi_config
1533 backend = http://manifold.pl.sophia.inria.fr:7080
1534 # the HRN that myslice uses, so that we are delegating to
1535 delegate = ple.upmc.slicebrowser
1536 # platform - this is a myslice concept
1538 # username - as of this writing (May 2013) a simple login name
1542 will first collect the slices that you are part of, then make sure
1543 all your credentials are up-to-date (read: refresh expired ones)
1544 then compute delegated credentials for user 'ple.upmc.slicebrowser'
1545 and upload them all on myslice backend, using 'platform' and 'user'.
1546 A password will be prompted for the upload part.
1548 $ sfi -v myslice -- or sfi -vv myslice
1549 same but with more and more verbosity
1551 $ sfi m -b http://mymanifold.foo.com:7080/
1552 is synonym to sfi myslice as no other command starts with an 'm'
1553 and uses a custom backend for this one call
1556 def myslice (self, options, args):
1558 """ This helper is for refreshing your credentials at myslice; it will
1559 * compute all the slices that you currently have credentials on
1560 * refresh all your credentials (you as a user and pi, your slices)
1561 * upload them to the manifold backend server
1562 for last phase, sfi_config is read to look for the [myslice] section,
1563 and namely the 'backend', 'delegate' and 'user' settings"""
1569 # enable info by default
1570 self.logger.setLevelFromOptVerbose(self.options.verbose+1)
1571 ### the rough sketch goes like this
1572 # (0) produce a p12 file
1573 self.client_bootstrap.my_pkcs12()
1575 # (a) rain check for sufficient config in sfi_config
1577 myslice_keys=[ 'backend', 'delegate', 'platform', 'username']
1578 for key in myslice_keys:
1580 # oct 2013 - I'm finding myself juggling with config files
1581 # so a couple of command-line options can now override config
1582 if hasattr(options,key) and getattr(options,key) is not None:
1583 value=getattr(options,key)
1585 full_key="MYSLICE_" + key.upper()
1586 value=getattr(self.config_instance,full_key,None)
1587 if value: myslice_dict[key]=value
1588 else: print "Unsufficient config, missing key %s in [myslice] section of sfi_config"%key
1589 if len(myslice_dict) != len(myslice_keys):
1592 # (b) figure whether we are PI for the authority where we belong
1593 self.logger.info("Resolving our own id %s"%self.user)
1594 my_records=self.registry().Resolve(self.user,self.my_credential_string)
1595 if len(my_records)!=1: print "Cannot Resolve %s -- exiting"%self.user; sys.exit(1)
1596 my_record=my_records[0]
1597 my_auths_all = my_record['reg-pi-authorities']
1598 self.logger.info("Found %d authorities that we are PI for"%len(my_auths_all))
1599 self.logger.debug("They are %s"%(my_auths_all))
1601 my_auths = my_auths_all
1602 if options.delegate_auths:
1603 my_auths = list(set(my_auths_all).intersection(set(options.delegate_auths)))
1604 self.logger.debug("Restricted to user-provided auths"%(my_auths))
1606 # (c) get the set of slices that we are in
1607 my_slices_all=my_record['reg-slices']
1608 self.logger.info("Found %d slices that we are member of"%len(my_slices_all))
1609 self.logger.debug("They are: %s"%(my_slices_all))
1611 my_slices = my_slices_all
1612 # if user provided slices, deal only with these - if they are found
1613 if options.delegate_slices:
1614 my_slices = list(set(my_slices_all).intersection(set(options.delegate_slices)))
1615 self.logger.debug("Restricted to user-provided slices: %s"%(my_slices))
1617 # (d) make sure we have *valid* credentials for all these
1619 hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1620 for auth_hrn in my_auths:
1621 hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1622 for slice_hrn in my_slices:
1623 hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1625 # (e) check for the delegated version of these
1626 # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever
1627 # switch to myslice using an authority instead of a user
1628 delegatee_type='user'
1629 delegatee_hrn=myslice_dict['delegate']
1630 hrn_delegated_credentials = []
1631 for (hrn, htype, credential) in hrn_credentials:
1632 delegated_credential = self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type)
1633 # save these so user can monitor what she's uploaded
1634 filename = os.path.join ( self.options.sfi_dir,
1635 "%s.%s_for_%s.%s.cred"%(hrn,htype,delegatee_hrn,delegatee_type))
1636 with file(filename,'w') as f:
1637 f.write(delegated_credential)
1638 self.logger.debug("(Over)wrote %s"%filename)
1639 hrn_delegated_credentials.append ((hrn, htype, delegated_credential, filename, ))
1641 # (f) and finally upload them to manifold server
1642 # xxx todo add an option so the password can be set on the command line
1643 # (but *NOT* in the config file) so other apps can leverage this
1644 self.logger.info("Uploading on backend at %s"%myslice_dict['backend'])
1645 uploader = ManifoldUploader (logger=self.logger,
1646 url=myslice_dict['backend'],
1647 platform=myslice_dict['platform'],
1648 username=myslice_dict['username'],
1649 password=options.password)
1650 uploader.prompt_all()
1651 (count_all,count_success)=(0,0)
1652 for (hrn,htype,delegated_credential,filename) in hrn_delegated_credentials:
1654 inspect=Credential(string=delegated_credential)
1655 expire_datetime=inspect.get_expiration()
1656 message="%s (%s) [exp:%s]"%(hrn,htype,expire_datetime)
1657 if uploader.upload(delegated_credential,message=message):
1660 self.logger.info("Successfully uploaded %d/%d credentials"%(count_success,count_all))
1662 # at first I thought we would want to save these,
1663 # like 'sfi delegate does' but on second thought
1664 # it is probably not helpful as people would not
1665 # need to run 'sfi delegate' at all anymore
1666 if count_success != count_all: sys.exit(1)
1669 @declare_command("cred","")
1670 def trusted(self, options, args):
1672 return the trusted certs at this interface (get_trusted_certs)
1674 if options.registry_interface:
1675 server=self.registry()
1677 server = self.sliceapi()
1678 cred = self.my_authority_credential_string()
1679 trusted_certs = server.get_trusted_certs(cred)
1680 if not options.registry_interface:
1681 trusted_certs = ReturnValue.get_value(trusted_certs)
1683 for trusted_cert in trusted_certs:
1684 print "\n===========================================================\n"
1685 gid = GID(string=trusted_cert)
1687 cert = Certificate(string=trusted_cert)
1688 self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1689 print "Certificate:\n%s\n\n"%trusted_cert