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
49 from sfa.client.common import optparse_listvalue_callback, optparse_dictvalue_callback, \
50 terminal_render, filter_records
53 def display_rspec(rspec, format='rspec'):
55 tree = etree.parse(StringIO(rspec))
57 result = root.xpath("./network/site/node/hostname/text()")
58 elif format in ['ip']:
59 # The IP address is not yet part of the new RSpec
60 # so this doesn't do anything yet.
61 tree = etree.parse(StringIO(rspec))
63 result = root.xpath("./network/site/node/ipv4/text()")
70 def display_list(results):
71 for result in results:
74 def display_records(recordList, dump=False):
75 ''' Print all fields in the record'''
76 for record in recordList:
77 display_record(record, dump)
79 def display_record(record, dump=False):
81 record.dump(sort=True)
83 info = record.getdict()
84 print "%s (%s)" % (info['hrn'], info['type'])
88 def filter_records(type, records):
90 for record in records:
91 if (record['type'] == type) or (type == "all"):
92 filtered_records.append(record)
93 return filtered_records
96 def credential_printable (cred):
97 credential=Credential(cred=cred)
99 result += credential.get_summary_tostring()
101 rights = credential.get_privileges()
102 result += "type=%s\n" % credential.type
103 result += "version=%s\n" % credential.version
104 result += "rights=%s\n"%rights
107 def show_credentials (cred_s):
108 if not isinstance (cred_s,list): cred_s = [cred_s]
110 print "Using Credential %s"%credential_printable(cred)
113 def save_raw_to_file(var, filename, format="text", banner=None):
115 # if filename is "-", send it to stdout
118 f = open(filename, "w")
123 elif format == "pickled":
124 f.write(pickle.dumps(var))
125 elif format == "json":
126 if hasattr(json, "dumps"):
127 f.write(json.dumps(var)) # python 2.6
129 f.write(json.write(var)) # python 2.5
131 # this should never happen
132 print "unknown output format", format
134 f.write('\n'+banner+"\n")
136 def save_rspec_to_file(rspec, filename):
137 if not filename.endswith(".rspec"):
138 filename = filename + ".rspec"
139 f = open(filename, 'w')
144 def save_records_to_file(filename, record_dicts, format="xml"):
147 for record_dict in record_dicts:
149 save_record_to_file(filename + "." + str(index), record_dict)
151 save_record_to_file(filename, record_dict)
153 elif format == "xmllist":
154 f = open(filename, "w")
155 f.write("<recordlist>\n")
156 for record_dict in record_dicts:
157 record_obj=Record(dict=record_dict)
158 f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
159 f.write("</recordlist>\n")
161 elif format == "hrnlist":
162 f = open(filename, "w")
163 for record_dict in record_dicts:
164 record_obj=Record(dict=record_dict)
165 f.write(record_obj.hrn + "\n")
168 # this should never happen
169 print "unknown output format", format
171 def save_record_to_file(filename, record_dict):
172 record = Record(dict=record_dict)
173 xml = record.save_as_xml()
174 f=codecs.open(filename, encoding='utf-8',mode="w")
179 # minimally check a key argument
180 def check_ssh_key (key):
181 good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
182 return re.match(good_ssh_key, key, re.IGNORECASE)
185 def load_record_from_opts(options):
187 if hasattr(options, 'xrn') and options.xrn:
188 if hasattr(options, 'type') and options.type:
189 xrn = Xrn(options.xrn, options.type)
191 xrn = Xrn(options.xrn)
192 record_dict['urn'] = xrn.get_urn()
193 record_dict['hrn'] = xrn.get_hrn()
194 record_dict['type'] = xrn.get_type()
195 if hasattr(options, 'key') and options.key:
197 pubkey = open(options.key, 'r').read()
200 if not check_ssh_key (pubkey):
201 raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format")
202 record_dict['keys'] = [pubkey]
203 if hasattr(options, 'slices') and options.slices:
204 record_dict['slices'] = options.slices
205 if hasattr(options, 'researchers') and options.researchers:
206 record_dict['researcher'] = options.researchers
207 if hasattr(options, 'email') and options.email:
208 record_dict['email'] = options.email
209 if hasattr(options, 'pis') and options.pis:
210 record_dict['pi'] = options.pis
212 # handle extra settings
213 record_dict.update(options.extras)
215 return Record(dict=record_dict)
217 def load_record_from_file(filename):
218 f=codecs.open(filename, encoding="utf-8", mode="r")
219 xml_string = f.read()
221 return Record(xml=xml_string)
225 def unique_call_id(): return uuid.uuid4().urn
229 # dirty hack to make this class usable from the outside
230 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
233 def default_sfi_dir ():
234 if os.path.isfile("./sfi_config"):
237 return os.path.expanduser("~/.sfi/")
239 # dummy to meet Sfi's expectations for its 'options' field
240 # i.e. s/t we can do setattr on
244 def __init__ (self,options=None):
245 if options is None: options=Sfi.DummyOptions()
246 for opt in Sfi.required_options:
247 if not hasattr(options,opt): setattr(options,opt,None)
248 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
249 self.options = options
251 self.authority = None
252 self.logger = sfi_logger
253 self.logger.enable_console()
254 self.available_names = [ tuple[0] for tuple in Sfi.available ]
255 self.available_dict = dict (Sfi.available)
257 # tuples command-name expected-args in the order in which they should appear in the help
260 ("list", "authority"),
263 ("update", "[record]"),
266 ("describe", "slice_hrn"),
267 ("create", "slice_hrn rspec"),
268 ("allocate", "slice_hrn rspec"),
269 ("provision", "slice_hrn"),
270 ("action", "slice_hrn action"),
271 ("delete", "slice_hrn"),
272 ("status", "slice_hrn"),
273 ("renew", "slice_hrn time"),
274 ("shutdown", "slice_hrn"),
275 ("get_ticket", "slice_hrn rspec"),
276 ("redeem_ticket", "ticket"),
277 ("delegate", "to_hrn"),
283 def print_command_help (self, options):
284 verbose=getattr(options,'verbose')
285 format3="%18s %-15s %s"
288 print format3%("command","cmd_args","description")
292 self.create_parser().print_help()
293 for command in self.available_names:
294 args=self.available_dict[command]
295 method=getattr(self,command,None)
297 if method: doc=getattr(method,'__doc__',"")
298 if not doc: doc="*** no doc found ***"
299 doc=doc.strip(" \t\n")
300 doc=doc.replace("\n","\n"+35*' ')
303 print format3%(command,args,doc)
305 self.create_command_parser(command).print_help()
307 def create_command_parser(self, command):
308 if command not in self.available_dict:
309 msg="Invalid command\n"
311 msg += ','.join(self.available_names)
312 self.logger.critical(msg)
315 parser = OptionParser(usage="sfi [sfi_options] %s [cmd_options] %s" \
316 % (command, self.available_dict[command]))
318 if command in ("add", "update"):
319 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
320 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
321 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
322 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
324 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
325 default='', type="str", action='callback', callback=optparse_listvalue_callback)
326 parser.add_option('-r', '--researchers', dest='researchers', metavar='<researchers>',
327 help='Set/replace slice researchers', default='', type="str", action='callback',
328 callback=optparse_listvalue_callback)
329 parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
330 default='', type="str", action='callback', callback=optparse_listvalue_callback)
331 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
332 action="callback", callback=optparse_dictvalue_callback, nargs=1,
333 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
335 # user specifies remote aggregate/sm/component
336 if command in ("resources", "describe", "allocate", "provision", "create", "delete", "allocate", "provision",
337 "action", "shutdown", "get_ticket", "renew", "status"):
338 parser.add_option("-d", "--delegate", dest="delegate", default=None,
340 help="Include a credential delegated to the user's root"+\
341 "authority in set of credentials for this call")
343 # show_credential option
344 if command in ("list","resources", "describe", "provision", "allocate", "create","add","update","remove","slices","delete","status","renew"):
345 parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
346 help="show credential(s) used in human-readable form")
347 # registy filter option
348 if command in ("list", "show", "remove"):
349 parser.add_option("-t", "--type", dest="type", type="choice",
350 help="type filter ([all]|user|slice|authority|node|aggregate)",
351 choices=("all", "user", "slice", "authority", "node", "aggregate"),
353 if command in ("show"):
354 parser.add_option("-k","--key",dest="keys",action="append",default=[],
355 help="specify specific keys to be displayed from record")
356 if command in ("resources", "describe"):
358 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
359 help="schema type and version of resulting RSpec")
360 # disable/enable cached rspecs
361 parser.add_option("-c", "--current", dest="current", default=False,
363 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
365 parser.add_option("-f", "--format", dest="format", type="choice",
366 help="display format ([xml]|dns|ip)", default="xml",
367 choices=("xml", "dns", "ip"))
368 #panos: a new option to define the type of information about resources a user is interested in
369 parser.add_option("-i", "--info", dest="info",
370 help="optional component information", default=None)
371 # a new option to retreive or not reservation-oriented RSpecs (leases)
372 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
373 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
374 choices=("all", "resources", "leases"), default="resources")
377 # 'create' does return the new rspec, makes sense to save that too
378 if command in ("resources", "describe", "allocate", "provision", "show", "list", "gid", 'create'):
379 parser.add_option("-o", "--output", dest="file",
380 help="output XML to file", metavar="FILE", default=None)
382 if command in ("show", "list"):
383 parser.add_option("-f", "--format", dest="format", type="choice",
384 help="display format ([text]|xml)", default="text",
385 choices=("text", "xml"))
387 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
388 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
389 choices=("xml", "xmllist", "hrnlist"))
390 if command == 'list':
391 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
392 help="list all child records", default=False)
393 parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
394 help="gives details, like user keys", default=False)
395 if command in ("delegate"):
396 parser.add_option("-u", "--user",
397 action="store_true", dest="delegate_user", default=False,
398 help="delegate your own credentials; default if no other option is provided")
399 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
400 metavar="slice_hrn", help="delegate cred. for slice HRN")
401 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
402 metavar='auth_hrn', help="delegate cred for auth HRN")
403 # this primarily is a shorthand for -a my_hrn^
404 parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
405 help="delegate your PI credentials, so s.t. like -a your_hrn^")
406 parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
407 help="""by default the mandatory argument is expected to be a user,
408 use this if you mean an authority instead""")
410 if command in ("version"):
411 parser.add_option("-R","--registry-version",
412 action="store_true", dest="version_registry", default=False,
413 help="probe registry version instead of sliceapi")
414 parser.add_option("-l","--local",
415 action="store_true", dest="version_local", default=False,
416 help="display version of the local client")
421 def create_parser(self):
423 # Generate command line parser
424 parser = OptionParser(usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
425 description="Commands: %s"%(" ".join(self.available_names)))
426 parser.add_option("-r", "--registry", dest="registry",
427 help="root registry", metavar="URL", default=None)
428 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
429 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
430 parser.add_option("-R", "--raw", dest="raw", default=None,
431 help="Save raw, unparsed server response to a file")
432 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
433 help="raw file format ([text]|pickled|json)", default="text",
434 choices=("text","pickled","json"))
435 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
436 help="text string to write before and after raw output")
437 parser.add_option("-d", "--dir", dest="sfi_dir",
438 help="config & working directory - default is %default",
439 metavar="PATH", default=Sfi.default_sfi_dir())
440 parser.add_option("-u", "--user", dest="user",
441 help="user name", metavar="HRN", default=None)
442 parser.add_option("-a", "--auth", dest="auth",
443 help="authority name", metavar="HRN", default=None)
444 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
445 help="verbose mode - cumulative")
446 parser.add_option("-D", "--debug",
447 action="store_true", dest="debug", default=False,
448 help="Debug (xml-rpc) protocol messages")
449 # would it make sense to use ~/.ssh/id_rsa as a default here ?
450 parser.add_option("-k", "--private-key",
451 action="store", dest="user_private_key", default=None,
452 help="point to the private key file to use if not yet installed in sfi_dir")
453 parser.add_option("-t", "--timeout", dest="timeout", default=None,
454 help="Amout of time to wait before timing out the request")
455 parser.add_option("-?", "--commands",
456 action="store_true", dest="command_help", default=False,
457 help="one page summary on commands & exit")
458 parser.disable_interspersed_args()
463 def print_help (self):
464 print "==================== Generic sfi usage"
465 self.sfi_parser.print_help()
466 print "==================== Specific command usage"
467 self.command_parser.print_help()
470 # Main: parse arguments and dispatch to command
472 def dispatch(self, command, command_options, command_args):
473 method=getattr(self, command,None)
475 print "Unknown command %s"%command
477 return method(command_options, command_args)
480 self.sfi_parser = self.create_parser()
481 (options, args) = self.sfi_parser.parse_args()
482 if options.command_help:
483 self.print_command_help(options)
485 self.options = options
487 self.logger.setLevelFromOptVerbose(self.options.verbose)
490 self.logger.critical("No command given. Use -h for help.")
491 self.print_command_help(options)
494 # complete / find unique match with command set
495 command_candidates = Candidates (self.available_names)
497 command = command_candidates.only_match(input)
499 self.print_command_help(options)
501 # second pass options parsing
502 self.command_parser = self.create_command_parser(command)
503 (command_options, command_args) = self.command_parser.parse_args(args[1:])
504 self.command_options = command_options
508 self.logger.debug("Command=%s" % command)
511 self.dispatch(command, command_options, command_args)
513 self.logger.log_exc ("sfi command %s failed"%command)
519 def read_config(self):
520 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
521 shell_config_file = os.path.join(self.options.sfi_dir,"sfi_config.sh")
523 if Config.is_ini(config_file):
524 config = Config (config_file)
526 # try upgrading from shell config format
527 fp, fn = mkstemp(suffix='sfi_config', text=True)
529 # we need to preload the sections we want parsed
530 # from the shell config
531 config.add_section('sfi')
532 config.add_section('sface')
533 config.load(config_file)
535 shutil.move(config_file, shell_config_file)
537 config.save(config_file)
540 self.logger.critical("Failed to read configuration file %s"%config_file)
541 self.logger.info("Make sure to remove the export clauses and to add quotes")
542 if self.options.verbose==0:
543 self.logger.info("Re-run with -v for more details")
545 self.logger.log_exc("Could not read config file %s"%config_file)
550 if (self.options.sm is not None):
551 self.sm_url = self.options.sm
552 elif hasattr(config, "SFI_SM"):
553 self.sm_url = config.SFI_SM
555 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
559 if (self.options.registry is not None):
560 self.reg_url = self.options.registry
561 elif hasattr(config, "SFI_REGISTRY"):
562 self.reg_url = config.SFI_REGISTRY
564 self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
568 if (self.options.user is not None):
569 self.user = self.options.user
570 elif hasattr(config, "SFI_USER"):
571 self.user = config.SFI_USER
573 self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
577 if (self.options.auth is not None):
578 self.authority = self.options.auth
579 elif hasattr(config, "SFI_AUTH"):
580 self.authority = config.SFI_AUTH
582 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
585 self.config_file=config_file
589 def show_config (self):
590 print "From configuration file %s"%self.config_file
593 ('SFI_AUTH','authority'),
595 ('SFI_REGISTRY','reg_url'),
597 for (external_name, internal_name) in flags:
598 print "%s='%s'"%(external_name,getattr(self,internal_name))
601 # Get various credential and spec files
603 # Establishes limiting conventions
604 # - conflates MAs and SAs
605 # - assumes last token in slice name is unique
607 # Bootstraps credentials
608 # - bootstrap user credential from self-signed certificate
609 # - bootstrap authority credential from user credential
610 # - bootstrap slice credential from user credential
613 # init self-signed cert, user credentials and gid
614 def bootstrap (self):
615 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
617 # if -k is provided, use this to initialize private key
618 if self.options.user_private_key:
619 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
621 # trigger legacy compat code if needed
622 # the name has changed from just <leaf>.pkey to <hrn>.pkey
623 if not os.path.isfile(client_bootstrap.private_key_filename()):
624 self.logger.info ("private key not found, trying legacy name")
626 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
627 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
628 client_bootstrap.init_private_key_if_missing (legacy_private_key)
629 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
631 self.logger.log_exc("Can't find private key ")
635 client_bootstrap.bootstrap_my_gid()
636 # extract what's needed
637 self.private_key = client_bootstrap.private_key()
638 self.my_credential_string = client_bootstrap.my_credential_string ()
639 self.my_credential = {'geni_type': 'geni_sfa',
640 'geni_version': '3.0',
641 'geni_value': self.my_credential_string}
642 self.my_gid = client_bootstrap.my_gid ()
643 self.client_bootstrap = client_bootstrap
646 def my_authority_credential_string(self):
647 if not self.authority:
648 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
650 return self.client_bootstrap.authority_credential_string (self.authority)
652 def authority_credential_string(self, auth_hrn):
653 return self.client_bootstrap.authority_credential_string (auth_hrn)
655 def slice_credential_string(self, name):
656 return self.client_bootstrap.slice_credential_string (name)
658 def slice_credential(self, name):
659 return {'geni_type': 'geni_sfa',
660 'geni_version': '3.0',
661 'geni_value': self.slice_credential_string(name)}
663 # xxx should be supported by sfaclientbootstrap as well
664 def delegate_cred(self, object_cred, hrn, type='authority'):
665 # the gid and hrn of the object we are delegating
666 if isinstance(object_cred, str):
667 object_cred = Credential(string=object_cred)
668 object_gid = object_cred.get_gid_object()
669 object_hrn = object_gid.get_hrn()
671 if not object_cred.get_privileges().get_all_delegate():
672 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
675 # the delegating user's gid
676 caller_gidfile = self.my_gid()
678 # the gid of the user who will be delegated to
679 delegee_gid = self.client_bootstrap.gid(hrn,type)
680 delegee_hrn = delegee_gid.get_hrn()
681 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
682 return dcred.save_to_string(save_parents=True)
685 # Management of the servers
690 if not hasattr (self, 'registry_proxy'):
691 self.logger.info("Contacting Registry at: %s"%self.reg_url)
692 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
693 timeout=self.options.timeout, verbose=self.options.debug)
694 return self.registry_proxy
698 if not hasattr (self, 'sliceapi_proxy'):
699 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
700 if hasattr(self.command_options,'component') and self.command_options.component:
701 # resolve the hrn at the registry
702 node_hrn = self.command_options.component
703 records = self.registry().Resolve(node_hrn, self.my_credential_string)
704 records = filter_records('node', records)
706 self.logger.warning("No such component:%r"% opts.component)
708 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
709 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
711 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
712 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
713 self.sm_url = 'http://' + self.sm_url
714 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
715 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
716 timeout=self.options.timeout, verbose=self.options.debug)
717 return self.sliceapi_proxy
719 def get_cached_server_version(self, server):
720 # check local cache first
723 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
724 cache_key = server.url + "-version"
726 cache = Cache(cache_file)
729 self.logger.info("Local cache not found at: %s" % cache_file)
732 version = cache.get(cache_key)
735 result = server.GetVersion()
736 version= ReturnValue.get_value(result)
737 # cache version for 20 minutes
738 cache.add(cache_key, version, ttl= 60*20)
739 self.logger.info("Updating cache file %s" % cache_file)
740 cache.save_to_file(cache_file)
744 ### resurrect this temporarily so we can support V1 aggregates for a while
745 def server_supports_options_arg(self, server):
747 Returns true if server support the optional call_id arg, false otherwise.
749 server_version = self.get_cached_server_version(server)
751 # xxx need to rewrite this
752 if int(server_version.get('geni_api')) >= 2:
756 def server_supports_call_id_arg(self, server):
757 server_version = self.get_cached_server_version(server)
759 if 'sfa' in server_version and 'code_tag' in server_version:
760 code_tag = server_version['code_tag']
761 code_tag_parts = code_tag.split("-")
762 version_parts = code_tag_parts[0].split(".")
763 major, minor = version_parts[0], version_parts[1]
764 rev = code_tag_parts[1]
765 if int(major) == 1 and minor == 0 and build >= 22:
769 ### ois = options if supported
770 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
771 def ois (self, server, option_dict):
772 if self.server_supports_options_arg (server):
774 elif self.server_supports_call_id_arg (server):
775 return [ unique_call_id () ]
779 ### cis = call_id if supported - like ois
780 def cis (self, server):
781 if self.server_supports_call_id_arg (server):
782 return [ unique_call_id ]
786 ######################################## miscell utilities
787 def get_rspec_file(self, rspec):
788 if (os.path.isabs(rspec)):
791 file = os.path.join(self.options.sfi_dir, rspec)
792 if (os.path.isfile(file)):
795 self.logger.critical("No such rspec file %s"%rspec)
798 def get_record_file(self, record):
799 if (os.path.isabs(record)):
802 file = os.path.join(self.options.sfi_dir, record)
803 if (os.path.isfile(file)):
806 self.logger.critical("No such registry record file %s"%record)
810 #==========================================================================
811 # Following functions implement the commands
813 # Registry-related commands
814 #==========================================================================
816 def version(self, options, args):
818 display an SFA server version (GetVersion)
819 or version information about sfi itself
821 if options.version_local:
822 version=version_core()
824 if options.version_registry:
825 server=self.registry()
827 server = self.sliceapi()
828 result = server.GetVersion()
829 version = ReturnValue.get_value(result)
831 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
833 pprinter = PrettyPrinter(indent=4)
834 pprinter.pprint(version)
836 def list(self, options, args):
838 list entries in named authority registry (List)
845 if options.recursive:
846 opts['recursive'] = options.recursive
848 if options.show_credential:
849 show_credentials(self.my_credential_string)
851 list = self.registry().List(hrn, self.my_credential_string, options)
853 raise Exception, "Not enough parameters for the 'list' command"
855 # filter on person, slice, site, node, etc.
856 # This really should be in the self.filter_records funct def comment...
857 list = filter_records(options.type, list)
858 terminal_render (list, options)
860 save_records_to_file(options.file, list, options.fileformat)
863 def show(self, options, args):
865 show details about named registry record (Resolve)
871 # explicitly require Resolve to run in details mode
872 record_dicts = self.registry().Resolve(hrn, self.my_credential_string, {'details':True})
873 record_dicts = filter_records(options.type, record_dicts)
875 self.logger.error("No record of type %s"% options.type)
877 # user has required to focus on some keys
879 def project (record):
881 for key in options.keys:
882 try: projected[key]=record[key]
885 record_dicts = [ project (record) for record in record_dicts ]
886 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
887 for record in records:
888 if (options.format == "text"): record.dump(sort=True)
889 else: print record.save_as_xml()
891 save_records_to_file(options.file, record_dicts, options.fileformat)
894 def add(self, options, args):
895 "add record into registry by using the command options (Recommended) or from xml file (Register)"
896 auth_cred = self.my_authority_credential_string()
897 if options.show_credential:
898 show_credentials(auth_cred)
905 record_filepath = args[0]
906 rec_file = self.get_record_file(record_filepath)
907 record_dict.update(load_record_from_file(rec_file).todict())
909 print "Cannot load record file %s"%record_filepath
912 record_dict.update(load_record_from_opts(options).todict())
913 # we should have a type by now
914 if 'type' not in record_dict :
917 # this is still planetlab dependent.. as plc will whine without that
918 # also, it's only for adding
919 if record_dict['type'] == 'user':
920 if not 'first_name' in record_dict:
921 record_dict['first_name'] = record_dict['hrn']
922 if 'last_name' not in record_dict:
923 record_dict['last_name'] = record_dict['hrn']
924 return self.registry().Register(record_dict, auth_cred)
926 def update(self, options, args):
927 "update record into registry by using the command options (Recommended) or from xml file (Update)"
930 record_filepath = args[0]
931 rec_file = self.get_record_file(record_filepath)
932 record_dict.update(load_record_from_file(rec_file).todict())
934 record_dict.update(load_record_from_opts(options).todict())
935 # at the very least we need 'type' here
936 if 'type' not in record_dict:
940 # don't translate into an object, as this would possibly distort
941 # user-provided data; e.g. add an 'email' field to Users
942 if record_dict['type'] == "user":
943 if record_dict['hrn'] == self.user:
944 cred = self.my_credential_string
946 cred = self.my_authority_credential_string()
947 elif record_dict['type'] in ["slice"]:
949 cred = self.slice_credential_string(record_dict['hrn'])
950 except ServerException, e:
951 # XXX smbaker -- once we have better error return codes, update this
952 # to do something better than a string compare
953 if "Permission error" in e.args[0]:
954 cred = self.my_authority_credential_string()
957 elif record_dict['type'] in ["authority"]:
958 cred = self.my_authority_credential_string()
959 elif record_dict['type'] == 'node':
960 cred = self.my_authority_credential_string()
962 raise "unknown record type" + record_dict['type']
963 if options.show_credential:
964 show_credentials(cred)
965 return self.registry().Update(record_dict, cred)
967 def remove(self, options, args):
968 "remove registry record by name (Remove)"
969 auth_cred = self.my_authority_credential_string()
977 if options.show_credential:
978 show_credentials(auth_cred)
979 return self.registry().Remove(hrn, auth_cred, type)
981 # ==================================================================
982 # Slice-related commands
983 # ==================================================================
985 def slices(self, options, args):
986 "list instantiated slices (ListSlices) - returns urn's"
987 server = self.sliceapi()
989 creds = [self.my_credential_string]
990 # options and call_id when supported
992 api_options['call_id']=unique_call_id()
993 if options.show_credential:
994 show_credentials(creds)
995 result = server.ListSlices(creds, *self.ois(server,api_options))
996 value = ReturnValue.get_value(result)
998 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1003 # show rspec for named slice
1004 def resources(self, options, args):
1006 discover available resources
1007 or with an slice hrn, shows currently provisioned resources
1009 server = self.sliceapi()
1012 creds = [self.my_credential]
1013 if options.delegate:
1014 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1015 if options.show_credential:
1016 show_credentials(creds)
1018 # no need to check if server accepts the options argument since the options has
1019 # been a required argument since v1 API
1021 # always send call_id to v2 servers
1022 api_options ['call_id'] = unique_call_id()
1023 # ask for cached value if available
1024 api_options ['cached'] = True
1026 api_options['info'] = options.info
1027 if options.list_leases:
1028 api_options['list_leases'] = options.list_leases
1030 if options.current == True:
1031 api_options['cached'] = False
1033 api_options['cached'] = True
1034 if options.rspec_version:
1035 version_manager = VersionManager()
1036 server_version = self.get_cached_server_version(server)
1037 if 'sfa' in server_version:
1038 # just request the version the client wants
1039 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1041 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1043 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1044 result = server.ListResources (creds, api_options)
1045 value = ReturnValue.get_value(result)
1046 if self.options.raw:
1047 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1048 if options.file is not None:
1049 save_rspec_to_file(value, options.file)
1050 if (self.options.raw is None) and (options.file is None):
1051 display_rspec(value, options.format)
1055 def describe(self, options, args):
1057 Shows currently provisioned resources.
1059 server = self.sliceapi()
1062 creds = [self.slice_credential(args[0])]
1063 if options.delegate:
1064 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1065 if options.show_credential:
1066 show_credentials(creds)
1068 api_options = {'call_id': unique_call_id(),
1070 'info': options.info,
1071 'list_leases': options.list_leases,
1072 'geni_rspec_version': {'type': 'geni', 'version': '3.0'},
1074 if options.rspec_version:
1075 version_manager = VersionManager()
1076 server_version = self.get_cached_server_version(server)
1077 if 'sfa' in server_version:
1078 # just request the version the client wants
1079 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1081 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1082 urn = Xrn(args[0], type='slice').get_urn()
1083 result = server.Describe([urn], creds, api_options)
1084 value = ReturnValue.get_value(result)
1085 if self.options.raw:
1086 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1087 if options.file is not None:
1088 save_rspec_to_file(value, options.file)
1089 if (self.options.raw is None) and (options.file is None):
1090 display_rspec(value, options.format)
1094 def create(self, options, args):
1096 create or update named slice with given rspec
1098 server = self.sliceapi()
1100 # xxx do we need to check usage (len(args)) ?
1103 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1106 creds = [self.slice_credential_string(slice_hrn)]
1108 delegated_cred = None
1109 server_version = self.get_cached_server_version(server)
1110 if server_version.get('interface') == 'slicemgr':
1111 # delegate our cred to the slice manager
1112 # do not delegate cred to slicemgr...not working at the moment
1114 #if server_version.get('hrn'):
1115 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1116 #elif server_version.get('urn'):
1117 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1119 if options.show_credential:
1120 show_credentials(creds)
1123 rspec_file = self.get_rspec_file(args[1])
1124 rspec = open(rspec_file).read()
1127 # need to pass along user keys to the aggregate.
1129 # { urn: urn:publicid:IDN+emulab.net+user+alice
1130 # keys: [<ssh key A>, <ssh key B>]
1133 # xxx Thierry 2012 sept. 21
1134 # contrary to what I was first thinking, calling Resolve with details=False does not yet work properly here
1135 # I am turning details=True on again on a - hopefully - temporary basis, just to get this whole thing to work again
1136 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1137 # slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string], {'details':True})
1138 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']:
1139 slice_record = slice_records[0]
1140 user_hrns = slice_record['reg-researchers']
1141 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1142 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1144 if 'sfa' not in server_version:
1145 users = pg_users_arg(user_records)
1146 rspec = RSpec(rspec)
1147 rspec.filter({'component_manager_id': server_version['urn']})
1148 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1150 users = sfa_users_arg(user_records, slice_record)
1152 # do not append users, keys, or slice tags. Anything
1153 # not contained in this request will be removed from the slice
1156 api_options ['append'] = False
1157 api_options ['call_id'] = unique_call_id()
1158 result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
1159 value = ReturnValue.get_value(result)
1160 if self.options.raw:
1161 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1162 if options.file is not None:
1163 save_rspec_to_file (value, options.file)
1164 if (self.options.raw is None) and (options.file is None):
1169 def delete(self, options, args):
1171 delete named slice (DeleteSliver)
1173 server = self.sliceapi()
1177 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1180 slice_cred = self.slice_credential(slice_hrn)
1181 creds = [slice_cred]
1183 # options and call_id when supported
1185 api_options ['call_id'] = unique_call_id()
1186 if options.show_credential:
1187 show_credentials(creds)
1188 result = server.Delete([slice_urn], creds, *self.ois(server, api_options ) )
1189 value = ReturnValue.get_value(result)
1190 if self.options.raw:
1191 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1196 def allocate(self, options, args):
1197 server = self.sliceapi()
1198 server_version = self.get_cached_server_version(server)
1200 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1203 creds = [self.slice_credential(slice_hrn)]
1205 delegated_cred = None
1206 if server_version.get('interface') == 'slicemgr':
1207 # delegate our cred to the slice manager
1208 # do not delegate cred to slicemgr...not working at the moment
1210 #if server_version.get('hrn'):
1211 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1212 #elif server_version.get('urn'):
1213 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1215 if options.show_credential:
1216 show_credentials(creds)
1219 rspec_file = self.get_rspec_file(args[1])
1220 rspec = open(rspec_file).read()
1222 api_options ['call_id'] = unique_call_id()
1223 result = server.Allocate(slice_urn, creds, rspec, api_options)
1224 value = ReturnValue.get_value(result)
1225 if self.options.raw:
1226 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1227 if options.file is not None:
1228 save_rspec_to_file (value, options.file)
1229 if (self.options.raw is None) and (options.file is None):
1235 def provision(self, options, args):
1236 server = self.sliceapi()
1237 server_version = self.get_cached_server_version(server)
1239 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1242 creds = [self.slice_credential(slice_hrn)]
1243 delegated_cred = None
1244 if server_version.get('interface') == 'slicemgr':
1245 # delegate our cred to the slice manager
1246 # do not delegate cred to slicemgr...not working at the moment
1248 #if server_version.get('hrn'):
1249 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1250 #elif server_version.get('urn'):
1251 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1253 if options.show_credential:
1254 show_credentials(creds)
1257 api_options ['call_id'] = unique_call_id()
1259 # set the requtested rspec version
1260 version_manager = VersionManager()
1261 rspec_version = version_manager._get_version('geni', '3.0').to_dict()
1262 api_options['geni_rspec_version'] = rspec_version
1265 # need to pass along user keys to the aggregate.
1267 # { urn: urn:publicid:IDN+emulab.net+user+alice
1268 # keys: [<ssh key A>, <ssh key B>]
1271 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1272 if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
1273 slice_record = slice_records[0]
1274 user_hrns = slice_record['researcher']
1275 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1276 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1277 users = pg_users_arg(user_records)
1279 api_options['geni_users'] = users
1280 result = server.Provision([slice_urn], creds, api_options)
1281 value = ReturnValue.get_value(result)
1282 if self.options.raw:
1283 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1284 if options.file is not None:
1285 save_rspec_to_file (value, options.file)
1286 if (self.options.raw is None) and (options.file is None):
1290 def status(self, options, args):
1292 retrieve slice status (SliverStatus)
1294 server = self.sliceapi()
1298 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1301 slice_cred = self.slice_credential(slice_hrn)
1302 creds = [slice_cred]
1304 # options and call_id when supported
1306 api_options['call_id']=unique_call_id()
1307 if options.show_credential:
1308 show_credentials(creds)
1309 result = server.Status([slice_urn], creds, *self.ois(server,api_options))
1310 value = ReturnValue.get_value(result)
1311 if self.options.raw:
1312 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1316 def start(self, options, args):
1318 start named slice (Start)
1320 server = self.sliceapi()
1324 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1327 slice_cred = self.slice_credential_string(args[0])
1328 creds = [slice_cred]
1329 # xxx Thierry - does this not need an api_options as well ?
1330 result = server.Start(slice_urn, creds)
1331 value = ReturnValue.get_value(result)
1332 if self.options.raw:
1333 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1338 def stop(self, options, args):
1340 stop named slice (Stop)
1342 server = self.sliceapi()
1345 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1347 slice_cred = self.slice_credential_string(args[0])
1348 creds = [slice_cred]
1349 result = server.Stop(slice_urn, creds)
1350 value = ReturnValue.get_value(result)
1351 if self.options.raw:
1352 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1358 def action(self, options, args):
1360 Perform the named operational action on the named slivers
1362 server = self.sliceapi()
1367 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1369 slice_cred = self.slice_credential(args[0])
1370 creds = [slice_cred]
1371 if options.delegate:
1372 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1373 creds.append(delegated_cred)
1375 result = server.PerformOperationalAction([slice_urn], creds, action , api_options)
1376 value = ReturnValue.get_value(result)
1377 if self.options.raw:
1378 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1383 def renew(self, options, args):
1385 renew slice (RenewSliver)
1387 server = self.sliceapi()
1391 [ slice_hrn, input_time ] = args
1393 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1394 # time: don't try to be smart on the time format, server-side will
1396 slice_cred = self.slice_credential(args[0])
1397 creds = [slice_cred]
1398 # options and call_id when supported
1400 api_options['call_id']=unique_call_id()
1401 if options.show_credential:
1402 show_credentials(creds)
1403 result = server.Renew([slice_urn], creds, input_time, *self.ois(server,api_options))
1404 value = ReturnValue.get_value(result)
1405 if self.options.raw:
1406 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1412 def shutdown(self, options, args):
1414 shutdown named slice (Shutdown)
1416 server = self.sliceapi()
1419 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1421 slice_cred = self.slice_credential(slice_hrn)
1422 creds = [slice_cred]
1423 result = server.Shutdown(slice_urn, creds)
1424 value = ReturnValue.get_value(result)
1425 if self.options.raw:
1426 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1432 def get_ticket(self, options, args):
1434 get a ticket for the specified slice
1436 server = self.sliceapi()
1438 slice_hrn, rspec_path = args[0], args[1]
1439 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1441 slice_cred = self.slice_credential_string(slice_hrn)
1442 creds = [slice_cred]
1444 rspec_file = self.get_rspec_file(rspec_path)
1445 rspec = open(rspec_file).read()
1446 # options and call_id when supported
1448 api_options['call_id']=unique_call_id()
1449 # get ticket at the server
1450 ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1452 file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1453 self.logger.info("writing ticket to %s"%file)
1454 ticket = SfaTicket(string=ticket_string)
1455 ticket.save_to_file(filename=file, save_parents=True)
1457 def redeem_ticket(self, options, args):
1459 Connects to nodes in a slice and redeems a ticket
1460 (slice hrn is retrieved from the ticket)
1462 ticket_file = args[0]
1464 # get slice hrn from the ticket
1465 # use this to get the right slice credential
1466 ticket = SfaTicket(filename=ticket_file)
1468 ticket_string = ticket.save_to_string(save_parents=True)
1470 slice_hrn = ticket.gidObject.get_hrn()
1471 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1472 #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1473 slice_cred = self.slice_credential_string(slice_hrn)
1475 # get a list of node hostnames from the RSpec
1476 tree = etree.parse(StringIO(ticket.rspec))
1477 root = tree.getroot()
1478 hostnames = root.xpath("./network/site/node/hostname/text()")
1480 # create an xmlrpc connection to the component manager at each of these
1481 # components and gall redeem_ticket
1483 for hostname in hostnames:
1485 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1486 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1487 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1488 server = self.server_proxy(hostname, CM_PORT, self.private_key,
1489 timeout=self.options.timeout, verbose=self.options.debug)
1490 server.RedeemTicket(ticket_string, slice_cred)
1491 self.logger.info("Success")
1492 except socket.gaierror:
1493 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1494 except Exception, e:
1495 self.logger.log_exc(e.message)
1498 def gid(self, options, args):
1500 Create a GID (CreateGid)
1505 target_hrn = args[0]
1506 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1507 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1509 filename = options.file
1511 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1512 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1513 GID(string=gid).save_to_file(filename)
1516 def delegate (self, options, args):
1518 (locally) create delegate credential for use by given hrn
1524 # support for several delegations in the same call
1525 # so first we gather the things to do
1527 for slice_hrn in options.delegate_slices:
1528 message="%s.slice"%slice_hrn
1529 original = self.slice_credential_string(slice_hrn)
1530 tuples.append ( (message, original,) )
1531 if options.delegate_pi:
1532 my_authority=self.authority
1533 message="%s.pi"%my_authority
1534 original = self.my_authority_credential_string()
1535 tuples.append ( (message, original,) )
1536 for auth_hrn in options.delegate_auths:
1537 message="%s.auth"%auth_hrn
1538 original=self.authority_credential_string(auth_hrn)
1539 tuples.append ( (message, original, ) )
1540 # if nothing was specified at all at this point, let's assume -u
1541 if not tuples: options.delegate_user=True
1543 if options.delegate_user:
1544 message="%s.user"%self.user
1545 original = self.my_credential_string
1546 tuples.append ( (message, original, ) )
1548 # default type for beneficial is user unless -A
1549 if options.delegate_to_authority: to_type='authority'
1550 else: to_type='user'
1552 # let's now handle all this
1553 # it's all in the filenaming scheme
1554 for (message,original) in tuples:
1555 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1556 delegated_credential = Credential (string=delegated_string)
1557 filename = os.path.join ( self.options.sfi_dir,
1558 "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1559 delegated_credential.save_to_file(filename, save_parents=True)
1560 self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1562 def trusted(self, options, args):
1564 return uhe trusted certs at this interface (get_trusted_certs)
1566 trusted_certs = self.registry().get_trusted_certs()
1567 for trusted_cert in trusted_certs:
1568 gid = GID(string=trusted_cert)
1570 cert = Certificate(string=trusted_cert)
1571 self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1574 def config (self, options, args):
1575 "Display contents of current config"