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