more compact output with -v - reliable return code for 'sfi myslice'
[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         
492         return parser
493
494         
495     #
496     # Main: parse arguments and dispatch to command
497     #
498     def dispatch(self, command, command_options, command_args):
499         method=getattr(self, command, None)
500         if not method:
501             print "Unknown command %s"%command
502             return
503         return method(command_options, command_args)
504
505     def main(self):
506         self.sfi_parser = self.create_parser_global()
507         (options, args) = self.sfi_parser.parse_args()
508         if options.help: 
509             self.print_commands_help(options)
510             sys.exit(1)
511         self.options = options
512
513         self.logger.setLevelFromOptVerbose(self.options.verbose)
514
515         if len(args) <= 0:
516             self.logger.critical("No command given. Use -h for help.")
517             self.print_commands_help(options)
518             return -1
519     
520         # complete / find unique match with command set
521         command_candidates = Candidates (commands_list)
522         input = args[0]
523         command = command_candidates.only_match(input)
524         if not command:
525             self.print_commands_help(options)
526             sys.exit(1)
527         # second pass options parsing
528         self.command=command
529         self.command_parser = self.create_parser_command(command)
530         (command_options, command_args) = self.command_parser.parse_args(args[1:])
531         if command_options.help:
532             self.print_help()
533             sys.exit(1)
534         self.command_options = command_options
535
536         self.read_config () 
537         self.bootstrap ()
538         self.logger.debug("Command=%s" % self.command)
539
540         try:
541             self.dispatch(command, command_options, command_args)
542         except SystemExit:
543             return 1
544         except:
545             self.logger.log_exc ("sfi command %s failed"%command)
546             return 1
547
548         return 0
549     
550     ####################
551     def read_config(self):
552         config_file = os.path.join(self.options.sfi_dir,"sfi_config")
553         shell_config_file  = os.path.join(self.options.sfi_dir,"sfi_config.sh")
554         try:
555             if Config.is_ini(config_file):
556                 config = Config (config_file)
557             else:
558                 # try upgrading from shell config format
559                 fp, fn = mkstemp(suffix='sfi_config', text=True)  
560                 config = Config(fn)
561                 # we need to preload the sections we want parsed 
562                 # from the shell config
563                 config.add_section('sfi')
564                 # sface users should be able to use this same file to configure their stuff
565                 config.add_section('sface')
566                 # manifold users should be able to specify the details 
567                 # of their backend server here for 'sfi myslice'
568                 config.add_section('myslice')
569                 config.load(config_file)
570                 # back up old config
571                 shutil.move(config_file, shell_config_file)
572                 # write new config
573                 config.save(config_file)
574                  
575         except:
576             self.logger.critical("Failed to read configuration file %s"%config_file)
577             self.logger.info("Make sure to remove the export clauses and to add quotes")
578             if self.options.verbose==0:
579                 self.logger.info("Re-run with -v for more details")
580             else:
581                 self.logger.log_exc("Could not read config file %s"%config_file)
582             sys.exit(1)
583      
584         self.config_instance=config
585         errors = 0
586         # Set SliceMgr URL
587         if (self.options.sm is not None):
588            self.sm_url = self.options.sm
589         elif hasattr(config, "SFI_SM"):
590            self.sm_url = config.SFI_SM
591         else:
592            self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
593            errors += 1 
594
595         # Set Registry URL
596         if (self.options.registry is not None):
597            self.reg_url = self.options.registry
598         elif hasattr(config, "SFI_REGISTRY"):
599            self.reg_url = config.SFI_REGISTRY
600         else:
601            self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
602            errors += 1 
603
604         # Set user HRN
605         if (self.options.user is not None):
606            self.user = self.options.user
607         elif hasattr(config, "SFI_USER"):
608            self.user = config.SFI_USER
609         else:
610            self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
611            errors += 1 
612
613         # Set authority HRN
614         if (self.options.auth is not None):
615            self.authority = self.options.auth
616         elif hasattr(config, "SFI_AUTH"):
617            self.authority = config.SFI_AUTH
618         else:
619            self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
620            errors += 1 
621
622         self.config_file=config_file
623         if errors:
624            sys.exit(1)
625
626     #
627     # Get various credential and spec files
628     #
629     # Establishes limiting conventions
630     #   - conflates MAs and SAs
631     #   - assumes last token in slice name is unique
632     #
633     # Bootstraps credentials
634     #   - bootstrap user credential from self-signed certificate
635     #   - bootstrap authority credential from user credential
636     #   - bootstrap slice credential from user credential
637     #
638     
639     # init self-signed cert, user credentials and gid
640     def bootstrap (self):
641         client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
642                                                logger=self.logger)
643         # if -k is provided, use this to initialize private key
644         if self.options.user_private_key:
645             client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
646         else:
647             # trigger legacy compat code if needed 
648             # the name has changed from just <leaf>.pkey to <hrn>.pkey
649             if not os.path.isfile(client_bootstrap.private_key_filename()):
650                 self.logger.info ("private key not found, trying legacy name")
651                 try:
652                     legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
653                     self.logger.debug("legacy_private_key=%s"%legacy_private_key)
654                     client_bootstrap.init_private_key_if_missing (legacy_private_key)
655                     self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
656                 except:
657                     self.logger.log_exc("Can't find private key ")
658                     sys.exit(1)
659             
660         # make it bootstrap
661         client_bootstrap.bootstrap_my_gid()
662         # extract what's needed
663         self.private_key = client_bootstrap.private_key()
664         self.my_credential_string = client_bootstrap.my_credential_string ()
665         self.my_credential = {'geni_type': 'geni_sfa',
666                               'geni_version': '3.0', 
667                               'geni_value': self.my_credential_string}
668         self.my_gid = client_bootstrap.my_gid ()
669         self.client_bootstrap = client_bootstrap
670
671
672     def my_authority_credential_string(self):
673         if not self.authority:
674             self.logger.critical("no authority specified. Use -a or set SF_AUTH")
675             sys.exit(-1)
676         return self.client_bootstrap.authority_credential_string (self.authority)
677
678     def authority_credential_string(self, auth_hrn):
679         return self.client_bootstrap.authority_credential_string (auth_hrn)
680
681     def slice_credential_string(self, name):
682         return self.client_bootstrap.slice_credential_string (name)
683
684     def slice_credential(self, name):
685         return {'geni_type': 'geni_sfa',
686                 'geni_version': '3.0',
687                 'geni_value': self.slice_credential_string(name)}    
688
689     # xxx should be supported by sfaclientbootstrap as well
690     def delegate_cred(self, object_cred, hrn, type='authority'):
691         # the gid and hrn of the object we are delegating
692         if isinstance(object_cred, str):
693             object_cred = Credential(string=object_cred) 
694         object_gid = object_cred.get_gid_object()
695         object_hrn = object_gid.get_hrn()
696     
697         if not object_cred.get_privileges().get_all_delegate():
698             self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
699             return
700
701         # the delegating user's gid
702         caller_gidfile = self.my_gid()
703   
704         # the gid of the user who will be delegated to
705         delegee_gid = self.client_bootstrap.gid(hrn,type)
706         delegee_hrn = delegee_gid.get_hrn()
707         dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
708         return dcred.save_to_string(save_parents=True)
709      
710     #
711     # Management of the servers
712     # 
713
714     def registry (self):
715         # cache the result
716         if not hasattr (self, 'registry_proxy'):
717             self.logger.info("Contacting Registry at: %s"%self.reg_url)
718             self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid, 
719                                                  timeout=self.options.timeout, verbose=self.options.debug)  
720         return self.registry_proxy
721
722     def sliceapi (self):
723         # cache the result
724         if not hasattr (self, 'sliceapi_proxy'):
725             # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
726             if hasattr(self.command_options,'component') and self.command_options.component:
727                 # resolve the hrn at the registry
728                 node_hrn = self.command_options.component
729                 records = self.registry().Resolve(node_hrn, self.my_credential_string)
730                 records = filter_records('node', records)
731                 if not records:
732                     self.logger.warning("No such component:%r"% opts.component)
733                 record = records[0]
734                 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
735                 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
736             else:
737                 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
738                 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
739                     self.sm_url = 'http://' + self.sm_url
740                 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
741                 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid, 
742                                                      timeout=self.options.timeout, verbose=self.options.debug)  
743         return self.sliceapi_proxy
744
745     def get_cached_server_version(self, server):
746         # check local cache first
747         cache = None
748         version = None 
749         cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
750         cache_key = server.url + "-version"
751         try:
752             cache = Cache(cache_file)
753         except IOError:
754             cache = Cache()
755             self.logger.info("Local cache not found at: %s" % cache_file)
756
757         if cache:
758             version = cache.get(cache_key)
759
760         if not version: 
761             result = server.GetVersion()
762             version= ReturnValue.get_value(result)
763             # cache version for 20 minutes
764             cache.add(cache_key, version, ttl= 60*20)
765             self.logger.info("Updating cache file %s" % cache_file)
766             cache.save_to_file(cache_file)
767
768         return version   
769         
770     ### resurrect this temporarily so we can support V1 aggregates for a while
771     def server_supports_options_arg(self, server):
772         """
773         Returns true if server support the optional call_id arg, false otherwise. 
774         """
775         server_version = self.get_cached_server_version(server)
776         result = False
777         # xxx need to rewrite this 
778         if int(server_version.get('geni_api')) >= 2:
779             result = True
780         return result
781
782     def server_supports_call_id_arg(self, server):
783         server_version = self.get_cached_server_version(server)
784         result = False      
785         if 'sfa' in server_version and 'code_tag' in server_version:
786             code_tag = server_version['code_tag']
787             code_tag_parts = code_tag.split("-")
788             version_parts = code_tag_parts[0].split(".")
789             major, minor = version_parts[0], version_parts[1]
790             rev = code_tag_parts[1]
791             if int(major) == 1 and minor == 0 and build >= 22:
792                 result = True
793         return result                 
794
795     ### ois = options if supported
796     # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
797     def ois (self, server, option_dict):
798         if self.server_supports_options_arg (server): 
799             return [option_dict]
800         elif self.server_supports_call_id_arg (server):
801             return [ unique_call_id () ]
802         else: 
803             return []
804
805     ### cis = call_id if supported - like ois
806     def cis (self, server):
807         if self.server_supports_call_id_arg (server):
808             return [ unique_call_id ]
809         else:
810             return []
811
812     ######################################## miscell utilities
813     def get_rspec_file(self, rspec):
814        if (os.path.isabs(rspec)):
815           file = rspec
816        else:
817           file = os.path.join(self.options.sfi_dir, rspec)
818        if (os.path.isfile(file)):
819           return file
820        else:
821           self.logger.critical("No such rspec file %s"%rspec)
822           sys.exit(1)
823     
824     def get_record_file(self, record):
825        if (os.path.isabs(record)):
826           file = record
827        else:
828           file = os.path.join(self.options.sfi_dir, record)
829        if (os.path.isfile(file)):
830           return file
831        else:
832           self.logger.critical("No such registry record file %s"%record)
833           sys.exit(1)
834
835
836     #==========================================================================
837     # Following functions implement the commands
838     #
839     # Registry-related commands
840     #==========================================================================
841
842     @register_command("","")
843     def config (self, options, args):
844         "Display contents of current config"
845         print "# From configuration file %s"%self.config_file
846         flags=[ ('sfi', [ ('registry','reg_url'),
847                           ('auth','authority'),
848                           ('user','user'),
849                           ('sm','sm_url'),
850                           ]),
851                 ]
852         if options.myslice:
853             flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
854
855         for (section, tuples) in flags:
856             print "[%s]"%section
857             try:
858                 for (external_name, internal_name) in tuples:
859                     print "%-20s = %s"%(external_name,getattr(self,internal_name))
860             except:
861                 for name in tuples:
862                     varname="%s_%s"%(section.upper(),name.upper())
863                     value=getattr(self.config_instance,varname)
864                     print "%-20s = %s"%(name,value)
865
866     @register_command("","")
867     def version(self, options, args):
868         """
869         display an SFA server version (GetVersion)
870   or version information about sfi itself
871         """
872         if options.version_local:
873             version=version_core()
874         else:
875             if options.version_registry:
876                 server=self.registry()
877             else:
878                 server = self.sliceapi()
879             result = server.GetVersion()
880             version = ReturnValue.get_value(result)
881         if self.options.raw:
882             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
883         else:
884             pprinter = PrettyPrinter(indent=4)
885             pprinter.pprint(version)
886
887     @register_command("authority","")
888     def list(self, options, args):
889         """
890         list entries in named authority registry (List)
891         """
892         if len(args)!= 1:
893             self.print_help()
894             sys.exit(1)
895         hrn = args[0]
896         opts = {}
897         if options.recursive:
898             opts['recursive'] = options.recursive
899         
900         if options.show_credential:
901             show_credentials(self.my_credential_string)
902         try:
903             list = self.registry().List(hrn, self.my_credential_string, options)
904         except IndexError:
905             raise Exception, "Not enough parameters for the 'list' command"
906
907         # filter on person, slice, site, node, etc.
908         # This really should be in the self.filter_records funct def comment...
909         list = filter_records(options.type, list)
910         terminal_render (list, options)
911         if options.file:
912             save_records_to_file(options.file, list, options.fileformat)
913         return
914     
915     @register_command("name","")
916     def show(self, options, args):
917         """
918         show details about named registry record (Resolve)
919         """
920         if len(args)!= 1:
921             self.print_help()
922             sys.exit(1)
923         hrn = args[0]
924         # explicitly require Resolve to run in details mode
925         record_dicts = self.registry().Resolve(hrn, self.my_credential_string, {'details':True})
926         record_dicts = filter_records(options.type, record_dicts)
927         if not record_dicts:
928             self.logger.error("No record of type %s"% options.type)
929             return
930         # user has required to focus on some keys
931         if options.keys:
932             def project (record):
933                 projected={}
934                 for key in options.keys:
935                     try: projected[key]=record[key]
936                     except: pass
937                 return projected
938             record_dicts = [ project (record) for record in record_dicts ]
939         records = [ Record(dict=record_dict) for record_dict in record_dicts ]
940         for record in records:
941             if (options.format == "text"):      record.dump(sort=True)  
942             else:                               print record.save_as_xml() 
943         if options.file:
944             save_records_to_file(options.file, record_dicts, options.fileformat)
945         return
946     
947     @register_command("[xml-filename]","")
948     def add(self, options, args):
949         """add record into registry (Register) 
950   from command line options (recommended) 
951   old-school method involving an xml file still supported"""
952
953         auth_cred = self.my_authority_credential_string()
954         if options.show_credential:
955             show_credentials(auth_cred)
956         record_dict = {}
957         if len(args) > 1:
958             self.print_help()
959             sys.exit(1)
960         if len(args)==1:
961             try:
962                 record_filepath = args[0]
963                 rec_file = self.get_record_file(record_filepath)
964                 record_dict.update(load_record_from_file(rec_file).todict())
965             except:
966                 print "Cannot load record file %s"%record_filepath
967                 sys.exit(1)
968         if options:
969             record_dict.update(load_record_from_opts(options).todict())
970         # we should have a type by now
971         if 'type' not in record_dict :
972             self.print_help()
973             sys.exit(1)
974         # this is still planetlab dependent.. as plc will whine without that
975         # also, it's only for adding
976         if record_dict['type'] == 'user':
977             if not 'first_name' in record_dict:
978                 record_dict['first_name'] = record_dict['hrn']
979             if 'last_name' not in record_dict:
980                 record_dict['last_name'] = record_dict['hrn'] 
981         return self.registry().Register(record_dict, auth_cred)
982     
983     @register_command("[xml-filename]","")
984     def update(self, options, args):
985         """update record into registry (Update) 
986   from command line options (recommended) 
987   old-school method involving an xml file still supported"""
988         record_dict = {}
989         if len(args) > 0:
990             record_filepath = args[0]
991             rec_file = self.get_record_file(record_filepath)
992             record_dict.update(load_record_from_file(rec_file).todict())
993         if options:
994             record_dict.update(load_record_from_opts(options).todict())
995         # at the very least we need 'type' here
996         if 'type' not in record_dict:
997             self.print_help()
998             sys.exit(1)
999
1000         # don't translate into an object, as this would possibly distort
1001         # user-provided data; e.g. add an 'email' field to Users
1002         if record_dict['type'] == "user":
1003             if record_dict['hrn'] == self.user:
1004                 cred = self.my_credential_string
1005             else:
1006                 cred = self.my_authority_credential_string()
1007         elif record_dict['type'] in ["slice"]:
1008             try:
1009                 cred = self.slice_credential_string(record_dict['hrn'])
1010             except ServerException, e:
1011                # XXX smbaker -- once we have better error return codes, update this
1012                # to do something better than a string compare
1013                if "Permission error" in e.args[0]:
1014                    cred = self.my_authority_credential_string()
1015                else:
1016                    raise
1017         elif record_dict['type'] in ["authority"]:
1018             cred = self.my_authority_credential_string()
1019         elif record_dict['type'] == 'node':
1020             cred = self.my_authority_credential_string()
1021         else:
1022             raise "unknown record type" + record_dict['type']
1023         if options.show_credential:
1024             show_credentials(cred)
1025         return self.registry().Update(record_dict, cred)
1026   
1027     @register_command("hrn","")
1028     def remove(self, options, args):
1029         "remove registry record by name (Remove)"
1030         auth_cred = self.my_authority_credential_string()
1031         if len(args)!=1:
1032             self.print_help()
1033             sys.exit(1)
1034         hrn = args[0]
1035         type = options.type 
1036         if type in ['all']:
1037             type = '*'
1038         if options.show_credential:
1039             show_credentials(auth_cred)
1040         return self.registry().Remove(hrn, auth_cred, type)
1041     
1042     # ==================================================================
1043     # Slice-related commands
1044     # ==================================================================
1045
1046     @register_command("","")
1047     def slices(self, options, args):
1048         "list instantiated slices (ListSlices) - returns urn's"
1049         server = self.sliceapi()
1050         # creds
1051         creds = [self.my_credential_string]
1052         # options and call_id when supported
1053         api_options = {}
1054         api_options['call_id']=unique_call_id()
1055         if options.show_credential:
1056             show_credentials(creds)
1057         result = server.ListSlices(creds, *self.ois(server,api_options))
1058         value = ReturnValue.get_value(result)
1059         if self.options.raw:
1060             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1061         else:
1062             display_list(value)
1063         return
1064
1065     # show rspec for named slice
1066     @register_command("","")
1067     def resources(self, options, args):
1068         """
1069         discover available resources (ListResources)
1070         """
1071         server = self.sliceapi()
1072
1073         # set creds
1074         creds = [self.my_credential]
1075         if options.delegate:
1076             creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1077         if options.show_credential:
1078             show_credentials(creds)
1079
1080         # no need to check if server accepts the options argument since the options has
1081         # been a required argument since v1 API
1082         api_options = {}
1083         # always send call_id to v2 servers
1084         api_options ['call_id'] = unique_call_id()
1085         # ask for cached value if available
1086         api_options ['cached'] = True
1087         if options.info:
1088             api_options['info'] = options.info
1089         if options.list_leases:
1090             api_options['list_leases'] = options.list_leases
1091         if options.current:
1092             if options.current == True:
1093                 api_options['cached'] = False
1094             else:
1095                 api_options['cached'] = True
1096         if options.rspec_version:
1097             version_manager = VersionManager()
1098             server_version = self.get_cached_server_version(server)
1099             if 'sfa' in server_version:
1100                 # just request the version the client wants
1101                 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1102             else:
1103                 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1104         else:
1105             api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1106         result = server.ListResources (creds, api_options)
1107         value = ReturnValue.get_value(result)
1108         if self.options.raw:
1109             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1110         if options.file is not None:
1111             save_rspec_to_file(value, options.file)
1112         if (self.options.raw is None) and (options.file is None):
1113             display_rspec(value, options.format)
1114
1115         return
1116
1117     @register_command("slice_hrn","")
1118     def describe(self, options, args):
1119         """
1120         shows currently allocated/provisioned resources 
1121         of the named slice or set of slivers (Describe) 
1122         """
1123         server = self.sliceapi()
1124
1125         # set creds
1126         creds = [self.slice_credential(args[0])]
1127         if options.delegate:
1128             creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1129         if options.show_credential:
1130             show_credentials(creds)
1131
1132         api_options = {'call_id': unique_call_id(),
1133                        'cached': True,
1134                        'info': options.info,
1135                        'list_leases': options.list_leases,
1136                        'geni_rspec_version': {'type': 'geni', 'version': '3.0'},
1137                       }
1138         if options.rspec_version:
1139             version_manager = VersionManager()
1140             server_version = self.get_cached_server_version(server)
1141             if 'sfa' in server_version:
1142                 # just request the version the client wants
1143                 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1144             else:
1145                 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1146         urn = Xrn(args[0], type='slice').get_urn()        
1147         result = server.Describe([urn], creds, api_options)
1148         value = ReturnValue.get_value(result)
1149         if self.options.raw:
1150             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1151         if options.file is not None:
1152             save_rspec_to_file(value, options.file)
1153         if (self.options.raw is None) and (options.file is None):
1154             display_rspec(value, options.format)
1155
1156         return 
1157
1158     @register_command("slice_hrn","")
1159     def delete(self, options, args):
1160         """
1161         de-allocate and de-provision all or named slivers of the slice (Delete)
1162         """
1163         server = self.sliceapi()
1164
1165         # slice urn
1166         slice_hrn = args[0]
1167         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1168
1169         # creds
1170         slice_cred = self.slice_credential(slice_hrn)
1171         creds = [slice_cred]
1172         
1173         # options and call_id when supported
1174         api_options = {}
1175         api_options ['call_id'] = unique_call_id()
1176         if options.show_credential:
1177             show_credentials(creds)
1178         result = server.Delete([slice_urn], creds, *self.ois(server, api_options ) )
1179         value = ReturnValue.get_value(result)
1180         if self.options.raw:
1181             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1182         else:
1183             print value
1184         return value
1185
1186     @register_command("slice_hrn rspec","")
1187     def allocate(self, options, args):
1188         """
1189          allocate resources to the named slice (Allocate)
1190         """
1191         server = self.sliceapi()
1192         server_version = self.get_cached_server_version(server)
1193         slice_hrn = args[0]
1194         slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1195
1196         # credentials
1197         creds = [self.slice_credential(slice_hrn)]
1198
1199         delegated_cred = None
1200         if server_version.get('interface') == 'slicemgr':
1201             # delegate our cred to the slice manager
1202             # do not delegate cred to slicemgr...not working at the moment
1203             pass
1204             #if server_version.get('hrn'):
1205             #    delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1206             #elif server_version.get('urn'):
1207             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1208
1209         if options.show_credential:
1210             show_credentials(creds)
1211
1212         # rspec
1213         rspec_file = self.get_rspec_file(args[1])
1214         rspec = open(rspec_file).read()
1215         api_options = {}
1216         api_options ['call_id'] = unique_call_id()
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.0').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 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
1270             slice_record = slice_records[0]
1271             user_hrns = slice_record['researcher']
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
1512         ### the rough sketch goes like this
1513         # (a) rain check for sufficient config in sfi_config
1514         # we don't allow to override these settings for now
1515         myslice_dict={}
1516         myslice_keys=['backend', 'delegate', 'platform', 'username']
1517         for key in myslice_keys:
1518             full_key="MYSLICE_" + key.upper()
1519             value=getattr(self.config_instance,full_key,None)
1520             if value:   myslice_dict[key]=value
1521             else:       print "Unsufficient config, missing key %s in [myslice] section of sfi_config"%key
1522         if len(myslice_dict) != len(myslice_keys):
1523             sys.exit(1)
1524
1525         # (b) figure whether we are PI for the authority where we belong
1526         self.logger.info("Resolving our own id")
1527         my_records=self.registry().Resolve(self.user,self.my_credential_string)
1528         if len(my_records)!=1: print "Cannot Resolve %s -- exiting"%self.user; sys.exit(1)
1529         my_record=my_records[0]
1530         my_auths = my_record['reg-pi-authorities']
1531         self.logger.info("Found %d authorities that we are PI for"%len(my_auths))
1532         self.logger.debug("They are %s"%(my_auths))
1533
1534         # (c) get the set of slices that we are in
1535         my_slices=my_record['reg-slices']
1536         self.logger.info("Found %d slices that we are member of"%len(my_slices))
1537         self.logger.debug("They are: %s"%(my_slices))
1538
1539         # (d) make sure we have *valid* credentials for all these
1540         hrn_credentials=[]
1541         hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1542         for auth_hrn in my_auths:
1543             hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1544         for slice_hrn in my_slices:
1545             hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1546
1547         # (e) check for the delegated version of these
1548         # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever 
1549         # switch to myslice using an authority instead of a user
1550         delegatee_type='user'
1551         delegatee_hrn=myslice_dict['delegate']
1552         hrn_delegated_credentials = []
1553         for (hrn, htype, credential) in hrn_credentials:
1554             delegated_credential = self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type)
1555             hrn_delegated_credentials.append ((hrn, htype, delegated_credential, ))
1556
1557         # (f) and finally upload them to manifold server
1558         # xxx todo add an option so the password can be set on the command line
1559         # (but *NOT* in the config file) so other apps can leverage this
1560         uploader = ManifoldUploader (logger=self.logger,
1561                                      url=myslice_dict['backend'],
1562                                      platform=myslice_dict['platform'],
1563                                      username=myslice_dict['username'],
1564                                      password=options.password)
1565         uploader.prompt_all()
1566         (count_all,count_success)=(0,0)
1567         for (hrn,htype,delegated_credential) in hrn_delegated_credentials:
1568             # inspect
1569             inspect=Credential(string=delegated_credential)
1570             expire_datetime=inspect.get_expiration()
1571             message="%s (%s) [exp:%s]"%(hrn,htype,expire_datetime)
1572             if uploader.upload(delegated_credential,message=message):
1573                 count_success+=1
1574             count_all+=1
1575
1576         self.logger.info("Successfully uploaded %d/%d credentials"%(count_success,count_all))
1577         # at first I thought we would want to save these,
1578         # like 'sfi delegate does' but on second thought
1579         # it is probably not helpful as people would not
1580         # need to run 'sfi delegate' at all anymore
1581         if count_success != count_all: sys.exit(1)
1582         return
1583
1584 # Thierry: I'm turning this off as a command, no idea what it's used for
1585 #    @register_command("cred","")
1586     def trusted(self, options, args):
1587         """
1588         return the trusted certs at this interface (get_trusted_certs)
1589         """ 
1590         trusted_certs = self.registry().get_trusted_certs()
1591         for trusted_cert in trusted_certs:
1592             gid = GID(string=trusted_cert)
1593             gid.dump()
1594             cert = Certificate(string=trusted_cert)
1595             self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1596         return 
1597