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("-I", "--interface", dest="interface", type="choice",
396 help="Select the SFA interface the call should target (Slice Interface (sm) | Registry Interface (registry))",
397 choices=("sm", "registry"),
400 if command in ("add", "update"):
401 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
402 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
403 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
404 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
406 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
407 default='', type="str", action='callback', callback=optparse_listvalue_callback)
408 parser.add_option('-r', '--researchers', dest='researchers', metavar='<researchers>',
409 help='Set/replace slice researchers', default='', type="str", action='callback',
410 callback=optparse_listvalue_callback)
411 parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
412 default='', type="str", action='callback', callback=optparse_listvalue_callback)
413 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
414 action="callback", callback=optparse_dictvalue_callback, nargs=1,
415 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
417 # user specifies remote aggregate/sm/component
418 if command in ("resources", "describe", "allocate", "provision", "delete", "allocate", "provision",
419 "action", "shutdown", "renew", "status"):
420 parser.add_option("-d", "--delegate", dest="delegate", default=None,
422 help="Include a credential delegated to the user's root"+\
423 "authority in set of credentials for this call")
425 # show_credential option
426 if command in ("list","resources", "describe", "provision", "allocate", "add","update","remove","delete","status","renew"):
427 parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
428 help="show credential(s) used in human-readable form")
429 # registy filter option
430 if command in ("list", "show", "remove"):
431 parser.add_option("-t", "--type", dest="type", type="choice",
432 help="type filter ([all]|user|slice|authority|node|aggregate)",
433 choices=("all", "user", "slice", "authority", "node", "aggregate"),
435 if command in ("show"):
436 parser.add_option("-k","--key",dest="keys",action="append",default=[],
437 help="specify specific keys to be displayed from record")
438 if command in ("resources", "describe"):
440 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="GENI 3",
441 help="schema type and version of resulting RSpec")
442 # disable/enable cached rspecs
443 parser.add_option("-c", "--current", dest="current", default=False,
445 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
447 parser.add_option("-f", "--format", dest="format", type="choice",
448 help="display format ([xml]|dns|ip)", default="xml",
449 choices=("xml", "dns", "ip"))
450 #panos: a new option to define the type of information about resources a user is interested in
451 parser.add_option("-i", "--info", dest="info",
452 help="optional component information", default=None)
453 # a new option to retreive or not reservation-oriented RSpecs (leases)
454 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
455 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
456 choices=("all", "resources", "leases"), default="resources")
459 if command in ("resources", "describe", "allocate", "provision", "show", "list", "gid"):
460 parser.add_option("-o", "--output", dest="file",
461 help="output XML to file", metavar="FILE", default=None)
463 if command in ("show", "list"):
464 parser.add_option("-f", "--format", dest="format", type="choice",
465 help="display format ([text]|xml)", default="text",
466 choices=("text", "xml"))
468 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
469 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
470 choices=("xml", "xmllist", "hrnlist"))
471 if command == 'list':
472 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
473 help="list all child records", default=False)
474 parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
475 help="gives details, like user keys", default=False)
476 if command in ("delegate"):
477 parser.add_option("-u", "--user",
478 action="store_true", dest="delegate_user", default=False,
479 help="delegate your own credentials; default if no other option is provided")
480 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
481 metavar="slice_hrn", help="delegate cred. for slice HRN")
482 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
483 metavar='auth_hrn', help="delegate cred for auth HRN")
484 # this primarily is a shorthand for -a my_hrn
485 parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
486 help="delegate your PI credentials, so s.t. like -a your_hrn^")
487 parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
488 help="""by default the mandatory argument is expected to be a user,
489 use this if you mean an authority instead""")
491 if command in ("myslice"):
492 parser.add_option("-p","--password",dest='password',action='store',default=None,
493 help="specify mainfold password on the command line")
494 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
495 metavar="slice_hrn", help="delegate cred. for slice HRN")
496 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
497 metavar='auth_hrn', help="delegate PI cred for auth HRN")
498 parser.add_option('-d', '--delegate', dest='delegate', help="Override 'delegate' from the config file")
499 parser.add_option('-b', '--backend', dest='backend', help="Override 'backend' from the config file")
505 # Main: parse arguments and dispatch to command
507 def dispatch(self, command, command_options, command_args):
508 method=getattr(self, command, None)
510 print "Unknown command %s"%command
512 return method(command_options, command_args)
515 self.sfi_parser = self.create_parser_global()
516 (options, args) = self.sfi_parser.parse_args()
518 self.print_commands_help(options)
520 self.options = options
522 self.logger.setLevelFromOptVerbose(self.options.verbose)
525 self.logger.critical("No command given. Use -h for help.")
526 self.print_commands_help(options)
529 # complete / find unique match with command set
530 command_candidates = Candidates (commands_list)
532 command = command_candidates.only_match(input)
534 self.print_commands_help(options)
536 # second pass options parsing
538 self.command_parser = self.create_parser_command(command)
539 (command_options, command_args) = self.command_parser.parse_args(args[1:])
540 if command_options.help:
543 self.command_options = command_options
547 self.logger.debug("Command=%s" % self.command)
550 self.dispatch(command, command_options, command_args)
554 self.logger.log_exc ("sfi command %s failed"%command)
560 def read_config(self):
561 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
562 shell_config_file = os.path.join(self.options.sfi_dir,"sfi_config.sh")
564 if Config.is_ini(config_file):
565 config = Config (config_file)
567 # try upgrading from shell config format
568 fp, fn = mkstemp(suffix='sfi_config', text=True)
570 # we need to preload the sections we want parsed
571 # from the shell config
572 config.add_section('sfi')
573 # sface users should be able to use this same file to configure their stuff
574 config.add_section('sface')
575 # manifold users should be able to specify the details
576 # of their backend server here for 'sfi myslice'
577 config.add_section('myslice')
578 config.load(config_file)
580 shutil.move(config_file, shell_config_file)
582 config.save(config_file)
585 self.logger.critical("Failed to read configuration file %s"%config_file)
586 self.logger.info("Make sure to remove the export clauses and to add quotes")
587 if self.options.verbose==0:
588 self.logger.info("Re-run with -v for more details")
590 self.logger.log_exc("Could not read config file %s"%config_file)
593 self.config_instance=config
596 if (self.options.sm is not None):
597 self.sm_url = self.options.sm
598 elif hasattr(config, "SFI_SM"):
599 self.sm_url = config.SFI_SM
601 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
605 if (self.options.registry is not None):
606 self.reg_url = self.options.registry
607 elif hasattr(config, "SFI_REGISTRY"):
608 self.reg_url = config.SFI_REGISTRY
610 self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
614 if (self.options.user is not None):
615 self.user = self.options.user
616 elif hasattr(config, "SFI_USER"):
617 self.user = config.SFI_USER
619 self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
623 if (self.options.auth is not None):
624 self.authority = self.options.auth
625 elif hasattr(config, "SFI_AUTH"):
626 self.authority = config.SFI_AUTH
628 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
631 self.config_file=config_file
636 # Get various credential and spec files
638 # Establishes limiting conventions
639 # - conflates MAs and SAs
640 # - assumes last token in slice name is unique
642 # Bootstraps credentials
643 # - bootstrap user credential from self-signed certificate
644 # - bootstrap authority credential from user credential
645 # - bootstrap slice credential from user credential
648 # init self-signed cert, user credentials and gid
649 def bootstrap (self):
650 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
652 # if -k is provided, use this to initialize private key
653 if self.options.user_private_key:
654 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
656 # trigger legacy compat code if needed
657 # the name has changed from just <leaf>.pkey to <hrn>.pkey
658 if not os.path.isfile(client_bootstrap.private_key_filename()):
659 self.logger.info ("private key not found, trying legacy name")
661 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
662 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
663 client_bootstrap.init_private_key_if_missing (legacy_private_key)
664 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
666 self.logger.log_exc("Can't find private key ")
670 client_bootstrap.bootstrap_my_gid()
671 # extract what's needed
672 self.private_key = client_bootstrap.private_key()
673 self.my_credential_string = client_bootstrap.my_credential_string ()
674 self.my_credential = {'geni_type': 'geni_sfa',
676 'geni_value': self.my_credential_string}
677 self.my_gid = client_bootstrap.my_gid ()
678 self.client_bootstrap = client_bootstrap
681 def my_authority_credential_string(self):
682 if not self.authority:
683 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
685 return self.client_bootstrap.authority_credential_string (self.authority)
687 def authority_credential_string(self, auth_hrn):
688 return self.client_bootstrap.authority_credential_string (auth_hrn)
690 def slice_credential_string(self, name):
691 return self.client_bootstrap.slice_credential_string (name)
693 def slice_credential(self, name):
694 return {'geni_type': 'geni_sfa',
696 'geni_value': self.slice_credential_string(name)}
698 # xxx should be supported by sfaclientbootstrap as well
699 def delegate_cred(self, object_cred, hrn, type='authority'):
700 # the gid and hrn of the object we are delegating
701 if isinstance(object_cred, str):
702 object_cred = Credential(string=object_cred)
703 object_gid = object_cred.get_gid_object()
704 object_hrn = object_gid.get_hrn()
706 if not object_cred.get_privileges().get_all_delegate():
707 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
710 # the delegating user's gid
711 caller_gidfile = self.my_gid()
713 # the gid of the user who will be delegated to
714 delegee_gid = self.client_bootstrap.gid(hrn,type)
715 delegee_hrn = delegee_gid.get_hrn()
716 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
717 return dcred.save_to_string(save_parents=True)
720 # Management of the servers
725 if not hasattr (self, 'registry_proxy'):
726 self.logger.info("Contacting Registry at: %s"%self.reg_url)
727 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
728 timeout=self.options.timeout, verbose=self.options.debug)
729 return self.registry_proxy
733 if not hasattr (self, 'sliceapi_proxy'):
734 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
735 if hasattr(self.command_options,'component') and self.command_options.component:
736 # resolve the hrn at the registry
737 node_hrn = self.command_options.component
738 records = self.registry().Resolve(node_hrn, self.my_credential_string)
739 records = filter_records('node', records)
741 self.logger.warning("No such component:%r"% opts.component)
743 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
744 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
746 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
747 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
748 self.sm_url = 'http://' + self.sm_url
749 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
750 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
751 timeout=self.options.timeout, verbose=self.options.debug)
752 return self.sliceapi_proxy
754 def get_cached_server_version(self, server):
755 # check local cache first
758 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
759 cache_key = server.url + "-version"
761 cache = Cache(cache_file)
764 self.logger.info("Local cache not found at: %s" % cache_file)
767 version = cache.get(cache_key)
770 result = server.GetVersion()
771 version= ReturnValue.get_value(result)
772 # cache version for 20 minutes
773 cache.add(cache_key, version, ttl= 60*20)
774 self.logger.info("Updating cache file %s" % cache_file)
775 cache.save_to_file(cache_file)
779 ### resurrect this temporarily so we can support V1 aggregates for a while
780 def server_supports_options_arg(self, server):
782 Returns true if server support the optional call_id arg, false otherwise.
784 server_version = self.get_cached_server_version(server)
786 # xxx need to rewrite this
787 if int(server_version.get('geni_api')) >= 2:
791 def server_supports_call_id_arg(self, server):
792 server_version = self.get_cached_server_version(server)
794 if 'sfa' in server_version and 'code_tag' in server_version:
795 code_tag = server_version['code_tag']
796 code_tag_parts = code_tag.split("-")
797 version_parts = code_tag_parts[0].split(".")
798 major, minor = version_parts[0], version_parts[1]
799 rev = code_tag_parts[1]
800 if int(major) == 1 and minor == 0 and build >= 22:
804 ### ois = options if supported
805 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
806 def ois (self, server, option_dict):
807 if self.server_supports_options_arg (server):
809 elif self.server_supports_call_id_arg (server):
810 return [ unique_call_id () ]
814 ### cis = call_id if supported - like ois
815 def cis (self, server):
816 if self.server_supports_call_id_arg (server):
817 return [ unique_call_id ]
821 ######################################## miscell utilities
822 def get_rspec_file(self, rspec):
823 if (os.path.isabs(rspec)):
826 file = os.path.join(self.options.sfi_dir, rspec)
827 if (os.path.isfile(file)):
830 self.logger.critical("No such rspec file %s"%rspec)
833 def get_record_file(self, record):
834 if (os.path.isabs(record)):
837 file = os.path.join(self.options.sfi_dir, record)
838 if (os.path.isfile(file)):
841 self.logger.critical("No such registry record file %s"%record)
845 #==========================================================================
846 # Following functions implement the commands
848 # Registry-related commands
849 #==========================================================================
851 @register_command("","")
852 def config (self, options, args):
853 "Display contents of current config"
854 print "# From configuration file %s"%self.config_file
855 flags=[ ('sfi', [ ('registry','reg_url'),
856 ('auth','authority'),
862 flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
864 for (section, tuples) in flags:
867 for (external_name, internal_name) in tuples:
868 print "%-20s = %s"%(external_name,getattr(self,internal_name))
871 varname="%s_%s"%(section.upper(),name.upper())
872 value=getattr(self.config_instance,varname)
873 print "%-20s = %s"%(name,value)
875 @register_command("","")
876 def version(self, options, args):
878 display an SFA server version (GetVersion)
879 or version information about sfi itself
881 if options.version_local:
882 version=version_core()
884 if options.interface == "registry":
885 server=self.registry()
887 server = self.sliceapi()
888 result = server.GetVersion()
889 version = ReturnValue.get_value(result)
891 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
893 pprinter = PrettyPrinter(indent=4)
894 pprinter.pprint(version)
896 @register_command("authority","")
897 def list(self, options, args):
899 list entries in named authority registry (List)
906 if options.recursive:
907 opts['recursive'] = options.recursive
909 if options.show_credential:
910 show_credentials(self.my_credential_string)
912 list = self.registry().List(hrn, self.my_credential_string, options)
914 raise Exception, "Not enough parameters for the 'list' command"
916 # filter on person, slice, site, node, etc.
917 # This really should be in the self.filter_records funct def comment...
918 list = filter_records(options.type, list)
919 terminal_render (list, options)
921 save_records_to_file(options.file, list, options.fileformat)
924 @register_command("name","")
925 def show(self, options, args):
927 show details about named registry record (Resolve)
933 # explicitly require Resolve to run in details mode
934 record_dicts = self.registry().Resolve(hrn, self.my_credential_string, {'details':True})
935 record_dicts = filter_records(options.type, record_dicts)
937 self.logger.error("No record of type %s"% options.type)
939 # user has required to focus on some keys
941 def project (record):
943 for key in options.keys:
944 try: projected[key]=record[key]
947 record_dicts = [ project (record) for record in record_dicts ]
948 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
949 for record in records:
950 if (options.format == "text"): record.dump(sort=True)
951 else: print record.save_as_xml()
953 save_records_to_file(options.file, record_dicts, options.fileformat)
956 @register_command("[xml-filename]","")
957 def add(self, options, args):
958 """add record into registry (Register)
959 from command line options (recommended)
960 old-school method involving an xml file still supported"""
962 auth_cred = self.my_authority_credential_string()
963 if options.show_credential:
964 show_credentials(auth_cred)
971 record_filepath = args[0]
972 rec_file = self.get_record_file(record_filepath)
973 record_dict.update(load_record_from_file(rec_file).todict())
975 print "Cannot load record file %s"%record_filepath
978 record_dict.update(load_record_from_opts(options).todict())
979 # we should have a type by now
980 if 'type' not in record_dict :
983 # this is still planetlab dependent.. as plc will whine without that
984 # also, it's only for adding
985 if record_dict['type'] == 'user':
986 if not 'first_name' in record_dict:
987 record_dict['first_name'] = record_dict['hrn']
988 if 'last_name' not in record_dict:
989 record_dict['last_name'] = record_dict['hrn']
990 return self.registry().Register(record_dict, auth_cred)
992 @register_command("[xml-filename]","")
993 def update(self, options, args):
994 """update record into registry (Update)
995 from command line options (recommended)
996 old-school method involving an xml file still supported"""
999 record_filepath = args[0]
1000 rec_file = self.get_record_file(record_filepath)
1001 record_dict.update(load_record_from_file(rec_file).todict())
1003 record_dict.update(load_record_from_opts(options).todict())
1004 # at the very least we need 'type' here
1005 if 'type' not in record_dict:
1009 # don't translate into an object, as this would possibly distort
1010 # user-provided data; e.g. add an 'email' field to Users
1011 if record_dict['type'] == "user":
1012 if record_dict['hrn'] == self.user:
1013 cred = self.my_credential_string
1015 cred = self.my_authority_credential_string()
1016 elif record_dict['type'] in ["slice"]:
1018 cred = self.slice_credential_string(record_dict['hrn'])
1019 except ServerException, e:
1020 # XXX smbaker -- once we have better error return codes, update this
1021 # to do something better than a string compare
1022 if "Permission error" in e.args[0]:
1023 cred = self.my_authority_credential_string()
1026 elif record_dict['type'] in ["authority"]:
1027 cred = self.my_authority_credential_string()
1028 elif record_dict['type'] == 'node':
1029 cred = self.my_authority_credential_string()
1031 raise "unknown record type" + record_dict['type']
1032 if options.show_credential:
1033 show_credentials(cred)
1034 return self.registry().Update(record_dict, cred)
1036 @register_command("hrn","")
1037 def remove(self, options, args):
1038 "remove registry record by name (Remove)"
1039 auth_cred = self.my_authority_credential_string()
1047 if options.show_credential:
1048 show_credentials(auth_cred)
1049 return self.registry().Remove(hrn, auth_cred, type)
1051 # ==================================================================
1052 # Slice-related commands
1053 # ==================================================================
1055 # show rspec for named slice
1056 @register_command("","")
1057 def resources(self, options, args):
1059 discover available resources (ListResources)
1061 server = self.sliceapi()
1064 creds = [self.my_credential]
1065 if options.delegate:
1066 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1067 if options.show_credential:
1068 show_credentials(creds)
1070 # no need to check if server accepts the options argument since the options has
1071 # been a required argument since v1 API
1073 # always send call_id to v2 servers
1074 api_options ['call_id'] = unique_call_id()
1075 # ask for cached value if available
1076 api_options ['cached'] = True
1078 api_options['info'] = options.info
1079 if options.list_leases:
1080 api_options['list_leases'] = options.list_leases
1082 if options.current == True:
1083 api_options['cached'] = False
1085 api_options['cached'] = True
1086 if options.rspec_version:
1087 version_manager = VersionManager()
1088 server_version = self.get_cached_server_version(server)
1089 if 'sfa' in server_version:
1090 # just request the version the client wants
1091 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1093 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1095 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1096 result = server.ListResources (creds, api_options)
1097 value = ReturnValue.get_value(result)
1098 if self.options.raw:
1099 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1100 if options.file is not None:
1101 save_rspec_to_file(value['geni_rspec'], options.file)
1102 if (self.options.raw is None) and (options.file is None):
1103 display_rspec(value, options.format)
1107 @register_command("slice_hrn","")
1108 def describe(self, options, args):
1110 shows currently allocated/provisioned resources
1111 of the named slice or set of slivers (Describe)
1113 server = self.sliceapi()
1116 creds = [self.slice_credential(args[0])]
1117 if options.delegate:
1118 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1119 if options.show_credential:
1120 show_credentials(creds)
1122 api_options = {'call_id': unique_call_id(),
1124 'info': options.info,
1125 'list_leases': options.list_leases,
1126 'geni_rspec_version': {'type': 'geni', 'version': '3'},
1128 if options.rspec_version:
1129 version_manager = VersionManager()
1130 server_version = self.get_cached_server_version(server)
1131 if 'sfa' in server_version:
1132 # just request the version the client wants
1133 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1135 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1136 urn = Xrn(args[0], type='slice').get_urn()
1137 result = server.Describe([urn], creds, api_options)
1138 value = ReturnValue.get_value(result)
1139 if self.options.raw:
1140 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1141 if options.file is not None:
1142 save_rspec_to_file(value['geni_rspec'], options.file)
1143 if (self.options.raw is None) and (options.file is None):
1144 display_rspec(value, options.format)
1148 @register_command("slice_hrn","")
1149 def delete(self, options, args):
1151 de-allocate and de-provision all or named slivers of the slice (Delete)
1153 server = self.sliceapi()
1157 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1160 slice_cred = self.slice_credential(slice_hrn)
1161 creds = [slice_cred]
1163 # options and call_id when supported
1165 api_options ['call_id'] = unique_call_id()
1166 if options.show_credential:
1167 show_credentials(creds)
1168 result = server.Delete([slice_urn], creds, *self.ois(server, api_options ) )
1169 value = ReturnValue.get_value(result)
1170 if self.options.raw:
1171 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1176 @register_command("slice_hrn rspec","")
1177 def allocate(self, options, args):
1179 allocate resources to the named slice (Allocate)
1181 server = self.sliceapi()
1182 server_version = self.get_cached_server_version(server)
1184 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1187 creds = [self.slice_credential(slice_hrn)]
1189 delegated_cred = None
1190 if server_version.get('interface') == 'slicemgr':
1191 # delegate our cred to the slice manager
1192 # do not delegate cred to slicemgr...not working at the moment
1194 #if server_version.get('hrn'):
1195 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1196 #elif server_version.get('urn'):
1197 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1199 if options.show_credential:
1200 show_credentials(creds)
1203 rspec_file = self.get_rspec_file(args[1])
1204 rspec = open(rspec_file).read()
1206 api_options ['call_id'] = unique_call_id()
1210 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1211 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1212 slice_record = slice_records[0]
1213 user_hrns = slice_record['reg-researchers']
1214 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1215 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1216 sfa_users = sfa_users_arg(user_records, slice_record)
1217 geni_users = pg_users_arg(user_records)
1219 api_options['sfa_users'] = sfa_users
1220 api_options['geni_users'] = geni_users
1222 result = server.Allocate(slice_urn, creds, rspec, api_options)
1223 value = ReturnValue.get_value(result)
1224 if self.options.raw:
1225 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1226 if options.file is not None:
1227 save_rspec_to_file (value['geni_rspec'], options.file)
1228 if (self.options.raw is None) and (options.file is None):
1233 @register_command("slice_hrn","")
1234 def provision(self, options, args):
1236 provision already allocated resources of named slice (Provision)
1238 server = self.sliceapi()
1239 server_version = self.get_cached_server_version(server)
1241 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1244 creds = [self.slice_credential(slice_hrn)]
1245 delegated_cred = None
1246 if server_version.get('interface') == 'slicemgr':
1247 # delegate our cred to the slice manager
1248 # do not delegate cred to slicemgr...not working at the moment
1250 #if server_version.get('hrn'):
1251 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1252 #elif server_version.get('urn'):
1253 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1255 if options.show_credential:
1256 show_credentials(creds)
1259 api_options ['call_id'] = unique_call_id()
1261 # set the requtested rspec version
1262 version_manager = VersionManager()
1263 rspec_version = version_manager._get_version('geni', '3').to_dict()
1264 api_options['geni_rspec_version'] = rspec_version
1267 # need to pass along user keys to the aggregate.
1269 # { urn: urn:publicid:IDN+emulab.net+user+alice
1270 # keys: [<ssh key A>, <ssh key B>]
1273 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1274 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1275 slice_record = slice_records[0]
1276 user_hrns = slice_record['reg-researchers']
1277 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1278 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1279 users = pg_users_arg(user_records)
1281 api_options['geni_users'] = users
1282 result = server.Provision([slice_urn], creds, api_options)
1283 value = ReturnValue.get_value(result)
1284 if self.options.raw:
1285 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1286 if options.file is not None:
1287 save_rspec_to_file (value['geni_rspec'], options.file)
1288 if (self.options.raw is None) and (options.file is None):
1292 @register_command("slice_hrn","")
1293 def status(self, options, args):
1295 retrieve the status of the slivers belonging to tne named slice (Status)
1297 server = self.sliceapi()
1301 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1304 slice_cred = self.slice_credential(slice_hrn)
1305 creds = [slice_cred]
1307 # options and call_id when supported
1309 api_options['call_id']=unique_call_id()
1310 if options.show_credential:
1311 show_credentials(creds)
1312 result = server.Status([slice_urn], creds, *self.ois(server,api_options))
1313 value = ReturnValue.get_value(result)
1314 if self.options.raw:
1315 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1318 # Thierry: seemed to be missing
1321 @register_command("slice_hrn action","")
1322 def action(self, options, args):
1324 Perform the named operational action on these slivers
1326 server = self.sliceapi()
1331 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1333 slice_cred = self.slice_credential(args[0])
1334 creds = [slice_cred]
1335 if options.delegate:
1336 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1337 creds.append(delegated_cred)
1339 result = server.PerformOperationalAction([slice_urn], creds, action , api_options)
1340 value = ReturnValue.get_value(result)
1341 if self.options.raw:
1342 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1347 @register_command("slice_hrn time","")
1348 def renew(self, options, args):
1350 renew slice (RenewSliver)
1352 server = self.sliceapi()
1356 [ slice_hrn, input_time ] = args
1358 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1359 # time: don't try to be smart on the time format, server-side will
1361 slice_cred = self.slice_credential(args[0])
1362 creds = [slice_cred]
1363 # options and call_id when supported
1365 api_options['call_id']=unique_call_id()
1366 if options.show_credential:
1367 show_credentials(creds)
1368 result = server.Renew([slice_urn], creds, input_time, *self.ois(server,api_options))
1369 value = ReturnValue.get_value(result)
1370 if self.options.raw:
1371 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1377 @register_command("slice_hrn","")
1378 def shutdown(self, options, args):
1380 shutdown named slice (Shutdown)
1382 server = self.sliceapi()
1385 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1387 slice_cred = self.slice_credential(slice_hrn)
1388 creds = [slice_cred]
1389 result = server.Shutdown(slice_urn, creds)
1390 value = ReturnValue.get_value(result)
1391 if self.options.raw:
1392 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1398 @register_command("[name]","")
1399 def gid(self, options, args):
1401 Create a GID (CreateGid)
1406 target_hrn = args[0]
1407 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1408 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1410 filename = options.file
1412 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1413 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1414 GID(string=gid).save_to_file(filename)
1416 ####################
1417 @register_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1419 will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1420 the set of credentials in the scope for this call would be
1421 (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1423 (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1425 (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1426 (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1427 because of the two -s options
1430 def delegate (self, options, args):
1432 (locally) create delegate credential for use by given hrn
1433 make sure to check for 'sfi myslice' instead if you plan
1440 # support for several delegations in the same call
1441 # so first we gather the things to do
1443 for slice_hrn in options.delegate_slices:
1444 message="%s.slice"%slice_hrn
1445 original = self.slice_credential_string(slice_hrn)
1446 tuples.append ( (message, original,) )
1447 if options.delegate_pi:
1448 my_authority=self.authority
1449 message="%s.pi"%my_authority
1450 original = self.my_authority_credential_string()
1451 tuples.append ( (message, original,) )
1452 for auth_hrn in options.delegate_auths:
1453 message="%s.auth"%auth_hrn
1454 original=self.authority_credential_string(auth_hrn)
1455 tuples.append ( (message, original, ) )
1456 # if nothing was specified at all at this point, let's assume -u
1457 if not tuples: options.delegate_user=True
1459 if options.delegate_user:
1460 message="%s.user"%self.user
1461 original = self.my_credential_string
1462 tuples.append ( (message, original, ) )
1464 # default type for beneficial is user unless -A
1465 if options.delegate_to_authority: to_type='authority'
1466 else: to_type='user'
1468 # let's now handle all this
1469 # it's all in the filenaming scheme
1470 for (message,original) in tuples:
1471 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1472 delegated_credential = Credential (string=delegated_string)
1473 filename = os.path.join ( self.options.sfi_dir,
1474 "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1475 delegated_credential.save_to_file(filename, save_parents=True)
1476 self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1478 ####################
1479 @register_command("","""$ less +/myslice sfi_config
1481 backend = http://manifold.pl.sophia.inria.fr:7080
1482 # the HRN that myslice uses, so that we are delegating to
1483 delegate = ple.upmc.slicebrowser
1484 # platform - this is a myslice concept
1486 # username - as of this writing (May 2013) a simple login name
1490 will first collect the slices that you are part of, then make sure
1491 all your credentials are up-to-date (read: refresh expired ones)
1492 then compute delegated credentials for user 'ple.upmc.slicebrowser'
1493 and upload them all on myslice backend, using 'platform' and 'user'.
1494 A password will be prompted for the upload part.
1496 $ sfi -v myslice -- or sfi -vv myslice
1497 same but with more and more verbosity
1499 $ sfi m -b http://mymanifold.foo.com:7080/
1500 is synonym to sfi myslice as no other command starts with an 'm'
1501 and uses a custom backend for this one call
1503 ) # register_command
1504 def myslice (self, options, args):
1506 """ This helper is for refreshing your credentials at myslice; it will
1507 * compute all the slices that you currently have credentials on
1508 * refresh all your credentials (you as a user and pi, your slices)
1509 * upload them to the manifold backend server
1510 for last phase, sfi_config is read to look for the [myslice] section,
1511 and namely the 'backend', 'delegate' and 'user' settings"""
1517 # enable info by default
1518 self.logger.setLevelFromOptVerbose(self.options.verbose+1)
1519 ### the rough sketch goes like this
1520 # (0) produce a p12 file
1521 self.client_bootstrap.my_pkcs12()
1523 # (a) rain check for sufficient config in sfi_config
1525 myslice_keys=[ 'backend', 'delegate', 'platform', 'username']
1526 for key in myslice_keys:
1528 # oct 2013 - I'm finding myself juggling with config files
1529 # so a couple of command-line options can now override config
1530 if hasattr(options,key) and getattr(options,key) is not None:
1531 value=getattr(options,key)
1533 full_key="MYSLICE_" + key.upper()
1534 value=getattr(self.config_instance,full_key,None)
1535 if value: myslice_dict[key]=value
1536 else: print "Unsufficient config, missing key %s in [myslice] section of sfi_config"%key
1537 if len(myslice_dict) != len(myslice_keys):
1540 # (b) figure whether we are PI for the authority where we belong
1541 self.logger.info("Resolving our own id %s"%self.user)
1542 my_records=self.registry().Resolve(self.user,self.my_credential_string)
1543 if len(my_records)!=1: print "Cannot Resolve %s -- exiting"%self.user; sys.exit(1)
1544 my_record=my_records[0]
1545 my_auths_all = my_record['reg-pi-authorities']
1546 self.logger.info("Found %d authorities that we are PI for"%len(my_auths_all))
1547 self.logger.debug("They are %s"%(my_auths_all))
1549 my_auths = my_auths_all
1550 if options.delegate_auths:
1551 my_auths = list(set(my_auths_all).intersection(set(options.delegate_auths)))
1552 self.logger.debug("Restricted to user-provided auths"%(my_auths))
1554 # (c) get the set of slices that we are in
1555 my_slices_all=my_record['reg-slices']
1556 self.logger.info("Found %d slices that we are member of"%len(my_slices_all))
1557 self.logger.debug("They are: %s"%(my_slices_all))
1559 my_slices = my_slices_all
1560 # if user provided slices, deal only with these - if they are found
1561 if options.delegate_slices:
1562 my_slices = list(set(my_slices_all).intersection(set(options.delegate_slices)))
1563 self.logger.debug("Restricted to user-provided slices: %s"%(my_slices))
1565 # (d) make sure we have *valid* credentials for all these
1567 hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1568 for auth_hrn in my_auths:
1569 hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1570 for slice_hrn in my_slices:
1571 hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1573 # (e) check for the delegated version of these
1574 # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever
1575 # switch to myslice using an authority instead of a user
1576 delegatee_type='user'
1577 delegatee_hrn=myslice_dict['delegate']
1578 hrn_delegated_credentials = []
1579 for (hrn, htype, credential) in hrn_credentials:
1580 delegated_credential = self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type)
1581 # save these so user can monitor what she's uploaded
1582 filename = os.path.join ( self.options.sfi_dir,
1583 "%s.%s_for_%s.%s.cred"%(hrn,htype,delegatee_hrn,delegatee_type))
1584 with file(filename,'w') as f:
1585 f.write(delegated_credential)
1586 self.logger.debug("(Over)wrote %s"%filename)
1587 hrn_delegated_credentials.append ((hrn, htype, delegated_credential, filename, ))
1589 # (f) and finally upload them to manifold server
1590 # xxx todo add an option so the password can be set on the command line
1591 # (but *NOT* in the config file) so other apps can leverage this
1592 self.logger.info("Uploading on backend at %s"%myslice_dict['backend'])
1593 uploader = ManifoldUploader (logger=self.logger,
1594 url=myslice_dict['backend'],
1595 platform=myslice_dict['platform'],
1596 username=myslice_dict['username'],
1597 password=options.password)
1598 uploader.prompt_all()
1599 (count_all,count_success)=(0,0)
1600 for (hrn,htype,delegated_credential,filename) in hrn_delegated_credentials:
1602 inspect=Credential(string=delegated_credential)
1603 expire_datetime=inspect.get_expiration()
1604 message="%s (%s) [exp:%s]"%(hrn,htype,expire_datetime)
1605 if uploader.upload(delegated_credential,message=message):
1608 self.logger.info("Successfully uploaded %d/%d credentials"%(count_success,count_all))
1610 # at first I thought we would want to save these,
1611 # like 'sfi delegate does' but on second thought
1612 # it is probably not helpful as people would not
1613 # need to run 'sfi delegate' at all anymore
1614 if count_success != count_all: sys.exit(1)
1617 @register_command("cred","")
1618 def trusted(self, options, args):
1620 return the trusted certs at this interface (get_trusted_certs)
1622 if options.interface == "registry":
1623 server=self.registry()
1625 server = self.sliceapi()
1626 cred = self.my_authority_credential_string()
1627 trusted_certs = server.get_trusted_certs(cred)
1628 if options.interface != "registry":
1629 trusted_certs = ReturnValue.get_value(trusted_certs)
1631 for trusted_cert in trusted_certs:
1632 print "\n===========================================================\n"
1633 gid = GID(string=trusted_cert)
1635 cert = Certificate(string=trusted_cert)
1636 self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1637 print "Certificate:\n%s\n\n"%trusted_cert