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