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_global_parser().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_command_parser(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_command_parser(self, command):
318 if command not in commands_dict:
319 msg="Invalid command\n"
321 msg += ','.join(commands_list)
322 self.logger.critical(msg)
325 # retrieve args_string
326 (_, args_string, __) = commands_dict[command]
328 parser = OptionParser(add_help_option=False,
329 usage="sfi [sfi_options] %s [cmd_options] %s"
330 % (command, args_string))
331 parser.add_option ("-h","--help",dest='help',action='store_true',default=False,
332 help="Summary of one command usage")
334 if command in ("add", "update"):
335 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
336 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
337 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
338 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
340 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
341 default='', type="str", action='callback', callback=optparse_listvalue_callback)
342 parser.add_option('-r', '--researchers', dest='researchers', metavar='<researchers>',
343 help='Set/replace slice researchers', default='', type="str", action='callback',
344 callback=optparse_listvalue_callback)
345 parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
346 default='', type="str", action='callback', callback=optparse_listvalue_callback)
347 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
348 action="callback", callback=optparse_dictvalue_callback, nargs=1,
349 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
351 # show_credential option
352 if command in ("list","resources","create","add","update","remove","slices","delete","status","renew"):
353 parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
354 help="show credential(s) used in human-readable form")
355 # registy filter option
356 if command in ("list", "show", "remove"):
357 parser.add_option("-t", "--type", dest="type", type="choice",
358 help="type filter ([all]|user|slice|authority|node|aggregate)",
359 choices=("all", "user", "slice", "authority", "node", "aggregate"),
361 if command in ("show"):
362 parser.add_option("-k","--key",dest="keys",action="append",default=[],
363 help="specify specific keys to be displayed from record")
364 if command in ("resources"):
366 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
367 help="schema type and version of resulting RSpec")
368 # disable/enable cached rspecs
369 parser.add_option("-c", "--current", dest="current", default=False,
371 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
373 parser.add_option("-f", "--format", dest="format", type="choice",
374 help="display format ([xml]|dns|ip)", default="xml",
375 choices=("xml", "dns", "ip"))
376 #panos: a new option to define the type of information about resources a user is interested in
377 parser.add_option("-i", "--info", dest="info",
378 help="optional component information", default=None)
379 # a new option to retreive or not reservation-oriented RSpecs (leases)
380 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
381 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
382 choices=("all", "resources", "leases"), default="resources")
385 # 'create' does return the new rspec, makes sense to save that too
386 if command in ("resources", "show", "list", "gid", 'create'):
387 parser.add_option("-o", "--output", dest="file",
388 help="output XML to file", metavar="FILE", default=None)
390 if command in ("show", "list"):
391 parser.add_option("-f", "--format", dest="format", type="choice",
392 help="display format ([text]|xml)", default="text",
393 choices=("text", "xml"))
395 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
396 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
397 choices=("xml", "xmllist", "hrnlist"))
398 if command == 'list':
399 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
400 help="list all child records", default=False)
401 parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
402 help="gives details, like user keys", default=False)
403 if command in ("delegate"):
404 parser.add_option("-u", "--user",
405 action="store_true", dest="delegate_user", default=False,
406 help="delegate your own credentials; default if no other option is provided")
407 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
408 metavar="slice_hrn", help="delegate cred. for slice HRN")
409 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
410 metavar='auth_hrn', help="delegate cred for auth HRN")
411 # this primarily is a shorthand for -a my_hrn
412 parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
413 help="delegate your PI credentials, so s.t. like -a your_hrn^")
414 parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
415 help="""by default the mandatory argument is expected to be a user,
416 use this if you mean an authority instead""")
418 if command in ("version"):
419 parser.add_option("-R","--registry-version",
420 action="store_true", dest="version_registry", default=False,
421 help="probe registry version instead of sliceapi")
422 parser.add_option("-l","--local",
423 action="store_true", dest="version_local", default=False,
424 help="display version of the local client")
429 def create_parser(self):
431 # Generate command line parser
432 parser = OptionParser(add_help_option=False,
433 usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
434 description="Commands: %s"%(" ".join(commands_list)))
435 parser.add_option("-r", "--registry", dest="registry",
436 help="root registry", metavar="URL", default=None)
437 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
438 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
439 parser.add_option("-R", "--raw", dest="raw", default=None,
440 help="Save raw, unparsed server response to a file")
441 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
442 help="raw file format ([text]|pickled|json)", default="text",
443 choices=("text","pickled","json"))
444 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
445 help="text string to write before and after raw output")
446 parser.add_option("-d", "--dir", dest="sfi_dir",
447 help="config & working directory - default is %default",
448 metavar="PATH", default=Sfi.default_sfi_dir())
449 parser.add_option("-u", "--user", dest="user",
450 help="user name", metavar="HRN", default=None)
451 parser.add_option("-a", "--auth", dest="auth",
452 help="authority name", metavar="HRN", default=None)
453 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
454 help="verbose mode - cumulative")
455 parser.add_option("-D", "--debug",
456 action="store_true", dest="debug", default=False,
457 help="Debug (xml-rpc) protocol messages")
458 # would it make sense to use ~/.ssh/id_rsa as a default here ?
459 parser.add_option("-k", "--private-key",
460 action="store", dest="user_private_key", default=None,
461 help="point to the private key file to use if not yet installed in sfi_dir")
462 parser.add_option("-t", "--timeout", dest="timeout", default=None,
463 help="Amout of time to wait before timing out the request")
464 parser.add_option("-h", "--help",
465 action="store_true", dest="help", default=False,
466 help="one page summary on commands & exit")
467 parser.disable_interspersed_args()
473 # Main: parse arguments and dispatch to command
475 def dispatch(self, command, command_options, command_args):
476 method=getattr(self, command, None)
478 print "Unknown command %s"%command
480 return method(command_options, command_args)
483 self.sfi_parser = self.create_parser()
484 (options, args) = self.sfi_parser.parse_args()
486 self.print_commands_help(options)
488 self.options = options
490 self.logger.setLevelFromOptVerbose(self.options.verbose)
493 self.logger.critical("No command given. Use -h for help.")
494 self.print_commands_help(options)
497 # complete / find unique match with command set
498 command_candidates = Candidates (commands_list)
500 command = command_candidates.only_match(input)
502 self.print_commands_help(options)
504 # second pass options parsing
506 self.command_parser = self.create_command_parser(command)
507 (command_options, command_args) = self.command_parser.parse_args(args[1:])
508 if command_options.help:
511 self.command_options = command_options
515 self.logger.debug("Command=%s" % self.command)
518 self.dispatch(command, command_options, command_args)
522 self.logger.log_exc ("sfi command %s failed"%command)
528 def read_config(self):
529 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
530 shell_config_file = os.path.join(self.options.sfi_dir,"sfi_config.sh")
532 if Config.is_ini(config_file):
533 config = Config (config_file)
535 # try upgrading from shell config format
536 fp, fn = mkstemp(suffix='sfi_config', text=True)
538 # we need to preload the sections we want parsed
539 # from the shell config
540 config.add_section('sfi')
541 # sface users should be able to use this same file to configure their stuff
542 config.add_section('sface')
543 # manifold users should be able to specify the details
544 # of their backend server here for 'sfi myslice'
545 config.add_section('myslice')
546 config.load(config_file)
548 shutil.move(config_file, shell_config_file)
550 config.save(config_file)
553 self.logger.critical("Failed to read configuration file %s"%config_file)
554 self.logger.info("Make sure to remove the export clauses and to add quotes")
555 if self.options.verbose==0:
556 self.logger.info("Re-run with -v for more details")
558 self.logger.log_exc("Could not read config file %s"%config_file)
561 self.config_instance=config
564 if (self.options.sm is not None):
565 self.sm_url = self.options.sm
566 elif hasattr(config, "SFI_SM"):
567 self.sm_url = config.SFI_SM
569 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
573 if (self.options.registry is not None):
574 self.reg_url = self.options.registry
575 elif hasattr(config, "SFI_REGISTRY"):
576 self.reg_url = config.SFI_REGISTRY
578 self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
582 if (self.options.user is not None):
583 self.user = self.options.user
584 elif hasattr(config, "SFI_USER"):
585 self.user = config.SFI_USER
587 self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
591 if (self.options.auth is not None):
592 self.authority = self.options.auth
593 elif hasattr(config, "SFI_AUTH"):
594 self.authority = config.SFI_AUTH
596 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
599 self.config_file=config_file
603 def show_config (self):
604 print "From configuration file %s"%self.config_file
607 ('SFI_AUTH','authority'),
609 ('SFI_REGISTRY','reg_url'),
611 for (external_name, internal_name) in flags:
612 print "%s='%s'"%(external_name,getattr(self,internal_name))
615 # Get various credential and spec files
617 # Establishes limiting conventions
618 # - conflates MAs and SAs
619 # - assumes last token in slice name is unique
621 # Bootstraps credentials
622 # - bootstrap user credential from self-signed certificate
623 # - bootstrap authority credential from user credential
624 # - bootstrap slice credential from user credential
627 # init self-signed cert, user credentials and gid
628 def bootstrap (self):
629 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
631 # if -k is provided, use this to initialize private key
632 if self.options.user_private_key:
633 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
635 # trigger legacy compat code if needed
636 # the name has changed from just <leaf>.pkey to <hrn>.pkey
637 if not os.path.isfile(client_bootstrap.private_key_filename()):
638 self.logger.info ("private key not found, trying legacy name")
640 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
641 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
642 client_bootstrap.init_private_key_if_missing (legacy_private_key)
643 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
645 self.logger.log_exc("Can't find private key ")
649 client_bootstrap.bootstrap_my_gid()
650 # extract what's needed
651 self.private_key = client_bootstrap.private_key()
652 self.my_credential_string = client_bootstrap.my_credential_string ()
653 self.my_gid = client_bootstrap.my_gid ()
654 self.client_bootstrap = client_bootstrap
657 def my_authority_credential_string(self):
658 if not self.authority:
659 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
661 return self.client_bootstrap.authority_credential_string (self.authority)
663 def authority_credential_string(self, auth_hrn):
664 return self.client_bootstrap.authority_credential_string (auth_hrn)
666 def slice_credential_string(self, name):
667 return self.client_bootstrap.slice_credential_string (name)
670 # Management of the servers
675 if not hasattr (self, 'registry_proxy'):
676 self.logger.info("Contacting Registry at: %s"%self.reg_url)
677 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
678 timeout=self.options.timeout, verbose=self.options.debug)
679 return self.registry_proxy
683 if not hasattr (self, 'sliceapi_proxy'):
684 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
685 if hasattr(self.command_options,'component') and self.command_options.component:
686 # resolve the hrn at the registry
687 node_hrn = self.command_options.component
688 records = self.registry().Resolve(node_hrn, self.my_credential_string)
689 records = filter_records('node', records)
691 self.logger.warning("No such component:%r"% opts.component)
693 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
694 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
696 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
697 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
698 self.sm_url = 'http://' + self.sm_url
699 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
700 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
701 timeout=self.options.timeout, verbose=self.options.debug)
702 return self.sliceapi_proxy
704 def get_cached_server_version(self, server):
705 # check local cache first
708 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
709 cache_key = server.url + "-version"
711 cache = Cache(cache_file)
714 self.logger.info("Local cache not found at: %s" % cache_file)
717 version = cache.get(cache_key)
720 result = server.GetVersion()
721 version= ReturnValue.get_value(result)
722 # cache version for 20 minutes
723 cache.add(cache_key, version, ttl= 60*20)
724 self.logger.info("Updating cache file %s" % cache_file)
725 cache.save_to_file(cache_file)
729 ### resurrect this temporarily so we can support V1 aggregates for a while
730 def server_supports_options_arg(self, server):
732 Returns true if server support the optional call_id arg, false otherwise.
734 server_version = self.get_cached_server_version(server)
736 # xxx need to rewrite this
737 if int(server_version.get('geni_api')) >= 2:
741 def server_supports_call_id_arg(self, server):
742 server_version = self.get_cached_server_version(server)
744 if 'sfa' in server_version and 'code_tag' in server_version:
745 code_tag = server_version['code_tag']
746 code_tag_parts = code_tag.split("-")
747 version_parts = code_tag_parts[0].split(".")
748 major, minor = version_parts[0], version_parts[1]
749 rev = code_tag_parts[1]
750 if int(major) == 1 and minor == 0 and build >= 22:
754 ### ois = options if supported
755 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
756 def ois (self, server, option_dict):
757 if self.server_supports_options_arg (server):
759 elif self.server_supports_call_id_arg (server):
760 return [ unique_call_id () ]
764 ### cis = call_id if supported - like ois
765 def cis (self, server):
766 if self.server_supports_call_id_arg (server):
767 return [ unique_call_id ]
771 ######################################## miscell utilities
772 def get_rspec_file(self, rspec):
773 if (os.path.isabs(rspec)):
776 file = os.path.join(self.options.sfi_dir, rspec)
777 if (os.path.isfile(file)):
780 self.logger.critical("No such rspec file %s"%rspec)
783 def get_record_file(self, record):
784 if (os.path.isabs(record)):
787 file = os.path.join(self.options.sfi_dir, record)
788 if (os.path.isfile(file)):
791 self.logger.critical("No such registry record file %s"%record)
795 #==========================================================================
796 # Following functions implement the commands
798 # Registry-related commands
799 #==========================================================================
801 @register_command("","")
802 def config (self, options, args):
803 "Display contents of current config"
806 @register_command("","")
807 def version(self, options, args):
809 display an SFA server version (GetVersion)
810 or version information about sfi itself
812 if options.version_local:
813 version=version_core()
815 if options.version_registry:
816 server=self.registry()
818 server = self.sliceapi()
819 result = server.GetVersion()
820 version = ReturnValue.get_value(result)
822 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
824 pprinter = PrettyPrinter(indent=4)
825 pprinter.pprint(version)
827 @register_command("authority","")
828 def list(self, options, args):
830 list entries in named authority registry (List)
837 if options.recursive:
838 opts['recursive'] = options.recursive
840 if options.show_credential:
841 show_credentials(self.my_credential_string)
843 list = self.registry().List(hrn, self.my_credential_string, options)
845 raise Exception, "Not enough parameters for the 'list' command"
847 # filter on person, slice, site, node, etc.
848 # This really should be in the self.filter_records funct def comment...
849 list = filter_records(options.type, list)
850 terminal_render (list, options)
852 save_records_to_file(options.file, list, options.fileformat)
855 @register_command("name","")
856 def show(self, options, args):
858 show details about named registry record (Resolve)
864 # explicitly require Resolve to run in details mode
865 record_dicts = self.registry().Resolve(hrn, self.my_credential_string, {'details':True})
866 record_dicts = filter_records(options.type, record_dicts)
868 self.logger.error("No record of type %s"% options.type)
870 # user has required to focus on some keys
872 def project (record):
874 for key in options.keys:
875 try: projected[key]=record[key]
878 record_dicts = [ project (record) for record in record_dicts ]
879 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
880 for record in records:
881 if (options.format == "text"): record.dump(sort=True)
882 else: print record.save_as_xml()
884 save_records_to_file(options.file, record_dicts, options.fileformat)
887 @register_command("[xml-filename]","")
888 def add(self, options, args):
889 """add record into registry (Register)
890 from command line options (recommended)
891 old-school method involving an xml file still supported"""
893 auth_cred = self.my_authority_credential_string()
894 if options.show_credential:
895 show_credentials(auth_cred)
902 record_filepath = args[0]
903 rec_file = self.get_record_file(record_filepath)
904 record_dict.update(load_record_from_file(rec_file).todict())
906 print "Cannot load record file %s"%record_filepath
909 record_dict.update(load_record_from_opts(options).todict())
910 # we should have a type by now
911 if 'type' not in record_dict :
914 # this is still planetlab dependent.. as plc will whine without that
915 # also, it's only for adding
916 if record_dict['type'] == 'user':
917 if not 'first_name' in record_dict:
918 record_dict['first_name'] = record_dict['hrn']
919 if 'last_name' not in record_dict:
920 record_dict['last_name'] = record_dict['hrn']
921 return self.registry().Register(record_dict, auth_cred)
923 @register_command("[xml-filename]","")
924 def update(self, options, args):
925 """update record into registry (Update)
926 from command line options (recommended)
927 old-school method involving an xml file still supported"""
930 record_filepath = args[0]
931 rec_file = self.get_record_file(record_filepath)
932 record_dict.update(load_record_from_file(rec_file).todict())
934 record_dict.update(load_record_from_opts(options).todict())
935 # at the very least we need 'type' here
936 if 'type' not in record_dict:
940 # don't translate into an object, as this would possibly distort
941 # user-provided data; e.g. add an 'email' field to Users
942 if record_dict['type'] == "user":
943 if record_dict['hrn'] == self.user:
944 cred = self.my_credential_string
946 cred = self.my_authority_credential_string()
947 elif record_dict['type'] in ["slice"]:
949 cred = self.slice_credential_string(record_dict['hrn'])
950 except ServerException, e:
951 # XXX smbaker -- once we have better error return codes, update this
952 # to do something better than a string compare
953 if "Permission error" in e.args[0]:
954 cred = self.my_authority_credential_string()
957 elif record_dict['type'] in ["authority"]:
958 cred = self.my_authority_credential_string()
959 elif record_dict['type'] == 'node':
960 cred = self.my_authority_credential_string()
962 raise "unknown record type" + record_dict['type']
963 if options.show_credential:
964 show_credentials(cred)
965 return self.registry().Update(record_dict, cred)
967 @register_command("name","")
968 def remove(self, options, args):
969 "remove registry record by name (Remove)"
970 auth_cred = self.my_authority_credential_string()
978 if options.show_credential:
979 show_credentials(auth_cred)
980 return self.registry().Remove(hrn, auth_cred, type)
982 # ==================================================================
983 # Slice-related commands
984 # ==================================================================
986 @register_command("","")
987 def slices(self, options, args):
988 "list instantiated slices (ListSlices) - returns urn's"
989 server = self.sliceapi()
991 creds = [self.my_credential_string]
992 # options and call_id when supported
994 api_options['call_id']=unique_call_id()
995 if options.show_credential:
996 show_credentials(creds)
997 result = server.ListSlices(creds, *self.ois(server,api_options))
998 value = ReturnValue.get_value(result)
1000 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1005 # show rspec for named slice
1006 @register_command("[slice_hrn]","")
1007 def resources(self, options, args):
1009 with no arg, discover available resources, (ListResources)
1010 or with an slice hrn, shows currently provisioned resources
1012 server = self.sliceapi()
1017 the_credential=self.slice_credential_string(args[0])
1018 creds.append(the_credential)
1020 the_credential=self.my_credential_string
1021 creds.append(the_credential)
1022 if options.show_credential:
1023 show_credentials(creds)
1025 # no need to check if server accepts the options argument since the options has
1026 # been a required argument since v1 API
1028 # always send call_id to v2 servers
1029 api_options ['call_id'] = unique_call_id()
1030 # ask for cached value if available
1031 api_options ['cached'] = True
1034 api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
1036 api_options['info'] = options.info
1037 if options.list_leases:
1038 api_options['list_leases'] = options.list_leases
1040 if options.current == True:
1041 api_options['cached'] = False
1043 api_options['cached'] = True
1044 if options.rspec_version:
1045 version_manager = VersionManager()
1046 server_version = self.get_cached_server_version(server)
1047 if 'sfa' in server_version:
1048 # just request the version the client wants
1049 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1051 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1053 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1054 result = server.ListResources (creds, api_options)
1055 value = ReturnValue.get_value(result)
1056 if self.options.raw:
1057 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1058 if options.file is not None:
1059 save_rspec_to_file(value, options.file)
1060 if (self.options.raw is None) and (options.file is None):
1061 display_rspec(value, options.format)
1065 @register_command("slice_hrn rspec","")
1066 def create(self, options, args):
1068 create or update named slice with given rspec (CreateSliver)
1070 server = self.sliceapi()
1072 # xxx do we need to check usage (len(args)) ?
1075 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1078 creds = [self.slice_credential_string(slice_hrn)]
1080 delegated_cred = None
1081 server_version = self.get_cached_server_version(server)
1082 if server_version.get('interface') == 'slicemgr':
1083 # delegate our cred to the slice manager
1084 # do not delegate cred to slicemgr...not working at the moment
1086 #if server_version.get('hrn'):
1087 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1088 #elif server_version.get('urn'):
1089 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1091 if options.show_credential:
1092 show_credentials(creds)
1095 rspec_file = self.get_rspec_file(args[1])
1096 rspec = open(rspec_file).read()
1099 # need to pass along user keys to the aggregate.
1101 # { urn: urn:publicid:IDN+emulab.net+user+alice
1102 # keys: [<ssh key A>, <ssh key B>]
1105 # xxx Thierry 2012 sept. 21
1106 # contrary to what I was first thinking, calling Resolve with details=False does not yet work properly here
1107 # I am turning details=True on again on a - hopefully - temporary basis, just to get this whole thing to work again
1108 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1109 # slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string], {'details':True})
1110 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']:
1111 slice_record = slice_records[0]
1112 user_hrns = slice_record['reg-researchers']
1113 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1114 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1116 if 'sfa' not in server_version:
1117 users = pg_users_arg(user_records)
1118 rspec = RSpec(rspec)
1119 rspec.filter({'component_manager_id': server_version['urn']})
1120 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1122 users = sfa_users_arg(user_records, slice_record)
1124 # do not append users, keys, or slice tags. Anything
1125 # not contained in this request will be removed from the slice
1127 # CreateSliver has supported the options argument for a while now so it should
1128 # be safe to assume this server support it
1130 api_options ['append'] = False
1131 api_options ['call_id'] = unique_call_id()
1132 result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
1133 value = ReturnValue.get_value(result)
1134 if self.options.raw:
1135 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1136 if options.file is not None:
1137 save_rspec_to_file (value, options.file)
1138 if (self.options.raw is None) and (options.file is None):
1143 @register_command("slice_hrn","")
1144 def delete(self, options, args):
1146 delete named slice (DeleteSliver)
1148 server = self.sliceapi()
1152 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1155 slice_cred = self.slice_credential_string(slice_hrn)
1156 creds = [slice_cred]
1158 # options and call_id when supported
1160 api_options ['call_id'] = unique_call_id()
1161 if options.show_credential:
1162 show_credentials(creds)
1163 result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
1164 value = ReturnValue.get_value(result)
1165 if self.options.raw:
1166 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1171 @register_command("slice_hrn","")
1172 def status(self, options, args):
1174 retrieve slice status (SliverStatus)
1176 server = self.sliceapi()
1180 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1183 slice_cred = self.slice_credential_string(slice_hrn)
1184 creds = [slice_cred]
1186 # options and call_id when supported
1188 api_options['call_id']=unique_call_id()
1189 if options.show_credential:
1190 show_credentials(creds)
1191 result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
1192 value = ReturnValue.get_value(result)
1193 if self.options.raw:
1194 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1198 @register_command("slice_hrn","")
1199 def start(self, options, args):
1201 start named slice (Start)
1203 server = self.sliceapi()
1207 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1210 slice_cred = self.slice_credential_string(args[0])
1211 creds = [slice_cred]
1212 # xxx Thierry - does this not need an api_options as well ?
1213 result = server.Start(slice_urn, creds)
1214 value = ReturnValue.get_value(result)
1215 if self.options.raw:
1216 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1221 @register_command("slice_hrn","")
1222 def stop(self, options, args):
1224 stop named slice (Stop)
1226 server = self.sliceapi()
1229 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1231 slice_cred = self.slice_credential_string(args[0])
1232 creds = [slice_cred]
1233 result = server.Stop(slice_urn, creds)
1234 value = ReturnValue.get_value(result)
1235 if self.options.raw:
1236 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1242 @register_command("slice_hrn","")
1243 def reset(self, options, args):
1245 reset named slice (reset_slice)
1247 server = self.sliceapi()
1250 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1252 slice_cred = self.slice_credential_string(args[0])
1253 creds = [slice_cred]
1254 result = server.reset_slice(creds, slice_urn)
1255 value = ReturnValue.get_value(result)
1256 if self.options.raw:
1257 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1262 @register_command("slice_hrn time","")
1263 def renew(self, options, args):
1265 renew slice (RenewSliver)
1267 server = self.sliceapi()
1271 [ slice_hrn, input_time ] = args
1273 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1274 # time: don't try to be smart on the time format, server-side will
1276 slice_cred = self.slice_credential_string(args[0])
1277 creds = [slice_cred]
1278 # options and call_id when supported
1280 api_options['call_id']=unique_call_id()
1281 if options.show_credential:
1282 show_credentials(creds)
1283 result = server.RenewSliver(slice_urn, creds, input_time, *self.ois(server,api_options))
1284 value = ReturnValue.get_value(result)
1285 if self.options.raw:
1286 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1292 @register_command("slice_hrn","")
1293 def shutdown(self, options, args):
1295 shutdown named slice (Shutdown)
1297 server = self.sliceapi()
1300 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1302 slice_cred = self.slice_credential_string(slice_hrn)
1303 creds = [slice_cred]
1304 result = server.Shutdown(slice_urn, creds)
1305 value = ReturnValue.get_value(result)
1306 if self.options.raw:
1307 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1313 @register_command("slice_hrn rspec","")
1314 def get_ticket(self, options, args):
1316 get a ticket for the specified slice
1318 server = self.sliceapi()
1320 slice_hrn, rspec_path = args[0], args[1]
1321 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1323 slice_cred = self.slice_credential_string(slice_hrn)
1324 creds = [slice_cred]
1326 rspec_file = self.get_rspec_file(rspec_path)
1327 rspec = open(rspec_file).read()
1328 # options and call_id when supported
1330 api_options['call_id']=unique_call_id()
1331 # get ticket at the server
1332 ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1334 file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1335 self.logger.info("writing ticket to %s"%file)
1336 ticket = SfaTicket(string=ticket_string)
1337 ticket.save_to_file(filename=file, save_parents=True)
1339 @register_command("ticket","")
1340 def redeem_ticket(self, options, args):
1342 Connects to nodes in a slice and redeems a ticket
1343 (slice hrn is retrieved from the ticket)
1345 ticket_file = args[0]
1347 # get slice hrn from the ticket
1348 # use this to get the right slice credential
1349 ticket = SfaTicket(filename=ticket_file)
1351 ticket_string = ticket.save_to_string(save_parents=True)
1353 slice_hrn = ticket.gidObject.get_hrn()
1354 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1355 #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1356 slice_cred = self.slice_credential_string(slice_hrn)
1358 # get a list of node hostnames from the RSpec
1359 tree = etree.parse(StringIO(ticket.rspec))
1360 root = tree.getroot()
1361 hostnames = root.xpath("./network/site/node/hostname/text()")
1363 # create an xmlrpc connection to the component manager at each of these
1364 # components and gall redeem_ticket
1366 for hostname in hostnames:
1368 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1369 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1370 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1371 server = self.server_proxy(hostname, CM_PORT, self.private_key,
1372 timeout=self.options.timeout, verbose=self.options.debug)
1373 server.RedeemTicket(ticket_string, slice_cred)
1374 self.logger.info("Success")
1375 except socket.gaierror:
1376 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1377 except Exception, e:
1378 self.logger.log_exc(e.message)
1381 @register_command("[name]","")
1382 def gid(self, options, args):
1384 Create a GID (CreateGid)
1389 target_hrn = args[0]
1390 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1391 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1393 filename = options.file
1395 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1396 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1397 GID(string=gid).save_to_file(filename)
1399 ####################
1400 @register_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1402 will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1403 the set of credentials in the scope for this call would be
1404 (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1406 (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1408 (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1409 (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1410 because of the two -s options
1413 def delegate (self, options, args):
1415 (locally) create delegate credential for use by given hrn
1416 make sure to check for 'sfi myslice' instead if you plan
1423 # support for several delegations in the same call
1424 # so first we gather the things to do
1426 for slice_hrn in options.delegate_slices:
1427 message="%s.slice"%slice_hrn
1428 original = self.slice_credential_string(slice_hrn)
1429 tuples.append ( (message, original,) )
1430 if options.delegate_pi:
1431 my_authority=self.authority
1432 message="%s.pi"%my_authority
1433 original = self.my_authority_credential_string()
1434 tuples.append ( (message, original,) )
1435 for auth_hrn in options.delegate_auths:
1436 message="%s.auth"%auth_hrn
1437 original=self.authority_credential_string(auth_hrn)
1438 tuples.append ( (message, original, ) )
1439 # if nothing was specified at all at this point, let's assume -u
1440 if not tuples: options.delegate_user=True
1442 if options.delegate_user:
1443 message="%s.user"%self.user
1444 original = self.my_credential_string
1445 tuples.append ( (message, original, ) )
1447 # default type for beneficial is user unless -A
1448 if options.delegate_to_authority: to_type='authority'
1449 else: to_type='user'
1451 # let's now handle all this
1452 # it's all in the filenaming scheme
1453 for (message,original) in tuples:
1454 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1455 delegated_credential = Credential (string=delegated_string)
1456 filename = os.path.join ( self.options.sfi_dir,
1457 "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1458 delegated_credential.save_to_file(filename, save_parents=True)
1459 self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1461 ####################
1462 @register_command("","""$ less +/myslice sfi_config
1464 backend = http://manifold.pl.sophia.inria.fr:7080
1465 # the HRN that myslice uses, so that we are delegating to
1466 delegate = ple.upmc.slicebrowser
1467 # platform - this is a myslice concept
1469 # username - as of this writing (May 2013) a simple login name
1473 will first collect the slices that you are part of, then make sure
1474 all your credentials are up-to-date (read: refresh expired ones)
1475 then compute delegated credentials for user 'ple.upmc.slicebrowser'
1476 and upload them all on myslice backend, using 'platform' and 'user'.
1477 A password will be prompted for the upload part.
1479 $ sfi -v myslice -- or sfi -vv myslice
1480 same but with more and more verbosity
1483 is synonym to sfi myslice as no other command starts with an 'm'
1485 ) # register_command
1486 def myslice (self, options, args):
1488 """ This helper is for refreshing your credentials at myslice; it will
1489 * compute all the slices that you currently have credentials on
1490 * refresh all your credentials (you as a user and pi, your slices)
1491 * upload them to the manifold backend server
1492 for last phase, sfi_config is read to look for the [myslice] section,
1493 and namely the 'backend', 'delegate' and 'user' settings"""
1500 ### the rough sketch goes like this
1501 # (a) rain check for sufficient config in sfi_config
1502 # we don't allow to override these settings for now
1504 myslice_keys=['backend', 'delegate', 'platform', 'username']
1505 for key in myslice_keys:
1506 full_key="MYSLICE_" + key.upper()
1507 value=getattr(self.config_instance,full_key,None)
1508 if value: myslice_dict[key]=value
1509 else: print "Unsufficient config, missing key %s in [myslice] section of sfi_config"%key
1510 if len(myslice_dict) != len(myslice_keys):
1513 # (b) figure whether we are PI for the authority where we belong
1514 sfi_logger.info("Resolving our own id")
1515 my_records=self.registry().Resolve(self.user,self.my_credential_string)
1516 if len(my_records)!=1: print "Cannot Resolve %s -- exiting"%self.user; sys.exit(1)
1517 my_record=my_records[0]
1518 sfi_logger.info("Checking for authorities that we are PI for")
1519 my_auths = my_record['reg-pi-authorities']
1520 sfi_logger.debug("Found %d authorities: %s"%(len(my_auths),my_auths))
1522 # (c) get the set of slices that we are in
1523 sfi_logger.info("Checking for slices that we are member of")
1524 my_slices=my_record['reg-slices']
1525 sfi_logger.debug("Found %d slices: %s"%(len(my_slices),my_slices))
1527 # (d) make sure we have *valid* credentials for all these
1529 hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1530 for auth_hrn in my_auths:
1531 hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1532 for slice_hrn in my_slices:
1533 hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1535 # (e) check for the delegated version of these
1536 # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever
1537 # switch to myslice using an authority instead of a user
1538 delegatee_type='user'
1539 delegatee_hrn=myslice_dict['delegate']
1540 hrn_delegated_credentials = [
1541 (hrn, htype, self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type),)
1542 for (hrn, htype, credential) in hrn_credentials ]
1544 # (f) and finally upload them to manifold server
1545 # xxx todo add an option so the password can be set on the command line
1546 # (but *NOT* in the config file) so other apps can leverage this
1547 uploader = ManifoldUploader (logger=sfi_logger,
1548 url=myslice_dict['backend'],
1549 platform=myslice_dict['platform'],
1550 username=myslice_dict['username'])
1551 for (hrn,htype,delegated_credential) in hrn_delegated_credentials:
1552 sfi_logger.info("Uploading delegated credential for %s (%s)"%(hrn,htype))
1553 uploader.upload(delegated_credential,message=hrn)
1554 # at first I thought we would want to save these,
1555 # like 'sfi delegate does' but on second thought
1556 # it is probably not helpful as people would not
1557 # need to run 'sfi delegate' at all anymore
1560 # Thierry: I'm turning this off as a command, no idea what it's used for
1561 # @register_command("cred","")
1562 def trusted(self, options, args):
1564 return the trusted certs at this interface (get_trusted_certs)
1566 trusted_certs = self.registry().get_trusted_certs()
1567 for trusted_cert in trusted_certs:
1568 gid = GID(string=trusted_cert)
1570 cert = Certificate(string=trusted_cert)
1571 self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())