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