fix Provision
[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                 rspec.filter({'component_manager_id': server_version['urn']})
1238                 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1239             else:
1240                 users = sfa_users_arg(user_records, slice_record)
1241
1242         api_options = {}
1243         api_options ['call_id'] = unique_call_id()
1244         api_options['geni_users'] = users    
1245         result = server.Allocate(slice_urn, creds, rspec, api_options)
1246         value = ReturnValue.get_value(result)
1247         if self.options.raw:
1248             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1249         if options.file is not None:
1250             save_rspec_to_file (value, options.file)
1251         if (self.options.raw is None) and (options.file is None):
1252             print value
1253
1254         return value
1255         
1256
1257     def provision(self, options, args):
1258         server = self.sliceapi()
1259         server_version = self.get_cached_server_version(server)
1260         slice_hrn = args[0]
1261         slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1262
1263         # credentials
1264         creds = [self.slice_credential_string(slice_hrn)]
1265         delegated_cred = None
1266         if server_version.get('interface') == 'slicemgr':
1267             # delegate our cred to the slice manager
1268             # do not delegate cred to slicemgr...not working at the moment
1269             pass
1270             #if server_version.get('hrn'):
1271             #    delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1272             #elif server_version.get('urn'):
1273             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1274
1275         if options.show_credential:
1276             show_credentials(creds)
1277
1278         api_options = {}
1279         api_options ['call_id'] = unique_call_id()
1280         result = server.Provision([slice_urn], creds, api_options)
1281         value = ReturnValue.get_value(result)
1282         if self.options.raw:
1283             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1284         if options.file is not None:
1285             save_rspec_to_file (value, options.file)
1286         if (self.options.raw is None) and (options.file is None):
1287             print value
1288         return value     
1289
1290     def status(self, options, args):
1291         """
1292         retrieve slice status (SliverStatus)
1293         """
1294         server = self.sliceapi()
1295
1296         # slice urn
1297         slice_hrn = args[0]
1298         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1299
1300         # creds 
1301         slice_cred = self.slice_credential_string(slice_hrn)
1302         creds = [slice_cred]
1303         if options.delegate:
1304             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1305             creds.append(delegated_cred)
1306
1307         # options and call_id when supported
1308         api_options = {}
1309         api_options['call_id']=unique_call_id()
1310         if options.show_credential:
1311             show_credentials(creds)
1312         result = server.Status([slice_urn], creds, *self.ois(server,api_options))
1313         value = ReturnValue.get_value(result)
1314         if self.options.raw:
1315             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1316         else:
1317             print value
1318
1319     def start(self, options, args):
1320         """
1321         start named slice (Start)
1322         """
1323         server = self.sliceapi()
1324
1325         # the slice urn
1326         slice_hrn = args[0]
1327         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1328         
1329         # cred
1330         slice_cred = self.slice_credential_string(args[0])
1331         creds = [slice_cred]
1332         if options.delegate:
1333             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1334             creds.append(delegated_cred)
1335         # xxx Thierry - does this not need an api_options as well ?
1336         result = server.Start(slice_urn, creds)
1337         value = ReturnValue.get_value(result)
1338         if self.options.raw:
1339             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1340         else:
1341             print value
1342         return value
1343     
1344     def stop(self, options, args):
1345         """
1346         stop named slice (Stop)
1347         """
1348         server = self.sliceapi()
1349         # slice urn
1350         slice_hrn = args[0]
1351         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1352         # cred
1353         slice_cred = self.slice_credential_string(args[0])
1354         creds = [slice_cred]
1355         if options.delegate:
1356             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1357             creds.append(delegated_cred)
1358         result =  server.Stop(slice_urn, creds)
1359         value = ReturnValue.get_value(result)
1360         if self.options.raw:
1361             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1362         else:
1363             print value
1364         return value
1365     
1366     # reset named slice
1367     def action(self, options, args):
1368         """
1369         Perform the named operational action on the named slivers
1370         """
1371         server = self.sliceapi()
1372         # slice urn
1373         slice_hrn = args[0]
1374         action = args[1]
1375         slice_urn = Xrn(slice_hrn, type='slice').get_urn() 
1376         # cred
1377         slice_cred = self.slice_credential_string(args[0])
1378         creds = [slice_cred]
1379         if options.delegate:
1380             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1381             creds.append(delegated_cred)
1382         
1383         result = server.PerformOperationalAction(slice_urn, creds, action )
1384         value = ReturnValue.get_value(result)
1385         if self.options.raw:
1386             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1387         else:
1388             print value
1389         return value
1390
1391     def renew(self, options, args):
1392         """
1393         renew slice (RenewSliver)
1394         """
1395         server = self.sliceapi()
1396         if len(args) != 2:
1397             self.print_help()
1398             sys.exit(1)
1399         [ slice_hrn, input_time ] = args
1400         # slice urn    
1401         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1402         # time: don't try to be smart on the time format, server-side will
1403         # creds
1404         slice_cred = self.slice_credential_string(args[0])
1405         creds = [slice_cred]
1406         if options.delegate:
1407             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1408             creds.append(delegated_cred)
1409         # options and call_id when supported
1410         api_options = {}
1411         api_options['call_id']=unique_call_id()
1412         if options.show_credential:
1413             show_credentials(creds)
1414         result =  server.Renew([slice_urn], creds, input_time, *self.ois(server,api_options))
1415         value = ReturnValue.get_value(result)
1416         if self.options.raw:
1417             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1418         else:
1419             print value
1420         return value
1421
1422
1423     def shutdown(self, options, args):
1424         """
1425         shutdown named slice (Shutdown)
1426         """
1427         server = self.sliceapi()
1428         # slice urn
1429         slice_hrn = args[0]
1430         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1431         # creds
1432         slice_cred = self.slice_credential_string(slice_hrn)
1433         creds = [slice_cred]
1434         if options.delegate:
1435             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1436             creds.append(delegated_cred)
1437         result = server.Shutdown(slice_urn, creds)
1438         value = ReturnValue.get_value(result)
1439         if self.options.raw:
1440             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1441         else:
1442             print value
1443         return value         
1444     
1445
1446     def get_ticket(self, options, args):
1447         """
1448         get a ticket for the specified slice
1449         """
1450         server = self.sliceapi()
1451         # slice urn
1452         slice_hrn, rspec_path = args[0], args[1]
1453         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1454         # creds
1455         slice_cred = self.slice_credential_string(slice_hrn)
1456         creds = [slice_cred]
1457         if options.delegate:
1458             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1459             creds.append(delegated_cred)
1460         # rspec
1461         rspec_file = self.get_rspec_file(rspec_path) 
1462         rspec = open(rspec_file).read()
1463         # options and call_id when supported
1464         api_options = {}
1465         api_options['call_id']=unique_call_id()
1466         # get ticket at the server
1467         ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1468         # save
1469         file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1470         self.logger.info("writing ticket to %s"%file)
1471         ticket = SfaTicket(string=ticket_string)
1472         ticket.save_to_file(filename=file, save_parents=True)
1473
1474     def redeem_ticket(self, options, args):
1475         """
1476         Connects to nodes in a slice and redeems a ticket
1477 (slice hrn is retrieved from the ticket)
1478         """
1479         ticket_file = args[0]
1480         
1481         # get slice hrn from the ticket
1482         # use this to get the right slice credential 
1483         ticket = SfaTicket(filename=ticket_file)
1484         ticket.decode()
1485         ticket_string = ticket.save_to_string(save_parents=True)
1486
1487         slice_hrn = ticket.gidObject.get_hrn()
1488         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1489         #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1490         slice_cred = self.slice_credential_string(slice_hrn)
1491         
1492         # get a list of node hostnames from the RSpec 
1493         tree = etree.parse(StringIO(ticket.rspec))
1494         root = tree.getroot()
1495         hostnames = root.xpath("./network/site/node/hostname/text()")
1496         
1497         # create an xmlrpc connection to the component manager at each of these
1498         # components and gall redeem_ticket
1499         connections = {}
1500         for hostname in hostnames:
1501             try:
1502                 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1503                 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1504                 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1505                 server = self.server_proxy(hostname, CM_PORT, self.private_key, 
1506                                            timeout=self.options.timeout, verbose=self.options.debug)
1507                 server.RedeemTicket(ticket_string, slice_cred)
1508                 self.logger.info("Success")
1509             except socket.gaierror:
1510                 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1511             except Exception, e:
1512                 self.logger.log_exc(e.message)
1513         return
1514
1515     def gid(self, options, args):
1516         """
1517         Create a GID (CreateGid)
1518         """
1519         if len(args) < 1:
1520             self.print_help()
1521             sys.exit(1)
1522         target_hrn = args[0]
1523         gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.client_bootstrap.my_gid_string())
1524         if options.file:
1525             filename = options.file
1526         else:
1527             filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1528         self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1529         GID(string=gid).save_to_file(filename)
1530          
1531
1532     def delegate(self, options, args):
1533         """
1534         (locally) create delegate credential for use by given hrn
1535         """
1536         delegee_hrn = args[0]
1537         if options.delegate_user:
1538             cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1539         elif options.delegate_slice:
1540             slice_cred = self.slice_credential_string(options.delegate_slice)
1541             cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1542         else:
1543             self.logger.warning("Must specify either --user or --slice <hrn>")
1544             return
1545         delegated_cred = Credential(string=cred)
1546         object_hrn = delegated_cred.get_gid_object().get_hrn()
1547         if options.delegate_user:
1548             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1549                                   + get_leaf(object_hrn) + ".cred")
1550         elif options.delegate_slice:
1551             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1552                                   + get_leaf(object_hrn) + ".cred")
1553
1554         delegated_cred.save_to_file(dest_fn, save_parents=True)
1555
1556         self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1557     
1558     def trusted(self, options, args):
1559         """
1560         return uhe trusted certs at this interface (get_trusted_certs)
1561         """ 
1562         trusted_certs = self.registry().get_trusted_certs()
1563         for trusted_cert in trusted_certs:
1564             gid = GID(string=trusted_cert)
1565             gid.dump()
1566             cert = Certificate(string=trusted_cert)
1567             self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1568         return 
1569
1570     def config (self, options, args):
1571         "Display contents of current config"
1572         self.show_config()