2 # sfi.py - basic SFA command-line client
3 # this module is also used in sfascan
17 from lxml import etree
18 from StringIO import StringIO
19 from optparse import OptionParser
20 from pprint import PrettyPrinter
21 from tempfile import mkstemp
23 from sfa.trust.certificate import Keypair, Certificate
24 from sfa.trust.gid import GID
25 from sfa.trust.credential import Credential
26 from sfa.trust.sfaticket import SfaTicket
28 from sfa.util.faults import SfaInvalidArgument
29 from sfa.util.sfalogging import sfi_logger
30 from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn, Xrn
31 from sfa.util.config import Config
32 from sfa.util.version import version_core
33 from sfa.util.cache import Cache
35 from sfa.storage.record import Record
37 from sfa.rspecs.rspec import RSpec
38 from sfa.rspecs.rspec_converter import RSpecConverter
39 from sfa.rspecs.version_manager import VersionManager
41 from sfa.client.sfaclientlib import SfaClientBootstrap
42 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
43 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
44 from sfa.client.return_value import ReturnValue
45 from sfa.client.candidates import Candidates
46 from sfa.client.manifolduploader import ManifoldUploader
50 from sfa.client.common import optparse_listvalue_callback, optparse_dictvalue_callback, \
51 terminal_render, filter_records
54 def display_rspec(rspec, format='rspec'):
56 tree = etree.parse(StringIO(rspec))
58 result = root.xpath("./network/site/node/hostname/text()")
59 elif format in ['ip']:
60 # The IP address is not yet part of the new RSpec
61 # so this doesn't do anything yet.
62 tree = etree.parse(StringIO(rspec))
64 result = root.xpath("./network/site/node/ipv4/text()")
71 def display_list(results):
72 for result in results:
75 def display_records(recordList, dump=False):
76 ''' Print all fields in the record'''
77 for record in recordList:
78 display_record(record, dump)
80 def display_record(record, dump=False):
82 record.dump(sort=True)
84 info = record.getdict()
85 print "%s (%s)" % (info['hrn'], info['type'])
89 def credential_printable (credential_string):
90 credential=Credential(string=credential_string)
92 result += credential.get_summary_tostring()
94 rights = credential.get_privileges()
95 result += "rights=%s"%rights
99 def show_credentials (cred_s):
100 if not isinstance (cred_s,list): cred_s = [cred_s]
102 print "Using Credential %s"%credential_printable(cred)
105 def save_raw_to_file(var, filename, format="text", banner=None):
107 # if filename is "-", send it to stdout
110 f = open(filename, "w")
115 elif format == "pickled":
116 f.write(pickle.dumps(var))
117 elif format == "json":
118 if hasattr(json, "dumps"):
119 f.write(json.dumps(var)) # python 2.6
121 f.write(json.write(var)) # python 2.5
123 # this should never happen
124 print "unknown output format", format
126 f.write('\n'+banner+"\n")
128 def save_rspec_to_file(rspec, filename):
129 if not filename.endswith(".rspec"):
130 filename = filename + ".rspec"
131 f = open(filename, 'w')
136 def save_records_to_file(filename, record_dicts, format="xml"):
139 for record_dict in record_dicts:
141 save_record_to_file(filename + "." + str(index), record_dict)
143 save_record_to_file(filename, record_dict)
145 elif format == "xmllist":
146 f = open(filename, "w")
147 f.write("<recordlist>\n")
148 for record_dict in record_dicts:
149 record_obj=Record(dict=record_dict)
150 f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
151 f.write("</recordlist>\n")
153 elif format == "hrnlist":
154 f = open(filename, "w")
155 for record_dict in record_dicts:
156 record_obj=Record(dict=record_dict)
157 f.write(record_obj.hrn + "\n")
160 # this should never happen
161 print "unknown output format", format
163 def save_record_to_file(filename, record_dict):
164 record = Record(dict=record_dict)
165 xml = record.save_as_xml()
166 f=codecs.open(filename, encoding='utf-8',mode="w")
171 # minimally check a key argument
172 def check_ssh_key (key):
173 good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
174 return re.match(good_ssh_key, key, re.IGNORECASE)
177 def load_record_from_opts(options):
179 if hasattr(options, 'xrn') and options.xrn:
180 if hasattr(options, 'type') and options.type:
181 xrn = Xrn(options.xrn, options.type)
183 xrn = Xrn(options.xrn)
184 record_dict['urn'] = xrn.get_urn()
185 record_dict['hrn'] = xrn.get_hrn()
186 record_dict['type'] = xrn.get_type()
187 if hasattr(options, 'key') and options.key:
189 pubkey = open(options.key, 'r').read()
192 if not check_ssh_key (pubkey):
193 raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format")
194 record_dict['keys'] = [pubkey]
195 if hasattr(options, 'slices') and options.slices:
196 record_dict['slices'] = options.slices
197 if hasattr(options, 'researchers') and options.researchers:
198 record_dict['researcher'] = options.researchers
199 if hasattr(options, 'email') and options.email:
200 record_dict['email'] = options.email
201 if hasattr(options, 'pis') and options.pis:
202 record_dict['pi'] = options.pis
204 # handle extra settings
205 record_dict.update(options.extras)
207 return Record(dict=record_dict)
209 def load_record_from_file(filename):
210 f=codecs.open(filename, encoding="utf-8", mode="r")
211 xml_string = f.read()
213 return Record(xml=xml_string)
217 def unique_call_id(): return uuid.uuid4().urn
219 ########## a simple model for maintaing 3 doc attributes per command (instead of just one)
220 # essentially for the methods that implement a subcommand like sfi list
221 # we need to keep track of
222 # (*) doc a few lines that tell what it does, still located in __doc__
223 # (*) args_string a simple one-liner that describes mandatory arguments
224 # (*) example well, one or several releant examples
226 # since __doc__ only accounts for one, we use this simple mechanism below
227 # however we keep doc in place for easier migration
229 from functools import wraps
231 # we use a list as well as a dict so we can keep track of the order
235 def register_command (args_string, example):
237 name=getattr(m,'__name__')
238 doc=getattr(m,'__doc__',"-- missing doc --")
239 doc=doc.strip(" \t\n")
240 commands_list.append(name)
241 commands_dict[name]=(doc, args_string, example)
243 def new_method (*args, **kwds): return m(*args, **kwds)
251 # dirty hack to make this class usable from the outside
252 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
255 def default_sfi_dir ():
256 if os.path.isfile("./sfi_config"):
259 return os.path.expanduser("~/.sfi/")
261 # dummy to meet Sfi's expectations for its 'options' field
262 # i.e. s/t we can do setattr on
266 def __init__ (self,options=None):
267 if options is None: options=Sfi.DummyOptions()
268 for opt in Sfi.required_options:
269 if not hasattr(options,opt): setattr(options,opt,None)
270 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
271 self.options = options
273 self.authority = None
274 self.logger = sfi_logger
275 self.logger.enable_console()
276 ### various auxiliary material that we keep at hand
278 # need to call this other than just 'config' as we have a command/method with that name
279 self.config_instance=None
280 self.config_file=None
281 self.client_bootstrap=None
283 ### suitable if no reasonable command has been provided
284 def print_commands_help (self, options):
285 verbose=getattr(options,'verbose')
286 format3="%18s %-15s %s"
289 print format3%("command","cmd_args","description")
293 self.create_global_parser().print_help()
294 # preserve order from the code
295 for command in commands_list:
296 (doc, args_string, example) = commands_dict[command]
299 doc=doc.replace("\n","\n"+35*' ')
300 print format3%(command,args_string,doc)
302 self.create_command_parser(command).print_help()
304 ### now if a known command was found we can be more verbose on that one
305 def print_help (self):
306 print "==================== Generic sfi usage"
307 self.sfi_parser.print_help()
308 (doc,_,example)=commands_dict[self.command]
309 print "\n==================== Purpose of %s"%self.command
311 print "\n==================== Specific usage for %s"%self.command
312 self.command_parser.print_help()
314 print "\n==================== %s example(s)"%self.command
317 def create_global_parser(self):
319 # Generate command line parser
320 parser = OptionParser(add_help_option=False,
321 usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
322 description="Commands: %s"%(" ".join(commands_list)))
323 parser.add_option("-r", "--registry", dest="registry",
324 help="root registry", metavar="URL", default=None)
325 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
326 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
327 parser.add_option("-R", "--raw", dest="raw", default=None,
328 help="Save raw, unparsed server response to a file")
329 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
330 help="raw file format ([text]|pickled|json)", default="text",
331 choices=("text","pickled","json"))
332 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
333 help="text string to write before and after raw output")
334 parser.add_option("-d", "--dir", dest="sfi_dir",
335 help="config & working directory - default is %default",
336 metavar="PATH", default=Sfi.default_sfi_dir())
337 parser.add_option("-u", "--user", dest="user",
338 help="user name", metavar="HRN", default=None)
339 parser.add_option("-a", "--auth", dest="auth",
340 help="authority name", metavar="HRN", default=None)
341 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
342 help="verbose mode - cumulative")
343 parser.add_option("-D", "--debug",
344 action="store_true", dest="debug", default=False,
345 help="Debug (xml-rpc) protocol messages")
346 # would it make sense to use ~/.ssh/id_rsa as a default here ?
347 parser.add_option("-k", "--private-key",
348 action="store", dest="user_private_key", default=None,
349 help="point to the private key file to use if not yet installed in sfi_dir")
350 parser.add_option("-t", "--timeout", dest="timeout", default=None,
351 help="Amout of time to wait before timing out the request")
352 parser.add_option("-h", "--help",
353 action="store_true", dest="help", default=False,
354 help="one page summary on commands & exit")
355 parser.disable_interspersed_args()
360 def create_command_parser(self, command):
361 if command not in commands_dict:
362 msg="Invalid command\n"
364 msg += ','.join(commands_list)
365 self.logger.critical(msg)
368 # retrieve args_string
369 (_, args_string, __) = commands_dict[command]
371 parser = OptionParser(add_help_option=False,
372 usage="sfi [sfi_options] %s [cmd_options] %s"
373 % (command, args_string))
374 parser.add_option ("-h","--help",dest='help',action='store_true',default=False,
375 help="Summary of one command usage")
377 if command in ("config"):
378 parser.add_option('-m', '--myslice', dest='myslice', action='store_true', default=False,
379 help='how myslice config variables as well')
381 if command in ("add", "update"):
382 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
383 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
384 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
385 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
387 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
388 default='', type="str", action='callback', callback=optparse_listvalue_callback)
389 parser.add_option('-r', '--researchers', dest='researchers', metavar='<researchers>',
390 help='Set/replace slice researchers', default='', type="str", action='callback',
391 callback=optparse_listvalue_callback)
392 parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
393 default='', type="str", action='callback', callback=optparse_listvalue_callback)
394 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
395 action="callback", callback=optparse_dictvalue_callback, nargs=1,
396 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
398 # show_credential option
399 if command in ("list","resources","create","add","update","remove","slices","delete","status","renew"):
400 parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
401 help="show credential(s) used in human-readable form")
402 # registy filter option
403 if command in ("list", "show", "remove"):
404 parser.add_option("-t", "--type", dest="type", type="choice",
405 help="type filter ([all]|user|slice|authority|node|aggregate)",
406 choices=("all", "user", "slice", "authority", "node", "aggregate"),
408 if command in ("show"):
409 parser.add_option("-k","--key",dest="keys",action="append",default=[],
410 help="specify specific keys to be displayed from record")
411 if command in ("resources"):
413 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
414 help="schema type and version of resulting RSpec")
415 # disable/enable cached rspecs
416 parser.add_option("-c", "--current", dest="current", default=False,
418 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
420 parser.add_option("-f", "--format", dest="format", type="choice",
421 help="display format ([xml]|dns|ip)", default="xml",
422 choices=("xml", "dns", "ip"))
423 #panos: a new option to define the type of information about resources a user is interested in
424 parser.add_option("-i", "--info", dest="info",
425 help="optional component information", default=None)
426 # a new option to retreive or not reservation-oriented RSpecs (leases)
427 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
428 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
429 choices=("all", "resources", "leases"), default="resources")
432 # 'create' does return the new rspec, makes sense to save that too
433 if command in ("resources", "show", "list", "gid", 'create'):
434 parser.add_option("-o", "--output", dest="file",
435 help="output XML to file", metavar="FILE", default=None)
437 if command in ("show", "list"):
438 parser.add_option("-f", "--format", dest="format", type="choice",
439 help="display format ([text]|xml)", default="text",
440 choices=("text", "xml"))
442 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
443 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
444 choices=("xml", "xmllist", "hrnlist"))
445 if command == 'list':
446 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
447 help="list all child records", default=False)
448 parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
449 help="gives details, like user keys", default=False)
450 if command in ("delegate"):
451 parser.add_option("-u", "--user",
452 action="store_true", dest="delegate_user", default=False,
453 help="delegate your own credentials; default if no other option is provided")
454 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
455 metavar="slice_hrn", help="delegate cred. for slice HRN")
456 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
457 metavar='auth_hrn', help="delegate cred for auth HRN")
458 # this primarily is a shorthand for -a my_hrn
459 parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
460 help="delegate your PI credentials, so s.t. like -a your_hrn^")
461 parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
462 help="""by default the mandatory argument is expected to be a user,
463 use this if you mean an authority instead""")
465 if command in ("version"):
466 parser.add_option("-R","--registry-version",
467 action="store_true", dest="version_registry", default=False,
468 help="probe registry version instead of sliceapi")
469 parser.add_option("-l","--local",
470 action="store_true", dest="version_local", default=False,
471 help="display version of the local client")
477 # Main: parse arguments and dispatch to command
479 def dispatch(self, command, command_options, command_args):
480 method=getattr(self, command, None)
482 print "Unknown command %s"%command
484 return method(command_options, command_args)
487 self.sfi_parser = self.create_global_parser()
488 (options, args) = self.sfi_parser.parse_args()
490 self.print_commands_help(options)
492 self.options = options
494 self.logger.setLevelFromOptVerbose(self.options.verbose)
497 self.logger.critical("No command given. Use -h for help.")
498 self.print_commands_help(options)
501 # complete / find unique match with command set
502 command_candidates = Candidates (commands_list)
504 command = command_candidates.only_match(input)
506 self.print_commands_help(options)
508 # second pass options parsing
510 self.command_parser = self.create_command_parser(command)
511 (command_options, command_args) = self.command_parser.parse_args(args[1:])
512 if command_options.help:
515 self.command_options = command_options
519 self.logger.debug("Command=%s" % self.command)
522 self.dispatch(command, command_options, command_args)
526 self.logger.log_exc ("sfi command %s failed"%command)
532 def read_config(self):
533 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
534 shell_config_file = os.path.join(self.options.sfi_dir,"sfi_config.sh")
536 if Config.is_ini(config_file):
537 config = Config (config_file)
539 # try upgrading from shell config format
540 fp, fn = mkstemp(suffix='sfi_config', text=True)
542 # we need to preload the sections we want parsed
543 # from the shell config
544 config.add_section('sfi')
545 # sface users should be able to use this same file to configure their stuff
546 config.add_section('sface')
547 # manifold users should be able to specify the details
548 # of their backend server here for 'sfi myslice'
549 config.add_section('myslice')
550 config.load(config_file)
552 shutil.move(config_file, shell_config_file)
554 config.save(config_file)
557 self.logger.critical("Failed to read configuration file %s"%config_file)
558 self.logger.info("Make sure to remove the export clauses and to add quotes")
559 if self.options.verbose==0:
560 self.logger.info("Re-run with -v for more details")
562 self.logger.log_exc("Could not read config file %s"%config_file)
565 self.config_instance=config
568 if (self.options.sm is not None):
569 self.sm_url = self.options.sm
570 elif hasattr(config, "SFI_SM"):
571 self.sm_url = config.SFI_SM
573 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
577 if (self.options.registry is not None):
578 self.reg_url = self.options.registry
579 elif hasattr(config, "SFI_REGISTRY"):
580 self.reg_url = config.SFI_REGISTRY
582 self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
586 if (self.options.user is not None):
587 self.user = self.options.user
588 elif hasattr(config, "SFI_USER"):
589 self.user = config.SFI_USER
591 self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
595 if (self.options.auth is not None):
596 self.authority = self.options.auth
597 elif hasattr(config, "SFI_AUTH"):
598 self.authority = config.SFI_AUTH
600 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
603 self.config_file=config_file
608 # Get various credential and spec files
610 # Establishes limiting conventions
611 # - conflates MAs and SAs
612 # - assumes last token in slice name is unique
614 # Bootstraps credentials
615 # - bootstrap user credential from self-signed certificate
616 # - bootstrap authority credential from user credential
617 # - bootstrap slice credential from user credential
620 # init self-signed cert, user credentials and gid
621 def bootstrap (self):
622 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
624 # if -k is provided, use this to initialize private key
625 if self.options.user_private_key:
626 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
628 # trigger legacy compat code if needed
629 # the name has changed from just <leaf>.pkey to <hrn>.pkey
630 if not os.path.isfile(client_bootstrap.private_key_filename()):
631 self.logger.info ("private key not found, trying legacy name")
633 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
634 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
635 client_bootstrap.init_private_key_if_missing (legacy_private_key)
636 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
638 self.logger.log_exc("Can't find private key ")
642 client_bootstrap.bootstrap_my_gid()
643 # extract what's needed
644 self.private_key = client_bootstrap.private_key()
645 self.my_credential_string = client_bootstrap.my_credential_string ()
646 self.my_gid = client_bootstrap.my_gid ()
647 self.client_bootstrap = client_bootstrap
650 def my_authority_credential_string(self):
651 if not self.authority:
652 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
654 return self.client_bootstrap.authority_credential_string (self.authority)
656 def authority_credential_string(self, auth_hrn):
657 return self.client_bootstrap.authority_credential_string (auth_hrn)
659 def slice_credential_string(self, name):
660 return self.client_bootstrap.slice_credential_string (name)
663 # Management of the servers
668 if not hasattr (self, 'registry_proxy'):
669 self.logger.info("Contacting Registry at: %s"%self.reg_url)
670 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
671 timeout=self.options.timeout, verbose=self.options.debug)
672 return self.registry_proxy
676 if not hasattr (self, 'sliceapi_proxy'):
677 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
678 if hasattr(self.command_options,'component') and self.command_options.component:
679 # resolve the hrn at the registry
680 node_hrn = self.command_options.component
681 records = self.registry().Resolve(node_hrn, self.my_credential_string)
682 records = filter_records('node', records)
684 self.logger.warning("No such component:%r"% opts.component)
686 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
687 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
689 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
690 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
691 self.sm_url = 'http://' + self.sm_url
692 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
693 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
694 timeout=self.options.timeout, verbose=self.options.debug)
695 return self.sliceapi_proxy
697 def get_cached_server_version(self, server):
698 # check local cache first
701 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
702 cache_key = server.url + "-version"
704 cache = Cache(cache_file)
707 self.logger.info("Local cache not found at: %s" % cache_file)
710 version = cache.get(cache_key)
713 result = server.GetVersion()
714 version= ReturnValue.get_value(result)
715 # cache version for 20 minutes
716 cache.add(cache_key, version, ttl= 60*20)
717 self.logger.info("Updating cache file %s" % cache_file)
718 cache.save_to_file(cache_file)
722 ### resurrect this temporarily so we can support V1 aggregates for a while
723 def server_supports_options_arg(self, server):
725 Returns true if server support the optional call_id arg, false otherwise.
727 server_version = self.get_cached_server_version(server)
729 # xxx need to rewrite this
730 if int(server_version.get('geni_api')) >= 2:
734 def server_supports_call_id_arg(self, server):
735 server_version = self.get_cached_server_version(server)
737 if 'sfa' in server_version and 'code_tag' in server_version:
738 code_tag = server_version['code_tag']
739 code_tag_parts = code_tag.split("-")
740 version_parts = code_tag_parts[0].split(".")
741 major, minor = version_parts[0], version_parts[1]
742 rev = code_tag_parts[1]
743 if int(major) == 1 and minor == 0 and build >= 22:
747 ### ois = options if supported
748 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
749 def ois (self, server, option_dict):
750 if self.server_supports_options_arg (server):
752 elif self.server_supports_call_id_arg (server):
753 return [ unique_call_id () ]
757 ### cis = call_id if supported - like ois
758 def cis (self, server):
759 if self.server_supports_call_id_arg (server):
760 return [ unique_call_id ]
764 ######################################## miscell utilities
765 def get_rspec_file(self, rspec):
766 if (os.path.isabs(rspec)):
769 file = os.path.join(self.options.sfi_dir, rspec)
770 if (os.path.isfile(file)):
773 self.logger.critical("No such rspec file %s"%rspec)
776 def get_record_file(self, record):
777 if (os.path.isabs(record)):
780 file = os.path.join(self.options.sfi_dir, record)
781 if (os.path.isfile(file)):
784 self.logger.critical("No such registry record file %s"%record)
788 #==========================================================================
789 # Following functions implement the commands
791 # Registry-related commands
792 #==========================================================================
794 @register_command("","")
795 def config (self, options, args):
796 "Display contents of current config"
797 print "# From configuration file %s"%self.config_file
798 flags=[ ('sfi', [ ('registry','reg_url'),
799 ('auth','authority'),
805 flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
807 for (section, tuples) in flags:
810 for (external_name, internal_name) in tuples:
811 print "%-20s = %s"%(external_name,getattr(self,internal_name))
814 varname="%s_%s"%(section.upper(),name.upper())
815 value=getattr(self.config_instance,varname)
816 print "%-20s = %s"%(name,value)
818 @register_command("","")
819 def version(self, options, args):
821 display an SFA server version (GetVersion)
822 or version information about sfi itself
824 if options.version_local:
825 version=version_core()
827 if options.version_registry:
828 server=self.registry()
830 server = self.sliceapi()
831 result = server.GetVersion()
832 version = ReturnValue.get_value(result)
834 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
836 pprinter = PrettyPrinter(indent=4)
837 pprinter.pprint(version)
839 @register_command("authority","")
840 def list(self, options, args):
842 list entries in named authority registry (List)
849 if options.recursive:
850 opts['recursive'] = options.recursive
852 if options.show_credential:
853 show_credentials(self.my_credential_string)
855 list = self.registry().List(hrn, self.my_credential_string, options)
857 raise Exception, "Not enough parameters for the 'list' command"
859 # filter on person, slice, site, node, etc.
860 # This really should be in the self.filter_records funct def comment...
861 list = filter_records(options.type, list)
862 terminal_render (list, options)
864 save_records_to_file(options.file, list, options.fileformat)
867 @register_command("name","")
868 def show(self, options, args):
870 show details about named registry record (Resolve)
876 # explicitly require Resolve to run in details mode
877 record_dicts = self.registry().Resolve(hrn, self.my_credential_string, {'details':True})
878 record_dicts = filter_records(options.type, record_dicts)
880 self.logger.error("No record of type %s"% options.type)
882 # user has required to focus on some keys
884 def project (record):
886 for key in options.keys:
887 try: projected[key]=record[key]
890 record_dicts = [ project (record) for record in record_dicts ]
891 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
892 for record in records:
893 if (options.format == "text"): record.dump(sort=True)
894 else: print record.save_as_xml()
896 save_records_to_file(options.file, record_dicts, options.fileformat)
899 @register_command("[xml-filename]","")
900 def add(self, options, args):
901 """add record into registry (Register)
902 from command line options (recommended)
903 old-school method involving an xml file still supported"""
905 auth_cred = self.my_authority_credential_string()
906 if options.show_credential:
907 show_credentials(auth_cred)
914 record_filepath = args[0]
915 rec_file = self.get_record_file(record_filepath)
916 record_dict.update(load_record_from_file(rec_file).todict())
918 print "Cannot load record file %s"%record_filepath
921 record_dict.update(load_record_from_opts(options).todict())
922 # we should have a type by now
923 if 'type' not in record_dict :
926 # this is still planetlab dependent.. as plc will whine without that
927 # also, it's only for adding
928 if record_dict['type'] == 'user':
929 if not 'first_name' in record_dict:
930 record_dict['first_name'] = record_dict['hrn']
931 if 'last_name' not in record_dict:
932 record_dict['last_name'] = record_dict['hrn']
933 return self.registry().Register(record_dict, auth_cred)
935 @register_command("[xml-filename]","")
936 def update(self, options, args):
937 """update record into registry (Update)
938 from command line options (recommended)
939 old-school method involving an xml file still supported"""
942 record_filepath = args[0]
943 rec_file = self.get_record_file(record_filepath)
944 record_dict.update(load_record_from_file(rec_file).todict())
946 record_dict.update(load_record_from_opts(options).todict())
947 # at the very least we need 'type' here
948 if 'type' not in record_dict:
952 # don't translate into an object, as this would possibly distort
953 # user-provided data; e.g. add an 'email' field to Users
954 if record_dict['type'] == "user":
955 if record_dict['hrn'] == self.user:
956 cred = self.my_credential_string
958 cred = self.my_authority_credential_string()
959 elif record_dict['type'] in ["slice"]:
961 cred = self.slice_credential_string(record_dict['hrn'])
962 except ServerException, e:
963 # XXX smbaker -- once we have better error return codes, update this
964 # to do something better than a string compare
965 if "Permission error" in e.args[0]:
966 cred = self.my_authority_credential_string()
969 elif record_dict['type'] in ["authority"]:
970 cred = self.my_authority_credential_string()
971 elif record_dict['type'] == 'node':
972 cred = self.my_authority_credential_string()
974 raise "unknown record type" + record_dict['type']
975 if options.show_credential:
976 show_credentials(cred)
977 return self.registry().Update(record_dict, cred)
979 @register_command("hrn","")
980 def remove(self, options, args):
981 "remove registry record by name (Remove)"
982 auth_cred = self.my_authority_credential_string()
990 if options.show_credential:
991 show_credentials(auth_cred)
992 return self.registry().Remove(hrn, auth_cred, type)
994 # ==================================================================
995 # Slice-related commands
996 # ==================================================================
998 @register_command("","")
999 def slices(self, options, args):
1000 "list instantiated slices (ListSlices) - returns urn's"
1001 server = self.sliceapi()
1003 creds = [self.my_credential_string]
1004 # options and call_id when supported
1006 api_options['call_id']=unique_call_id()
1007 if options.show_credential:
1008 show_credentials(creds)
1009 result = server.ListSlices(creds, *self.ois(server,api_options))
1010 value = ReturnValue.get_value(result)
1011 if self.options.raw:
1012 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1017 # show rspec for named slice
1018 @register_command("[slice_hrn]","")
1019 def resources(self, options, args):
1021 with no arg, discover available resources, (ListResources)
1022 or with an slice hrn, shows currently provisioned resources
1024 server = self.sliceapi()
1029 the_credential=self.slice_credential_string(args[0])
1030 creds.append(the_credential)
1032 the_credential=self.my_credential_string
1033 creds.append(the_credential)
1034 if options.show_credential:
1035 show_credentials(creds)
1037 # no need to check if server accepts the options argument since the options has
1038 # been a required argument since v1 API
1040 # always send call_id to v2 servers
1041 api_options ['call_id'] = unique_call_id()
1042 # ask for cached value if available
1043 api_options ['cached'] = True
1046 api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
1048 api_options['info'] = options.info
1049 if options.list_leases:
1050 api_options['list_leases'] = options.list_leases
1052 if options.current == True:
1053 api_options['cached'] = False
1055 api_options['cached'] = True
1056 if options.rspec_version:
1057 version_manager = VersionManager()
1058 server_version = self.get_cached_server_version(server)
1059 if 'sfa' in server_version:
1060 # just request the version the client wants
1061 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1063 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1065 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1066 result = server.ListResources (creds, api_options)
1067 value = ReturnValue.get_value(result)
1068 if self.options.raw:
1069 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1070 if options.file is not None:
1071 save_rspec_to_file(value, options.file)
1072 if (self.options.raw is None) and (options.file is None):
1073 display_rspec(value, options.format)
1077 @register_command("slice_hrn rspec","")
1078 def create(self, options, args):
1080 create or update named slice with given rspec (CreateSliver)
1082 server = self.sliceapi()
1084 # xxx do we need to check usage (len(args)) ?
1087 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1090 creds = [self.slice_credential_string(slice_hrn)]
1092 delegated_cred = None
1093 server_version = self.get_cached_server_version(server)
1094 if server_version.get('interface') == 'slicemgr':
1095 # delegate our cred to the slice manager
1096 # do not delegate cred to slicemgr...not working at the moment
1098 #if server_version.get('hrn'):
1099 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1100 #elif server_version.get('urn'):
1101 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1103 if options.show_credential:
1104 show_credentials(creds)
1107 rspec_file = self.get_rspec_file(args[1])
1108 rspec = open(rspec_file).read()
1111 # need to pass along user keys to the aggregate.
1113 # { urn: urn:publicid:IDN+emulab.net+user+alice
1114 # keys: [<ssh key A>, <ssh key B>]
1117 # xxx Thierry 2012 sept. 21
1118 # contrary to what I was first thinking, calling Resolve with details=False does not yet work properly here
1119 # I am turning details=True on again on a - hopefully - temporary basis, just to get this whole thing to work again
1120 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1121 # slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string], {'details':True})
1122 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']:
1123 slice_record = slice_records[0]
1124 user_hrns = slice_record['reg-researchers']
1125 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1126 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1128 if 'sfa' not in server_version:
1129 users = pg_users_arg(user_records)
1130 rspec = RSpec(rspec)
1131 rspec.filter({'component_manager_id': server_version['urn']})
1132 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1134 users = sfa_users_arg(user_records, slice_record)
1136 # do not append users, keys, or slice tags. Anything
1137 # not contained in this request will be removed from the slice
1139 # CreateSliver has supported the options argument for a while now so it should
1140 # be safe to assume this server support it
1142 api_options ['append'] = False
1143 api_options ['call_id'] = unique_call_id()
1144 result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
1145 value = ReturnValue.get_value(result)
1146 if self.options.raw:
1147 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1148 if options.file is not None:
1149 save_rspec_to_file (value, options.file)
1150 if (self.options.raw is None) and (options.file is None):
1155 @register_command("slice_hrn","")
1156 def delete(self, options, args):
1158 delete named slice (DeleteSliver)
1160 server = self.sliceapi()
1164 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1167 slice_cred = self.slice_credential_string(slice_hrn)
1168 creds = [slice_cred]
1170 # options and call_id when supported
1172 api_options ['call_id'] = unique_call_id()
1173 if options.show_credential:
1174 show_credentials(creds)
1175 result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
1176 value = ReturnValue.get_value(result)
1177 if self.options.raw:
1178 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1183 @register_command("slice_hrn","")
1184 def status(self, options, args):
1186 retrieve slice status (SliverStatus)
1188 server = self.sliceapi()
1192 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1195 slice_cred = self.slice_credential_string(slice_hrn)
1196 creds = [slice_cred]
1198 # options and call_id when supported
1200 api_options['call_id']=unique_call_id()
1201 if options.show_credential:
1202 show_credentials(creds)
1203 result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
1204 value = ReturnValue.get_value(result)
1205 if self.options.raw:
1206 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1210 @register_command("slice_hrn","")
1211 def start(self, options, args):
1213 start named slice (Start)
1215 server = self.sliceapi()
1219 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1222 slice_cred = self.slice_credential_string(args[0])
1223 creds = [slice_cred]
1224 # xxx Thierry - does this not need an api_options as well ?
1225 result = server.Start(slice_urn, creds)
1226 value = ReturnValue.get_value(result)
1227 if self.options.raw:
1228 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1233 @register_command("slice_hrn","")
1234 def stop(self, options, args):
1236 stop named slice (Stop)
1238 server = self.sliceapi()
1241 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1243 slice_cred = self.slice_credential_string(args[0])
1244 creds = [slice_cred]
1245 result = server.Stop(slice_urn, creds)
1246 value = ReturnValue.get_value(result)
1247 if self.options.raw:
1248 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1254 @register_command("slice_hrn","")
1255 def reset(self, options, args):
1257 reset named slice (reset_slice)
1259 server = self.sliceapi()
1262 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1264 slice_cred = self.slice_credential_string(args[0])
1265 creds = [slice_cred]
1266 result = server.reset_slice(creds, slice_urn)
1267 value = ReturnValue.get_value(result)
1268 if self.options.raw:
1269 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1274 @register_command("slice_hrn time","")
1275 def renew(self, options, args):
1277 renew slice (RenewSliver)
1279 server = self.sliceapi()
1283 [ slice_hrn, input_time ] = args
1285 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1286 # time: don't try to be smart on the time format, server-side will
1288 slice_cred = self.slice_credential_string(args[0])
1289 creds = [slice_cred]
1290 # options and call_id when supported
1292 api_options['call_id']=unique_call_id()
1293 if options.show_credential:
1294 show_credentials(creds)
1295 result = server.RenewSliver(slice_urn, creds, input_time, *self.ois(server,api_options))
1296 value = ReturnValue.get_value(result)
1297 if self.options.raw:
1298 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1304 @register_command("slice_hrn","")
1305 def shutdown(self, options, args):
1307 shutdown named slice (Shutdown)
1309 server = self.sliceapi()
1312 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1314 slice_cred = self.slice_credential_string(slice_hrn)
1315 creds = [slice_cred]
1316 result = server.Shutdown(slice_urn, creds)
1317 value = ReturnValue.get_value(result)
1318 if self.options.raw:
1319 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1325 @register_command("slice_hrn rspec","")
1326 def get_ticket(self, options, args):
1328 get a ticket for the specified slice
1330 server = self.sliceapi()
1332 slice_hrn, rspec_path = args[0], args[1]
1333 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1335 slice_cred = self.slice_credential_string(slice_hrn)
1336 creds = [slice_cred]
1338 rspec_file = self.get_rspec_file(rspec_path)
1339 rspec = open(rspec_file).read()
1340 # options and call_id when supported
1342 api_options['call_id']=unique_call_id()
1343 # get ticket at the server
1344 ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1346 file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1347 self.logger.info("writing ticket to %s"%file)
1348 ticket = SfaTicket(string=ticket_string)
1349 ticket.save_to_file(filename=file, save_parents=True)
1351 @register_command("ticket","")
1352 def redeem_ticket(self, options, args):
1354 Connects to nodes in a slice and redeems a ticket
1355 (slice hrn is retrieved from the ticket)
1357 ticket_file = args[0]
1359 # get slice hrn from the ticket
1360 # use this to get the right slice credential
1361 ticket = SfaTicket(filename=ticket_file)
1363 ticket_string = ticket.save_to_string(save_parents=True)
1365 slice_hrn = ticket.gidObject.get_hrn()
1366 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1367 #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1368 slice_cred = self.slice_credential_string(slice_hrn)
1370 # get a list of node hostnames from the RSpec
1371 tree = etree.parse(StringIO(ticket.rspec))
1372 root = tree.getroot()
1373 hostnames = root.xpath("./network/site/node/hostname/text()")
1375 # create an xmlrpc connection to the component manager at each of these
1376 # components and gall redeem_ticket
1378 for hostname in hostnames:
1380 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1381 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1382 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1383 server = self.server_proxy(hostname, CM_PORT, self.private_key,
1384 timeout=self.options.timeout, verbose=self.options.debug)
1385 server.RedeemTicket(ticket_string, slice_cred)
1386 self.logger.info("Success")
1387 except socket.gaierror:
1388 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1389 except Exception, e:
1390 self.logger.log_exc(e.message)
1393 @register_command("[name]","")
1394 def gid(self, options, args):
1396 Create a GID (CreateGid)
1401 target_hrn = args[0]
1402 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1403 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1405 filename = options.file
1407 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1408 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1409 GID(string=gid).save_to_file(filename)
1411 ####################
1412 @register_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1414 will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1415 the set of credentials in the scope for this call would be
1416 (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1418 (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1420 (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1421 (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1422 because of the two -s options
1425 def delegate (self, options, args):
1427 (locally) create delegate credential for use by given hrn
1428 make sure to check for 'sfi myslice' instead if you plan
1435 # support for several delegations in the same call
1436 # so first we gather the things to do
1438 for slice_hrn in options.delegate_slices:
1439 message="%s.slice"%slice_hrn
1440 original = self.slice_credential_string(slice_hrn)
1441 tuples.append ( (message, original,) )
1442 if options.delegate_pi:
1443 my_authority=self.authority
1444 message="%s.pi"%my_authority
1445 original = self.my_authority_credential_string()
1446 tuples.append ( (message, original,) )
1447 for auth_hrn in options.delegate_auths:
1448 message="%s.auth"%auth_hrn
1449 original=self.authority_credential_string(auth_hrn)
1450 tuples.append ( (message, original, ) )
1451 # if nothing was specified at all at this point, let's assume -u
1452 if not tuples: options.delegate_user=True
1454 if options.delegate_user:
1455 message="%s.user"%self.user
1456 original = self.my_credential_string
1457 tuples.append ( (message, original, ) )
1459 # default type for beneficial is user unless -A
1460 if options.delegate_to_authority: to_type='authority'
1461 else: to_type='user'
1463 # let's now handle all this
1464 # it's all in the filenaming scheme
1465 for (message,original) in tuples:
1466 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1467 delegated_credential = Credential (string=delegated_string)
1468 filename = os.path.join ( self.options.sfi_dir,
1469 "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1470 delegated_credential.save_to_file(filename, save_parents=True)
1471 self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1473 ####################
1474 @register_command("","""$ less +/myslice sfi_config
1476 backend = http://manifold.pl.sophia.inria.fr:7080
1477 # the HRN that myslice uses, so that we are delegating to
1478 delegate = ple.upmc.slicebrowser
1479 # platform - this is a myslice concept
1481 # username - as of this writing (May 2013) a simple login name
1485 will first collect the slices that you are part of, then make sure
1486 all your credentials are up-to-date (read: refresh expired ones)
1487 then compute delegated credentials for user 'ple.upmc.slicebrowser'
1488 and upload them all on myslice backend, using 'platform' and 'user'.
1489 A password will be prompted for the upload part.
1491 $ sfi -v myslice -- or sfi -vv myslice
1492 same but with more and more verbosity
1495 is synonym to sfi myslice as no other command starts with an 'm'
1497 ) # register_command
1498 def myslice (self, options, args):
1500 """ This helper is for refreshing your credentials at myslice; it will
1501 * compute all the slices that you currently have credentials on
1502 * refresh all your credentials (you as a user and pi, your slices)
1503 * upload them to the manifold backend server
1504 for last phase, sfi_config is read to look for the [myslice] section,
1505 and namely the 'backend', 'delegate' and 'user' settings"""
1512 ### the rough sketch goes like this
1513 # (a) rain check for sufficient config in sfi_config
1514 # we don't allow to override these settings for now
1516 myslice_keys=['backend', 'delegate', 'platform', 'username']
1517 for key in myslice_keys:
1518 full_key="MYSLICE_" + key.upper()
1519 value=getattr(self.config_instance,full_key,None)
1520 if value: myslice_dict[key]=value
1521 else: print "Unsufficient config, missing key %s in [myslice] section of sfi_config"%key
1522 if len(myslice_dict) != len(myslice_keys):
1525 # (b) figure whether we are PI for the authority where we belong
1526 sfi_logger.info("Resolving our own id")
1527 my_records=self.registry().Resolve(self.user,self.my_credential_string)
1528 if len(my_records)!=1: print "Cannot Resolve %s -- exiting"%self.user; sys.exit(1)
1529 my_record=my_records[0]
1530 sfi_logger.info("Checking for authorities that we are PI for")
1531 my_auths = my_record['reg-pi-authorities']
1532 sfi_logger.debug("Found %d authorities: %s"%(len(my_auths),my_auths))
1534 # (c) get the set of slices that we are in
1535 sfi_logger.info("Checking for slices that we are member of")
1536 my_slices=my_record['reg-slices']
1537 sfi_logger.debug("Found %d slices: %s"%(len(my_slices),my_slices))
1539 # (d) make sure we have *valid* credentials for all these
1541 hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1542 for auth_hrn in my_auths:
1543 hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1544 for slice_hrn in my_slices:
1545 hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1547 # (e) check for the delegated version of these
1548 # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever
1549 # switch to myslice using an authority instead of a user
1550 delegatee_type='user'
1551 delegatee_hrn=myslice_dict['delegate']
1552 hrn_delegated_credentials = [
1553 (hrn, htype, self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type),)
1554 for (hrn, htype, credential) in hrn_credentials ]
1556 # (f) and finally upload them to manifold server
1557 # xxx todo add an option so the password can be set on the command line
1558 # (but *NOT* in the config file) so other apps can leverage this
1559 uploader = ManifoldUploader (logger=sfi_logger,
1560 url=myslice_dict['backend'],
1561 platform=myslice_dict['platform'],
1562 username=myslice_dict['username'])
1563 for (hrn,htype,delegated_credential) in hrn_delegated_credentials:
1564 sfi_logger.info("Uploading delegated credential for %s (%s)"%(hrn,htype))
1565 uploader.upload(delegated_credential,message=hrn)
1566 # at first I thought we would want to save these,
1567 # like 'sfi delegate does' but on second thought
1568 # it is probably not helpful as people would not
1569 # need to run 'sfi delegate' at all anymore
1572 # Thierry: I'm turning this off as a command, no idea what it's used for
1573 # @register_command("cred","")
1574 def trusted(self, options, args):
1576 return the trusted certs at this interface (get_trusted_certs)
1578 trusted_certs = self.registry().get_trusted_certs()
1579 for trusted_cert in trusted_certs:
1580 gid = GID(string=trusted_cert)
1582 cert = Certificate(string=trusted_cert)
1583 self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())