roll back the option --interface and use --registry-interface (-R) for both version...
[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 from sfa.client.manifolduploader import ManifoldUploader
47
48 CM_PORT=12346
49
50 from sfa.client.common import optparse_listvalue_callback, optparse_dictvalue_callback, \
51     terminal_render, filter_records 
52
53 # display methods
54 def display_rspec(rspec, format='rspec'):
55     if format in ['dns']:
56         tree = etree.parse(StringIO(rspec))
57         root = tree.getroot()
58         result = root.xpath("./network/site/node/hostname/text()")
59     elif format in ['ip']:
60         # The IP address is not yet part of the new RSpec
61         # so this doesn't do anything yet.
62         tree = etree.parse(StringIO(rspec))
63         root = tree.getroot()
64         result = root.xpath("./network/site/node/ipv4/text()")
65     else:
66         result = rspec
67
68     print result
69     return
70
71 def display_list(results):
72     for result in results:
73         print result
74
75 def display_records(recordList, dump=False):
76     ''' Print all fields in the record'''
77     for record in recordList:
78         display_record(record, dump)
79
80 def display_record(record, dump=False):
81     if dump:
82         record.dump(sort=True)
83     else:
84         info = record.getdict()
85         print "%s (%s)" % (info['hrn'], info['type'])
86     return
87
88
89 def credential_printable (credential_string):
90     credential=Credential(string=credential_string)
91     result=""
92     result += credential.get_summary_tostring()
93     result += "\n"
94     rights = credential.get_privileges()
95     result += "rights=%s"%rights
96     result += "\n"
97     return result
98
99 def show_credentials (cred_s):
100     if not isinstance (cred_s,list): cred_s = [cred_s]
101     for cred in cred_s:
102         print "Using Credential %s"%credential_printable(cred)
103
104 # save methods
105 def save_raw_to_file(var, filename, format="text", banner=None):
106     if filename == "-":
107         # if filename is "-", send it to stdout
108         f = sys.stdout
109     else:
110         f = open(filename, "w")
111     if banner:
112         f.write(banner+"\n")
113     if format == "text":
114         f.write(str(var))
115     elif format == "pickled":
116         f.write(pickle.dumps(var))
117     elif format == "json":
118         if hasattr(json, "dumps"):
119             f.write(json.dumps(var))   # python 2.6
120         else:
121             f.write(json.write(var))   # python 2.5
122     else:
123         # this should never happen
124         print "unknown output format", format
125     if banner:
126         f.write('\n'+banner+"\n")
127
128 def save_rspec_to_file(rspec, filename):
129     if not filename.endswith(".rspec"):
130         filename = filename + ".rspec"
131     f = open(filename, 'w')
132     f.write(rspec)
133     f.close()
134     return
135
136 def save_records_to_file(filename, record_dicts, format="xml"):
137     if format == "xml":
138         index = 0
139         for record_dict in record_dicts:
140             if index > 0:
141                 save_record_to_file(filename + "." + str(index), record_dict)
142             else:
143                 save_record_to_file(filename, record_dict)
144             index = index + 1
145     elif format == "xmllist":
146         f = open(filename, "w")
147         f.write("<recordlist>\n")
148         for record_dict in record_dicts:
149             record_obj=Record(dict=record_dict)
150             f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
151         f.write("</recordlist>\n")
152         f.close()
153     elif format == "hrnlist":
154         f = open(filename, "w")
155         for record_dict in record_dicts:
156             record_obj=Record(dict=record_dict)
157             f.write(record_obj.hrn + "\n")
158         f.close()
159     else:
160         # this should never happen
161         print "unknown output format", format
162
163 def save_record_to_file(filename, record_dict):
164     record = Record(dict=record_dict)
165     xml = record.save_as_xml()
166     f=codecs.open(filename, encoding='utf-8',mode="w")
167     f.write(xml)
168     f.close()
169     return
170
171 # minimally check a key argument
172 def check_ssh_key (key):
173     good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
174     return re.match(good_ssh_key, key, re.IGNORECASE)
175
176 # load methods
177 def load_record_from_opts(options):
178     record_dict = {}
179     if hasattr(options, 'xrn') and options.xrn:
180         if hasattr(options, 'type') and options.type:
181             xrn = Xrn(options.xrn, options.type)
182         else:
183             xrn = Xrn(options.xrn)
184         record_dict['urn'] = xrn.get_urn()
185         record_dict['hrn'] = xrn.get_hrn()
186         record_dict['type'] = xrn.get_type()
187     if hasattr(options, 'key') and options.key:
188         try:
189             pubkey = open(options.key, 'r').read()
190         except IOError:
191             pubkey = options.key
192         if not check_ssh_key (pubkey):
193             raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format")
194         record_dict['keys'] = [pubkey]
195     if hasattr(options, 'slices') and options.slices:
196         record_dict['slices'] = options.slices
197     if hasattr(options, 'researchers') and options.researchers:
198         record_dict['researcher'] = options.researchers
199     if hasattr(options, 'email') and options.email:
200         record_dict['email'] = options.email
201     if hasattr(options, 'pis') and options.pis:
202         record_dict['pi'] = options.pis
203
204     # handle extra settings
205     record_dict.update(options.extras)
206     
207     return Record(dict=record_dict)
208
209 def load_record_from_file(filename):
210     f=codecs.open(filename, encoding="utf-8", mode="r")
211     xml_string = f.read()
212     f.close()
213     return Record(xml=xml_string)
214
215
216 import uuid
217 def unique_call_id(): return uuid.uuid4().urn
218
219 ########## a simple model for maintaing 3 doc attributes per command (instead of just one)
220 # essentially for the methods that implement a subcommand like sfi list
221 # we need to keep track of
222 # (*) doc         a few lines that tell what it does, still located in __doc__
223 # (*) args_string a simple one-liner that describes mandatory arguments
224 # (*) example     well, one or several releant examples
225
226 # since __doc__ only accounts for one, we use this simple mechanism below
227 # however we keep doc in place for easier migration
228
229 from functools import wraps
230
231 # we use a list as well as a dict so we can keep track of the order
232 commands_list=[]
233 commands_dict={}
234
235 def register_command (args_string, example):
236     def wrap(m): 
237         name=getattr(m,'__name__')
238         doc=getattr(m,'__doc__',"-- missing doc --")
239         doc=doc.strip(" \t\n")
240         commands_list.append(name)
241         commands_dict[name]=(doc, args_string, example)
242         @wraps(m)
243         def new_method (*args, **kwds): return m(*args, **kwds)
244         return new_method
245     return wrap
246
247 ##########
248
249 class Sfi:
250     
251     # dirty hack to make this class usable from the outside
252     required_options=['verbose',  'debug',  'registry',  'sm',  'auth',  'user', 'user_private_key']
253
254     @staticmethod
255     def default_sfi_dir ():
256         if os.path.isfile("./sfi_config"): 
257             return os.getcwd()
258         else:
259             return os.path.expanduser("~/.sfi/")
260
261     # dummy to meet Sfi's expectations for its 'options' field
262     # i.e. s/t we can do setattr on
263     class DummyOptions:
264         pass
265
266     def __init__ (self,options=None):
267         if options is None: options=Sfi.DummyOptions()
268         for opt in Sfi.required_options:
269             if not hasattr(options,opt): setattr(options,opt,None)
270         if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
271         self.options = options
272         self.user = None
273         self.authority = None
274         self.logger = sfi_logger
275         self.logger.enable_console()
276         ### various auxiliary material that we keep at hand 
277         self.command=None
278         # need to call this other than just 'config' as we have a command/method with that name
279         self.config_instance=None
280         self.config_file=None
281         self.client_bootstrap=None
282
283     ### suitable if no reasonable command has been provided
284     def print_commands_help (self, options):
285         verbose=getattr(options,'verbose')
286         format3="%18s %-15s %s"
287         line=80*'-'
288         if not verbose:
289             print format3%("command","cmd_args","description")
290             print line
291         else:
292             print line
293             self.create_parser_global().print_help()
294         # preserve order from the code
295         for command in commands_list:
296             (doc, args_string, example) = commands_dict[command]
297             if verbose:
298                 print line
299             doc=doc.replace("\n","\n"+35*' ')
300             print format3%(command,args_string,doc)
301             if verbose:
302                 self.create_parser_command(command).print_help()
303             
304     ### now if a known command was found we can be more verbose on that one
305     def print_help (self):
306         print "==================== Generic sfi usage"
307         self.sfi_parser.print_help()
308         (doc,_,example)=commands_dict[self.command]
309         print "\n==================== Purpose of %s"%self.command
310         print doc
311         print "\n==================== Specific usage for %s"%self.command
312         self.command_parser.print_help()
313         if example:
314             print "\n==================== %s example(s)"%self.command
315             print example
316
317     def create_parser_global(self):
318
319         # Generate command line parser
320         parser = OptionParser(add_help_option=False,
321                               usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
322                               description="Commands: %s"%(" ".join(commands_list)))
323         parser.add_option("-r", "--registry", dest="registry",
324                          help="root registry", metavar="URL", default=None)
325         parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
326                          help="slice API - in general a SM URL, but can be used to talk to an aggregate")
327         parser.add_option("-R", "--raw", dest="raw", default=None,
328                           help="Save raw, unparsed server response to a file")
329         parser.add_option("", "--rawformat", dest="rawformat", type="choice",
330                           help="raw file format ([text]|pickled|json)", default="text",
331                           choices=("text","pickled","json"))
332         parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
333                           help="text string to write before and after raw output")
334         parser.add_option("-d", "--dir", dest="sfi_dir",
335                          help="config & working directory - default is %default",
336                          metavar="PATH", default=Sfi.default_sfi_dir())
337         parser.add_option("-u", "--user", dest="user",
338                          help="user name", metavar="HRN", default=None)
339         parser.add_option("-a", "--auth", dest="auth",
340                          help="authority name", metavar="HRN", default=None)
341         parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
342                          help="verbose mode - cumulative")
343         parser.add_option("-D", "--debug",
344                           action="store_true", dest="debug", default=False,
345                           help="Debug (xml-rpc) protocol messages")
346         # would it make sense to use ~/.ssh/id_rsa as a default here ?
347         parser.add_option("-k", "--private-key",
348                          action="store", dest="user_private_key", default=None,
349                          help="point to the private key file to use if not yet installed in sfi_dir")
350         parser.add_option("-t", "--timeout", dest="timeout", default=None,
351                          help="Amout of time to wait before timing out the request")
352         parser.add_option("-h", "--help", 
353                          action="store_true", dest="help", default=False,
354                          help="one page summary on commands & exit")
355         parser.disable_interspersed_args()
356
357         return parser
358         
359
360     def create_parser_command(self, command):
361         if command not in commands_dict:
362             msg="Invalid command\n"
363             msg+="Commands: "
364             msg += ','.join(commands_list)            
365             self.logger.critical(msg)
366             sys.exit(2)
367
368         # retrieve args_string
369         (_, args_string, __) = commands_dict[command]
370
371         parser = OptionParser(add_help_option=False,
372                               usage="sfi [sfi_options] %s [cmd_options] %s"
373                               % (command, args_string))
374         parser.add_option ("-h","--help",dest='help',action='store_true',default=False,
375                            help="Summary of one command usage")
376
377         if command in ("config"):
378             parser.add_option('-m', '--myslice', dest='myslice', action='store_true', default=False,
379                               help='how myslice config variables as well')
380
381         if command in ("version"):
382             parser.add_option("-l","--local",
383                               action="store_true", dest="version_local", default=False,
384                               help="display version of the local client")
385
386         if command in ("version", "trusted"):
387             parser.add_option("-R","--registry-interface",
388                              action="store_true", dest="registry-interface", default=False,
389                              help="target the registry interface instead of slice interface")
390
391         if command in ("add", "update"):
392             parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
393             parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
394             parser.add_option('-e', '--email', dest='email', default="",  help="email (mandatory for users)") 
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='Set/replace slice xrns',
398                               default='', type="str", action='callback', callback=optparse_listvalue_callback)
399             parser.add_option('-r', '--researchers', dest='researchers', metavar='<researchers>', 
400                               help='Set/replace slice researchers', default='', type="str", action='callback', 
401                               callback=optparse_listvalue_callback)
402             parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
403                               default='', type="str", action='callback', callback=optparse_listvalue_callback)
404             parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
405                                action="callback", callback=optparse_dictvalue_callback, nargs=1,
406                                help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
407
408         # show_credential option
409         if command in ("list","resources","create","add","update","remove","slices","delete","status","renew"):
410             parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
411                               help="show credential(s) used in human-readable form")
412         # registy filter option
413         if command in ("list", "show", "remove"):
414             parser.add_option("-t", "--type", dest="type", type="choice",
415                             help="type filter ([all]|user|slice|authority|node|aggregate)",
416                             choices=("all", "user", "slice", "authority", "node", "aggregate"),
417                             default="all")
418         if command in ("show"):
419             parser.add_option("-k","--key",dest="keys",action="append",default=[],
420                               help="specify specific keys to be displayed from record")
421         if command in ("resources"):
422             # rspec version
423             parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
424                               help="schema type and version of resulting RSpec")
425             # disable/enable cached rspecs
426             parser.add_option("-c", "--current", dest="current", default=False,
427                               action="store_true",  
428                               help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
429             # display formats
430             parser.add_option("-f", "--format", dest="format", type="choice",
431                              help="display format ([xml]|dns|ip)", default="xml",
432                              choices=("xml", "dns", "ip"))
433             #panos: a new option to define the type of information about resources a user is interested in
434             parser.add_option("-i", "--info", dest="info",
435                                 help="optional component information", default=None)
436             # a new option to retreive or not reservation-oriented RSpecs (leases)
437             parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
438                                 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
439                                 choices=("all", "resources", "leases"), default="resources")
440
441
442         # 'create' does return the new rspec, makes sense to save that too
443         if command in ("resources", "show", "list", "gid", 'create'):
444            parser.add_option("-o", "--output", dest="file",
445                             help="output XML to file", metavar="FILE", default=None)
446
447         if command in ("show", "list"):
448            parser.add_option("-f", "--format", dest="format", type="choice",
449                              help="display format ([text]|xml)", default="text",
450                              choices=("text", "xml"))
451
452            parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
453                              help="output file format ([xml]|xmllist|hrnlist)", default="xml",
454                              choices=("xml", "xmllist", "hrnlist"))
455         if command == 'list':
456            parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
457                              help="list all child records", default=False)
458            parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
459                              help="gives details, like user keys", default=False)
460         if command in ("delegate"):
461            parser.add_option("-u", "--user",
462                              action="store_true", dest="delegate_user", default=False,
463                              help="delegate your own credentials; default if no other option is provided")
464            parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
465                              metavar="slice_hrn", help="delegate cred. for slice HRN")
466            parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
467                              metavar='auth_hrn', help="delegate cred for auth HRN")
468            # this primarily is a shorthand for -a my_hrn
469            parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
470                              help="delegate your PI credentials, so s.t. like -a your_hrn^")
471            parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
472                              help="""by default the mandatory argument is expected to be a user, 
473 use this if you mean an authority instead""")
474
475         if command in ("myslice"):
476             parser.add_option("-p","--password",dest='password',action='store',default=None,
477                               help="specify mainfold password on the command line")
478             parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
479                              metavar="slice_hrn", help="delegate cred. for slice HRN")
480             parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
481                              metavar='auth_hrn', help="delegate PI cred for auth HRN")
482             parser.add_option('-d', '--delegate', dest='delegate', help="Override 'delegate' from the config file")
483             parser.add_option('-b', '--backend',  dest='backend',  help="Override 'backend' from the config file")
484         
485         return parser
486
487         
488     #
489     # Main: parse arguments and dispatch to command
490     #
491     def dispatch(self, command, command_options, command_args):
492         method=getattr(self, command, None)
493         if not method:
494             print "Unknown command %s"%command
495             return
496         return method(command_options, command_args)
497
498     def main(self):
499         self.sfi_parser = self.create_parser_global()
500         (options, args) = self.sfi_parser.parse_args()
501         if options.help: 
502             self.print_commands_help(options)
503             sys.exit(1)
504         self.options = options
505
506         self.logger.setLevelFromOptVerbose(self.options.verbose)
507
508         if len(args) <= 0:
509             self.logger.critical("No command given. Use -h for help.")
510             self.print_commands_help(options)
511             return -1
512     
513         # complete / find unique match with command set
514         command_candidates = Candidates (commands_list)
515         input = args[0]
516         command = command_candidates.only_match(input)
517         if not command:
518             self.print_commands_help(options)
519             sys.exit(1)
520         # second pass options parsing
521         self.command=command
522         self.command_parser = self.create_parser_command(command)
523         (command_options, command_args) = self.command_parser.parse_args(args[1:])
524         if command_options.help:
525             self.print_help()
526             sys.exit(1)
527         self.command_options = command_options
528
529         self.read_config () 
530         self.bootstrap ()
531         self.logger.debug("Command=%s" % self.command)
532
533         try:
534             self.dispatch(command, command_options, command_args)
535         except SystemExit:
536             return 1
537         except:
538             self.logger.log_exc ("sfi command %s failed"%command)
539             return 1
540
541         return 0
542     
543     ####################
544     def read_config(self):
545         config_file = os.path.join(self.options.sfi_dir,"sfi_config")
546         shell_config_file  = os.path.join(self.options.sfi_dir,"sfi_config.sh")
547         try:
548             if Config.is_ini(config_file):
549                 config = Config (config_file)
550             else:
551                 # try upgrading from shell config format
552                 fp, fn = mkstemp(suffix='sfi_config', text=True)  
553                 config = Config(fn)
554                 # we need to preload the sections we want parsed 
555                 # from the shell config
556                 config.add_section('sfi')
557                 # sface users should be able to use this same file to configure their stuff
558                 config.add_section('sface')
559                 # manifold users should be able to specify the details 
560                 # of their backend server here for 'sfi myslice'
561                 config.add_section('myslice')
562                 config.load(config_file)
563                 # back up old config
564                 shutil.move(config_file, shell_config_file)
565                 # write new config
566                 config.save(config_file)
567                  
568         except:
569             self.logger.critical("Failed to read configuration file %s"%config_file)
570             self.logger.info("Make sure to remove the export clauses and to add quotes")
571             if self.options.verbose==0:
572                 self.logger.info("Re-run with -v for more details")
573             else:
574                 self.logger.log_exc("Could not read config file %s"%config_file)
575             sys.exit(1)
576      
577         self.config_instance=config
578         errors = 0
579         # Set SliceMgr URL
580         if (self.options.sm is not None):
581            self.sm_url = self.options.sm
582         elif hasattr(config, "SFI_SM"):
583            self.sm_url = config.SFI_SM
584         else:
585            self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
586            errors += 1 
587
588         # Set Registry URL
589         if (self.options.registry is not None):
590            self.reg_url = self.options.registry
591         elif hasattr(config, "SFI_REGISTRY"):
592            self.reg_url = config.SFI_REGISTRY
593         else:
594            self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
595            errors += 1 
596
597         # Set user HRN
598         if (self.options.user is not None):
599            self.user = self.options.user
600         elif hasattr(config, "SFI_USER"):
601            self.user = config.SFI_USER
602         else:
603            self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
604            errors += 1 
605
606         # Set authority HRN
607         if (self.options.auth is not None):
608            self.authority = self.options.auth
609         elif hasattr(config, "SFI_AUTH"):
610            self.authority = config.SFI_AUTH
611         else:
612            self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
613            errors += 1 
614
615         self.config_file=config_file
616         if errors:
617            sys.exit(1)
618
619     #
620     # Get various credential and spec files
621     #
622     # Establishes limiting conventions
623     #   - conflates MAs and SAs
624     #   - assumes last token in slice name is unique
625     #
626     # Bootstraps credentials
627     #   - bootstrap user credential from self-signed certificate
628     #   - bootstrap authority credential from user credential
629     #   - bootstrap slice credential from user credential
630     #
631     
632     # init self-signed cert, user credentials and gid
633     def bootstrap (self):
634         client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
635                                                logger=self.logger)
636         # if -k is provided, use this to initialize private key
637         if self.options.user_private_key:
638             client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
639         else:
640             # trigger legacy compat code if needed 
641             # the name has changed from just <leaf>.pkey to <hrn>.pkey
642             if not os.path.isfile(client_bootstrap.private_key_filename()):
643                 self.logger.info ("private key not found, trying legacy name")
644                 try:
645                     legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
646                     self.logger.debug("legacy_private_key=%s"%legacy_private_key)
647                     client_bootstrap.init_private_key_if_missing (legacy_private_key)
648                     self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
649                 except:
650                     self.logger.log_exc("Can't find private key ")
651                     sys.exit(1)
652             
653         # make it bootstrap
654         client_bootstrap.bootstrap_my_gid()
655         # extract what's needed
656         self.private_key = client_bootstrap.private_key()
657         self.my_credential_string = client_bootstrap.my_credential_string ()
658         self.my_gid = client_bootstrap.my_gid ()
659         self.client_bootstrap = client_bootstrap
660
661
662     def my_authority_credential_string(self):
663         if not self.authority:
664             self.logger.critical("no authority specified. Use -a or set SF_AUTH")
665             sys.exit(-1)
666         return self.client_bootstrap.authority_credential_string (self.authority)
667
668     def authority_credential_string(self, auth_hrn):
669         return self.client_bootstrap.authority_credential_string (auth_hrn)
670
671     def slice_credential_string(self, name):
672         return self.client_bootstrap.slice_credential_string (name)
673
674     #
675     # Management of the servers
676     # 
677
678     def registry (self):
679         # cache the result
680         if not hasattr (self, 'registry_proxy'):
681             self.logger.info("Contacting Registry at: %s"%self.reg_url)
682             self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid, 
683                                                  timeout=self.options.timeout, verbose=self.options.debug)  
684         return self.registry_proxy
685
686     def sliceapi (self):
687         # cache the result
688         if not hasattr (self, 'sliceapi_proxy'):
689             # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
690             if hasattr(self.command_options,'component') and self.command_options.component:
691                 # resolve the hrn at the registry
692                 node_hrn = self.command_options.component
693                 records = self.registry().Resolve(node_hrn, self.my_credential_string)
694                 records = filter_records('node', records)
695                 if not records:
696                     self.logger.warning("No such component:%r"% opts.component)
697                 record = records[0]
698                 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
699                 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
700             else:
701                 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
702                 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
703                     self.sm_url = 'http://' + self.sm_url
704                 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
705                 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid, 
706                                                      timeout=self.options.timeout, verbose=self.options.debug)  
707         return self.sliceapi_proxy
708
709     def get_cached_server_version(self, server):
710         # check local cache first
711         cache = None
712         version = None 
713         cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
714         cache_key = server.url + "-version"
715         try:
716             cache = Cache(cache_file)
717         except IOError:
718             cache = Cache()
719             self.logger.info("Local cache not found at: %s" % cache_file)
720
721         if cache:
722             version = cache.get(cache_key)
723
724         if not version: 
725             result = server.GetVersion()
726             version= ReturnValue.get_value(result)
727             # cache version for 20 minutes
728             cache.add(cache_key, version, ttl= 60*20)
729             self.logger.info("Updating cache file %s" % cache_file)
730             cache.save_to_file(cache_file)
731
732         return version   
733         
734     ### resurrect this temporarily so we can support V1 aggregates for a while
735     def server_supports_options_arg(self, server):
736         """
737         Returns true if server support the optional call_id arg, false otherwise. 
738         """
739         server_version = self.get_cached_server_version(server)
740         result = False
741         # xxx need to rewrite this 
742         if int(server_version.get('geni_api')) >= 2:
743             result = True
744         return result
745
746     def server_supports_call_id_arg(self, server):
747         server_version = self.get_cached_server_version(server)
748         result = False      
749         if 'sfa' in server_version and 'code_tag' in server_version:
750             code_tag = server_version['code_tag']
751             code_tag_parts = code_tag.split("-")
752             version_parts = code_tag_parts[0].split(".")
753             major, minor = version_parts[0], version_parts[1]
754             rev = code_tag_parts[1]
755             if int(major) == 1 and minor == 0 and build >= 22:
756                 result = True
757         return result                 
758
759     ### ois = options if supported
760     # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
761     def ois (self, server, option_dict):
762         if self.server_supports_options_arg (server): 
763             return [option_dict]
764         elif self.server_supports_call_id_arg (server):
765             return [ unique_call_id () ]
766         else: 
767             return []
768
769     ### cis = call_id if supported - like ois
770     def cis (self, server):
771         if self.server_supports_call_id_arg (server):
772             return [ unique_call_id ]
773         else:
774             return []
775
776     ######################################## miscell utilities
777     def get_rspec_file(self, rspec):
778        if (os.path.isabs(rspec)):
779           file = rspec
780        else:
781           file = os.path.join(self.options.sfi_dir, rspec)
782        if (os.path.isfile(file)):
783           return file
784        else:
785           self.logger.critical("No such rspec file %s"%rspec)
786           sys.exit(1)
787     
788     def get_record_file(self, record):
789        if (os.path.isabs(record)):
790           file = record
791        else:
792           file = os.path.join(self.options.sfi_dir, record)
793        if (os.path.isfile(file)):
794           return file
795        else:
796           self.logger.critical("No such registry record file %s"%record)
797           sys.exit(1)
798
799
800     #==========================================================================
801     # Following functions implement the commands
802     #
803     # Registry-related commands
804     #==========================================================================
805
806     @register_command("","")
807     def config (self, options, args):
808         "Display contents of current config"
809         print "# From configuration file %s"%self.config_file
810         flags=[ ('sfi', [ ('registry','reg_url'),
811                           ('auth','authority'),
812                           ('user','user'),
813                           ('sm','sm_url'),
814                           ]),
815                 ]
816         if options.myslice:
817             flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
818
819         for (section, tuples) in flags:
820             print "[%s]"%section
821             try:
822                 for (external_name, internal_name) in tuples:
823                     print "%-20s = %s"%(external_name,getattr(self,internal_name))
824             except:
825                 for name in tuples:
826                     varname="%s_%s"%(section.upper(),name.upper())
827                     value=getattr(self.config_instance,varname)
828                     print "%-20s = %s"%(name,value)
829
830     @register_command("","")
831     def version(self, options, args):
832         """
833         display an SFA server version (GetVersion)
834   or version information about sfi itself
835         """
836         if options.version_local:
837             version=version_core()
838         else:
839             if options.registry-interface:
840                 server=self.registry()
841             else:
842                 server = self.sliceapi()
843             result = server.GetVersion()
844             version = ReturnValue.get_value(result)
845         if self.options.raw:
846             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
847         else:
848             pprinter = PrettyPrinter(indent=4)
849             pprinter.pprint(version)
850
851     @register_command("authority","")
852     def list(self, options, args):
853         """
854         list entries in named authority registry (List)
855         """
856         if len(args)!= 1:
857             self.print_help()
858             sys.exit(1)
859         hrn = args[0]
860         opts = {}
861         if options.recursive:
862             opts['recursive'] = options.recursive
863         
864         if options.show_credential:
865             show_credentials(self.my_credential_string)
866         try:
867             list = self.registry().List(hrn, self.my_credential_string, options)
868         except IndexError:
869             raise Exception, "Not enough parameters for the 'list' command"
870
871         # filter on person, slice, site, node, etc.
872         # This really should be in the self.filter_records funct def comment...
873         list = filter_records(options.type, list)
874         terminal_render (list, options)
875         if options.file:
876             save_records_to_file(options.file, list, options.fileformat)
877         return
878     
879     @register_command("name","")
880     def show(self, options, args):
881         """
882         show details about named registry record (Resolve)
883         """
884         if len(args)!= 1:
885             self.print_help()
886             sys.exit(1)
887         hrn = args[0]
888         # explicitly require Resolve to run in details mode
889         record_dicts = self.registry().Resolve(hrn, self.my_credential_string, {'details':True})
890         record_dicts = filter_records(options.type, record_dicts)
891         if not record_dicts:
892             self.logger.error("No record of type %s"% options.type)
893             return
894         # user has required to focus on some keys
895         if options.keys:
896             def project (record):
897                 projected={}
898                 for key in options.keys:
899                     try: projected[key]=record[key]
900                     except: pass
901                 return projected
902             record_dicts = [ project (record) for record in record_dicts ]
903         records = [ Record(dict=record_dict) for record_dict in record_dicts ]
904         for record in records:
905             if (options.format == "text"):      record.dump(sort=True)  
906             else:                               print record.save_as_xml() 
907         if options.file:
908             save_records_to_file(options.file, record_dicts, options.fileformat)
909         return
910     
911     @register_command("[xml-filename]","")
912     def add(self, options, args):
913         """add record into registry (Register) 
914   from command line options (recommended) 
915   old-school method involving an xml file still supported"""
916
917         auth_cred = self.my_authority_credential_string()
918         if options.show_credential:
919             show_credentials(auth_cred)
920         record_dict = {}
921         if len(args) > 1:
922             self.print_help()
923             sys.exit(1)
924         if len(args)==1:
925             try:
926                 record_filepath = args[0]
927                 rec_file = self.get_record_file(record_filepath)
928                 record_dict.update(load_record_from_file(rec_file).todict())
929             except:
930                 print "Cannot load record file %s"%record_filepath
931                 sys.exit(1)
932         if options:
933             record_dict.update(load_record_from_opts(options).todict())
934         # we should have a type by now
935         if 'type' not in record_dict :
936             self.print_help()
937             sys.exit(1)
938         # this is still planetlab dependent.. as plc will whine without that
939         # also, it's only for adding
940         if record_dict['type'] == 'user':
941             if not 'first_name' in record_dict:
942                 record_dict['first_name'] = record_dict['hrn']
943             if 'last_name' not in record_dict:
944                 record_dict['last_name'] = record_dict['hrn'] 
945         return self.registry().Register(record_dict, auth_cred)
946     
947     @register_command("[xml-filename]","")
948     def update(self, options, args):
949         """update record into registry (Update) 
950   from command line options (recommended) 
951   old-school method involving an xml file still supported"""
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     @register_command("hrn","")
992     def remove(self, options, args):
993         "remove registry record by name (Remove)"
994         auth_cred = self.my_authority_credential_string()
995         if len(args)!=1:
996             self.print_help()
997             sys.exit(1)
998         hrn = args[0]
999         type = options.type 
1000         if type in ['all']:
1001             type = '*'
1002         if options.show_credential:
1003             show_credentials(auth_cred)
1004         return self.registry().Remove(hrn, auth_cred, type)
1005     
1006     # ==================================================================
1007     # Slice-related commands
1008     # ==================================================================
1009
1010     @register_command("","")
1011     def slices(self, options, args):
1012         "list instantiated slices (ListSlices) - returns urn's"
1013         server = self.sliceapi()
1014         # creds
1015         creds = [self.my_credential_string]
1016         # options and call_id when supported
1017         api_options = {}
1018         api_options['call_id']=unique_call_id()
1019         if options.show_credential:
1020             show_credentials(creds)
1021         result = server.ListSlices(creds, *self.ois(server,api_options))
1022         value = ReturnValue.get_value(result)
1023         if self.options.raw:
1024             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1025         else:
1026             display_list(value)
1027         return
1028
1029     # show rspec for named slice
1030     @register_command("[slice_hrn]","")
1031     def resources(self, options, args):
1032         """
1033         with no arg, discover available resources, (ListResources)
1034   or with an slice hrn, shows currently provisioned resources
1035         """
1036         server = self.sliceapi()
1037
1038         # set creds
1039         creds = []
1040         if args:
1041             the_credential=self.slice_credential_string(args[0])
1042             creds.append(the_credential)
1043         else:
1044             the_credential=self.my_credential_string
1045             creds.append(the_credential)
1046         if options.show_credential:
1047             show_credentials(creds)
1048
1049         # no need to check if server accepts the options argument since the options has
1050         # been a required argument since v1 API
1051         api_options = {}
1052         # always send call_id to v2 servers
1053         api_options ['call_id'] = unique_call_id()
1054         # ask for cached value if available
1055         api_options ['cached'] = True
1056         if args:
1057             hrn = args[0]
1058             api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
1059         if options.info:
1060             api_options['info'] = options.info
1061         if options.list_leases:
1062             api_options['list_leases'] = options.list_leases
1063         if options.current:
1064             if options.current == True:
1065                 api_options['cached'] = False
1066             else:
1067                 api_options['cached'] = True
1068         if options.rspec_version:
1069             version_manager = VersionManager()
1070             server_version = self.get_cached_server_version(server)
1071             if 'sfa' in server_version:
1072                 # just request the version the client wants
1073                 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1074             else:
1075                 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1076         else:
1077             api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1078         result = server.ListResources (creds, api_options)
1079         value = ReturnValue.get_value(result)
1080         if self.options.raw:
1081             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1082         if options.file is not None:
1083             save_rspec_to_file(value, options.file)
1084         if (self.options.raw is None) and (options.file is None):
1085             display_rspec(value, options.format)
1086
1087         return
1088
1089     @register_command("slice_hrn rspec","")
1090     def create(self, options, args):
1091         """
1092         create or update named slice with given rspec (CreateSliver)
1093         """
1094         server = self.sliceapi()
1095
1096         # xxx do we need to check usage (len(args)) ?
1097         # slice urn
1098         slice_hrn = args[0]
1099         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1100
1101         # credentials
1102         creds = [self.slice_credential_string(slice_hrn)]
1103
1104         delegated_cred = None
1105         server_version = self.get_cached_server_version(server)
1106         if server_version.get('interface') == 'slicemgr':
1107             # delegate our cred to the slice manager
1108             # do not delegate cred to slicemgr...not working at the moment
1109             pass
1110             #if server_version.get('hrn'):
1111             #    delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1112             #elif server_version.get('urn'):
1113             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1114
1115         if options.show_credential:
1116             show_credentials(creds)
1117
1118         # rspec
1119         rspec_file = self.get_rspec_file(args[1])
1120         rspec = open(rspec_file).read()
1121
1122         # users
1123         # need to pass along user keys to the aggregate.
1124         # users = [
1125         #  { urn: urn:publicid:IDN+emulab.net+user+alice
1126         #    keys: [<ssh key A>, <ssh key B>]
1127         #  }]
1128         users = []
1129         # xxx Thierry 2012 sept. 21
1130         # contrary to what I was first thinking, calling Resolve with details=False does not yet work properly here
1131         # I am turning details=True on again on a - hopefully - temporary basis, just to get this whole thing to work again
1132         slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1133         # slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string], {'details':True})
1134         if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']:
1135             slice_record = slice_records[0]
1136             user_hrns = slice_record['reg-researchers']
1137             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1138             user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1139
1140             if 'sfa' not in server_version:
1141                 users = pg_users_arg(user_records)
1142                 #rspec = RSpec(rspec)
1143                 #rspec.filter({'component_manager_id': server_version['urn']})
1144                 #rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1145             else:
1146                 users = sfa_users_arg(user_records, slice_record)
1147
1148         # do not append users, keys, or slice tags. Anything
1149         # not contained in this request will be removed from the slice
1150
1151         # CreateSliver has supported the options argument for a while now so it should
1152         # be safe to assume this server support it
1153         api_options = {}
1154         api_options ['append'] = False
1155         api_options ['call_id'] = unique_call_id()
1156         result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
1157         value = ReturnValue.get_value(result)
1158         if self.options.raw:
1159             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1160         if options.file is not None:
1161             save_rspec_to_file (value, options.file)
1162         if (self.options.raw is None) and (options.file is None):
1163             print value
1164
1165         return value
1166
1167     @register_command("slice_hrn","")
1168     def delete(self, options, args):
1169         """
1170         delete named slice (DeleteSliver)
1171         """
1172         server = self.sliceapi()
1173
1174         # slice urn
1175         slice_hrn = args[0]
1176         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1177
1178         # creds
1179         slice_cred = self.slice_credential_string(slice_hrn)
1180         creds = [slice_cred]
1181         
1182         # options and call_id when supported
1183         api_options = {}
1184         api_options ['call_id'] = unique_call_id()
1185         if options.show_credential:
1186             show_credentials(creds)
1187         result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
1188         value = ReturnValue.get_value(result)
1189         if self.options.raw:
1190             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1191         else:
1192             print value
1193         return value 
1194   
1195     @register_command("slice_hrn","")
1196     def status(self, options, args):
1197         """
1198         retrieve slice status (SliverStatus)
1199         """
1200         server = self.sliceapi()
1201
1202         # slice urn
1203         slice_hrn = args[0]
1204         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1205
1206         # creds 
1207         slice_cred = self.slice_credential_string(slice_hrn)
1208         creds = [slice_cred]
1209
1210         # options and call_id when supported
1211         api_options = {}
1212         api_options['call_id']=unique_call_id()
1213         if options.show_credential:
1214             show_credentials(creds)
1215         result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
1216         value = ReturnValue.get_value(result)
1217         if self.options.raw:
1218             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1219         else:
1220             print value
1221
1222     @register_command("slice_hrn","")
1223     def start(self, options, args):
1224         """
1225         start named slice (Start)
1226         """
1227         server = self.sliceapi()
1228
1229         # the slice urn
1230         slice_hrn = args[0]
1231         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1232         
1233         # cred
1234         slice_cred = self.slice_credential_string(args[0])
1235         creds = [slice_cred]
1236         # xxx Thierry - does this not need an api_options as well ?
1237         result = server.Start(slice_urn, creds)
1238         value = ReturnValue.get_value(result)
1239         if self.options.raw:
1240             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1241         else:
1242             print value
1243         return value
1244     
1245     @register_command("slice_hrn","")
1246     def stop(self, options, args):
1247         """
1248         stop named slice (Stop)
1249         """
1250         server = self.sliceapi()
1251         # slice urn
1252         slice_hrn = args[0]
1253         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1254         # cred
1255         slice_cred = self.slice_credential_string(args[0])
1256         creds = [slice_cred]
1257         result =  server.Stop(slice_urn, creds)
1258         value = ReturnValue.get_value(result)
1259         if self.options.raw:
1260             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1261         else:
1262             print value
1263         return value
1264     
1265     # reset named slice
1266     @register_command("slice_hrn","")
1267     def reset(self, options, args):
1268         """
1269         reset named slice (reset_slice)
1270         """
1271         server = self.sliceapi()
1272         # slice urn
1273         slice_hrn = args[0]
1274         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1275         # cred
1276         slice_cred = self.slice_credential_string(args[0])
1277         creds = [slice_cred]
1278         result = server.reset_slice(creds, slice_urn)
1279         value = ReturnValue.get_value(result)
1280         if self.options.raw:
1281             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1282         else:
1283             print value
1284         return value
1285
1286     @register_command("slice_hrn time","")
1287     def renew(self, options, args):
1288         """
1289         renew slice (RenewSliver)
1290         """
1291         server = self.sliceapi()
1292         if len(args) != 2:
1293             self.print_help()
1294             sys.exit(1)
1295         [ slice_hrn, input_time ] = args
1296         # slice urn    
1297         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1298         # time: don't try to be smart on the time format, server-side will
1299         # creds
1300         slice_cred = self.slice_credential_string(args[0])
1301         creds = [slice_cred]
1302         # options and call_id when supported
1303         api_options = {}
1304         api_options['call_id']=unique_call_id()
1305         if options.show_credential:
1306             show_credentials(creds)
1307         result =  server.RenewSliver(slice_urn, creds, input_time, *self.ois(server,api_options))
1308         value = ReturnValue.get_value(result)
1309         if self.options.raw:
1310             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1311         else:
1312             print value
1313         return value
1314
1315
1316     @register_command("slice_hrn","")
1317     def shutdown(self, options, args):
1318         """
1319         shutdown named slice (Shutdown)
1320         """
1321         server = self.sliceapi()
1322         # slice urn
1323         slice_hrn = args[0]
1324         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1325         # creds
1326         slice_cred = self.slice_credential_string(slice_hrn)
1327         creds = [slice_cred]
1328         result = server.Shutdown(slice_urn, creds)
1329         value = ReturnValue.get_value(result)
1330         if self.options.raw:
1331             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1332         else:
1333             print value
1334         return value         
1335     
1336
1337     @register_command("slice_hrn rspec","")
1338     def get_ticket(self, options, args):
1339         """
1340         get a ticket for the specified slice
1341         """
1342         server = self.sliceapi()
1343         # slice urn
1344         slice_hrn, rspec_path = args[0], args[1]
1345         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1346         # creds
1347         slice_cred = self.slice_credential_string(slice_hrn)
1348         creds = [slice_cred]
1349         # rspec
1350         rspec_file = self.get_rspec_file(rspec_path) 
1351         rspec = open(rspec_file).read()
1352         # options and call_id when supported
1353         api_options = {}
1354         api_options['call_id']=unique_call_id()
1355         # get ticket at the server
1356         ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1357         # save
1358         file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1359         self.logger.info("writing ticket to %s"%file)
1360         ticket = SfaTicket(string=ticket_string)
1361         ticket.save_to_file(filename=file, save_parents=True)
1362
1363     @register_command("ticket","")
1364     def redeem_ticket(self, options, args):
1365         """
1366         Connects to nodes in a slice and redeems a ticket
1367   (slice hrn is retrieved from the ticket)
1368         """
1369         ticket_file = args[0]
1370         
1371         # get slice hrn from the ticket
1372         # use this to get the right slice credential 
1373         ticket = SfaTicket(filename=ticket_file)
1374         ticket.decode()
1375         ticket_string = ticket.save_to_string(save_parents=True)
1376
1377         slice_hrn = ticket.gidObject.get_hrn()
1378         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1379         #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1380         slice_cred = self.slice_credential_string(slice_hrn)
1381         
1382         # get a list of node hostnames from the RSpec 
1383         tree = etree.parse(StringIO(ticket.rspec))
1384         root = tree.getroot()
1385         hostnames = root.xpath("./network/site/node/hostname/text()")
1386         
1387         # create an xmlrpc connection to the component manager at each of these
1388         # components and gall redeem_ticket
1389         connections = {}
1390         for hostname in hostnames:
1391             try:
1392                 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1393                 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1394                 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1395                 server = self.server_proxy(hostname, CM_PORT, self.private_key, 
1396                                            timeout=self.options.timeout, verbose=self.options.debug)
1397                 server.RedeemTicket(ticket_string, slice_cred)
1398                 self.logger.info("Success")
1399             except socket.gaierror:
1400                 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1401             except Exception, e:
1402                 self.logger.log_exc(e.message)
1403         return
1404
1405     @register_command("[name]","")
1406     def gid(self, options, args):
1407         """
1408         Create a GID (CreateGid)
1409         """
1410         if len(args) < 1:
1411             self.print_help()
1412             sys.exit(1)
1413         target_hrn = args[0]
1414         my_gid_string = open(self.client_bootstrap.my_gid()).read() 
1415         gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1416         if options.file:
1417             filename = options.file
1418         else:
1419             filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1420         self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1421         GID(string=gid).save_to_file(filename)
1422          
1423     ####################
1424     @register_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1425
1426   will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1427   the set of credentials in the scope for this call would be
1428   (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1429       as per -u/--user
1430   (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1431       as per -p/--pi
1432   (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1433   (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1434       because of the two -s options
1435
1436 """)
1437     def delegate (self, options, args):
1438         """
1439         (locally) create delegate credential for use by given hrn
1440   make sure to check for 'sfi myslice' instead if you plan
1441   on using MySlice
1442         """
1443         if len(args) != 1:
1444             self.print_help()
1445             sys.exit(1)
1446         to_hrn = args[0]
1447         # support for several delegations in the same call
1448         # so first we gather the things to do
1449         tuples=[]
1450         for slice_hrn in options.delegate_slices:
1451             message="%s.slice"%slice_hrn
1452             original = self.slice_credential_string(slice_hrn)
1453             tuples.append ( (message, original,) )
1454         if options.delegate_pi:
1455             my_authority=self.authority
1456             message="%s.pi"%my_authority
1457             original = self.my_authority_credential_string()
1458             tuples.append ( (message, original,) )
1459         for auth_hrn in options.delegate_auths:
1460             message="%s.auth"%auth_hrn
1461             original=self.authority_credential_string(auth_hrn)
1462             tuples.append ( (message, original, ) )
1463         # if nothing was specified at all at this point, let's assume -u
1464         if not tuples: options.delegate_user=True
1465         # this user cred
1466         if options.delegate_user:
1467             message="%s.user"%self.user
1468             original = self.my_credential_string
1469             tuples.append ( (message, original, ) )
1470
1471         # default type for beneficial is user unless -A
1472         if options.delegate_to_authority:       to_type='authority'
1473         else:                                   to_type='user'
1474
1475         # let's now handle all this
1476         # it's all in the filenaming scheme
1477         for (message,original) in tuples:
1478             delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1479             delegated_credential = Credential (string=delegated_string)
1480             filename = os.path.join ( self.options.sfi_dir,
1481                                       "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1482             delegated_credential.save_to_file(filename, save_parents=True)
1483             self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1484     
1485     ####################
1486     @register_command("","""$ less +/myslice sfi_config
1487 [myslice]
1488 backend  = http://manifold.pl.sophia.inria.fr:7080
1489 # the HRN that myslice uses, so that we are delegating to
1490 delegate = ple.upmc.slicebrowser
1491 # platform - this is a myslice concept
1492 platform = ple
1493 # username - as of this writing (May 2013) a simple login name
1494 username = thierry
1495
1496 $ sfi myslice
1497   will first collect the slices that you are part of, then make sure
1498   all your credentials are up-to-date (read: refresh expired ones)
1499   then compute delegated credentials for user 'ple.upmc.slicebrowser'
1500   and upload them all on myslice backend, using 'platform' and 'user'.
1501   A password will be prompted for the upload part.
1502
1503 $ sfi -v myslice  -- or sfi -vv myslice
1504   same but with more and more verbosity
1505
1506 $ sfi m -b http://mymanifold.foo.com:7080/
1507   is synonym to sfi myslice as no other command starts with an 'm'
1508   and uses a custom backend for this one call
1509 """
1510 ) # register_command
1511     def myslice (self, options, args):
1512
1513         """ This helper is for refreshing your credentials at myslice; it will
1514   * compute all the slices that you currently have credentials on
1515   * refresh all your credentials (you as a user and pi, your slices)
1516   * upload them to the manifold backend server
1517   for last phase, sfi_config is read to look for the [myslice] section, 
1518   and namely the 'backend', 'delegate' and 'user' settings"""
1519
1520         ##########
1521         if len(args)>0:
1522             self.print_help()
1523             sys.exit(1)
1524         # enable info by default
1525         self.logger.setLevelFromOptVerbose(self.options.verbose+1)
1526         ### the rough sketch goes like this
1527         # (0) produce a p12 file
1528         self.client_bootstrap.my_pkcs12()
1529
1530         # (a) rain check for sufficient config in sfi_config
1531         myslice_dict={}
1532         myslice_keys=[ 'backend', 'delegate', 'platform', 'username']
1533         for key in myslice_keys:
1534             value=None
1535             # oct 2013 - I'm finding myself juggling with config files
1536             # so a couple of command-line options can now override config
1537             if hasattr(options,key) and getattr(options,key) is not None:
1538                 value=getattr(options,key)
1539             else:
1540                 full_key="MYSLICE_" + key.upper()
1541                 value=getattr(self.config_instance,full_key,None)
1542             if value:   myslice_dict[key]=value
1543             else:       print "Unsufficient config, missing key %s in [myslice] section of sfi_config"%key
1544         if len(myslice_dict) != len(myslice_keys):
1545             sys.exit(1)
1546
1547         # (b) figure whether we are PI for the authority where we belong
1548         self.logger.info("Resolving our own id %s"%self.user)
1549         my_records=self.registry().Resolve(self.user,self.my_credential_string)
1550         if len(my_records)!=1: print "Cannot Resolve %s -- exiting"%self.user; sys.exit(1)
1551         my_record=my_records[0]
1552         my_auths_all = my_record['reg-pi-authorities']
1553         self.logger.info("Found %d authorities that we are PI for"%len(my_auths_all))
1554         self.logger.debug("They are %s"%(my_auths_all))
1555         
1556         my_auths = my_auths_all
1557         if options.delegate_auths:
1558             my_auths = list(set(my_auths_all).intersection(set(options.delegate_auths)))
1559             self.logger.debug("Restricted to user-provided auths"%(my_auths))
1560
1561         # (c) get the set of slices that we are in
1562         my_slices_all=my_record['reg-slices']
1563         self.logger.info("Found %d slices that we are member of"%len(my_slices_all))
1564         self.logger.debug("They are: %s"%(my_slices_all))
1565  
1566         my_slices = my_slices_all
1567         # if user provided slices, deal only with these - if they are found
1568         if options.delegate_slices:
1569             my_slices = list(set(my_slices_all).intersection(set(options.delegate_slices)))
1570             self.logger.debug("Restricted to user-provided slices: %s"%(my_slices))
1571
1572         # (d) make sure we have *valid* credentials for all these
1573         hrn_credentials=[]
1574         hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1575         for auth_hrn in my_auths:
1576             hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1577         for slice_hrn in my_slices:
1578             hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1579
1580         # (e) check for the delegated version of these
1581         # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever 
1582         # switch to myslice using an authority instead of a user
1583         delegatee_type='user'
1584         delegatee_hrn=myslice_dict['delegate']
1585         hrn_delegated_credentials = []
1586         for (hrn, htype, credential) in hrn_credentials:
1587             delegated_credential = self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type)
1588             # save these so user can monitor what she's uploaded
1589             filename = os.path.join ( self.options.sfi_dir,
1590                                       "%s.%s_for_%s.%s.cred"%(hrn,htype,delegatee_hrn,delegatee_type))
1591             with file(filename,'w') as f:
1592                 f.write(delegated_credential)
1593             self.logger.debug("(Over)wrote %s"%filename)
1594             hrn_delegated_credentials.append ((hrn, htype, delegated_credential, filename, ))
1595
1596         # (f) and finally upload them to manifold server
1597         # xxx todo add an option so the password can be set on the command line
1598         # (but *NOT* in the config file) so other apps can leverage this
1599         self.logger.info("Uploading on backend at %s"%myslice_dict['backend'])
1600         uploader = ManifoldUploader (logger=self.logger,
1601                                      url=myslice_dict['backend'],
1602                                      platform=myslice_dict['platform'],
1603                                      username=myslice_dict['username'],
1604                                      password=options.password)
1605         uploader.prompt_all()
1606         (count_all,count_success)=(0,0)
1607         for (hrn,htype,delegated_credential,filename) in hrn_delegated_credentials:
1608             # inspect
1609             inspect=Credential(string=delegated_credential)
1610             expire_datetime=inspect.get_expiration()
1611             message="%s (%s) [exp:%s]"%(hrn,htype,expire_datetime)
1612             if uploader.upload(delegated_credential,message=message):
1613                 count_success+=1
1614             count_all+=1
1615         self.logger.info("Successfully uploaded %d/%d credentials"%(count_success,count_all))
1616
1617         # at first I thought we would want to save these,
1618         # like 'sfi delegate does' but on second thought
1619         # it is probably not helpful as people would not
1620         # need to run 'sfi delegate' at all anymore
1621         if count_success != count_all: sys.exit(1)
1622         return
1623
1624     @register_command("cred","")
1625     def trusted(self, options, args):
1626         """
1627         return the trusted certs at this interface (get_trusted_certs)
1628         """ 
1629         if options.registry-interface:
1630             server=self.registry()
1631         else:
1632             server = self.sliceapi()
1633         cred = self.my_authority_credential_string()
1634         trusted_certs = server.get_trusted_certs(cred)
1635         if not options.registry-interface:
1636             trusted_certs = ReturnValue.get_value(trusted_certs)
1637
1638         for trusted_cert in trusted_certs:
1639              print "\n===========================================================\n"
1640              gid = GID(string=trusted_cert)
1641              gid.dump()
1642              cert = Certificate(string=trusted_cert)
1643              self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1644              print "Certificate:\n%s\n\n"%trusted_cert
1645
1646         return