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