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 credential_printable (credential_string):
90 credential=Credential(string=credential_string)
92 result += credential.get_summary_tostring()
94 rights = credential.get_privileges()
95 result += "rights=%s"%rights
99 def show_credentials (cred_s):
100 if not isinstance (cred_s,list): cred_s = [cred_s]
102 print "Using Credential %s"%credential_printable(cred)
105 def save_raw_to_file(var, filename, format="text", banner=None):
107 # if filename is "-", send it to stdout
110 f = open(filename, "w")
115 elif format == "pickled":
116 f.write(pickle.dumps(var))
117 elif format == "json":
118 if hasattr(json, "dumps"):
119 f.write(json.dumps(var)) # python 2.6
121 f.write(json.write(var)) # python 2.5
123 # this should never happen
124 print "unknown output format", format
126 f.write('\n'+banner+"\n")
128 def save_rspec_to_file(rspec, filename):
129 if not filename.endswith(".rspec"):
130 filename = filename + ".rspec"
131 f = open(filename, 'w')
136 def save_records_to_file(filename, record_dicts, format="xml"):
139 for record_dict in record_dicts:
141 save_record_to_file(filename + "." + str(index), record_dict)
143 save_record_to_file(filename, record_dict)
145 elif format == "xmllist":
146 f = open(filename, "w")
147 f.write("<recordlist>\n")
148 for record_dict in record_dicts:
149 record_obj=Record(dict=record_dict)
150 f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
151 f.write("</recordlist>\n")
153 elif format == "hrnlist":
154 f = open(filename, "w")
155 for record_dict in record_dicts:
156 record_obj=Record(dict=record_dict)
157 f.write(record_obj.hrn + "\n")
160 # this should never happen
161 print "unknown output format", format
163 def save_record_to_file(filename, record_dict):
164 record = Record(dict=record_dict)
165 xml = record.save_as_xml()
166 f=codecs.open(filename, encoding='utf-8',mode="w")
171 # minimally check a key argument
172 def check_ssh_key (key):
173 good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
174 return re.match(good_ssh_key, key, re.IGNORECASE)
177 def load_record_from_opts(options):
179 if hasattr(options, 'xrn') and options.xrn:
180 if hasattr(options, 'type') and options.type:
181 xrn = Xrn(options.xrn, options.type)
183 xrn = Xrn(options.xrn)
184 record_dict['urn'] = xrn.get_urn()
185 record_dict['hrn'] = xrn.get_hrn()
186 record_dict['type'] = xrn.get_type()
187 if hasattr(options, 'key') and options.key:
189 pubkey = open(options.key, 'r').read()
192 if not check_ssh_key (pubkey):
193 raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format")
194 record_dict['keys'] = [pubkey]
195 if hasattr(options, 'slices') and options.slices:
196 record_dict['slices'] = options.slices
197 if hasattr(options, 'researchers') and options.researchers:
198 record_dict['researcher'] = options.researchers
199 if hasattr(options, 'email') and options.email:
200 record_dict['email'] = options.email
201 if hasattr(options, 'pis') and options.pis:
202 record_dict['pi'] = options.pis
204 # handle extra settings
205 record_dict.update(options.extras)
207 return Record(dict=record_dict)
209 def load_record_from_file(filename):
210 f=codecs.open(filename, encoding="utf-8", mode="r")
211 xml_string = f.read()
213 return Record(xml=xml_string)
217 def unique_call_id(): return uuid.uuid4().urn
219 ########## a simple model for maintaing 3 doc attributes per command (instead of just one)
220 # essentially for the methods that implement a subcommand like sfi list
221 # we need to keep track of
222 # (*) doc a few lines that tell what it does, still located in __doc__
223 # (*) args_string a simple one-liner that describes mandatory arguments
224 # (*) example well, one or several releant examples
226 # since __doc__ only accounts for one, we use this simple mechanism below
227 # however we keep doc in place for easier migration
229 from functools import wraps
231 # we use a list as well as a dict so we can keep track of the order
235 def register_command (args_string, example):
237 name=getattr(m,'__name__')
238 doc=getattr(m,'__doc__',"-- missing doc --")
239 doc=doc.strip(" \t\n")
240 commands_list.append(name)
241 commands_dict[name]=(doc, args_string, example)
243 def new_method (*args, **kwds): return m(*args, **kwds)
251 # dirty hack to make this class usable from the outside
252 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
255 def default_sfi_dir ():
256 if os.path.isfile("./sfi_config"):
259 return os.path.expanduser("~/.sfi/")
261 # dummy to meet Sfi's expectations for its 'options' field
262 # i.e. s/t we can do setattr on
266 def __init__ (self,options=None):
267 if options is None: options=Sfi.DummyOptions()
268 for opt in Sfi.required_options:
269 if not hasattr(options,opt): setattr(options,opt,None)
270 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
271 self.options = options
273 self.authority = None
274 self.logger = sfi_logger
275 self.logger.enable_console()
276 ### various auxiliary material that we keep at hand
278 # need to call this other than just 'config' as we have a command/method with that name
279 self.config_instance=None
280 self.config_file=None
281 self.client_bootstrap=None
283 ### suitable if no reasonable command has been provided
284 def print_commands_help (self, options):
285 verbose=getattr(options,'verbose')
286 format3="%18s %-15s %s"
289 print format3%("command","cmd_args","description")
293 self.create_parser_global().print_help()
294 # preserve order from the code
295 for command in commands_list:
296 (doc, args_string, example) = commands_dict[command]
299 doc=doc.replace("\n","\n"+35*' ')
300 print format3%(command,args_string,doc)
302 self.create_parser_command(command).print_help()
304 ### now if a known command was found we can be more verbose on that one
305 def print_help (self):
306 print "==================== Generic sfi usage"
307 self.sfi_parser.print_help()
308 (doc,_,example)=commands_dict[self.command]
309 print "\n==================== Purpose of %s"%self.command
311 print "\n==================== Specific usage for %s"%self.command
312 self.command_parser.print_help()
314 print "\n==================== %s example(s)"%self.command
317 def create_parser_global(self):
319 # Generate command line parser
320 parser = OptionParser(add_help_option=False,
321 usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
322 description="Commands: %s"%(" ".join(commands_list)))
323 parser.add_option("-r", "--registry", dest="registry",
324 help="root registry", metavar="URL", default=None)
325 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
326 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
327 parser.add_option("-R", "--raw", dest="raw", default=None,
328 help="Save raw, unparsed server response to a file")
329 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
330 help="raw file format ([text]|pickled|json)", default="text",
331 choices=("text","pickled","json"))
332 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
333 help="text string to write before and after raw output")
334 parser.add_option("-d", "--dir", dest="sfi_dir",
335 help="config & working directory - default is %default",
336 metavar="PATH", default=Sfi.default_sfi_dir())
337 parser.add_option("-u", "--user", dest="user",
338 help="user name", metavar="HRN", default=None)
339 parser.add_option("-a", "--auth", dest="auth",
340 help="authority name", metavar="HRN", default=None)
341 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
342 help="verbose mode - cumulative")
343 parser.add_option("-D", "--debug",
344 action="store_true", dest="debug", default=False,
345 help="Debug (xml-rpc) protocol messages")
346 # would it make sense to use ~/.ssh/id_rsa as a default here ?
347 parser.add_option("-k", "--private-key",
348 action="store", dest="user_private_key", default=None,
349 help="point to the private key file to use if not yet installed in sfi_dir")
350 parser.add_option("-t", "--timeout", dest="timeout", default=None,
351 help="Amout of time to wait before timing out the request")
352 parser.add_option("-h", "--help",
353 action="store_true", dest="help", default=False,
354 help="one page summary on commands & exit")
355 parser.disable_interspersed_args()
360 def create_parser_command(self, command):
361 if command not in commands_dict:
362 msg="Invalid command\n"
364 msg += ','.join(commands_list)
365 self.logger.critical(msg)
368 # retrieve args_string
369 (_, args_string, __) = commands_dict[command]
371 parser = OptionParser(add_help_option=False,
372 usage="sfi [sfi_options] %s [cmd_options] %s"
373 % (command, args_string))
374 parser.add_option ("-h","--help",dest='help',action='store_true',default=False,
375 help="Summary of one command usage")
377 if command in ("config"):
378 parser.add_option('-m', '--myslice', dest='myslice', action='store_true', default=False,
379 help='how myslice config variables as well')
381 if command in ("version"):
382 parser.add_option("-R","--registry-version",
383 action="store_true", dest="version_registry", default=False,
384 help="probe registry version instead of sliceapi")
385 parser.add_option("-l","--local",
386 action="store_true", dest="version_local", default=False,
387 help="display version of the local client")
389 if command in ("add", "update"):
390 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
391 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
392 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
393 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
395 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
396 default='', type="str", action='callback', callback=optparse_listvalue_callback)
397 parser.add_option('-r', '--researchers', dest='researchers', metavar='<researchers>',
398 help='Set/replace slice researchers', default='', type="str", action='callback',
399 callback=optparse_listvalue_callback)
400 parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
401 default='', type="str", action='callback', callback=optparse_listvalue_callback)
402 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
403 action="callback", callback=optparse_dictvalue_callback, nargs=1,
404 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
406 # show_credential option
407 if command in ("list","resources","create","add","update","remove","slices","delete","status","renew"):
408 parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
409 help="show credential(s) used in human-readable form")
410 # registy filter option
411 if command in ("list", "show", "remove"):
412 parser.add_option("-t", "--type", dest="type", type="choice",
413 help="type filter ([all]|user|slice|authority|node|aggregate)",
414 choices=("all", "user", "slice", "authority", "node", "aggregate"),
416 if command in ("show"):
417 parser.add_option("-k","--key",dest="keys",action="append",default=[],
418 help="specify specific keys to be displayed from record")
419 if command in ("resources"):
421 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
422 help="schema type and version of resulting RSpec")
423 # disable/enable cached rspecs
424 parser.add_option("-c", "--current", dest="current", default=False,
426 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
428 parser.add_option("-f", "--format", dest="format", type="choice",
429 help="display format ([xml]|dns|ip)", default="xml",
430 choices=("xml", "dns", "ip"))
431 #panos: a new option to define the type of information about resources a user is interested in
432 parser.add_option("-i", "--info", dest="info",
433 help="optional component information", default=None)
434 # a new option to retreive or not reservation-oriented RSpecs (leases)
435 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
436 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
437 choices=("all", "resources", "leases"), default="resources")
440 # 'create' does return the new rspec, makes sense to save that too
441 if command in ("resources", "show", "list", "gid", 'create'):
442 parser.add_option("-o", "--output", dest="file",
443 help="output XML to file", metavar="FILE", default=None)
445 if command in ("show", "list"):
446 parser.add_option("-f", "--format", dest="format", type="choice",
447 help="display format ([text]|xml)", default="text",
448 choices=("text", "xml"))
450 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
451 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
452 choices=("xml", "xmllist", "hrnlist"))
453 if command == 'list':
454 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
455 help="list all child records", default=False)
456 parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
457 help="gives details, like user keys", default=False)
458 if command in ("delegate"):
459 parser.add_option("-u", "--user",
460 action="store_true", dest="delegate_user", default=False,
461 help="delegate your own credentials; default if no other option is provided")
462 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
463 metavar="slice_hrn", help="delegate cred. for slice HRN")
464 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
465 metavar='auth_hrn', help="delegate cred for auth HRN")
466 # this primarily is a shorthand for -a my_hrn
467 parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
468 help="delegate your PI credentials, so s.t. like -a your_hrn^")
469 parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
470 help="""by default the mandatory argument is expected to be a user,
471 use this if you mean an authority instead""")
473 if command in ("myslice"):
474 parser.add_option("-p","--password",dest='password',action='store',default=None,
475 help="specify mainfold password on the command line")
481 # Main: parse arguments and dispatch to command
483 def dispatch(self, command, command_options, command_args):
484 method=getattr(self, command, None)
486 print "Unknown command %s"%command
488 return method(command_options, command_args)
491 self.sfi_parser = self.create_parser_global()
492 (options, args) = self.sfi_parser.parse_args()
494 self.print_commands_help(options)
496 self.options = options
498 self.logger.setLevelFromOptVerbose(self.options.verbose)
501 self.logger.critical("No command given. Use -h for help.")
502 self.print_commands_help(options)
505 # complete / find unique match with command set
506 command_candidates = Candidates (commands_list)
508 command = command_candidates.only_match(input)
510 self.print_commands_help(options)
512 # second pass options parsing
514 self.command_parser = self.create_parser_command(command)
515 (command_options, command_args) = self.command_parser.parse_args(args[1:])
516 if command_options.help:
519 self.command_options = command_options
523 self.logger.debug("Command=%s" % self.command)
526 self.dispatch(command, command_options, command_args)
530 self.logger.log_exc ("sfi command %s failed"%command)
536 def read_config(self):
537 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
538 shell_config_file = os.path.join(self.options.sfi_dir,"sfi_config.sh")
540 if Config.is_ini(config_file):
541 config = Config (config_file)
543 # try upgrading from shell config format
544 fp, fn = mkstemp(suffix='sfi_config', text=True)
546 # we need to preload the sections we want parsed
547 # from the shell config
548 config.add_section('sfi')
549 # sface users should be able to use this same file to configure their stuff
550 config.add_section('sface')
551 # manifold users should be able to specify the details
552 # of their backend server here for 'sfi myslice'
553 config.add_section('myslice')
554 config.load(config_file)
556 shutil.move(config_file, shell_config_file)
558 config.save(config_file)
561 self.logger.critical("Failed to read configuration file %s"%config_file)
562 self.logger.info("Make sure to remove the export clauses and to add quotes")
563 if self.options.verbose==0:
564 self.logger.info("Re-run with -v for more details")
566 self.logger.log_exc("Could not read config file %s"%config_file)
569 self.config_instance=config
572 if (self.options.sm is not None):
573 self.sm_url = self.options.sm
574 elif hasattr(config, "SFI_SM"):
575 self.sm_url = config.SFI_SM
577 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
581 if (self.options.registry is not None):
582 self.reg_url = self.options.registry
583 elif hasattr(config, "SFI_REGISTRY"):
584 self.reg_url = config.SFI_REGISTRY
586 self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
590 if (self.options.user is not None):
591 self.user = self.options.user
592 elif hasattr(config, "SFI_USER"):
593 self.user = config.SFI_USER
595 self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
599 if (self.options.auth is not None):
600 self.authority = self.options.auth
601 elif hasattr(config, "SFI_AUTH"):
602 self.authority = config.SFI_AUTH
604 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
607 self.config_file=config_file
612 # Get various credential and spec files
614 # Establishes limiting conventions
615 # - conflates MAs and SAs
616 # - assumes last token in slice name is unique
618 # Bootstraps credentials
619 # - bootstrap user credential from self-signed certificate
620 # - bootstrap authority credential from user credential
621 # - bootstrap slice credential from user credential
624 # init self-signed cert, user credentials and gid
625 def bootstrap (self):
626 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
628 # if -k is provided, use this to initialize private key
629 if self.options.user_private_key:
630 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
632 # trigger legacy compat code if needed
633 # the name has changed from just <leaf>.pkey to <hrn>.pkey
634 if not os.path.isfile(client_bootstrap.private_key_filename()):
635 self.logger.info ("private key not found, trying legacy name")
637 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
638 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
639 client_bootstrap.init_private_key_if_missing (legacy_private_key)
640 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
642 self.logger.log_exc("Can't find private key ")
646 client_bootstrap.bootstrap_my_gid()
647 # extract what's needed
648 self.private_key = client_bootstrap.private_key()
649 self.my_credential_string = client_bootstrap.my_credential_string ()
650 self.my_gid = client_bootstrap.my_gid ()
651 self.client_bootstrap = client_bootstrap
654 def my_authority_credential_string(self):
655 if not self.authority:
656 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
658 return self.client_bootstrap.authority_credential_string (self.authority)
660 def authority_credential_string(self, auth_hrn):
661 return self.client_bootstrap.authority_credential_string (auth_hrn)
663 def slice_credential_string(self, name):
664 return self.client_bootstrap.slice_credential_string (name)
667 # Management of the servers
672 if not hasattr (self, 'registry_proxy'):
673 self.logger.info("Contacting Registry at: %s"%self.reg_url)
674 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
675 timeout=self.options.timeout, verbose=self.options.debug)
676 return self.registry_proxy
680 if not hasattr (self, 'sliceapi_proxy'):
681 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
682 if hasattr(self.command_options,'component') and self.command_options.component:
683 # resolve the hrn at the registry
684 node_hrn = self.command_options.component
685 records = self.registry().Resolve(node_hrn, self.my_credential_string)
686 records = filter_records('node', records)
688 self.logger.warning("No such component:%r"% opts.component)
690 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
691 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
693 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
694 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
695 self.sm_url = 'http://' + self.sm_url
696 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
697 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
698 timeout=self.options.timeout, verbose=self.options.debug)
699 return self.sliceapi_proxy
701 def get_cached_server_version(self, server):
702 # check local cache first
705 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
706 cache_key = server.url + "-version"
708 cache = Cache(cache_file)
711 self.logger.info("Local cache not found at: %s" % cache_file)
714 version = cache.get(cache_key)
717 result = server.GetVersion()
718 version= ReturnValue.get_value(result)
719 # cache version for 20 minutes
720 cache.add(cache_key, version, ttl= 60*20)
721 self.logger.info("Updating cache file %s" % cache_file)
722 cache.save_to_file(cache_file)
726 ### resurrect this temporarily so we can support V1 aggregates for a while
727 def server_supports_options_arg(self, server):
729 Returns true if server support the optional call_id arg, false otherwise.
731 server_version = self.get_cached_server_version(server)
733 # xxx need to rewrite this
734 if int(server_version.get('geni_api')) >= 2:
738 def server_supports_call_id_arg(self, server):
739 server_version = self.get_cached_server_version(server)
741 if 'sfa' in server_version and 'code_tag' in server_version:
742 code_tag = server_version['code_tag']
743 code_tag_parts = code_tag.split("-")
744 version_parts = code_tag_parts[0].split(".")
745 major, minor = version_parts[0], version_parts[1]
746 rev = code_tag_parts[1]
747 if int(major) == 1 and minor == 0 and build >= 22:
751 ### ois = options if supported
752 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
753 def ois (self, server, option_dict):
754 if self.server_supports_options_arg (server):
756 elif self.server_supports_call_id_arg (server):
757 return [ unique_call_id () ]
761 ### cis = call_id if supported - like ois
762 def cis (self, server):
763 if self.server_supports_call_id_arg (server):
764 return [ unique_call_id ]
768 ######################################## miscell utilities
769 def get_rspec_file(self, rspec):
770 if (os.path.isabs(rspec)):
773 file = os.path.join(self.options.sfi_dir, rspec)
774 if (os.path.isfile(file)):
777 self.logger.critical("No such rspec file %s"%rspec)
780 def get_record_file(self, record):
781 if (os.path.isabs(record)):
784 file = os.path.join(self.options.sfi_dir, record)
785 if (os.path.isfile(file)):
788 self.logger.critical("No such registry record file %s"%record)
792 #==========================================================================
793 # Following functions implement the commands
795 # Registry-related commands
796 #==========================================================================
798 @register_command("","")
799 def config (self, options, args):
800 "Display contents of current config"
801 print "# From configuration file %s"%self.config_file
802 flags=[ ('sfi', [ ('registry','reg_url'),
803 ('auth','authority'),
809 flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
811 for (section, tuples) in flags:
814 for (external_name, internal_name) in tuples:
815 print "%-20s = %s"%(external_name,getattr(self,internal_name))
818 varname="%s_%s"%(section.upper(),name.upper())
819 value=getattr(self.config_instance,varname)
820 print "%-20s = %s"%(name,value)
822 @register_command("","")
823 def version(self, options, args):
825 display an SFA server version (GetVersion)
826 or version information about sfi itself
828 if options.version_local:
829 version=version_core()
831 if options.version_registry:
832 server=self.registry()
834 server = self.sliceapi()
835 result = server.GetVersion()
836 version = ReturnValue.get_value(result)
838 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
840 pprinter = PrettyPrinter(indent=4)
841 pprinter.pprint(version)
843 @register_command("authority","")
844 def list(self, options, args):
846 list entries in named authority registry (List)
853 if options.recursive:
854 opts['recursive'] = options.recursive
856 if options.show_credential:
857 show_credentials(self.my_credential_string)
859 list = self.registry().List(hrn, self.my_credential_string, options)
861 raise Exception, "Not enough parameters for the 'list' command"
863 # filter on person, slice, site, node, etc.
864 # This really should be in the self.filter_records funct def comment...
865 list = filter_records(options.type, list)
866 terminal_render (list, options)
868 save_records_to_file(options.file, list, options.fileformat)
871 @register_command("name","")
872 def show(self, options, args):
874 show details about named registry record (Resolve)
880 # explicitly require Resolve to run in details mode
881 record_dicts = self.registry().Resolve(hrn, self.my_credential_string, {'details':True})
882 record_dicts = filter_records(options.type, record_dicts)
884 self.logger.error("No record of type %s"% options.type)
886 # user has required to focus on some keys
888 def project (record):
890 for key in options.keys:
891 try: projected[key]=record[key]
894 record_dicts = [ project (record) for record in record_dicts ]
895 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
896 for record in records:
897 if (options.format == "text"): record.dump(sort=True)
898 else: print record.save_as_xml()
900 save_records_to_file(options.file, record_dicts, options.fileformat)
903 @register_command("[xml-filename]","")
904 def add(self, options, args):
905 """add record into registry (Register)
906 from command line options (recommended)
907 old-school method involving an xml file still supported"""
909 auth_cred = self.my_authority_credential_string()
910 if options.show_credential:
911 show_credentials(auth_cred)
918 record_filepath = args[0]
919 rec_file = self.get_record_file(record_filepath)
920 record_dict.update(load_record_from_file(rec_file).todict())
922 print "Cannot load record file %s"%record_filepath
925 record_dict.update(load_record_from_opts(options).todict())
926 # we should have a type by now
927 if 'type' not in record_dict :
930 # this is still planetlab dependent.. as plc will whine without that
931 # also, it's only for adding
932 if record_dict['type'] == 'user':
933 if not 'first_name' in record_dict:
934 record_dict['first_name'] = record_dict['hrn']
935 if 'last_name' not in record_dict:
936 record_dict['last_name'] = record_dict['hrn']
937 return self.registry().Register(record_dict, auth_cred)
939 @register_command("[xml-filename]","")
940 def update(self, options, args):
941 """update record into registry (Update)
942 from command line options (recommended)
943 old-school method involving an xml file still supported"""
946 record_filepath = args[0]
947 rec_file = self.get_record_file(record_filepath)
948 record_dict.update(load_record_from_file(rec_file).todict())
950 record_dict.update(load_record_from_opts(options).todict())
951 # at the very least we need 'type' here
952 if 'type' not in record_dict:
956 # don't translate into an object, as this would possibly distort
957 # user-provided data; e.g. add an 'email' field to Users
958 if record_dict['type'] == "user":
959 if record_dict['hrn'] == self.user:
960 cred = self.my_credential_string
962 cred = self.my_authority_credential_string()
963 elif record_dict['type'] in ["slice"]:
965 cred = self.slice_credential_string(record_dict['hrn'])
966 except ServerException, e:
967 # XXX smbaker -- once we have better error return codes, update this
968 # to do something better than a string compare
969 if "Permission error" in e.args[0]:
970 cred = self.my_authority_credential_string()
973 elif record_dict['type'] in ["authority"]:
974 cred = self.my_authority_credential_string()
975 elif record_dict['type'] == 'node':
976 cred = self.my_authority_credential_string()
978 raise "unknown record type" + record_dict['type']
979 if options.show_credential:
980 show_credentials(cred)
981 return self.registry().Update(record_dict, cred)
983 @register_command("hrn","")
984 def remove(self, options, args):
985 "remove registry record by name (Remove)"
986 auth_cred = self.my_authority_credential_string()
994 if options.show_credential:
995 show_credentials(auth_cred)
996 return self.registry().Remove(hrn, auth_cred, type)
998 # ==================================================================
999 # Slice-related commands
1000 # ==================================================================
1002 @register_command("","")
1003 def slices(self, options, args):
1004 "list instantiated slices (ListSlices) - returns urn's"
1005 server = self.sliceapi()
1007 creds = [self.my_credential_string]
1008 # options and call_id when supported
1010 api_options['call_id']=unique_call_id()
1011 if options.show_credential:
1012 show_credentials(creds)
1013 result = server.ListSlices(creds, *self.ois(server,api_options))
1014 value = ReturnValue.get_value(result)
1015 if self.options.raw:
1016 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1021 # show rspec for named slice
1022 @register_command("[slice_hrn]","")
1023 def resources(self, options, args):
1025 with no arg, discover available resources, (ListResources)
1026 or with an slice hrn, shows currently provisioned resources
1028 server = self.sliceapi()
1033 the_credential=self.slice_credential_string(args[0])
1034 creds.append(the_credential)
1036 the_credential=self.my_credential_string
1037 creds.append(the_credential)
1038 if options.show_credential:
1039 show_credentials(creds)
1041 # no need to check if server accepts the options argument since the options has
1042 # been a required argument since v1 API
1044 # always send call_id to v2 servers
1045 api_options ['call_id'] = unique_call_id()
1046 # ask for cached value if available
1047 api_options ['cached'] = True
1050 api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
1052 api_options['info'] = options.info
1053 if options.list_leases:
1054 api_options['list_leases'] = options.list_leases
1056 if options.current == True:
1057 api_options['cached'] = False
1059 api_options['cached'] = True
1060 if options.rspec_version:
1061 version_manager = VersionManager()
1062 server_version = self.get_cached_server_version(server)
1063 if 'sfa' in server_version:
1064 # just request the version the client wants
1065 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1067 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1069 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1070 result = server.ListResources (creds, api_options)
1071 value = ReturnValue.get_value(result)
1072 if self.options.raw:
1073 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1074 if options.file is not None:
1075 save_rspec_to_file(value, options.file)
1076 if (self.options.raw is None) and (options.file is None):
1077 display_rspec(value, options.format)
1081 @register_command("slice_hrn rspec","")
1082 def create(self, options, args):
1084 create or update named slice with given rspec (CreateSliver)
1086 server = self.sliceapi()
1088 # xxx do we need to check usage (len(args)) ?
1091 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1094 creds = [self.slice_credential_string(slice_hrn)]
1096 delegated_cred = None
1097 server_version = self.get_cached_server_version(server)
1098 if server_version.get('interface') == 'slicemgr':
1099 # delegate our cred to the slice manager
1100 # do not delegate cred to slicemgr...not working at the moment
1102 #if server_version.get('hrn'):
1103 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1104 #elif server_version.get('urn'):
1105 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1107 if options.show_credential:
1108 show_credentials(creds)
1111 rspec_file = self.get_rspec_file(args[1])
1112 rspec = open(rspec_file).read()
1115 # need to pass along user keys to the aggregate.
1117 # { urn: urn:publicid:IDN+emulab.net+user+alice
1118 # keys: [<ssh key A>, <ssh key B>]
1121 # xxx Thierry 2012 sept. 21
1122 # contrary to what I was first thinking, calling Resolve with details=False does not yet work properly here
1123 # I am turning details=True on again on a - hopefully - temporary basis, just to get this whole thing to work again
1124 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1125 # slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string], {'details':True})
1126 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']:
1127 slice_record = slice_records[0]
1128 user_hrns = slice_record['reg-researchers']
1129 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1130 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1132 if 'sfa' not in server_version:
1133 users = pg_users_arg(user_records)
1134 rspec = RSpec(rspec)
1135 rspec.filter({'component_manager_id': server_version['urn']})
1136 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1138 users = sfa_users_arg(user_records, slice_record)
1140 # do not append users, keys, or slice tags. Anything
1141 # not contained in this request will be removed from the slice
1143 # CreateSliver has supported the options argument for a while now so it should
1144 # be safe to assume this server support it
1146 api_options ['append'] = False
1147 api_options ['call_id'] = unique_call_id()
1148 result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
1149 value = ReturnValue.get_value(result)
1150 if self.options.raw:
1151 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1152 if options.file is not None:
1153 save_rspec_to_file (value, options.file)
1154 if (self.options.raw is None) and (options.file is None):
1159 @register_command("slice_hrn","")
1160 def delete(self, options, args):
1162 delete named slice (DeleteSliver)
1164 server = self.sliceapi()
1168 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1171 slice_cred = self.slice_credential_string(slice_hrn)
1172 creds = [slice_cred]
1174 # options and call_id when supported
1176 api_options ['call_id'] = unique_call_id()
1177 if options.show_credential:
1178 show_credentials(creds)
1179 result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
1180 value = ReturnValue.get_value(result)
1181 if self.options.raw:
1182 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1187 @register_command("slice_hrn","")
1188 def status(self, options, args):
1190 retrieve slice status (SliverStatus)
1192 server = self.sliceapi()
1196 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1199 slice_cred = self.slice_credential_string(slice_hrn)
1200 creds = [slice_cred]
1202 # options and call_id when supported
1204 api_options['call_id']=unique_call_id()
1205 if options.show_credential:
1206 show_credentials(creds)
1207 result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
1208 value = ReturnValue.get_value(result)
1209 if self.options.raw:
1210 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1214 @register_command("slice_hrn","")
1215 def start(self, options, args):
1217 start named slice (Start)
1219 server = self.sliceapi()
1223 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1226 slice_cred = self.slice_credential_string(args[0])
1227 creds = [slice_cred]
1228 # xxx Thierry - does this not need an api_options as well ?
1229 result = server.Start(slice_urn, creds)
1230 value = ReturnValue.get_value(result)
1231 if self.options.raw:
1232 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1237 @register_command("slice_hrn","")
1238 def stop(self, options, args):
1240 stop named slice (Stop)
1242 server = self.sliceapi()
1245 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1247 slice_cred = self.slice_credential_string(args[0])
1248 creds = [slice_cred]
1249 result = server.Stop(slice_urn, creds)
1250 value = ReturnValue.get_value(result)
1251 if self.options.raw:
1252 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1258 @register_command("slice_hrn","")
1259 def reset(self, options, args):
1261 reset named slice (reset_slice)
1263 server = self.sliceapi()
1266 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1268 slice_cred = self.slice_credential_string(args[0])
1269 creds = [slice_cred]
1270 result = server.reset_slice(creds, slice_urn)
1271 value = ReturnValue.get_value(result)
1272 if self.options.raw:
1273 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1278 @register_command("slice_hrn time","")
1279 def renew(self, options, args):
1281 renew slice (RenewSliver)
1283 server = self.sliceapi()
1287 [ slice_hrn, input_time ] = args
1289 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1290 # time: don't try to be smart on the time format, server-side will
1292 slice_cred = self.slice_credential_string(args[0])
1293 creds = [slice_cred]
1294 # options and call_id when supported
1296 api_options['call_id']=unique_call_id()
1297 if options.show_credential:
1298 show_credentials(creds)
1299 result = server.RenewSliver(slice_urn, creds, input_time, *self.ois(server,api_options))
1300 value = ReturnValue.get_value(result)
1301 if self.options.raw:
1302 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1308 @register_command("slice_hrn","")
1309 def shutdown(self, options, args):
1311 shutdown named slice (Shutdown)
1313 server = self.sliceapi()
1316 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1318 slice_cred = self.slice_credential_string(slice_hrn)
1319 creds = [slice_cred]
1320 result = server.Shutdown(slice_urn, creds)
1321 value = ReturnValue.get_value(result)
1322 if self.options.raw:
1323 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1329 @register_command("slice_hrn rspec","")
1330 def get_ticket(self, options, args):
1332 get a ticket for the specified slice
1334 server = self.sliceapi()
1336 slice_hrn, rspec_path = args[0], args[1]
1337 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1339 slice_cred = self.slice_credential_string(slice_hrn)
1340 creds = [slice_cred]
1342 rspec_file = self.get_rspec_file(rspec_path)
1343 rspec = open(rspec_file).read()
1344 # options and call_id when supported
1346 api_options['call_id']=unique_call_id()
1347 # get ticket at the server
1348 ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1350 file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1351 self.logger.info("writing ticket to %s"%file)
1352 ticket = SfaTicket(string=ticket_string)
1353 ticket.save_to_file(filename=file, save_parents=True)
1355 @register_command("ticket","")
1356 def redeem_ticket(self, options, args):
1358 Connects to nodes in a slice and redeems a ticket
1359 (slice hrn is retrieved from the ticket)
1361 ticket_file = args[0]
1363 # get slice hrn from the ticket
1364 # use this to get the right slice credential
1365 ticket = SfaTicket(filename=ticket_file)
1367 ticket_string = ticket.save_to_string(save_parents=True)
1369 slice_hrn = ticket.gidObject.get_hrn()
1370 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1371 #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1372 slice_cred = self.slice_credential_string(slice_hrn)
1374 # get a list of node hostnames from the RSpec
1375 tree = etree.parse(StringIO(ticket.rspec))
1376 root = tree.getroot()
1377 hostnames = root.xpath("./network/site/node/hostname/text()")
1379 # create an xmlrpc connection to the component manager at each of these
1380 # components and gall redeem_ticket
1382 for hostname in hostnames:
1384 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1385 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1386 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1387 server = self.server_proxy(hostname, CM_PORT, self.private_key,
1388 timeout=self.options.timeout, verbose=self.options.debug)
1389 server.RedeemTicket(ticket_string, slice_cred)
1390 self.logger.info("Success")
1391 except socket.gaierror:
1392 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1393 except Exception, e:
1394 self.logger.log_exc(e.message)
1397 @register_command("[name]","")
1398 def gid(self, options, args):
1400 Create a GID (CreateGid)
1405 target_hrn = args[0]
1406 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1407 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1409 filename = options.file
1411 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1412 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1413 GID(string=gid).save_to_file(filename)
1415 ####################
1416 @register_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1418 will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1419 the set of credentials in the scope for this call would be
1420 (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1422 (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1424 (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1425 (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1426 because of the two -s options
1429 def delegate (self, options, args):
1431 (locally) create delegate credential for use by given hrn
1432 make sure to check for 'sfi myslice' instead if you plan
1439 # support for several delegations in the same call
1440 # so first we gather the things to do
1442 for slice_hrn in options.delegate_slices:
1443 message="%s.slice"%slice_hrn
1444 original = self.slice_credential_string(slice_hrn)
1445 tuples.append ( (message, original,) )
1446 if options.delegate_pi:
1447 my_authority=self.authority
1448 message="%s.pi"%my_authority
1449 original = self.my_authority_credential_string()
1450 tuples.append ( (message, original,) )
1451 for auth_hrn in options.delegate_auths:
1452 message="%s.auth"%auth_hrn
1453 original=self.authority_credential_string(auth_hrn)
1454 tuples.append ( (message, original, ) )
1455 # if nothing was specified at all at this point, let's assume -u
1456 if not tuples: options.delegate_user=True
1458 if options.delegate_user:
1459 message="%s.user"%self.user
1460 original = self.my_credential_string
1461 tuples.append ( (message, original, ) )
1463 # default type for beneficial is user unless -A
1464 if options.delegate_to_authority: to_type='authority'
1465 else: to_type='user'
1467 # let's now handle all this
1468 # it's all in the filenaming scheme
1469 for (message,original) in tuples:
1470 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1471 delegated_credential = Credential (string=delegated_string)
1472 filename = os.path.join ( self.options.sfi_dir,
1473 "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1474 delegated_credential.save_to_file(filename, save_parents=True)
1475 self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1477 ####################
1478 @register_command("","""$ less +/myslice sfi_config
1480 backend = http://manifold.pl.sophia.inria.fr:7080
1481 # the HRN that myslice uses, so that we are delegating to
1482 delegate = ple.upmc.slicebrowser
1483 # platform - this is a myslice concept
1485 # username - as of this writing (May 2013) a simple login name
1489 will first collect the slices that you are part of, then make sure
1490 all your credentials are up-to-date (read: refresh expired ones)
1491 then compute delegated credentials for user 'ple.upmc.slicebrowser'
1492 and upload them all on myslice backend, using 'platform' and 'user'.
1493 A password will be prompted for the upload part.
1495 $ sfi -v myslice -- or sfi -vv myslice
1496 same but with more and more verbosity
1499 is synonym to sfi myslice as no other command starts with an 'm'
1501 ) # register_command
1502 def myslice (self, options, args):
1504 """ This helper is for refreshing your credentials at myslice; it will
1505 * compute all the slices that you currently have credentials on
1506 * refresh all your credentials (you as a user and pi, your slices)
1507 * upload them to the manifold backend server
1508 for last phase, sfi_config is read to look for the [myslice] section,
1509 and namely the 'backend', 'delegate' and 'user' settings"""
1516 ### the rough sketch goes like this
1517 # (a) rain check for sufficient config in sfi_config
1518 # we don't allow to override these settings for now
1520 myslice_keys=['backend', 'delegate', 'platform', 'username']
1521 for key in myslice_keys:
1522 full_key="MYSLICE_" + key.upper()
1523 value=getattr(self.config_instance,full_key,None)
1524 if value: myslice_dict[key]=value
1525 else: print "Unsufficient config, missing key %s in [myslice] section of sfi_config"%key
1526 if len(myslice_dict) != len(myslice_keys):
1529 # (b) figure whether we are PI for the authority where we belong
1530 self.logger.info("Resolving our own id")
1531 my_records=self.registry().Resolve(self.user,self.my_credential_string)
1532 if len(my_records)!=1: print "Cannot Resolve %s -- exiting"%self.user; sys.exit(1)
1533 my_record=my_records[0]
1534 my_auths = my_record['reg-pi-authorities']
1535 self.logger.info("Found %d authorities that we are PI for"%len(my_auths))
1536 self.logger.debug("They are %s"%(my_auths))
1538 # (c) get the set of slices that we are in
1539 my_slices=my_record['reg-slices']
1540 self.logger.info("Found %d slices that we are member of"%len(my_slices))
1541 self.logger.debug("They are: %s"%(my_slices))
1543 # (d) make sure we have *valid* credentials for all these
1545 hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1546 for auth_hrn in my_auths:
1547 hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1548 for slice_hrn in my_slices:
1549 hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1551 # (e) check for the delegated version of these
1552 # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever
1553 # switch to myslice using an authority instead of a user
1554 delegatee_type='user'
1555 delegatee_hrn=myslice_dict['delegate']
1556 hrn_delegated_credentials = []
1557 for (hrn, htype, credential) in hrn_credentials:
1558 delegated_credential = self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type)
1559 hrn_delegated_credentials.append ((hrn, htype, delegated_credential, ))
1561 # (f) and finally upload them to manifold server
1562 # xxx todo add an option so the password can be set on the command line
1563 # (but *NOT* in the config file) so other apps can leverage this
1564 uploader = ManifoldUploader (logger=self.logger,
1565 url=myslice_dict['backend'],
1566 platform=myslice_dict['platform'],
1567 username=myslice_dict['username'],
1568 password=options.password)
1569 uploader.prompt_all()
1570 (count_all,count_success)=(0,0)
1571 for (hrn,htype,delegated_credential) in hrn_delegated_credentials:
1573 inspect=Credential(string=delegated_credential)
1574 expire_datetime=inspect.get_expiration()
1575 message="%s (%s) [exp:%s]"%(hrn,htype,expire_datetime)
1576 if uploader.upload(delegated_credential,message=message):
1580 self.logger.info("Successfully uploaded %d/%d credentials"%(count_success,count_all))
1581 # at first I thought we would want to save these,
1582 # like 'sfi delegate does' but on second thought
1583 # it is probably not helpful as people would not
1584 # need to run 'sfi delegate' at all anymore
1585 if count_success != count_all: sys.exit(1)
1588 # Thierry: I'm turning this off as a command, no idea what it's used for
1589 # @register_command("cred","")
1590 def trusted(self, options, args):
1592 return the trusted certs at this interface (get_trusted_certs)
1594 trusted_certs = self.registry().get_trusted_certs()
1595 for trusted_cert in trusted_certs:
1596 gid = GID(string=trusted_cert)
1598 cert = Certificate(string=trusted_cert)
1599 self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())