2 # sfi.py - basic SFA command-line client
3 # this module is also used in sfascan
17 from lxml import etree
18 from StringIO import StringIO
19 from optparse import OptionParser
20 from pprint import PrettyPrinter
21 from tempfile import mkstemp
23 from sfa.trust.certificate import Keypair, Certificate
24 from sfa.trust.gid import GID
25 from sfa.trust.credential import Credential
26 from sfa.trust.sfaticket import SfaTicket
28 from sfa.util.faults import SfaInvalidArgument
29 from sfa.util.sfalogging import sfi_logger
30 from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn, Xrn
31 from sfa.util.config import Config
32 from sfa.util.version import version_core
33 from sfa.util.cache import Cache
35 from sfa.storage.record import Record
37 from sfa.rspecs.rspec import RSpec
38 from sfa.rspecs.rspec_converter import RSpecConverter
39 from sfa.rspecs.version_manager import VersionManager
41 from sfa.client.sfaclientlib import SfaClientBootstrap
42 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
43 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
44 from sfa.client.return_value import ReturnValue
45 from sfa.client.candidates import Candidates
46 from sfa.client.manifolduploader import ManifoldUploader
50 from sfa.client.common import optparse_listvalue_callback, optparse_dictvalue_callback, \
51 terminal_render, filter_records
54 def display_rspec(rspec, format='rspec'):
56 tree = etree.parse(StringIO(rspec))
58 result = root.xpath("./network/site/node/hostname/text()")
59 elif format in ['ip']:
60 # The IP address is not yet part of the new RSpec
61 # so this doesn't do anything yet.
62 tree = etree.parse(StringIO(rspec))
64 result = root.xpath("./network/site/node/ipv4/text()")
71 def display_list(results):
72 for result in results:
75 def display_records(recordList, dump=False):
76 ''' Print all fields in the record'''
77 for record in recordList:
78 display_record(record, dump)
80 def display_record(record, dump=False):
82 record.dump(sort=True)
84 info = record.getdict()
85 print "%s (%s)" % (info['hrn'], info['type'])
89 def filter_records(type, records):
91 for record in records:
92 if (record['type'] == type) or (type == "all"):
93 filtered_records.append(record)
94 return filtered_records
97 def credential_printable (cred):
98 credential=Credential(cred=cred)
100 result += credential.get_summary_tostring()
102 rights = credential.get_privileges()
103 result += "type=%s\n" % credential.type
104 result += "version=%s\n" % credential.version
105 result += "rights=%s\n"%rights
108 def show_credentials (cred_s):
109 if not isinstance (cred_s,list): cred_s = [cred_s]
111 print "Using Credential %s"%credential_printable(cred)
114 def save_raw_to_file(var, filename, format="text", banner=None):
116 # if filename is "-", send it to stdout
119 f = open(filename, "w")
124 elif format == "pickled":
125 f.write(pickle.dumps(var))
126 elif format == "json":
127 if hasattr(json, "dumps"):
128 f.write(json.dumps(var)) # python 2.6
130 f.write(json.write(var)) # python 2.5
132 # this should never happen
133 print "unknown output format", format
135 f.write('\n'+banner+"\n")
137 def save_rspec_to_file(rspec, filename):
138 if not filename.endswith(".rspec"):
139 filename = filename + ".rspec"
140 f = open(filename, 'w')
145 def save_records_to_file(filename, record_dicts, format="xml"):
148 for record_dict in record_dicts:
150 save_record_to_file(filename + "." + str(index), record_dict)
152 save_record_to_file(filename, record_dict)
154 elif format == "xmllist":
155 f = open(filename, "w")
156 f.write("<recordlist>\n")
157 for record_dict in record_dicts:
158 record_obj=Record(dict=record_dict)
159 f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
160 f.write("</recordlist>\n")
162 elif format == "hrnlist":
163 f = open(filename, "w")
164 for record_dict in record_dicts:
165 record_obj=Record(dict=record_dict)
166 f.write(record_obj.hrn + "\n")
169 # this should never happen
170 print "unknown output format", format
172 def save_record_to_file(filename, record_dict):
173 record = Record(dict=record_dict)
174 xml = record.save_as_xml()
175 f=codecs.open(filename, encoding='utf-8',mode="w")
180 # minimally check a key argument
181 def check_ssh_key (key):
182 good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
183 return re.match(good_ssh_key, key, re.IGNORECASE)
186 def load_record_from_opts(options):
188 if hasattr(options, 'xrn') and options.xrn:
189 if hasattr(options, 'type') and options.type:
190 xrn = Xrn(options.xrn, options.type)
192 xrn = Xrn(options.xrn)
193 record_dict['urn'] = xrn.get_urn()
194 record_dict['hrn'] = xrn.get_hrn()
195 record_dict['type'] = xrn.get_type()
196 if hasattr(options, 'key') and options.key:
198 pubkey = open(options.key, 'r').read()
201 if not check_ssh_key (pubkey):
202 raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format")
203 record_dict['keys'] = [pubkey]
204 if hasattr(options, 'slices') and options.slices:
205 record_dict['slices'] = options.slices
206 if hasattr(options, 'researchers') and options.researchers:
207 record_dict['researcher'] = options.researchers
208 if hasattr(options, 'email') and options.email:
209 record_dict['email'] = options.email
210 if hasattr(options, 'pis') and options.pis:
211 record_dict['pi'] = options.pis
213 # handle extra settings
214 record_dict.update(options.extras)
216 return Record(dict=record_dict)
218 def load_record_from_file(filename):
219 f=codecs.open(filename, encoding="utf-8", mode="r")
220 xml_string = f.read()
222 return Record(xml=xml_string)
226 def unique_call_id(): return uuid.uuid4().urn
228 ########## a simple model for maintaing 3 doc attributes per command (instead of just one)
229 # essentially for the methods that implement a subcommand like sfi list
230 # we need to keep track of
231 # (*) doc a few lines that tell what it does, still located in __doc__
232 # (*) args_string a simple one-liner that describes mandatory arguments
233 # (*) example well, one or several releant examples
235 # since __doc__ only accounts for one, we use this simple mechanism below
236 # however we keep doc in place for easier migration
238 from functools import wraps
240 # we use a list as well as a dict so we can keep track of the order
244 def register_command (args_string, example):
246 name=getattr(m,'__name__')
247 doc=getattr(m,'__doc__',"-- missing doc --")
248 doc=doc.strip(" \t\n")
249 commands_list.append(name)
250 commands_dict[name]=(doc, args_string, example)
252 def new_method (*args, **kwds): return m(*args, **kwds)
260 # dirty hack to make this class usable from the outside
261 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
264 def default_sfi_dir ():
265 if os.path.isfile("./sfi_config"):
268 return os.path.expanduser("~/.sfi/")
270 # dummy to meet Sfi's expectations for its 'options' field
271 # i.e. s/t we can do setattr on
275 def __init__ (self,options=None):
276 if options is None: options=Sfi.DummyOptions()
277 for opt in Sfi.required_options:
278 if not hasattr(options,opt): setattr(options,opt,None)
279 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
280 self.options = options
282 self.authority = None
283 self.logger = sfi_logger
284 self.logger.enable_console()
285 ### various auxiliary material that we keep at hand
288 self.config_file=None
289 self.client_bootstrap=None
291 ### suitable if no reasonable command has been provided
292 def print_commands_help (self, options):
293 verbose=getattr(options,'verbose')
294 format3="%18s %-15s %s"
297 print format3%("command","cmd_args","description")
301 self.create_parser().print_help()
302 # preserve order from the code
303 for command in commands_list:
304 (doc, args_string, example) = commands_dict[command]
307 doc=doc.replace("\n","\n"+35*' ')
308 print format3%(command,args_string,doc)
310 self.create_command_parser(command).print_help()
312 ### now if a known command was found we can be more verbose on that one
313 def print_help (self):
314 print "==================== Generic sfi usage"
315 self.sfi_parser.print_help()
316 (doc,_,example)=commands_dict[self.command]
317 print "\n==================== Purpose of %s"%self.command
319 print "\n==================== Specific usage for %s"%self.command
320 self.command_parser.print_help()
322 print "\n==================== %s example(s)"%self.command
325 def create_command_parser(self, command):
326 if command not in commands_dict:
327 msg="Invalid command\n"
329 msg += ','.join(commands_list)
330 self.logger.critical(msg)
333 # retrieve args_string
334 (_, args_string, __) = commands_dict[command]
336 parser = OptionParser(add_help_option=False,
337 usage="sfi [sfi_options] %s [cmd_options] %s"
338 % (command, args_string))
339 parser.add_option ("-h","--help",dest='help',action='store_true',default=False,
340 help="Summary of one command usage")
342 if command in ("add", "update"):
343 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
344 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
345 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
346 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
348 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
349 default='', type="str", action='callback', callback=optparse_listvalue_callback)
350 parser.add_option('-r', '--researchers', dest='researchers', metavar='<researchers>',
351 help='Set/replace slice researchers', default='', type="str", action='callback',
352 callback=optparse_listvalue_callback)
353 parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
354 default='', type="str", action='callback', callback=optparse_listvalue_callback)
355 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
356 action="callback", callback=optparse_dictvalue_callback, nargs=1,
357 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
359 # user specifies remote aggregate/sm/component
360 if command in ("resources", "describe", "allocate", "provision", "delete", "allocate", "provision",
361 "action", "shutdown", "renew", "status"):
362 parser.add_option("-d", "--delegate", dest="delegate", default=None,
364 help="Include a credential delegated to the user's root"+\
365 "authority in set of credentials for this call")
367 # show_credential option
368 if command in ("list","resources", "describe", "provision", "allocate", "add","update","remove","slices","delete","status","renew"):
369 parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
370 help="show credential(s) used in human-readable form")
371 # registy filter option
372 if command in ("list", "show", "remove"):
373 parser.add_option("-t", "--type", dest="type", type="choice",
374 help="type filter ([all]|user|slice|authority|node|aggregate)",
375 choices=("all", "user", "slice", "authority", "node", "aggregate"),
377 if command in ("show"):
378 parser.add_option("-k","--key",dest="keys",action="append",default=[],
379 help="specify specific keys to be displayed from record")
380 if command in ("resources", "describe"):
382 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
383 help="schema type and version of resulting RSpec")
384 # disable/enable cached rspecs
385 parser.add_option("-c", "--current", dest="current", default=False,
387 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
389 parser.add_option("-f", "--format", dest="format", type="choice",
390 help="display format ([xml]|dns|ip)", default="xml",
391 choices=("xml", "dns", "ip"))
392 #panos: a new option to define the type of information about resources a user is interested in
393 parser.add_option("-i", "--info", dest="info",
394 help="optional component information", default=None)
395 # a new option to retreive or not reservation-oriented RSpecs (leases)
396 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
397 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
398 choices=("all", "resources", "leases"), default="resources")
401 if command in ("resources", "describe", "allocate", "provision", "show", "list", "gid"):
402 parser.add_option("-o", "--output", dest="file",
403 help="output XML to file", metavar="FILE", default=None)
405 if command in ("show", "list"):
406 parser.add_option("-f", "--format", dest="format", type="choice",
407 help="display format ([text]|xml)", default="text",
408 choices=("text", "xml"))
410 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
411 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
412 choices=("xml", "xmllist", "hrnlist"))
413 if command == 'list':
414 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
415 help="list all child records", default=False)
416 parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
417 help="gives details, like user keys", default=False)
418 if command in ("delegate"):
419 parser.add_option("-u", "--user",
420 action="store_true", dest="delegate_user", default=False,
421 help="delegate your own credentials; default if no other option is provided")
422 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
423 metavar="slice_hrn", help="delegate cred. for slice HRN")
424 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
425 metavar='auth_hrn', help="delegate cred for auth HRN")
426 # this primarily is a shorthand for -a my_hrn
427 parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
428 help="delegate your PI credentials, so s.t. like -a your_hrn^")
429 parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
430 help="""by default the mandatory argument is expected to be a user,
431 use this if you mean an authority instead""")
433 if command in ("version"):
434 parser.add_option("-R","--registry-version",
435 action="store_true", dest="version_registry", default=False,
436 help="probe registry version instead of sliceapi")
437 parser.add_option("-l","--local",
438 action="store_true", dest="version_local", default=False,
439 help="display version of the local client")
444 def create_parser(self):
446 # Generate command line parser
447 parser = OptionParser(add_help_option=False,
448 usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
449 description="Commands: %s"%(" ".join(commands_list)))
450 parser.add_option("-r", "--registry", dest="registry",
451 help="root registry", metavar="URL", default=None)
452 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
453 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
454 parser.add_option("-R", "--raw", dest="raw", default=None,
455 help="Save raw, unparsed server response to a file")
456 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
457 help="raw file format ([text]|pickled|json)", default="text",
458 choices=("text","pickled","json"))
459 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
460 help="text string to write before and after raw output")
461 parser.add_option("-d", "--dir", dest="sfi_dir",
462 help="config & working directory - default is %default",
463 metavar="PATH", default=Sfi.default_sfi_dir())
464 parser.add_option("-u", "--user", dest="user",
465 help="user name", metavar="HRN", default=None)
466 parser.add_option("-a", "--auth", dest="auth",
467 help="authority name", metavar="HRN", default=None)
468 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
469 help="verbose mode - cumulative")
470 parser.add_option("-D", "--debug",
471 action="store_true", dest="debug", default=False,
472 help="Debug (xml-rpc) protocol messages")
473 # would it make sense to use ~/.ssh/id_rsa as a default here ?
474 parser.add_option("-k", "--private-key",
475 action="store", dest="user_private_key", default=None,
476 help="point to the private key file to use if not yet installed in sfi_dir")
477 parser.add_option("-t", "--timeout", dest="timeout", default=None,
478 help="Amout of time to wait before timing out the request")
479 parser.add_option("-h", "--help",
480 action="store_true", dest="help", default=False,
481 help="one page summary on commands & exit")
482 parser.disable_interspersed_args()
488 # Main: parse arguments and dispatch to command
490 def dispatch(self, command, command_options, command_args):
491 method=getattr(self, command, None)
493 print "Unknown command %s"%command
495 return method(command_options, command_args)
498 self.sfi_parser = self.create_parser()
499 (options, args) = self.sfi_parser.parse_args()
501 self.print_commands_help(options)
503 self.options = options
505 self.logger.setLevelFromOptVerbose(self.options.verbose)
508 self.logger.critical("No command given. Use -h for help.")
509 self.print_commands_help(options)
512 # complete / find unique match with command set
513 command_candidates = Candidates (commands_list)
515 command = command_candidates.only_match(input)
517 self.print_commands_help(options)
519 # second pass options parsing
521 self.command_parser = self.create_command_parser(command)
522 (command_options, command_args) = self.command_parser.parse_args(args[1:])
523 if command_options.help:
526 self.command_options = command_options
530 self.logger.debug("Command=%s" % self.command)
533 self.dispatch(command, command_options, command_args)
537 self.logger.log_exc ("sfi command %s failed"%command)
543 def read_config(self):
544 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
545 shell_config_file = os.path.join(self.options.sfi_dir,"sfi_config.sh")
547 if Config.is_ini(config_file):
548 config = Config (config_file)
550 # try upgrading from shell config format
551 fp, fn = mkstemp(suffix='sfi_config', text=True)
553 # we need to preload the sections we want parsed
554 # from the shell config
555 config.add_section('sfi')
556 # sface users should be able to use this same file to configure their stuff
557 config.add_section('sface')
558 # manifold users should be able to specify the details
559 # of their backend server here for 'sfi myslice'
560 config.add_section('myslice')
561 config.load(config_file)
563 shutil.move(config_file, shell_config_file)
565 config.save(config_file)
568 self.logger.critical("Failed to read configuration file %s"%config_file)
569 self.logger.info("Make sure to remove the export clauses and to add quotes")
570 if self.options.verbose==0:
571 self.logger.info("Re-run with -v for more details")
573 self.logger.log_exc("Could not read config file %s"%config_file)
579 if (self.options.sm is not None):
580 self.sm_url = self.options.sm
581 elif hasattr(config, "SFI_SM"):
582 self.sm_url = config.SFI_SM
584 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
588 if (self.options.registry is not None):
589 self.reg_url = self.options.registry
590 elif hasattr(config, "SFI_REGISTRY"):
591 self.reg_url = config.SFI_REGISTRY
593 self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
597 if (self.options.user is not None):
598 self.user = self.options.user
599 elif hasattr(config, "SFI_USER"):
600 self.user = config.SFI_USER
602 self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
606 if (self.options.auth is not None):
607 self.authority = self.options.auth
608 elif hasattr(config, "SFI_AUTH"):
609 self.authority = config.SFI_AUTH
611 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
614 self.config_file=config_file
618 def show_config (self):
619 print "From configuration file %s"%self.config_file
622 ('SFI_AUTH','authority'),
624 ('SFI_REGISTRY','reg_url'),
626 for (external_name, internal_name) in flags:
627 print "%s='%s'"%(external_name,getattr(self,internal_name))
630 # Get various credential and spec files
632 # Establishes limiting conventions
633 # - conflates MAs and SAs
634 # - assumes last token in slice name is unique
636 # Bootstraps credentials
637 # - bootstrap user credential from self-signed certificate
638 # - bootstrap authority credential from user credential
639 # - bootstrap slice credential from user credential
642 # init self-signed cert, user credentials and gid
643 def bootstrap (self):
644 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
646 # if -k is provided, use this to initialize private key
647 if self.options.user_private_key:
648 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
650 # trigger legacy compat code if needed
651 # the name has changed from just <leaf>.pkey to <hrn>.pkey
652 if not os.path.isfile(client_bootstrap.private_key_filename()):
653 self.logger.info ("private key not found, trying legacy name")
655 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
656 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
657 client_bootstrap.init_private_key_if_missing (legacy_private_key)
658 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
660 self.logger.log_exc("Can't find private key ")
664 client_bootstrap.bootstrap_my_gid()
665 # extract what's needed
666 self.private_key = client_bootstrap.private_key()
667 self.my_credential_string = client_bootstrap.my_credential_string ()
668 self.my_credential = {'geni_type': 'geni_sfa',
669 'geni_version': '3.0',
670 'geni_value': self.my_credential_string}
671 self.my_gid = client_bootstrap.my_gid ()
672 self.client_bootstrap = client_bootstrap
675 def my_authority_credential_string(self):
676 if not self.authority:
677 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
679 return self.client_bootstrap.authority_credential_string (self.authority)
681 def authority_credential_string(self, auth_hrn):
682 return self.client_bootstrap.authority_credential_string (auth_hrn)
684 def slice_credential_string(self, name):
685 return self.client_bootstrap.slice_credential_string (name)
687 def slice_credential(self, name):
688 return {'geni_type': 'geni_sfa',
689 'geni_version': '3.0',
690 'geni_value': self.slice_credential_string(name)}
692 # xxx should be supported by sfaclientbootstrap as well
693 def delegate_cred(self, object_cred, hrn, type='authority'):
694 # the gid and hrn of the object we are delegating
695 if isinstance(object_cred, str):
696 object_cred = Credential(string=object_cred)
697 object_gid = object_cred.get_gid_object()
698 object_hrn = object_gid.get_hrn()
700 if not object_cred.get_privileges().get_all_delegate():
701 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
704 # the delegating user's gid
705 caller_gidfile = self.my_gid()
707 # the gid of the user who will be delegated to
708 delegee_gid = self.client_bootstrap.gid(hrn,type)
709 delegee_hrn = delegee_gid.get_hrn()
710 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
711 return dcred.save_to_string(save_parents=True)
714 # Management of the servers
719 if not hasattr (self, 'registry_proxy'):
720 self.logger.info("Contacting Registry at: %s"%self.reg_url)
721 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
722 timeout=self.options.timeout, verbose=self.options.debug)
723 return self.registry_proxy
727 if not hasattr (self, 'sliceapi_proxy'):
728 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
729 if hasattr(self.command_options,'component') and self.command_options.component:
730 # resolve the hrn at the registry
731 node_hrn = self.command_options.component
732 records = self.registry().Resolve(node_hrn, self.my_credential_string)
733 records = filter_records('node', records)
735 self.logger.warning("No such component:%r"% opts.component)
737 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
738 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
740 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
741 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
742 self.sm_url = 'http://' + self.sm_url
743 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
744 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
745 timeout=self.options.timeout, verbose=self.options.debug)
746 return self.sliceapi_proxy
748 def get_cached_server_version(self, server):
749 # check local cache first
752 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
753 cache_key = server.url + "-version"
755 cache = Cache(cache_file)
758 self.logger.info("Local cache not found at: %s" % cache_file)
761 version = cache.get(cache_key)
764 result = server.GetVersion()
765 version= ReturnValue.get_value(result)
766 # cache version for 20 minutes
767 cache.add(cache_key, version, ttl= 60*20)
768 self.logger.info("Updating cache file %s" % cache_file)
769 cache.save_to_file(cache_file)
773 ### resurrect this temporarily so we can support V1 aggregates for a while
774 def server_supports_options_arg(self, server):
776 Returns true if server support the optional call_id arg, false otherwise.
778 server_version = self.get_cached_server_version(server)
780 # xxx need to rewrite this
781 if int(server_version.get('geni_api')) >= 2:
785 def server_supports_call_id_arg(self, server):
786 server_version = self.get_cached_server_version(server)
788 if 'sfa' in server_version and 'code_tag' in server_version:
789 code_tag = server_version['code_tag']
790 code_tag_parts = code_tag.split("-")
791 version_parts = code_tag_parts[0].split(".")
792 major, minor = version_parts[0], version_parts[1]
793 rev = code_tag_parts[1]
794 if int(major) == 1 and minor == 0 and build >= 22:
798 ### ois = options if supported
799 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
800 def ois (self, server, option_dict):
801 if self.server_supports_options_arg (server):
803 elif self.server_supports_call_id_arg (server):
804 return [ unique_call_id () ]
808 ### cis = call_id if supported - like ois
809 def cis (self, server):
810 if self.server_supports_call_id_arg (server):
811 return [ unique_call_id ]
815 ######################################## miscell utilities
816 def get_rspec_file(self, rspec):
817 if (os.path.isabs(rspec)):
820 file = os.path.join(self.options.sfi_dir, rspec)
821 if (os.path.isfile(file)):
824 self.logger.critical("No such rspec file %s"%rspec)
827 def get_record_file(self, record):
828 if (os.path.isabs(record)):
831 file = os.path.join(self.options.sfi_dir, record)
832 if (os.path.isfile(file)):
835 self.logger.critical("No such registry record file %s"%record)
839 #==========================================================================
840 # Following functions implement the commands
842 # Registry-related commands
843 #==========================================================================
845 @register_command("","")
846 def config (self, options, args):
847 "Display contents of current config"
850 @register_command("","")
851 def version(self, options, args):
853 display an SFA server version (GetVersion)
854 or version information about sfi itself
856 if options.version_local:
857 version=version_core()
859 if options.version_registry:
860 server=self.registry()
862 server = self.sliceapi()
863 result = server.GetVersion()
864 version = ReturnValue.get_value(result)
866 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
868 pprinter = PrettyPrinter(indent=4)
869 pprinter.pprint(version)
871 @register_command("authority","")
872 def list(self, options, args):
874 list entries in named authority registry (List)
881 if options.recursive:
882 opts['recursive'] = options.recursive
884 if options.show_credential:
885 show_credentials(self.my_credential_string)
887 list = self.registry().List(hrn, self.my_credential_string, options)
889 raise Exception, "Not enough parameters for the 'list' command"
891 # filter on person, slice, site, node, etc.
892 # This really should be in the self.filter_records funct def comment...
893 list = filter_records(options.type, list)
894 terminal_render (list, options)
896 save_records_to_file(options.file, list, options.fileformat)
899 @register_command("name","")
900 def show(self, options, args):
902 show details about named registry record (Resolve)
908 # explicitly require Resolve to run in details mode
909 record_dicts = self.registry().Resolve(hrn, self.my_credential_string, {'details':True})
910 record_dicts = filter_records(options.type, record_dicts)
912 self.logger.error("No record of type %s"% options.type)
914 # user has required to focus on some keys
916 def project (record):
918 for key in options.keys:
919 try: projected[key]=record[key]
922 record_dicts = [ project (record) for record in record_dicts ]
923 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
924 for record in records:
925 if (options.format == "text"): record.dump(sort=True)
926 else: print record.save_as_xml()
928 save_records_to_file(options.file, record_dicts, options.fileformat)
931 @register_command("[xml-filename]","")
932 def add(self, options, args):
933 """add record into registry (Register)
934 from command line options (recommended)
935 old-school method involving an xml file still supported"""
937 auth_cred = self.my_authority_credential_string()
938 if options.show_credential:
939 show_credentials(auth_cred)
946 record_filepath = args[0]
947 rec_file = self.get_record_file(record_filepath)
948 record_dict.update(load_record_from_file(rec_file).todict())
950 print "Cannot load record file %s"%record_filepath
953 record_dict.update(load_record_from_opts(options).todict())
954 # we should have a type by now
955 if 'type' not in record_dict :
958 # this is still planetlab dependent.. as plc will whine without that
959 # also, it's only for adding
960 if record_dict['type'] == 'user':
961 if not 'first_name' in record_dict:
962 record_dict['first_name'] = record_dict['hrn']
963 if 'last_name' not in record_dict:
964 record_dict['last_name'] = record_dict['hrn']
965 return self.registry().Register(record_dict, auth_cred)
967 @register_command("[xml-filename]","")
968 def update(self, options, args):
969 """update record into registry (Update)
970 from command line options (recommended)
971 old-school method involving an xml file still supported"""
974 record_filepath = args[0]
975 rec_file = self.get_record_file(record_filepath)
976 record_dict.update(load_record_from_file(rec_file).todict())
978 record_dict.update(load_record_from_opts(options).todict())
979 # at the very least we need 'type' here
980 if 'type' not in record_dict:
984 # don't translate into an object, as this would possibly distort
985 # user-provided data; e.g. add an 'email' field to Users
986 if record_dict['type'] == "user":
987 if record_dict['hrn'] == self.user:
988 cred = self.my_credential_string
990 cred = self.my_authority_credential_string()
991 elif record_dict['type'] in ["slice"]:
993 cred = self.slice_credential_string(record_dict['hrn'])
994 except ServerException, e:
995 # XXX smbaker -- once we have better error return codes, update this
996 # to do something better than a string compare
997 if "Permission error" in e.args[0]:
998 cred = self.my_authority_credential_string()
1001 elif record_dict['type'] in ["authority"]:
1002 cred = self.my_authority_credential_string()
1003 elif record_dict['type'] == 'node':
1004 cred = self.my_authority_credential_string()
1006 raise "unknown record type" + record_dict['type']
1007 if options.show_credential:
1008 show_credentials(cred)
1009 return self.registry().Update(record_dict, cred)
1011 @register_command("name","")
1012 def remove(self, options, args):
1013 "remove registry record by name (Remove)"
1014 auth_cred = self.my_authority_credential_string()
1022 if options.show_credential:
1023 show_credentials(auth_cred)
1024 return self.registry().Remove(hrn, auth_cred, type)
1026 # ==================================================================
1027 # Slice-related commands
1028 # ==================================================================
1030 @register_command("","")
1031 def slices(self, options, args):
1032 "list instantiated slices (ListSlices) - returns urn's"
1033 server = self.sliceapi()
1035 creds = [self.my_credential_string]
1036 # options and call_id when supported
1038 api_options['call_id']=unique_call_id()
1039 if options.show_credential:
1040 show_credentials(creds)
1041 result = server.ListSlices(creds, *self.ois(server,api_options))
1042 value = ReturnValue.get_value(result)
1043 if self.options.raw:
1044 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1049 # show rspec for named slice
1050 @register_command("","")
1051 def resources(self, options, args):
1053 discover available resources (ListResources)
1055 server = self.sliceapi()
1058 creds = [self.my_credential]
1059 if options.delegate:
1060 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1061 if options.show_credential:
1062 show_credentials(creds)
1064 # no need to check if server accepts the options argument since the options has
1065 # been a required argument since v1 API
1067 # always send call_id to v2 servers
1068 api_options ['call_id'] = unique_call_id()
1069 # ask for cached value if available
1070 api_options ['cached'] = True
1072 api_options['info'] = options.info
1073 if options.list_leases:
1074 api_options['list_leases'] = options.list_leases
1076 if options.current == True:
1077 api_options['cached'] = False
1079 api_options['cached'] = True
1080 if options.rspec_version:
1081 version_manager = VersionManager()
1082 server_version = self.get_cached_server_version(server)
1083 if 'sfa' in server_version:
1084 # just request the version the client wants
1085 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1087 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1089 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1090 result = server.ListResources (creds, api_options)
1091 value = ReturnValue.get_value(result)
1092 if self.options.raw:
1093 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1094 if options.file is not None:
1095 save_rspec_to_file(value, options.file)
1096 if (self.options.raw is None) and (options.file is None):
1097 display_rspec(value, options.format)
1101 @register_command("slice_hrn","")
1102 def describe(self, options, args):
1104 shows currently allocated/provisioned resources
1105 of the named slice or set of slivers (Describe)
1107 server = self.sliceapi()
1110 creds = [self.slice_credential(args[0])]
1111 if options.delegate:
1112 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1113 if options.show_credential:
1114 show_credentials(creds)
1116 api_options = {'call_id': unique_call_id(),
1118 'info': options.info,
1119 'list_leases': options.list_leases,
1120 'geni_rspec_version': {'type': 'geni', 'version': '3.0'},
1122 if options.rspec_version:
1123 version_manager = VersionManager()
1124 server_version = self.get_cached_server_version(server)
1125 if 'sfa' in server_version:
1126 # just request the version the client wants
1127 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1129 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1130 urn = Xrn(args[0], type='slice').get_urn()
1131 result = server.Describe([urn], creds, 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):
1138 display_rspec(value, options.format)
1142 @register_command("slice_hrn","")
1143 def delete(self, options, args):
1145 de-allocate and de-provision all or named slivers of the slice (Delete)
1147 server = self.sliceapi()
1151 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1154 slice_cred = self.slice_credential(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.Delete([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 rspec","")
1171 def allocate(self, options, args):
1173 allocate resources to the named slice (Allocate)
1175 server = self.sliceapi()
1176 server_version = self.get_cached_server_version(server)
1178 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1181 creds = [self.slice_credential(slice_hrn)]
1183 delegated_cred = None
1184 if server_version.get('interface') == 'slicemgr':
1185 # delegate our cred to the slice manager
1186 # do not delegate cred to slicemgr...not working at the moment
1188 #if server_version.get('hrn'):
1189 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1190 #elif server_version.get('urn'):
1191 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1193 if options.show_credential:
1194 show_credentials(creds)
1197 rspec_file = self.get_rspec_file(args[1])
1198 rspec = open(rspec_file).read()
1200 api_options ['call_id'] = unique_call_id()
1201 result = server.Allocate(slice_urn, creds, rspec, api_options)
1202 value = ReturnValue.get_value(result)
1203 if self.options.raw:
1204 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1205 if options.file is not None:
1206 save_rspec_to_file (value, options.file)
1207 if (self.options.raw is None) and (options.file is None):
1212 @register_command("slice_hrn","")
1213 def provision(self, options, args):
1215 provision already allocated resources of named slice (Provision)
1217 server = self.sliceapi()
1218 server_version = self.get_cached_server_version(server)
1220 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1223 creds = [self.slice_credential(slice_hrn)]
1224 delegated_cred = None
1225 if server_version.get('interface') == 'slicemgr':
1226 # delegate our cred to the slice manager
1227 # do not delegate cred to slicemgr...not working at the moment
1229 #if server_version.get('hrn'):
1230 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1231 #elif server_version.get('urn'):
1232 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1234 if options.show_credential:
1235 show_credentials(creds)
1238 api_options ['call_id'] = unique_call_id()
1240 # set the requtested rspec version
1241 version_manager = VersionManager()
1242 rspec_version = version_manager._get_version('geni', '3.0').to_dict()
1243 api_options['geni_rspec_version'] = rspec_version
1246 # need to pass along user keys to the aggregate.
1248 # { urn: urn:publicid:IDN+emulab.net+user+alice
1249 # keys: [<ssh key A>, <ssh key B>]
1252 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1253 if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
1254 slice_record = slice_records[0]
1255 user_hrns = slice_record['researcher']
1256 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1257 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1258 users = pg_users_arg(user_records)
1260 api_options['geni_users'] = users
1261 result = server.Provision([slice_urn], creds, api_options)
1262 value = ReturnValue.get_value(result)
1263 if self.options.raw:
1264 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1265 if options.file is not None:
1266 save_rspec_to_file (value, options.file)
1267 if (self.options.raw is None) and (options.file is None):
1271 @register_command("slice_hrn","")
1272 def status(self, options, args):
1274 retrieve the status of the slivers belonging to tne named slice (Status)
1276 server = self.sliceapi()
1280 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1283 slice_cred = self.slice_credential(slice_hrn)
1284 creds = [slice_cred]
1286 # options and call_id when supported
1288 api_options['call_id']=unique_call_id()
1289 if options.show_credential:
1290 show_credentials(creds)
1291 result = server.Status([slice_urn], creds, *self.ois(server,api_options))
1292 value = ReturnValue.get_value(result)
1293 if self.options.raw:
1294 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1297 # Thierry: seemed to be missing
1300 @register_command("slice_hrn action","")
1301 def action(self, options, args):
1303 Perform the named operational action on these slivers
1305 server = self.sliceapi()
1310 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1312 slice_cred = self.slice_credential(args[0])
1313 creds = [slice_cred]
1314 if options.delegate:
1315 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1316 creds.append(delegated_cred)
1318 result = server.PerformOperationalAction([slice_urn], creds, action , api_options)
1319 value = ReturnValue.get_value(result)
1320 if self.options.raw:
1321 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1326 @register_command("slice_hrn time","")
1327 def renew(self, options, args):
1329 renew slice (RenewSliver)
1331 server = self.sliceapi()
1335 [ slice_hrn, input_time ] = args
1337 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1338 # time: don't try to be smart on the time format, server-side will
1340 slice_cred = self.slice_credential(args[0])
1341 creds = [slice_cred]
1342 # options and call_id when supported
1344 api_options['call_id']=unique_call_id()
1345 if options.show_credential:
1346 show_credentials(creds)
1347 result = server.Renew([slice_urn], creds, input_time, *self.ois(server,api_options))
1348 value = ReturnValue.get_value(result)
1349 if self.options.raw:
1350 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1356 @register_command("slice_hrn","")
1357 def shutdown(self, options, args):
1359 shutdown named slice (Shutdown)
1361 server = self.sliceapi()
1364 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1366 slice_cred = self.slice_credential(slice_hrn)
1367 creds = [slice_cred]
1368 result = server.Shutdown(slice_urn, creds)
1369 value = ReturnValue.get_value(result)
1370 if self.options.raw:
1371 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1377 @register_command("[name]","")
1378 def gid(self, options, args):
1380 Create a GID (CreateGid)
1385 target_hrn = args[0]
1386 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1387 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1389 filename = options.file
1391 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1392 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1393 GID(string=gid).save_to_file(filename)
1395 ####################
1396 @register_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1398 will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1399 the set of credentials in the scope for this call would be
1400 (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1402 (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1404 (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1405 (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1406 because of the two -s options
1409 def delegate (self, options, args):
1411 (locally) create delegate credential for use by given hrn
1412 make sure to check for 'sfi myslice' instead if you plan
1419 # support for several delegations in the same call
1420 # so first we gather the things to do
1422 for slice_hrn in options.delegate_slices:
1423 message="%s.slice"%slice_hrn
1424 original = self.slice_credential_string(slice_hrn)
1425 tuples.append ( (message, original,) )
1426 if options.delegate_pi:
1427 my_authority=self.authority
1428 message="%s.pi"%my_authority
1429 original = self.my_authority_credential_string()
1430 tuples.append ( (message, original,) )
1431 for auth_hrn in options.delegate_auths:
1432 message="%s.auth"%auth_hrn
1433 original=self.authority_credential_string(auth_hrn)
1434 tuples.append ( (message, original, ) )
1435 # if nothing was specified at all at this point, let's assume -u
1436 if not tuples: options.delegate_user=True
1438 if options.delegate_user:
1439 message="%s.user"%self.user
1440 original = self.my_credential_string
1441 tuples.append ( (message, original, ) )
1443 # default type for beneficial is user unless -A
1444 if options.delegate_to_authority: to_type='authority'
1445 else: to_type='user'
1447 # let's now handle all this
1448 # it's all in the filenaming scheme
1449 for (message,original) in tuples:
1450 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1451 delegated_credential = Credential (string=delegated_string)
1452 filename = os.path.join ( self.options.sfi_dir,
1453 "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1454 delegated_credential.save_to_file(filename, save_parents=True)
1455 self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1457 ####################
1458 @register_command("","""$ less +/myslice sfi_config
1460 backend = http://manifold.pl.sophia.inria.fr:7080
1461 # the HRN that myslice uses, so that we are delegating to
1462 delegate = ple.upmc.slicebrowser
1463 # platform - this is a myslice concept
1465 # username - as of this writing (May 2013) a simple login name
1469 will first collect the slices that you are part of, then make sure
1470 all your credentials are up-to-date (read: refresh expired ones)
1471 then compute delegated credentials for user 'ple.upmc.slicebrowser'
1472 and upload them all on myslice backend, using 'platform' and 'user'.
1473 A password will be prompted for the upload part.
1475 $ sfi -v myslice -- or sfi -vv myslice
1476 same but with more and more verbosity
1479 is synonym to sfi myslice as no other command starts with an 'm'
1481 ) # register_command
1482 def myslice (self, options, args):
1484 """ This helper is for refreshing your credentials at myslice; it will
1485 * compute all the slices that you currently have credentials on
1486 * refresh all your credentials (you as a user and pi, your slices)
1487 * upload them to the manifold backend server
1488 for last phase, sfi_config is read to look for the [myslice] section,
1489 and namely the 'backend', 'delegate' and 'user' settings"""
1496 ### the rough sketch goes like this
1497 # (a) rain check for sufficient config in sfi_config
1498 # we don't allow to override these settings for now
1500 myslice_keys=['backend', 'delegate', 'platform', 'username']
1501 for key in myslice_keys:
1502 full_key="MYSLICE_" + key.upper()
1503 value=getattr(self.config,full_key,None)
1504 if value: myslice_dict[key]=value
1505 else: print "Unsufficient config, missing key %s in [myslice] section of sfi_config"%key
1506 if len(myslice_dict) != len(myslice_keys):
1509 # (b) figure whether we are PI for the authority where we belong
1510 sfi_logger.info("Resolving our own id")
1511 my_records=self.registry().Resolve(self.user,self.my_credential_string)
1512 if len(my_records)!=1: print "Cannot Resolve %s -- exiting"%self.user; sys.exit(1)
1513 my_record=my_records[0]
1514 sfi_logger.info("Checking for authorities that we are PI for")
1515 my_auths = my_record['reg-pi-authorities']
1516 sfi_logger.debug("Found %d authorities: %s"%(len(my_auths),my_auths))
1518 # (c) get the set of slices that we are in
1519 sfi_logger.info("Checking for slices that we are member of")
1520 my_slices=my_record['reg-slices']
1521 sfi_logger.debug("Found %d slices: %s"%(len(my_slices),my_slices))
1523 # (d) make sure we have *valid* credentials for all these
1525 hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1526 for auth_hrn in my_auths:
1527 hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1528 for slice_hrn in my_slices:
1529 hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1531 # (e) check for the delegated version of these
1532 # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever
1533 # switch to myslice using an authority instead of a user
1534 delegatee_type='user'
1535 delegatee_hrn=myslice_dict['delegate']
1536 hrn_delegated_credentials = [
1537 (hrn, htype, self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type),)
1538 for (hrn, htype, credential) in hrn_credentials ]
1540 # (f) and finally upload them to manifold server
1541 # xxx todo add an option so the password can be set on the command line
1542 # (but *NOT* in the config file) so other apps can leverage this
1543 uploader = ManifoldUploader (logger=sfi_logger,
1544 url=myslice_dict['backend'],
1545 platform=myslice_dict['platform'],
1546 username=myslice_dict['username'])
1547 for (hrn,htype,delegated_credential) in hrn_delegated_credentials:
1548 sfi_logger.info("Uploading delegated credential for %s (%s)"%(hrn,htype))
1549 uploader.upload(delegated_credential,message=hrn)
1550 # at first I thought we would want to save these,
1551 # like 'sfi delegate does' but on second thought
1552 # it is probably not helpful as people would not
1553 # need to run 'sfi delegate' at all anymore
1556 # Thierry: I'm turning this off as a command, no idea what it's used for
1557 # @register_command("cred","")
1558 def trusted(self, options, args):
1560 return the trusted certs at this interface (get_trusted_certs)
1562 trusted_certs = self.registry().get_trusted_certs()
1563 for trusted_cert in trusted_certs:
1564 gid = GID(string=trusted_cert)
1566 cert = Certificate(string=trusted_cert)
1567 self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())