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