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