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