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