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