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