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