sfi: remove testbed-dependent options for add/update (like url or
[sfa.git] / sfa / client / sfi.py
1 #
2 # sfi.py - basic SFA command-line client
3 # the actual binary in sfa/clientbin essentially runs main()
4 # this module is used in sfascan
5 #
6
7 import sys
8 sys.path.append('.')
9
10 import os, os.path
11 import socket
12 import re
13 import datetime
14 import codecs
15 import pickle
16 import json
17 from lxml import etree
18 from StringIO import StringIO
19 from optparse import OptionParser
20 from pprint import PrettyPrinter
21
22 from sfa.trust.certificate import Keypair, Certificate
23 from sfa.trust.gid import GID
24 from sfa.trust.credential import Credential
25 from sfa.trust.sfaticket import SfaTicket
26
27 from sfa.util.faults import SfaInvalidArgument
28 from sfa.util.sfalogging import sfi_logger
29 from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn, Xrn
30 from sfa.util.config import Config
31 from sfa.util.version import version_core
32 from sfa.util.cache import Cache
33
34 from sfa.storage.record import Record
35
36 from sfa.rspecs.rspec import RSpec
37 from sfa.rspecs.rspec_converter import RSpecConverter
38 from sfa.rspecs.version_manager import VersionManager
39
40 from sfa.client.sfaclientlib import SfaClientBootstrap
41 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
42 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
43 from sfa.client.return_value import ReturnValue
44
45 CM_PORT=12346
46
47 # utility methods here
48 def optparse_listvalue_callback(option, option_string, value, parser):
49     setattr(parser.values, option.dest, value.split(','))
50
51 # a code fragment that could be helpful for argparse which unfortunately is 
52 # available with 2.7 only, so this feels like too strong a requirement for the client side
53 #class ExtraArgAction  (argparse.Action):
54 #    def __call__ (self, parser, namespace, values, option_string=None):
55 # would need a try/except of course
56 #        (k,v)=values.split('=')
57 #        d=getattr(namespace,self.dest)
58 #        d[k]=v
59 #####
60 #parser.add_argument ("-X","--extra",dest='extras', default={}, action=ExtraArgAction,
61 #                     help="set extra flags, testbed dependent, e.g. --extra enabled=true")
62     
63 def optparse_dictvalue_callback (option, option_string, value, parser):
64     try:
65         (k,v)=value.split('=',1)
66         d=getattr(parser.values, option.dest)
67         d[k]=v
68     except:
69         parser.print_help()
70         sys.exit(1)
71
72 # display methods
73 def display_rspec(rspec, format='rspec'):
74     if format in ['dns']:
75         tree = etree.parse(StringIO(rspec))
76         root = tree.getroot()
77         result = root.xpath("./network/site/node/hostname/text()")
78     elif format in ['ip']:
79         # The IP address is not yet part of the new RSpec
80         # so this doesn't do anything yet.
81         tree = etree.parse(StringIO(rspec))
82         root = tree.getroot()
83         result = root.xpath("./network/site/node/ipv4/text()")
84     else:
85         result = rspec
86
87     print result
88     return
89
90 def display_list(results):
91     for result in results:
92         print result
93
94 def display_records(recordList, dump=False):
95     ''' Print all fields in the record'''
96     for record in recordList:
97         display_record(record, dump)
98
99 def display_record(record, dump=False):
100     if dump:
101         record.dump()
102     else:
103         info = record.getdict()
104         print "%s (%s)" % (info['hrn'], info['type'])
105     return
106
107
108 def filter_records(type, records):
109     filtered_records = []
110     for record in records:
111         if (record['type'] == type) or (type == "all"):
112             filtered_records.append(record)
113     return filtered_records
114
115
116 # save methods
117 def save_raw_to_file(var, filename, format="text", banner=None):
118     if filename == "-":
119         # if filename is "-", send it to stdout
120         f = sys.stdout
121     else:
122         f = open(filename, "w")
123     if banner:
124         f.write(banner+"\n")
125     if format == "text":
126         f.write(str(var))
127     elif format == "pickled":
128         f.write(pickle.dumps(var))
129     elif format == "json":
130         if hasattr(json, "dumps"):
131             f.write(json.dumps(var))   # python 2.6
132         else:
133             f.write(json.write(var))   # python 2.5
134     else:
135         # this should never happen
136         print "unknown output format", format
137     if banner:
138         f.write('\n'+banner+"\n")
139
140 def save_rspec_to_file(rspec, filename):
141     if not filename.endswith(".rspec"):
142         filename = filename + ".rspec"
143     f = open(filename, 'w')
144     f.write(rspec)
145     f.close()
146     return
147
148 def save_records_to_file(filename, record_dicts, format="xml"):
149     if format == "xml":
150         index = 0
151         for record_dict in record_dicts:
152             if index > 0:
153                 save_record_to_file(filename + "." + str(index), record_dict)
154             else:
155                 save_record_to_file(filename, record_dict)
156             index = index + 1
157     elif format == "xmllist":
158         f = open(filename, "w")
159         f.write("<recordlist>\n")
160         for record_dict in record_dicts:
161             record_obj=Record(dict=record_dict)
162             f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
163         f.write("</recordlist>\n")
164         f.close()
165     elif format == "hrnlist":
166         f = open(filename, "w")
167         for record_dict in record_dicts:
168             record_obj=Record(dict=record_dict)
169             f.write(record_obj.hrn + "\n")
170         f.close()
171     else:
172         # this should never happen
173         print "unknown output format", format
174
175 def save_record_to_file(filename, record_dict):
176     record = Record(dict=record_dict)
177     xml = record.save_as_xml()
178     f=codecs.open(filename, encoding='utf-8',mode="w")
179     f.write(xml)
180     f.close()
181     return
182
183 # minimally check a key argument
184 def check_ssh_key (key):
185     good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
186     return re.match(good_ssh_key, key, re.IGNORECASE)
187
188 # load methods
189 def load_record_from_opts(options):
190     record_dict = {}
191     if hasattr(options, 'xrn') and options.xrn:
192         if hasattr(options, 'type') and options.type:
193             xrn = Xrn(options.xrn, options.type)
194         else:
195             xrn = Xrn(options.xrn)
196         record_dict['urn'] = xrn.get_urn()
197         record_dict['hrn'] = xrn.get_hrn()
198         record_dict['type'] = xrn.get_type()
199     if hasattr(options, 'key') and options.key:
200         try:
201             pubkey = open(options.key, 'r').read()
202         except IOError:
203             pubkey = options.key
204         if not check_ssh_key (pubkey):
205             raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format")
206         record_dict['keys'] = [pubkey]
207     if hasattr(options, 'slices') and options.slices:
208         record_dict['slices'] = options.slices
209     if hasattr(options, 'researchers') and options.researchers:
210         record_dict['researcher'] = options.researchers
211     if hasattr(options, 'email') and options.email:
212         record_dict['email'] = options.email
213     if hasattr(options, 'pis') and options.pis:
214         record_dict['pi'] = options.pis
215
216     # handle extra settings
217     record_dict.update(options.extras)
218     
219     return Record(dict=record_dict)
220
221 def load_record_from_file(filename):
222     f=codecs.open(filename, encoding="utf-8", mode="r")
223     xml_string = f.read()
224     f.close()
225     return Record(xml=xml_string)
226
227
228 import uuid
229 def unique_call_id(): return uuid.uuid4().urn
230
231 class Sfi:
232     
233     # dirty hack to make this class usable from the outside
234     required_options=['verbose',  'debug',  'registry',  'sm',  'auth',  'user', 'user_private_key']
235
236     @staticmethod
237     def default_sfi_dir ():
238         if os.path.isfile("./sfi_config"): 
239             return os.getcwd()
240         else:
241             return os.path.expanduser("~/.sfi/")
242
243     # dummy to meet Sfi's expectations for its 'options' field
244     # i.e. s/t we can do setattr on
245     class DummyOptions:
246         pass
247
248     def __init__ (self,options=None):
249         if options is None: options=Sfi.DummyOptions()
250         for opt in Sfi.required_options:
251             if not hasattr(options,opt): setattr(options,opt,None)
252         if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
253         self.options = options
254         self.user = None
255         self.authority = None
256         self.logger = sfi_logger
257         self.logger.enable_console()
258         self.available_names = [ tuple[0] for tuple in Sfi.available ]
259         self.available_dict = dict (Sfi.available)
260    
261     # tuples command-name expected-args in the order in which they should appear in the help
262     available = [ 
263         ("version", ""),  
264         ("list", "authority"),
265         ("show", "name"),
266         ("add", "record"),
267         ("update", "record"),
268         ("remove", "name"),
269         ("slices", ""),
270         ("resources", "[slice_hrn]"),
271         ("create", "slice_hrn rspec"),
272         ("delete", "slice_hrn"),
273         ("status", "slice_hrn"),
274         ("start", "slice_hrn"),
275         ("stop", "slice_hrn"),
276         ("reset", "slice_hrn"),
277         ("renew", "slice_hrn time"),
278         ("shutdown", "slice_hrn"),
279         ("get_ticket", "slice_hrn rspec"),
280         ("redeem_ticket", "ticket"),
281         ("delegate", "name"),
282         ("create_gid", "[name]"),
283         ("get_trusted_certs", "cred"),
284         ("config", ""),
285         ]
286
287     def print_command_help (self, options):
288         verbose=getattr(options,'verbose')
289         format3="%18s %-15s %s"
290         line=80*'-'
291         if not verbose:
292             print format3%("command","cmd_args","description")
293             print line
294         else:
295             print line
296             self.create_parser().print_help()
297         for command in self.available_names:
298             args=self.available_dict[command]
299             method=getattr(self,command,None)
300             doc=""
301             if method: doc=getattr(method,'__doc__',"")
302             if not doc: doc="*** no doc found ***"
303             doc=doc.strip(" \t\n")
304             doc=doc.replace("\n","\n"+35*' ')
305             if verbose:
306                 print line
307             print format3%(command,args,doc)
308             if verbose:
309                 self.create_command_parser(command).print_help()
310
311     def create_command_parser(self, command):
312         if command not in self.available_dict:
313             msg="Invalid command\n"
314             msg+="Commands: "
315             msg += ','.join(self.available_names)            
316             self.logger.critical(msg)
317             sys.exit(2)
318
319         parser = OptionParser(usage="sfi [sfi_options] %s [cmd_options] %s" \
320                                      % (command, self.available_dict[command]))
321
322         if command in ("add", "update"):
323             parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
324             parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
325             parser.add_option('-e', '--email', dest='email', default="",  help="email (mandatory for users)") 
326 # use --extra instead
327 #            parser.add_option('-u', '--url', dest='url', metavar='<url>', default=None, help="URL, useful for slices") 
328 #            parser.add_option('-d', '--description', dest='description', metavar='<description>', 
329 #                              help='Description, useful for slices', default=None)
330             parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file', 
331                               default=None)
332             parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='slice xrns',
333                               default='', type="str", action='callback', callback=optparse_listvalue_callback)
334             parser.add_option('-r', '--researchers', dest='researchers', metavar='<researchers>', 
335                               help='slice researchers', default='', type="str", action='callback', 
336                               callback=optparse_listvalue_callback)
337             parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Principal Investigators/Project Managers',
338                               default='', type="str", action='callback', callback=optparse_listvalue_callback)
339 # use --extra instead
340 #            parser.add_option('-f', '--firstname', dest='firstname', metavar='<firstname>', help='user first name')
341 #            parser.add_option('-l', '--lastname', dest='lastname', metavar='<lastname>', help='user last name')
342             parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
343                                action="callback", callback=optparse_dictvalue_callback, nargs=1,
344                                help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
345
346         # user specifies remote aggregate/sm/component                          
347         if command in ("resources", "slices", "create", "delete", "start", "stop", 
348                        "restart", "shutdown",  "get_ticket", "renew", "status"):
349             parser.add_option("-d", "--delegate", dest="delegate", default=None, 
350                              action="store_true",
351                              help="Include a credential delegated to the user's root"+\
352                                   "authority in set of credentials for this call")
353
354         # registy filter option
355         if command in ("list", "show", "remove"):
356             parser.add_option("-t", "--type", dest="type", type="choice",
357                             help="type filter ([all]|user|slice|authority|node|aggregate)",
358                             choices=("all", "user", "slice", "authority", "node", "aggregate"),
359                             default="all")
360         if command in ("resources"):
361             # rspec version
362             parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
363                               help="schema type and version of resulting RSpec")
364             # disable/enable cached rspecs
365             parser.add_option("-c", "--current", dest="current", default=False,
366                               action="store_true",  
367                               help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
368             # display formats
369             parser.add_option("-f", "--format", dest="format", type="choice",
370                              help="display format ([xml]|dns|ip)", default="xml",
371                              choices=("xml", "dns", "ip"))
372             #panos: a new option to define the type of information about resources a user is interested in
373             parser.add_option("-i", "--info", dest="info",
374                                 help="optional component information", default=None)
375
376
377         # 'create' does return the new rspec, makes sense to save that too
378         if command in ("resources", "show", "list", "create_gid", 'create'):
379            parser.add_option("-o", "--output", dest="file",
380                             help="output XML to file", metavar="FILE", default=None)
381
382         if command in ("show", "list"):
383            parser.add_option("-f", "--format", dest="format", type="choice",
384                              help="display format ([text]|xml)", default="text",
385                              choices=("text", "xml"))
386
387            parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
388                              help="output file format ([xml]|xmllist|hrnlist)", default="xml",
389                              choices=("xml", "xmllist", "hrnlist"))
390         if command == 'list':
391            parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
392                              help="list all child records", default=False)
393         if command in ("delegate"):
394            parser.add_option("-u", "--user",
395                             action="store_true", dest="delegate_user", default=False,
396                             help="delegate user credential")
397            parser.add_option("-s", "--slice", dest="delegate_slice",
398                             help="delegate slice credential", metavar="HRN", default=None)
399         
400         if command in ("version"):
401             parser.add_option("-R","--registry-version",
402                               action="store_true", dest="version_registry", default=False,
403                               help="probe registry version instead of sliceapi")
404             parser.add_option("-l","--local",
405                               action="store_true", dest="version_local", default=False,
406                               help="display version of the local client")
407
408         return parser
409
410         
411     def create_parser(self):
412
413         # Generate command line parser
414         parser = OptionParser(usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
415                              description="Commands: %s"%(" ".join(self.available_names)))
416         parser.add_option("-r", "--registry", dest="registry",
417                          help="root registry", metavar="URL", default=None)
418         parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
419                          help="slice API - in general a SM URL, but can be used to talk to an aggregate")
420         parser.add_option("-R", "--raw", dest="raw", default=None,
421                           help="Save raw, unparsed server response to a file")
422         parser.add_option("", "--rawformat", dest="rawformat", type="choice",
423                           help="raw file format ([text]|pickled|json)", default="text",
424                           choices=("text","pickled","json"))
425         parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
426                           help="text string to write before and after raw output")
427         parser.add_option("-d", "--dir", dest="sfi_dir",
428                          help="config & working directory - default is %default",
429                          metavar="PATH", default=Sfi.default_sfi_dir())
430         parser.add_option("-u", "--user", dest="user",
431                          help="user name", metavar="HRN", default=None)
432         parser.add_option("-a", "--auth", dest="auth",
433                          help="authority name", metavar="HRN", default=None)
434         parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
435                          help="verbose mode - cumulative")
436         parser.add_option("-D", "--debug",
437                           action="store_true", dest="debug", default=False,
438                           help="Debug (xml-rpc) protocol messages")
439         # would it make sense to use ~/.ssh/id_rsa as a default here ?
440         parser.add_option("-k", "--private-key",
441                          action="store", dest="user_private_key", default=None,
442                          help="point to the private key file to use if not yet installed in sfi_dir")
443         parser.add_option("-t", "--timeout", dest="timeout", default=None,
444                          help="Amout of time to wait before timing out the request")
445         parser.add_option("-?", "--commands", 
446                          action="store_true", dest="command_help", default=False,
447                          help="one page summary on commands & exit")
448         parser.disable_interspersed_args()
449
450         return parser
451         
452
453     def print_help (self):
454         self.sfi_parser.print_help()
455         self.command_parser.print_help()
456
457     #
458     # Main: parse arguments and dispatch to command
459     #
460     def dispatch(self, command, command_options, command_args):
461         return getattr(self, command)(command_options, command_args)
462
463     def main(self):
464         self.sfi_parser = self.create_parser()
465         (options, args) = self.sfi_parser.parse_args()
466         if options.command_help: 
467             self.print_command_help(options)
468             sys.exit(1)
469         self.options = options
470
471         self.logger.setLevelFromOptVerbose(self.options.verbose)
472
473         if len(args) <= 0:
474             self.logger.critical("No command given. Use -h for help.")
475             self.print_command_help(options)
476             return -1
477     
478         command = args[0]
479         self.command_parser = self.create_command_parser(command)
480         (command_options, command_args) = self.command_parser.parse_args(args[1:])
481         self.command_options = command_options
482
483         self.read_config () 
484         self.bootstrap ()
485         self.logger.info("Command=%s" % command)
486
487         try:
488             self.dispatch(command, command_options, command_args)
489         except KeyError:
490             self.logger.critical ("Unknown command %s"%command)
491             raise
492             sys.exit(1)
493     
494         return
495     
496     ####################
497     def read_config(self):
498         config_file = os.path.join(self.options.sfi_dir,"sfi_config")
499         try:
500            config = Config (config_file)
501         except:
502            self.logger.critical("Failed to read configuration file %s"%config_file)
503            self.logger.info("Make sure to remove the export clauses and to add quotes")
504            if self.options.verbose==0:
505                self.logger.info("Re-run with -v for more details")
506            else:
507                self.logger.log_exc("Could not read config file %s"%config_file)
508            sys.exit(1)
509      
510         errors = 0
511         # Set SliceMgr URL
512         if (self.options.sm is not None):
513            self.sm_url = self.options.sm
514         elif hasattr(config, "SFI_SM"):
515            self.sm_url = config.SFI_SM
516         else:
517            self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
518            errors += 1 
519
520         # Set Registry URL
521         if (self.options.registry is not None):
522            self.reg_url = self.options.registry
523         elif hasattr(config, "SFI_REGISTRY"):
524            self.reg_url = config.SFI_REGISTRY
525         else:
526            self.logger.errors("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
527            errors += 1 
528
529         # Set user HRN
530         if (self.options.user is not None):
531            self.user = self.options.user
532         elif hasattr(config, "SFI_USER"):
533            self.user = config.SFI_USER
534         else:
535            self.logger.errors("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
536            errors += 1 
537
538         # Set authority HRN
539         if (self.options.auth is not None):
540            self.authority = self.options.auth
541         elif hasattr(config, "SFI_AUTH"):
542            self.authority = config.SFI_AUTH
543         else:
544            self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
545            errors += 1 
546
547         self.config_file=config_file
548         if errors:
549            sys.exit(1)
550
551     def show_config (self):
552         print "From configuration file %s"%self.config_file
553         flags=[ 
554             ('SFI_USER','user'),
555             ('SFI_AUTH','authority'),
556             ('SFI_SM','sm_url'),
557             ('SFI_REGISTRY','reg_url'),
558             ]
559         for (external_name, internal_name) in flags:
560             print "%s='%s'"%(external_name,getattr(self,internal_name))
561
562     #
563     # Get various credential and spec files
564     #
565     # Establishes limiting conventions
566     #   - conflates MAs and SAs
567     #   - assumes last token in slice name is unique
568     #
569     # Bootstraps credentials
570     #   - bootstrap user credential from self-signed certificate
571     #   - bootstrap authority credential from user credential
572     #   - bootstrap slice credential from user credential
573     #
574     
575     # init self-signed cert, user credentials and gid
576     def bootstrap (self):
577         client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir)
578         # if -k is provided, use this to initialize private key
579         if self.options.user_private_key:
580             client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
581         else:
582             # trigger legacy compat code if needed 
583             # the name has changed from just <leaf>.pkey to <hrn>.pkey
584             if not os.path.isfile(client_bootstrap.private_key_filename()):
585                 self.logger.info ("private key not found, trying legacy name")
586                 try:
587                     legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%get_leaf(self.user))
588                     self.logger.debug("legacy_private_key=%s"%legacy_private_key)
589                     client_bootstrap.init_private_key_if_missing (legacy_private_key)
590                     self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
591                 except:
592                     self.logger.log_exc("Can't find private key ")
593                     sys.exit(1)
594             
595         # make it bootstrap
596         client_bootstrap.bootstrap_my_gid()
597         # extract what's needed
598         self.private_key = client_bootstrap.private_key()
599         self.my_credential_string = client_bootstrap.my_credential_string ()
600         self.my_gid = client_bootstrap.my_gid ()
601         self.client_bootstrap = client_bootstrap
602
603
604     def my_authority_credential_string(self):
605         if not self.authority:
606             self.logger.critical("no authority specified. Use -a or set SF_AUTH")
607             sys.exit(-1)
608         return self.client_bootstrap.authority_credential_string (self.authority)
609
610     def slice_credential_string(self, name):
611         return self.client_bootstrap.slice_credential_string (name)
612
613     # xxx should be supported by sfaclientbootstrap as well
614     def delegate_cred(self, object_cred, hrn, type='authority'):
615         # the gid and hrn of the object we are delegating
616         if isinstance(object_cred, str):
617             object_cred = Credential(string=object_cred) 
618         object_gid = object_cred.get_gid_object()
619         object_hrn = object_gid.get_hrn()
620     
621         if not object_cred.get_privileges().get_all_delegate():
622             self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
623             return
624
625         # the delegating user's gid
626         caller_gidfile = self.my_gid()
627   
628         # the gid of the user who will be delegated to
629         delegee_gid = self.client_bootstrap.gid(hrn,type)
630         delegee_hrn = delegee_gid.get_hrn()
631         dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
632         return dcred.save_to_string(save_parents=True)
633      
634     #
635     # Management of the servers
636     # 
637
638     def registry (self):
639         # cache the result
640         if not hasattr (self, 'registry_proxy'):
641             self.logger.info("Contacting Registry at: %s"%self.reg_url)
642             self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid, 
643                                                  timeout=self.options.timeout, verbose=self.options.debug)  
644         return self.registry_proxy
645
646     def sliceapi (self):
647         # cache the result
648         if not hasattr (self, 'sliceapi_proxy'):
649             # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
650             if hasattr(self.command_options,'component') and self.command_options.component:
651                 # resolve the hrn at the registry
652                 node_hrn = self.command_options.component
653                 records = self.registry().Resolve(node_hrn, self.my_credential_string)
654                 records = filter_records('node', records)
655                 if not records:
656                     self.logger.warning("No such component:%r"% opts.component)
657                 record = records[0]
658                 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
659                 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
660             else:
661                 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
662                 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
663                     self.sm_url = 'http://' + self.sm_url
664                 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
665                 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid, 
666                                                      timeout=self.options.timeout, verbose=self.options.debug)  
667         return self.sliceapi_proxy
668
669     def get_cached_server_version(self, server):
670         # check local cache first
671         cache = None
672         version = None 
673         cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
674         cache_key = server.url + "-version"
675         try:
676             cache = Cache(cache_file)
677         except IOError:
678             cache = Cache()
679             self.logger.info("Local cache not found at: %s" % cache_file)
680
681         if cache:
682             version = cache.get(cache_key)
683
684         if not version: 
685             result = server.GetVersion()
686             version= ReturnValue.get_value(result)
687             # cache version for 20 minutes
688             cache.add(cache_key, version, ttl= 60*20)
689             self.logger.info("Updating cache file %s" % cache_file)
690             cache.save_to_file(cache_file)
691
692         return version   
693         
694     ### resurrect this temporarily so we can support V1 aggregates for a while
695     def server_supports_options_arg(self, server):
696         """
697         Returns true if server support the optional call_id arg, false otherwise. 
698         """
699         server_version = self.get_cached_server_version(server)
700         result = False
701         # xxx need to rewrite this 
702         if int(server_version.get('geni_api')) >= 2:
703             result = True
704         return result
705
706     def server_supports_call_id_arg(self, server):
707         server_version = self.get_cached_server_version(server)
708         result = False      
709         if 'sfa' in server_version and 'code_tag' in server_version:
710             code_tag = server_version['code_tag']
711             code_tag_parts = code_tag.split("-")
712             version_parts = code_tag_parts[0].split(".")
713             major, minor = version_parts[0], version_parts[1]
714             rev = code_tag_parts[1]
715             if int(major) == 1 and minor == 0 and build >= 22:
716                 result = True
717         return result                 
718
719     ### ois = options if supported
720     # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
721     def ois (self, server, option_dict):
722         if self.server_supports_options_arg (server): 
723             return [option_dict]
724         elif self.server_supports_call_id_arg (server):
725             return [ unique_call_id () ]
726         else: 
727             return []
728
729     ### cis = call_id if supported - like ois
730     def cis (self, server):
731         if self.server_supports_call_id_arg (server):
732             return [ unique_call_id ]
733         else:
734             return []
735
736     ######################################## miscell utilities
737     def get_rspec_file(self, rspec):
738        if (os.path.isabs(rspec)):
739           file = rspec
740        else:
741           file = os.path.join(self.options.sfi_dir, rspec)
742        if (os.path.isfile(file)):
743           return file
744        else:
745           self.logger.critical("No such rspec file %s"%rspec)
746           sys.exit(1)
747     
748     def get_record_file(self, record):
749        if (os.path.isabs(record)):
750           file = record
751        else:
752           file = os.path.join(self.options.sfi_dir, record)
753        if (os.path.isfile(file)):
754           return file
755        else:
756           self.logger.critical("No such registry record file %s"%record)
757           sys.exit(1)
758
759
760     #==========================================================================
761     # Following functions implement the commands
762     #
763     # Registry-related commands
764     #==========================================================================
765
766     def version(self, options, args):
767         """
768         display an SFA server version (GetVersion)
769 or version information about sfi itself
770         """
771         if options.version_local:
772             version=version_core()
773         else:
774             if options.version_registry:
775                 server=self.registry()
776             else:
777                 server = self.sliceapi()
778             result = server.GetVersion()
779             version = ReturnValue.get_value(result)
780         if self.options.raw:
781             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
782         else:
783             pprinter = PrettyPrinter(indent=4)
784             pprinter.pprint(version)
785
786     def list(self, options, args):
787         """
788         list entries in named authority registry (List)
789         """
790         if len(args)!= 1:
791             self.print_help()
792             sys.exit(1)
793         hrn = args[0]
794         opts = {}
795         if options.recursive:
796             opts['recursive'] = options.recursive
797         
798         try:
799             list = self.registry().List(hrn, self.my_credential_string, options)
800         except IndexError:
801             raise Exception, "Not enough parameters for the 'list' command"
802
803         # filter on person, slice, site, node, etc.
804         # THis really should be in the self.filter_records funct def comment...
805         list = filter_records(options.type, list)
806         for record in list:
807             print "%s (%s)" % (record['hrn'], record['type'])
808         if options.file:
809             save_records_to_file(options.file, list, options.fileformat)
810         return
811     
812     def show(self, options, args):
813         """
814         show details about named registry record (Resolve)
815         """
816         if len(args)!= 1:
817             self.print_help()
818             sys.exit(1)
819         hrn = args[0]
820         record_dicts = self.registry().Resolve(hrn, self.my_credential_string)
821         record_dicts = filter_records(options.type, record_dicts)
822         if not record_dicts:
823             self.logger.error("No record of type %s"% options.type)
824         records = [ Record(dict=record_dict) for record_dict in record_dicts ]
825         for record in records:
826             if (options.format == "text"):      record.dump()  
827             else:                               print record.save_as_xml() 
828         if options.file:
829             save_records_to_file(options.file, record_dicts, options.fileformat)
830         return
831     
832     def add(self, options, args):
833         "add record into registry from xml file (Register)"
834         auth_cred = self.my_authority_credential_string()
835         record_dict = {}
836         if len(args) > 0:
837             record_filepath = args[0]
838             rec_file = self.get_record_file(record_filepath)
839             record_dict.update(load_record_from_file(rec_file).todict())
840         if options:
841             record_dict.update(load_record_from_opts(options).todict())
842         print 'bkpt',record_dict
843         sys.exit(0)
844         # we should have a type by now
845         if 'type' not in record_dict :
846             self.print_help()
847             sys.exit(1)
848         # this is still planetlab dependent.. as plc will whine without that
849         # also, it's only for adding
850         if record_dict['type'] == 'user':
851             if not 'first_name' in record_dict:
852                 record_dict['first_name'] = record_dict['hrn']
853             if 'last_name' not in record_dict:
854                 record_dict['last_name'] = record_dict['hrn'] 
855         return self.registry().Register(record_dict, auth_cred)
856     
857     def update(self, options, args):
858         "update record into registry from xml file (Update)"
859         record_dict = {}
860         if len(args) > 0:
861             record_filepath = args[0]
862             rec_file = self.get_record_file(record_filepath)
863             record_dict.update(load_record_from_file(rec_file).todict())
864         if options:
865             record_dict.update(load_record_from_opts(options).todict())
866         if not record_dict:
867             self.print_help()
868             sys.exit(1)
869
870         record = Record(dict=record_dict)        
871         if record.type == "user":
872             if record.hrn == self.user:
873                 cred = self.my_credential_string
874             else:
875                 cred = self.my_authority_credential_string()
876         elif record.type in ["slice"]:
877             try:
878                 cred = self.slice_credential_string(record.hrn)
879             except ServerException, e:
880                # XXX smbaker -- once we have better error return codes, update this
881                # to do something better than a string compare
882                if "Permission error" in e.args[0]:
883                    cred = self.my_authority_credential_string()
884                else:
885                    raise
886         elif record.type in ["authority"]:
887             cred = self.my_authority_credential_string()
888         elif record.type == 'node':
889             cred = self.my_authority_credential_string()
890         else:
891             raise "unknown record type" + record.type
892         record_dict = record.todict()
893         return self.registry().Update(record_dict, cred)
894   
895     def remove(self, options, args):
896         "remove registry record by name (Remove)"
897         auth_cred = self.my_authority_credential_string()
898         if len(args)!=1:
899             self.print_help()
900             sys.exit(1)
901         hrn = args[0]
902         type = options.type 
903         if type in ['all']:
904             type = '*'
905         return self.registry().Remove(hrn, auth_cred, type)
906     
907     # ==================================================================
908     # Slice-related commands
909     # ==================================================================
910
911     def slices(self, options, args):
912         "list instantiated slices (ListSlices) - returns urn's"
913         server = self.sliceapi()
914         # creds
915         creds = [self.my_credential_string]
916         if options.delegate:
917             delegated_cred = self.delegate_cred(self.my_credential_string, get_authority(self.authority))
918             creds.append(delegated_cred)  
919         # options and call_id when supported
920         api_options = {}
921         api_options['call_id']=unique_call_id()
922         result = server.ListSlices(creds, *self.ois(server,api_options))
923         value = ReturnValue.get_value(result)
924         if self.options.raw:
925             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
926         else:
927             display_list(value)
928         return
929
930     # show rspec for named slice
931     def resources(self, options, args):
932         """
933         with no arg, discover available resources, (ListResources)
934 or with an slice hrn, shows currently provisioned resources
935         """
936         server = self.sliceapi()
937
938         # set creds
939         creds = []
940         if args:
941             creds.append(self.slice_credential_string(args[0]))
942         else:
943             creds.append(self.my_credential_string)
944         if options.delegate:
945             creds.append(self.delegate_cred(cred, get_authority(self.authority)))
946
947         # no need to check if server accepts the options argument since the options has
948         # been a required argument since v1 API
949         api_options = {}
950         # always send call_id to v2 servers
951         api_options ['call_id'] = unique_call_id()
952         # ask for cached value if available
953         api_options ['cached'] = True
954         if args:
955             hrn = args[0]
956             api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
957         if options.info:
958             api_options['info'] = options.info
959         if options.current:
960             if options.current == True:
961                 api_options['cached'] = False
962             else:
963                 api_options['cached'] = True
964         if options.rspec_version:
965             version_manager = VersionManager()
966             server_version = self.get_cached_server_version(server)
967             if 'sfa' in server_version:
968                 # just request the version the client wants
969                 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
970             else:
971                 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
972         else:
973             api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
974         result = server.ListResources (creds, api_options)
975         value = ReturnValue.get_value(result)
976         if self.options.raw:
977             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
978         if options.file is not None:
979             save_rspec_to_file(value, options.file)
980         if (self.options.raw is None) and (options.file is None):
981             display_rspec(value, options.format)
982
983         return
984
985     def create(self, options, args):
986         """
987         create or update named slice with given rspec
988         """
989         server = self.sliceapi()
990
991         # xxx do we need to check usage (len(args)) ?
992         # slice urn
993         slice_hrn = args[0]
994         slice_urn = hrn_to_urn(slice_hrn, 'slice')
995
996         # credentials
997         creds = [self.slice_credential_string(slice_hrn)]
998         delegated_cred = None
999         server_version = self.get_cached_server_version(server)
1000         if server_version.get('interface') == 'slicemgr':
1001             # delegate our cred to the slice manager
1002             # do not delegate cred to slicemgr...not working at the moment
1003             pass
1004             #if server_version.get('hrn'):
1005             #    delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1006             #elif server_version.get('urn'):
1007             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1008
1009         # rspec
1010         rspec_file = self.get_rspec_file(args[1])
1011         rspec = open(rspec_file).read()
1012
1013         # users
1014         # need to pass along user keys to the aggregate.
1015         # users = [
1016         #  { urn: urn:publicid:IDN+emulab.net+user+alice
1017         #    keys: [<ssh key A>, <ssh key B>]
1018         #  }]
1019         users = []
1020         slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1021         if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
1022             slice_record = slice_records[0]
1023             user_hrns = slice_record['researcher']
1024             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1025             user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1026
1027             if 'sfa' not in server_version:
1028                 users = pg_users_arg(user_records)
1029                 rspec = RSpec(rspec)
1030                 rspec.filter({'component_manager_id': server_version['urn']})
1031                 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1032             else:
1033                 users = sfa_users_arg(user_records, slice_record)
1034
1035         # do not append users, keys, or slice tags. Anything
1036         # not contained in this request will be removed from the slice
1037
1038         # CreateSliver has supported the options argument for a while now so it should
1039         # be safe to assume this server support it
1040         api_options = {}
1041         api_options ['append'] = False
1042         api_options ['call_id'] = unique_call_id()
1043         result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, 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             print value
1051
1052         return value
1053
1054     def delete(self, options, args):
1055         """
1056         delete named slice (DeleteSliver)
1057         """
1058         server = self.sliceapi()
1059
1060         # slice urn
1061         slice_hrn = args[0]
1062         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1063
1064         # creds
1065         slice_cred = self.slice_credential_string(slice_hrn)
1066         creds = [slice_cred]
1067         if options.delegate:
1068             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1069             creds.append(delegated_cred)
1070         
1071         # options and call_id when supported
1072         api_options = {}
1073         api_options ['call_id'] = unique_call_id()
1074         result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
1075         value = ReturnValue.get_value(result)
1076         if self.options.raw:
1077             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1078         else:
1079             print value
1080         return value 
1081   
1082     def status(self, options, args):
1083         """
1084         retrieve slice status (SliverStatus)
1085         """
1086         server = self.sliceapi()
1087
1088         # slice urn
1089         slice_hrn = args[0]
1090         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1091
1092         # creds 
1093         slice_cred = self.slice_credential_string(slice_hrn)
1094         creds = [slice_cred]
1095         if options.delegate:
1096             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1097             creds.append(delegated_cred)
1098
1099         # options and call_id when supported
1100         api_options = {}
1101         api_options['call_id']=unique_call_id()
1102         result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
1103         value = ReturnValue.get_value(result)
1104         if self.options.raw:
1105             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1106         else:
1107             print value
1108
1109     def start(self, options, args):
1110         """
1111         start named slice (Start)
1112         """
1113         server = self.sliceapi()
1114
1115         # the slice urn
1116         slice_hrn = args[0]
1117         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1118         
1119         # cred
1120         slice_cred = self.slice_credential_string(args[0])
1121         creds = [slice_cred]
1122         if options.delegate:
1123             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1124             creds.append(delegated_cred)
1125         # xxx Thierry - does this not need an api_options as well ?
1126         result = server.Start(slice_urn, creds)
1127         value = ReturnValue.get_value(result)
1128         if self.options.raw:
1129             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1130         else:
1131             print value
1132         return value
1133     
1134     def stop(self, options, args):
1135         """
1136         stop named slice (Stop)
1137         """
1138         server = self.sliceapi()
1139         # slice urn
1140         slice_hrn = args[0]
1141         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1142         # cred
1143         slice_cred = self.slice_credential_string(args[0])
1144         creds = [slice_cred]
1145         if options.delegate:
1146             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1147             creds.append(delegated_cred)
1148         result =  server.Stop(slice_urn, creds)
1149         value = ReturnValue.get_value(result)
1150         if self.options.raw:
1151             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1152         else:
1153             print value
1154         return value
1155     
1156     # reset named slice
1157     def reset(self, options, args):
1158         """
1159         reset named slice (reset_slice)
1160         """
1161         server = self.sliceapi()
1162         # slice urn
1163         slice_hrn = args[0]
1164         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1165         # cred
1166         slice_cred = self.slice_credential_string(args[0])
1167         creds = [slice_cred]
1168         if options.delegate:
1169             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1170             creds.append(delegated_cred)
1171         result = server.reset_slice(creds, slice_urn)
1172         value = ReturnValue.get_value(result)
1173         if self.options.raw:
1174             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1175         else:
1176             print value
1177         return value
1178
1179     def renew(self, options, args):
1180         """
1181         renew slice (RenewSliver)
1182         """
1183         server = self.sliceapi()
1184         # slice urn    
1185         slice_hrn = args[0]
1186         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1187         # creds
1188         slice_cred = self.slice_credential_string(args[0])
1189         creds = [slice_cred]
1190         if options.delegate:
1191             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1192             creds.append(delegated_cred)
1193         # time
1194         time = args[1]
1195         # options and call_id when supported
1196         api_options = {}
1197         api_options['call_id']=unique_call_id()
1198         result =  server.RenewSliver(slice_urn, creds, time, *self.ois(server,api_options))
1199         value = ReturnValue.get_value(result)
1200         if self.options.raw:
1201             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1202         else:
1203             print value
1204         return value
1205
1206
1207     def shutdown(self, options, args):
1208         """
1209         shutdown named slice (Shutdown)
1210         """
1211         server = self.sliceapi()
1212         # slice urn
1213         slice_hrn = args[0]
1214         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1215         # creds
1216         slice_cred = self.slice_credential_string(slice_hrn)
1217         creds = [slice_cred]
1218         if options.delegate:
1219             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1220             creds.append(delegated_cred)
1221         result = server.Shutdown(slice_urn, creds)
1222         value = ReturnValue.get_value(result)
1223         if self.options.raw:
1224             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1225         else:
1226             print value
1227         return value         
1228     
1229
1230     def get_ticket(self, options, args):
1231         """
1232         get a ticket for the specified slice
1233         """
1234         server = self.sliceapi()
1235         # slice urn
1236         slice_hrn, rspec_path = args[0], args[1]
1237         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1238         # creds
1239         slice_cred = self.slice_credential_string(slice_hrn)
1240         creds = [slice_cred]
1241         if options.delegate:
1242             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1243             creds.append(delegated_cred)
1244         # rspec
1245         rspec_file = self.get_rspec_file(rspec_path) 
1246         rspec = open(rspec_file).read()
1247         # options and call_id when supported
1248         api_options = {}
1249         api_options['call_id']=unique_call_id()
1250         # get ticket at the server
1251         ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1252         # save
1253         file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1254         self.logger.info("writing ticket to %s"%file)
1255         ticket = SfaTicket(string=ticket_string)
1256         ticket.save_to_file(filename=file, save_parents=True)
1257
1258     def redeem_ticket(self, options, args):
1259         """
1260         Connects to nodes in a slice and redeems a ticket
1261 (slice hrn is retrieved from the ticket)
1262         """
1263         ticket_file = args[0]
1264         
1265         # get slice hrn from the ticket
1266         # use this to get the right slice credential 
1267         ticket = SfaTicket(filename=ticket_file)
1268         ticket.decode()
1269         ticket_string = ticket.save_to_string(save_parents=True)
1270
1271         slice_hrn = ticket.gidObject.get_hrn()
1272         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1273         #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1274         slice_cred = self.slice_credential_string(slice_hrn)
1275         
1276         # get a list of node hostnames from the RSpec 
1277         tree = etree.parse(StringIO(ticket.rspec))
1278         root = tree.getroot()
1279         hostnames = root.xpath("./network/site/node/hostname/text()")
1280         
1281         # create an xmlrpc connection to the component manager at each of these
1282         # components and gall redeem_ticket
1283         connections = {}
1284         for hostname in hostnames:
1285             try:
1286                 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1287                 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1288                 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1289                 server = self.server_proxy(hostname, CM_PORT, self.private_key, 
1290                                            timeout=self.options.timeout, verbose=self.options.debug)
1291                 server.RedeemTicket(ticket_string, slice_cred)
1292                 self.logger.info("Success")
1293             except socket.gaierror:
1294                 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1295             except Exception, e:
1296                 self.logger.log_exc(e.message)
1297         return
1298
1299     def create_gid(self, options, args):
1300         """
1301         Create a GID (CreateGid)
1302         """
1303         if len(args) < 1:
1304             self.print_help()
1305             sys.exit(1)
1306         target_hrn = args[0]
1307         gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.client_bootstrap.my_gid_string())
1308         if options.file:
1309             filename = options.file
1310         else:
1311             filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1312         self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1313         GID(string=gid).save_to_file(filename)
1314          
1315
1316     def delegate(self, options, args):
1317         """
1318         (locally) create delegate credential for use by given hrn
1319         """
1320         delegee_hrn = args[0]
1321         if options.delegate_user:
1322             cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1323         elif options.delegate_slice:
1324             slice_cred = self.slice_credential_string(options.delegate_slice)
1325             cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1326         else:
1327             self.logger.warning("Must specify either --user or --slice <hrn>")
1328             return
1329         delegated_cred = Credential(string=cred)
1330         object_hrn = delegated_cred.get_gid_object().get_hrn()
1331         if options.delegate_user:
1332             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1333                                   + get_leaf(object_hrn) + ".cred")
1334         elif options.delegate_slice:
1335             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1336                                   + get_leaf(object_hrn) + ".cred")
1337
1338         delegated_cred.save_to_file(dest_fn, save_parents=True)
1339
1340         self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1341     
1342     def get_trusted_certs(self, options, args):
1343         """
1344         return uhe trusted certs at this interface (get_trusted_certs)
1345         """ 
1346         trusted_certs = self.registry().get_trusted_certs()
1347         for trusted_cert in trusted_certs:
1348             gid = GID(string=trusted_cert)
1349             gid.dump()
1350             cert = Certificate(string=trusted_cert)
1351             self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())
1352         return 
1353
1354     def config (self, options, args):
1355         "Display contents of current config"
1356         self.show_config()