whoops !!
[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         # we should have a type by now
843         if 'type' not in record_dict :
844             self.print_help()
845             sys.exit(1)
846         # this is still planetlab dependent.. as plc will whine without that
847         # also, it's only for adding
848         if record_dict['type'] == 'user':
849             if not 'first_name' in record_dict:
850                 record_dict['first_name'] = record_dict['hrn']
851             if 'last_name' not in record_dict:
852                 record_dict['last_name'] = record_dict['hrn'] 
853         return self.registry().Register(record_dict, auth_cred)
854     
855     def update(self, options, args):
856         "update record into registry from xml file (Update)"
857         record_dict = {}
858         if len(args) > 0:
859             record_filepath = args[0]
860             rec_file = self.get_record_file(record_filepath)
861             record_dict.update(load_record_from_file(rec_file).todict())
862         if options:
863             record_dict.update(load_record_from_opts(options).todict())
864         if not record_dict:
865             self.print_help()
866             sys.exit(1)
867
868         record = Record(dict=record_dict)        
869         if record.type == "user":
870             if record.hrn == self.user:
871                 cred = self.my_credential_string
872             else:
873                 cred = self.my_authority_credential_string()
874         elif record.type in ["slice"]:
875             try:
876                 cred = self.slice_credential_string(record.hrn)
877             except ServerException, e:
878                # XXX smbaker -- once we have better error return codes, update this
879                # to do something better than a string compare
880                if "Permission error" in e.args[0]:
881                    cred = self.my_authority_credential_string()
882                else:
883                    raise
884         elif record.type in ["authority"]:
885             cred = self.my_authority_credential_string()
886         elif record.type == 'node':
887             cred = self.my_authority_credential_string()
888         else:
889             raise "unknown record type" + record.type
890         record_dict = record.todict()
891         return self.registry().Update(record_dict, cred)
892   
893     def remove(self, options, args):
894         "remove registry record by name (Remove)"
895         auth_cred = self.my_authority_credential_string()
896         if len(args)!=1:
897             self.print_help()
898             sys.exit(1)
899         hrn = args[0]
900         type = options.type 
901         if type in ['all']:
902             type = '*'
903         return self.registry().Remove(hrn, auth_cred, type)
904     
905     # ==================================================================
906     # Slice-related commands
907     # ==================================================================
908
909     def slices(self, options, args):
910         "list instantiated slices (ListSlices) - returns urn's"
911         server = self.sliceapi()
912         # creds
913         creds = [self.my_credential_string]
914         if options.delegate:
915             delegated_cred = self.delegate_cred(self.my_credential_string, get_authority(self.authority))
916             creds.append(delegated_cred)  
917         # options and call_id when supported
918         api_options = {}
919         api_options['call_id']=unique_call_id()
920         result = server.ListSlices(creds, *self.ois(server,api_options))
921         value = ReturnValue.get_value(result)
922         if self.options.raw:
923             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
924         else:
925             display_list(value)
926         return
927
928     # show rspec for named slice
929     def resources(self, options, args):
930         """
931         with no arg, discover available resources, (ListResources)
932 or with an slice hrn, shows currently provisioned resources
933         """
934         server = self.sliceapi()
935
936         # set creds
937         creds = []
938         if args:
939             creds.append(self.slice_credential_string(args[0]))
940         else:
941             creds.append(self.my_credential_string)
942         if options.delegate:
943             creds.append(self.delegate_cred(cred, get_authority(self.authority)))
944
945         # no need to check if server accepts the options argument since the options has
946         # been a required argument since v1 API
947         api_options = {}
948         # always send call_id to v2 servers
949         api_options ['call_id'] = unique_call_id()
950         # ask for cached value if available
951         api_options ['cached'] = True
952         if args:
953             hrn = args[0]
954             api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
955         if options.info:
956             api_options['info'] = options.info
957         if options.current:
958             if options.current == True:
959                 api_options['cached'] = False
960             else:
961                 api_options['cached'] = True
962         if options.rspec_version:
963             version_manager = VersionManager()
964             server_version = self.get_cached_server_version(server)
965             if 'sfa' in server_version:
966                 # just request the version the client wants
967                 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
968             else:
969                 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
970         else:
971             api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
972         result = server.ListResources (creds, api_options)
973         value = ReturnValue.get_value(result)
974         if self.options.raw:
975             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
976         if options.file is not None:
977             save_rspec_to_file(value, options.file)
978         if (self.options.raw is None) and (options.file is None):
979             display_rspec(value, options.format)
980
981         return
982
983     def create(self, options, args):
984         """
985         create or update named slice with given rspec
986         """
987         server = self.sliceapi()
988
989         # xxx do we need to check usage (len(args)) ?
990         # slice urn
991         slice_hrn = args[0]
992         slice_urn = hrn_to_urn(slice_hrn, 'slice')
993
994         # credentials
995         creds = [self.slice_credential_string(slice_hrn)]
996         delegated_cred = None
997         server_version = self.get_cached_server_version(server)
998         if server_version.get('interface') == 'slicemgr':
999             # delegate our cred to the slice manager
1000             # do not delegate cred to slicemgr...not working at the moment
1001             pass
1002             #if server_version.get('hrn'):
1003             #    delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1004             #elif server_version.get('urn'):
1005             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1006
1007         # rspec
1008         rspec_file = self.get_rspec_file(args[1])
1009         rspec = open(rspec_file).read()
1010
1011         # users
1012         # need to pass along user keys to the aggregate.
1013         # users = [
1014         #  { urn: urn:publicid:IDN+emulab.net+user+alice
1015         #    keys: [<ssh key A>, <ssh key B>]
1016         #  }]
1017         users = []
1018         slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1019         if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
1020             slice_record = slice_records[0]
1021             user_hrns = slice_record['researcher']
1022             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1023             user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1024
1025             if 'sfa' not in server_version:
1026                 users = pg_users_arg(user_records)
1027                 rspec = RSpec(rspec)
1028                 rspec.filter({'component_manager_id': server_version['urn']})
1029                 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1030             else:
1031                 users = sfa_users_arg(user_records, slice_record)
1032
1033         # do not append users, keys, or slice tags. Anything
1034         # not contained in this request will be removed from the slice
1035
1036         # CreateSliver has supported the options argument for a while now so it should
1037         # be safe to assume this server support it
1038         api_options = {}
1039         api_options ['append'] = False
1040         api_options ['call_id'] = unique_call_id()
1041         result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
1042         value = ReturnValue.get_value(result)
1043         if self.options.raw:
1044             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1045         if options.file is not None:
1046             save_rspec_to_file (value, options.file)
1047         if (self.options.raw is None) and (options.file is None):
1048             print value
1049
1050         return value
1051
1052     def delete(self, options, args):
1053         """
1054         delete named slice (DeleteSliver)
1055         """
1056         server = self.sliceapi()
1057
1058         # slice urn
1059         slice_hrn = args[0]
1060         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1061
1062         # creds
1063         slice_cred = self.slice_credential_string(slice_hrn)
1064         creds = [slice_cred]
1065         if options.delegate:
1066             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1067             creds.append(delegated_cred)
1068         
1069         # options and call_id when supported
1070         api_options = {}
1071         api_options ['call_id'] = unique_call_id()
1072         result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
1073         value = ReturnValue.get_value(result)
1074         if self.options.raw:
1075             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1076         else:
1077             print value
1078         return value 
1079   
1080     def status(self, options, args):
1081         """
1082         retrieve slice status (SliverStatus)
1083         """
1084         server = self.sliceapi()
1085
1086         # slice urn
1087         slice_hrn = args[0]
1088         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1089
1090         # creds 
1091         slice_cred = self.slice_credential_string(slice_hrn)
1092         creds = [slice_cred]
1093         if options.delegate:
1094             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1095             creds.append(delegated_cred)
1096
1097         # options and call_id when supported
1098         api_options = {}
1099         api_options['call_id']=unique_call_id()
1100         result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
1101         value = ReturnValue.get_value(result)
1102         if self.options.raw:
1103             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1104         else:
1105             print value
1106
1107     def start(self, options, args):
1108         """
1109         start named slice (Start)
1110         """
1111         server = self.sliceapi()
1112
1113         # the slice urn
1114         slice_hrn = args[0]
1115         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1116         
1117         # cred
1118         slice_cred = self.slice_credential_string(args[0])
1119         creds = [slice_cred]
1120         if options.delegate:
1121             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1122             creds.append(delegated_cred)
1123         # xxx Thierry - does this not need an api_options as well ?
1124         result = server.Start(slice_urn, creds)
1125         value = ReturnValue.get_value(result)
1126         if self.options.raw:
1127             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1128         else:
1129             print value
1130         return value
1131     
1132     def stop(self, options, args):
1133         """
1134         stop named slice (Stop)
1135         """
1136         server = self.sliceapi()
1137         # slice urn
1138         slice_hrn = args[0]
1139         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1140         # cred
1141         slice_cred = self.slice_credential_string(args[0])
1142         creds = [slice_cred]
1143         if options.delegate:
1144             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1145             creds.append(delegated_cred)
1146         result =  server.Stop(slice_urn, creds)
1147         value = ReturnValue.get_value(result)
1148         if self.options.raw:
1149             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1150         else:
1151             print value
1152         return value
1153     
1154     # reset named slice
1155     def reset(self, options, args):
1156         """
1157         reset named slice (reset_slice)
1158         """
1159         server = self.sliceapi()
1160         # slice urn
1161         slice_hrn = args[0]
1162         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1163         # cred
1164         slice_cred = self.slice_credential_string(args[0])
1165         creds = [slice_cred]
1166         if options.delegate:
1167             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1168             creds.append(delegated_cred)
1169         result = server.reset_slice(creds, slice_urn)
1170         value = ReturnValue.get_value(result)
1171         if self.options.raw:
1172             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1173         else:
1174             print value
1175         return value
1176
1177     def renew(self, options, args):
1178         """
1179         renew slice (RenewSliver)
1180         """
1181         server = self.sliceapi()
1182         # slice urn    
1183         slice_hrn = args[0]
1184         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1185         # creds
1186         slice_cred = self.slice_credential_string(args[0])
1187         creds = [slice_cred]
1188         if options.delegate:
1189             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1190             creds.append(delegated_cred)
1191         # time
1192         time = args[1]
1193         # options and call_id when supported
1194         api_options = {}
1195         api_options['call_id']=unique_call_id()
1196         result =  server.RenewSliver(slice_urn, creds, time, *self.ois(server,api_options))
1197         value = ReturnValue.get_value(result)
1198         if self.options.raw:
1199             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1200         else:
1201             print value
1202         return value
1203
1204
1205     def shutdown(self, options, args):
1206         """
1207         shutdown named slice (Shutdown)
1208         """
1209         server = self.sliceapi()
1210         # slice urn
1211         slice_hrn = args[0]
1212         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1213         # creds
1214         slice_cred = self.slice_credential_string(slice_hrn)
1215         creds = [slice_cred]
1216         if options.delegate:
1217             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1218             creds.append(delegated_cred)
1219         result = server.Shutdown(slice_urn, creds)
1220         value = ReturnValue.get_value(result)
1221         if self.options.raw:
1222             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1223         else:
1224             print value
1225         return value         
1226     
1227
1228     def get_ticket(self, options, args):
1229         """
1230         get a ticket for the specified slice
1231         """
1232         server = self.sliceapi()
1233         # slice urn
1234         slice_hrn, rspec_path = args[0], args[1]
1235         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1236         # creds
1237         slice_cred = self.slice_credential_string(slice_hrn)
1238         creds = [slice_cred]
1239         if options.delegate:
1240             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1241             creds.append(delegated_cred)
1242         # rspec
1243         rspec_file = self.get_rspec_file(rspec_path) 
1244         rspec = open(rspec_file).read()
1245         # options and call_id when supported
1246         api_options = {}
1247         api_options['call_id']=unique_call_id()
1248         # get ticket at the server
1249         ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1250         # save
1251         file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1252         self.logger.info("writing ticket to %s"%file)
1253         ticket = SfaTicket(string=ticket_string)
1254         ticket.save_to_file(filename=file, save_parents=True)
1255
1256     def redeem_ticket(self, options, args):
1257         """
1258         Connects to nodes in a slice and redeems a ticket
1259 (slice hrn is retrieved from the ticket)
1260         """
1261         ticket_file = args[0]
1262         
1263         # get slice hrn from the ticket
1264         # use this to get the right slice credential 
1265         ticket = SfaTicket(filename=ticket_file)
1266         ticket.decode()
1267         ticket_string = ticket.save_to_string(save_parents=True)
1268
1269         slice_hrn = ticket.gidObject.get_hrn()
1270         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1271         #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1272         slice_cred = self.slice_credential_string(slice_hrn)
1273         
1274         # get a list of node hostnames from the RSpec 
1275         tree = etree.parse(StringIO(ticket.rspec))
1276         root = tree.getroot()
1277         hostnames = root.xpath("./network/site/node/hostname/text()")
1278         
1279         # create an xmlrpc connection to the component manager at each of these
1280         # components and gall redeem_ticket
1281         connections = {}
1282         for hostname in hostnames:
1283             try:
1284                 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1285                 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1286                 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1287                 server = self.server_proxy(hostname, CM_PORT, self.private_key, 
1288                                            timeout=self.options.timeout, verbose=self.options.debug)
1289                 server.RedeemTicket(ticket_string, slice_cred)
1290                 self.logger.info("Success")
1291             except socket.gaierror:
1292                 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1293             except Exception, e:
1294                 self.logger.log_exc(e.message)
1295         return
1296
1297     def create_gid(self, options, args):
1298         """
1299         Create a GID (CreateGid)
1300         """
1301         if len(args) < 1:
1302             self.print_help()
1303             sys.exit(1)
1304         target_hrn = args[0]
1305         gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.client_bootstrap.my_gid_string())
1306         if options.file:
1307             filename = options.file
1308         else:
1309             filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1310         self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1311         GID(string=gid).save_to_file(filename)
1312          
1313
1314     def delegate(self, options, args):
1315         """
1316         (locally) create delegate credential for use by given hrn
1317         """
1318         delegee_hrn = args[0]
1319         if options.delegate_user:
1320             cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1321         elif options.delegate_slice:
1322             slice_cred = self.slice_credential_string(options.delegate_slice)
1323             cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1324         else:
1325             self.logger.warning("Must specify either --user or --slice <hrn>")
1326             return
1327         delegated_cred = Credential(string=cred)
1328         object_hrn = delegated_cred.get_gid_object().get_hrn()
1329         if options.delegate_user:
1330             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1331                                   + get_leaf(object_hrn) + ".cred")
1332         elif options.delegate_slice:
1333             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1334                                   + get_leaf(object_hrn) + ".cred")
1335
1336         delegated_cred.save_to_file(dest_fn, save_parents=True)
1337
1338         self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1339     
1340     def get_trusted_certs(self, options, args):
1341         """
1342         return uhe trusted certs at this interface (get_trusted_certs)
1343         """ 
1344         trusted_certs = self.registry().get_trusted_certs()
1345         for trusted_cert in trusted_certs:
1346             gid = GID(string=trusted_cert)
1347             gid.dump()
1348             cert = Certificate(string=trusted_cert)
1349             self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())
1350         return 
1351
1352     def config (self, options, args):
1353         "Display contents of current config"
1354         self.show_config()