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 ("provision", "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", "allocate", "provision", "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", "describe", "provision", "allocate", "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", "describe"):
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", "describe", "allocate", "provision", "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_leases,
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):
1195 server = self.sliceapi()
1196 server_version = self.get_cached_server_version(server)
1198 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1201 creds = [self.slice_credential_string(slice_hrn)]
1203 delegated_cred = None
1204 if server_version.get('interface') == 'slicemgr':
1205 # delegate our cred to the slice manager
1206 # do not delegate cred to slicemgr...not working at the moment
1208 #if server_version.get('hrn'):
1209 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1210 #elif server_version.get('urn'):
1211 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1213 if options.show_credential:
1214 show_credentials(creds)
1217 rspec_file = self.get_rspec_file(args[1])
1218 rspec = open(rspec_file).read()
1221 # need to pass along user keys to the aggregate.
1223 # { urn: urn:publicid:IDN+emulab.net+user+alice
1224 # keys: [<ssh key A>, <ssh key B>]
1227 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1228 if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
1229 slice_record = slice_records[0]
1230 user_hrns = slice_record['researcher']
1231 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1232 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1234 if 'sfa' not in server_version:
1235 users = pg_users_arg(user_records)
1236 rspec = RSpec(rspec)
1237 cm_hrn = Xrn(xrn=server_version['urn']).get_hrn()
1238 cm_urn = Xrn(cm_hrn, type='authority+cm').get_urn()
1239 rspec.filter({'component_manager_id': cm_urn})
1240 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1242 users = sfa_users_arg(user_records, slice_record)
1245 api_options ['call_id'] = unique_call_id()
1246 api_options['geni_users'] = users
1247 result = server.Allocate(slice_urn, creds, rspec, api_options)
1248 value = ReturnValue.get_value(result)
1249 if self.options.raw:
1250 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1251 if options.file is not None:
1252 save_rspec_to_file (value, options.file)
1253 if (self.options.raw is None) and (options.file is None):
1259 def provision(self, options, args):
1260 server = self.sliceapi()
1261 server_version = self.get_cached_server_version(server)
1263 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1266 creds = [self.slice_credential_string(slice_hrn)]
1267 delegated_cred = None
1268 if server_version.get('interface') == 'slicemgr':
1269 # delegate our cred to the slice manager
1270 # do not delegate cred to slicemgr...not working at the moment
1272 #if server_version.get('hrn'):
1273 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1274 #elif server_version.get('urn'):
1275 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1277 if options.show_credential:
1278 show_credentials(creds)
1281 api_options ['call_id'] = unique_call_id()
1282 result = server.Provision([slice_urn], creds, api_options)
1283 value = ReturnValue.get_value(result)
1284 if self.options.raw:
1285 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1286 if options.file is not None:
1287 save_rspec_to_file (value, options.file)
1288 if (self.options.raw is None) and (options.file is None):
1292 def status(self, options, args):
1294 retrieve slice status (SliverStatus)
1296 server = self.sliceapi()
1300 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1303 slice_cred = self.slice_credential_string(slice_hrn)
1304 creds = [slice_cred]
1305 if options.delegate:
1306 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1307 creds.append(delegated_cred)
1309 # options and call_id when supported
1311 api_options['call_id']=unique_call_id()
1312 if options.show_credential:
1313 show_credentials(creds)
1314 result = server.Status([slice_urn], creds, *self.ois(server,api_options))
1315 value = ReturnValue.get_value(result)
1316 if self.options.raw:
1317 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1321 def start(self, options, args):
1323 start named slice (Start)
1325 server = self.sliceapi()
1329 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1332 slice_cred = self.slice_credential_string(args[0])
1333 creds = [slice_cred]
1334 if options.delegate:
1335 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1336 creds.append(delegated_cred)
1337 # xxx Thierry - does this not need an api_options as well ?
1338 result = server.Start(slice_urn, creds)
1339 value = ReturnValue.get_value(result)
1340 if self.options.raw:
1341 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1346 def stop(self, options, args):
1348 stop named slice (Stop)
1350 server = self.sliceapi()
1353 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1355 slice_cred = self.slice_credential_string(args[0])
1356 creds = [slice_cred]
1357 if options.delegate:
1358 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1359 creds.append(delegated_cred)
1360 result = server.Stop(slice_urn, creds)
1361 value = ReturnValue.get_value(result)
1362 if self.options.raw:
1363 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1369 def action(self, options, args):
1371 Perform the named operational action on the named slivers
1373 server = self.sliceapi()
1377 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1379 slice_cred = self.slice_credential_string(args[0])
1380 creds = [slice_cred]
1381 if options.delegate:
1382 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1383 creds.append(delegated_cred)
1385 result = server.PerformOperationalAction(slice_urn, creds, action )
1386 value = ReturnValue.get_value(result)
1387 if self.options.raw:
1388 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1393 def renew(self, options, args):
1395 renew slice (RenewSliver)
1397 server = self.sliceapi()
1401 [ slice_hrn, input_time ] = args
1403 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1404 # time: don't try to be smart on the time format, server-side will
1406 slice_cred = self.slice_credential_string(args[0])
1407 creds = [slice_cred]
1408 if options.delegate:
1409 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1410 creds.append(delegated_cred)
1411 # options and call_id when supported
1413 api_options['call_id']=unique_call_id()
1414 if options.show_credential:
1415 show_credentials(creds)
1416 result = server.Renew([slice_urn], creds, input_time, *self.ois(server,api_options))
1417 value = ReturnValue.get_value(result)
1418 if self.options.raw:
1419 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1425 def shutdown(self, options, args):
1427 shutdown named slice (Shutdown)
1429 server = self.sliceapi()
1432 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1434 slice_cred = self.slice_credential_string(slice_hrn)
1435 creds = [slice_cred]
1436 if options.delegate:
1437 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1438 creds.append(delegated_cred)
1439 result = server.Shutdown(slice_urn, creds)
1440 value = ReturnValue.get_value(result)
1441 if self.options.raw:
1442 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1448 def get_ticket(self, options, args):
1450 get a ticket for the specified slice
1452 server = self.sliceapi()
1454 slice_hrn, rspec_path = args[0], args[1]
1455 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1457 slice_cred = self.slice_credential_string(slice_hrn)
1458 creds = [slice_cred]
1459 if options.delegate:
1460 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1461 creds.append(delegated_cred)
1463 rspec_file = self.get_rspec_file(rspec_path)
1464 rspec = open(rspec_file).read()
1465 # options and call_id when supported
1467 api_options['call_id']=unique_call_id()
1468 # get ticket at the server
1469 ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1471 file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1472 self.logger.info("writing ticket to %s"%file)
1473 ticket = SfaTicket(string=ticket_string)
1474 ticket.save_to_file(filename=file, save_parents=True)
1476 def redeem_ticket(self, options, args):
1478 Connects to nodes in a slice and redeems a ticket
1479 (slice hrn is retrieved from the ticket)
1481 ticket_file = args[0]
1483 # get slice hrn from the ticket
1484 # use this to get the right slice credential
1485 ticket = SfaTicket(filename=ticket_file)
1487 ticket_string = ticket.save_to_string(save_parents=True)
1489 slice_hrn = ticket.gidObject.get_hrn()
1490 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1491 #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1492 slice_cred = self.slice_credential_string(slice_hrn)
1494 # get a list of node hostnames from the RSpec
1495 tree = etree.parse(StringIO(ticket.rspec))
1496 root = tree.getroot()
1497 hostnames = root.xpath("./network/site/node/hostname/text()")
1499 # create an xmlrpc connection to the component manager at each of these
1500 # components and gall redeem_ticket
1502 for hostname in hostnames:
1504 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1505 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1506 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1507 server = self.server_proxy(hostname, CM_PORT, self.private_key,
1508 timeout=self.options.timeout, verbose=self.options.debug)
1509 server.RedeemTicket(ticket_string, slice_cred)
1510 self.logger.info("Success")
1511 except socket.gaierror:
1512 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1513 except Exception, e:
1514 self.logger.log_exc(e.message)
1517 def gid(self, options, args):
1519 Create a GID (CreateGid)
1524 target_hrn = args[0]
1525 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.client_bootstrap.my_gid_string())
1527 filename = options.file
1529 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1530 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1531 GID(string=gid).save_to_file(filename)
1534 def delegate(self, options, args):
1536 (locally) create delegate credential for use by given hrn
1538 delegee_hrn = args[0]
1539 if options.delegate_user:
1540 cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1541 elif options.delegate_slice:
1542 slice_cred = self.slice_credential_string(options.delegate_slice)
1543 cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1545 self.logger.warning("Must specify either --user or --slice <hrn>")
1547 delegated_cred = Credential(string=cred)
1548 object_hrn = delegated_cred.get_gid_object().get_hrn()
1549 if options.delegate_user:
1550 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1551 + get_leaf(object_hrn) + ".cred")
1552 elif options.delegate_slice:
1553 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1554 + get_leaf(object_hrn) + ".cred")
1556 delegated_cred.save_to_file(dest_fn, save_parents=True)
1558 self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1560 def trusted(self, options, args):
1562 return uhe trusted certs at this interface (get_trusted_certs)
1564 trusted_certs = self.registry().get_trusted_certs()
1565 for trusted_cert in trusted_certs:
1566 gid = GID(string=trusted_cert)
1568 cert = Certificate(string=trusted_cert)
1569 self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1572 def config (self, options, args):
1573 "Display contents of current config"