Merge branch 'master' into senslab2
[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                 print >>sys.stderr, "\r\n \r\n \r\n WOOOOOO"
1038                 users = sfa_users_arg(user_records, slice_record)
1039
1040         # do not append users, keys, or slice tags. Anything
1041         # not contained in this request will be removed from the slice
1042
1043         # CreateSliver has supported the options argument for a while now so it should
1044         # be safe to assume this server support it
1045         api_options = {}
1046         api_options ['append'] = False
1047         api_options ['call_id'] = unique_call_id()
1048         result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
1049         value = ReturnValue.get_value(result)
1050         if self.options.raw:
1051             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1052         if options.file is not None:
1053             save_rspec_to_file (value, options.file)
1054         if (self.options.raw is None) and (options.file is None):
1055             print value
1056
1057         return value
1058
1059     def delete(self, options, args):
1060         """
1061         delete named slice (DeleteSliver)
1062         """
1063         server = self.sliceapi()
1064
1065         # slice urn
1066         slice_hrn = args[0]
1067         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1068
1069         # creds
1070         slice_cred = self.slice_credential_string(slice_hrn)
1071         creds = [slice_cred]
1072         if options.delegate:
1073             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1074             creds.append(delegated_cred)
1075         
1076         # options and call_id when supported
1077         api_options = {}
1078         api_options ['call_id'] = unique_call_id()
1079         result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
1080         value = ReturnValue.get_value(result)
1081         if self.options.raw:
1082             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1083         else:
1084             print value
1085         return value 
1086   
1087     def status(self, options, args):
1088         """
1089         retrieve slice status (SliverStatus)
1090         """
1091         server = self.sliceapi()
1092
1093         # slice urn
1094         slice_hrn = args[0]
1095         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1096
1097         # creds 
1098         slice_cred = self.slice_credential_string(slice_hrn)
1099         creds = [slice_cred]
1100         if options.delegate:
1101             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1102             creds.append(delegated_cred)
1103
1104         # options and call_id when supported
1105         api_options = {}
1106         api_options['call_id']=unique_call_id()
1107         result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
1108         value = ReturnValue.get_value(result)
1109         if self.options.raw:
1110             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1111         else:
1112             print value
1113
1114     def start(self, options, args):
1115         """
1116         start named slice (Start)
1117         """
1118         server = self.sliceapi()
1119
1120         # the slice urn
1121         slice_hrn = args[0]
1122         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1123         
1124         # cred
1125         slice_cred = self.slice_credential_string(args[0])
1126         creds = [slice_cred]
1127         if options.delegate:
1128             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1129             creds.append(delegated_cred)
1130         # xxx Thierry - does this not need an api_options as well ?
1131         result = server.Start(slice_urn, creds)
1132         value = ReturnValue.get_value(result)
1133         if self.options.raw:
1134             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1135         else:
1136             print value
1137         return value
1138     
1139     def stop(self, options, args):
1140         """
1141         stop named slice (Stop)
1142         """
1143         server = self.sliceapi()
1144         # slice urn
1145         slice_hrn = args[0]
1146         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1147         # cred
1148         slice_cred = self.slice_credential_string(args[0])
1149         creds = [slice_cred]
1150         if options.delegate:
1151             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1152             creds.append(delegated_cred)
1153         result =  server.Stop(slice_urn, creds)
1154         value = ReturnValue.get_value(result)
1155         if self.options.raw:
1156             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1157         else:
1158             print value
1159         return value
1160     
1161     # reset named slice
1162     def reset(self, options, args):
1163         """
1164         reset named slice (reset_slice)
1165         """
1166         server = self.sliceapi()
1167         # slice urn
1168         slice_hrn = args[0]
1169         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1170         # cred
1171         slice_cred = self.slice_credential_string(args[0])
1172         creds = [slice_cred]
1173         if options.delegate:
1174             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1175             creds.append(delegated_cred)
1176         result = server.reset_slice(creds, slice_urn)
1177         value = ReturnValue.get_value(result)
1178         if self.options.raw:
1179             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1180         else:
1181             print value
1182         return value
1183
1184     def renew(self, options, args):
1185         """
1186         renew slice (RenewSliver)
1187         """
1188         server = self.sliceapi()
1189         # slice urn    
1190         slice_hrn = args[0]
1191         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1192         # creds
1193         slice_cred = self.slice_credential_string(args[0])
1194         creds = [slice_cred]
1195         if options.delegate:
1196             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1197             creds.append(delegated_cred)
1198         # time
1199         time = args[1]
1200         # options and call_id when supported
1201         api_options = {}
1202         api_options['call_id']=unique_call_id()
1203         result =  server.RenewSliver(slice_urn, creds, time, *self.ois(server,api_options))
1204         value = ReturnValue.get_value(result)
1205         if self.options.raw:
1206             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1207         else:
1208             print value
1209         return value
1210
1211
1212     def shutdown(self, options, args):
1213         """
1214         shutdown named slice (Shutdown)
1215         """
1216         server = self.sliceapi()
1217         # slice urn
1218         slice_hrn = args[0]
1219         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1220         # creds
1221         slice_cred = self.slice_credential_string(slice_hrn)
1222         creds = [slice_cred]
1223         if options.delegate:
1224             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1225             creds.append(delegated_cred)
1226         result = server.Shutdown(slice_urn, creds)
1227         value = ReturnValue.get_value(result)
1228         if self.options.raw:
1229             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1230         else:
1231             print value
1232         return value         
1233     
1234
1235     def get_ticket(self, options, args):
1236         """
1237         get a ticket for the specified slice
1238         """
1239         server = self.sliceapi()
1240         # slice urn
1241         slice_hrn, rspec_path = args[0], args[1]
1242         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1243         # creds
1244         slice_cred = self.slice_credential_string(slice_hrn)
1245         creds = [slice_cred]
1246         if options.delegate:
1247             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1248             creds.append(delegated_cred)
1249         # rspec
1250         rspec_file = self.get_rspec_file(rspec_path) 
1251         rspec = open(rspec_file).read()
1252         # options and call_id when supported
1253         api_options = {}
1254         api_options['call_id']=unique_call_id()
1255         # get ticket at the server
1256         ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1257         # save
1258         file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1259         self.logger.info("writing ticket to %s"%file)
1260         ticket = SfaTicket(string=ticket_string)
1261         ticket.save_to_file(filename=file, save_parents=True)
1262
1263     def redeem_ticket(self, options, args):
1264         """
1265         Connects to nodes in a slice and redeems a ticket
1266 (slice hrn is retrieved from the ticket)
1267         """
1268         ticket_file = args[0]
1269         
1270         # get slice hrn from the ticket
1271         # use this to get the right slice credential 
1272         ticket = SfaTicket(filename=ticket_file)
1273         ticket.decode()
1274         ticket_string = ticket.save_to_string(save_parents=True)
1275
1276         slice_hrn = ticket.gidObject.get_hrn()
1277         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1278         #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1279         slice_cred = self.slice_credential_string(slice_hrn)
1280         
1281         # get a list of node hostnames from the RSpec 
1282         tree = etree.parse(StringIO(ticket.rspec))
1283         root = tree.getroot()
1284         hostnames = root.xpath("./network/site/node/hostname/text()")
1285         
1286         # create an xmlrpc connection to the component manager at each of these
1287         # components and gall redeem_ticket
1288         connections = {}
1289         for hostname in hostnames:
1290             try:
1291                 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1292                 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1293                 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1294                 server = self.server_proxy(hostname, CM_PORT, self.private_key, 
1295                                            timeout=self.options.timeout, verbose=self.options.debug)
1296                 server.RedeemTicket(ticket_string, slice_cred)
1297                 self.logger.info("Success")
1298             except socket.gaierror:
1299                 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1300             except Exception, e:
1301                 self.logger.log_exc(e.message)
1302         return
1303
1304     def create_gid(self, options, args):
1305         """
1306         Create a GID (CreateGid)
1307         """
1308         if len(args) < 1:
1309             self.print_help()
1310             sys.exit(1)
1311         target_hrn = args[0]
1312         gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.client_bootstrap.my_gid_string())
1313         if options.file:
1314             filename = options.file
1315         else:
1316             filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1317         self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1318         GID(string=gid).save_to_file(filename)
1319          
1320
1321     def delegate(self, options, args):
1322         """
1323         (locally) create delegate credential for use by given hrn
1324         """
1325         delegee_hrn = args[0]
1326         if options.delegate_user:
1327             cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1328         elif options.delegate_slice:
1329             slice_cred = self.slice_credential_string(options.delegate_slice)
1330             cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1331         else:
1332             self.logger.warning("Must specify either --user or --slice <hrn>")
1333             return
1334         delegated_cred = Credential(string=cred)
1335         object_hrn = delegated_cred.get_gid_object().get_hrn()
1336         if options.delegate_user:
1337             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1338                                   + get_leaf(object_hrn) + ".cred")
1339         elif options.delegate_slice:
1340             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1341                                   + get_leaf(object_hrn) + ".cred")
1342
1343         delegated_cred.save_to_file(dest_fn, save_parents=True)
1344
1345         self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1346     
1347     def get_trusted_certs(self, options, args):
1348         """
1349         return uhe trusted certs at this interface (get_trusted_certs)
1350         """ 
1351         trusted_certs = self.registry().get_trusted_certs()
1352         for trusted_cert in trusted_certs:
1353             gid = GID(string=trusted_cert)
1354             gid.dump()
1355             cert = Certificate(string=trusted_cert)
1356             self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())
1357         return 
1358
1359     def config (self, options, args):
1360         "Display contents of current config"
1361         self.show_config()