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
279 self.config_file=None
280 self.client_bootstrap=None
282 ### suitable if no reasonable command has been provided
283 def print_commands_help (self, options):
284 verbose=getattr(options,'verbose')
285 format3="%18s %-15s %s"
288 print format3%("command","cmd_args","description")
292 self.create_parser().print_help()
293 # preserve order from the code
294 for command in commands_list:
295 (doc, args_string, example) = commands_dict[command]
298 doc=doc.replace("\n","\n"+35*' ')
299 print format3%(command,args_string,doc)
301 self.create_command_parser(command).print_help()
303 ### now if a known command was found we can be more verbose on that one
304 def print_help (self):
305 print "==================== Generic sfi usage"
306 self.sfi_parser.print_help()
307 (doc,_,example)=commands_dict[self.command]
308 print "\n==================== Purpose of %s"%self.command
310 print "\n==================== Specific usage for %s"%self.command
311 self.command_parser.print_help()
313 print "\n==================== %s example(s)"%self.command
316 def create_command_parser(self, command):
317 if command not in commands_dict:
318 msg="Invalid command\n"
320 msg += ','.join(commands_list)
321 self.logger.critical(msg)
324 # retrieve args_string
325 (_, args_string, __) = commands_dict[command]
327 parser = OptionParser(add_help_option=False,
328 usage="sfi [sfi_options] %s [cmd_options] %s"
329 % (command, args_string))
330 parser.add_option ("-h","--help",dest='help',action='store_true',default=False,
331 help="Summary of one command usage")
333 if command in ("add", "update"):
334 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
335 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
336 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
337 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
339 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
340 default='', type="str", action='callback', callback=optparse_listvalue_callback)
341 parser.add_option('-r', '--researchers', dest='researchers', metavar='<researchers>',
342 help='Set/replace slice researchers', default='', type="str", action='callback',
343 callback=optparse_listvalue_callback)
344 parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
345 default='', type="str", action='callback', callback=optparse_listvalue_callback)
346 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
347 action="callback", callback=optparse_dictvalue_callback, nargs=1,
348 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
350 # show_credential option
351 if command in ("list","resources","create","add","update","remove","slices","delete","status","renew"):
352 parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
353 help="show credential(s) used in human-readable form")
354 # registy filter option
355 if command in ("list", "show", "remove"):
356 parser.add_option("-t", "--type", dest="type", type="choice",
357 help="type filter ([all]|user|slice|authority|node|aggregate)",
358 choices=("all", "user", "slice", "authority", "node", "aggregate"),
360 if command in ("show"):
361 parser.add_option("-k","--key",dest="keys",action="append",default=[],
362 help="specify specific keys to be displayed from record")
363 if command in ("resources"):
365 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
366 help="schema type and version of resulting RSpec")
367 # disable/enable cached rspecs
368 parser.add_option("-c", "--current", dest="current", default=False,
370 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
372 parser.add_option("-f", "--format", dest="format", type="choice",
373 help="display format ([xml]|dns|ip)", default="xml",
374 choices=("xml", "dns", "ip"))
375 #panos: a new option to define the type of information about resources a user is interested in
376 parser.add_option("-i", "--info", dest="info",
377 help="optional component information", default=None)
378 # a new option to retreive or not reservation-oriented RSpecs (leases)
379 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
380 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
381 choices=("all", "resources", "leases"), default="resources")
384 # 'create' does return the new rspec, makes sense to save that too
385 if command in ("resources", "show", "list", "gid", 'create'):
386 parser.add_option("-o", "--output", dest="file",
387 help="output XML to file", metavar="FILE", default=None)
389 if command in ("show", "list"):
390 parser.add_option("-f", "--format", dest="format", type="choice",
391 help="display format ([text]|xml)", default="text",
392 choices=("text", "xml"))
394 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
395 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
396 choices=("xml", "xmllist", "hrnlist"))
397 if command == 'list':
398 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
399 help="list all child records", default=False)
400 parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
401 help="gives details, like user keys", default=False)
402 if command in ("delegate"):
403 parser.add_option("-u", "--user",
404 action="store_true", dest="delegate_user", default=False,
405 help="delegate your own credentials; default if no other option is provided")
406 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
407 metavar="slice_hrn", help="delegate cred. for slice HRN")
408 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
409 metavar='auth_hrn', help="delegate cred for auth HRN")
410 # this primarily is a shorthand for -a my_hrn
411 parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
412 help="delegate your PI credentials, so s.t. like -a your_hrn^")
413 parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
414 help="""by default the mandatory argument is expected to be a user,
415 use this if you mean an authority instead""")
417 if command in ("version"):
418 parser.add_option("-R","--registry-version",
419 action="store_true", dest="version_registry", default=False,
420 help="probe registry version instead of sliceapi")
421 parser.add_option("-l","--local",
422 action="store_true", dest="version_local", default=False,
423 help="display version of the local client")
428 def create_parser(self):
430 # Generate command line parser
431 parser = OptionParser(add_help_option=False,
432 usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
433 description="Commands: %s"%(" ".join(commands_list)))
434 parser.add_option("-r", "--registry", dest="registry",
435 help="root registry", metavar="URL", default=None)
436 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
437 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
438 parser.add_option("-R", "--raw", dest="raw", default=None,
439 help="Save raw, unparsed server response to a file")
440 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
441 help="raw file format ([text]|pickled|json)", default="text",
442 choices=("text","pickled","json"))
443 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
444 help="text string to write before and after raw output")
445 parser.add_option("-d", "--dir", dest="sfi_dir",
446 help="config & working directory - default is %default",
447 metavar="PATH", default=Sfi.default_sfi_dir())
448 parser.add_option("-u", "--user", dest="user",
449 help="user name", metavar="HRN", default=None)
450 parser.add_option("-a", "--auth", dest="auth",
451 help="authority name", metavar="HRN", default=None)
452 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
453 help="verbose mode - cumulative")
454 parser.add_option("-D", "--debug",
455 action="store_true", dest="debug", default=False,
456 help="Debug (xml-rpc) protocol messages")
457 # would it make sense to use ~/.ssh/id_rsa as a default here ?
458 parser.add_option("-k", "--private-key",
459 action="store", dest="user_private_key", default=None,
460 help="point to the private key file to use if not yet installed in sfi_dir")
461 parser.add_option("-t", "--timeout", dest="timeout", default=None,
462 help="Amout of time to wait before timing out the request")
463 parser.add_option("-h", "--help",
464 action="store_true", dest="help", default=False,
465 help="one page summary on commands & exit")
466 parser.disable_interspersed_args()
472 # Main: parse arguments and dispatch to command
474 def dispatch(self, command, command_options, command_args):
475 method=getattr(self, command, None)
477 print "Unknown command %s"%command
479 return method(command_options, command_args)
482 self.sfi_parser = self.create_parser()
483 (options, args) = self.sfi_parser.parse_args()
485 self.print_commands_help(options)
487 self.options = options
489 self.logger.setLevelFromOptVerbose(self.options.verbose)
492 self.logger.critical("No command given. Use -h for help.")
493 self.print_commands_help(options)
496 # complete / find unique match with command set
497 command_candidates = Candidates (commands_list)
499 command = command_candidates.only_match(input)
501 self.print_commands_help(options)
503 # second pass options parsing
505 self.command_parser = self.create_command_parser(command)
506 (command_options, command_args) = self.command_parser.parse_args(args[1:])
507 if command_options.help:
510 self.command_options = command_options
514 self.logger.debug("Command=%s" % self.command)
517 self.dispatch(command, command_options, command_args)
521 self.logger.log_exc ("sfi command %s failed"%command)
527 def read_config(self):
528 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
529 shell_config_file = os.path.join(self.options.sfi_dir,"sfi_config.sh")
531 if Config.is_ini(config_file):
532 config = Config (config_file)
534 # try upgrading from shell config format
535 fp, fn = mkstemp(suffix='sfi_config', text=True)
537 # we need to preload the sections we want parsed
538 # from the shell config
539 config.add_section('sfi')
540 # sface users should be able to use this same file to configure their stuff
541 config.add_section('sface')
542 # manifold users should be able to specify the details
543 # of their backend server here for 'sfi myslice'
544 config.add_section('myslice')
545 config.load(config_file)
547 shutil.move(config_file, shell_config_file)
549 config.save(config_file)
552 self.logger.critical("Failed to read configuration file %s"%config_file)
553 self.logger.info("Make sure to remove the export clauses and to add quotes")
554 if self.options.verbose==0:
555 self.logger.info("Re-run with -v for more details")
557 self.logger.log_exc("Could not read config file %s"%config_file)
563 if (self.options.sm is not None):
564 self.sm_url = self.options.sm
565 elif hasattr(config, "SFI_SM"):
566 self.sm_url = config.SFI_SM
568 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
572 if (self.options.registry is not None):
573 self.reg_url = self.options.registry
574 elif hasattr(config, "SFI_REGISTRY"):
575 self.reg_url = config.SFI_REGISTRY
577 self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
581 if (self.options.user is not None):
582 self.user = self.options.user
583 elif hasattr(config, "SFI_USER"):
584 self.user = config.SFI_USER
586 self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
590 if (self.options.auth is not None):
591 self.authority = self.options.auth
592 elif hasattr(config, "SFI_AUTH"):
593 self.authority = config.SFI_AUTH
595 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
598 self.config_file=config_file
602 def show_config (self):
603 print "From configuration file %s"%self.config_file
606 ('SFI_AUTH','authority'),
608 ('SFI_REGISTRY','reg_url'),
610 for (external_name, internal_name) in flags:
611 print "%s='%s'"%(external_name,getattr(self,internal_name))
614 # Get various credential and spec files
616 # Establishes limiting conventions
617 # - conflates MAs and SAs
618 # - assumes last token in slice name is unique
620 # Bootstraps credentials
621 # - bootstrap user credential from self-signed certificate
622 # - bootstrap authority credential from user credential
623 # - bootstrap slice credential from user credential
626 # init self-signed cert, user credentials and gid
627 def bootstrap (self):
628 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
630 # if -k is provided, use this to initialize private key
631 if self.options.user_private_key:
632 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
634 # trigger legacy compat code if needed
635 # the name has changed from just <leaf>.pkey to <hrn>.pkey
636 if not os.path.isfile(client_bootstrap.private_key_filename()):
637 self.logger.info ("private key not found, trying legacy name")
639 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
640 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
641 client_bootstrap.init_private_key_if_missing (legacy_private_key)
642 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
644 self.logger.log_exc("Can't find private key ")
648 client_bootstrap.bootstrap_my_gid()
649 # extract what's needed
650 self.private_key = client_bootstrap.private_key()
651 self.my_credential_string = client_bootstrap.my_credential_string ()
652 self.my_gid = client_bootstrap.my_gid ()
653 self.client_bootstrap = client_bootstrap
656 def my_authority_credential_string(self):
657 if not self.authority:
658 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
660 return self.client_bootstrap.authority_credential_string (self.authority)
662 def authority_credential_string(self, auth_hrn):
663 return self.client_bootstrap.authority_credential_string (auth_hrn)
665 def slice_credential_string(self, name):
666 return self.client_bootstrap.slice_credential_string (name)
669 # Management of the servers
674 if not hasattr (self, 'registry_proxy'):
675 self.logger.info("Contacting Registry at: %s"%self.reg_url)
676 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
677 timeout=self.options.timeout, verbose=self.options.debug)
678 return self.registry_proxy
682 if not hasattr (self, 'sliceapi_proxy'):
683 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
684 if hasattr(self.command_options,'component') and self.command_options.component:
685 # resolve the hrn at the registry
686 node_hrn = self.command_options.component
687 records = self.registry().Resolve(node_hrn, self.my_credential_string)
688 records = filter_records('node', records)
690 self.logger.warning("No such component:%r"% opts.component)
692 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
693 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
695 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
696 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
697 self.sm_url = 'http://' + self.sm_url
698 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
699 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
700 timeout=self.options.timeout, verbose=self.options.debug)
701 return self.sliceapi_proxy
703 def get_cached_server_version(self, server):
704 # check local cache first
707 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
708 cache_key = server.url + "-version"
710 cache = Cache(cache_file)
713 self.logger.info("Local cache not found at: %s" % cache_file)
716 version = cache.get(cache_key)
719 result = server.GetVersion()
720 version= ReturnValue.get_value(result)
721 # cache version for 20 minutes
722 cache.add(cache_key, version, ttl= 60*20)
723 self.logger.info("Updating cache file %s" % cache_file)
724 cache.save_to_file(cache_file)
728 ### resurrect this temporarily so we can support V1 aggregates for a while
729 def server_supports_options_arg(self, server):
731 Returns true if server support the optional call_id arg, false otherwise.
733 server_version = self.get_cached_server_version(server)
735 # xxx need to rewrite this
736 if int(server_version.get('geni_api')) >= 2:
740 def server_supports_call_id_arg(self, server):
741 server_version = self.get_cached_server_version(server)
743 if 'sfa' in server_version and 'code_tag' in server_version:
744 code_tag = server_version['code_tag']
745 code_tag_parts = code_tag.split("-")
746 version_parts = code_tag_parts[0].split(".")
747 major, minor = version_parts[0], version_parts[1]
748 rev = code_tag_parts[1]
749 if int(major) == 1 and minor == 0 and build >= 22:
753 ### ois = options if supported
754 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
755 def ois (self, server, option_dict):
756 if self.server_supports_options_arg (server):
758 elif self.server_supports_call_id_arg (server):
759 return [ unique_call_id () ]
763 ### cis = call_id if supported - like ois
764 def cis (self, server):
765 if self.server_supports_call_id_arg (server):
766 return [ unique_call_id ]
770 ######################################## miscell utilities
771 def get_rspec_file(self, rspec):
772 if (os.path.isabs(rspec)):
775 file = os.path.join(self.options.sfi_dir, rspec)
776 if (os.path.isfile(file)):
779 self.logger.critical("No such rspec file %s"%rspec)
782 def get_record_file(self, record):
783 if (os.path.isabs(record)):
786 file = os.path.join(self.options.sfi_dir, record)
787 if (os.path.isfile(file)):
790 self.logger.critical("No such registry record file %s"%record)
794 #==========================================================================
795 # Following functions implement the commands
797 # Registry-related commands
798 #==========================================================================
800 @register_command("","")
801 def config (self, options, args):
802 "Display contents of current config"
805 @register_command("","")
806 def version(self, options, args):
808 display an SFA server version (GetVersion)
809 or version information about sfi itself
811 if options.version_local:
812 version=version_core()
814 if options.version_registry:
815 server=self.registry()
817 server = self.sliceapi()
818 result = server.GetVersion()
819 version = ReturnValue.get_value(result)
821 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
823 pprinter = PrettyPrinter(indent=4)
824 pprinter.pprint(version)
826 @register_command("authority","")
827 def list(self, options, args):
829 list entries in named authority registry (List)
836 if options.recursive:
837 opts['recursive'] = options.recursive
839 if options.show_credential:
840 show_credentials(self.my_credential_string)
842 list = self.registry().List(hrn, self.my_credential_string, options)
844 raise Exception, "Not enough parameters for the 'list' command"
846 # filter on person, slice, site, node, etc.
847 # This really should be in the self.filter_records funct def comment...
848 list = filter_records(options.type, list)
849 terminal_render (list, options)
851 save_records_to_file(options.file, list, options.fileformat)
854 @register_command("name","")
855 def show(self, options, args):
857 show details about named registry record (Resolve)
863 # explicitly require Resolve to run in details mode
864 record_dicts = self.registry().Resolve(hrn, self.my_credential_string, {'details':True})
865 record_dicts = filter_records(options.type, record_dicts)
867 self.logger.error("No record of type %s"% options.type)
869 # user has required to focus on some keys
871 def project (record):
873 for key in options.keys:
874 try: projected[key]=record[key]
877 record_dicts = [ project (record) for record in record_dicts ]
878 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
879 for record in records:
880 if (options.format == "text"): record.dump(sort=True)
881 else: print record.save_as_xml()
883 save_records_to_file(options.file, record_dicts, options.fileformat)
886 @register_command("[xml-filename]","")
887 def add(self, options, args):
888 """add record into registry (Register)
889 from command line options (recommended)
890 old-school method involving an xml file still supported"""
892 auth_cred = self.my_authority_credential_string()
893 if options.show_credential:
894 show_credentials(auth_cred)
901 record_filepath = args[0]
902 rec_file = self.get_record_file(record_filepath)
903 record_dict.update(load_record_from_file(rec_file).todict())
905 print "Cannot load record file %s"%record_filepath
908 record_dict.update(load_record_from_opts(options).todict())
909 # we should have a type by now
910 if 'type' not in record_dict :
913 # this is still planetlab dependent.. as plc will whine without that
914 # also, it's only for adding
915 if record_dict['type'] == 'user':
916 if not 'first_name' in record_dict:
917 record_dict['first_name'] = record_dict['hrn']
918 if 'last_name' not in record_dict:
919 record_dict['last_name'] = record_dict['hrn']
920 return self.registry().Register(record_dict, auth_cred)
922 @register_command("[xml-filename]","")
923 def update(self, options, args):
924 """update record into registry (Update)
925 from command line options (recommended)
926 old-school method involving an xml file still supported"""
929 record_filepath = args[0]
930 rec_file = self.get_record_file(record_filepath)
931 record_dict.update(load_record_from_file(rec_file).todict())
933 record_dict.update(load_record_from_opts(options).todict())
934 # at the very least we need 'type' here
935 if 'type' not in record_dict:
939 # don't translate into an object, as this would possibly distort
940 # user-provided data; e.g. add an 'email' field to Users
941 if record_dict['type'] == "user":
942 if record_dict['hrn'] == self.user:
943 cred = self.my_credential_string
945 cred = self.my_authority_credential_string()
946 elif record_dict['type'] in ["slice"]:
948 cred = self.slice_credential_string(record_dict['hrn'])
949 except ServerException, e:
950 # XXX smbaker -- once we have better error return codes, update this
951 # to do something better than a string compare
952 if "Permission error" in e.args[0]:
953 cred = self.my_authority_credential_string()
956 elif record_dict['type'] in ["authority"]:
957 cred = self.my_authority_credential_string()
958 elif record_dict['type'] == 'node':
959 cred = self.my_authority_credential_string()
961 raise "unknown record type" + record_dict['type']
962 if options.show_credential:
963 show_credentials(cred)
964 return self.registry().Update(record_dict, cred)
966 @register_command("name","")
967 def remove(self, options, args):
968 "remove registry record by name (Remove)"
969 auth_cred = self.my_authority_credential_string()
977 if options.show_credential:
978 show_credentials(auth_cred)
979 return self.registry().Remove(hrn, auth_cred, type)
981 # ==================================================================
982 # Slice-related commands
983 # ==================================================================
985 @register_command("","")
986 def slices(self, options, args):
987 "list instantiated slices (ListSlices) - returns urn's"
988 server = self.sliceapi()
990 creds = [self.my_credential_string]
991 # options and call_id when supported
993 api_options['call_id']=unique_call_id()
994 if options.show_credential:
995 show_credentials(creds)
996 result = server.ListSlices(creds, *self.ois(server,api_options))
997 value = ReturnValue.get_value(result)
999 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1004 # show rspec for named slice
1005 @register_command("[slice_hrn]","")
1006 def resources(self, options, args):
1008 with no arg, discover available resources, (ListResources)
1009 or with an slice hrn, shows currently provisioned resources
1011 server = self.sliceapi()
1016 the_credential=self.slice_credential_string(args[0])
1017 creds.append(the_credential)
1019 the_credential=self.my_credential_string
1020 creds.append(the_credential)
1021 if options.show_credential:
1022 show_credentials(creds)
1024 # no need to check if server accepts the options argument since the options has
1025 # been a required argument since v1 API
1027 # always send call_id to v2 servers
1028 api_options ['call_id'] = unique_call_id()
1029 # ask for cached value if available
1030 api_options ['cached'] = True
1033 api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
1035 api_options['info'] = options.info
1036 if options.list_leases:
1037 api_options['list_leases'] = options.list_leases
1039 if options.current == True:
1040 api_options['cached'] = False
1042 api_options['cached'] = True
1043 if options.rspec_version:
1044 version_manager = VersionManager()
1045 server_version = self.get_cached_server_version(server)
1046 if 'sfa' in server_version:
1047 # just request the version the client wants
1048 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1050 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1052 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1053 result = server.ListResources (creds, api_options)
1054 value = ReturnValue.get_value(result)
1055 if self.options.raw:
1056 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1057 if options.file is not None:
1058 save_rspec_to_file(value, options.file)
1059 if (self.options.raw is None) and (options.file is None):
1060 display_rspec(value, options.format)
1064 @register_command("slice_hrn rspec","")
1065 def create(self, options, args):
1067 create or update named slice with given rspec (CreateSliver)
1069 server = self.sliceapi()
1071 # xxx do we need to check usage (len(args)) ?
1074 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1077 creds = [self.slice_credential_string(slice_hrn)]
1079 delegated_cred = None
1080 server_version = self.get_cached_server_version(server)
1081 if server_version.get('interface') == 'slicemgr':
1082 # delegate our cred to the slice manager
1083 # do not delegate cred to slicemgr...not working at the moment
1085 #if server_version.get('hrn'):
1086 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1087 #elif server_version.get('urn'):
1088 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1090 if options.show_credential:
1091 show_credentials(creds)
1094 rspec_file = self.get_rspec_file(args[1])
1095 rspec = open(rspec_file).read()
1098 # need to pass along user keys to the aggregate.
1100 # { urn: urn:publicid:IDN+emulab.net+user+alice
1101 # keys: [<ssh key A>, <ssh key B>]
1104 # xxx Thierry 2012 sept. 21
1105 # contrary to what I was first thinking, calling Resolve with details=False does not yet work properly here
1106 # I am turning details=True on again on a - hopefully - temporary basis, just to get this whole thing to work again
1107 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1108 # slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string], {'details':True})
1109 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']:
1110 slice_record = slice_records[0]
1111 user_hrns = slice_record['reg-researchers']
1112 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1113 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1115 if 'sfa' not in server_version:
1116 users = pg_users_arg(user_records)
1117 rspec = RSpec(rspec)
1118 rspec.filter({'component_manager_id': server_version['urn']})
1119 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1121 users = sfa_users_arg(user_records, slice_record)
1123 # do not append users, keys, or slice tags. Anything
1124 # not contained in this request will be removed from the slice
1126 # CreateSliver has supported the options argument for a while now so it should
1127 # be safe to assume this server support it
1129 api_options ['append'] = False
1130 api_options ['call_id'] = unique_call_id()
1131 result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
1132 value = ReturnValue.get_value(result)
1133 if self.options.raw:
1134 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1135 if options.file is not None:
1136 save_rspec_to_file (value, options.file)
1137 if (self.options.raw is None) and (options.file is None):
1142 @register_command("slice_hrn","")
1143 def delete(self, options, args):
1145 delete named slice (DeleteSliver)
1147 server = self.sliceapi()
1151 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1154 slice_cred = self.slice_credential_string(slice_hrn)
1155 creds = [slice_cred]
1157 # options and call_id when supported
1159 api_options ['call_id'] = unique_call_id()
1160 if options.show_credential:
1161 show_credentials(creds)
1162 result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
1163 value = ReturnValue.get_value(result)
1164 if self.options.raw:
1165 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1170 @register_command("slice_hrn","")
1171 def status(self, options, args):
1173 retrieve slice status (SliverStatus)
1175 server = self.sliceapi()
1179 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1182 slice_cred = self.slice_credential_string(slice_hrn)
1183 creds = [slice_cred]
1185 # options and call_id when supported
1187 api_options['call_id']=unique_call_id()
1188 if options.show_credential:
1189 show_credentials(creds)
1190 result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
1191 value = ReturnValue.get_value(result)
1192 if self.options.raw:
1193 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1197 @register_command("slice_hrn","")
1198 def start(self, options, args):
1200 start named slice (Start)
1202 server = self.sliceapi()
1206 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1209 slice_cred = self.slice_credential_string(args[0])
1210 creds = [slice_cred]
1211 # xxx Thierry - does this not need an api_options as well ?
1212 result = server.Start(slice_urn, creds)
1213 value = ReturnValue.get_value(result)
1214 if self.options.raw:
1215 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1220 @register_command("slice_hrn","")
1221 def stop(self, options, args):
1223 stop named slice (Stop)
1225 server = self.sliceapi()
1228 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1230 slice_cred = self.slice_credential_string(args[0])
1231 creds = [slice_cred]
1232 result = server.Stop(slice_urn, creds)
1233 value = ReturnValue.get_value(result)
1234 if self.options.raw:
1235 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1241 @register_command("slice_hrn","")
1242 def reset(self, options, args):
1244 reset named slice (reset_slice)
1246 server = self.sliceapi()
1249 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1251 slice_cred = self.slice_credential_string(args[0])
1252 creds = [slice_cred]
1253 result = server.reset_slice(creds, slice_urn)
1254 value = ReturnValue.get_value(result)
1255 if self.options.raw:
1256 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1261 @register_command("slice_hrn time","")
1262 def renew(self, options, args):
1264 renew slice (RenewSliver)
1266 server = self.sliceapi()
1270 [ slice_hrn, input_time ] = args
1272 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1273 # time: don't try to be smart on the time format, server-side will
1275 slice_cred = self.slice_credential_string(args[0])
1276 creds = [slice_cred]
1277 # options and call_id when supported
1279 api_options['call_id']=unique_call_id()
1280 if options.show_credential:
1281 show_credentials(creds)
1282 result = server.RenewSliver(slice_urn, creds, input_time, *self.ois(server,api_options))
1283 value = ReturnValue.get_value(result)
1284 if self.options.raw:
1285 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1291 @register_command("slice_hrn","")
1292 def shutdown(self, options, args):
1294 shutdown named slice (Shutdown)
1296 server = self.sliceapi()
1299 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1301 slice_cred = self.slice_credential_string(slice_hrn)
1302 creds = [slice_cred]
1303 result = server.Shutdown(slice_urn, creds)
1304 value = ReturnValue.get_value(result)
1305 if self.options.raw:
1306 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1312 @register_command("slice_hrn rspec","")
1313 def get_ticket(self, options, args):
1315 get a ticket for the specified slice
1317 server = self.sliceapi()
1319 slice_hrn, rspec_path = args[0], args[1]
1320 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1322 slice_cred = self.slice_credential_string(slice_hrn)
1323 creds = [slice_cred]
1325 rspec_file = self.get_rspec_file(rspec_path)
1326 rspec = open(rspec_file).read()
1327 # options and call_id when supported
1329 api_options['call_id']=unique_call_id()
1330 # get ticket at the server
1331 ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1333 file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1334 self.logger.info("writing ticket to %s"%file)
1335 ticket = SfaTicket(string=ticket_string)
1336 ticket.save_to_file(filename=file, save_parents=True)
1338 @register_command("ticket","")
1339 def redeem_ticket(self, options, args):
1341 Connects to nodes in a slice and redeems a ticket
1342 (slice hrn is retrieved from the ticket)
1344 ticket_file = args[0]
1346 # get slice hrn from the ticket
1347 # use this to get the right slice credential
1348 ticket = SfaTicket(filename=ticket_file)
1350 ticket_string = ticket.save_to_string(save_parents=True)
1352 slice_hrn = ticket.gidObject.get_hrn()
1353 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1354 #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1355 slice_cred = self.slice_credential_string(slice_hrn)
1357 # get a list of node hostnames from the RSpec
1358 tree = etree.parse(StringIO(ticket.rspec))
1359 root = tree.getroot()
1360 hostnames = root.xpath("./network/site/node/hostname/text()")
1362 # create an xmlrpc connection to the component manager at each of these
1363 # components and gall redeem_ticket
1365 for hostname in hostnames:
1367 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1368 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1369 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1370 server = self.server_proxy(hostname, CM_PORT, self.private_key,
1371 timeout=self.options.timeout, verbose=self.options.debug)
1372 server.RedeemTicket(ticket_string, slice_cred)
1373 self.logger.info("Success")
1374 except socket.gaierror:
1375 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1376 except Exception, e:
1377 self.logger.log_exc(e.message)
1380 @register_command("[name]","")
1381 def gid(self, options, args):
1383 Create a GID (CreateGid)
1388 target_hrn = args[0]
1389 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1390 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1392 filename = options.file
1394 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1395 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1396 GID(string=gid).save_to_file(filename)
1398 ####################
1399 @register_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1401 will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1402 the set of credentials in the scope for this call would be
1403 (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1405 (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1407 (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1408 (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1409 because of the two -s options
1412 def delegate (self, options, args):
1414 (locally) create delegate credential for use by given hrn
1415 make sure to check for 'sfi myslice' instead if you plan
1422 # support for several delegations in the same call
1423 # so first we gather the things to do
1425 for slice_hrn in options.delegate_slices:
1426 message="%s.slice"%slice_hrn
1427 original = self.slice_credential_string(slice_hrn)
1428 tuples.append ( (message, original,) )
1429 if options.delegate_pi:
1430 my_authority=self.authority
1431 message="%s.pi"%my_authority
1432 original = self.my_authority_credential_string()
1433 tuples.append ( (message, original,) )
1434 for auth_hrn in options.delegate_auths:
1435 message="%s.auth"%auth_hrn
1436 original=self.authority_credential_string(auth_hrn)
1437 tuples.append ( (message, original, ) )
1438 # if nothing was specified at all at this point, let's assume -u
1439 if not tuples: options.delegate_user=True
1441 if options.delegate_user:
1442 message="%s.user"%self.user
1443 original = self.my_credential_string
1444 tuples.append ( (message, original, ) )
1446 # default type for beneficial is user unless -A
1447 if options.delegate_to_authority: to_type='authority'
1448 else: to_type='user'
1450 # let's now handle all this
1451 # it's all in the filenaming scheme
1452 for (message,original) in tuples:
1453 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1454 delegated_credential = Credential (string=delegated_string)
1455 filename = os.path.join ( self.options.sfi_dir,
1456 "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1457 delegated_credential.save_to_file(filename, save_parents=True)
1458 self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1460 ####################
1461 @register_command("","""$ less +/myslice sfi_config
1463 backend = http://manifold.pl.sophia.inria.fr:7080
1464 # the HRN that myslice uses, so that we are delegating to
1465 delegate = ple.upmc.slicebrowser
1466 # platform - this is a myslice concept
1468 # username - as of this writing (May 2013) a simple login name
1472 will first collect the slices that you are part of, then make sure
1473 all your credentials are up-to-date (read: refresh expired ones)
1474 then compute delegated credentials for user 'ple.upmc.slicebrowser'
1475 and upload them all on myslice backend, using 'platform' and 'user'.
1476 A password will be prompted for the upload part.
1478 $ sfi -v myslice -- or sfi -vv myslice
1479 same but with more and more verbosity
1482 is synonym to sfi myslice as no other command starts with an 'm'
1484 ) # register_command
1485 def myslice (self, options, args):
1487 """ This helper is for refreshing your credentials at myslice; it will
1488 * compute all the slices that you currently have credentials on
1489 * refresh all your credentials (you as a user and pi, your slices)
1490 * upload them to the manifold backend server
1491 for last phase, sfi_config is read to look for the [myslice] section,
1492 and namely the 'backend', 'delegate' and 'user' settings"""
1499 ### the rough sketch goes like this
1500 # (a) rain check for sufficient config in sfi_config
1501 # we don't allow to override these settings for now
1503 myslice_keys=['backend', 'delegate', 'platform', 'username']
1504 for key in myslice_keys:
1505 full_key="MYSLICE_" + key.upper()
1506 value=getattr(self.config,full_key,None)
1507 if value: myslice_dict[key]=value
1508 else: print "Unsufficient config, missing key %s in [myslice] section of sfi_config"%key
1509 if len(myslice_dict) != len(myslice_keys):
1512 # (b) figure whether we are PI for the authority where we belong
1513 sfi_logger.info("Resolving our own id")
1514 my_records=self.registry().Resolve(self.user,self.my_credential_string)
1515 if len(my_records)!=1: print "Cannot Resolve %s -- exiting"%self.user; sys.exit(1)
1516 my_record=my_records[0]
1517 sfi_logger.info("Checking for authorities that we are PI for")
1518 my_auths = my_record['reg-pi-authorities']
1519 sfi_logger.debug("Found %d authorities: %s"%(len(my_auths),my_auths))
1521 # (c) get the set of slices that we are in
1522 sfi_logger.info("Checking for slices that we are member of")
1523 my_slices=my_record['reg-slices']
1524 sfi_logger.debug("Found %d slices: %s"%(len(my_slices),my_slices))
1526 # (d) make sure we have *valid* credentials for all these
1528 hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1529 for auth_hrn in my_auths:
1530 hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1531 for slice_hrn in my_slices:
1532 hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1534 # (e) check for the delegated version of these
1535 # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever
1536 # switch to myslice using an authority instead of a user
1537 delegatee_type='user'
1538 delegatee_hrn=myslice_dict['delegate']
1539 hrn_delegated_credentials = [
1540 (hrn, htype, self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type),)
1541 for (hrn, htype, credential) in hrn_credentials ]
1543 # (f) and finally upload them to manifold server
1544 # xxx todo add an option so the password can be set on the command line
1545 # (but *NOT* in the config file) so other apps can leverage this
1546 uploader = ManifoldUploader (logger=sfi_logger,
1547 url=myslice_dict['backend'],
1548 platform=myslice_dict['platform'],
1549 username=myslice_dict['username'])
1550 for (hrn,htype,delegated_credential) in hrn_delegated_credentials:
1551 sfi_logger.info("Uploading delegated credential for %s (%s)"%(hrn,htype))
1552 uploader.upload(delegated_credential,message=hrn)
1553 # at first I thought we would want to save these,
1554 # like 'sfi delegate does' but on second thought
1555 # it is probably not helpful as people would not
1556 # need to run 'sfi delegate' at all anymore
1559 # Thierry: I'm turning this off as a command, no idea what it's used for
1560 # @register_command("cred","")
1561 def trusted(self, options, args):
1563 return the trusted certs at this interface (get_trusted_certs)
1565 trusted_certs = self.registry().get_trusted_certs()
1566 for trusted_cert in trusted_certs:
1567 gid = GID(string=trusted_cert)
1569 cert = Certificate(string=trusted_cert)
1570 self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())