reorder parser create funcs
[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_global_parser().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_command_parser(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_command_parser(self, command):
327         if command not in commands_dict:
328             msg="Invalid command\n"
329             msg+="Commands: "
330             msg += ','.join(commands_list)            
331             self.logger.critical(msg)
332             sys.exit(2)
333
334         # retrieve args_string
335         (_, args_string, __) = commands_dict[command]
336
337         parser = OptionParser(add_help_option=False,
338                               usage="sfi [sfi_options] %s [cmd_options] %s"
339                               % (command, args_string))
340         parser.add_option ("-h","--help",dest='help',action='store_true',default=False,
341                            help="Summary of one command usage")
342
343         if command in ("add", "update"):
344             parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
345             parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
346             parser.add_option('-e', '--email', dest='email', default="",  help="email (mandatory for users)") 
347             parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file', 
348                               default=None)
349             parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
350                               default='', type="str", action='callback', callback=optparse_listvalue_callback)
351             parser.add_option('-r', '--researchers', dest='researchers', metavar='<researchers>', 
352                               help='Set/replace slice researchers', default='', type="str", action='callback', 
353                               callback=optparse_listvalue_callback)
354             parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
355                               default='', type="str", action='callback', callback=optparse_listvalue_callback)
356             parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
357                                action="callback", callback=optparse_dictvalue_callback, nargs=1,
358                                help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
359
360         # user specifies remote aggregate/sm/component                          
361         if command in ("resources", "describe", "allocate", "provision", "delete", "allocate", "provision", 
362                        "action", "shutdown", "renew", "status"):
363             parser.add_option("-d", "--delegate", dest="delegate", default=None, 
364                              action="store_true",
365                              help="Include a credential delegated to the user's root"+\
366                                   "authority in set of credentials for this call")
367
368         # show_credential option
369         if command in ("list","resources", "describe", "provision", "allocate", "add","update","remove","slices","delete","status","renew"):
370             parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
371                               help="show credential(s) used in human-readable form")
372         # registy filter option
373         if command in ("list", "show", "remove"):
374             parser.add_option("-t", "--type", dest="type", type="choice",
375                             help="type filter ([all]|user|slice|authority|node|aggregate)",
376                             choices=("all", "user", "slice", "authority", "node", "aggregate"),
377                             default="all")
378         if command in ("show"):
379             parser.add_option("-k","--key",dest="keys",action="append",default=[],
380                               help="specify specific keys to be displayed from record")
381         if command in ("resources", "describe"):
382             # rspec version
383             parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
384                               help="schema type and version of resulting RSpec")
385             # disable/enable cached rspecs
386             parser.add_option("-c", "--current", dest="current", default=False,
387                               action="store_true",  
388                               help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
389             # display formats
390             parser.add_option("-f", "--format", dest="format", type="choice",
391                              help="display format ([xml]|dns|ip)", default="xml",
392                              choices=("xml", "dns", "ip"))
393             #panos: a new option to define the type of information about resources a user is interested in
394             parser.add_option("-i", "--info", dest="info",
395                                 help="optional component information", default=None)
396             # a new option to retreive or not reservation-oriented RSpecs (leases)
397             parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
398                                 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
399                                 choices=("all", "resources", "leases"), default="resources")
400
401
402         if command in ("resources", "describe", "allocate", "provision", "show", "list", "gid"):
403            parser.add_option("-o", "--output", dest="file",
404                             help="output XML to file", metavar="FILE", default=None)
405
406         if command in ("show", "list"):
407            parser.add_option("-f", "--format", dest="format", type="choice",
408                              help="display format ([text]|xml)", default="text",
409                              choices=("text", "xml"))
410
411            parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
412                              help="output file format ([xml]|xmllist|hrnlist)", default="xml",
413                              choices=("xml", "xmllist", "hrnlist"))
414         if command == 'list':
415            parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
416                              help="list all child records", default=False)
417            parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
418                              help="gives details, like user keys", default=False)
419         if command in ("delegate"):
420            parser.add_option("-u", "--user",
421                              action="store_true", dest="delegate_user", default=False,
422                              help="delegate your own credentials; default if no other option is provided")
423            parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
424                              metavar="slice_hrn", help="delegate cred. for slice HRN")
425            parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
426                              metavar='auth_hrn', help="delegate cred for auth HRN")
427            # this primarily is a shorthand for -a my_hrn
428            parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
429                              help="delegate your PI credentials, so s.t. like -a your_hrn^")
430            parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
431                              help="""by default the mandatory argument is expected to be a user, 
432 use this if you mean an authority instead""")
433         
434         if command in ("version"):
435             parser.add_option("-R","--registry-version",
436                               action="store_true", dest="version_registry", default=False,
437                               help="probe registry version instead of sliceapi")
438             parser.add_option("-l","--local",
439                               action="store_true", dest="version_local", default=False,
440                               help="display version of the local client")
441
442         return parser
443
444         
445     def create_parser(self):
446
447         # Generate command line parser
448         parser = OptionParser(add_help_option=False,
449                               usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
450                               description="Commands: %s"%(" ".join(commands_list)))
451         parser.add_option("-r", "--registry", dest="registry",
452                          help="root registry", metavar="URL", default=None)
453         parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
454                          help="slice API - in general a SM URL, but can be used to talk to an aggregate")
455         parser.add_option("-R", "--raw", dest="raw", default=None,
456                           help="Save raw, unparsed server response to a file")
457         parser.add_option("", "--rawformat", dest="rawformat", type="choice",
458                           help="raw file format ([text]|pickled|json)", default="text",
459                           choices=("text","pickled","json"))
460         parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
461                           help="text string to write before and after raw output")
462         parser.add_option("-d", "--dir", dest="sfi_dir",
463                          help="config & working directory - default is %default",
464                          metavar="PATH", default=Sfi.default_sfi_dir())
465         parser.add_option("-u", "--user", dest="user",
466                          help="user name", metavar="HRN", default=None)
467         parser.add_option("-a", "--auth", dest="auth",
468                          help="authority name", metavar="HRN", default=None)
469         parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
470                          help="verbose mode - cumulative")
471         parser.add_option("-D", "--debug",
472                           action="store_true", dest="debug", default=False,
473                           help="Debug (xml-rpc) protocol messages")
474         # would it make sense to use ~/.ssh/id_rsa as a default here ?
475         parser.add_option("-k", "--private-key",
476                          action="store", dest="user_private_key", default=None,
477                          help="point to the private key file to use if not yet installed in sfi_dir")
478         parser.add_option("-t", "--timeout", dest="timeout", default=None,
479                          help="Amout of time to wait before timing out the request")
480         parser.add_option("-h", "--help", 
481                          action="store_true", dest="help", default=False,
482                          help="one page summary on commands & exit")
483         parser.disable_interspersed_args()
484
485         return parser
486         
487
488     #
489     # Main: parse arguments and dispatch to command
490     #
491     def dispatch(self, command, command_options, command_args):
492         method=getattr(self, command, None)
493         if not method:
494             print "Unknown command %s"%command
495             return
496         return method(command_options, command_args)
497
498     def main(self):
499         self.sfi_parser = self.create_parser()
500         (options, args) = self.sfi_parser.parse_args()
501         if options.help: 
502             self.print_commands_help(options)
503             sys.exit(1)
504         self.options = options
505
506         self.logger.setLevelFromOptVerbose(self.options.verbose)
507
508         if len(args) <= 0:
509             self.logger.critical("No command given. Use -h for help.")
510             self.print_commands_help(options)
511             return -1
512     
513         # complete / find unique match with command set
514         command_candidates = Candidates (commands_list)
515         input = args[0]
516         command = command_candidates.only_match(input)
517         if not command:
518             self.print_commands_help(options)
519             sys.exit(1)
520         # second pass options parsing
521         self.command=command
522         self.command_parser = self.create_command_parser(command)
523         (command_options, command_args) = self.command_parser.parse_args(args[1:])
524         if command_options.help:
525             self.print_help()
526             sys.exit(1)
527         self.command_options = command_options
528
529         self.read_config () 
530         self.bootstrap ()
531         self.logger.debug("Command=%s" % self.command)
532
533         try:
534             self.dispatch(command, command_options, command_args)
535         except SystemExit:
536             return 1
537         except:
538             self.logger.log_exc ("sfi command %s failed"%command)
539             return 1
540
541         return 0
542     
543     ####################
544     def read_config(self):
545         config_file = os.path.join(self.options.sfi_dir,"sfi_config")
546         shell_config_file  = os.path.join(self.options.sfi_dir,"sfi_config.sh")
547         try:
548             if Config.is_ini(config_file):
549                 config = Config (config_file)
550             else:
551                 # try upgrading from shell config format
552                 fp, fn = mkstemp(suffix='sfi_config', text=True)  
553                 config = Config(fn)
554                 # we need to preload the sections we want parsed 
555                 # from the shell config
556                 config.add_section('sfi')
557                 # sface users should be able to use this same file to configure their stuff
558                 config.add_section('sface')
559                 # manifold users should be able to specify the details 
560                 # of their backend server here for 'sfi myslice'
561                 config.add_section('myslice')
562                 config.load(config_file)
563                 # back up old config
564                 shutil.move(config_file, shell_config_file)
565                 # write new config
566                 config.save(config_file)
567                  
568         except:
569             self.logger.critical("Failed to read configuration file %s"%config_file)
570             self.logger.info("Make sure to remove the export clauses and to add quotes")
571             if self.options.verbose==0:
572                 self.logger.info("Re-run with -v for more details")
573             else:
574                 self.logger.log_exc("Could not read config file %s"%config_file)
575             sys.exit(1)
576      
577         self.config_instance=config
578         errors = 0
579         # Set SliceMgr URL
580         if (self.options.sm is not None):
581            self.sm_url = self.options.sm
582         elif hasattr(config, "SFI_SM"):
583            self.sm_url = config.SFI_SM
584         else:
585            self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
586            errors += 1 
587
588         # Set Registry URL
589         if (self.options.registry is not None):
590            self.reg_url = self.options.registry
591         elif hasattr(config, "SFI_REGISTRY"):
592            self.reg_url = config.SFI_REGISTRY
593         else:
594            self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
595            errors += 1 
596
597         # Set user HRN
598         if (self.options.user is not None):
599            self.user = self.options.user
600         elif hasattr(config, "SFI_USER"):
601            self.user = config.SFI_USER
602         else:
603            self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
604            errors += 1 
605
606         # Set authority HRN
607         if (self.options.auth is not None):
608            self.authority = self.options.auth
609         elif hasattr(config, "SFI_AUTH"):
610            self.authority = config.SFI_AUTH
611         else:
612            self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
613            errors += 1 
614
615         self.config_file=config_file
616         if errors:
617            sys.exit(1)
618
619     def show_config (self):
620         print "From configuration file %s"%self.config_file
621         flags=[ 
622             ('SFI_USER','user'),
623             ('SFI_AUTH','authority'),
624             ('SFI_SM','sm_url'),
625             ('SFI_REGISTRY','reg_url'),
626             ]
627         for (external_name, internal_name) in flags:
628             print "%s='%s'"%(external_name,getattr(self,internal_name))
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         self.show_config()
850
851     @register_command("","")
852     def version(self, options, args):
853         """
854         display an SFA server version (GetVersion)
855   or version information about sfi itself
856         """
857         if options.version_local:
858             version=version_core()
859         else:
860             if options.version_registry:
861                 server=self.registry()
862             else:
863                 server = self.sliceapi()
864             result = server.GetVersion()
865             version = ReturnValue.get_value(result)
866         if self.options.raw:
867             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
868         else:
869             pprinter = PrettyPrinter(indent=4)
870             pprinter.pprint(version)
871
872     @register_command("authority","")
873     def list(self, options, args):
874         """
875         list entries in named authority registry (List)
876         """
877         if len(args)!= 1:
878             self.print_help()
879             sys.exit(1)
880         hrn = args[0]
881         opts = {}
882         if options.recursive:
883             opts['recursive'] = options.recursive
884         
885         if options.show_credential:
886             show_credentials(self.my_credential_string)
887         try:
888             list = self.registry().List(hrn, self.my_credential_string, options)
889         except IndexError:
890             raise Exception, "Not enough parameters for the 'list' command"
891
892         # filter on person, slice, site, node, etc.
893         # This really should be in the self.filter_records funct def comment...
894         list = filter_records(options.type, list)
895         terminal_render (list, options)
896         if options.file:
897             save_records_to_file(options.file, list, options.fileformat)
898         return
899     
900     @register_command("name","")
901     def show(self, options, args):
902         """
903         show details about named registry record (Resolve)
904         """
905         if len(args)!= 1:
906             self.print_help()
907             sys.exit(1)
908         hrn = args[0]
909         # explicitly require Resolve to run in details mode
910         record_dicts = self.registry().Resolve(hrn, self.my_credential_string, {'details':True})
911         record_dicts = filter_records(options.type, record_dicts)
912         if not record_dicts:
913             self.logger.error("No record of type %s"% options.type)
914             return
915         # user has required to focus on some keys
916         if options.keys:
917             def project (record):
918                 projected={}
919                 for key in options.keys:
920                     try: projected[key]=record[key]
921                     except: pass
922                 return projected
923             record_dicts = [ project (record) for record in record_dicts ]
924         records = [ Record(dict=record_dict) for record_dict in record_dicts ]
925         for record in records:
926             if (options.format == "text"):      record.dump(sort=True)  
927             else:                               print record.save_as_xml() 
928         if options.file:
929             save_records_to_file(options.file, record_dicts, options.fileformat)
930         return
931     
932     @register_command("[xml-filename]","")
933     def add(self, options, args):
934         """add record into registry (Register) 
935   from command line options (recommended) 
936   old-school method involving an xml file still supported"""
937
938         auth_cred = self.my_authority_credential_string()
939         if options.show_credential:
940             show_credentials(auth_cred)
941         record_dict = {}
942         if len(args) > 1:
943             self.print_help()
944             sys.exit(1)
945         if len(args)==1:
946             try:
947                 record_filepath = args[0]
948                 rec_file = self.get_record_file(record_filepath)
949                 record_dict.update(load_record_from_file(rec_file).todict())
950             except:
951                 print "Cannot load record file %s"%record_filepath
952                 sys.exit(1)
953         if options:
954             record_dict.update(load_record_from_opts(options).todict())
955         # we should have a type by now
956         if 'type' not in record_dict :
957             self.print_help()
958             sys.exit(1)
959         # this is still planetlab dependent.. as plc will whine without that
960         # also, it's only for adding
961         if record_dict['type'] == 'user':
962             if not 'first_name' in record_dict:
963                 record_dict['first_name'] = record_dict['hrn']
964             if 'last_name' not in record_dict:
965                 record_dict['last_name'] = record_dict['hrn'] 
966         return self.registry().Register(record_dict, auth_cred)
967     
968     @register_command("[xml-filename]","")
969     def update(self, options, args):
970         """update record into registry (Update) 
971   from command line options (recommended) 
972   old-school method involving an xml file still supported"""
973         record_dict = {}
974         if len(args) > 0:
975             record_filepath = args[0]
976             rec_file = self.get_record_file(record_filepath)
977             record_dict.update(load_record_from_file(rec_file).todict())
978         if options:
979             record_dict.update(load_record_from_opts(options).todict())
980         # at the very least we need 'type' here
981         if 'type' not in record_dict:
982             self.print_help()
983             sys.exit(1)
984
985         # don't translate into an object, as this would possibly distort
986         # user-provided data; e.g. add an 'email' field to Users
987         if record_dict['type'] == "user":
988             if record_dict['hrn'] == self.user:
989                 cred = self.my_credential_string
990             else:
991                 cred = self.my_authority_credential_string()
992         elif record_dict['type'] in ["slice"]:
993             try:
994                 cred = self.slice_credential_string(record_dict['hrn'])
995             except ServerException, e:
996                # XXX smbaker -- once we have better error return codes, update this
997                # to do something better than a string compare
998                if "Permission error" in e.args[0]:
999                    cred = self.my_authority_credential_string()
1000                else:
1001                    raise
1002         elif record_dict['type'] in ["authority"]:
1003             cred = self.my_authority_credential_string()
1004         elif record_dict['type'] == 'node':
1005             cred = self.my_authority_credential_string()
1006         else:
1007             raise "unknown record type" + record_dict['type']
1008         if options.show_credential:
1009             show_credentials(cred)
1010         return self.registry().Update(record_dict, cred)
1011   
1012     @register_command("name","")
1013     def remove(self, options, args):
1014         "remove registry record by name (Remove)"
1015         auth_cred = self.my_authority_credential_string()
1016         if len(args)!=1:
1017             self.print_help()
1018             sys.exit(1)
1019         hrn = args[0]
1020         type = options.type 
1021         if type in ['all']:
1022             type = '*'
1023         if options.show_credential:
1024             show_credentials(auth_cred)
1025         return self.registry().Remove(hrn, auth_cred, type)
1026     
1027     # ==================================================================
1028     # Slice-related commands
1029     # ==================================================================
1030
1031     @register_command("","")
1032     def slices(self, options, args):
1033         "list instantiated slices (ListSlices) - returns urn's"
1034         server = self.sliceapi()
1035         # creds
1036         creds = [self.my_credential_string]
1037         # options and call_id when supported
1038         api_options = {}
1039         api_options['call_id']=unique_call_id()
1040         if options.show_credential:
1041             show_credentials(creds)
1042         result = server.ListSlices(creds, *self.ois(server,api_options))
1043         value = ReturnValue.get_value(result)
1044         if self.options.raw:
1045             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1046         else:
1047             display_list(value)
1048         return
1049
1050     # show rspec for named slice
1051     @register_command("","")
1052     def resources(self, options, args):
1053         """
1054         discover available resources (ListResources)
1055         """
1056         server = self.sliceapi()
1057
1058         # set creds
1059         creds = [self.my_credential]
1060         if options.delegate:
1061             creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1062         if options.show_credential:
1063             show_credentials(creds)
1064
1065         # no need to check if server accepts the options argument since the options has
1066         # been a required argument since v1 API
1067         api_options = {}
1068         # always send call_id to v2 servers
1069         api_options ['call_id'] = unique_call_id()
1070         # ask for cached value if available
1071         api_options ['cached'] = True
1072         if options.info:
1073             api_options['info'] = options.info
1074         if options.list_leases:
1075             api_options['list_leases'] = options.list_leases
1076         if options.current:
1077             if options.current == True:
1078                 api_options['cached'] = False
1079             else:
1080                 api_options['cached'] = True
1081         if options.rspec_version:
1082             version_manager = VersionManager()
1083             server_version = self.get_cached_server_version(server)
1084             if 'sfa' in server_version:
1085                 # just request the version the client wants
1086                 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1087             else:
1088                 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1089         else:
1090             api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1091         result = server.ListResources (creds, api_options)
1092         value = ReturnValue.get_value(result)
1093         if self.options.raw:
1094             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1095         if options.file is not None:
1096             save_rspec_to_file(value, options.file)
1097         if (self.options.raw is None) and (options.file is None):
1098             display_rspec(value, options.format)
1099
1100         return
1101
1102     @register_command("slice_hrn","")
1103     def describe(self, options, args):
1104         """
1105         shows currently allocated/provisioned resources 
1106         of the named slice or set of slivers (Describe) 
1107         """
1108         server = self.sliceapi()
1109
1110         # set creds
1111         creds = [self.slice_credential(args[0])]
1112         if options.delegate:
1113             creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1114         if options.show_credential:
1115             show_credentials(creds)
1116
1117         api_options = {'call_id': unique_call_id(),
1118                        'cached': True,
1119                        'info': options.info,
1120                        'list_leases': options.list_leases,
1121                        'geni_rspec_version': {'type': 'geni', 'version': '3.0'},
1122                       }
1123         if options.rspec_version:
1124             version_manager = VersionManager()
1125             server_version = self.get_cached_server_version(server)
1126             if 'sfa' in server_version:
1127                 # just request the version the client wants
1128                 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1129             else:
1130                 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1131         urn = Xrn(args[0], type='slice').get_urn()        
1132         result = server.Describe([urn], creds, api_options)
1133         value = ReturnValue.get_value(result)
1134         if self.options.raw:
1135             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1136         if options.file is not None:
1137             save_rspec_to_file(value, options.file)
1138         if (self.options.raw is None) and (options.file is None):
1139             display_rspec(value, options.format)
1140
1141         return 
1142
1143     @register_command("slice_hrn","")
1144     def delete(self, options, args):
1145         """
1146         de-allocate and de-provision all or named slivers of the slice (Delete)
1147         """
1148         server = self.sliceapi()
1149
1150         # slice urn
1151         slice_hrn = args[0]
1152         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1153
1154         # creds
1155         slice_cred = self.slice_credential(slice_hrn)
1156         creds = [slice_cred]
1157         
1158         # options and call_id when supported
1159         api_options = {}
1160         api_options ['call_id'] = unique_call_id()
1161         if options.show_credential:
1162             show_credentials(creds)
1163         result = server.Delete([slice_urn], creds, *self.ois(server, api_options ) )
1164         value = ReturnValue.get_value(result)
1165         if self.options.raw:
1166             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1167         else:
1168             print value
1169         return value
1170
1171     @register_command("slice_hrn rspec","")
1172     def allocate(self, options, args):
1173         """
1174          allocate resources to the named slice (Allocate)
1175         """
1176         server = self.sliceapi()
1177         server_version = self.get_cached_server_version(server)
1178         slice_hrn = args[0]
1179         slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1180
1181         # credentials
1182         creds = [self.slice_credential(slice_hrn)]
1183
1184         delegated_cred = None
1185         if server_version.get('interface') == 'slicemgr':
1186             # delegate our cred to the slice manager
1187             # do not delegate cred to slicemgr...not working at the moment
1188             pass
1189             #if server_version.get('hrn'):
1190             #    delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1191             #elif server_version.get('urn'):
1192             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1193
1194         if options.show_credential:
1195             show_credentials(creds)
1196
1197         # rspec
1198         rspec_file = self.get_rspec_file(args[1])
1199         rspec = open(rspec_file).read()
1200         api_options = {}
1201         api_options ['call_id'] = unique_call_id()
1202         result = server.Allocate(slice_urn, creds, rspec, api_options)
1203         value = ReturnValue.get_value(result)
1204         if self.options.raw:
1205             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1206         if options.file is not None:
1207             save_rspec_to_file (value, options.file)
1208         if (self.options.raw is None) and (options.file is None):
1209             print value
1210         return value
1211         
1212
1213     @register_command("slice_hrn","")
1214     def provision(self, options, args):
1215         """
1216         provision already allocated resources of named slice (Provision)
1217         """
1218         server = self.sliceapi()
1219         server_version = self.get_cached_server_version(server)
1220         slice_hrn = args[0]
1221         slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1222
1223         # credentials
1224         creds = [self.slice_credential(slice_hrn)]
1225         delegated_cred = None
1226         if server_version.get('interface') == 'slicemgr':
1227             # delegate our cred to the slice manager
1228             # do not delegate cred to slicemgr...not working at the moment
1229             pass
1230             #if server_version.get('hrn'):
1231             #    delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1232             #elif server_version.get('urn'):
1233             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1234
1235         if options.show_credential:
1236             show_credentials(creds)
1237
1238         api_options = {}
1239         api_options ['call_id'] = unique_call_id()
1240
1241         # set the requtested rspec version
1242         version_manager = VersionManager()
1243         rspec_version = version_manager._get_version('geni', '3.0').to_dict()
1244         api_options['geni_rspec_version'] = rspec_version
1245
1246         # users
1247         # need to pass along user keys to the aggregate.
1248         # users = [
1249         #  { urn: urn:publicid:IDN+emulab.net+user+alice
1250         #    keys: [<ssh key A>, <ssh key B>]
1251         #  }]
1252         users = []
1253         slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1254         if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
1255             slice_record = slice_records[0]
1256             user_hrns = slice_record['researcher']
1257             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1258             user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1259             users = pg_users_arg(user_records)
1260         
1261         api_options['geni_users'] = users
1262         result = server.Provision([slice_urn], creds, api_options)
1263         value = ReturnValue.get_value(result)
1264         if self.options.raw:
1265             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1266         if options.file is not None:
1267             save_rspec_to_file (value, options.file)
1268         if (self.options.raw is None) and (options.file is None):
1269             print value
1270         return value     
1271
1272     @register_command("slice_hrn","")
1273     def status(self, options, args):
1274         """
1275         retrieve the status of the slivers belonging to tne named slice (Status)
1276         """
1277         server = self.sliceapi()
1278
1279         # slice urn
1280         slice_hrn = args[0]
1281         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1282
1283         # creds 
1284         slice_cred = self.slice_credential(slice_hrn)
1285         creds = [slice_cred]
1286
1287         # options and call_id when supported
1288         api_options = {}
1289         api_options['call_id']=unique_call_id()
1290         if options.show_credential:
1291             show_credentials(creds)
1292         result = server.Status([slice_urn], creds, *self.ois(server,api_options))
1293         value = ReturnValue.get_value(result)
1294         if self.options.raw:
1295             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1296         else:
1297             print value
1298         # Thierry: seemed to be missing
1299         return value
1300
1301     @register_command("slice_hrn action","")
1302     def action(self, options, args):
1303         """
1304         Perform the named operational action on these slivers
1305         """
1306         server = self.sliceapi()
1307         api_options = {}
1308         # slice urn
1309         slice_hrn = args[0]
1310         action = args[1]
1311         slice_urn = Xrn(slice_hrn, type='slice').get_urn() 
1312         # cred
1313         slice_cred = self.slice_credential(args[0])
1314         creds = [slice_cred]
1315         if options.delegate:
1316             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1317             creds.append(delegated_cred)
1318         
1319         result = server.PerformOperationalAction([slice_urn], creds, action , api_options)
1320         value = ReturnValue.get_value(result)
1321         if self.options.raw:
1322             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1323         else:
1324             print value
1325         return value
1326
1327     @register_command("slice_hrn time","")
1328     def renew(self, options, args):
1329         """
1330         renew slice (RenewSliver)
1331         """
1332         server = self.sliceapi()
1333         if len(args) != 2:
1334             self.print_help()
1335             sys.exit(1)
1336         [ slice_hrn, input_time ] = args
1337         # slice urn    
1338         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1339         # time: don't try to be smart on the time format, server-side will
1340         # creds
1341         slice_cred = self.slice_credential(args[0])
1342         creds = [slice_cred]
1343         # options and call_id when supported
1344         api_options = {}
1345         api_options['call_id']=unique_call_id()
1346         if options.show_credential:
1347             show_credentials(creds)
1348         result =  server.Renew([slice_urn], creds, input_time, *self.ois(server,api_options))
1349         value = ReturnValue.get_value(result)
1350         if self.options.raw:
1351             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1352         else:
1353             print value
1354         return value
1355
1356
1357     @register_command("slice_hrn","")
1358     def shutdown(self, options, args):
1359         """
1360         shutdown named slice (Shutdown)
1361         """
1362         server = self.sliceapi()
1363         # slice urn
1364         slice_hrn = args[0]
1365         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1366         # creds
1367         slice_cred = self.slice_credential(slice_hrn)
1368         creds = [slice_cred]
1369         result = server.Shutdown(slice_urn, creds)
1370         value = ReturnValue.get_value(result)
1371         if self.options.raw:
1372             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1373         else:
1374             print value
1375         return value         
1376     
1377
1378     @register_command("[name]","")
1379     def gid(self, options, args):
1380         """
1381         Create a GID (CreateGid)
1382         """
1383         if len(args) < 1:
1384             self.print_help()
1385             sys.exit(1)
1386         target_hrn = args[0]
1387         my_gid_string = open(self.client_bootstrap.my_gid()).read() 
1388         gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1389         if options.file:
1390             filename = options.file
1391         else:
1392             filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1393         self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1394         GID(string=gid).save_to_file(filename)
1395          
1396     ####################
1397     @register_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1398
1399   will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1400   the set of credentials in the scope for this call would be
1401   (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1402       as per -u/--user
1403   (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1404       as per -p/--pi
1405   (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1406   (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1407       because of the two -s options
1408
1409 """)
1410     def delegate (self, options, args):
1411         """
1412         (locally) create delegate credential for use by given hrn
1413   make sure to check for 'sfi myslice' instead if you plan
1414   on using MySlice
1415         """
1416         if len(args) != 1:
1417             self.print_help()
1418             sys.exit(1)
1419         to_hrn = args[0]
1420         # support for several delegations in the same call
1421         # so first we gather the things to do
1422         tuples=[]
1423         for slice_hrn in options.delegate_slices:
1424             message="%s.slice"%slice_hrn
1425             original = self.slice_credential_string(slice_hrn)
1426             tuples.append ( (message, original,) )
1427         if options.delegate_pi:
1428             my_authority=self.authority
1429             message="%s.pi"%my_authority
1430             original = self.my_authority_credential_string()
1431             tuples.append ( (message, original,) )
1432         for auth_hrn in options.delegate_auths:
1433             message="%s.auth"%auth_hrn
1434             original=self.authority_credential_string(auth_hrn)
1435             tuples.append ( (message, original, ) )
1436         # if nothing was specified at all at this point, let's assume -u
1437         if not tuples: options.delegate_user=True
1438         # this user cred
1439         if options.delegate_user:
1440             message="%s.user"%self.user
1441             original = self.my_credential_string
1442             tuples.append ( (message, original, ) )
1443
1444         # default type for beneficial is user unless -A
1445         if options.delegate_to_authority:       to_type='authority'
1446         else:                                   to_type='user'
1447
1448         # let's now handle all this
1449         # it's all in the filenaming scheme
1450         for (message,original) in tuples:
1451             delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1452             delegated_credential = Credential (string=delegated_string)
1453             filename = os.path.join ( self.options.sfi_dir,
1454                                       "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1455             delegated_credential.save_to_file(filename, save_parents=True)
1456             self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1457     
1458     ####################
1459     @register_command("","""$ less +/myslice sfi_config
1460 [myslice]
1461 backend  = http://manifold.pl.sophia.inria.fr:7080
1462 # the HRN that myslice uses, so that we are delegating to
1463 delegate = ple.upmc.slicebrowser
1464 # platform - this is a myslice concept
1465 platform = ple
1466 # username - as of this writing (May 2013) a simple login name
1467 username = thierry
1468
1469 $ sfi myslice
1470   will first collect the slices that you are part of, then make sure
1471   all your credentials are up-to-date (read: refresh expired ones)
1472   then compute delegated credentials for user 'ple.upmc.slicebrowser'
1473   and upload them all on myslice backend, using 'platform' and 'user'.
1474   A password will be prompted for the upload part.
1475
1476 $ sfi -v myslice  -- or sfi -vv myslice
1477   same but with more and more verbosity
1478
1479 $ sfi m
1480   is synonym to sfi myslice as no other command starts with an 'm'
1481 """
1482 ) # register_command
1483     def myslice (self, options, args):
1484
1485         """ This helper is for refreshing your credentials at myslice; it will
1486   * compute all the slices that you currently have credentials on
1487   * refresh all your credentials (you as a user and pi, your slices)
1488   * upload them to the manifold backend server
1489   for last phase, sfi_config is read to look for the [myslice] section, 
1490   and namely the 'backend', 'delegate' and 'user' settings"""
1491
1492         ##########
1493         if len(args)>0:
1494             self.print_help()
1495             sys.exit(1)
1496
1497         ### the rough sketch goes like this
1498         # (a) rain check for sufficient config in sfi_config
1499         # we don't allow to override these settings for now
1500         myslice_dict={}
1501         myslice_keys=['backend', 'delegate', 'platform', 'username']
1502         for key in myslice_keys:
1503             full_key="MYSLICE_" + key.upper()
1504             value=getattr(self.config_instance,full_key,None)
1505             if value:   myslice_dict[key]=value
1506             else:       print "Unsufficient config, missing key %s in [myslice] section of sfi_config"%key
1507         if len(myslice_dict) != len(myslice_keys):
1508             sys.exit(1)
1509
1510         # (b) figure whether we are PI for the authority where we belong
1511         sfi_logger.info("Resolving our own id")
1512         my_records=self.registry().Resolve(self.user,self.my_credential_string)
1513         if len(my_records)!=1: print "Cannot Resolve %s -- exiting"%self.user; sys.exit(1)
1514         my_record=my_records[0]
1515         sfi_logger.info("Checking for authorities that we are PI for")
1516         my_auths = my_record['reg-pi-authorities']
1517         sfi_logger.debug("Found %d authorities: %s"%(len(my_auths),my_auths))
1518
1519         # (c) get the set of slices that we are in
1520         sfi_logger.info("Checking for slices that we are member of")
1521         my_slices=my_record['reg-slices']
1522         sfi_logger.debug("Found %d slices: %s"%(len(my_slices),my_slices))
1523
1524         # (d) make sure we have *valid* credentials for all these
1525         hrn_credentials=[]
1526         hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1527         for auth_hrn in my_auths:
1528             hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1529         for slice_hrn in my_slices:
1530             hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1531
1532         # (e) check for the delegated version of these
1533         # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever 
1534         # switch to myslice using an authority instead of a user
1535         delegatee_type='user'
1536         delegatee_hrn=myslice_dict['delegate']
1537         hrn_delegated_credentials = [
1538             (hrn, htype, self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type),)
1539             for (hrn, htype, credential) in hrn_credentials ]
1540
1541         # (f) and finally upload them to manifold server
1542         # xxx todo add an option so the password can be set on the command line
1543         # (but *NOT* in the config file) so other apps can leverage this
1544         uploader = ManifoldUploader (logger=sfi_logger,
1545                                      url=myslice_dict['backend'],
1546                                      platform=myslice_dict['platform'],
1547                                      username=myslice_dict['username'])
1548         for (hrn,htype,delegated_credential) in hrn_delegated_credentials:
1549             sfi_logger.info("Uploading delegated credential for %s (%s)"%(hrn,htype))
1550             uploader.upload(delegated_credential,message=hrn)
1551         # at first I thought we would want to save these,
1552         # like 'sfi delegate does' but on second thought
1553         # it is probably not helpful as people would not
1554         # need to run 'sfi delegate' at all anymore
1555         return
1556
1557 # Thierry: I'm turning this off as a command, no idea what it's used for
1558 #    @register_command("cred","")
1559     def trusted(self, options, args):
1560         """
1561         return the trusted certs at this interface (get_trusted_certs)
1562         """ 
1563         trusted_certs = self.registry().get_trusted_certs()
1564         for trusted_cert in trusted_certs:
1565             gid = GID(string=trusted_cert)
1566             gid.dump()
1567             cert = Certificate(string=trusted_cert)
1568             self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1569         return 
1570