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