fix read_config()
[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.add_section('sface')
540             tmp_config.load(config_file)
541             tmp_config.save(config_file)
542         except:
543             raise
544         finally:
545             os.unlink(fn)
546         
547         
548     ####################
549     def read_config(self):
550         config_file = os.path.join(self.options.sfi_dir,"sfi_config")
551         try:
552             if Config.is_ini(config_file):
553                 config = Config (config_file)
554             else:
555                 # try upgrading from shell config format
556                 self.upgrade_config(config_file)
557                 config = Config(config_file)
558                  
559         except:
560             self.logger.critical("Failed to read configuration file %s"%config_file)
561             self.logger.info("Make sure to remove the export clauses and to add quotes")
562             if self.options.verbose==0:
563                 self.logger.info("Re-run with -v for more details")
564             else:
565                 self.logger.log_exc("Could not read config file %s"%config_file)
566             sys.exit(1)
567      
568         errors = 0
569         # Set SliceMgr URL
570         if (self.options.sm is not None):
571            self.sm_url = self.options.sm
572         elif hasattr(config, "SFI_SM"):
573            self.sm_url = config.SFI_SM
574         else:
575            self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
576            errors += 1 
577
578         # Set Registry URL
579         if (self.options.registry is not None):
580            self.reg_url = self.options.registry
581         elif hasattr(config, "SFI_REGISTRY"):
582            self.reg_url = config.SFI_REGISTRY
583         else:
584            self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
585            errors += 1 
586
587         # Set user HRN
588         if (self.options.user is not None):
589            self.user = self.options.user
590         elif hasattr(config, "SFI_USER"):
591            self.user = config.SFI_USER
592         else:
593            self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
594            errors += 1 
595
596         # Set authority HRN
597         if (self.options.auth is not None):
598            self.authority = self.options.auth
599         elif hasattr(config, "SFI_AUTH"):
600            self.authority = config.SFI_AUTH
601         else:
602            self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
603            errors += 1 
604
605         self.config_file=config_file
606         if errors:
607            sys.exit(1)
608
609     def show_config (self):
610         print "From configuration file %s"%self.config_file
611         flags=[ 
612             ('SFI_USER','user'),
613             ('SFI_AUTH','authority'),
614             ('SFI_SM','sm_url'),
615             ('SFI_REGISTRY','reg_url'),
616             ]
617         for (external_name, internal_name) in flags:
618             print "%s='%s'"%(external_name,getattr(self,internal_name))
619
620     #
621     # Get various credential and spec files
622     #
623     # Establishes limiting conventions
624     #   - conflates MAs and SAs
625     #   - assumes last token in slice name is unique
626     #
627     # Bootstraps credentials
628     #   - bootstrap user credential from self-signed certificate
629     #   - bootstrap authority credential from user credential
630     #   - bootstrap slice credential from user credential
631     #
632     
633     # init self-signed cert, user credentials and gid
634     def bootstrap (self):
635         client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
636                                                logger=self.logger)
637         # if -k is provided, use this to initialize private key
638         if self.options.user_private_key:
639             client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
640         else:
641             # trigger legacy compat code if needed 
642             # the name has changed from just <leaf>.pkey to <hrn>.pkey
643             if not os.path.isfile(client_bootstrap.private_key_filename()):
644                 self.logger.info ("private key not found, trying legacy name")
645                 try:
646                     legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%get_leaf(self.user))
647                     self.logger.debug("legacy_private_key=%s"%legacy_private_key)
648                     client_bootstrap.init_private_key_if_missing (legacy_private_key)
649                     self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
650                 except:
651                     self.logger.log_exc("Can't find private key ")
652                     sys.exit(1)
653             
654         # make it bootstrap
655         client_bootstrap.bootstrap_my_gid()
656         # extract what's needed
657         self.private_key = client_bootstrap.private_key()
658         self.my_credential_string = client_bootstrap.my_credential_string ()
659         self.my_gid = client_bootstrap.my_gid ()
660         self.client_bootstrap = client_bootstrap
661
662
663     def my_authority_credential_string(self):
664         if not self.authority:
665             self.logger.critical("no authority specified. Use -a or set SF_AUTH")
666             sys.exit(-1)
667         return self.client_bootstrap.authority_credential_string (self.authority)
668
669     def slice_credential_string(self, name):
670         return self.client_bootstrap.slice_credential_string (name)
671
672     # xxx should be supported by sfaclientbootstrap as well
673     def delegate_cred(self, object_cred, hrn, type='authority'):
674         # the gid and hrn of the object we are delegating
675         if isinstance(object_cred, str):
676             object_cred = Credential(string=object_cred) 
677         object_gid = object_cred.get_gid_object()
678         object_hrn = object_gid.get_hrn()
679     
680         if not object_cred.get_privileges().get_all_delegate():
681             self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
682             return
683
684         # the delegating user's gid
685         caller_gidfile = self.my_gid()
686   
687         # the gid of the user who will be delegated to
688         delegee_gid = self.client_bootstrap.gid(hrn,type)
689         delegee_hrn = delegee_gid.get_hrn()
690         dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
691         return dcred.save_to_string(save_parents=True)
692      
693     #
694     # Management of the servers
695     # 
696
697     def registry (self):
698         # cache the result
699         if not hasattr (self, 'registry_proxy'):
700             self.logger.info("Contacting Registry at: %s"%self.reg_url)
701             self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid, 
702                                                  timeout=self.options.timeout, verbose=self.options.debug)  
703         return self.registry_proxy
704
705     def sliceapi (self):
706         # cache the result
707         if not hasattr (self, 'sliceapi_proxy'):
708             # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
709             if hasattr(self.command_options,'component') and self.command_options.component:
710                 # resolve the hrn at the registry
711                 node_hrn = self.command_options.component
712                 records = self.registry().Resolve(node_hrn, self.my_credential_string)
713                 records = filter_records('node', records)
714                 if not records:
715                     self.logger.warning("No such component:%r"% opts.component)
716                 record = records[0]
717                 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
718                 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
719             else:
720                 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
721                 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
722                     self.sm_url = 'http://' + self.sm_url
723                 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
724                 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid, 
725                                                      timeout=self.options.timeout, verbose=self.options.debug)  
726         return self.sliceapi_proxy
727
728     def get_cached_server_version(self, server):
729         # check local cache first
730         cache = None
731         version = None 
732         cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
733         cache_key = server.url + "-version"
734         try:
735             cache = Cache(cache_file)
736         except IOError:
737             cache = Cache()
738             self.logger.info("Local cache not found at: %s" % cache_file)
739
740         if cache:
741             version = cache.get(cache_key)
742
743         if not version: 
744             result = server.GetVersion()
745             version= ReturnValue.get_value(result)
746             # cache version for 20 minutes
747             cache.add(cache_key, version, ttl= 60*20)
748             self.logger.info("Updating cache file %s" % cache_file)
749             cache.save_to_file(cache_file)
750
751         return version   
752         
753     ### resurrect this temporarily so we can support V1 aggregates for a while
754     def server_supports_options_arg(self, server):
755         """
756         Returns true if server support the optional call_id arg, false otherwise. 
757         """
758         server_version = self.get_cached_server_version(server)
759         result = False
760         # xxx need to rewrite this 
761         if int(server_version.get('geni_api')) >= 2:
762             result = True
763         return result
764
765     def server_supports_call_id_arg(self, server):
766         server_version = self.get_cached_server_version(server)
767         result = False      
768         if 'sfa' in server_version and 'code_tag' in server_version:
769             code_tag = server_version['code_tag']
770             code_tag_parts = code_tag.split("-")
771             version_parts = code_tag_parts[0].split(".")
772             major, minor = version_parts[0], version_parts[1]
773             rev = code_tag_parts[1]
774             if int(major) == 1 and minor == 0 and build >= 22:
775                 result = True
776         return result                 
777
778     ### ois = options if supported
779     # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
780     def ois (self, server, option_dict):
781         if self.server_supports_options_arg (server): 
782             return [option_dict]
783         elif self.server_supports_call_id_arg (server):
784             return [ unique_call_id () ]
785         else: 
786             return []
787
788     ### cis = call_id if supported - like ois
789     def cis (self, server):
790         if self.server_supports_call_id_arg (server):
791             return [ unique_call_id ]
792         else:
793             return []
794
795     ######################################## miscell utilities
796     def get_rspec_file(self, rspec):
797        if (os.path.isabs(rspec)):
798           file = rspec
799        else:
800           file = os.path.join(self.options.sfi_dir, rspec)
801        if (os.path.isfile(file)):
802           return file
803        else:
804           self.logger.critical("No such rspec file %s"%rspec)
805           sys.exit(1)
806     
807     def get_record_file(self, record):
808        if (os.path.isabs(record)):
809           file = record
810        else:
811           file = os.path.join(self.options.sfi_dir, record)
812        if (os.path.isfile(file)):
813           return file
814        else:
815           self.logger.critical("No such registry record file %s"%record)
816           sys.exit(1)
817
818
819     #==========================================================================
820     # Following functions implement the commands
821     #
822     # Registry-related commands
823     #==========================================================================
824
825     def version(self, options, args):
826         """
827         display an SFA server version (GetVersion)
828 or version information about sfi itself
829         """
830         if options.version_local:
831             version=version_core()
832         else:
833             if options.version_registry:
834                 server=self.registry()
835             else:
836                 server = self.sliceapi()
837             result = server.GetVersion()
838             version = ReturnValue.get_value(result)
839         if self.options.raw:
840             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
841         else:
842             pprinter = PrettyPrinter(indent=4)
843             pprinter.pprint(version)
844
845     def list(self, options, args):
846         """
847         list entries in named authority registry (List)
848         """
849         if len(args)!= 1:
850             self.print_help()
851             sys.exit(1)
852         hrn = args[0]
853         opts = {}
854         if options.recursive:
855             opts['recursive'] = options.recursive
856         
857         if options.show_credential:
858             show_credentials(self.my_credential_string)
859         try:
860             list = self.registry().List(hrn, self.my_credential_string, options)
861         except IndexError:
862             raise Exception, "Not enough parameters for the 'list' command"
863
864         # filter on person, slice, site, node, etc.
865         # THis really should be in the self.filter_records funct def comment...
866         list = filter_records(options.type, list)
867         for record in list:
868             print "%s (%s)" % (record['hrn'], record['type'])
869         if options.file:
870             save_records_to_file(options.file, list, options.fileformat)
871         return
872     
873     def show(self, options, args):
874         """
875         show details about named registry record (Resolve)
876         """
877         if len(args)!= 1:
878             self.print_help()
879             sys.exit(1)
880         hrn = args[0]
881         record_dicts = self.registry().Resolve(hrn, self.my_credential_string)
882         record_dicts = filter_records(options.type, record_dicts)
883         if not record_dicts:
884             self.logger.error("No record of type %s"% options.type)
885             return
886         # user has required to focus on some keys
887         if options.keys:
888             def project (record):
889                 projected={}
890                 for key in options.keys:
891                     try: projected[key]=record[key]
892                     except: pass
893                 return projected
894             record_dicts = [ project (record) for record in record_dicts ]
895         records = [ Record(dict=record_dict) for record_dict in record_dicts ]
896         for record in records:
897             if (options.format == "text"):      record.dump(sort=True)  
898             else:                               print record.save_as_xml() 
899         if options.file:
900             save_records_to_file(options.file, record_dicts, options.fileformat)
901         return
902     
903     def add(self, options, args):
904         "add record into registry from xml file (Register)"
905         auth_cred = self.my_authority_credential_string()
906         if options.show_credential:
907             show_credentials(auth_cred)
908         record_dict = {}
909         if len(args) > 0:
910             record_filepath = args[0]
911             rec_file = self.get_record_file(record_filepath)
912             record_dict.update(load_record_from_file(rec_file).todict())
913         if options:
914             record_dict.update(load_record_from_opts(options).todict())
915         # we should have a type by now
916         if 'type' not in record_dict :
917             self.print_help()
918             sys.exit(1)
919         # this is still planetlab dependent.. as plc will whine without that
920         # also, it's only for adding
921         if record_dict['type'] == 'user':
922             if not 'first_name' in record_dict:
923                 record_dict['first_name'] = record_dict['hrn']
924             if 'last_name' not in record_dict:
925                 record_dict['last_name'] = record_dict['hrn'] 
926         return self.registry().Register(record_dict, auth_cred)
927     
928     def update(self, options, args):
929         "update record into registry from xml file (Update)"
930         record_dict = {}
931         if len(args) > 0:
932             record_filepath = args[0]
933             rec_file = self.get_record_file(record_filepath)
934             record_dict.update(load_record_from_file(rec_file).todict())
935         if options:
936             record_dict.update(load_record_from_opts(options).todict())
937         # at the very least we need 'type' here
938         if 'type' not in record_dict:
939             self.print_help()
940             sys.exit(1)
941
942         # don't translate into an object, as this would possibly distort
943         # user-provided data; e.g. add an 'email' field to Users
944         if record_dict['type'] == "user":
945             if record_dict['hrn'] == self.user:
946                 cred = self.my_credential_string
947             else:
948                 cred = self.my_authority_credential_string()
949         elif record_dict['type'] in ["slice"]:
950             try:
951                 cred = self.slice_credential_string(record_dict['hrn'])
952             except ServerException, e:
953                # XXX smbaker -- once we have better error return codes, update this
954                # to do something better than a string compare
955                if "Permission error" in e.args[0]:
956                    cred = self.my_authority_credential_string()
957                else:
958                    raise
959         elif record_dict['type'] in ["authority"]:
960             cred = self.my_authority_credential_string()
961         elif record_dict['type'] == 'node':
962             cred = self.my_authority_credential_string()
963         else:
964             raise "unknown record type" + record_dict['type']
965         if options.show_credential:
966             show_credentials(cred)
967         return self.registry().Update(record_dict, cred)
968   
969     def remove(self, options, args):
970         "remove registry record by name (Remove)"
971         auth_cred = self.my_authority_credential_string()
972         if len(args)!=1:
973             self.print_help()
974             sys.exit(1)
975         hrn = args[0]
976         type = options.type 
977         if type in ['all']:
978             type = '*'
979         if options.show_credential:
980             show_credentials(auth_cred)
981         return self.registry().Remove(hrn, auth_cred, type)
982     
983     # ==================================================================
984     # Slice-related commands
985     # ==================================================================
986
987     def slices(self, options, args):
988         "list instantiated slices (ListSlices) - returns urn's"
989         server = self.sliceapi()
990         # creds
991         creds = [self.my_credential_string]
992         if options.delegate:
993             delegated_cred = self.delegate_cred(self.my_credential_string, get_authority(self.authority))
994             creds.append(delegated_cred)  
995         # options and call_id when supported
996         api_options = {}
997         api_options['call_id']=unique_call_id()
998         if options.show_credential:
999             show_credentials(creds)
1000         result = server.ListSlices(creds, *self.ois(server,api_options))
1001         value = ReturnValue.get_value(result)
1002         if self.options.raw:
1003             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1004         else:
1005             display_list(value)
1006         return
1007
1008     # show rspec for named slice
1009     def resources(self, options, args):
1010         """
1011         with no arg, discover available resources, (ListResources)
1012 or with an slice hrn, shows currently provisioned resources
1013         """
1014         server = self.sliceapi()
1015
1016         # set creds
1017         creds = []
1018         if args:
1019             creds.append(self.slice_credential_string(args[0]))
1020         else:
1021             creds.append(self.my_credential_string)
1022         if options.delegate:
1023             creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1024         if options.show_credential:
1025             show_credentials(creds)
1026
1027         # no need to check if server accepts the options argument since the options has
1028         # been a required argument since v1 API
1029         api_options = {}
1030         # always send call_id to v2 servers
1031         api_options ['call_id'] = unique_call_id()
1032         # ask for cached value if available
1033         api_options ['cached'] = True
1034         if args:
1035             hrn = args[0]
1036             api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
1037         if options.info:
1038             api_options['info'] = options.info
1039         if options.list_leases:
1040             api_options['list_leases'] = options.list_leases
1041         if options.current:
1042             if options.current == True:
1043                 api_options['cached'] = False
1044             else:
1045                 api_options['cached'] = True
1046         if options.rspec_version:
1047             version_manager = VersionManager()
1048             server_version = self.get_cached_server_version(server)
1049             if 'sfa' in server_version:
1050                 # just request the version the client wants
1051                 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1052             else:
1053                 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1054         else:
1055             api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1056         result = server.ListResources (creds, api_options)
1057         value = ReturnValue.get_value(result)
1058         if self.options.raw:
1059             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1060         if options.file is not None:
1061             save_rspec_to_file(value, options.file)
1062         if (self.options.raw is None) and (options.file is None):
1063             display_rspec(value, options.format)
1064
1065         return
1066
1067     def create(self, options, args):
1068         """
1069         create or update named slice with given rspec
1070         """
1071         server = self.sliceapi()
1072
1073         # xxx do we need to check usage (len(args)) ?
1074         # slice urn
1075         slice_hrn = args[0]
1076         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1077
1078         # credentials
1079         creds = [self.slice_credential_string(slice_hrn)]
1080
1081         delegated_cred = None
1082         server_version = self.get_cached_server_version(server)
1083         if server_version.get('interface') == 'slicemgr':
1084             # delegate our cred to the slice manager
1085             # do not delegate cred to slicemgr...not working at the moment
1086             pass
1087             #if server_version.get('hrn'):
1088             #    delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1089             #elif server_version.get('urn'):
1090             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1091
1092         if options.show_credential:
1093             show_credentials(creds)
1094
1095         # rspec
1096         rspec_file = self.get_rspec_file(args[1])
1097         rspec = open(rspec_file).read()
1098
1099         # users
1100         # need to pass along user keys to the aggregate.
1101         # users = [
1102         #  { urn: urn:publicid:IDN+emulab.net+user+alice
1103         #    keys: [<ssh key A>, <ssh key B>]
1104         #  }]
1105         users = []
1106         slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1107         if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
1108             slice_record = slice_records[0]
1109             user_hrns = slice_record['researcher']
1110             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1111             user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1112
1113             if 'sfa' not in server_version:
1114                 users = pg_users_arg(user_records)
1115                 rspec = RSpec(rspec)
1116                 rspec.filter({'component_manager_id': server_version['urn']})
1117                 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1118             else:
1119                 users = sfa_users_arg(user_records, slice_record)
1120
1121         # do not append users, keys, or slice tags. Anything
1122         # not contained in this request will be removed from the slice
1123
1124         # CreateSliver has supported the options argument for a while now so it should
1125         # be safe to assume this server support it
1126         api_options = {}
1127         api_options ['append'] = False
1128         api_options ['call_id'] = unique_call_id()
1129         result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
1130         value = ReturnValue.get_value(result)
1131         if self.options.raw:
1132             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1133         if options.file is not None:
1134             save_rspec_to_file (value, options.file)
1135         if (self.options.raw is None) and (options.file is None):
1136             print value
1137
1138         return value
1139
1140     def delete(self, options, args):
1141         """
1142         delete named slice (DeleteSliver)
1143         """
1144         server = self.sliceapi()
1145
1146         # slice urn
1147         slice_hrn = args[0]
1148         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1149
1150         # creds
1151         slice_cred = self.slice_credential_string(slice_hrn)
1152         creds = [slice_cred]
1153         if options.delegate:
1154             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1155             creds.append(delegated_cred)
1156         
1157         # options and call_id when supported
1158         api_options = {}
1159         api_options ['call_id'] = unique_call_id()
1160         if options.show_credential:
1161             show_credentials(creds)
1162         result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
1163         value = ReturnValue.get_value(result)
1164         if self.options.raw:
1165             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1166         else:
1167             print value
1168         return value 
1169   
1170     def status(self, options, args):
1171         """
1172         retrieve slice status (SliverStatus)
1173         """
1174         server = self.sliceapi()
1175
1176         # slice urn
1177         slice_hrn = args[0]
1178         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1179
1180         # creds 
1181         slice_cred = self.slice_credential_string(slice_hrn)
1182         creds = [slice_cred]
1183         if options.delegate:
1184             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1185             creds.append(delegated_cred)
1186
1187         # options and call_id when supported
1188         api_options = {}
1189         api_options['call_id']=unique_call_id()
1190         if options.show_credential:
1191             show_credentials(creds)
1192         result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
1193         value = ReturnValue.get_value(result)
1194         if self.options.raw:
1195             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1196         else:
1197             print value
1198
1199     def start(self, options, args):
1200         """
1201         start named slice (Start)
1202         """
1203         server = self.sliceapi()
1204
1205         # the slice urn
1206         slice_hrn = args[0]
1207         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1208         
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         # xxx Thierry - does this not need an api_options as well ?
1216         result = server.Start(slice_urn, creds)
1217         value = ReturnValue.get_value(result)
1218         if self.options.raw:
1219             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1220         else:
1221             print value
1222         return value
1223     
1224     def stop(self, options, args):
1225         """
1226         stop named slice (Stop)
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.Stop(slice_urn, creds)
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     # reset named slice
1247     def reset(self, options, args):
1248         """
1249         reset named slice (reset_slice)
1250         """
1251         server = self.sliceapi()
1252         # slice urn
1253         slice_hrn = args[0]
1254         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1255         # cred
1256         slice_cred = self.slice_credential_string(args[0])
1257         creds = [slice_cred]
1258         if options.delegate:
1259             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1260             creds.append(delegated_cred)
1261         result = server.reset_slice(creds, slice_urn)
1262         value = ReturnValue.get_value(result)
1263         if self.options.raw:
1264             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1265         else:
1266             print value
1267         return value
1268
1269     def renew(self, options, args):
1270         """
1271         renew slice (RenewSliver)
1272         """
1273         server = self.sliceapi()
1274         if len(args) != 2:
1275             self.print_help()
1276             sys.exit(1)
1277         [ slice_hrn, input_time ] = args
1278         # slice urn    
1279         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1280         # time: don't try to be smart on the time format, server-side will
1281         # creds
1282         slice_cred = self.slice_credential_string(args[0])
1283         creds = [slice_cred]
1284         if options.delegate:
1285             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1286             creds.append(delegated_cred)
1287         # options and call_id when supported
1288         api_options = {}
1289         api_options['call_id']=unique_call_id()
1290         if options.show_credential:
1291             show_credentials(creds)
1292         result =  server.RenewSliver(slice_urn, creds, input_time, *self.ois(server,api_options))
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 shutdown(self, options, args):
1302         """
1303         shutdown named slice (Shutdown)
1304         """
1305         server = self.sliceapi()
1306         # slice urn
1307         slice_hrn = args[0]
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         result = server.Shutdown(slice_urn, creds)
1316         value = ReturnValue.get_value(result)
1317         if self.options.raw:
1318             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1319         else:
1320             print value
1321         return value         
1322     
1323
1324     def get_ticket(self, options, args):
1325         """
1326         get a ticket for the specified slice
1327         """
1328         server = self.sliceapi()
1329         # slice urn
1330         slice_hrn, rspec_path = args[0], args[1]
1331         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1332         # creds
1333         slice_cred = self.slice_credential_string(slice_hrn)
1334         creds = [slice_cred]
1335         if options.delegate:
1336             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1337             creds.append(delegated_cred)
1338         # rspec
1339         rspec_file = self.get_rspec_file(rspec_path) 
1340         rspec = open(rspec_file).read()
1341         # options and call_id when supported
1342         api_options = {}
1343         api_options['call_id']=unique_call_id()
1344         # get ticket at the server
1345         ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1346         # save
1347         file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1348         self.logger.info("writing ticket to %s"%file)
1349         ticket = SfaTicket(string=ticket_string)
1350         ticket.save_to_file(filename=file, save_parents=True)
1351
1352     def redeem_ticket(self, options, args):
1353         """
1354         Connects to nodes in a slice and redeems a ticket
1355 (slice hrn is retrieved from the ticket)
1356         """
1357         ticket_file = args[0]
1358         
1359         # get slice hrn from the ticket
1360         # use this to get the right slice credential 
1361         ticket = SfaTicket(filename=ticket_file)
1362         ticket.decode()
1363         ticket_string = ticket.save_to_string(save_parents=True)
1364
1365         slice_hrn = ticket.gidObject.get_hrn()
1366         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1367         #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1368         slice_cred = self.slice_credential_string(slice_hrn)
1369         
1370         # get a list of node hostnames from the RSpec 
1371         tree = etree.parse(StringIO(ticket.rspec))
1372         root = tree.getroot()
1373         hostnames = root.xpath("./network/site/node/hostname/text()")
1374         
1375         # create an xmlrpc connection to the component manager at each of these
1376         # components and gall redeem_ticket
1377         connections = {}
1378         for hostname in hostnames:
1379             try:
1380                 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1381                 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1382                 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1383                 server = self.server_proxy(hostname, CM_PORT, self.private_key, 
1384                                            timeout=self.options.timeout, verbose=self.options.debug)
1385                 server.RedeemTicket(ticket_string, slice_cred)
1386                 self.logger.info("Success")
1387             except socket.gaierror:
1388                 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1389             except Exception, e:
1390                 self.logger.log_exc(e.message)
1391         return
1392
1393     def gid(self, options, args):
1394         """
1395         Create a GID (CreateGid)
1396         """
1397         if len(args) < 1:
1398             self.print_help()
1399             sys.exit(1)
1400         target_hrn = args[0]
1401         gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.client_bootstrap.my_gid_string())
1402         if options.file:
1403             filename = options.file
1404         else:
1405             filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1406         self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1407         GID(string=gid).save_to_file(filename)
1408          
1409
1410     def delegate(self, options, args):
1411         """
1412         (locally) create delegate credential for use by given hrn
1413         """
1414         delegee_hrn = args[0]
1415         if options.delegate_user:
1416             cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1417         elif options.delegate_slice:
1418             slice_cred = self.slice_credential_string(options.delegate_slice)
1419             cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1420         else:
1421             self.logger.warning("Must specify either --user or --slice <hrn>")
1422             return
1423         delegated_cred = Credential(string=cred)
1424         object_hrn = delegated_cred.get_gid_object().get_hrn()
1425         if options.delegate_user:
1426             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1427                                   + get_leaf(object_hrn) + ".cred")
1428         elif options.delegate_slice:
1429             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1430                                   + get_leaf(object_hrn) + ".cred")
1431
1432         delegated_cred.save_to_file(dest_fn, save_parents=True)
1433
1434         self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1435     
1436     def trusted(self, options, args):
1437         """
1438         return uhe trusted certs at this interface (get_trusted_certs)
1439         """ 
1440         trusted_certs = self.registry().get_trusted_certs()
1441         for trusted_cert in trusted_certs:
1442             gid = GID(string=trusted_cert)
1443             gid.dump()
1444             cert = Certificate(string=trusted_cert)
1445             self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1446         return 
1447
1448     def config (self, options, args):
1449         "Display contents of current config"
1450         self.show_config()