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 # utility methods here
50 def optparse_listvalue_callback(option, option_string, value, parser):
51 setattr(parser.values, option.dest, value.split(','))
53 # a code fragment that could be helpful for argparse which unfortunately is
54 # available with 2.7 only, so this feels like too strong a requirement for the client side
55 #class ExtraArgAction (argparse.Action):
56 # def __call__ (self, parser, namespace, values, option_string=None):
57 # would need a try/except of course
58 # (k,v)=values.split('=')
59 # d=getattr(namespace,self.dest)
62 #parser.add_argument ("-X","--extra",dest='extras', default={}, action=ExtraArgAction,
63 # help="set extra flags, testbed dependent, e.g. --extra enabled=true")
65 def optparse_dictvalue_callback (option, option_string, value, parser):
67 (k,v)=value.split('=',1)
68 d=getattr(parser.values, option.dest)
75 def display_rspec(rspec, format='rspec'):
77 tree = etree.parse(StringIO(rspec))
79 result = root.xpath("./network/site/node/hostname/text()")
80 elif format in ['ip']:
81 # The IP address is not yet part of the new RSpec
82 # so this doesn't do anything yet.
83 tree = etree.parse(StringIO(rspec))
85 result = root.xpath("./network/site/node/ipv4/text()")
92 def display_list(results):
93 for result in results:
96 def display_records(recordList, dump=False):
97 ''' Print all fields in the record'''
98 for record in recordList:
99 display_record(record, dump)
101 def display_record(record, dump=False):
103 record.dump(sort=True)
105 info = record.getdict()
106 print "%s (%s)" % (info['hrn'], info['type'])
110 def filter_records(type, records):
111 filtered_records = []
112 for record in records:
113 if (record['type'] == type) or (type == "all"):
114 filtered_records.append(record)
115 return filtered_records
118 def credential_printable (credential_string):
119 credential=Credential(string=credential_string)
121 result += credential.get_summary_tostring()
123 rights = credential.get_privileges()
124 result += "rights=%s"%rights
128 def show_credentials (cred_s):
129 if not isinstance (cred_s,list): cred_s = [cred_s]
131 print "Using Credential %s"%credential_printable(cred)
134 def save_raw_to_file(var, filename, format="text", banner=None):
136 # if filename is "-", send it to stdout
139 f = open(filename, "w")
144 elif format == "pickled":
145 f.write(pickle.dumps(var))
146 elif format == "json":
147 if hasattr(json, "dumps"):
148 f.write(json.dumps(var)) # python 2.6
150 f.write(json.write(var)) # python 2.5
152 # this should never happen
153 print "unknown output format", format
155 f.write('\n'+banner+"\n")
157 def save_rspec_to_file(rspec, filename):
158 if not filename.endswith(".rspec"):
159 filename = filename + ".rspec"
160 f = open(filename, 'w')
165 def save_records_to_file(filename, record_dicts, format="xml"):
168 for record_dict in record_dicts:
170 save_record_to_file(filename + "." + str(index), record_dict)
172 save_record_to_file(filename, record_dict)
174 elif format == "xmllist":
175 f = open(filename, "w")
176 f.write("<recordlist>\n")
177 for record_dict in record_dicts:
178 record_obj=Record(dict=record_dict)
179 f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
180 f.write("</recordlist>\n")
182 elif format == "hrnlist":
183 f = open(filename, "w")
184 for record_dict in record_dicts:
185 record_obj=Record(dict=record_dict)
186 f.write(record_obj.hrn + "\n")
189 # this should never happen
190 print "unknown output format", format
192 def save_record_to_file(filename, record_dict):
193 record = Record(dict=record_dict)
194 xml = record.save_as_xml()
195 f=codecs.open(filename, encoding='utf-8',mode="w")
200 # minimally check a key argument
201 def check_ssh_key (key):
202 good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
203 return re.match(good_ssh_key, key, re.IGNORECASE)
206 def load_record_from_opts(options):
208 if hasattr(options, 'xrn') and options.xrn:
209 if hasattr(options, 'type') and options.type:
210 xrn = Xrn(options.xrn, options.type)
212 xrn = Xrn(options.xrn)
213 record_dict['urn'] = xrn.get_urn()
214 record_dict['hrn'] = xrn.get_hrn()
215 record_dict['type'] = xrn.get_type()
216 if hasattr(options, 'key') and options.key:
218 pubkey = open(options.key, 'r').read()
221 if not check_ssh_key (pubkey):
222 raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format")
223 record_dict['keys'] = [pubkey]
224 if hasattr(options, 'slices') and options.slices:
225 record_dict['slices'] = options.slices
226 if hasattr(options, 'researchers') and options.researchers:
227 record_dict['researcher'] = options.researchers
228 if hasattr(options, 'email') and options.email:
229 record_dict['email'] = options.email
230 if hasattr(options, 'pis') and options.pis:
231 record_dict['pi'] = options.pis
233 # handle extra settings
234 record_dict.update(options.extras)
236 return Record(dict=record_dict)
238 def load_record_from_file(filename):
239 f=codecs.open(filename, encoding="utf-8", mode="r")
240 xml_string = f.read()
242 return Record(xml=xml_string)
246 def unique_call_id(): return uuid.uuid4().urn
250 # dirty hack to make this class usable from the outside
251 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
254 def default_sfi_dir ():
255 if os.path.isfile("./sfi_config"):
258 return os.path.expanduser("~/.sfi/")
260 # dummy to meet Sfi's expectations for its 'options' field
261 # i.e. s/t we can do setattr on
265 def __init__ (self,options=None):
266 if options is None: options=Sfi.DummyOptions()
267 for opt in Sfi.required_options:
268 if not hasattr(options,opt): setattr(options,opt,None)
269 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
270 self.options = options
272 self.authority = None
273 self.logger = sfi_logger
274 self.logger.enable_console()
275 self.available_names = [ tuple[0] for tuple in Sfi.available ]
276 self.available_dict = dict (Sfi.available)
278 # tuples command-name expected-args in the order in which they should appear in the help
281 ("list", "authority"),
284 ("update", "record"),
287 ("describe", "slice_hrn"),
288 ("create", "slice_hrn rspec"),
289 ("allocate", "slice_hrn rspec"),
290 ("provison", "slice_hrn"),
291 ("action", "slice_hrn action"),
292 ("delete", "slice_hrn"),
293 ("status", "slice_hrn"),
294 ("renew", "slice_hrn time"),
295 ("shutdown", "slice_hrn"),
296 ("get_ticket", "slice_hrn rspec"),
297 ("redeem_ticket", "ticket"),
298 ("delegate", "name"),
304 def print_command_help (self, options):
305 verbose=getattr(options,'verbose')
306 format3="%18s %-15s %s"
309 print format3%("command","cmd_args","description")
313 self.create_parser().print_help()
314 for command in self.available_names:
315 args=self.available_dict[command]
316 method=getattr(self,command,None)
318 if method: doc=getattr(method,'__doc__',"")
319 if not doc: doc="*** no doc found ***"
320 doc=doc.strip(" \t\n")
321 doc=doc.replace("\n","\n"+35*' ')
324 print format3%(command,args,doc)
326 self.create_command_parser(command).print_help()
328 def create_command_parser(self, command):
329 if command not in self.available_dict:
330 msg="Invalid command\n"
332 msg += ','.join(self.available_names)
333 self.logger.critical(msg)
336 parser = OptionParser(usage="sfi [sfi_options] %s [cmd_options] %s" \
337 % (command, self.available_dict[command]))
339 if command in ("add", "update"):
340 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
341 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
342 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
343 # use --extra instead
344 # parser.add_option('-u', '--url', dest='url', metavar='<url>', default=None, help="URL, useful for slices")
345 # parser.add_option('-d', '--description', dest='description', metavar='<description>',
346 # help='Description, useful for slices', default=None)
347 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
349 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='slice xrns',
350 default='', type="str", action='callback', callback=optparse_listvalue_callback)
351 parser.add_option('-r', '--researchers', dest='researchers', metavar='<researchers>',
352 help='slice researchers', default='', type="str", action='callback',
353 callback=optparse_listvalue_callback)
354 parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Principal Investigators/Project Managers',
355 default='', type="str", action='callback', callback=optparse_listvalue_callback)
356 # use --extra instead
357 # parser.add_option('-f', '--firstname', dest='firstname', metavar='<firstname>', help='user first name')
358 # parser.add_option('-l', '--lastname', dest='lastname', metavar='<lastname>', help='user last name')
359 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
360 action="callback", callback=optparse_dictvalue_callback, nargs=1,
361 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
363 # user specifies remote aggregate/sm/component
364 if command in ("resources", "describe", "create", "delete", "allocate", "provision",
365 "action", "shutdown", "get_ticket", "renew", "status"):
366 parser.add_option("-d", "--delegate", dest="delegate", default=None,
368 help="Include a credential delegated to the user's root"+\
369 "authority in set of credentials for this call")
371 # show_credential option
372 if command in ("list","resources","create","add","update","remove","slices","delete","status","renew"):
373 parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
374 help="show credential(s) used in human-readable form")
375 # registy filter option
376 if command in ("list", "show", "remove"):
377 parser.add_option("-t", "--type", dest="type", type="choice",
378 help="type filter ([all]|user|slice|authority|node|aggregate)",
379 choices=("all", "user", "slice", "authority", "node", "aggregate"),
381 if command in ("show"):
382 parser.add_option("-k","--key",dest="keys",action="append",default=[],
383 help="specify specific keys to be displayed from record")
384 if command in ("resources"):
386 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
387 help="schema type and version of resulting RSpec")
388 # disable/enable cached rspecs
389 parser.add_option("-c", "--current", dest="current", default=False,
391 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
393 parser.add_option("-f", "--format", dest="format", type="choice",
394 help="display format ([xml]|dns|ip)", default="xml",
395 choices=("xml", "dns", "ip"))
396 #panos: a new option to define the type of information about resources a user is interested in
397 parser.add_option("-i", "--info", dest="info",
398 help="optional component information", default=None)
399 # a new option to retreive or not reservation-oriented RSpecs (leases)
400 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
401 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
402 choices=("all", "resources", "leases"), default="resources")
405 # 'create' does return the new rspec, makes sense to save that too
406 if command in ("resources", "show", "list", "gid", 'create'):
407 parser.add_option("-o", "--output", dest="file",
408 help="output XML to file", metavar="FILE", default=None)
410 if command in ("show", "list"):
411 parser.add_option("-f", "--format", dest="format", type="choice",
412 help="display format ([text]|xml)", default="text",
413 choices=("text", "xml"))
415 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
416 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
417 choices=("xml", "xmllist", "hrnlist"))
418 if command == 'list':
419 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
420 help="list all child records", default=False)
421 if command in ("delegate"):
422 parser.add_option("-u", "--user",
423 action="store_true", dest="delegate_user", default=False,
424 help="delegate user credential")
425 parser.add_option("-s", "--slice", dest="delegate_slice",
426 help="delegate slice credential", metavar="HRN", default=None)
428 if command in ("version"):
429 parser.add_option("-R","--registry-version",
430 action="store_true", dest="version_registry", default=False,
431 help="probe registry version instead of sliceapi")
432 parser.add_option("-l","--local",
433 action="store_true", dest="version_local", default=False,
434 help="display version of the local client")
439 def create_parser(self):
441 # Generate command line parser
442 parser = OptionParser(usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
443 description="Commands: %s"%(" ".join(self.available_names)))
444 parser.add_option("-r", "--registry", dest="registry",
445 help="root registry", metavar="URL", default=None)
446 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
447 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
448 parser.add_option("-R", "--raw", dest="raw", default=None,
449 help="Save raw, unparsed server response to a file")
450 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
451 help="raw file format ([text]|pickled|json)", default="text",
452 choices=("text","pickled","json"))
453 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
454 help="text string to write before and after raw output")
455 parser.add_option("-d", "--dir", dest="sfi_dir",
456 help="config & working directory - default is %default",
457 metavar="PATH", default=Sfi.default_sfi_dir())
458 parser.add_option("-u", "--user", dest="user",
459 help="user name", metavar="HRN", default=None)
460 parser.add_option("-a", "--auth", dest="auth",
461 help="authority name", metavar="HRN", default=None)
462 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
463 help="verbose mode - cumulative")
464 parser.add_option("-D", "--debug",
465 action="store_true", dest="debug", default=False,
466 help="Debug (xml-rpc) protocol messages")
467 # would it make sense to use ~/.ssh/id_rsa as a default here ?
468 parser.add_option("-k", "--private-key",
469 action="store", dest="user_private_key", default=None,
470 help="point to the private key file to use if not yet installed in sfi_dir")
471 parser.add_option("-t", "--timeout", dest="timeout", default=None,
472 help="Amout of time to wait before timing out the request")
473 parser.add_option("-?", "--commands",
474 action="store_true", dest="command_help", default=False,
475 help="one page summary on commands & exit")
476 parser.disable_interspersed_args()
481 def print_help (self):
482 print "==================== Generic sfi usage"
483 self.sfi_parser.print_help()
484 print "==================== Specific command usage"
485 self.command_parser.print_help()
488 # Main: parse arguments and dispatch to command
490 def dispatch(self, command, command_options, command_args):
491 return getattr(self, command)(command_options, command_args)
494 self.sfi_parser = self.create_parser()
495 (options, args) = self.sfi_parser.parse_args()
496 if options.command_help:
497 self.print_command_help(options)
499 self.options = options
501 self.logger.setLevelFromOptVerbose(self.options.verbose)
504 self.logger.critical("No command given. Use -h for help.")
505 self.print_command_help(options)
508 # complete / find unique match with command set
509 command_candidates = Candidates (self.available_names)
511 command = command_candidates.only_match(input)
513 self.print_command_help(options)
515 # second pass options parsing
516 self.command_parser = self.create_command_parser(command)
517 (command_options, command_args) = self.command_parser.parse_args(args[1:])
518 self.command_options = command_options
522 self.logger.debug("Command=%s" % command)
525 self.dispatch(command, command_options, command_args)
527 self.logger.critical ("Unknown command %s"%command)
533 def read_config(self):
534 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
535 shell_config_file = os.path.join(self.options.sfi_dir,"sfi_config.sh")
537 if Config.is_ini(config_file):
538 config = Config (config_file)
540 # try upgrading from shell config format
541 fp, fn = mkstemp(suffix='sfi_config', text=True)
543 # we need to preload the sections we want parsed
544 # from the shell config
545 config.add_section('sfi')
546 config.add_section('sface')
547 config.load(config_file)
549 shutil.move(config_file, shell_config_file)
551 config.save(config_file)
554 self.logger.critical("Failed to read configuration file %s"%config_file)
555 self.logger.info("Make sure to remove the export clauses and to add quotes")
556 if self.options.verbose==0:
557 self.logger.info("Re-run with -v for more details")
559 self.logger.log_exc("Could not read config file %s"%config_file)
564 if (self.options.sm is not None):
565 self.sm_url = self.options.sm
566 elif hasattr(config, "SFI_SM"):
567 self.sm_url = config.SFI_SM
569 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
573 if (self.options.registry is not None):
574 self.reg_url = self.options.registry
575 elif hasattr(config, "SFI_REGISTRY"):
576 self.reg_url = config.SFI_REGISTRY
578 self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
582 if (self.options.user is not None):
583 self.user = self.options.user
584 elif hasattr(config, "SFI_USER"):
585 self.user = config.SFI_USER
587 self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
591 if (self.options.auth is not None):
592 self.authority = self.options.auth
593 elif hasattr(config, "SFI_AUTH"):
594 self.authority = config.SFI_AUTH
596 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
599 self.config_file=config_file
603 def show_config (self):
604 print "From configuration file %s"%self.config_file
607 ('SFI_AUTH','authority'),
609 ('SFI_REGISTRY','reg_url'),
611 for (external_name, internal_name) in flags:
612 print "%s='%s'"%(external_name,getattr(self,internal_name))
615 # Get various credential and spec files
617 # Establishes limiting conventions
618 # - conflates MAs and SAs
619 # - assumes last token in slice name is unique
621 # Bootstraps credentials
622 # - bootstrap user credential from self-signed certificate
623 # - bootstrap authority credential from user credential
624 # - bootstrap slice credential from user credential
627 # init self-signed cert, user credentials and gid
628 def bootstrap (self):
629 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
631 # if -k is provided, use this to initialize private key
632 if self.options.user_private_key:
633 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
635 # trigger legacy compat code if needed
636 # the name has changed from just <leaf>.pkey to <hrn>.pkey
637 if not os.path.isfile(client_bootstrap.private_key_filename()):
638 self.logger.info ("private key not found, trying legacy name")
640 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
641 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
642 client_bootstrap.init_private_key_if_missing (legacy_private_key)
643 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
645 self.logger.log_exc("Can't find private key ")
649 client_bootstrap.bootstrap_my_gid()
650 # extract what's needed
651 self.private_key = client_bootstrap.private_key()
652 self.my_credential_string = client_bootstrap.my_credential_string ()
653 self.my_gid = client_bootstrap.my_gid ()
654 self.client_bootstrap = client_bootstrap
657 def my_authority_credential_string(self):
658 if not self.authority:
659 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
661 return self.client_bootstrap.authority_credential_string (self.authority)
663 def slice_credential_string(self, name):
664 return self.client_bootstrap.slice_credential_string (name)
666 # xxx should be supported by sfaclientbootstrap as well
667 def delegate_cred(self, object_cred, hrn, type='authority'):
668 # the gid and hrn of the object we are delegating
669 if isinstance(object_cred, str):
670 object_cred = Credential(string=object_cred)
671 object_gid = object_cred.get_gid_object()
672 object_hrn = object_gid.get_hrn()
674 if not object_cred.get_privileges().get_all_delegate():
675 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
678 # the delegating user's gid
679 caller_gidfile = self.my_gid()
681 # the gid of the user who will be delegated to
682 delegee_gid = self.client_bootstrap.gid(hrn,type)
683 delegee_hrn = delegee_gid.get_hrn()
684 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
685 return dcred.save_to_string(save_parents=True)
688 # Management of the servers
693 if not hasattr (self, 'registry_proxy'):
694 self.logger.info("Contacting Registry at: %s"%self.reg_url)
695 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
696 timeout=self.options.timeout, verbose=self.options.debug)
697 return self.registry_proxy
701 if not hasattr (self, 'sliceapi_proxy'):
702 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
703 if hasattr(self.command_options,'component') and self.command_options.component:
704 # resolve the hrn at the registry
705 node_hrn = self.command_options.component
706 records = self.registry().Resolve(node_hrn, self.my_credential_string)
707 records = filter_records('node', records)
709 self.logger.warning("No such component:%r"% opts.component)
711 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
712 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
714 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
715 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
716 self.sm_url = 'http://' + self.sm_url
717 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
718 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
719 timeout=self.options.timeout, verbose=self.options.debug)
720 return self.sliceapi_proxy
722 def get_cached_server_version(self, server):
723 # check local cache first
726 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
727 cache_key = server.url + "-version"
729 cache = Cache(cache_file)
732 self.logger.info("Local cache not found at: %s" % cache_file)
735 version = cache.get(cache_key)
738 result = server.GetVersion()
739 version= ReturnValue.get_value(result)
740 # cache version for 20 minutes
741 cache.add(cache_key, version, ttl= 60*20)
742 self.logger.info("Updating cache file %s" % cache_file)
743 cache.save_to_file(cache_file)
747 ### resurrect this temporarily so we can support V1 aggregates for a while
748 def server_supports_options_arg(self, server):
750 Returns true if server support the optional call_id arg, false otherwise.
752 server_version = self.get_cached_server_version(server)
754 # xxx need to rewrite this
755 if int(server_version.get('geni_api')) >= 2:
759 def server_supports_call_id_arg(self, server):
760 server_version = self.get_cached_server_version(server)
762 if 'sfa' in server_version and 'code_tag' in server_version:
763 code_tag = server_version['code_tag']
764 code_tag_parts = code_tag.split("-")
765 version_parts = code_tag_parts[0].split(".")
766 major, minor = version_parts[0], version_parts[1]
767 rev = code_tag_parts[1]
768 if int(major) == 1 and minor == 0 and build >= 22:
772 ### ois = options if supported
773 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
774 def ois (self, server, option_dict):
775 if self.server_supports_options_arg (server):
777 elif self.server_supports_call_id_arg (server):
778 return [ unique_call_id () ]
782 ### cis = call_id if supported - like ois
783 def cis (self, server):
784 if self.server_supports_call_id_arg (server):
785 return [ unique_call_id ]
789 ######################################## miscell utilities
790 def get_rspec_file(self, rspec):
791 if (os.path.isabs(rspec)):
794 file = os.path.join(self.options.sfi_dir, rspec)
795 if (os.path.isfile(file)):
798 self.logger.critical("No such rspec file %s"%rspec)
801 def get_record_file(self, record):
802 if (os.path.isabs(record)):
805 file = os.path.join(self.options.sfi_dir, record)
806 if (os.path.isfile(file)):
809 self.logger.critical("No such registry record file %s"%record)
813 #==========================================================================
814 # Following functions implement the commands
816 # Registry-related commands
817 #==========================================================================
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 def list(self, options, args):
841 list entries in named authority registry (List)
848 if options.recursive:
849 opts['recursive'] = options.recursive
851 if options.show_credential:
852 show_credentials(self.my_credential_string)
854 list = self.registry().List(hrn, self.my_credential_string, options)
856 raise Exception, "Not enough parameters for the 'list' command"
858 # filter on person, slice, site, node, etc.
859 # THis really should be in the self.filter_records funct def comment...
860 list = filter_records(options.type, list)
862 print "%s (%s)" % (record['hrn'], record['type'])
864 save_records_to_file(options.file, list, options.fileformat)
867 def show(self, options, args):
869 show details about named registry record (Resolve)
875 record_dicts = self.registry().Resolve(hrn, self.my_credential_string)
876 record_dicts = filter_records(options.type, record_dicts)
878 self.logger.error("No record of type %s"% options.type)
880 # user has required to focus on some keys
882 def project (record):
884 for key in options.keys:
885 try: projected[key]=record[key]
888 record_dicts = [ project (record) for record in record_dicts ]
889 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
890 for record in records:
891 if (options.format == "text"): record.dump(sort=True)
892 else: print record.save_as_xml()
894 save_records_to_file(options.file, record_dicts, options.fileformat)
897 def add(self, options, args):
898 "add record into registry from xml file (Register)"
899 auth_cred = self.my_authority_credential_string()
900 if options.show_credential:
901 show_credentials(auth_cred)
904 record_filepath = args[0]
905 rec_file = self.get_record_file(record_filepath)
906 record_dict.update(load_record_from_file(rec_file).todict())
908 record_dict.update(load_record_from_opts(options).todict())
909 # we should have a type by now
910 if 'type' not in record_dict :
913 # this is still planetlab dependent.. as plc will whine without that
914 # also, it's only for adding
915 if record_dict['type'] == 'user':
916 if not 'first_name' in record_dict:
917 record_dict['first_name'] = record_dict['hrn']
918 if 'last_name' not in record_dict:
919 record_dict['last_name'] = record_dict['hrn']
920 return self.registry().Register(record_dict, auth_cred)
922 def update(self, options, args):
923 "update record into registry from xml file (Update)"
926 record_filepath = args[0]
927 rec_file = self.get_record_file(record_filepath)
928 record_dict.update(load_record_from_file(rec_file).todict())
930 record_dict.update(load_record_from_opts(options).todict())
931 # at the very least we need 'type' here
932 if 'type' not in record_dict:
936 # don't translate into an object, as this would possibly distort
937 # user-provided data; e.g. add an 'email' field to Users
938 if record_dict['type'] == "user":
939 if record_dict['hrn'] == self.user:
940 cred = self.my_credential_string
942 cred = self.my_authority_credential_string()
943 elif record_dict['type'] in ["slice"]:
945 cred = self.slice_credential_string(record_dict['hrn'])
946 except ServerException, e:
947 # XXX smbaker -- once we have better error return codes, update this
948 # to do something better than a string compare
949 if "Permission error" in e.args[0]:
950 cred = self.my_authority_credential_string()
953 elif record_dict['type'] in ["authority"]:
954 cred = self.my_authority_credential_string()
955 elif record_dict['type'] == 'node':
956 cred = self.my_authority_credential_string()
958 raise "unknown record type" + record_dict['type']
959 if options.show_credential:
960 show_credentials(cred)
961 return self.registry().Update(record_dict, cred)
963 def remove(self, options, args):
964 "remove registry record by name (Remove)"
965 auth_cred = self.my_authority_credential_string()
973 if options.show_credential:
974 show_credentials(auth_cred)
975 return self.registry().Remove(hrn, auth_cred, type)
977 # ==================================================================
978 # Slice-related commands
979 # ==================================================================
981 def slices(self, options, args):
982 "list instantiated slices (ListSlices) - returns urn's"
983 server = self.sliceapi()
985 creds = [self.my_credential_string]
987 delegated_cred = self.delegate_cred(self.my_credential_string, get_authority(self.authority))
988 creds.append(delegated_cred)
989 # options and call_id when supported
991 api_options['call_id']=unique_call_id()
992 if options.show_credential:
993 show_credentials(creds)
994 result = server.ListSlices(creds, *self.ois(server,api_options))
995 value = ReturnValue.get_value(result)
997 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1002 # show rspec for named slice
1003 def resources(self, options, args):
1005 discover available resources
1006 or with an slice hrn, shows currently provisioned resources
1008 server = self.sliceapi()
1011 creds = [self.my_credential_string]
1012 if options.delegate:
1013 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1014 if options.show_credential:
1015 show_credentials(creds)
1017 # no need to check if server accepts the options argument since the options has
1018 # been a required argument since v1 API
1020 # always send call_id to v2 servers
1021 api_options ['call_id'] = unique_call_id()
1022 # ask for cached value if available
1023 api_options ['cached'] = True
1025 api_options['info'] = options.info
1026 if options.list_leases:
1027 api_options['list_leases'] = options.list_leases
1029 if options.current == True:
1030 api_options['cached'] = False
1032 api_options['cached'] = True
1033 if options.rspec_version:
1034 version_manager = VersionManager()
1035 server_version = self.get_cached_server_version(server)
1036 if 'sfa' in server_version:
1037 # just request the version the client wants
1038 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1040 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1042 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1043 result = server.ListResources (creds, api_options)
1044 value = ReturnValue.get_value(result)
1045 if self.options.raw:
1046 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1047 if options.file is not None:
1048 save_rspec_to_file(value, options.file)
1049 if (self.options.raw is None) and (options.file is None):
1050 display_rspec(value, options.format)
1054 def describe(self, options, args):
1056 Shows currently provisioned resources.
1058 server = self.sliceapi()
1061 creds = [self.slice_credential_string(args[0])]
1062 if options.delegate:
1063 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1064 if options.show_credential:
1065 show_credentials(creds)
1067 api_options = {'call_id': unique_call_id(),
1069 'info': options.info,
1070 'list_leases': options.list_lease,
1071 'geni_rspec_version': {'type': 'geni', 'version': '3.0'},
1073 if options.rspec_version:
1074 version_manager = VersionManager()
1075 server_version = self.get_cached_server_version(server)
1076 if 'sfa' in server_version:
1077 # just request the version the client wants
1078 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1080 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1081 urn = Xrn(args[0], type='slice').get_urn()
1082 result = server.Describe([urn], creds, api_options)
1083 value = ReturnValue.get_value(result)
1084 if self.options.raw:
1085 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1086 if options.file is not None:
1087 save_rspec_to_file(value, options.file)
1088 if (self.options.raw is None) and (options.file is None):
1089 display_rspec(value, options.format)
1093 def create(self, options, args):
1095 create or update named slice with given rspec
1097 server = self.sliceapi()
1099 # xxx do we need to check usage (len(args)) ?
1102 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1105 creds = [self.slice_credential_string(slice_hrn)]
1107 delegated_cred = None
1108 server_version = self.get_cached_server_version(server)
1109 if server_version.get('interface') == 'slicemgr':
1110 # delegate our cred to the slice manager
1111 # do not delegate cred to slicemgr...not working at the moment
1113 #if server_version.get('hrn'):
1114 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1115 #elif server_version.get('urn'):
1116 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1118 if options.show_credential:
1119 show_credentials(creds)
1122 rspec_file = self.get_rspec_file(args[1])
1123 rspec = open(rspec_file).read()
1126 # need to pass along user keys to the aggregate.
1128 # { urn: urn:publicid:IDN+emulab.net+user+alice
1129 # keys: [<ssh key A>, <ssh key B>]
1132 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1133 if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
1134 slice_record = slice_records[0]
1135 user_hrns = slice_record['researcher']
1136 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1137 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1139 if 'sfa' not in server_version:
1140 users = pg_users_arg(user_records)
1141 rspec = RSpec(rspec)
1142 rspec.filter({'component_manager_id': server_version['urn']})
1143 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1145 users = sfa_users_arg(user_records, slice_record)
1147 # do not append users, keys, or slice tags. Anything
1148 # not contained in this request will be removed from the slice
1151 api_options ['append'] = False
1152 api_options ['call_id'] = unique_call_id()
1153 result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
1154 value = ReturnValue.get_value(result)
1155 if self.options.raw:
1156 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1157 if options.file is not None:
1158 save_rspec_to_file (value, options.file)
1159 if (self.options.raw is None) and (options.file is None):
1164 def delete(self, options, args):
1166 delete named slice (DeleteSliver)
1168 server = self.sliceapi()
1172 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1175 slice_cred = self.slice_credential_string(slice_hrn)
1176 creds = [slice_cred]
1177 if options.delegate:
1178 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1179 creds.append(delegated_cred)
1181 # options and call_id when supported
1183 api_options ['call_id'] = unique_call_id()
1184 if options.show_credential:
1185 show_credentials(creds)
1186 result = server.Delete(slice_urn, creds, *self.ois(server, api_options ) )
1187 value = ReturnValue.get_value(result)
1188 if self.options.raw:
1189 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1194 def allocate(self, options, args):
1196 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1199 creds = [self.slice_credential_string(slice_hrn)]
1201 delegated_cred = None
1202 server_version = self.get_cached_server_version(server)
1203 if server_version.get('interface') == 'slicemgr':
1204 # delegate our cred to the slice manager
1205 # do not delegate cred to slicemgr...not working at the moment
1207 #if server_version.get('hrn'):
1208 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1209 #elif server_version.get('urn'):
1210 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1212 if options.show_credential:
1213 show_credentials(creds)
1216 rspec_file = self.get_rspec_file(args[1])
1217 rspec = open(rspec_file).read()
1220 # need to pass along user keys to the aggregate.
1222 # { urn: urn:publicid:IDN+emulab.net+user+alice
1223 # keys: [<ssh key A>, <ssh key B>]
1226 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1227 if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
1228 slice_record = slice_records[0]
1229 user_hrns = slice_record['researcher']
1230 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1231 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1233 if 'sfa' not in server_version:
1234 users = pg_users_arg(user_records)
1235 rspec = RSpec(rspec)
1236 rspec.filter({'component_manager_id': server_version['urn']})
1237 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1239 users = sfa_users_arg(user_records, slice_record)
1242 api_options ['call_id'] = unique_call_id()
1243 api_options['geni_users'] = users
1244 result = server.Allocate(slice_urn, creds, rspec, api_options)
1245 value = ReturnValue.get_value(result)
1246 if self.options.raw:
1247 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1248 if options.file is not None:
1249 save_rspec_to_file (value, options.file)
1250 if (self.options.raw is None) and (options.file is None):
1256 def provision(self, options, args):
1258 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1261 creds = [self.slice_credential_string(slice_hrn)]
1262 delegated_cred = None
1263 server_version = self.get_cached_server_version(server)
1264 if server_version.get('interface') == 'slicemgr':
1265 # delegate our cred to the slice manager
1266 # do not delegate cred to slicemgr...not working at the moment
1268 #if server_version.get('hrn'):
1269 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1270 #elif server_version.get('urn'):
1271 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1273 if options.show_credential:
1274 show_credentials(creds)
1277 api_options ['call_id'] = unique_call_id()
1278 result = server.CreateSliver(slice_urn, creds, api_options)
1279 value = ReturnValue.get_value(result)
1280 if self.options.raw:
1281 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1282 if options.file is not None:
1283 save_rspec_to_file (value, options.file)
1284 if (self.options.raw is None) and (options.file is None):
1288 def status(self, options, args):
1290 retrieve slice status (SliverStatus)
1292 server = self.sliceapi()
1296 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1299 slice_cred = self.slice_credential_string(slice_hrn)
1300 creds = [slice_cred]
1301 if options.delegate:
1302 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1303 creds.append(delegated_cred)
1305 # options and call_id when supported
1307 api_options['call_id']=unique_call_id()
1308 if options.show_credential:
1309 show_credentials(creds)
1310 result = server.Status(slice_urn, creds, *self.ois(server,api_options))
1311 value = ReturnValue.get_value(result)
1312 if self.options.raw:
1313 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1317 def start(self, options, args):
1319 start named slice (Start)
1321 server = self.sliceapi()
1325 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1328 slice_cred = self.slice_credential_string(args[0])
1329 creds = [slice_cred]
1330 if options.delegate:
1331 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1332 creds.append(delegated_cred)
1333 # xxx Thierry - does this not need an api_options as well ?
1334 result = server.Start(slice_urn, creds)
1335 value = ReturnValue.get_value(result)
1336 if self.options.raw:
1337 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1342 def stop(self, options, args):
1344 stop named slice (Stop)
1346 server = self.sliceapi()
1349 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1351 slice_cred = self.slice_credential_string(args[0])
1352 creds = [slice_cred]
1353 if options.delegate:
1354 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1355 creds.append(delegated_cred)
1356 result = server.Stop(slice_urn, creds)
1357 value = ReturnValue.get_value(result)
1358 if self.options.raw:
1359 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1365 def action(self, options, args):
1367 Perform the named operational action on the named slivers
1369 server = self.sliceapi()
1373 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1375 slice_cred = self.slice_credential_string(args[0])
1376 creds = [slice_cred]
1377 if options.delegate:
1378 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1379 creds.append(delegated_cred)
1381 result = server.PerformOperationalAction(slice_urn, creds, action )
1382 value = ReturnValue.get_value(result)
1383 if self.options.raw:
1384 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1389 def renew(self, options, args):
1391 renew slice (RenewSliver)
1393 server = self.sliceapi()
1397 [ slice_hrn, input_time ] = args
1399 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1400 # time: don't try to be smart on the time format, server-side will
1402 slice_cred = self.slice_credential_string(args[0])
1403 creds = [slice_cred]
1404 if options.delegate:
1405 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1406 creds.append(delegated_cred)
1407 # options and call_id when supported
1409 api_options['call_id']=unique_call_id()
1410 if options.show_credential:
1411 show_credentials(creds)
1412 result = server.Renew(slice_urn, creds, input_time, *self.ois(server,api_options))
1413 value = ReturnValue.get_value(result)
1414 if self.options.raw:
1415 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1421 def shutdown(self, options, args):
1423 shutdown named slice (Shutdown)
1425 server = self.sliceapi()
1428 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1430 slice_cred = self.slice_credential_string(slice_hrn)
1431 creds = [slice_cred]
1432 if options.delegate:
1433 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1434 creds.append(delegated_cred)
1435 result = server.Shutdown(slice_urn, creds)
1436 value = ReturnValue.get_value(result)
1437 if self.options.raw:
1438 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1444 def get_ticket(self, options, args):
1446 get a ticket for the specified slice
1448 server = self.sliceapi()
1450 slice_hrn, rspec_path = args[0], args[1]
1451 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1453 slice_cred = self.slice_credential_string(slice_hrn)
1454 creds = [slice_cred]
1455 if options.delegate:
1456 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1457 creds.append(delegated_cred)
1459 rspec_file = self.get_rspec_file(rspec_path)
1460 rspec = open(rspec_file).read()
1461 # options and call_id when supported
1463 api_options['call_id']=unique_call_id()
1464 # get ticket at the server
1465 ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1467 file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1468 self.logger.info("writing ticket to %s"%file)
1469 ticket = SfaTicket(string=ticket_string)
1470 ticket.save_to_file(filename=file, save_parents=True)
1472 def redeem_ticket(self, options, args):
1474 Connects to nodes in a slice and redeems a ticket
1475 (slice hrn is retrieved from the ticket)
1477 ticket_file = args[0]
1479 # get slice hrn from the ticket
1480 # use this to get the right slice credential
1481 ticket = SfaTicket(filename=ticket_file)
1483 ticket_string = ticket.save_to_string(save_parents=True)
1485 slice_hrn = ticket.gidObject.get_hrn()
1486 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1487 #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1488 slice_cred = self.slice_credential_string(slice_hrn)
1490 # get a list of node hostnames from the RSpec
1491 tree = etree.parse(StringIO(ticket.rspec))
1492 root = tree.getroot()
1493 hostnames = root.xpath("./network/site/node/hostname/text()")
1495 # create an xmlrpc connection to the component manager at each of these
1496 # components and gall redeem_ticket
1498 for hostname in hostnames:
1500 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1501 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1502 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1503 server = self.server_proxy(hostname, CM_PORT, self.private_key,
1504 timeout=self.options.timeout, verbose=self.options.debug)
1505 server.RedeemTicket(ticket_string, slice_cred)
1506 self.logger.info("Success")
1507 except socket.gaierror:
1508 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1509 except Exception, e:
1510 self.logger.log_exc(e.message)
1513 def gid(self, options, args):
1515 Create a GID (CreateGid)
1520 target_hrn = args[0]
1521 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.client_bootstrap.my_gid_string())
1523 filename = options.file
1525 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1526 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1527 GID(string=gid).save_to_file(filename)
1530 def delegate(self, options, args):
1532 (locally) create delegate credential for use by given hrn
1534 delegee_hrn = args[0]
1535 if options.delegate_user:
1536 cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1537 elif options.delegate_slice:
1538 slice_cred = self.slice_credential_string(options.delegate_slice)
1539 cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1541 self.logger.warning("Must specify either --user or --slice <hrn>")
1543 delegated_cred = Credential(string=cred)
1544 object_hrn = delegated_cred.get_gid_object().get_hrn()
1545 if options.delegate_user:
1546 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1547 + get_leaf(object_hrn) + ".cred")
1548 elif options.delegate_slice:
1549 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1550 + get_leaf(object_hrn) + ".cred")
1552 delegated_cred.save_to_file(dest_fn, save_parents=True)
1554 self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1556 def trusted(self, options, args):
1558 return uhe trusted certs at this interface (get_trusted_certs)
1560 trusted_certs = self.registry().get_trusted_certs()
1561 for trusted_cert in trusted_certs:
1562 gid = GID(string=trusted_cert)
1564 cert = Certificate(string=trusted_cert)
1565 self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1568 def config (self, options, args):
1569 "Display contents of current config"