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