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