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
35 from sfa.storage.record import Record
37 from sfa.rspecs.rspec import RSpec
38 from sfa.rspecs.rspec_converter import RSpecConverter
39 from sfa.rspecs.version_manager import VersionManager
41 from sfa.client.sfaclientlib import SfaClientBootstrap
42 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
43 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
44 from sfa.client.return_value import ReturnValue
45 from sfa.client.candidates import Candidates
46 from sfa.client.manifolduploader import ManifoldUploader
50 from sfa.client.common import optparse_listvalue_callback, optparse_dictvalue_callback, \
51 terminal_render, filter_records
54 def display_rspec(rspec, format='rspec'):
56 tree = etree.parse(StringIO(rspec))
58 result = root.xpath("./network/site/node/hostname/text()")
59 elif format in ['ip']:
60 # The IP address is not yet part of the new RSpec
61 # so this doesn't do anything yet.
62 tree = etree.parse(StringIO(rspec))
64 result = root.xpath("./network/site/node/ipv4/text()")
71 def display_list(results):
72 for result in results:
75 def display_records(recordList, dump=False):
76 ''' Print all fields in the record'''
77 for record in recordList:
78 display_record(record, dump)
80 def display_record(record, dump=False):
82 record.dump(sort=True)
84 info = record.getdict()
85 print "%s (%s)" % (info['hrn'], info['type'])
89 def filter_records(type, records):
91 for record in records:
92 if (record['type'] == type) or (type == "all"):
93 filtered_records.append(record)
94 return filtered_records
97 def credential_printable (cred):
98 credential=Credential(cred=cred)
100 result += credential.get_summary_tostring()
102 rights = credential.get_privileges()
103 result += "type=%s\n" % credential.type
104 result += "version=%s\n" % credential.version
105 result += "rights=%s\n"%rights
108 def show_credentials (cred_s):
109 if not isinstance (cred_s,list): cred_s = [cred_s]
111 print "Using Credential %s"%credential_printable(cred)
114 def save_raw_to_file(var, filename, format="text", banner=None):
116 # if filename is "-", send it to stdout
119 f = open(filename, "w")
124 elif format == "pickled":
125 f.write(pickle.dumps(var))
126 elif format == "json":
127 if hasattr(json, "dumps"):
128 f.write(json.dumps(var)) # python 2.6
130 f.write(json.write(var)) # python 2.5
132 # this should never happen
133 print "unknown output format", format
135 f.write('\n'+banner+"\n")
137 def save_rspec_to_file(rspec, filename):
138 if not filename.endswith(".rspec"):
139 filename = filename + ".rspec"
140 f = open(filename, 'w')
145 def save_records_to_file(filename, record_dicts, format="xml"):
148 for record_dict in record_dicts:
150 save_record_to_file(filename + "." + str(index), record_dict)
152 save_record_to_file(filename, record_dict)
154 elif format == "xmllist":
155 f = open(filename, "w")
156 f.write("<recordlist>\n")
157 for record_dict in record_dicts:
158 record_obj=Record(dict=record_dict)
159 f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
160 f.write("</recordlist>\n")
162 elif format == "hrnlist":
163 f = open(filename, "w")
164 for record_dict in record_dicts:
165 record_obj=Record(dict=record_dict)
166 f.write(record_obj.hrn + "\n")
169 # this should never happen
170 print "unknown output format", format
172 def save_record_to_file(filename, record_dict):
173 record = Record(dict=record_dict)
174 xml = record.save_as_xml()
175 f=codecs.open(filename, encoding='utf-8',mode="w")
180 # minimally check a key argument
181 def check_ssh_key (key):
182 good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
183 return re.match(good_ssh_key, key, re.IGNORECASE)
186 def load_record_from_opts(options):
188 if hasattr(options, 'xrn') and options.xrn:
189 if hasattr(options, 'type') and options.type:
190 xrn = Xrn(options.xrn, options.type)
192 xrn = Xrn(options.xrn)
193 record_dict['urn'] = xrn.get_urn()
194 record_dict['hrn'] = xrn.get_hrn()
195 record_dict['type'] = xrn.get_type()
196 if hasattr(options, 'key') and options.key:
198 pubkey = open(options.key, 'r').read()
201 if not check_ssh_key (pubkey):
202 raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format")
203 record_dict['keys'] = [pubkey]
204 if hasattr(options, 'slices') and options.slices:
205 record_dict['slices'] = options.slices
206 if hasattr(options, 'researchers') and options.researchers:
207 record_dict['researcher'] = options.researchers
208 if hasattr(options, 'email') and options.email:
209 record_dict['email'] = options.email
210 if hasattr(options, 'pis') and options.pis:
211 record_dict['pi'] = options.pis
213 # handle extra settings
214 record_dict.update(options.extras)
216 return Record(dict=record_dict)
218 def load_record_from_file(filename):
219 f=codecs.open(filename, encoding="utf-8", mode="r")
220 xml_string = f.read()
222 return Record(xml=xml_string)
226 def unique_call_id(): return uuid.uuid4().urn
228 ########## a simple model for maintaing 3 doc attributes per command (instead of just one)
229 # essentially for the methods that implement a subcommand like sfi list
230 # we need to keep track of
231 # (*) doc a few lines that tell what it does, still located in __doc__
232 # (*) args_string a simple one-liner that describes mandatory arguments
233 # (*) example well, one or several releant examples
235 # since __doc__ only accounts for one, we use this simple mechanism below
236 # however we keep doc in place for easier migration
238 from functools import wraps
240 # we use a list as well as a dict so we can keep track of the order
244 def register_command (args_string, example):
246 name=getattr(m,'__name__')
247 doc=getattr(m,'__doc__',"-- missing doc --")
248 doc=doc.strip(" \t\n")
249 commands_list.append(name)
250 commands_dict[name]=(doc, args_string, example)
252 def new_method (*args, **kwds): return m(*args, **kwds)
260 # dirty hack to make this class usable from the outside
261 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
264 def default_sfi_dir ():
265 if os.path.isfile("./sfi_config"):
268 return os.path.expanduser("~/.sfi/")
270 # dummy to meet Sfi's expectations for its 'options' field
271 # i.e. s/t we can do setattr on
275 def __init__ (self,options=None):
276 if options is None: options=Sfi.DummyOptions()
277 for opt in Sfi.required_options:
278 if not hasattr(options,opt): setattr(options,opt,None)
279 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
280 self.options = options
282 self.authority = None
283 self.logger = sfi_logger
284 self.logger.enable_console()
285 ### various auxiliary material that we keep at hand
287 # need to call this other than just 'config' as we have a command/method with that name
288 self.config_instance=None
289 self.config_file=None
290 self.client_bootstrap=None
292 ### suitable if no reasonable command has been provided
293 def print_commands_help (self, options):
294 verbose=getattr(options,'verbose')
295 format3="%18s %-15s %s"
298 print format3%("command","cmd_args","description")
302 self.create_parser_global().print_help()
303 # preserve order from the code
304 for command in commands_list:
305 (doc, args_string, example) = commands_dict[command]
308 doc=doc.replace("\n","\n"+35*' ')
309 print format3%(command,args_string,doc)
311 self.create_parser_command(command).print_help()
313 ### now if a known command was found we can be more verbose on that one
314 def print_help (self):
315 print "==================== Generic sfi usage"
316 self.sfi_parser.print_help()
317 (doc,_,example)=commands_dict[self.command]
318 print "\n==================== Purpose of %s"%self.command
320 print "\n==================== Specific usage for %s"%self.command
321 self.command_parser.print_help()
323 print "\n==================== %s example(s)"%self.command
326 def create_parser_global(self):
327 # Generate command line parser
328 parser = OptionParser(add_help_option=False,
329 usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
330 description="Commands: %s"%(" ".join(commands_list)))
331 parser.add_option("-r", "--registry", dest="registry",
332 help="root registry", metavar="URL", default=None)
333 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
334 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
335 parser.add_option("-R", "--raw", dest="raw", default=None,
336 help="Save raw, unparsed server response to a file")
337 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
338 help="raw file format ([text]|pickled|json)", default="text",
339 choices=("text","pickled","json"))
340 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
341 help="text string to write before and after raw output")
342 parser.add_option("-d", "--dir", dest="sfi_dir",
343 help="config & working directory - default is %default",
344 metavar="PATH", default=Sfi.default_sfi_dir())
345 parser.add_option("-u", "--user", dest="user",
346 help="user name", metavar="HRN", default=None)
347 parser.add_option("-a", "--auth", dest="auth",
348 help="authority name", metavar="HRN", default=None)
349 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
350 help="verbose mode - cumulative")
351 parser.add_option("-D", "--debug",
352 action="store_true", dest="debug", default=False,
353 help="Debug (xml-rpc) protocol messages")
354 # would it make sense to use ~/.ssh/id_rsa as a default here ?
355 parser.add_option("-k", "--private-key",
356 action="store", dest="user_private_key", default=None,
357 help="point to the private key file to use if not yet installed in sfi_dir")
358 parser.add_option("-t", "--timeout", dest="timeout", default=None,
359 help="Amout of time to wait before timing out the request")
360 parser.add_option("-h", "--help",
361 action="store_true", dest="help", default=False,
362 help="one page summary on commands & exit")
363 parser.disable_interspersed_args()
368 def create_parser_command(self, command):
369 if command not in commands_dict:
370 msg="Invalid command\n"
372 msg += ','.join(commands_list)
373 self.logger.critical(msg)
376 # retrieve args_string
377 (_, args_string, __) = commands_dict[command]
379 parser = OptionParser(add_help_option=False,
380 usage="sfi [sfi_options] %s [cmd_options] %s"
381 % (command, args_string))
382 parser.add_option ("-h","--help",dest='help',action='store_true',default=False,
383 help="Summary of one command usage")
385 if command in ("config"):
386 parser.add_option('-m', '--myslice', dest='myslice', action='store_true', default=False,
387 help='how myslice config variables as well')
389 if command in ("version"):
390 parser.add_option("-l","--local",
391 action="store_true", dest="version_local", default=False,
392 help="display version of the local client")
394 if command in ("version", "trusted"):
395 parser.add_option("-R","--registry_interface",
396 action="store_true", dest="registry_interface", default=False,
397 help="target the registry interface instead of slice interface")
399 if command in ("add", "update"):
400 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
401 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
402 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
403 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
405 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
406 default='', type="str", action='callback', callback=optparse_listvalue_callback)
407 parser.add_option('-r', '--researchers', dest='researchers', metavar='<researchers>',
408 help='Set/replace slice researchers', default='', type="str", action='callback',
409 callback=optparse_listvalue_callback)
410 parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
411 default='', type="str", action='callback', callback=optparse_listvalue_callback)
412 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
413 action="callback", callback=optparse_dictvalue_callback, nargs=1,
414 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
416 # user specifies remote aggregate/sm/component
417 if command in ("resources", "describe", "allocate", "provision", "delete", "allocate", "provision",
418 "action", "shutdown", "renew", "status"):
419 parser.add_option("-d", "--delegate", dest="delegate", default=None,
421 help="Include a credential delegated to the user's root"+\
422 "authority in set of credentials for this call")
424 # show_credential option
425 if command in ("list","resources", "describe", "provision", "allocate", "add","update","remove","delete","status","renew"):
426 parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
427 help="show credential(s) used in human-readable form")
428 # registy filter option
429 if command in ("list", "show", "remove"):
430 parser.add_option("-t", "--type", dest="type", type="choice",
431 help="type filter ([all]|user|slice|authority|node|aggregate)",
432 choices=("all", "user", "slice", "authority", "node", "aggregate"),
434 if command in ("show"):
435 parser.add_option("-k","--key",dest="keys",action="append",default=[],
436 help="specify specific keys to be displayed from record")
437 if command in ("resources", "describe"):
439 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="GENI 3",
440 help="schema type and version of resulting RSpec")
441 # disable/enable cached rspecs
442 parser.add_option("-c", "--current", dest="current", default=False,
444 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
446 parser.add_option("-f", "--format", dest="format", type="choice",
447 help="display format ([xml]|dns|ip)", default="xml",
448 choices=("xml", "dns", "ip"))
449 #panos: a new option to define the type of information about resources a user is interested in
450 parser.add_option("-i", "--info", dest="info",
451 help="optional component information", default=None)
452 # a new option to retreive or not reservation-oriented RSpecs (leases)
453 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
454 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
455 choices=("all", "resources", "leases"), default="resources")
458 if command in ("resources", "describe", "allocate", "provision", "show", "list", "gid"):
459 parser.add_option("-o", "--output", dest="file",
460 help="output XML to file", metavar="FILE", default=None)
462 if command in ("show", "list"):
463 parser.add_option("-f", "--format", dest="format", type="choice",
464 help="display format ([text]|xml)", default="text",
465 choices=("text", "xml"))
467 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
468 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
469 choices=("xml", "xmllist", "hrnlist"))
470 if command == 'list':
471 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
472 help="list all child records", default=False)
473 parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
474 help="gives details, like user keys", default=False)
475 if command in ("delegate"):
476 parser.add_option("-u", "--user",
477 action="store_true", dest="delegate_user", default=False,
478 help="delegate your own credentials; default if no other option is provided")
479 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
480 metavar="slice_hrn", help="delegate cred. for slice HRN")
481 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
482 metavar='auth_hrn', help="delegate cred for auth HRN")
483 # this primarily is a shorthand for -a my_hrn
484 parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
485 help="delegate your PI credentials, so s.t. like -a your_hrn^")
486 parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
487 help="""by default the mandatory argument is expected to be a user,
488 use this if you mean an authority instead""")
490 if command in ("myslice"):
491 parser.add_option("-p","--password",dest='password',action='store',default=None,
492 help="specify mainfold password on the command line")
493 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
494 metavar="slice_hrn", help="delegate cred. for slice HRN")
495 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
496 metavar='auth_hrn', help="delegate PI cred for auth HRN")
497 parser.add_option('-d', '--delegate', dest='delegate', help="Override 'delegate' from the config file")
498 parser.add_option('-b', '--backend', dest='backend', help="Override 'backend' from the config file")
504 # Main: parse arguments and dispatch to command
506 def dispatch(self, command, command_options, command_args):
507 method=getattr(self, command, None)
509 print "Unknown command %s"%command
511 return method(command_options, command_args)
514 self.sfi_parser = self.create_parser_global()
515 (options, args) = self.sfi_parser.parse_args()
517 self.print_commands_help(options)
519 self.options = options
521 self.logger.setLevelFromOptVerbose(self.options.verbose)
524 self.logger.critical("No command given. Use -h for help.")
525 self.print_commands_help(options)
528 # complete / find unique match with command set
529 command_candidates = Candidates (commands_list)
531 command = command_candidates.only_match(input)
533 self.print_commands_help(options)
535 # second pass options parsing
537 self.command_parser = self.create_parser_command(command)
538 (command_options, command_args) = self.command_parser.parse_args(args[1:])
539 if command_options.help:
542 self.command_options = command_options
546 self.logger.debug("Command=%s" % self.command)
549 self.dispatch(command, command_options, command_args)
553 self.logger.log_exc ("sfi command %s failed"%command)
559 def read_config(self):
560 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
561 shell_config_file = os.path.join(self.options.sfi_dir,"sfi_config.sh")
563 if Config.is_ini(config_file):
564 config = Config (config_file)
566 # try upgrading from shell config format
567 fp, fn = mkstemp(suffix='sfi_config', text=True)
569 # we need to preload the sections we want parsed
570 # from the shell config
571 config.add_section('sfi')
572 # sface users should be able to use this same file to configure their stuff
573 config.add_section('sface')
574 # manifold users should be able to specify the details
575 # of their backend server here for 'sfi myslice'
576 config.add_section('myslice')
577 config.load(config_file)
579 shutil.move(config_file, shell_config_file)
581 config.save(config_file)
584 self.logger.critical("Failed to read configuration file %s"%config_file)
585 self.logger.info("Make sure to remove the export clauses and to add quotes")
586 if self.options.verbose==0:
587 self.logger.info("Re-run with -v for more details")
589 self.logger.log_exc("Could not read config file %s"%config_file)
592 self.config_instance=config
595 if (self.options.sm is not None):
596 self.sm_url = self.options.sm
597 elif hasattr(config, "SFI_SM"):
598 self.sm_url = config.SFI_SM
600 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
604 if (self.options.registry is not None):
605 self.reg_url = self.options.registry
606 elif hasattr(config, "SFI_REGISTRY"):
607 self.reg_url = config.SFI_REGISTRY
609 self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
613 if (self.options.user is not None):
614 self.user = self.options.user
615 elif hasattr(config, "SFI_USER"):
616 self.user = config.SFI_USER
618 self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
622 if (self.options.auth is not None):
623 self.authority = self.options.auth
624 elif hasattr(config, "SFI_AUTH"):
625 self.authority = config.SFI_AUTH
627 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
630 self.config_file=config_file
635 # Get various credential and spec files
637 # Establishes limiting conventions
638 # - conflates MAs and SAs
639 # - assumes last token in slice name is unique
641 # Bootstraps credentials
642 # - bootstrap user credential from self-signed certificate
643 # - bootstrap authority credential from user credential
644 # - bootstrap slice credential from user credential
647 # init self-signed cert, user credentials and gid
648 def bootstrap (self):
649 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
651 # if -k is provided, use this to initialize private key
652 if self.options.user_private_key:
653 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
655 # trigger legacy compat code if needed
656 # the name has changed from just <leaf>.pkey to <hrn>.pkey
657 if not os.path.isfile(client_bootstrap.private_key_filename()):
658 self.logger.info ("private key not found, trying legacy name")
660 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
661 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
662 client_bootstrap.init_private_key_if_missing (legacy_private_key)
663 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
665 self.logger.log_exc("Can't find private key ")
669 client_bootstrap.bootstrap_my_gid()
670 # extract what's needed
671 self.private_key = client_bootstrap.private_key()
672 self.my_credential_string = client_bootstrap.my_credential_string ()
673 self.my_credential = {'geni_type': 'geni_sfa',
675 'geni_value': self.my_credential_string}
676 self.my_gid = client_bootstrap.my_gid ()
677 self.client_bootstrap = client_bootstrap
680 def my_authority_credential_string(self):
681 if not self.authority:
682 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
684 return self.client_bootstrap.authority_credential_string (self.authority)
686 def authority_credential_string(self, auth_hrn):
687 return self.client_bootstrap.authority_credential_string (auth_hrn)
689 def slice_credential_string(self, name):
690 return self.client_bootstrap.slice_credential_string (name)
692 def slice_credential(self, name):
693 return {'geni_type': 'geni_sfa',
695 'geni_value': self.slice_credential_string(name)}
697 # xxx should be supported by sfaclientbootstrap as well
698 def delegate_cred(self, object_cred, hrn, type='authority'):
699 # the gid and hrn of the object we are delegating
700 if isinstance(object_cred, str):
701 object_cred = Credential(string=object_cred)
702 object_gid = object_cred.get_gid_object()
703 object_hrn = object_gid.get_hrn()
705 if not object_cred.get_privileges().get_all_delegate():
706 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
709 # the delegating user's gid
710 caller_gidfile = self.my_gid()
712 # the gid of the user who will be delegated to
713 delegee_gid = self.client_bootstrap.gid(hrn,type)
714 delegee_hrn = delegee_gid.get_hrn()
715 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
716 return dcred.save_to_string(save_parents=True)
719 # Management of the servers
724 if not hasattr (self, 'registry_proxy'):
725 self.logger.info("Contacting Registry at: %s"%self.reg_url)
726 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
727 timeout=self.options.timeout, verbose=self.options.debug)
728 return self.registry_proxy
732 if not hasattr (self, 'sliceapi_proxy'):
733 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
734 if hasattr(self.command_options,'component') and self.command_options.component:
735 # resolve the hrn at the registry
736 node_hrn = self.command_options.component
737 records = self.registry().Resolve(node_hrn, self.my_credential_string)
738 records = filter_records('node', records)
740 self.logger.warning("No such component:%r"% opts.component)
742 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
743 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
745 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
746 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
747 self.sm_url = 'http://' + self.sm_url
748 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
749 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
750 timeout=self.options.timeout, verbose=self.options.debug)
751 return self.sliceapi_proxy
753 def get_cached_server_version(self, server):
754 # check local cache first
757 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
758 cache_key = server.url + "-version"
760 cache = Cache(cache_file)
763 self.logger.info("Local cache not found at: %s" % cache_file)
766 version = cache.get(cache_key)
769 result = server.GetVersion()
770 version= ReturnValue.get_value(result)
771 # cache version for 20 minutes
772 cache.add(cache_key, version, ttl= 60*20)
773 self.logger.info("Updating cache file %s" % cache_file)
774 cache.save_to_file(cache_file)
778 ### resurrect this temporarily so we can support V1 aggregates for a while
779 def server_supports_options_arg(self, server):
781 Returns true if server support the optional call_id arg, false otherwise.
783 server_version = self.get_cached_server_version(server)
785 # xxx need to rewrite this
786 if int(server_version.get('geni_api')) >= 2:
790 def server_supports_call_id_arg(self, server):
791 server_version = self.get_cached_server_version(server)
793 if 'sfa' in server_version and 'code_tag' in server_version:
794 code_tag = server_version['code_tag']
795 code_tag_parts = code_tag.split("-")
796 version_parts = code_tag_parts[0].split(".")
797 major, minor = version_parts[0], version_parts[1]
798 rev = code_tag_parts[1]
799 if int(major) == 1 and minor == 0 and build >= 22:
803 ### ois = options if supported
804 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
805 def ois (self, server, option_dict):
806 if self.server_supports_options_arg (server):
808 elif self.server_supports_call_id_arg (server):
809 return [ unique_call_id () ]
813 ### cis = call_id if supported - like ois
814 def cis (self, server):
815 if self.server_supports_call_id_arg (server):
816 return [ unique_call_id ]
820 ######################################## miscell utilities
821 def get_rspec_file(self, rspec):
822 if (os.path.isabs(rspec)):
825 file = os.path.join(self.options.sfi_dir, rspec)
826 if (os.path.isfile(file)):
829 self.logger.critical("No such rspec file %s"%rspec)
832 def get_record_file(self, record):
833 if (os.path.isabs(record)):
836 file = os.path.join(self.options.sfi_dir, record)
837 if (os.path.isfile(file)):
840 self.logger.critical("No such registry record file %s"%record)
844 #==========================================================================
845 # Following functions implement the commands
847 # Registry-related commands
848 #==========================================================================
850 @register_command("","")
851 def config (self, options, args):
852 "Display contents of current config"
853 print "# From configuration file %s"%self.config_file
854 flags=[ ('sfi', [ ('registry','reg_url'),
855 ('auth','authority'),
861 flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
863 for (section, tuples) in flags:
866 for (external_name, internal_name) in tuples:
867 print "%-20s = %s"%(external_name,getattr(self,internal_name))
870 varname="%s_%s"%(section.upper(),name.upper())
871 value=getattr(self.config_instance,varname)
872 print "%-20s = %s"%(name,value)
874 @register_command("","")
875 def version(self, options, args):
877 display an SFA server version (GetVersion)
878 or version information about sfi itself
880 if options.version_local:
881 version=version_core()
883 if options.registry_interface:
884 server=self.registry()
886 server = self.sliceapi()
887 result = server.GetVersion()
888 version = ReturnValue.get_value(result)
890 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
892 pprinter = PrettyPrinter(indent=4)
893 pprinter.pprint(version)
895 @register_command("authority","")
896 def list(self, options, args):
898 list entries in named authority registry (List)
905 if options.recursive:
906 opts['recursive'] = options.recursive
908 if options.show_credential:
909 show_credentials(self.my_credential_string)
911 list = self.registry().List(hrn, self.my_credential_string, options)
913 raise Exception, "Not enough parameters for the 'list' command"
915 # filter on person, slice, site, node, etc.
916 # This really should be in the self.filter_records funct def comment...
917 list = filter_records(options.type, list)
918 terminal_render (list, options)
920 save_records_to_file(options.file, list, options.fileformat)
923 @register_command("name","")
924 def show(self, options, args):
926 show details about named registry record (Resolve)
932 # explicitly require Resolve to run in details mode
933 record_dicts = self.registry().Resolve(hrn, self.my_credential_string, {'details':True})
934 record_dicts = filter_records(options.type, record_dicts)
936 self.logger.error("No record of type %s"% options.type)
938 # user has required to focus on some keys
940 def project (record):
942 for key in options.keys:
943 try: projected[key]=record[key]
946 record_dicts = [ project (record) for record in record_dicts ]
947 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
948 for record in records:
949 if (options.format == "text"): record.dump(sort=True)
950 else: print record.save_as_xml()
952 save_records_to_file(options.file, record_dicts, options.fileformat)
955 @register_command("[xml-filename]","")
956 def add(self, options, args):
957 """add record into registry (Register)
958 from command line options (recommended)
959 old-school method involving an xml file still supported"""
961 auth_cred = self.my_authority_credential_string()
962 if options.show_credential:
963 show_credentials(auth_cred)
970 record_filepath = args[0]
971 rec_file = self.get_record_file(record_filepath)
972 record_dict.update(load_record_from_file(rec_file).todict())
974 print "Cannot load record file %s"%record_filepath
977 record_dict.update(load_record_from_opts(options).todict())
978 # we should have a type by now
979 if 'type' not in record_dict :
982 # this is still planetlab dependent.. as plc will whine without that
983 # also, it's only for adding
984 if record_dict['type'] == 'user':
985 if not 'first_name' in record_dict:
986 record_dict['first_name'] = record_dict['hrn']
987 if 'last_name' not in record_dict:
988 record_dict['last_name'] = record_dict['hrn']
989 return self.registry().Register(record_dict, auth_cred)
991 @register_command("[xml-filename]","")
992 def update(self, options, args):
993 """update record into registry (Update)
994 from command line options (recommended)
995 old-school method involving an xml file still supported"""
998 record_filepath = args[0]
999 rec_file = self.get_record_file(record_filepath)
1000 record_dict.update(load_record_from_file(rec_file).todict())
1002 record_dict.update(load_record_from_opts(options).todict())
1003 # at the very least we need 'type' here
1004 if 'type' not in record_dict:
1008 # don't translate into an object, as this would possibly distort
1009 # user-provided data; e.g. add an 'email' field to Users
1010 if record_dict['type'] == "user":
1011 if record_dict['hrn'] == self.user:
1012 cred = self.my_credential_string
1014 cred = self.my_authority_credential_string()
1015 elif record_dict['type'] in ["slice"]:
1017 cred = self.slice_credential_string(record_dict['hrn'])
1018 except ServerException, e:
1019 # XXX smbaker -- once we have better error return codes, update this
1020 # to do something better than a string compare
1021 if "Permission error" in e.args[0]:
1022 cred = self.my_authority_credential_string()
1025 elif record_dict['type'] in ["authority"]:
1026 cred = self.my_authority_credential_string()
1027 elif record_dict['type'] == 'node':
1028 cred = self.my_authority_credential_string()
1030 raise "unknown record type" + record_dict['type']
1031 if options.show_credential:
1032 show_credentials(cred)
1033 return self.registry().Update(record_dict, cred)
1035 @register_command("hrn","")
1036 def remove(self, options, args):
1037 "remove registry record by name (Remove)"
1038 auth_cred = self.my_authority_credential_string()
1046 if options.show_credential:
1047 show_credentials(auth_cred)
1048 return self.registry().Remove(hrn, auth_cred, type)
1050 # ==================================================================
1051 # Slice-related commands
1052 # ==================================================================
1054 # show rspec for named slice
1055 @register_command("","")
1056 def resources(self, options, args):
1058 discover available resources (ListResources)
1060 server = self.sliceapi()
1063 creds = [self.my_credential]
1064 if options.delegate:
1065 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1066 if options.show_credential:
1067 show_credentials(creds)
1069 # no need to check if server accepts the options argument since the options has
1070 # been a required argument since v1 API
1072 # always send call_id to v2 servers
1073 api_options ['call_id'] = unique_call_id()
1074 # ask for cached value if available
1075 api_options ['cached'] = True
1077 api_options['info'] = options.info
1078 if options.list_leases:
1079 api_options['list_leases'] = options.list_leases
1081 if options.current == True:
1082 api_options['cached'] = False
1084 api_options['cached'] = True
1085 if options.rspec_version:
1086 version_manager = VersionManager()
1087 server_version = self.get_cached_server_version(server)
1088 if 'sfa' in server_version:
1089 # just request the version the client wants
1090 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1092 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1094 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1095 result = server.ListResources (creds, api_options)
1096 value = ReturnValue.get_value(result)
1097 if self.options.raw:
1098 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1099 if options.file is not None:
1100 save_rspec_to_file(value, options.file)
1101 if (self.options.raw is None) and (options.file is None):
1102 display_rspec(value, options.format)
1106 @register_command("slice_hrn","")
1107 def describe(self, options, args):
1109 shows currently allocated/provisioned resources
1110 of the named slice or set of slivers (Describe)
1112 server = self.sliceapi()
1115 creds = [self.slice_credential(args[0])]
1116 if options.delegate:
1117 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1118 if options.show_credential:
1119 show_credentials(creds)
1121 api_options = {'call_id': unique_call_id(),
1123 #'info': options.info,
1124 'list_leases': options.list_leases,
1125 'geni_rspec_version': {'type': 'geni', 'version': '3'},
1128 api_options['info'] = options.info
1130 if options.rspec_version:
1131 version_manager = VersionManager()
1132 server_version = self.get_cached_server_version(server)
1133 if 'sfa' in server_version:
1134 # just request the version the client wants
1135 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1137 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1138 urn = Xrn(args[0], type='slice').get_urn()
1139 result = server.Describe([urn], creds, api_options)
1140 value = ReturnValue.get_value(result)
1141 if self.options.raw:
1142 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1143 if options.file is not None:
1144 save_rspec_to_file(value['geni_rspec'], options.file)
1145 if (self.options.raw is None) and (options.file is None):
1146 display_rspec(value, options.format)
1150 @register_command("slice_hrn [<sliver_urn> ... <sliver_urn>]","")
1151 def delete(self, options, args):
1153 de-allocate and de-provision all or named slivers of the named slice (Delete)
1155 server = self.sliceapi()
1159 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1162 # we have sliver urns
1163 sliver_urns = args[1:]
1165 # we provision all the slivers of the slice
1166 sliver_urns = [slice_urn]
1169 slice_cred = self.slice_credential(slice_hrn)
1170 creds = [slice_cred]
1172 # options and call_id when supported
1174 api_options ['call_id'] = unique_call_id()
1175 if options.show_credential:
1176 show_credentials(creds)
1177 result = server.Delete(sliver_urns, creds, *self.ois(server, api_options ) )
1178 value = ReturnValue.get_value(result)
1179 if self.options.raw:
1180 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1185 @register_command("slice_hrn rspec","")
1186 def allocate(self, options, args):
1188 allocate resources to the named slice (Allocate)
1190 server = self.sliceapi()
1191 server_version = self.get_cached_server_version(server)
1193 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1196 creds = [self.slice_credential(slice_hrn)]
1198 delegated_cred = None
1199 if server_version.get('interface') == 'slicemgr':
1200 # delegate our cred to the slice manager
1201 # do not delegate cred to slicemgr...not working at the moment
1203 #if server_version.get('hrn'):
1204 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1205 #elif server_version.get('urn'):
1206 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1208 if options.show_credential:
1209 show_credentials(creds)
1212 rspec_file = self.get_rspec_file(args[1])
1213 rspec = open(rspec_file).read()
1215 api_options ['call_id'] = unique_call_id()
1219 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1220 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1221 slice_record = slice_records[0]
1222 user_hrns = slice_record['reg-researchers']
1223 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1224 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1225 sfa_users = sfa_users_arg(user_records, slice_record)
1226 geni_users = pg_users_arg(user_records)
1228 api_options['sfa_users'] = sfa_users
1229 api_options['geni_users'] = geni_users
1231 result = server.Allocate(slice_urn, creds, rspec, api_options)
1232 value = ReturnValue.get_value(result)
1233 if self.options.raw:
1234 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1235 if options.file is not None:
1236 save_rspec_to_file (value['geni_rspec'], options.file)
1237 if (self.options.raw is None) and (options.file is None):
1242 @register_command("slice_hrn [<sliver_urn> ... <sliver_urn>]","")
1243 def provision(self, options, args):
1245 provision all or named already allocated slivers of the named slice (Provision)
1247 server = self.sliceapi()
1248 server_version = self.get_cached_server_version(server)
1250 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1252 # we have sliver urns
1253 sliver_urns = args[1:]
1255 # we provision all the slivers of the slice
1256 sliver_urns = [slice_urn]
1259 creds = [self.slice_credential(slice_hrn)]
1260 delegated_cred = None
1261 if server_version.get('interface') == 'slicemgr':
1262 # delegate our cred to the slice manager
1263 # do not delegate cred to slicemgr...not working at the moment
1265 #if server_version.get('hrn'):
1266 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1267 #elif server_version.get('urn'):
1268 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1270 if options.show_credential:
1271 show_credentials(creds)
1274 api_options ['call_id'] = unique_call_id()
1276 # set the requtested rspec version
1277 version_manager = VersionManager()
1278 rspec_version = version_manager._get_version('geni', '3').to_dict()
1279 api_options['geni_rspec_version'] = rspec_version
1282 # need to pass along user keys to the aggregate.
1284 # { urn: urn:publicid:IDN+emulab.net+user+alice
1285 # keys: [<ssh key A>, <ssh key B>]
1288 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1289 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1290 slice_record = slice_records[0]
1291 user_hrns = slice_record['reg-researchers']
1292 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1293 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1294 users = pg_users_arg(user_records)
1296 api_options['geni_users'] = users
1297 result = server.Provision(sliver_urns, creds, api_options)
1298 value = ReturnValue.get_value(result)
1299 if self.options.raw:
1300 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1301 if options.file is not None:
1302 save_rspec_to_file (value['geni_rspec'], options.file)
1303 if (self.options.raw is None) and (options.file is None):
1307 @register_command("slice_hrn","")
1308 def status(self, options, args):
1310 retrieve the status of the slivers belonging to the named slice (Status)
1312 server = self.sliceapi()
1316 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1319 slice_cred = self.slice_credential(slice_hrn)
1320 creds = [slice_cred]
1322 # options and call_id when supported
1324 api_options['call_id']=unique_call_id()
1325 if options.show_credential:
1326 show_credentials(creds)
1327 result = server.Status([slice_urn], creds, *self.ois(server,api_options))
1328 value = ReturnValue.get_value(result)
1329 if self.options.raw:
1330 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1333 # Thierry: seemed to be missing
1336 @register_command("slice_hrn [<sliver_urn> ... <sliver_urn>] action","")
1337 def action(self, options, args):
1339 Perform the named operational action on all or named slivers of the named slice
1341 server = self.sliceapi()
1345 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1347 # we have sliver urns
1348 sliver_urns = args[1:-1]
1350 # we provision all the slivers of the slice
1351 sliver_urns = [slice_urn]
1354 slice_cred = self.slice_credential(args[0])
1355 creds = [slice_cred]
1356 if options.delegate:
1357 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1358 creds.append(delegated_cred)
1360 result = server.PerformOperationalAction(sliver_urns, creds, action , api_options)
1361 value = ReturnValue.get_value(result)
1362 if self.options.raw:
1363 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1368 @register_command("slice_hrn [<sliver_urn> ... <sliver_urn>] time","")
1369 def renew(self, options, args):
1373 server = self.sliceapi()
1378 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]
1386 input_time = args[-1]
1388 # time: don't try to be smart on the time format, server-side will
1390 slice_cred = self.slice_credential(args[0])
1391 creds = [slice_cred]
1392 # options and call_id when supported
1394 api_options['call_id']=unique_call_id()
1395 if options.show_credential:
1396 show_credentials(creds)
1397 result = server.Renew(sliver_urns, creds, input_time, *self.ois(server,api_options))
1398 value = ReturnValue.get_value(result)
1399 if self.options.raw:
1400 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1406 @register_command("slice_hrn","")
1407 def shutdown(self, options, args):
1409 shutdown named slice (Shutdown)
1411 server = self.sliceapi()
1414 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1416 slice_cred = self.slice_credential(slice_hrn)
1417 creds = [slice_cred]
1418 result = server.Shutdown(slice_urn, creds)
1419 value = ReturnValue.get_value(result)
1420 if self.options.raw:
1421 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1427 @register_command("[name]","")
1428 def gid(self, options, args):
1430 Create a GID (CreateGid)
1435 target_hrn = args[0]
1436 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1437 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1439 filename = options.file
1441 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1442 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1443 GID(string=gid).save_to_file(filename)
1445 ####################
1446 @register_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1448 will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1449 the set of credentials in the scope for this call would be
1450 (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1452 (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1454 (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1455 (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1456 because of the two -s options
1459 def delegate (self, options, args):
1461 (locally) create delegate credential for use by given hrn
1462 make sure to check for 'sfi myslice' instead if you plan
1469 # support for several delegations in the same call
1470 # so first we gather the things to do
1472 for slice_hrn in options.delegate_slices:
1473 message="%s.slice"%slice_hrn
1474 original = self.slice_credential_string(slice_hrn)
1475 tuples.append ( (message, original,) )
1476 if options.delegate_pi:
1477 my_authority=self.authority
1478 message="%s.pi"%my_authority
1479 original = self.my_authority_credential_string()
1480 tuples.append ( (message, original,) )
1481 for auth_hrn in options.delegate_auths:
1482 message="%s.auth"%auth_hrn
1483 original=self.authority_credential_string(auth_hrn)
1484 tuples.append ( (message, original, ) )
1485 # if nothing was specified at all at this point, let's assume -u
1486 if not tuples: options.delegate_user=True
1488 if options.delegate_user:
1489 message="%s.user"%self.user
1490 original = self.my_credential_string
1491 tuples.append ( (message, original, ) )
1493 # default type for beneficial is user unless -A
1494 if options.delegate_to_authority: to_type='authority'
1495 else: to_type='user'
1497 # let's now handle all this
1498 # it's all in the filenaming scheme
1499 for (message,original) in tuples:
1500 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1501 delegated_credential = Credential (string=delegated_string)
1502 filename = os.path.join ( self.options.sfi_dir,
1503 "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1504 delegated_credential.save_to_file(filename, save_parents=True)
1505 self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1507 ####################
1508 @register_command("","""$ less +/myslice sfi_config
1510 backend = http://manifold.pl.sophia.inria.fr:7080
1511 # the HRN that myslice uses, so that we are delegating to
1512 delegate = ple.upmc.slicebrowser
1513 # platform - this is a myslice concept
1515 # username - as of this writing (May 2013) a simple login name
1519 will first collect the slices that you are part of, then make sure
1520 all your credentials are up-to-date (read: refresh expired ones)
1521 then compute delegated credentials for user 'ple.upmc.slicebrowser'
1522 and upload them all on myslice backend, using 'platform' and 'user'.
1523 A password will be prompted for the upload part.
1525 $ sfi -v myslice -- or sfi -vv myslice
1526 same but with more and more verbosity
1528 $ sfi m -b http://mymanifold.foo.com:7080/
1529 is synonym to sfi myslice as no other command starts with an 'm'
1530 and uses a custom backend for this one call
1532 ) # register_command
1533 def myslice (self, options, args):
1535 """ This helper is for refreshing your credentials at myslice; it will
1536 * compute all the slices that you currently have credentials on
1537 * refresh all your credentials (you as a user and pi, your slices)
1538 * upload them to the manifold backend server
1539 for last phase, sfi_config is read to look for the [myslice] section,
1540 and namely the 'backend', 'delegate' and 'user' settings"""
1546 # enable info by default
1547 self.logger.setLevelFromOptVerbose(self.options.verbose+1)
1548 ### the rough sketch goes like this
1549 # (0) produce a p12 file
1550 self.client_bootstrap.my_pkcs12()
1552 # (a) rain check for sufficient config in sfi_config
1554 myslice_keys=[ 'backend', 'delegate', 'platform', 'username']
1555 for key in myslice_keys:
1557 # oct 2013 - I'm finding myself juggling with config files
1558 # so a couple of command-line options can now override config
1559 if hasattr(options,key) and getattr(options,key) is not None:
1560 value=getattr(options,key)
1562 full_key="MYSLICE_" + key.upper()
1563 value=getattr(self.config_instance,full_key,None)
1564 if value: myslice_dict[key]=value
1565 else: print "Unsufficient config, missing key %s in [myslice] section of sfi_config"%key
1566 if len(myslice_dict) != len(myslice_keys):
1569 # (b) figure whether we are PI for the authority where we belong
1570 self.logger.info("Resolving our own id %s"%self.user)
1571 my_records=self.registry().Resolve(self.user,self.my_credential_string)
1572 if len(my_records)!=1: print "Cannot Resolve %s -- exiting"%self.user; sys.exit(1)
1573 my_record=my_records[0]
1574 my_auths_all = my_record['reg-pi-authorities']
1575 self.logger.info("Found %d authorities that we are PI for"%len(my_auths_all))
1576 self.logger.debug("They are %s"%(my_auths_all))
1578 my_auths = my_auths_all
1579 if options.delegate_auths:
1580 my_auths = list(set(my_auths_all).intersection(set(options.delegate_auths)))
1581 self.logger.debug("Restricted to user-provided auths"%(my_auths))
1583 # (c) get the set of slices that we are in
1584 my_slices_all=my_record['reg-slices']
1585 self.logger.info("Found %d slices that we are member of"%len(my_slices_all))
1586 self.logger.debug("They are: %s"%(my_slices_all))
1588 my_slices = my_slices_all
1589 # if user provided slices, deal only with these - if they are found
1590 if options.delegate_slices:
1591 my_slices = list(set(my_slices_all).intersection(set(options.delegate_slices)))
1592 self.logger.debug("Restricted to user-provided slices: %s"%(my_slices))
1594 # (d) make sure we have *valid* credentials for all these
1596 hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1597 for auth_hrn in my_auths:
1598 hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1599 for slice_hrn in my_slices:
1600 hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1602 # (e) check for the delegated version of these
1603 # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever
1604 # switch to myslice using an authority instead of a user
1605 delegatee_type='user'
1606 delegatee_hrn=myslice_dict['delegate']
1607 hrn_delegated_credentials = []
1608 for (hrn, htype, credential) in hrn_credentials:
1609 delegated_credential = self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type)
1610 # save these so user can monitor what she's uploaded
1611 filename = os.path.join ( self.options.sfi_dir,
1612 "%s.%s_for_%s.%s.cred"%(hrn,htype,delegatee_hrn,delegatee_type))
1613 with file(filename,'w') as f:
1614 f.write(delegated_credential)
1615 self.logger.debug("(Over)wrote %s"%filename)
1616 hrn_delegated_credentials.append ((hrn, htype, delegated_credential, filename, ))
1618 # (f) and finally upload them to manifold server
1619 # xxx todo add an option so the password can be set on the command line
1620 # (but *NOT* in the config file) so other apps can leverage this
1621 self.logger.info("Uploading on backend at %s"%myslice_dict['backend'])
1622 uploader = ManifoldUploader (logger=self.logger,
1623 url=myslice_dict['backend'],
1624 platform=myslice_dict['platform'],
1625 username=myslice_dict['username'],
1626 password=options.password)
1627 uploader.prompt_all()
1628 (count_all,count_success)=(0,0)
1629 for (hrn,htype,delegated_credential,filename) in hrn_delegated_credentials:
1631 inspect=Credential(string=delegated_credential)
1632 expire_datetime=inspect.get_expiration()
1633 message="%s (%s) [exp:%s]"%(hrn,htype,expire_datetime)
1634 if uploader.upload(delegated_credential,message=message):
1637 self.logger.info("Successfully uploaded %d/%d credentials"%(count_success,count_all))
1639 # at first I thought we would want to save these,
1640 # like 'sfi delegate does' but on second thought
1641 # it is probably not helpful as people would not
1642 # need to run 'sfi delegate' at all anymore
1643 if count_success != count_all: sys.exit(1)
1646 @register_command("cred","")
1647 def trusted(self, options, args):
1649 return the trusted certs at this interface (get_trusted_certs)
1651 if options.registry_interface:
1652 server=self.registry()
1654 server = self.sliceapi()
1655 cred = self.my_authority_credential_string()
1656 trusted_certs = server.get_trusted_certs(cred)
1657 if not options.registry_interface:
1658 trusted_certs = ReturnValue.get_value(trusted_certs)
1660 for trusted_cert in trusted_certs:
1661 print "\n===========================================================\n"
1662 gid = GID(string=trusted_cert)
1664 cert = Certificate(string=trusted_cert)
1665 self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1666 print "Certificate:\n%s\n\n"%trusted_cert