sfi myslice: user might want specify which authority/slice creds to delegate to mysli...
[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","slices","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.0', 
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.0',
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     @register_command("","")
1051     def slices(self, options, args):
1052         "list instantiated slices (ListSlices) - returns urn's"
1053         server = self.sliceapi()
1054         # creds
1055         creds = [self.my_credential_string]
1056         # options and call_id when supported
1057         api_options = {}
1058         api_options['call_id']=unique_call_id()
1059         if options.show_credential:
1060             show_credentials(creds)
1061         result = server.ListSlices(creds, *self.ois(server,api_options))
1062         value = ReturnValue.get_value(result)
1063         if self.options.raw:
1064             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1065         else:
1066             display_list(value)
1067         return
1068
1069     # show rspec for named slice
1070     @register_command("","")
1071     def resources(self, options, args):
1072         """
1073         discover available resources (ListResources)
1074         """
1075         server = self.sliceapi()
1076
1077         # set creds
1078         creds = [self.my_credential]
1079         if options.delegate:
1080             creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1081         if options.show_credential:
1082             show_credentials(creds)
1083
1084         # no need to check if server accepts the options argument since the options has
1085         # been a required argument since v1 API
1086         api_options = {}
1087         # always send call_id to v2 servers
1088         api_options ['call_id'] = unique_call_id()
1089         # ask for cached value if available
1090         api_options ['cached'] = True
1091         if options.info:
1092             api_options['info'] = options.info
1093         if options.list_leases:
1094             api_options['list_leases'] = options.list_leases
1095         if options.current:
1096             if options.current == True:
1097                 api_options['cached'] = False
1098             else:
1099                 api_options['cached'] = True
1100         if options.rspec_version:
1101             version_manager = VersionManager()
1102             server_version = self.get_cached_server_version(server)
1103             if 'sfa' in server_version:
1104                 # just request the version the client wants
1105                 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1106             else:
1107                 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1108         else:
1109             api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1110         result = server.ListResources (creds, api_options)
1111         value = ReturnValue.get_value(result)
1112         if self.options.raw:
1113             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1114         if options.file is not None:
1115             save_rspec_to_file(value, options.file)
1116         if (self.options.raw is None) and (options.file is None):
1117             display_rspec(value, options.format)
1118
1119         return
1120
1121     @register_command("slice_hrn","")
1122     def describe(self, options, args):
1123         """
1124         shows currently allocated/provisioned resources 
1125         of the named slice or set of slivers (Describe) 
1126         """
1127         server = self.sliceapi()
1128
1129         # set creds
1130         creds = [self.slice_credential(args[0])]
1131         if options.delegate:
1132             creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1133         if options.show_credential:
1134             show_credentials(creds)
1135
1136         api_options = {'call_id': unique_call_id(),
1137                        'cached': True,
1138                        'info': options.info,
1139                        'list_leases': options.list_leases,
1140                        'geni_rspec_version': {'type': 'geni', 'version': '3.0'},
1141                       }
1142         if options.rspec_version:
1143             version_manager = VersionManager()
1144             server_version = self.get_cached_server_version(server)
1145             if 'sfa' in server_version:
1146                 # just request the version the client wants
1147                 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1148             else:
1149                 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1150         urn = Xrn(args[0], type='slice').get_urn()        
1151         result = server.Describe([urn], creds, api_options)
1152         value = ReturnValue.get_value(result)
1153         if self.options.raw:
1154             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1155         if options.file is not None:
1156             save_rspec_to_file(value, options.file)
1157         if (self.options.raw is None) and (options.file is None):
1158             display_rspec(value, options.format)
1159
1160         return 
1161
1162     @register_command("slice_hrn","")
1163     def delete(self, options, args):
1164         """
1165         de-allocate and de-provision all or named slivers of the slice (Delete)
1166         """
1167         server = self.sliceapi()
1168
1169         # slice urn
1170         slice_hrn = args[0]
1171         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1172
1173         # creds
1174         slice_cred = self.slice_credential(slice_hrn)
1175         creds = [slice_cred]
1176         
1177         # options and call_id when supported
1178         api_options = {}
1179         api_options ['call_id'] = unique_call_id()
1180         if options.show_credential:
1181             show_credentials(creds)
1182         result = server.Delete([slice_urn], creds, *self.ois(server, api_options ) )
1183         value = ReturnValue.get_value(result)
1184         if self.options.raw:
1185             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1186         else:
1187             print value
1188         return value
1189
1190     @register_command("slice_hrn rspec","")
1191     def allocate(self, options, args):
1192         """
1193          allocate resources to the named slice (Allocate)
1194         """
1195         server = self.sliceapi()
1196         server_version = self.get_cached_server_version(server)
1197         slice_hrn = args[0]
1198         slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1199
1200         # credentials
1201         creds = [self.slice_credential(slice_hrn)]
1202
1203         delegated_cred = None
1204         if server_version.get('interface') == 'slicemgr':
1205             # delegate our cred to the slice manager
1206             # do not delegate cred to slicemgr...not working at the moment
1207             pass
1208             #if server_version.get('hrn'):
1209             #    delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1210             #elif server_version.get('urn'):
1211             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1212
1213         if options.show_credential:
1214             show_credentials(creds)
1215
1216         # rspec
1217         rspec_file = self.get_rspec_file(args[1])
1218         rspec = open(rspec_file).read()
1219         api_options = {}
1220         api_options ['call_id'] = unique_call_id()
1221         result = server.Allocate(slice_urn, creds, rspec, api_options)
1222         value = ReturnValue.get_value(result)
1223         if self.options.raw:
1224             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1225         if options.file is not None:
1226             save_rspec_to_file (value, options.file)
1227         if (self.options.raw is None) and (options.file is None):
1228             print value
1229         return value
1230         
1231
1232     @register_command("slice_hrn","")
1233     def provision(self, options, args):
1234         """
1235         provision already allocated resources of named slice (Provision)
1236         """
1237         server = self.sliceapi()
1238         server_version = self.get_cached_server_version(server)
1239         slice_hrn = args[0]
1240         slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1241
1242         # credentials
1243         creds = [self.slice_credential(slice_hrn)]
1244         delegated_cred = None
1245         if server_version.get('interface') == 'slicemgr':
1246             # delegate our cred to the slice manager
1247             # do not delegate cred to slicemgr...not working at the moment
1248             pass
1249             #if server_version.get('hrn'):
1250             #    delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1251             #elif server_version.get('urn'):
1252             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1253
1254         if options.show_credential:
1255             show_credentials(creds)
1256
1257         api_options = {}
1258         api_options ['call_id'] = unique_call_id()
1259
1260         # set the requtested rspec version
1261         version_manager = VersionManager()
1262         rspec_version = version_manager._get_version('geni', '3.0').to_dict()
1263         api_options['geni_rspec_version'] = rspec_version
1264
1265         # users
1266         # need to pass along user keys to the aggregate.
1267         # users = [
1268         #  { urn: urn:publicid:IDN+emulab.net+user+alice
1269         #    keys: [<ssh key A>, <ssh key B>]
1270         #  }]
1271         users = []
1272         slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1273         if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
1274             slice_record = slice_records[0]
1275             user_hrns = slice_record['researcher']
1276             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1277             user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1278             users = pg_users_arg(user_records)
1279         
1280         api_options['geni_users'] = users
1281         result = server.Provision([slice_urn], creds, api_options)
1282         value = ReturnValue.get_value(result)
1283         if self.options.raw:
1284             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1285         if options.file is not None:
1286             save_rspec_to_file (value, options.file)
1287         if (self.options.raw is None) and (options.file is None):
1288             print value
1289         return value     
1290
1291     @register_command("slice_hrn","")
1292     def status(self, options, args):
1293         """
1294         retrieve the status of the slivers belonging to tne named slice (Status)
1295         """
1296         server = self.sliceapi()
1297
1298         # slice urn
1299         slice_hrn = args[0]
1300         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1301
1302         # creds 
1303         slice_cred = self.slice_credential(slice_hrn)
1304         creds = [slice_cred]
1305
1306         # options and call_id when supported
1307         api_options = {}
1308         api_options['call_id']=unique_call_id()
1309         if options.show_credential:
1310             show_credentials(creds)
1311         result = server.Status([slice_urn], creds, *self.ois(server,api_options))
1312         value = ReturnValue.get_value(result)
1313         if self.options.raw:
1314             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1315         else:
1316             print value
1317         # Thierry: seemed to be missing
1318         return value
1319
1320     @register_command("slice_hrn action","")
1321     def action(self, options, args):
1322         """
1323         Perform the named operational action on these slivers
1324         """
1325         server = self.sliceapi()
1326         api_options = {}
1327         # slice urn
1328         slice_hrn = args[0]
1329         action = args[1]
1330         slice_urn = Xrn(slice_hrn, type='slice').get_urn() 
1331         # cred
1332         slice_cred = self.slice_credential(args[0])
1333         creds = [slice_cred]
1334         if options.delegate:
1335             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1336             creds.append(delegated_cred)
1337         
1338         result = server.PerformOperationalAction([slice_urn], creds, action , api_options)
1339         value = ReturnValue.get_value(result)
1340         if self.options.raw:
1341             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1342         else:
1343             print value
1344         return value
1345
1346     @register_command("slice_hrn time","")
1347     def renew(self, options, args):
1348         """
1349         renew slice (RenewSliver)
1350         """
1351         server = self.sliceapi()
1352         if len(args) != 2:
1353             self.print_help()
1354             sys.exit(1)
1355         [ slice_hrn, input_time ] = args
1356         # slice urn    
1357         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1358         # time: don't try to be smart on the time format, server-side will
1359         # creds
1360         slice_cred = self.slice_credential(args[0])
1361         creds = [slice_cred]
1362         # options and call_id when supported
1363         api_options = {}
1364         api_options['call_id']=unique_call_id()
1365         if options.show_credential:
1366             show_credentials(creds)
1367         result =  server.Renew([slice_urn], creds, input_time, *self.ois(server,api_options))
1368         value = ReturnValue.get_value(result)
1369         if self.options.raw:
1370             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1371         else:
1372             print value
1373         return value
1374
1375
1376     @register_command("slice_hrn","")
1377     def shutdown(self, options, args):
1378         """
1379         shutdown named slice (Shutdown)
1380         """
1381         server = self.sliceapi()
1382         # slice urn
1383         slice_hrn = args[0]
1384         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1385         # creds
1386         slice_cred = self.slice_credential(slice_hrn)
1387         creds = [slice_cred]
1388         result = server.Shutdown(slice_urn, creds)
1389         value = ReturnValue.get_value(result)
1390         if self.options.raw:
1391             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1392         else:
1393             print value
1394         return value         
1395     
1396
1397     @register_command("[name]","")
1398     def gid(self, options, args):
1399         """
1400         Create a GID (CreateGid)
1401         """
1402         if len(args) < 1:
1403             self.print_help()
1404             sys.exit(1)
1405         target_hrn = args[0]
1406         my_gid_string = open(self.client_bootstrap.my_gid()).read() 
1407         gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1408         if options.file:
1409             filename = options.file
1410         else:
1411             filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1412         self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1413         GID(string=gid).save_to_file(filename)
1414          
1415     ####################
1416     @register_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1417
1418   will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1419   the set of credentials in the scope for this call would be
1420   (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1421       as per -u/--user
1422   (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1423       as per -p/--pi
1424   (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1425   (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1426       because of the two -s options
1427
1428 """)
1429     def delegate (self, options, args):
1430         """
1431         (locally) create delegate credential for use by given hrn
1432   make sure to check for 'sfi myslice' instead if you plan
1433   on using MySlice
1434         """
1435         if len(args) != 1:
1436             self.print_help()
1437             sys.exit(1)
1438         to_hrn = args[0]
1439         # support for several delegations in the same call
1440         # so first we gather the things to do
1441         tuples=[]
1442         for slice_hrn in options.delegate_slices:
1443             message="%s.slice"%slice_hrn
1444             original = self.slice_credential_string(slice_hrn)
1445             tuples.append ( (message, original,) )
1446         if options.delegate_pi:
1447             my_authority=self.authority
1448             message="%s.pi"%my_authority
1449             original = self.my_authority_credential_string()
1450             tuples.append ( (message, original,) )
1451         for auth_hrn in options.delegate_auths:
1452             message="%s.auth"%auth_hrn
1453             original=self.authority_credential_string(auth_hrn)
1454             tuples.append ( (message, original, ) )
1455         # if nothing was specified at all at this point, let's assume -u
1456         if not tuples: options.delegate_user=True
1457         # this user cred
1458         if options.delegate_user:
1459             message="%s.user"%self.user
1460             original = self.my_credential_string
1461             tuples.append ( (message, original, ) )
1462
1463         # default type for beneficial is user unless -A
1464         if options.delegate_to_authority:       to_type='authority'
1465         else:                                   to_type='user'
1466
1467         # let's now handle all this
1468         # it's all in the filenaming scheme
1469         for (message,original) in tuples:
1470             delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1471             delegated_credential = Credential (string=delegated_string)
1472             filename = os.path.join ( self.options.sfi_dir,
1473                                       "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1474             delegated_credential.save_to_file(filename, save_parents=True)
1475             self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1476     
1477     ####################
1478     @register_command("","""$ less +/myslice sfi_config
1479 [myslice]
1480 backend  = http://manifold.pl.sophia.inria.fr:7080
1481 # the HRN that myslice uses, so that we are delegating to
1482 delegate = ple.upmc.slicebrowser
1483 # platform - this is a myslice concept
1484 platform = ple
1485 # username - as of this writing (May 2013) a simple login name
1486 username = thierry
1487
1488 $ sfi myslice
1489   will first collect the slices that you are part of, then make sure
1490   all your credentials are up-to-date (read: refresh expired ones)
1491   then compute delegated credentials for user 'ple.upmc.slicebrowser'
1492   and upload them all on myslice backend, using 'platform' and 'user'.
1493   A password will be prompted for the upload part.
1494
1495 $ sfi -v myslice  -- or sfi -vv myslice
1496   same but with more and more verbosity
1497
1498 $ sfi m
1499   is synonym to sfi myslice as no other command starts with an 'm'
1500 """
1501 ) # register_command
1502     def myslice (self, options, args):
1503
1504         """ This helper is for refreshing your credentials at myslice; it will
1505   * compute all the slices that you currently have credentials on
1506   * refresh all your credentials (you as a user and pi, your slices)
1507   * upload them to the manifold backend server
1508   for last phase, sfi_config is read to look for the [myslice] section, 
1509   and namely the 'backend', 'delegate' and 'user' settings"""
1510
1511         ##########
1512         if len(args)>0:
1513             self.print_help()
1514             sys.exit(1)
1515         ### the rough sketch goes like this
1516         # (a) rain check for sufficient config in sfi_config
1517         # we don't allow to override these settings for now
1518         myslice_dict={}
1519         myslice_keys=['backend', 'delegate', 'platform', 'username']
1520         for key in myslice_keys:
1521             full_key="MYSLICE_" + key.upper()
1522             value=getattr(self.config_instance,full_key,None)
1523             if value:   myslice_dict[key]=value
1524             else:       print "Unsufficient config, missing key %s in [myslice] section of sfi_config"%key
1525         if len(myslice_dict) != len(myslice_keys):
1526             sys.exit(1)
1527
1528         # (b) figure whether we are PI for the authority where we belong
1529         self.logger.info("Resolving our own id")
1530         my_records=self.registry().Resolve(self.user,self.my_credential_string)
1531         if len(my_records)!=1: print "Cannot Resolve %s -- exiting"%self.user; sys.exit(1)
1532         my_record=my_records[0]
1533         my_auths_all = my_record['reg-pi-authorities']
1534         self.logger.info("Found %d authorities that we are PI for"%len(my_auths_all))
1535         self.logger.debug("They are %s"%(my_auths_all))
1536         
1537         my_auths = my_auths_all
1538         if options.delegate_auths:
1539             my_auths = list(set(my_auths_all).intersection(set(options.delegate_auths)))
1540
1541         self.logger.info("Delegate PI creds for authorities: %s"%my_auths        )
1542         # (c) get the set of slices that we are in
1543         my_slices_all=my_record['reg-slices']
1544         self.logger.info("Found %d slices that we are member of"%len(my_slices_all))
1545         self.logger.debug("They are: %s"%(my_slices_all))
1546  
1547         my_slices = my_slices_all
1548         if options.delegate_slices:
1549             my_slices = list(set(my_slices_all).intersection(set(options.delegate_slices)))
1550
1551         self.logger.info("Delegate slice creds for slices: %s"%my_slices)
1552
1553         # (d) make sure we have *valid* credentials for all these
1554         hrn_credentials=[]
1555         hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1556         for auth_hrn in my_auths:
1557             hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1558         for slice_hrn in my_slices:
1559             hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1560
1561         # (e) check for the delegated version of these
1562         # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever 
1563         # switch to myslice using an authority instead of a user
1564         delegatee_type='user'
1565         delegatee_hrn=myslice_dict['delegate']
1566         hrn_delegated_credentials = []
1567         for (hrn, htype, credential) in hrn_credentials:
1568             delegated_credential = self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type)
1569             hrn_delegated_credentials.append ((hrn, htype, delegated_credential, ))
1570
1571         # (f) and finally upload them to manifold server
1572         # xxx todo add an option so the password can be set on the command line
1573         # (but *NOT* in the config file) so other apps can leverage this
1574         uploader = ManifoldUploader (logger=self.logger,
1575                                      url=myslice_dict['backend'],
1576                                      platform=myslice_dict['platform'],
1577                                      username=myslice_dict['username'],
1578                                      password=options.password)
1579         uploader.prompt_all()
1580         (count_all,count_success)=(0,0)
1581         for (hrn,htype,delegated_credential) in hrn_delegated_credentials:
1582             # inspect
1583             inspect=Credential(string=delegated_credential)
1584             expire_datetime=inspect.get_expiration()
1585             message="%s (%s) [exp:%s]"%(hrn,htype,expire_datetime)
1586             if uploader.upload(delegated_credential,message=message):
1587                 count_success+=1
1588             count_all+=1
1589
1590         self.logger.info("Successfully uploaded %d/%d credentials"%(count_success,count_all))
1591         # at first I thought we would want to save these,
1592         # like 'sfi delegate does' but on second thought
1593         # it is probably not helpful as people would not
1594         # need to run 'sfi delegate' at all anymore
1595         if count_success != count_all: sys.exit(1)
1596         return
1597
1598 # Thierry: I'm turning this off as a command, no idea what it's used for
1599 #    @register_command("cred","")
1600     def trusted(self, options, args):
1601         """
1602         return the trusted certs at this interface (get_trusted_certs)
1603         """ 
1604         trusted_certs = self.registry().get_trusted_certs()
1605         for trusted_cert in trusted_certs:
1606             gid = GID(string=trusted_cert)
1607             gid.dump()
1608             cert = Certificate(string=trusted_cert)
1609             self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1610         return 
1611