last (cleaned up) version with the -d/--delegate option that was
[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 your own credentials")
475            parser.add_option("-s", "--slice", dest="delegate_slice",
476                             help="delegate slice credential", metavar="HRN", default=None)
477            parser.add_option("-a", "--authority", dest='delegate_to_authority', default=None, action='store_true',
478                              help="""by default the only argument is expected to be a user, 
479 use this if you mean an authority instead""")
480         
481         if command in ("version"):
482             parser.add_option("-R","--registry-version",
483                               action="store_true", dest="version_registry", default=False,
484                               help="probe registry version instead of sliceapi")
485             parser.add_option("-l","--local",
486                               action="store_true", dest="version_local", default=False,
487                               help="display version of the local client")
488
489         return parser
490
491         
492     def create_parser(self):
493
494         # Generate command line parser
495         parser = OptionParser(usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
496                              description="Commands: %s"%(" ".join(self.available_names)))
497         parser.add_option("-r", "--registry", dest="registry",
498                          help="root registry", metavar="URL", default=None)
499         parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
500                          help="slice API - in general a SM URL, but can be used to talk to an aggregate")
501         parser.add_option("-R", "--raw", dest="raw", default=None,
502                           help="Save raw, unparsed server response to a file")
503         parser.add_option("", "--rawformat", dest="rawformat", type="choice",
504                           help="raw file format ([text]|pickled|json)", default="text",
505                           choices=("text","pickled","json"))
506         parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
507                           help="text string to write before and after raw output")
508         parser.add_option("-d", "--dir", dest="sfi_dir",
509                          help="config & working directory - default is %default",
510                          metavar="PATH", default=Sfi.default_sfi_dir())
511         parser.add_option("-u", "--user", dest="user",
512                          help="user name", metavar="HRN", default=None)
513         parser.add_option("-a", "--auth", dest="auth",
514                          help="authority name", metavar="HRN", default=None)
515         parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
516                          help="verbose mode - cumulative")
517         parser.add_option("-D", "--debug",
518                           action="store_true", dest="debug", default=False,
519                           help="Debug (xml-rpc) protocol messages")
520         # would it make sense to use ~/.ssh/id_rsa as a default here ?
521         parser.add_option("-k", "--private-key",
522                          action="store", dest="user_private_key", default=None,
523                          help="point to the private key file to use if not yet installed in sfi_dir")
524         parser.add_option("-t", "--timeout", dest="timeout", default=None,
525                          help="Amout of time to wait before timing out the request")
526         parser.add_option("-?", "--commands", 
527                          action="store_true", dest="command_help", default=False,
528                          help="one page summary on commands & exit")
529         parser.disable_interspersed_args()
530
531         return parser
532         
533
534     def print_help (self):
535         print "==================== Generic sfi usage"
536         self.sfi_parser.print_help()
537         print "==================== Specific command usage"
538         self.command_parser.print_help()
539
540     #
541     # Main: parse arguments and dispatch to command
542     #
543     def dispatch(self, command, command_options, command_args):
544         method=getattr(self, command,None)
545         if not method:
546             print "Unknown command %s"%command
547             return
548         return method(command_options, command_args)
549
550     def main(self):
551         self.sfi_parser = self.create_parser()
552         (options, args) = self.sfi_parser.parse_args()
553         if options.command_help: 
554             self.print_command_help(options)
555             sys.exit(1)
556         self.options = options
557
558         self.logger.setLevelFromOptVerbose(self.options.verbose)
559
560         if len(args) <= 0:
561             self.logger.critical("No command given. Use -h for help.")
562             self.print_command_help(options)
563             return -1
564     
565         # complete / find unique match with command set
566         command_candidates = Candidates (self.available_names)
567         input = args[0]
568         command = command_candidates.only_match(input)
569         if not command:
570             self.print_command_help(options)
571             sys.exit(1)
572         # second pass options parsing
573         self.command_parser = self.create_command_parser(command)
574         (command_options, command_args) = self.command_parser.parse_args(args[1:])
575         self.command_options = command_options
576
577         self.read_config () 
578         self.bootstrap ()
579         self.logger.debug("Command=%s" % command)
580
581         try:
582             self.dispatch(command, command_options, command_args)
583         except:
584             self.logger.log_exc ("sfi command %s failed"%command)
585             sys.exit(1)
586
587         return
588     
589     ####################
590     def read_config(self):
591         config_file = os.path.join(self.options.sfi_dir,"sfi_config")
592         shell_config_file  = os.path.join(self.options.sfi_dir,"sfi_config.sh")
593         try:
594             if Config.is_ini(config_file):
595                 config = Config (config_file)
596             else:
597                 # try upgrading from shell config format
598                 fp, fn = mkstemp(suffix='sfi_config', text=True)  
599                 config = Config(fn)
600                 # we need to preload the sections we want parsed 
601                 # from the shell config
602                 config.add_section('sfi')
603                 config.add_section('sface')
604                 config.load(config_file)
605                 # back up old config
606                 shutil.move(config_file, shell_config_file)
607                 # write new config
608                 config.save(config_file)
609                  
610         except:
611             self.logger.critical("Failed to read configuration file %s"%config_file)
612             self.logger.info("Make sure to remove the export clauses and to add quotes")
613             if self.options.verbose==0:
614                 self.logger.info("Re-run with -v for more details")
615             else:
616                 self.logger.log_exc("Could not read config file %s"%config_file)
617             sys.exit(1)
618      
619         errors = 0
620         # Set SliceMgr URL
621         if (self.options.sm is not None):
622            self.sm_url = self.options.sm
623         elif hasattr(config, "SFI_SM"):
624            self.sm_url = config.SFI_SM
625         else:
626            self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
627            errors += 1 
628
629         # Set Registry URL
630         if (self.options.registry is not None):
631            self.reg_url = self.options.registry
632         elif hasattr(config, "SFI_REGISTRY"):
633            self.reg_url = config.SFI_REGISTRY
634         else:
635            self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
636            errors += 1 
637
638         # Set user HRN
639         if (self.options.user is not None):
640            self.user = self.options.user
641         elif hasattr(config, "SFI_USER"):
642            self.user = config.SFI_USER
643         else:
644            self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
645            errors += 1 
646
647         # Set authority HRN
648         if (self.options.auth is not None):
649            self.authority = self.options.auth
650         elif hasattr(config, "SFI_AUTH"):
651            self.authority = config.SFI_AUTH
652         else:
653            self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
654            errors += 1 
655
656         self.config_file=config_file
657         if errors:
658            sys.exit(1)
659
660     def show_config (self):
661         print "From configuration file %s"%self.config_file
662         flags=[ 
663             ('SFI_USER','user'),
664             ('SFI_AUTH','authority'),
665             ('SFI_SM','sm_url'),
666             ('SFI_REGISTRY','reg_url'),
667             ]
668         for (external_name, internal_name) in flags:
669             print "%s='%s'"%(external_name,getattr(self,internal_name))
670
671     #
672     # Get various credential and spec files
673     #
674     # Establishes limiting conventions
675     #   - conflates MAs and SAs
676     #   - assumes last token in slice name is unique
677     #
678     # Bootstraps credentials
679     #   - bootstrap user credential from self-signed certificate
680     #   - bootstrap authority credential from user credential
681     #   - bootstrap slice credential from user credential
682     #
683     
684     # init self-signed cert, user credentials and gid
685     def bootstrap (self):
686         client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
687                                                logger=self.logger)
688         # if -k is provided, use this to initialize private key
689         if self.options.user_private_key:
690             client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
691         else:
692             # trigger legacy compat code if needed 
693             # the name has changed from just <leaf>.pkey to <hrn>.pkey
694             if not os.path.isfile(client_bootstrap.private_key_filename()):
695                 self.logger.info ("private key not found, trying legacy name")
696                 try:
697                     legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
698                     self.logger.debug("legacy_private_key=%s"%legacy_private_key)
699                     client_bootstrap.init_private_key_if_missing (legacy_private_key)
700                     self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
701                 except:
702                     self.logger.log_exc("Can't find private key ")
703                     sys.exit(1)
704             
705         # make it bootstrap
706         client_bootstrap.bootstrap_my_gid()
707         # extract what's needed
708         self.private_key = client_bootstrap.private_key()
709         self.my_credential_string = client_bootstrap.my_credential_string ()
710         self.my_gid = client_bootstrap.my_gid ()
711         self.client_bootstrap = client_bootstrap
712
713
714     def my_authority_credential_string(self):
715         if not self.authority:
716             self.logger.critical("no authority specified. Use -a or set SF_AUTH")
717             sys.exit(-1)
718         return self.client_bootstrap.authority_credential_string (self.authority)
719
720     def slice_credential_string(self, name):
721         return self.client_bootstrap.slice_credential_string (name)
722
723     #
724     # Management of the servers
725     # 
726
727     def registry (self):
728         # cache the result
729         if not hasattr (self, 'registry_proxy'):
730             self.logger.info("Contacting Registry at: %s"%self.reg_url)
731             self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid, 
732                                                  timeout=self.options.timeout, verbose=self.options.debug)  
733         return self.registry_proxy
734
735     def sliceapi (self):
736         # cache the result
737         if not hasattr (self, 'sliceapi_proxy'):
738             # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
739             if hasattr(self.command_options,'component') and self.command_options.component:
740                 # resolve the hrn at the registry
741                 node_hrn = self.command_options.component
742                 records = self.registry().Resolve(node_hrn, self.my_credential_string)
743                 records = filter_records('node', records)
744                 if not records:
745                     self.logger.warning("No such component:%r"% opts.component)
746                 record = records[0]
747                 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
748                 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
749             else:
750                 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
751                 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
752                     self.sm_url = 'http://' + self.sm_url
753                 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
754                 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid, 
755                                                      timeout=self.options.timeout, verbose=self.options.debug)  
756         return self.sliceapi_proxy
757
758     def get_cached_server_version(self, server):
759         # check local cache first
760         cache = None
761         version = None 
762         cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
763         cache_key = server.url + "-version"
764         try:
765             cache = Cache(cache_file)
766         except IOError:
767             cache = Cache()
768             self.logger.info("Local cache not found at: %s" % cache_file)
769
770         if cache:
771             version = cache.get(cache_key)
772
773         if not version: 
774             result = server.GetVersion()
775             version= ReturnValue.get_value(result)
776             # cache version for 20 minutes
777             cache.add(cache_key, version, ttl= 60*20)
778             self.logger.info("Updating cache file %s" % cache_file)
779             cache.save_to_file(cache_file)
780
781         return version   
782         
783     ### resurrect this temporarily so we can support V1 aggregates for a while
784     def server_supports_options_arg(self, server):
785         """
786         Returns true if server support the optional call_id arg, false otherwise. 
787         """
788         server_version = self.get_cached_server_version(server)
789         result = False
790         # xxx need to rewrite this 
791         if int(server_version.get('geni_api')) >= 2:
792             result = True
793         return result
794
795     def server_supports_call_id_arg(self, server):
796         server_version = self.get_cached_server_version(server)
797         result = False      
798         if 'sfa' in server_version and 'code_tag' in server_version:
799             code_tag = server_version['code_tag']
800             code_tag_parts = code_tag.split("-")
801             version_parts = code_tag_parts[0].split(".")
802             major, minor = version_parts[0], version_parts[1]
803             rev = code_tag_parts[1]
804             if int(major) == 1 and minor == 0 and build >= 22:
805                 result = True
806         return result                 
807
808     ### ois = options if supported
809     # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
810     def ois (self, server, option_dict):
811         if self.server_supports_options_arg (server): 
812             return [option_dict]
813         elif self.server_supports_call_id_arg (server):
814             return [ unique_call_id () ]
815         else: 
816             return []
817
818     ### cis = call_id if supported - like ois
819     def cis (self, server):
820         if self.server_supports_call_id_arg (server):
821             return [ unique_call_id ]
822         else:
823             return []
824
825     #################### dealing with delegated credentials
826     # most commands have a -d option that means 'delegate to my own authority'
827     # if is unclear if that is useful at all, but just in case..
828     def delegate_to_my_authority (original):
829         return self.client_bootstrap.delegate_credential_string (original, self.authority, 'authority')
830
831     ######################################## miscell utilities
832     def get_rspec_file(self, rspec):
833        if (os.path.isabs(rspec)):
834           file = rspec
835        else:
836           file = os.path.join(self.options.sfi_dir, rspec)
837        if (os.path.isfile(file)):
838           return file
839        else:
840           self.logger.critical("No such rspec file %s"%rspec)
841           sys.exit(1)
842     
843     def get_record_file(self, record):
844        if (os.path.isabs(record)):
845           file = record
846        else:
847           file = os.path.join(self.options.sfi_dir, record)
848        if (os.path.isfile(file)):
849           return file
850        else:
851           self.logger.critical("No such registry record file %s"%record)
852           sys.exit(1)
853
854
855     #==========================================================================
856     # Following functions implement the commands
857     #
858     # Registry-related commands
859     #==========================================================================
860
861     def version(self, options, args):
862         """
863         display an SFA server version (GetVersion)
864 or version information about sfi itself
865         """
866         if options.version_local:
867             version=version_core()
868         else:
869             if options.version_registry:
870                 server=self.registry()
871             else:
872                 server = self.sliceapi()
873             result = server.GetVersion()
874             version = ReturnValue.get_value(result)
875         if self.options.raw:
876             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
877         else:
878             pprinter = PrettyPrinter(indent=4)
879             pprinter.pprint(version)
880
881     def list(self, options, args):
882         """
883         list entries in named authority registry (List)
884         """
885         if len(args)!= 1:
886             self.print_help()
887             sys.exit(1)
888         hrn = args[0]
889         opts = {}
890         if options.recursive:
891             opts['recursive'] = options.recursive
892         
893         if options.show_credential:
894             show_credentials(self.my_credential_string)
895         try:
896             list = self.registry().List(hrn, self.my_credential_string, options)
897         except IndexError:
898             raise Exception, "Not enough parameters for the 'list' command"
899
900         # filter on person, slice, site, node, etc.
901         # This really should be in the self.filter_records funct def comment...
902         list = filter_records(options.type, list)
903         terminal_render (list, options)
904         if options.file:
905             save_records_to_file(options.file, list, options.fileformat)
906         return
907     
908     def show(self, options, args):
909         """
910         show details about named registry record (Resolve)
911         """
912         if len(args)!= 1:
913             self.print_help()
914             sys.exit(1)
915         hrn = args[0]
916         # explicitly require Resolve to run in details mode
917         record_dicts = self.registry().Resolve(hrn, self.my_credential_string, {'details':True})
918         record_dicts = filter_records(options.type, record_dicts)
919         if not record_dicts:
920             self.logger.error("No record of type %s"% options.type)
921             return
922         # user has required to focus on some keys
923         if options.keys:
924             def project (record):
925                 projected={}
926                 for key in options.keys:
927                     try: projected[key]=record[key]
928                     except: pass
929                 return projected
930             record_dicts = [ project (record) for record in record_dicts ]
931         records = [ Record(dict=record_dict) for record_dict in record_dicts ]
932         for record in records:
933             if (options.format == "text"):      record.dump(sort=True)  
934             else:                               print record.save_as_xml() 
935         if options.file:
936             save_records_to_file(options.file, record_dicts, options.fileformat)
937         return
938     
939     def add(self, options, args):
940         "add record into registry from xml file (Register)"
941         auth_cred = self.my_authority_credential_string()
942         if options.show_credential:
943             show_credentials(auth_cred)
944         record_dict = {}
945         if len(args) > 0:
946             record_filepath = args[0]
947             rec_file = self.get_record_file(record_filepath)
948             record_dict.update(load_record_from_file(rec_file).todict())
949         if options:
950             record_dict.update(load_record_from_opts(options).todict())
951         # we should have a type by now
952         if 'type' not in record_dict :
953             self.print_help()
954             sys.exit(1)
955         # this is still planetlab dependent.. as plc will whine without that
956         # also, it's only for adding
957         if record_dict['type'] == 'user':
958             if not 'first_name' in record_dict:
959                 record_dict['first_name'] = record_dict['hrn']
960             if 'last_name' not in record_dict:
961                 record_dict['last_name'] = record_dict['hrn'] 
962         return self.registry().Register(record_dict, auth_cred)
963     
964     def update(self, options, args):
965         "update record into registry from xml file (Update)"
966         record_dict = {}
967         if len(args) > 0:
968             record_filepath = args[0]
969             rec_file = self.get_record_file(record_filepath)
970             record_dict.update(load_record_from_file(rec_file).todict())
971         if options:
972             record_dict.update(load_record_from_opts(options).todict())
973         # at the very least we need 'type' here
974         if 'type' not in record_dict:
975             self.print_help()
976             sys.exit(1)
977
978         # don't translate into an object, as this would possibly distort
979         # user-provided data; e.g. add an 'email' field to Users
980         if record_dict['type'] == "user":
981             if record_dict['hrn'] == self.user:
982                 cred = self.my_credential_string
983             else:
984                 cred = self.my_authority_credential_string()
985         elif record_dict['type'] in ["slice"]:
986             try:
987                 cred = self.slice_credential_string(record_dict['hrn'])
988             except ServerException, e:
989                # XXX smbaker -- once we have better error return codes, update this
990                # to do something better than a string compare
991                if "Permission error" in e.args[0]:
992                    cred = self.my_authority_credential_string()
993                else:
994                    raise
995         elif record_dict['type'] in ["authority"]:
996             cred = self.my_authority_credential_string()
997         elif record_dict['type'] == 'node':
998             cred = self.my_authority_credential_string()
999         else:
1000             raise "unknown record type" + record_dict['type']
1001         if options.show_credential:
1002             show_credentials(cred)
1003         return self.registry().Update(record_dict, cred)
1004   
1005     def remove(self, options, args):
1006         "remove registry record by name (Remove)"
1007         auth_cred = self.my_authority_credential_string()
1008         if len(args)!=1:
1009             self.print_help()
1010             sys.exit(1)
1011         hrn = args[0]
1012         type = options.type 
1013         if type in ['all']:
1014             type = '*'
1015         if options.show_credential:
1016             show_credentials(auth_cred)
1017         return self.registry().Remove(hrn, auth_cred, type)
1018     
1019     # ==================================================================
1020     # Slice-related commands
1021     # ==================================================================
1022
1023     def slices(self, options, args):
1024         "list instantiated slices (ListSlices) - returns urn's"
1025         server = self.sliceapi()
1026         # creds
1027         creds = [self.my_credential_string]
1028         if options.delegate:
1029             creds.append ( self.delegate_to_my_authority(self.my_credential_string) )
1030         # options and call_id when supported
1031         api_options = {}
1032         api_options['call_id']=unique_call_id()
1033         if options.show_credential:
1034             show_credentials(creds)
1035         result = server.ListSlices(creds, *self.ois(server,api_options))
1036         value = ReturnValue.get_value(result)
1037         if self.options.raw:
1038             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1039         else:
1040             display_list(value)
1041         return
1042
1043     # show rspec for named slice
1044     def resources(self, options, args):
1045         """
1046         with no arg, discover available resources, (ListResources)
1047 or with an slice hrn, shows currently provisioned resources
1048         """
1049         server = self.sliceapi()
1050
1051         # set creds
1052         creds = []
1053         if args:
1054             the_credential=self.slice_credential_string(args[0])
1055             creds.append(the_credential)
1056         else:
1057             the_credential=self.my_credential_string
1058             creds.append(the_credential)
1059         if options.delegate:
1060             creds.append(self.delegate_to_my_authority(the_credential))
1061         if options.show_credential:
1062             show_credentials(creds)
1063
1064         # no need to check if server accepts the options argument since the options has
1065         # been a required argument since v1 API
1066         api_options = {}
1067         # always send call_id to v2 servers
1068         api_options ['call_id'] = unique_call_id()
1069         # ask for cached value if available
1070         api_options ['cached'] = True
1071         if args:
1072             hrn = args[0]
1073             api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
1074         if options.info:
1075             api_options['info'] = options.info
1076         if options.list_leases:
1077             api_options['list_leases'] = options.list_leases
1078         if options.current:
1079             if options.current == True:
1080                 api_options['cached'] = False
1081             else:
1082                 api_options['cached'] = True
1083         if options.rspec_version:
1084             version_manager = VersionManager()
1085             server_version = self.get_cached_server_version(server)
1086             if 'sfa' in server_version:
1087                 # just request the version the client wants
1088                 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1089             else:
1090                 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1091         else:
1092             api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1093         result = server.ListResources (creds, api_options)
1094         value = ReturnValue.get_value(result)
1095         if self.options.raw:
1096             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1097         if options.file is not None:
1098             save_rspec_to_file(value, options.file)
1099         if (self.options.raw is None) and (options.file is None):
1100             display_rspec(value, options.format)
1101
1102         return
1103
1104     def create(self, options, args):
1105         """
1106         create or update named slice with given rspec
1107         """
1108         server = self.sliceapi()
1109
1110         # xxx do we need to check usage (len(args)) ?
1111         # slice urn
1112         slice_hrn = args[0]
1113         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1114
1115         # credentials
1116         creds = [self.slice_credential_string(slice_hrn)]
1117
1118         delegated_cred = None
1119         server_version = self.get_cached_server_version(server)
1120         if server_version.get('interface') == 'slicemgr':
1121             # delegate our cred to the slice manager
1122             # do not delegate cred to slicemgr...not working at the moment
1123             pass
1124             #if server_version.get('hrn'):
1125             #    delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1126             #elif server_version.get('urn'):
1127             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1128
1129         if options.show_credential:
1130             show_credentials(creds)
1131
1132         # rspec
1133         rspec_file = self.get_rspec_file(args[1])
1134         rspec = open(rspec_file).read()
1135
1136         # users
1137         # need to pass along user keys to the aggregate.
1138         # users = [
1139         #  { urn: urn:publicid:IDN+emulab.net+user+alice
1140         #    keys: [<ssh key A>, <ssh key B>]
1141         #  }]
1142         users = []
1143         # xxx Thierry 2012 sept. 21
1144         # contrary to what I was first thinking, calling Resolve with details=False does not yet work properly here
1145         # I am turning details=True on again on a - hopefully - temporary basis, just to get this whole thing to work again
1146         slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1147         # slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string], {'details':True})
1148         if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']:
1149             slice_record = slice_records[0]
1150             user_hrns = slice_record['reg-researchers']
1151             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1152             user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1153
1154             if 'sfa' not in server_version:
1155                 users = pg_users_arg(user_records)
1156                 rspec = RSpec(rspec)
1157                 rspec.filter({'component_manager_id': server_version['urn']})
1158                 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1159             else:
1160                 users = sfa_users_arg(user_records, slice_record)
1161
1162         # do not append users, keys, or slice tags. Anything
1163         # not contained in this request will be removed from the slice
1164
1165         # CreateSliver has supported the options argument for a while now so it should
1166         # be safe to assume this server support it
1167         api_options = {}
1168         api_options ['append'] = False
1169         api_options ['call_id'] = unique_call_id()
1170         result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
1171         value = ReturnValue.get_value(result)
1172         if self.options.raw:
1173             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1174         if options.file is not None:
1175             save_rspec_to_file (value, options.file)
1176         if (self.options.raw is None) and (options.file is None):
1177             print value
1178
1179         return value
1180
1181     def delete(self, options, args):
1182         """
1183         delete named slice (DeleteSliver)
1184         """
1185         server = self.sliceapi()
1186
1187         # slice urn
1188         slice_hrn = args[0]
1189         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1190
1191         # creds
1192         slice_cred = self.slice_credential_string(slice_hrn)
1193         creds = [slice_cred]
1194         if options.delegate:
1195             creds.append (self.delegate_to_my_authority (slice_cred))
1196         
1197         # options and call_id when supported
1198         api_options = {}
1199         api_options ['call_id'] = unique_call_id()
1200         if options.show_credential:
1201             show_credentials(creds)
1202         result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
1203         value = ReturnValue.get_value(result)
1204         if self.options.raw:
1205             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1206         else:
1207             print value
1208         return value 
1209   
1210     def status(self, options, args):
1211         """
1212         retrieve slice status (SliverStatus)
1213         """
1214         server = self.sliceapi()
1215
1216         # slice urn
1217         slice_hrn = args[0]
1218         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1219
1220         # creds 
1221         slice_cred = self.slice_credential_string(slice_hrn)
1222         creds = [slice_cred]
1223         if options.delegate:
1224             creds.append (self.delegate_to_my_authority (slice_cred))
1225
1226         # options and call_id when supported
1227         api_options = {}
1228         api_options['call_id']=unique_call_id()
1229         if options.show_credential:
1230             show_credentials(creds)
1231         result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
1232         value = ReturnValue.get_value(result)
1233         if self.options.raw:
1234             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1235         else:
1236             print value
1237
1238     def start(self, options, args):
1239         """
1240         start named slice (Start)
1241         """
1242         server = self.sliceapi()
1243
1244         # the slice urn
1245         slice_hrn = args[0]
1246         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1247         
1248         # cred
1249         slice_cred = self.slice_credential_string(args[0])
1250         creds = [slice_cred]
1251         if options.delegate:
1252             creds.append (self.delegate_to_my_authority (slice_cred))
1253         # xxx Thierry - does this not need an api_options as well ?
1254         result = server.Start(slice_urn, creds)
1255         value = ReturnValue.get_value(result)
1256         if self.options.raw:
1257             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1258         else:
1259             print value
1260         return value
1261     
1262     def stop(self, options, args):
1263         """
1264         stop named slice (Stop)
1265         """
1266         server = self.sliceapi()
1267         # slice urn
1268         slice_hrn = args[0]
1269         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1270         # cred
1271         slice_cred = self.slice_credential_string(args[0])
1272         creds = [slice_cred]
1273         if options.delegate:
1274             creds.append (self.delegate_to_my_authority (slice_cred))
1275         result =  server.Stop(slice_urn, creds)
1276         value = ReturnValue.get_value(result)
1277         if self.options.raw:
1278             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1279         else:
1280             print value
1281         return value
1282     
1283     # reset named slice
1284     def reset(self, options, args):
1285         """
1286         reset named slice (reset_slice)
1287         """
1288         server = self.sliceapi()
1289         # slice urn
1290         slice_hrn = args[0]
1291         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1292         # cred
1293         slice_cred = self.slice_credential_string(args[0])
1294         creds = [slice_cred]
1295         if options.delegate:
1296             creds.append (self.delegate_to_my_authority (slice_cred))
1297         result = server.reset_slice(creds, slice_urn)
1298         value = ReturnValue.get_value(result)
1299         if self.options.raw:
1300             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1301         else:
1302             print value
1303         return value
1304
1305     def renew(self, options, args):
1306         """
1307         renew slice (RenewSliver)
1308         """
1309         server = self.sliceapi()
1310         if len(args) != 2:
1311             self.print_help()
1312             sys.exit(1)
1313         [ slice_hrn, input_time ] = args
1314         # slice urn    
1315         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1316         # time: don't try to be smart on the time format, server-side will
1317         # creds
1318         slice_cred = self.slice_credential_string(args[0])
1319         creds = [slice_cred]
1320         if options.delegate:
1321             creds.append (self.delegate_to_my_authority (slice_cred))
1322         # options and call_id when supported
1323         api_options = {}
1324         api_options['call_id']=unique_call_id()
1325         if options.show_credential:
1326             show_credentials(creds)
1327         result =  server.RenewSliver(slice_urn, creds, input_time, *self.ois(server,api_options))
1328         value = ReturnValue.get_value(result)
1329         if self.options.raw:
1330             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1331         else:
1332             print value
1333         return value
1334
1335
1336     def shutdown(self, options, args):
1337         """
1338         shutdown named slice (Shutdown)
1339         """
1340         server = self.sliceapi()
1341         # slice urn
1342         slice_hrn = args[0]
1343         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1344         # creds
1345         slice_cred = self.slice_credential_string(slice_hrn)
1346         creds = [slice_cred]
1347         if options.delegate:
1348             creds.append (self.delegate_to_my_authority (slice_cred))
1349         result = server.Shutdown(slice_urn, creds)
1350         value = ReturnValue.get_value(result)
1351         if self.options.raw:
1352             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1353         else:
1354             print value
1355         return value         
1356     
1357
1358     def get_ticket(self, options, args):
1359         """
1360         get a ticket for the specified slice
1361         """
1362         server = self.sliceapi()
1363         # slice urn
1364         slice_hrn, rspec_path = args[0], args[1]
1365         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1366         # creds
1367         slice_cred = self.slice_credential_string(slice_hrn)
1368         creds = [slice_cred]
1369         if options.delegate:
1370             delegated_cred = self.delegate_to_my_authority(slice_cred)
1371             creds.append(delegated_cred)
1372         # rspec
1373         rspec_file = self.get_rspec_file(rspec_path) 
1374         rspec = open(rspec_file).read()
1375         # options and call_id when supported
1376         api_options = {}
1377         api_options['call_id']=unique_call_id()
1378         # get ticket at the server
1379         ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1380         # save
1381         file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1382         self.logger.info("writing ticket to %s"%file)
1383         ticket = SfaTicket(string=ticket_string)
1384         ticket.save_to_file(filename=file, save_parents=True)
1385
1386     def redeem_ticket(self, options, args):
1387         """
1388         Connects to nodes in a slice and redeems a ticket
1389 (slice hrn is retrieved from the ticket)
1390         """
1391         ticket_file = args[0]
1392         
1393         # get slice hrn from the ticket
1394         # use this to get the right slice credential 
1395         ticket = SfaTicket(filename=ticket_file)
1396         ticket.decode()
1397         ticket_string = ticket.save_to_string(save_parents=True)
1398
1399         slice_hrn = ticket.gidObject.get_hrn()
1400         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1401         #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1402         slice_cred = self.slice_credential_string(slice_hrn)
1403         
1404         # get a list of node hostnames from the RSpec 
1405         tree = etree.parse(StringIO(ticket.rspec))
1406         root = tree.getroot()
1407         hostnames = root.xpath("./network/site/node/hostname/text()")
1408         
1409         # create an xmlrpc connection to the component manager at each of these
1410         # components and gall redeem_ticket
1411         connections = {}
1412         for hostname in hostnames:
1413             try:
1414                 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1415                 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1416                 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1417                 server = self.server_proxy(hostname, CM_PORT, self.private_key, 
1418                                            timeout=self.options.timeout, verbose=self.options.debug)
1419                 server.RedeemTicket(ticket_string, slice_cred)
1420                 self.logger.info("Success")
1421             except socket.gaierror:
1422                 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1423             except Exception, e:
1424                 self.logger.log_exc(e.message)
1425         return
1426
1427     def gid(self, options, args):
1428         """
1429         Create a GID (CreateGid)
1430         """
1431         if len(args) < 1:
1432             self.print_help()
1433             sys.exit(1)
1434         target_hrn = args[0]
1435         gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.client_bootstrap.my_gid_string())
1436         if options.file:
1437             filename = options.file
1438         else:
1439             filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1440         self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1441         GID(string=gid).save_to_file(filename)
1442          
1443
1444     def delegate (self, options, args):
1445         """
1446         (locally) create delegate credential for use by given hrn
1447         """
1448         if len(args) != 1:
1449             self.print_help()
1450             sys.exit(1)
1451         to_hrn = args[0]
1452         print 'to_hrn',to_hrn
1453         if options.delegate_to_authority:       to_type='authority'
1454         else:                                   to_type='user'
1455         if options.delegate_user:
1456             message="%s.user"%self.user
1457             original = self.my_credential_string
1458         elif options.delegate_slice:
1459             message="%s.slice"%options.delegate_slice
1460             original = self.slice_credential_string(options.delegate_slice)
1461         else:
1462             self.logger.warning("Must specify either --user or --slice <hrn>")
1463             return
1464         delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1465         delegated_credential = Credential (string=delegated_string)
1466         filename = os.path.join ( self.options.sfi_dir,
1467                                   "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1468         delegated_credential.save_to_file(filename, save_parents=True)
1469         self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1470     
1471     def trusted(self, options, args):
1472         """
1473         return uhe trusted certs at this interface (get_trusted_certs)
1474         """ 
1475         trusted_certs = self.registry().get_trusted_certs()
1476         for trusted_cert in trusted_certs:
1477             gid = GID(string=trusted_cert)
1478             gid.dump()
1479             cert = Certificate(string=trusted_cert)
1480             self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1481         return 
1482
1483     def config (self, options, args):
1484         "Display contents of current config"
1485         self.show_config()