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