no need to convert rspec in sfi
[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("-R","--registry-version",
383                               action="store_true", dest="version_registry", default=False,
384                               help="probe registry version instead of sliceapi")
385             parser.add_option("-l","--local",
386                               action="store_true", dest="version_local", default=False,
387                               help="display version of the local client")
388
389         if command in ("add", "update"):
390             parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
391             parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
392             parser.add_option('-e', '--email', dest='email', default="",  help="email (mandatory for users)") 
393             parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file', 
394                               default=None)
395             parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
396                               default='', type="str", action='callback', callback=optparse_listvalue_callback)
397             parser.add_option('-r', '--researchers', dest='researchers', metavar='<researchers>', 
398                               help='Set/replace slice researchers', default='', type="str", action='callback', 
399                               callback=optparse_listvalue_callback)
400             parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
401                               default='', type="str", action='callback', callback=optparse_listvalue_callback)
402             parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
403                                action="callback", callback=optparse_dictvalue_callback, nargs=1,
404                                help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
405
406         # show_credential option
407         if command in ("list","resources","create","add","update","remove","slices","delete","status","renew"):
408             parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
409                               help="show credential(s) used in human-readable form")
410         # registy filter option
411         if command in ("list", "show", "remove"):
412             parser.add_option("-t", "--type", dest="type", type="choice",
413                             help="type filter ([all]|user|slice|authority|node|aggregate)",
414                             choices=("all", "user", "slice", "authority", "node", "aggregate"),
415                             default="all")
416         if command in ("show"):
417             parser.add_option("-k","--key",dest="keys",action="append",default=[],
418                               help="specify specific keys to be displayed from record")
419         if command in ("resources"):
420             # rspec version
421             parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
422                               help="schema type and version of resulting RSpec")
423             # disable/enable cached rspecs
424             parser.add_option("-c", "--current", dest="current", default=False,
425                               action="store_true",  
426                               help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
427             # display formats
428             parser.add_option("-f", "--format", dest="format", type="choice",
429                              help="display format ([xml]|dns|ip)", default="xml",
430                              choices=("xml", "dns", "ip"))
431             #panos: a new option to define the type of information about resources a user is interested in
432             parser.add_option("-i", "--info", dest="info",
433                                 help="optional component information", default=None)
434             # a new option to retreive or not reservation-oriented RSpecs (leases)
435             parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
436                                 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
437                                 choices=("all", "resources", "leases"), default="resources")
438
439
440         # 'create' does return the new rspec, makes sense to save that too
441         if command in ("resources", "show", "list", "gid", 'create'):
442            parser.add_option("-o", "--output", dest="file",
443                             help="output XML to file", metavar="FILE", default=None)
444
445         if command in ("show", "list"):
446            parser.add_option("-f", "--format", dest="format", type="choice",
447                              help="display format ([text]|xml)", default="text",
448                              choices=("text", "xml"))
449
450            parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
451                              help="output file format ([xml]|xmllist|hrnlist)", default="xml",
452                              choices=("xml", "xmllist", "hrnlist"))
453         if command == 'list':
454            parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
455                              help="list all child records", default=False)
456            parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
457                              help="gives details, like user keys", default=False)
458         if command in ("delegate"):
459            parser.add_option("-u", "--user",
460                              action="store_true", dest="delegate_user", default=False,
461                              help="delegate your own credentials; default if no other option is provided")
462            parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
463                              metavar="slice_hrn", help="delegate cred. for slice HRN")
464            parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
465                              metavar='auth_hrn', help="delegate cred for auth HRN")
466            # this primarily is a shorthand for -a my_hrn
467            parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
468                              help="delegate your PI credentials, so s.t. like -a your_hrn^")
469            parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
470                              help="""by default the mandatory argument is expected to be a user, 
471 use this if you mean an authority instead""")
472
473         if command in ("myslice"):
474             parser.add_option("-p","--password",dest='password',action='store',default=None,
475                               help="specify mainfold password on the command line")
476             parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
477                              metavar="slice_hrn", help="delegate cred. for slice HRN")
478             parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
479                              metavar='auth_hrn', help="delegate PI cred for auth HRN")
480         
481         return parser
482
483         
484     #
485     # Main: parse arguments and dispatch to command
486     #
487     def dispatch(self, command, command_options, command_args):
488         method=getattr(self, command, None)
489         if not method:
490             print "Unknown command %s"%command
491             return
492         return method(command_options, command_args)
493
494     def main(self):
495         self.sfi_parser = self.create_parser_global()
496         (options, args) = self.sfi_parser.parse_args()
497         if options.help: 
498             self.print_commands_help(options)
499             sys.exit(1)
500         self.options = options
501
502         self.logger.setLevelFromOptVerbose(self.options.verbose)
503
504         if len(args) <= 0:
505             self.logger.critical("No command given. Use -h for help.")
506             self.print_commands_help(options)
507             return -1
508     
509         # complete / find unique match with command set
510         command_candidates = Candidates (commands_list)
511         input = args[0]
512         command = command_candidates.only_match(input)
513         if not command:
514             self.print_commands_help(options)
515             sys.exit(1)
516         # second pass options parsing
517         self.command=command
518         self.command_parser = self.create_parser_command(command)
519         (command_options, command_args) = self.command_parser.parse_args(args[1:])
520         if command_options.help:
521             self.print_help()
522             sys.exit(1)
523         self.command_options = command_options
524
525         self.read_config () 
526         self.bootstrap ()
527         self.logger.debug("Command=%s" % self.command)
528
529         try:
530             self.dispatch(command, command_options, command_args)
531         except SystemExit:
532             return 1
533         except:
534             self.logger.log_exc ("sfi command %s failed"%command)
535             return 1
536
537         return 0
538     
539     ####################
540     def read_config(self):
541         config_file = os.path.join(self.options.sfi_dir,"sfi_config")
542         shell_config_file  = os.path.join(self.options.sfi_dir,"sfi_config.sh")
543         try:
544             if Config.is_ini(config_file):
545                 config = Config (config_file)
546             else:
547                 # try upgrading from shell config format
548                 fp, fn = mkstemp(suffix='sfi_config', text=True)  
549                 config = Config(fn)
550                 # we need to preload the sections we want parsed 
551                 # from the shell config
552                 config.add_section('sfi')
553                 # sface users should be able to use this same file to configure their stuff
554                 config.add_section('sface')
555                 # manifold users should be able to specify the details 
556                 # of their backend server here for 'sfi myslice'
557                 config.add_section('myslice')
558                 config.load(config_file)
559                 # back up old config
560                 shutil.move(config_file, shell_config_file)
561                 # write new config
562                 config.save(config_file)
563                  
564         except:
565             self.logger.critical("Failed to read configuration file %s"%config_file)
566             self.logger.info("Make sure to remove the export clauses and to add quotes")
567             if self.options.verbose==0:
568                 self.logger.info("Re-run with -v for more details")
569             else:
570                 self.logger.log_exc("Could not read config file %s"%config_file)
571             sys.exit(1)
572      
573         self.config_instance=config
574         errors = 0
575         # Set SliceMgr URL
576         if (self.options.sm is not None):
577            self.sm_url = self.options.sm
578         elif hasattr(config, "SFI_SM"):
579            self.sm_url = config.SFI_SM
580         else:
581            self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
582            errors += 1 
583
584         # Set Registry URL
585         if (self.options.registry is not None):
586            self.reg_url = self.options.registry
587         elif hasattr(config, "SFI_REGISTRY"):
588            self.reg_url = config.SFI_REGISTRY
589         else:
590            self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
591            errors += 1 
592
593         # Set user HRN
594         if (self.options.user is not None):
595            self.user = self.options.user
596         elif hasattr(config, "SFI_USER"):
597            self.user = config.SFI_USER
598         else:
599            self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
600            errors += 1 
601
602         # Set authority HRN
603         if (self.options.auth is not None):
604            self.authority = self.options.auth
605         elif hasattr(config, "SFI_AUTH"):
606            self.authority = config.SFI_AUTH
607         else:
608            self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
609            errors += 1 
610
611         self.config_file=config_file
612         if errors:
613            sys.exit(1)
614
615     #
616     # Get various credential and spec files
617     #
618     # Establishes limiting conventions
619     #   - conflates MAs and SAs
620     #   - assumes last token in slice name is unique
621     #
622     # Bootstraps credentials
623     #   - bootstrap user credential from self-signed certificate
624     #   - bootstrap authority credential from user credential
625     #   - bootstrap slice credential from user credential
626     #
627     
628     # init self-signed cert, user credentials and gid
629     def bootstrap (self):
630         client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
631                                                logger=self.logger)
632         # if -k is provided, use this to initialize private key
633         if self.options.user_private_key:
634             client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
635         else:
636             # trigger legacy compat code if needed 
637             # the name has changed from just <leaf>.pkey to <hrn>.pkey
638             if not os.path.isfile(client_bootstrap.private_key_filename()):
639                 self.logger.info ("private key not found, trying legacy name")
640                 try:
641                     legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
642                     self.logger.debug("legacy_private_key=%s"%legacy_private_key)
643                     client_bootstrap.init_private_key_if_missing (legacy_private_key)
644                     self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
645                 except:
646                     self.logger.log_exc("Can't find private key ")
647                     sys.exit(1)
648             
649         # make it bootstrap
650         client_bootstrap.bootstrap_my_gid()
651         # extract what's needed
652         self.private_key = client_bootstrap.private_key()
653         self.my_credential_string = client_bootstrap.my_credential_string ()
654         self.my_gid = client_bootstrap.my_gid ()
655         self.client_bootstrap = client_bootstrap
656
657
658     def my_authority_credential_string(self):
659         if not self.authority:
660             self.logger.critical("no authority specified. Use -a or set SF_AUTH")
661             sys.exit(-1)
662         return self.client_bootstrap.authority_credential_string (self.authority)
663
664     def authority_credential_string(self, auth_hrn):
665         return self.client_bootstrap.authority_credential_string (auth_hrn)
666
667     def slice_credential_string(self, name):
668         return self.client_bootstrap.slice_credential_string (name)
669
670     #
671     # Management of the servers
672     # 
673
674     def registry (self):
675         # cache the result
676         if not hasattr (self, 'registry_proxy'):
677             self.logger.info("Contacting Registry at: %s"%self.reg_url)
678             self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid, 
679                                                  timeout=self.options.timeout, verbose=self.options.debug)  
680         return self.registry_proxy
681
682     def sliceapi (self):
683         # cache the result
684         if not hasattr (self, 'sliceapi_proxy'):
685             # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
686             if hasattr(self.command_options,'component') and self.command_options.component:
687                 # resolve the hrn at the registry
688                 node_hrn = self.command_options.component
689                 records = self.registry().Resolve(node_hrn, self.my_credential_string)
690                 records = filter_records('node', records)
691                 if not records:
692                     self.logger.warning("No such component:%r"% opts.component)
693                 record = records[0]
694                 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
695                 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
696             else:
697                 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
698                 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
699                     self.sm_url = 'http://' + self.sm_url
700                 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
701                 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid, 
702                                                      timeout=self.options.timeout, verbose=self.options.debug)  
703         return self.sliceapi_proxy
704
705     def get_cached_server_version(self, server):
706         # check local cache first
707         cache = None
708         version = None 
709         cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
710         cache_key = server.url + "-version"
711         try:
712             cache = Cache(cache_file)
713         except IOError:
714             cache = Cache()
715             self.logger.info("Local cache not found at: %s" % cache_file)
716
717         if cache:
718             version = cache.get(cache_key)
719
720         if not version: 
721             result = server.GetVersion()
722             version= ReturnValue.get_value(result)
723             # cache version for 20 minutes
724             cache.add(cache_key, version, ttl= 60*20)
725             self.logger.info("Updating cache file %s" % cache_file)
726             cache.save_to_file(cache_file)
727
728         return version   
729         
730     ### resurrect this temporarily so we can support V1 aggregates for a while
731     def server_supports_options_arg(self, server):
732         """
733         Returns true if server support the optional call_id arg, false otherwise. 
734         """
735         server_version = self.get_cached_server_version(server)
736         result = False
737         # xxx need to rewrite this 
738         if int(server_version.get('geni_api')) >= 2:
739             result = True
740         return result
741
742     def server_supports_call_id_arg(self, server):
743         server_version = self.get_cached_server_version(server)
744         result = False      
745         if 'sfa' in server_version and 'code_tag' in server_version:
746             code_tag = server_version['code_tag']
747             code_tag_parts = code_tag.split("-")
748             version_parts = code_tag_parts[0].split(".")
749             major, minor = version_parts[0], version_parts[1]
750             rev = code_tag_parts[1]
751             if int(major) == 1 and minor == 0 and build >= 22:
752                 result = True
753         return result                 
754
755     ### ois = options if supported
756     # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
757     def ois (self, server, option_dict):
758         if self.server_supports_options_arg (server): 
759             return [option_dict]
760         elif self.server_supports_call_id_arg (server):
761             return [ unique_call_id () ]
762         else: 
763             return []
764
765     ### cis = call_id if supported - like ois
766     def cis (self, server):
767         if self.server_supports_call_id_arg (server):
768             return [ unique_call_id ]
769         else:
770             return []
771
772     ######################################## miscell utilities
773     def get_rspec_file(self, rspec):
774        if (os.path.isabs(rspec)):
775           file = rspec
776        else:
777           file = os.path.join(self.options.sfi_dir, rspec)
778        if (os.path.isfile(file)):
779           return file
780        else:
781           self.logger.critical("No such rspec file %s"%rspec)
782           sys.exit(1)
783     
784     def get_record_file(self, record):
785        if (os.path.isabs(record)):
786           file = record
787        else:
788           file = os.path.join(self.options.sfi_dir, record)
789        if (os.path.isfile(file)):
790           return file
791        else:
792           self.logger.critical("No such registry record file %s"%record)
793           sys.exit(1)
794
795
796     #==========================================================================
797     # Following functions implement the commands
798     #
799     # Registry-related commands
800     #==========================================================================
801
802     @register_command("","")
803     def config (self, options, args):
804         "Display contents of current config"
805         print "# From configuration file %s"%self.config_file
806         flags=[ ('sfi', [ ('registry','reg_url'),
807                           ('auth','authority'),
808                           ('user','user'),
809                           ('sm','sm_url'),
810                           ]),
811                 ]
812         if options.myslice:
813             flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
814
815         for (section, tuples) in flags:
816             print "[%s]"%section
817             try:
818                 for (external_name, internal_name) in tuples:
819                     print "%-20s = %s"%(external_name,getattr(self,internal_name))
820             except:
821                 for name in tuples:
822                     varname="%s_%s"%(section.upper(),name.upper())
823                     value=getattr(self.config_instance,varname)
824                     print "%-20s = %s"%(name,value)
825
826     @register_command("","")
827     def version(self, options, args):
828         """
829         display an SFA server version (GetVersion)
830   or version information about sfi itself
831         """
832         if options.version_local:
833             version=version_core()
834         else:
835             if options.version_registry:
836                 server=self.registry()
837             else:
838                 server = self.sliceapi()
839             result = server.GetVersion()
840             version = ReturnValue.get_value(result)
841         if self.options.raw:
842             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
843         else:
844             pprinter = PrettyPrinter(indent=4)
845             pprinter.pprint(version)
846
847     @register_command("authority","")
848     def list(self, options, args):
849         """
850         list entries in named authority registry (List)
851         """
852         if len(args)!= 1:
853             self.print_help()
854             sys.exit(1)
855         hrn = args[0]
856         opts = {}
857         if options.recursive:
858             opts['recursive'] = options.recursive
859         
860         if options.show_credential:
861             show_credentials(self.my_credential_string)
862         try:
863             list = self.registry().List(hrn, self.my_credential_string, options)
864         except IndexError:
865             raise Exception, "Not enough parameters for the 'list' command"
866
867         # filter on person, slice, site, node, etc.
868         # This really should be in the self.filter_records funct def comment...
869         list = filter_records(options.type, list)
870         terminal_render (list, options)
871         if options.file:
872             save_records_to_file(options.file, list, options.fileformat)
873         return
874     
875     @register_command("name","")
876     def show(self, options, args):
877         """
878         show details about named registry record (Resolve)
879         """
880         if len(args)!= 1:
881             self.print_help()
882             sys.exit(1)
883         hrn = args[0]
884         # explicitly require Resolve to run in details mode
885         record_dicts = self.registry().Resolve(hrn, self.my_credential_string, {'details':True})
886         record_dicts = filter_records(options.type, record_dicts)
887         if not record_dicts:
888             self.logger.error("No record of type %s"% options.type)
889             return
890         # user has required to focus on some keys
891         if options.keys:
892             def project (record):
893                 projected={}
894                 for key in options.keys:
895                     try: projected[key]=record[key]
896                     except: pass
897                 return projected
898             record_dicts = [ project (record) for record in record_dicts ]
899         records = [ Record(dict=record_dict) for record_dict in record_dicts ]
900         for record in records:
901             if (options.format == "text"):      record.dump(sort=True)  
902             else:                               print record.save_as_xml() 
903         if options.file:
904             save_records_to_file(options.file, record_dicts, options.fileformat)
905         return
906     
907     @register_command("[xml-filename]","")
908     def add(self, options, args):
909         """add record into registry (Register) 
910   from command line options (recommended) 
911   old-school method involving an xml file still supported"""
912
913         auth_cred = self.my_authority_credential_string()
914         if options.show_credential:
915             show_credentials(auth_cred)
916         record_dict = {}
917         if len(args) > 1:
918             self.print_help()
919             sys.exit(1)
920         if len(args)==1:
921             try:
922                 record_filepath = args[0]
923                 rec_file = self.get_record_file(record_filepath)
924                 record_dict.update(load_record_from_file(rec_file).todict())
925             except:
926                 print "Cannot load record file %s"%record_filepath
927                 sys.exit(1)
928         if options:
929             record_dict.update(load_record_from_opts(options).todict())
930         # we should have a type by now
931         if 'type' not in record_dict :
932             self.print_help()
933             sys.exit(1)
934         # this is still planetlab dependent.. as plc will whine without that
935         # also, it's only for adding
936         if record_dict['type'] == 'user':
937             if not 'first_name' in record_dict:
938                 record_dict['first_name'] = record_dict['hrn']
939             if 'last_name' not in record_dict:
940                 record_dict['last_name'] = record_dict['hrn'] 
941         return self.registry().Register(record_dict, auth_cred)
942     
943     @register_command("[xml-filename]","")
944     def update(self, options, args):
945         """update record into registry (Update) 
946   from command line options (recommended) 
947   old-school method involving an xml file still supported"""
948         record_dict = {}
949         if len(args) > 0:
950             record_filepath = args[0]
951             rec_file = self.get_record_file(record_filepath)
952             record_dict.update(load_record_from_file(rec_file).todict())
953         if options:
954             record_dict.update(load_record_from_opts(options).todict())
955         # at the very least we need 'type' here
956         if 'type' not in record_dict:
957             self.print_help()
958             sys.exit(1)
959
960         # don't translate into an object, as this would possibly distort
961         # user-provided data; e.g. add an 'email' field to Users
962         if record_dict['type'] == "user":
963             if record_dict['hrn'] == self.user:
964                 cred = self.my_credential_string
965             else:
966                 cred = self.my_authority_credential_string()
967         elif record_dict['type'] in ["slice"]:
968             try:
969                 cred = self.slice_credential_string(record_dict['hrn'])
970             except ServerException, e:
971                # XXX smbaker -- once we have better error return codes, update this
972                # to do something better than a string compare
973                if "Permission error" in e.args[0]:
974                    cred = self.my_authority_credential_string()
975                else:
976                    raise
977         elif record_dict['type'] in ["authority"]:
978             cred = self.my_authority_credential_string()
979         elif record_dict['type'] == 'node':
980             cred = self.my_authority_credential_string()
981         else:
982             raise "unknown record type" + record_dict['type']
983         if options.show_credential:
984             show_credentials(cred)
985         return self.registry().Update(record_dict, cred)
986   
987     @register_command("hrn","")
988     def remove(self, options, args):
989         "remove registry record by name (Remove)"
990         auth_cred = self.my_authority_credential_string()
991         if len(args)!=1:
992             self.print_help()
993             sys.exit(1)
994         hrn = args[0]
995         type = options.type 
996         if type in ['all']:
997             type = '*'
998         if options.show_credential:
999             show_credentials(auth_cred)
1000         return self.registry().Remove(hrn, auth_cred, type)
1001     
1002     # ==================================================================
1003     # Slice-related commands
1004     # ==================================================================
1005
1006     @register_command("","")
1007     def slices(self, options, args):
1008         "list instantiated slices (ListSlices) - returns urn's"
1009         server = self.sliceapi()
1010         # creds
1011         creds = [self.my_credential_string]
1012         # options and call_id when supported
1013         api_options = {}
1014         api_options['call_id']=unique_call_id()
1015         if options.show_credential:
1016             show_credentials(creds)
1017         result = server.ListSlices(creds, *self.ois(server,api_options))
1018         value = ReturnValue.get_value(result)
1019         if self.options.raw:
1020             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1021         else:
1022             display_list(value)
1023         return
1024
1025     # show rspec for named slice
1026     @register_command("[slice_hrn]","")
1027     def resources(self, options, args):
1028         """
1029         with no arg, discover available resources, (ListResources)
1030   or with an slice hrn, shows currently provisioned resources
1031         """
1032         server = self.sliceapi()
1033
1034         # set creds
1035         creds = []
1036         if args:
1037             the_credential=self.slice_credential_string(args[0])
1038             creds.append(the_credential)
1039         else:
1040             the_credential=self.my_credential_string
1041             creds.append(the_credential)
1042         if options.show_credential:
1043             show_credentials(creds)
1044
1045         # no need to check if server accepts the options argument since the options has
1046         # been a required argument since v1 API
1047         api_options = {}
1048         # always send call_id to v2 servers
1049         api_options ['call_id'] = unique_call_id()
1050         # ask for cached value if available
1051         api_options ['cached'] = True
1052         if args:
1053             hrn = args[0]
1054             api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
1055         if options.info:
1056             api_options['info'] = options.info
1057         if options.list_leases:
1058             api_options['list_leases'] = options.list_leases
1059         if options.current:
1060             if options.current == True:
1061                 api_options['cached'] = False
1062             else:
1063                 api_options['cached'] = True
1064         if options.rspec_version:
1065             version_manager = VersionManager()
1066             server_version = self.get_cached_server_version(server)
1067             if 'sfa' in server_version:
1068                 # just request the version the client wants
1069                 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1070             else:
1071                 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1072         else:
1073             api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1074         result = server.ListResources (creds, api_options)
1075         value = ReturnValue.get_value(result)
1076         if self.options.raw:
1077             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1078         if options.file is not None:
1079             save_rspec_to_file(value, options.file)
1080         if (self.options.raw is None) and (options.file is None):
1081             display_rspec(value, options.format)
1082
1083         return
1084
1085     @register_command("slice_hrn rspec","")
1086     def create(self, options, args):
1087         """
1088         create or update named slice with given rspec (CreateSliver)
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     @register_command("slice_hrn","")
1164     def delete(self, options, args):
1165         """
1166         delete named slice (DeleteSliver)
1167         """
1168         server = self.sliceapi()
1169
1170         # slice urn
1171         slice_hrn = args[0]
1172         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1173
1174         # creds
1175         slice_cred = self.slice_credential_string(slice_hrn)
1176         creds = [slice_cred]
1177         
1178         # options and call_id when supported
1179         api_options = {}
1180         api_options ['call_id'] = unique_call_id()
1181         if options.show_credential:
1182             show_credentials(creds)
1183         result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
1184         value = ReturnValue.get_value(result)
1185         if self.options.raw:
1186             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1187         else:
1188             print value
1189         return value 
1190   
1191     @register_command("slice_hrn","")
1192     def status(self, options, args):
1193         """
1194         retrieve slice status (SliverStatus)
1195         """
1196         server = self.sliceapi()
1197
1198         # slice urn
1199         slice_hrn = args[0]
1200         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1201
1202         # creds 
1203         slice_cred = self.slice_credential_string(slice_hrn)
1204         creds = [slice_cred]
1205
1206         # options and call_id when supported
1207         api_options = {}
1208         api_options['call_id']=unique_call_id()
1209         if options.show_credential:
1210             show_credentials(creds)
1211         result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
1212         value = ReturnValue.get_value(result)
1213         if self.options.raw:
1214             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1215         else:
1216             print value
1217
1218     @register_command("slice_hrn","")
1219     def start(self, options, args):
1220         """
1221         start named slice (Start)
1222         """
1223         server = self.sliceapi()
1224
1225         # the slice urn
1226         slice_hrn = args[0]
1227         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1228         
1229         # cred
1230         slice_cred = self.slice_credential_string(args[0])
1231         creds = [slice_cred]
1232         # xxx Thierry - does this not need an api_options as well ?
1233         result = server.Start(slice_urn, creds)
1234         value = ReturnValue.get_value(result)
1235         if self.options.raw:
1236             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1237         else:
1238             print value
1239         return value
1240     
1241     @register_command("slice_hrn","")
1242     def stop(self, options, args):
1243         """
1244         stop named slice (Stop)
1245         """
1246         server = self.sliceapi()
1247         # slice urn
1248         slice_hrn = args[0]
1249         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1250         # cred
1251         slice_cred = self.slice_credential_string(args[0])
1252         creds = [slice_cred]
1253         result =  server.Stop(slice_urn, creds)
1254         value = ReturnValue.get_value(result)
1255         if self.options.raw:
1256             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1257         else:
1258             print value
1259         return value
1260     
1261     # reset named slice
1262     @register_command("slice_hrn","")
1263     def reset(self, options, args):
1264         """
1265         reset named slice (reset_slice)
1266         """
1267         server = self.sliceapi()
1268         # slice urn
1269         slice_hrn = args[0]
1270         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1271         # cred
1272         slice_cred = self.slice_credential_string(args[0])
1273         creds = [slice_cred]
1274         result = server.reset_slice(creds, slice_urn)
1275         value = ReturnValue.get_value(result)
1276         if self.options.raw:
1277             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1278         else:
1279             print value
1280         return value
1281
1282     @register_command("slice_hrn time","")
1283     def renew(self, options, args):
1284         """
1285         renew slice (RenewSliver)
1286         """
1287         server = self.sliceapi()
1288         if len(args) != 2:
1289             self.print_help()
1290             sys.exit(1)
1291         [ slice_hrn, input_time ] = args
1292         # slice urn    
1293         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1294         # time: don't try to be smart on the time format, server-side will
1295         # creds
1296         slice_cred = self.slice_credential_string(args[0])
1297         creds = [slice_cred]
1298         # options and call_id when supported
1299         api_options = {}
1300         api_options['call_id']=unique_call_id()
1301         if options.show_credential:
1302             show_credentials(creds)
1303         result =  server.RenewSliver(slice_urn, creds, input_time, *self.ois(server,api_options))
1304         value = ReturnValue.get_value(result)
1305         if self.options.raw:
1306             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1307         else:
1308             print value
1309         return value
1310
1311
1312     @register_command("slice_hrn","")
1313     def shutdown(self, options, args):
1314         """
1315         shutdown named slice (Shutdown)
1316         """
1317         server = self.sliceapi()
1318         # slice urn
1319         slice_hrn = args[0]
1320         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1321         # creds
1322         slice_cred = self.slice_credential_string(slice_hrn)
1323         creds = [slice_cred]
1324         result = server.Shutdown(slice_urn, creds)
1325         value = ReturnValue.get_value(result)
1326         if self.options.raw:
1327             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1328         else:
1329             print value
1330         return value         
1331     
1332
1333     @register_command("slice_hrn rspec","")
1334     def get_ticket(self, options, args):
1335         """
1336         get a ticket for the specified slice
1337         """
1338         server = self.sliceapi()
1339         # slice urn
1340         slice_hrn, rspec_path = args[0], args[1]
1341         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1342         # creds
1343         slice_cred = self.slice_credential_string(slice_hrn)
1344         creds = [slice_cred]
1345         # rspec
1346         rspec_file = self.get_rspec_file(rspec_path) 
1347         rspec = open(rspec_file).read()
1348         # options and call_id when supported
1349         api_options = {}
1350         api_options['call_id']=unique_call_id()
1351         # get ticket at the server
1352         ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1353         # save
1354         file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1355         self.logger.info("writing ticket to %s"%file)
1356         ticket = SfaTicket(string=ticket_string)
1357         ticket.save_to_file(filename=file, save_parents=True)
1358
1359     @register_command("ticket","")
1360     def redeem_ticket(self, options, args):
1361         """
1362         Connects to nodes in a slice and redeems a ticket
1363   (slice hrn is retrieved from the ticket)
1364         """
1365         ticket_file = args[0]
1366         
1367         # get slice hrn from the ticket
1368         # use this to get the right slice credential 
1369         ticket = SfaTicket(filename=ticket_file)
1370         ticket.decode()
1371         ticket_string = ticket.save_to_string(save_parents=True)
1372
1373         slice_hrn = ticket.gidObject.get_hrn()
1374         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1375         #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1376         slice_cred = self.slice_credential_string(slice_hrn)
1377         
1378         # get a list of node hostnames from the RSpec 
1379         tree = etree.parse(StringIO(ticket.rspec))
1380         root = tree.getroot()
1381         hostnames = root.xpath("./network/site/node/hostname/text()")
1382         
1383         # create an xmlrpc connection to the component manager at each of these
1384         # components and gall redeem_ticket
1385         connections = {}
1386         for hostname in hostnames:
1387             try:
1388                 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1389                 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1390                 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1391                 server = self.server_proxy(hostname, CM_PORT, self.private_key, 
1392                                            timeout=self.options.timeout, verbose=self.options.debug)
1393                 server.RedeemTicket(ticket_string, slice_cred)
1394                 self.logger.info("Success")
1395             except socket.gaierror:
1396                 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1397             except Exception, e:
1398                 self.logger.log_exc(e.message)
1399         return
1400
1401     @register_command("[name]","")
1402     def gid(self, options, args):
1403         """
1404         Create a GID (CreateGid)
1405         """
1406         if len(args) < 1:
1407             self.print_help()
1408             sys.exit(1)
1409         target_hrn = args[0]
1410         my_gid_string = open(self.client_bootstrap.my_gid()).read() 
1411         gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1412         if options.file:
1413             filename = options.file
1414         else:
1415             filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1416         self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1417         GID(string=gid).save_to_file(filename)
1418          
1419     ####################
1420     @register_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1421
1422   will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1423   the set of credentials in the scope for this call would be
1424   (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1425       as per -u/--user
1426   (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1427       as per -p/--pi
1428   (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1429   (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1430       because of the two -s options
1431
1432 """)
1433     def delegate (self, options, args):
1434         """
1435         (locally) create delegate credential for use by given hrn
1436   make sure to check for 'sfi myslice' instead if you plan
1437   on using MySlice
1438         """
1439         if len(args) != 1:
1440             self.print_help()
1441             sys.exit(1)
1442         to_hrn = args[0]
1443         # support for several delegations in the same call
1444         # so first we gather the things to do
1445         tuples=[]
1446         for slice_hrn in options.delegate_slices:
1447             message="%s.slice"%slice_hrn
1448             original = self.slice_credential_string(slice_hrn)
1449             tuples.append ( (message, original,) )
1450         if options.delegate_pi:
1451             my_authority=self.authority
1452             message="%s.pi"%my_authority
1453             original = self.my_authority_credential_string()
1454             tuples.append ( (message, original,) )
1455         for auth_hrn in options.delegate_auths:
1456             message="%s.auth"%auth_hrn
1457             original=self.authority_credential_string(auth_hrn)
1458             tuples.append ( (message, original, ) )
1459         # if nothing was specified at all at this point, let's assume -u
1460         if not tuples: options.delegate_user=True
1461         # this user cred
1462         if options.delegate_user:
1463             message="%s.user"%self.user
1464             original = self.my_credential_string
1465             tuples.append ( (message, original, ) )
1466
1467         # default type for beneficial is user unless -A
1468         if options.delegate_to_authority:       to_type='authority'
1469         else:                                   to_type='user'
1470
1471         # let's now handle all this
1472         # it's all in the filenaming scheme
1473         for (message,original) in tuples:
1474             delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1475             delegated_credential = Credential (string=delegated_string)
1476             filename = os.path.join ( self.options.sfi_dir,
1477                                       "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1478             delegated_credential.save_to_file(filename, save_parents=True)
1479             self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1480     
1481     ####################
1482     @register_command("","""$ less +/myslice sfi_config
1483 [myslice]
1484 backend  = http://manifold.pl.sophia.inria.fr:7080
1485 # the HRN that myslice uses, so that we are delegating to
1486 delegate = ple.upmc.slicebrowser
1487 # platform - this is a myslice concept
1488 platform = ple
1489 # username - as of this writing (May 2013) a simple login name
1490 username = thierry
1491
1492 $ sfi myslice
1493   will first collect the slices that you are part of, then make sure
1494   all your credentials are up-to-date (read: refresh expired ones)
1495   then compute delegated credentials for user 'ple.upmc.slicebrowser'
1496   and upload them all on myslice backend, using 'platform' and 'user'.
1497   A password will be prompted for the upload part.
1498
1499 $ sfi -v myslice  -- or sfi -vv myslice
1500   same but with more and more verbosity
1501
1502 $ sfi m
1503   is synonym to sfi myslice as no other command starts with an 'm'
1504 """
1505 ) # register_command
1506     def myslice (self, options, args):
1507
1508         """ This helper is for refreshing your credentials at myslice; it will
1509   * compute all the slices that you currently have credentials on
1510   * refresh all your credentials (you as a user and pi, your slices)
1511   * upload them to the manifold backend server
1512   for last phase, sfi_config is read to look for the [myslice] section, 
1513   and namely the 'backend', 'delegate' and 'user' settings"""
1514
1515         ##########
1516         if len(args)>0:
1517             self.print_help()
1518             sys.exit(1)
1519         ### the rough sketch goes like this
1520         # (a) rain check for sufficient config in sfi_config
1521         # we don't allow to override these settings for now
1522         myslice_dict={}
1523         myslice_keys=['backend', 'delegate', 'platform', 'username']
1524         for key in myslice_keys:
1525             full_key="MYSLICE_" + key.upper()
1526             value=getattr(self.config_instance,full_key,None)
1527             if value:   myslice_dict[key]=value
1528             else:       print "Unsufficient config, missing key %s in [myslice] section of sfi_config"%key
1529         if len(myslice_dict) != len(myslice_keys):
1530             sys.exit(1)
1531
1532         # (b) figure whether we are PI for the authority where we belong
1533         self.logger.info("Resolving our own id")
1534         my_records=self.registry().Resolve(self.user,self.my_credential_string)
1535         if len(my_records)!=1: print "Cannot Resolve %s -- exiting"%self.user; sys.exit(1)
1536         my_record=my_records[0]
1537         my_auths_all = my_record['reg-pi-authorities']
1538         self.logger.info("Found %d authorities that we are PI for"%len(my_auths_all))
1539         self.logger.debug("They are %s"%(my_auths_all))
1540         
1541         my_auths = my_auths_all
1542         if options.delegate_auths:
1543             my_auths = list(set(my_auths_all).intersection(set(options.delegate_auths)))
1544
1545         self.logger.info("Delegate PI creds for authorities: %s"%my_auths        )
1546         # (c) get the set of slices that we are in
1547         my_slices_all=my_record['reg-slices']
1548         self.logger.info("Found %d slices that we are member of"%len(my_slices_all))
1549         self.logger.debug("They are: %s"%(my_slices_all))
1550  
1551         my_slices = my_slices_all
1552         if options.delegate_slices:
1553             my_slices = list(set(my_slices_all).intersection(set(options.delegate_slices)))
1554
1555         self.logger.info("Delegate slice creds for slices: %s"%my_slices)
1556
1557         # (d) make sure we have *valid* credentials for all these
1558         hrn_credentials=[]
1559         hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1560         for auth_hrn in my_auths:
1561             hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1562         for slice_hrn in my_slices:
1563             hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1564
1565         # (e) check for the delegated version of these
1566         # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever 
1567         # switch to myslice using an authority instead of a user
1568         delegatee_type='user'
1569         delegatee_hrn=myslice_dict['delegate']
1570         hrn_delegated_credentials = []
1571         for (hrn, htype, credential) in hrn_credentials:
1572             delegated_credential = self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type)
1573             hrn_delegated_credentials.append ((hrn, htype, delegated_credential, ))
1574
1575         # (f) and finally upload them to manifold server
1576         # xxx todo add an option so the password can be set on the command line
1577         # (but *NOT* in the config file) so other apps can leverage this
1578         uploader = ManifoldUploader (logger=self.logger,
1579                                      url=myslice_dict['backend'],
1580                                      platform=myslice_dict['platform'],
1581                                      username=myslice_dict['username'],
1582                                      password=options.password)
1583         uploader.prompt_all()
1584         (count_all,count_success)=(0,0)
1585         for (hrn,htype,delegated_credential) in hrn_delegated_credentials:
1586             # inspect
1587             inspect=Credential(string=delegated_credential)
1588             expire_datetime=inspect.get_expiration()
1589             message="%s (%s) [exp:%s]"%(hrn,htype,expire_datetime)
1590             if uploader.upload(delegated_credential,message=message):
1591                 count_success+=1
1592             count_all+=1
1593
1594         self.logger.info("Successfully uploaded %d/%d credentials"%(count_success,count_all))
1595         # at first I thought we would want to save these,
1596         # like 'sfi delegate does' but on second thought
1597         # it is probably not helpful as people would not
1598         # need to run 'sfi delegate' at all anymore
1599         if count_success != count_all: sys.exit(1)
1600         return
1601
1602     @register_command("cred","")
1603     def trusted(self, options, args):
1604         """
1605         return the trusted certs at this interface (get_trusted_certs)
1606         """ 
1607         trusted_certs = self.registry().get_trusted_certs()
1608         for trusted_cert in trusted_certs:
1609             print "\n===========================================================\n"
1610             gid = GID(string=trusted_cert)
1611             gid.dump()
1612             cert = Certificate(string=trusted_cert)
1613             self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1614             print "Certificate:\n%s\n\n"%trusted_cert
1615         return 
1616