fix Allocate
[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         ("resources", ""),
287         ("describe", "slice_hrn"),
288         ("create", "slice_hrn rspec"),
289         ("allocate", "slice_hrn rspec"),
290         ("provision", "slice_hrn"),
291         ("action", "slice_hrn action"), 
292         ("delete", "slice_hrn"),
293         ("status", "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", "describe", "allocate", "provision", "create", "delete", "allocate", "provision", 
365                        "action", "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", "describe", "provision", "allocate", "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", "describe"):
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", "describe", "allocate", "provision", "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         discover available resources
1006 or with an slice hrn, shows currently provisioned resources
1007         """
1008         server = self.sliceapi()
1009
1010         # set creds
1011         creds = [self.my_credential_string]
1012         if options.delegate:
1013             creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1014         if options.show_credential:
1015             show_credentials(creds)
1016
1017         # no need to check if server accepts the options argument since the options has
1018         # been a required argument since v1 API
1019         api_options = {}
1020         # always send call_id to v2 servers
1021         api_options ['call_id'] = unique_call_id()
1022         # ask for cached value if available
1023         api_options ['cached'] = True
1024         if options.info:
1025             api_options['info'] = options.info
1026         if options.list_leases:
1027             api_options['list_leases'] = options.list_leases
1028         if options.current:
1029             if options.current == True:
1030                 api_options['cached'] = False
1031             else:
1032                 api_options['cached'] = True
1033         if options.rspec_version:
1034             version_manager = VersionManager()
1035             server_version = self.get_cached_server_version(server)
1036             if 'sfa' in server_version:
1037                 # just request the version the client wants
1038                 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1039             else:
1040                 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1041         else:
1042             api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1043         result = server.ListResources (creds, api_options)
1044         value = ReturnValue.get_value(result)
1045         if self.options.raw:
1046             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1047         if options.file is not None:
1048             save_rspec_to_file(value, options.file)
1049         if (self.options.raw is None) and (options.file is None):
1050             display_rspec(value, options.format)
1051
1052         return
1053
1054     def describe(self, options, args):
1055         """
1056         Shows currently provisioned resources.
1057         """
1058         server = self.sliceapi()
1059
1060         # set creds
1061         creds = [self.slice_credential_string(args[0])]
1062         if options.delegate:
1063             creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1064         if options.show_credential:
1065             show_credentials(creds)
1066
1067         api_options = {'call_id': unique_call_id(),
1068                        'cached': True,
1069                        'info': options.info,
1070                        'list_leases': options.list_leases,
1071                        'geni_rspec_version': {'type': 'geni', 'version': '3.0'},
1072                       }
1073         if options.rspec_version:
1074             version_manager = VersionManager()
1075             server_version = self.get_cached_server_version(server)
1076             if 'sfa' in server_version:
1077                 # just request the version the client wants
1078                 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1079             else:
1080                 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1081         urn = Xrn(args[0], type='slice').get_urn()        
1082         result = server.Describe([urn], creds, api_options)
1083         value = ReturnValue.get_value(result)
1084         if self.options.raw:
1085             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1086         if options.file is not None:
1087             save_rspec_to_file(value, options.file)
1088         if (self.options.raw is None) and (options.file is None):
1089             display_rspec(value, options.format)
1090
1091         return 
1092
1093     def create(self, options, args):
1094         """
1095         create or update named slice with given rspec
1096         """
1097         server = self.sliceapi()
1098
1099         # xxx do we need to check usage (len(args)) ?
1100         # slice urn
1101         slice_hrn = args[0]
1102         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1103
1104         # credentials
1105         creds = [self.slice_credential_string(slice_hrn)]
1106
1107         delegated_cred = None
1108         server_version = self.get_cached_server_version(server)
1109         if server_version.get('interface') == 'slicemgr':
1110             # delegate our cred to the slice manager
1111             # do not delegate cred to slicemgr...not working at the moment
1112             pass
1113             #if server_version.get('hrn'):
1114             #    delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1115             #elif server_version.get('urn'):
1116             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1117
1118         if options.show_credential:
1119             show_credentials(creds)
1120
1121         # rspec
1122         rspec_file = self.get_rspec_file(args[1])
1123         rspec = open(rspec_file).read()
1124
1125         # users
1126         # need to pass along user keys to the aggregate.
1127         # users = [
1128         #  { urn: urn:publicid:IDN+emulab.net+user+alice
1129         #    keys: [<ssh key A>, <ssh key B>]
1130         #  }]
1131         users = []
1132         slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1133         if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
1134             slice_record = slice_records[0]
1135             user_hrns = slice_record['researcher']
1136             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1137             user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1138
1139             if 'sfa' not in server_version:
1140                 users = pg_users_arg(user_records)
1141                 rspec = RSpec(rspec)
1142                 rspec.filter({'component_manager_id': server_version['urn']})
1143                 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1144             else:
1145                 users = sfa_users_arg(user_records, slice_record)
1146
1147         # do not append users, keys, or slice tags. Anything
1148         # not contained in this request will be removed from the slice
1149
1150         api_options = {}
1151         api_options ['append'] = False
1152         api_options ['call_id'] = unique_call_id()
1153         result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
1154         value = ReturnValue.get_value(result)
1155         if self.options.raw:
1156             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1157         if options.file is not None:
1158             save_rspec_to_file (value, options.file)
1159         if (self.options.raw is None) and (options.file is None):
1160             print value
1161
1162         return value
1163
1164     def delete(self, options, args):
1165         """
1166         delete named slice (DeleteSliver)
1167         """
1168         server = self.sliceapi()
1169
1170         # slice urn
1171         slice_hrn = args[0]
1172         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1173
1174         # creds
1175         slice_cred = self.slice_credential_string(slice_hrn)
1176         creds = [slice_cred]
1177         if options.delegate:
1178             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1179             creds.append(delegated_cred)
1180         
1181         # options and call_id when supported
1182         api_options = {}
1183         api_options ['call_id'] = unique_call_id()
1184         if options.show_credential:
1185             show_credentials(creds)
1186         result = server.Delete([slice_urn], creds, *self.ois(server, api_options ) )
1187         value = ReturnValue.get_value(result)
1188         if self.options.raw:
1189             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1190         else:
1191             print value
1192         return value
1193
1194     def allocate(self, options, args):
1195         server = self.sliceapi()
1196         server_version = self.get_cached_server_version(server)
1197         slice_hrn = args[0]
1198         slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1199
1200         # credentials
1201         creds = [self.slice_credential_string(slice_hrn)]
1202
1203         delegated_cred = None
1204         if server_version.get('interface') == 'slicemgr':
1205             # delegate our cred to the slice manager
1206             # do not delegate cred to slicemgr...not working at the moment
1207             pass
1208             #if server_version.get('hrn'):
1209             #    delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1210             #elif server_version.get('urn'):
1211             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1212
1213         if options.show_credential:
1214             show_credentials(creds)
1215
1216         # rspec
1217         rspec_file = self.get_rspec_file(args[1])
1218         rspec = open(rspec_file).read()
1219
1220         # users
1221         # need to pass along user keys to the aggregate.
1222         # users = [
1223         #  { urn: urn:publicid:IDN+emulab.net+user+alice
1224         #    keys: [<ssh key A>, <ssh key B>]
1225         #  }]
1226         users = []
1227         slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1228         if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
1229             slice_record = slice_records[0]
1230             user_hrns = slice_record['researcher']
1231             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1232             user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1233
1234             if 'sfa' not in server_version:
1235                 users = pg_users_arg(user_records)
1236                 rspec = RSpec(rspec)
1237                 cm_hrn = Xrn(xrn=server_version['urn']).get_hrn()
1238                 cm_urn = Xrn(cm_hrn, type='authority+cm').get_urn()
1239                 rspec.filter({'component_manager_id': cm_urn})
1240                 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1241             else:
1242                 users = sfa_users_arg(user_records, slice_record)
1243
1244         api_options = {}
1245         api_options ['call_id'] = unique_call_id()
1246         api_options['geni_users'] = users    
1247         result = server.Allocate(slice_urn, creds, rspec, api_options)
1248         value = ReturnValue.get_value(result)
1249         if self.options.raw:
1250             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1251         if options.file is not None:
1252             save_rspec_to_file (value, options.file)
1253         if (self.options.raw is None) and (options.file is None):
1254             print value
1255
1256         return value
1257         
1258
1259     def provision(self, options, args):
1260         server = self.sliceapi()
1261         server_version = self.get_cached_server_version(server)
1262         slice_hrn = args[0]
1263         slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1264
1265         # credentials
1266         creds = [self.slice_credential_string(slice_hrn)]
1267         delegated_cred = None
1268         if server_version.get('interface') == 'slicemgr':
1269             # delegate our cred to the slice manager
1270             # do not delegate cred to slicemgr...not working at the moment
1271             pass
1272             #if server_version.get('hrn'):
1273             #    delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1274             #elif server_version.get('urn'):
1275             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1276
1277         if options.show_credential:
1278             show_credentials(creds)
1279
1280         api_options = {}
1281         api_options ['call_id'] = unique_call_id()
1282         result = server.Provision([slice_urn], creds, api_options)
1283         value = ReturnValue.get_value(result)
1284         if self.options.raw:
1285             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1286         if options.file is not None:
1287             save_rspec_to_file (value, options.file)
1288         if (self.options.raw is None) and (options.file is None):
1289             print value
1290         return value     
1291
1292     def status(self, options, args):
1293         """
1294         retrieve slice status (SliverStatus)
1295         """
1296         server = self.sliceapi()
1297
1298         # slice urn
1299         slice_hrn = args[0]
1300         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1301
1302         # creds 
1303         slice_cred = self.slice_credential_string(slice_hrn)
1304         creds = [slice_cred]
1305         if options.delegate:
1306             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1307             creds.append(delegated_cred)
1308
1309         # options and call_id when supported
1310         api_options = {}
1311         api_options['call_id']=unique_call_id()
1312         if options.show_credential:
1313             show_credentials(creds)
1314         result = server.Status([slice_urn], creds, *self.ois(server,api_options))
1315         value = ReturnValue.get_value(result)
1316         if self.options.raw:
1317             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1318         else:
1319             print value
1320
1321     def start(self, options, args):
1322         """
1323         start named slice (Start)
1324         """
1325         server = self.sliceapi()
1326
1327         # the slice urn
1328         slice_hrn = args[0]
1329         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1330         
1331         # cred
1332         slice_cred = self.slice_credential_string(args[0])
1333         creds = [slice_cred]
1334         if options.delegate:
1335             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1336             creds.append(delegated_cred)
1337         # xxx Thierry - does this not need an api_options as well ?
1338         result = server.Start(slice_urn, creds)
1339         value = ReturnValue.get_value(result)
1340         if self.options.raw:
1341             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1342         else:
1343             print value
1344         return value
1345     
1346     def stop(self, options, args):
1347         """
1348         stop named slice (Stop)
1349         """
1350         server = self.sliceapi()
1351         # slice urn
1352         slice_hrn = args[0]
1353         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1354         # cred
1355         slice_cred = self.slice_credential_string(args[0])
1356         creds = [slice_cred]
1357         if options.delegate:
1358             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1359             creds.append(delegated_cred)
1360         result =  server.Stop(slice_urn, creds)
1361         value = ReturnValue.get_value(result)
1362         if self.options.raw:
1363             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1364         else:
1365             print value
1366         return value
1367     
1368     # reset named slice
1369     def action(self, options, args):
1370         """
1371         Perform the named operational action on the named slivers
1372         """
1373         server = self.sliceapi()
1374         # slice urn
1375         slice_hrn = args[0]
1376         action = args[1]
1377         slice_urn = Xrn(slice_hrn, type='slice').get_urn() 
1378         # cred
1379         slice_cred = self.slice_credential_string(args[0])
1380         creds = [slice_cred]
1381         if options.delegate:
1382             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1383             creds.append(delegated_cred)
1384         
1385         result = server.PerformOperationalAction(slice_urn, creds, action )
1386         value = ReturnValue.get_value(result)
1387         if self.options.raw:
1388             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1389         else:
1390             print value
1391         return value
1392
1393     def renew(self, options, args):
1394         """
1395         renew slice (RenewSliver)
1396         """
1397         server = self.sliceapi()
1398         if len(args) != 2:
1399             self.print_help()
1400             sys.exit(1)
1401         [ slice_hrn, input_time ] = args
1402         # slice urn    
1403         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1404         # time: don't try to be smart on the time format, server-side will
1405         # creds
1406         slice_cred = self.slice_credential_string(args[0])
1407         creds = [slice_cred]
1408         if options.delegate:
1409             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1410             creds.append(delegated_cred)
1411         # options and call_id when supported
1412         api_options = {}
1413         api_options['call_id']=unique_call_id()
1414         if options.show_credential:
1415             show_credentials(creds)
1416         result =  server.Renew([slice_urn], creds, input_time, *self.ois(server,api_options))
1417         value = ReturnValue.get_value(result)
1418         if self.options.raw:
1419             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1420         else:
1421             print value
1422         return value
1423
1424
1425     def shutdown(self, options, args):
1426         """
1427         shutdown named slice (Shutdown)
1428         """
1429         server = self.sliceapi()
1430         # slice urn
1431         slice_hrn = args[0]
1432         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1433         # creds
1434         slice_cred = self.slice_credential_string(slice_hrn)
1435         creds = [slice_cred]
1436         if options.delegate:
1437             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1438             creds.append(delegated_cred)
1439         result = server.Shutdown(slice_urn, creds)
1440         value = ReturnValue.get_value(result)
1441         if self.options.raw:
1442             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1443         else:
1444             print value
1445         return value         
1446     
1447
1448     def get_ticket(self, options, args):
1449         """
1450         get a ticket for the specified slice
1451         """
1452         server = self.sliceapi()
1453         # slice urn
1454         slice_hrn, rspec_path = args[0], args[1]
1455         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1456         # creds
1457         slice_cred = self.slice_credential_string(slice_hrn)
1458         creds = [slice_cred]
1459         if options.delegate:
1460             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1461             creds.append(delegated_cred)
1462         # rspec
1463         rspec_file = self.get_rspec_file(rspec_path) 
1464         rspec = open(rspec_file).read()
1465         # options and call_id when supported
1466         api_options = {}
1467         api_options['call_id']=unique_call_id()
1468         # get ticket at the server
1469         ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1470         # save
1471         file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1472         self.logger.info("writing ticket to %s"%file)
1473         ticket = SfaTicket(string=ticket_string)
1474         ticket.save_to_file(filename=file, save_parents=True)
1475
1476     def redeem_ticket(self, options, args):
1477         """
1478         Connects to nodes in a slice and redeems a ticket
1479 (slice hrn is retrieved from the ticket)
1480         """
1481         ticket_file = args[0]
1482         
1483         # get slice hrn from the ticket
1484         # use this to get the right slice credential 
1485         ticket = SfaTicket(filename=ticket_file)
1486         ticket.decode()
1487         ticket_string = ticket.save_to_string(save_parents=True)
1488
1489         slice_hrn = ticket.gidObject.get_hrn()
1490         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1491         #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1492         slice_cred = self.slice_credential_string(slice_hrn)
1493         
1494         # get a list of node hostnames from the RSpec 
1495         tree = etree.parse(StringIO(ticket.rspec))
1496         root = tree.getroot()
1497         hostnames = root.xpath("./network/site/node/hostname/text()")
1498         
1499         # create an xmlrpc connection to the component manager at each of these
1500         # components and gall redeem_ticket
1501         connections = {}
1502         for hostname in hostnames:
1503             try:
1504                 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1505                 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1506                 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1507                 server = self.server_proxy(hostname, CM_PORT, self.private_key, 
1508                                            timeout=self.options.timeout, verbose=self.options.debug)
1509                 server.RedeemTicket(ticket_string, slice_cred)
1510                 self.logger.info("Success")
1511             except socket.gaierror:
1512                 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1513             except Exception, e:
1514                 self.logger.log_exc(e.message)
1515         return
1516
1517     def gid(self, options, args):
1518         """
1519         Create a GID (CreateGid)
1520         """
1521         if len(args) < 1:
1522             self.print_help()
1523             sys.exit(1)
1524         target_hrn = args[0]
1525         gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.client_bootstrap.my_gid_string())
1526         if options.file:
1527             filename = options.file
1528         else:
1529             filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1530         self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1531         GID(string=gid).save_to_file(filename)
1532          
1533
1534     def delegate(self, options, args):
1535         """
1536         (locally) create delegate credential for use by given hrn
1537         """
1538         delegee_hrn = args[0]
1539         if options.delegate_user:
1540             cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1541         elif options.delegate_slice:
1542             slice_cred = self.slice_credential_string(options.delegate_slice)
1543             cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1544         else:
1545             self.logger.warning("Must specify either --user or --slice <hrn>")
1546             return
1547         delegated_cred = Credential(string=cred)
1548         object_hrn = delegated_cred.get_gid_object().get_hrn()
1549         if options.delegate_user:
1550             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1551                                   + get_leaf(object_hrn) + ".cred")
1552         elif options.delegate_slice:
1553             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1554                                   + get_leaf(object_hrn) + ".cred")
1555
1556         delegated_cred.save_to_file(dest_fn, save_parents=True)
1557
1558         self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1559     
1560     def trusted(self, options, args):
1561         """
1562         return uhe trusted certs at this interface (get_trusted_certs)
1563         """ 
1564         trusted_certs = self.registry().get_trusted_certs()
1565         for trusted_cert in trusted_certs:
1566             gid = GID(string=trusted_cert)
1567             gid.dump()
1568             cert = Certificate(string=trusted_cert)
1569             self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1570         return 
1571
1572     def config (self, options, args):
1573         "Display contents of current config"
1574         self.show_config()