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