nicer sfi config
[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_global_parser(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_command_parser(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 ("add", "update"):
390             parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
391             parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
392             parser.add_option('-e', '--email', dest='email', default="",  help="email (mandatory for users)") 
393             parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file', 
394                               default=None)
395             parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
396                               default='', type="str", action='callback', callback=optparse_listvalue_callback)
397             parser.add_option('-r', '--researchers', dest='researchers', metavar='<researchers>', 
398                               help='Set/replace slice researchers', default='', type="str", action='callback', 
399                               callback=optparse_listvalue_callback)
400             parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
401                               default='', type="str", action='callback', callback=optparse_listvalue_callback)
402             parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
403                                action="callback", callback=optparse_dictvalue_callback, nargs=1,
404                                help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
405
406         # user specifies remote aggregate/sm/component                          
407         if command in ("resources", "describe", "allocate", "provision", "delete", "allocate", "provision", 
408                        "action", "shutdown", "renew", "status"):
409             parser.add_option("-d", "--delegate", dest="delegate", default=None, 
410                              action="store_true",
411                              help="Include a credential delegated to the user's root"+\
412                                   "authority in set of credentials for this call")
413
414         # show_credential option
415         if command in ("list","resources", "describe", "provision", "allocate", "add","update","remove","slices","delete","status","renew"):
416             parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
417                               help="show credential(s) used in human-readable form")
418         # registy filter option
419         if command in ("list", "show", "remove"):
420             parser.add_option("-t", "--type", dest="type", type="choice",
421                             help="type filter ([all]|user|slice|authority|node|aggregate)",
422                             choices=("all", "user", "slice", "authority", "node", "aggregate"),
423                             default="all")
424         if command in ("show"):
425             parser.add_option("-k","--key",dest="keys",action="append",default=[],
426                               help="specify specific keys to be displayed from record")
427         if command in ("resources", "describe"):
428             # rspec version
429             parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
430                               help="schema type and version of resulting RSpec")
431             # disable/enable cached rspecs
432             parser.add_option("-c", "--current", dest="current", default=False,
433                               action="store_true",  
434                               help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
435             # display formats
436             parser.add_option("-f", "--format", dest="format", type="choice",
437                              help="display format ([xml]|dns|ip)", default="xml",
438                              choices=("xml", "dns", "ip"))
439             #panos: a new option to define the type of information about resources a user is interested in
440             parser.add_option("-i", "--info", dest="info",
441                                 help="optional component information", default=None)
442             # a new option to retreive or not reservation-oriented RSpecs (leases)
443             parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
444                                 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
445                                 choices=("all", "resources", "leases"), default="resources")
446
447
448         if command in ("resources", "describe", "allocate", "provision", "show", "list", "gid"):
449            parser.add_option("-o", "--output", dest="file",
450                             help="output XML to file", metavar="FILE", default=None)
451
452         if command in ("show", "list"):
453            parser.add_option("-f", "--format", dest="format", type="choice",
454                              help="display format ([text]|xml)", default="text",
455                              choices=("text", "xml"))
456
457            parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
458                              help="output file format ([xml]|xmllist|hrnlist)", default="xml",
459                              choices=("xml", "xmllist", "hrnlist"))
460         if command == 'list':
461            parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
462                              help="list all child records", default=False)
463            parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
464                              help="gives details, like user keys", default=False)
465         if command in ("delegate"):
466            parser.add_option("-u", "--user",
467                              action="store_true", dest="delegate_user", default=False,
468                              help="delegate your own credentials; default if no other option is provided")
469            parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
470                              metavar="slice_hrn", help="delegate cred. for slice HRN")
471            parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
472                              metavar='auth_hrn', help="delegate cred for auth HRN")
473            # this primarily is a shorthand for -a my_hrn
474            parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
475                              help="delegate your PI credentials, so s.t. like -a your_hrn^")
476            parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
477                              help="""by default the mandatory argument is expected to be a user, 
478 use this if you mean an authority instead""")
479         
480         if command in ("version"):
481             parser.add_option("-R","--registry-version",
482                               action="store_true", dest="version_registry", default=False,
483                               help="probe registry version instead of sliceapi")
484             parser.add_option("-l","--local",
485                               action="store_true", dest="version_local", default=False,
486                               help="display version of the local client")
487
488         return parser
489
490         
491     #
492     # Main: parse arguments and dispatch to command
493     #
494     def dispatch(self, command, command_options, command_args):
495         method=getattr(self, command, None)
496         if not method:
497             print "Unknown command %s"%command
498             return
499         return method(command_options, command_args)
500
501     def main(self):
502         self.sfi_parser = self.create_global_parser()
503         (options, args) = self.sfi_parser.parse_args()
504         if options.help: 
505             self.print_commands_help(options)
506             sys.exit(1)
507         self.options = options
508
509         self.logger.setLevelFromOptVerbose(self.options.verbose)
510
511         if len(args) <= 0:
512             self.logger.critical("No command given. Use -h for help.")
513             self.print_commands_help(options)
514             return -1
515     
516         # complete / find unique match with command set
517         command_candidates = Candidates (commands_list)
518         input = args[0]
519         command = command_candidates.only_match(input)
520         if not command:
521             self.print_commands_help(options)
522             sys.exit(1)
523         # second pass options parsing
524         self.command=command
525         self.command_parser = self.create_command_parser(command)
526         (command_options, command_args) = self.command_parser.parse_args(args[1:])
527         if command_options.help:
528             self.print_help()
529             sys.exit(1)
530         self.command_options = command_options
531
532         self.read_config () 
533         self.bootstrap ()
534         self.logger.debug("Command=%s" % self.command)
535
536         try:
537             self.dispatch(command, command_options, command_args)
538         except SystemExit:
539             return 1
540         except:
541             self.logger.log_exc ("sfi command %s failed"%command)
542             return 1
543
544         return 0
545     
546     ####################
547     def read_config(self):
548         config_file = os.path.join(self.options.sfi_dir,"sfi_config")
549         shell_config_file  = os.path.join(self.options.sfi_dir,"sfi_config.sh")
550         try:
551             if Config.is_ini(config_file):
552                 config = Config (config_file)
553             else:
554                 # try upgrading from shell config format
555                 fp, fn = mkstemp(suffix='sfi_config', text=True)  
556                 config = Config(fn)
557                 # we need to preload the sections we want parsed 
558                 # from the shell config
559                 config.add_section('sfi')
560                 # sface users should be able to use this same file to configure their stuff
561                 config.add_section('sface')
562                 # manifold users should be able to specify the details 
563                 # of their backend server here for 'sfi myslice'
564                 config.add_section('myslice')
565                 config.load(config_file)
566                 # back up old config
567                 shutil.move(config_file, shell_config_file)
568                 # write new config
569                 config.save(config_file)
570                  
571         except:
572             self.logger.critical("Failed to read configuration file %s"%config_file)
573             self.logger.info("Make sure to remove the export clauses and to add quotes")
574             if self.options.verbose==0:
575                 self.logger.info("Re-run with -v for more details")
576             else:
577                 self.logger.log_exc("Could not read config file %s"%config_file)
578             sys.exit(1)
579      
580         self.config_instance=config
581         errors = 0
582         # Set SliceMgr URL
583         if (self.options.sm is not None):
584            self.sm_url = self.options.sm
585         elif hasattr(config, "SFI_SM"):
586            self.sm_url = config.SFI_SM
587         else:
588            self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
589            errors += 1 
590
591         # Set Registry URL
592         if (self.options.registry is not None):
593            self.reg_url = self.options.registry
594         elif hasattr(config, "SFI_REGISTRY"):
595            self.reg_url = config.SFI_REGISTRY
596         else:
597            self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
598            errors += 1 
599
600         # Set user HRN
601         if (self.options.user is not None):
602            self.user = self.options.user
603         elif hasattr(config, "SFI_USER"):
604            self.user = config.SFI_USER
605         else:
606            self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
607            errors += 1 
608
609         # Set authority HRN
610         if (self.options.auth is not None):
611            self.authority = self.options.auth
612         elif hasattr(config, "SFI_AUTH"):
613            self.authority = config.SFI_AUTH
614         else:
615            self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
616            errors += 1 
617
618         self.config_file=config_file
619         if errors:
620            sys.exit(1)
621
622     #
623     # Get various credential and spec files
624     #
625     # Establishes limiting conventions
626     #   - conflates MAs and SAs
627     #   - assumes last token in slice name is unique
628     #
629     # Bootstraps credentials
630     #   - bootstrap user credential from self-signed certificate
631     #   - bootstrap authority credential from user credential
632     #   - bootstrap slice credential from user credential
633     #
634     
635     # init self-signed cert, user credentials and gid
636     def bootstrap (self):
637         client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
638                                                logger=self.logger)
639         # if -k is provided, use this to initialize private key
640         if self.options.user_private_key:
641             client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
642         else:
643             # trigger legacy compat code if needed 
644             # the name has changed from just <leaf>.pkey to <hrn>.pkey
645             if not os.path.isfile(client_bootstrap.private_key_filename()):
646                 self.logger.info ("private key not found, trying legacy name")
647                 try:
648                     legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
649                     self.logger.debug("legacy_private_key=%s"%legacy_private_key)
650                     client_bootstrap.init_private_key_if_missing (legacy_private_key)
651                     self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
652                 except:
653                     self.logger.log_exc("Can't find private key ")
654                     sys.exit(1)
655             
656         # make it bootstrap
657         client_bootstrap.bootstrap_my_gid()
658         # extract what's needed
659         self.private_key = client_bootstrap.private_key()
660         self.my_credential_string = client_bootstrap.my_credential_string ()
661         self.my_credential = {'geni_type': 'geni_sfa',
662                               'geni_version': '3.0', 
663                               'geni_value': self.my_credential_string}
664         self.my_gid = client_bootstrap.my_gid ()
665         self.client_bootstrap = client_bootstrap
666
667
668     def my_authority_credential_string(self):
669         if not self.authority:
670             self.logger.critical("no authority specified. Use -a or set SF_AUTH")
671             sys.exit(-1)
672         return self.client_bootstrap.authority_credential_string (self.authority)
673
674     def authority_credential_string(self, auth_hrn):
675         return self.client_bootstrap.authority_credential_string (auth_hrn)
676
677     def slice_credential_string(self, name):
678         return self.client_bootstrap.slice_credential_string (name)
679
680     def slice_credential(self, name):
681         return {'geni_type': 'geni_sfa',
682                 'geni_version': '3.0',
683                 'geni_value': self.slice_credential_string(name)}    
684
685     # xxx should be supported by sfaclientbootstrap as well
686     def delegate_cred(self, object_cred, hrn, type='authority'):
687         # the gid and hrn of the object we are delegating
688         if isinstance(object_cred, str):
689             object_cred = Credential(string=object_cred) 
690         object_gid = object_cred.get_gid_object()
691         object_hrn = object_gid.get_hrn()
692     
693         if not object_cred.get_privileges().get_all_delegate():
694             self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
695             return
696
697         # the delegating user's gid
698         caller_gidfile = self.my_gid()
699   
700         # the gid of the user who will be delegated to
701         delegee_gid = self.client_bootstrap.gid(hrn,type)
702         delegee_hrn = delegee_gid.get_hrn()
703         dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
704         return dcred.save_to_string(save_parents=True)
705      
706     #
707     # Management of the servers
708     # 
709
710     def registry (self):
711         # cache the result
712         if not hasattr (self, 'registry_proxy'):
713             self.logger.info("Contacting Registry at: %s"%self.reg_url)
714             self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid, 
715                                                  timeout=self.options.timeout, verbose=self.options.debug)  
716         return self.registry_proxy
717
718     def sliceapi (self):
719         # cache the result
720         if not hasattr (self, 'sliceapi_proxy'):
721             # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
722             if hasattr(self.command_options,'component') and self.command_options.component:
723                 # resolve the hrn at the registry
724                 node_hrn = self.command_options.component
725                 records = self.registry().Resolve(node_hrn, self.my_credential_string)
726                 records = filter_records('node', records)
727                 if not records:
728                     self.logger.warning("No such component:%r"% opts.component)
729                 record = records[0]
730                 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
731                 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
732             else:
733                 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
734                 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
735                     self.sm_url = 'http://' + self.sm_url
736                 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
737                 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid, 
738                                                      timeout=self.options.timeout, verbose=self.options.debug)  
739         return self.sliceapi_proxy
740
741     def get_cached_server_version(self, server):
742         # check local cache first
743         cache = None
744         version = None 
745         cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
746         cache_key = server.url + "-version"
747         try:
748             cache = Cache(cache_file)
749         except IOError:
750             cache = Cache()
751             self.logger.info("Local cache not found at: %s" % cache_file)
752
753         if cache:
754             version = cache.get(cache_key)
755
756         if not version: 
757             result = server.GetVersion()
758             version= ReturnValue.get_value(result)
759             # cache version for 20 minutes
760             cache.add(cache_key, version, ttl= 60*20)
761             self.logger.info("Updating cache file %s" % cache_file)
762             cache.save_to_file(cache_file)
763
764         return version   
765         
766     ### resurrect this temporarily so we can support V1 aggregates for a while
767     def server_supports_options_arg(self, server):
768         """
769         Returns true if server support the optional call_id arg, false otherwise. 
770         """
771         server_version = self.get_cached_server_version(server)
772         result = False
773         # xxx need to rewrite this 
774         if int(server_version.get('geni_api')) >= 2:
775             result = True
776         return result
777
778     def server_supports_call_id_arg(self, server):
779         server_version = self.get_cached_server_version(server)
780         result = False      
781         if 'sfa' in server_version and 'code_tag' in server_version:
782             code_tag = server_version['code_tag']
783             code_tag_parts = code_tag.split("-")
784             version_parts = code_tag_parts[0].split(".")
785             major, minor = version_parts[0], version_parts[1]
786             rev = code_tag_parts[1]
787             if int(major) == 1 and minor == 0 and build >= 22:
788                 result = True
789         return result                 
790
791     ### ois = options if supported
792     # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
793     def ois (self, server, option_dict):
794         if self.server_supports_options_arg (server): 
795             return [option_dict]
796         elif self.server_supports_call_id_arg (server):
797             return [ unique_call_id () ]
798         else: 
799             return []
800
801     ### cis = call_id if supported - like ois
802     def cis (self, server):
803         if self.server_supports_call_id_arg (server):
804             return [ unique_call_id ]
805         else:
806             return []
807
808     ######################################## miscell utilities
809     def get_rspec_file(self, rspec):
810        if (os.path.isabs(rspec)):
811           file = rspec
812        else:
813           file = os.path.join(self.options.sfi_dir, rspec)
814        if (os.path.isfile(file)):
815           return file
816        else:
817           self.logger.critical("No such rspec file %s"%rspec)
818           sys.exit(1)
819     
820     def get_record_file(self, record):
821        if (os.path.isabs(record)):
822           file = record
823        else:
824           file = os.path.join(self.options.sfi_dir, record)
825        if (os.path.isfile(file)):
826           return file
827        else:
828           self.logger.critical("No such registry record file %s"%record)
829           sys.exit(1)
830
831
832     #==========================================================================
833     # Following functions implement the commands
834     #
835     # Registry-related commands
836     #==========================================================================
837
838     @register_command("","")
839     def config (self, options, args):
840         "Display contents of current config"
841         print "# From configuration file %s"%self.config_file
842         flags=[ ('sfi', [ ('registry','reg_url'),
843                           ('auth','authority'),
844                           ('user','user'),
845                           ('sm','sm_url'),
846                           ]),
847                 ]
848         if options.myslice:
849             flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
850
851         for (section, tuples) in flags:
852             print "[%s]"%section
853             try:
854                 for (external_name, internal_name) in tuples:
855                     print "%-20s = %s"%(external_name,getattr(self,internal_name))
856             except:
857                 for name in tuples:
858                     varname="%s_%s"%(section.upper(),name.upper())
859                     value=getattr(self.config_instance,varname)
860                     print "%-20s = %s"%(name,value)
861
862     @register_command("","")
863     def version(self, options, args):
864         """
865         display an SFA server version (GetVersion)
866   or version information about sfi itself
867         """
868         if options.version_local:
869             version=version_core()
870         else:
871             if options.version_registry:
872                 server=self.registry()
873             else:
874                 server = self.sliceapi()
875             result = server.GetVersion()
876             version = ReturnValue.get_value(result)
877         if self.options.raw:
878             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
879         else:
880             pprinter = PrettyPrinter(indent=4)
881             pprinter.pprint(version)
882
883     @register_command("authority","")
884     def list(self, options, args):
885         """
886         list entries in named authority registry (List)
887         """
888         if len(args)!= 1:
889             self.print_help()
890             sys.exit(1)
891         hrn = args[0]
892         opts = {}
893         if options.recursive:
894             opts['recursive'] = options.recursive
895         
896         if options.show_credential:
897             show_credentials(self.my_credential_string)
898         try:
899             list = self.registry().List(hrn, self.my_credential_string, options)
900         except IndexError:
901             raise Exception, "Not enough parameters for the 'list' command"
902
903         # filter on person, slice, site, node, etc.
904         # This really should be in the self.filter_records funct def comment...
905         list = filter_records(options.type, list)
906         terminal_render (list, options)
907         if options.file:
908             save_records_to_file(options.file, list, options.fileformat)
909         return
910     
911     @register_command("name","")
912     def show(self, options, args):
913         """
914         show details about named registry record (Resolve)
915         """
916         if len(args)!= 1:
917             self.print_help()
918             sys.exit(1)
919         hrn = args[0]
920         # explicitly require Resolve to run in details mode
921         record_dicts = self.registry().Resolve(hrn, self.my_credential_string, {'details':True})
922         record_dicts = filter_records(options.type, record_dicts)
923         if not record_dicts:
924             self.logger.error("No record of type %s"% options.type)
925             return
926         # user has required to focus on some keys
927         if options.keys:
928             def project (record):
929                 projected={}
930                 for key in options.keys:
931                     try: projected[key]=record[key]
932                     except: pass
933                 return projected
934             record_dicts = [ project (record) for record in record_dicts ]
935         records = [ Record(dict=record_dict) for record_dict in record_dicts ]
936         for record in records:
937             if (options.format == "text"):      record.dump(sort=True)  
938             else:                               print record.save_as_xml() 
939         if options.file:
940             save_records_to_file(options.file, record_dicts, options.fileformat)
941         return
942     
943     @register_command("[xml-filename]","")
944     def add(self, options, args):
945         """add record into registry (Register) 
946   from command line options (recommended) 
947   old-school method involving an xml file still supported"""
948
949         auth_cred = self.my_authority_credential_string()
950         if options.show_credential:
951             show_credentials(auth_cred)
952         record_dict = {}
953         if len(args) > 1:
954             self.print_help()
955             sys.exit(1)
956         if len(args)==1:
957             try:
958                 record_filepath = args[0]
959                 rec_file = self.get_record_file(record_filepath)
960                 record_dict.update(load_record_from_file(rec_file).todict())
961             except:
962                 print "Cannot load record file %s"%record_filepath
963                 sys.exit(1)
964         if options:
965             record_dict.update(load_record_from_opts(options).todict())
966         # we should have a type by now
967         if 'type' not in record_dict :
968             self.print_help()
969             sys.exit(1)
970         # this is still planetlab dependent.. as plc will whine without that
971         # also, it's only for adding
972         if record_dict['type'] == 'user':
973             if not 'first_name' in record_dict:
974                 record_dict['first_name'] = record_dict['hrn']
975             if 'last_name' not in record_dict:
976                 record_dict['last_name'] = record_dict['hrn'] 
977         return self.registry().Register(record_dict, auth_cred)
978     
979     @register_command("[xml-filename]","")
980     def update(self, options, args):
981         """update record into registry (Update) 
982   from command line options (recommended) 
983   old-school method involving an xml file still supported"""
984         record_dict = {}
985         if len(args) > 0:
986             record_filepath = args[0]
987             rec_file = self.get_record_file(record_filepath)
988             record_dict.update(load_record_from_file(rec_file).todict())
989         if options:
990             record_dict.update(load_record_from_opts(options).todict())
991         # at the very least we need 'type' here
992         if 'type' not in record_dict:
993             self.print_help()
994             sys.exit(1)
995
996         # don't translate into an object, as this would possibly distort
997         # user-provided data; e.g. add an 'email' field to Users
998         if record_dict['type'] == "user":
999             if record_dict['hrn'] == self.user:
1000                 cred = self.my_credential_string
1001             else:
1002                 cred = self.my_authority_credential_string()
1003         elif record_dict['type'] in ["slice"]:
1004             try:
1005                 cred = self.slice_credential_string(record_dict['hrn'])
1006             except ServerException, e:
1007                # XXX smbaker -- once we have better error return codes, update this
1008                # to do something better than a string compare
1009                if "Permission error" in e.args[0]:
1010                    cred = self.my_authority_credential_string()
1011                else:
1012                    raise
1013         elif record_dict['type'] in ["authority"]:
1014             cred = self.my_authority_credential_string()
1015         elif record_dict['type'] == 'node':
1016             cred = self.my_authority_credential_string()
1017         else:
1018             raise "unknown record type" + record_dict['type']
1019         if options.show_credential:
1020             show_credentials(cred)
1021         return self.registry().Update(record_dict, cred)
1022   
1023     @register_command("name","")
1024     def remove(self, options, args):
1025         "remove registry record by name (Remove)"
1026         auth_cred = self.my_authority_credential_string()
1027         if len(args)!=1:
1028             self.print_help()
1029             sys.exit(1)
1030         hrn = args[0]
1031         type = options.type 
1032         if type in ['all']:
1033             type = '*'
1034         if options.show_credential:
1035             show_credentials(auth_cred)
1036         return self.registry().Remove(hrn, auth_cred, type)
1037     
1038     # ==================================================================
1039     # Slice-related commands
1040     # ==================================================================
1041
1042     @register_command("","")
1043     def slices(self, options, args):
1044         "list instantiated slices (ListSlices) - returns urn's"
1045         server = self.sliceapi()
1046         # creds
1047         creds = [self.my_credential_string]
1048         # options and call_id when supported
1049         api_options = {}
1050         api_options['call_id']=unique_call_id()
1051         if options.show_credential:
1052             show_credentials(creds)
1053         result = server.ListSlices(creds, *self.ois(server,api_options))
1054         value = ReturnValue.get_value(result)
1055         if self.options.raw:
1056             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1057         else:
1058             display_list(value)
1059         return
1060
1061     # show rspec for named slice
1062     @register_command("","")
1063     def resources(self, options, args):
1064         """
1065         discover available resources (ListResources)
1066         """
1067         server = self.sliceapi()
1068
1069         # set creds
1070         creds = [self.my_credential]
1071         if options.delegate:
1072             creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1073         if options.show_credential:
1074             show_credentials(creds)
1075
1076         # no need to check if server accepts the options argument since the options has
1077         # been a required argument since v1 API
1078         api_options = {}
1079         # always send call_id to v2 servers
1080         api_options ['call_id'] = unique_call_id()
1081         # ask for cached value if available
1082         api_options ['cached'] = True
1083         if options.info:
1084             api_options['info'] = options.info
1085         if options.list_leases:
1086             api_options['list_leases'] = options.list_leases
1087         if options.current:
1088             if options.current == True:
1089                 api_options['cached'] = False
1090             else:
1091                 api_options['cached'] = True
1092         if options.rspec_version:
1093             version_manager = VersionManager()
1094             server_version = self.get_cached_server_version(server)
1095             if 'sfa' in server_version:
1096                 # just request the version the client wants
1097                 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1098             else:
1099                 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1100         else:
1101             api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1102         result = server.ListResources (creds, api_options)
1103         value = ReturnValue.get_value(result)
1104         if self.options.raw:
1105             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1106         if options.file is not None:
1107             save_rspec_to_file(value, options.file)
1108         if (self.options.raw is None) and (options.file is None):
1109             display_rspec(value, options.format)
1110
1111         return
1112
1113     @register_command("slice_hrn","")
1114     def describe(self, options, args):
1115         """
1116         shows currently allocated/provisioned resources 
1117         of the named slice or set of slivers (Describe) 
1118         """
1119         server = self.sliceapi()
1120
1121         # set creds
1122         creds = [self.slice_credential(args[0])]
1123         if options.delegate:
1124             creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1125         if options.show_credential:
1126             show_credentials(creds)
1127
1128         api_options = {'call_id': unique_call_id(),
1129                        'cached': True,
1130                        'info': options.info,
1131                        'list_leases': options.list_leases,
1132                        'geni_rspec_version': {'type': 'geni', 'version': '3.0'},
1133                       }
1134         if options.rspec_version:
1135             version_manager = VersionManager()
1136             server_version = self.get_cached_server_version(server)
1137             if 'sfa' in server_version:
1138                 # just request the version the client wants
1139                 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1140             else:
1141                 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1142         urn = Xrn(args[0], type='slice').get_urn()        
1143         result = server.Describe([urn], creds, api_options)
1144         value = ReturnValue.get_value(result)
1145         if self.options.raw:
1146             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1147         if options.file is not None:
1148             save_rspec_to_file(value, options.file)
1149         if (self.options.raw is None) and (options.file is None):
1150             display_rspec(value, options.format)
1151
1152         return 
1153
1154     @register_command("slice_hrn","")
1155     def delete(self, options, args):
1156         """
1157         de-allocate and de-provision all or named slivers of the slice (Delete)
1158         """
1159         server = self.sliceapi()
1160
1161         # slice urn
1162         slice_hrn = args[0]
1163         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1164
1165         # creds
1166         slice_cred = self.slice_credential(slice_hrn)
1167         creds = [slice_cred]
1168         
1169         # options and call_id when supported
1170         api_options = {}
1171         api_options ['call_id'] = unique_call_id()
1172         if options.show_credential:
1173             show_credentials(creds)
1174         result = server.Delete([slice_urn], creds, *self.ois(server, api_options ) )
1175         value = ReturnValue.get_value(result)
1176         if self.options.raw:
1177             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1178         else:
1179             print value
1180         return value
1181
1182     @register_command("slice_hrn rspec","")
1183     def allocate(self, options, args):
1184         """
1185          allocate resources to the named slice (Allocate)
1186         """
1187         server = self.sliceapi()
1188         server_version = self.get_cached_server_version(server)
1189         slice_hrn = args[0]
1190         slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1191
1192         # credentials
1193         creds = [self.slice_credential(slice_hrn)]
1194
1195         delegated_cred = None
1196         if server_version.get('interface') == 'slicemgr':
1197             # delegate our cred to the slice manager
1198             # do not delegate cred to slicemgr...not working at the moment
1199             pass
1200             #if server_version.get('hrn'):
1201             #    delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1202             #elif server_version.get('urn'):
1203             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1204
1205         if options.show_credential:
1206             show_credentials(creds)
1207
1208         # rspec
1209         rspec_file = self.get_rspec_file(args[1])
1210         rspec = open(rspec_file).read()
1211         api_options = {}
1212         api_options ['call_id'] = unique_call_id()
1213         result = server.Allocate(slice_urn, creds, rspec, api_options)
1214         value = ReturnValue.get_value(result)
1215         if self.options.raw:
1216             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1217         if options.file is not None:
1218             save_rspec_to_file (value, options.file)
1219         if (self.options.raw is None) and (options.file is None):
1220             print value
1221         return value
1222         
1223
1224     @register_command("slice_hrn","")
1225     def provision(self, options, args):
1226         """
1227         provision already allocated resources of named slice (Provision)
1228         """
1229         server = self.sliceapi()
1230         server_version = self.get_cached_server_version(server)
1231         slice_hrn = args[0]
1232         slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1233
1234         # credentials
1235         creds = [self.slice_credential(slice_hrn)]
1236         delegated_cred = None
1237         if server_version.get('interface') == 'slicemgr':
1238             # delegate our cred to the slice manager
1239             # do not delegate cred to slicemgr...not working at the moment
1240             pass
1241             #if server_version.get('hrn'):
1242             #    delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1243             #elif server_version.get('urn'):
1244             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1245
1246         if options.show_credential:
1247             show_credentials(creds)
1248
1249         api_options = {}
1250         api_options ['call_id'] = unique_call_id()
1251
1252         # set the requtested rspec version
1253         version_manager = VersionManager()
1254         rspec_version = version_manager._get_version('geni', '3.0').to_dict()
1255         api_options['geni_rspec_version'] = rspec_version
1256
1257         # users
1258         # need to pass along user keys to the aggregate.
1259         # users = [
1260         #  { urn: urn:publicid:IDN+emulab.net+user+alice
1261         #    keys: [<ssh key A>, <ssh key B>]
1262         #  }]
1263         users = []
1264         slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1265         if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
1266             slice_record = slice_records[0]
1267             user_hrns = slice_record['researcher']
1268             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1269             user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1270             users = pg_users_arg(user_records)
1271         
1272         api_options['geni_users'] = users
1273         result = server.Provision([slice_urn], creds, api_options)
1274         value = ReturnValue.get_value(result)
1275         if self.options.raw:
1276             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1277         if options.file is not None:
1278             save_rspec_to_file (value, options.file)
1279         if (self.options.raw is None) and (options.file is None):
1280             print value
1281         return value     
1282
1283     @register_command("slice_hrn","")
1284     def status(self, options, args):
1285         """
1286         retrieve the status of the slivers belonging to tne named slice (Status)
1287         """
1288         server = self.sliceapi()
1289
1290         # slice urn
1291         slice_hrn = args[0]
1292         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1293
1294         # creds 
1295         slice_cred = self.slice_credential(slice_hrn)
1296         creds = [slice_cred]
1297
1298         # options and call_id when supported
1299         api_options = {}
1300         api_options['call_id']=unique_call_id()
1301         if options.show_credential:
1302             show_credentials(creds)
1303         result = server.Status([slice_urn], creds, *self.ois(server,api_options))
1304         value = ReturnValue.get_value(result)
1305         if self.options.raw:
1306             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1307         else:
1308             print value
1309         # Thierry: seemed to be missing
1310         return value
1311
1312     @register_command("slice_hrn action","")
1313     def action(self, options, args):
1314         """
1315         Perform the named operational action on these slivers
1316         """
1317         server = self.sliceapi()
1318         api_options = {}
1319         # slice urn
1320         slice_hrn = args[0]
1321         action = args[1]
1322         slice_urn = Xrn(slice_hrn, type='slice').get_urn() 
1323         # cred
1324         slice_cred = self.slice_credential(args[0])
1325         creds = [slice_cred]
1326         if options.delegate:
1327             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1328             creds.append(delegated_cred)
1329         
1330         result = server.PerformOperationalAction([slice_urn], creds, action , api_options)
1331         value = ReturnValue.get_value(result)
1332         if self.options.raw:
1333             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1334         else:
1335             print value
1336         return value
1337
1338     @register_command("slice_hrn time","")
1339     def renew(self, options, args):
1340         """
1341         renew slice (RenewSliver)
1342         """
1343         server = self.sliceapi()
1344         if len(args) != 2:
1345             self.print_help()
1346             sys.exit(1)
1347         [ slice_hrn, input_time ] = args
1348         # slice urn    
1349         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1350         # time: don't try to be smart on the time format, server-side will
1351         # creds
1352         slice_cred = self.slice_credential(args[0])
1353         creds = [slice_cred]
1354         # options and call_id when supported
1355         api_options = {}
1356         api_options['call_id']=unique_call_id()
1357         if options.show_credential:
1358             show_credentials(creds)
1359         result =  server.Renew([slice_urn], creds, input_time, *self.ois(server,api_options))
1360         value = ReturnValue.get_value(result)
1361         if self.options.raw:
1362             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1363         else:
1364             print value
1365         return value
1366
1367
1368     @register_command("slice_hrn","")
1369     def shutdown(self, options, args):
1370         """
1371         shutdown named slice (Shutdown)
1372         """
1373         server = self.sliceapi()
1374         # slice urn
1375         slice_hrn = args[0]
1376         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1377         # creds
1378         slice_cred = self.slice_credential(slice_hrn)
1379         creds = [slice_cred]
1380         result = server.Shutdown(slice_urn, creds)
1381         value = ReturnValue.get_value(result)
1382         if self.options.raw:
1383             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1384         else:
1385             print value
1386         return value         
1387     
1388
1389     @register_command("[name]","")
1390     def gid(self, options, args):
1391         """
1392         Create a GID (CreateGid)
1393         """
1394         if len(args) < 1:
1395             self.print_help()
1396             sys.exit(1)
1397         target_hrn = args[0]
1398         my_gid_string = open(self.client_bootstrap.my_gid()).read() 
1399         gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1400         if options.file:
1401             filename = options.file
1402         else:
1403             filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1404         self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1405         GID(string=gid).save_to_file(filename)
1406          
1407     ####################
1408     @register_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1409
1410   will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1411   the set of credentials in the scope for this call would be
1412   (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1413       as per -u/--user
1414   (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1415       as per -p/--pi
1416   (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1417   (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1418       because of the two -s options
1419
1420 """)
1421     def delegate (self, options, args):
1422         """
1423         (locally) create delegate credential for use by given hrn
1424   make sure to check for 'sfi myslice' instead if you plan
1425   on using MySlice
1426         """
1427         if len(args) != 1:
1428             self.print_help()
1429             sys.exit(1)
1430         to_hrn = args[0]
1431         # support for several delegations in the same call
1432         # so first we gather the things to do
1433         tuples=[]
1434         for slice_hrn in options.delegate_slices:
1435             message="%s.slice"%slice_hrn
1436             original = self.slice_credential_string(slice_hrn)
1437             tuples.append ( (message, original,) )
1438         if options.delegate_pi:
1439             my_authority=self.authority
1440             message="%s.pi"%my_authority
1441             original = self.my_authority_credential_string()
1442             tuples.append ( (message, original,) )
1443         for auth_hrn in options.delegate_auths:
1444             message="%s.auth"%auth_hrn
1445             original=self.authority_credential_string(auth_hrn)
1446             tuples.append ( (message, original, ) )
1447         # if nothing was specified at all at this point, let's assume -u
1448         if not tuples: options.delegate_user=True
1449         # this user cred
1450         if options.delegate_user:
1451             message="%s.user"%self.user
1452             original = self.my_credential_string
1453             tuples.append ( (message, original, ) )
1454
1455         # default type for beneficial is user unless -A
1456         if options.delegate_to_authority:       to_type='authority'
1457         else:                                   to_type='user'
1458
1459         # let's now handle all this
1460         # it's all in the filenaming scheme
1461         for (message,original) in tuples:
1462             delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1463             delegated_credential = Credential (string=delegated_string)
1464             filename = os.path.join ( self.options.sfi_dir,
1465                                       "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1466             delegated_credential.save_to_file(filename, save_parents=True)
1467             self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1468     
1469     ####################
1470     @register_command("","""$ less +/myslice sfi_config
1471 [myslice]
1472 backend  = http://manifold.pl.sophia.inria.fr:7080
1473 # the HRN that myslice uses, so that we are delegating to
1474 delegate = ple.upmc.slicebrowser
1475 # platform - this is a myslice concept
1476 platform = ple
1477 # username - as of this writing (May 2013) a simple login name
1478 username = thierry
1479
1480 $ sfi myslice
1481   will first collect the slices that you are part of, then make sure
1482   all your credentials are up-to-date (read: refresh expired ones)
1483   then compute delegated credentials for user 'ple.upmc.slicebrowser'
1484   and upload them all on myslice backend, using 'platform' and 'user'.
1485   A password will be prompted for the upload part.
1486
1487 $ sfi -v myslice  -- or sfi -vv myslice
1488   same but with more and more verbosity
1489
1490 $ sfi m
1491   is synonym to sfi myslice as no other command starts with an 'm'
1492 """
1493 ) # register_command
1494     def myslice (self, options, args):
1495
1496         """ This helper is for refreshing your credentials at myslice; it will
1497   * compute all the slices that you currently have credentials on
1498   * refresh all your credentials (you as a user and pi, your slices)
1499   * upload them to the manifold backend server
1500   for last phase, sfi_config is read to look for the [myslice] section, 
1501   and namely the 'backend', 'delegate' and 'user' settings"""
1502
1503         ##########
1504         if len(args)>0:
1505             self.print_help()
1506             sys.exit(1)
1507
1508         ### the rough sketch goes like this
1509         # (a) rain check for sufficient config in sfi_config
1510         # we don't allow to override these settings for now
1511         myslice_dict={}
1512         myslice_keys=['backend', 'delegate', 'platform', 'username']
1513         for key in myslice_keys:
1514             full_key="MYSLICE_" + key.upper()
1515             value=getattr(self.config_instance,full_key,None)
1516             if value:   myslice_dict[key]=value
1517             else:       print "Unsufficient config, missing key %s in [myslice] section of sfi_config"%key
1518         if len(myslice_dict) != len(myslice_keys):
1519             sys.exit(1)
1520
1521         # (b) figure whether we are PI for the authority where we belong
1522         sfi_logger.info("Resolving our own id")
1523         my_records=self.registry().Resolve(self.user,self.my_credential_string)
1524         if len(my_records)!=1: print "Cannot Resolve %s -- exiting"%self.user; sys.exit(1)
1525         my_record=my_records[0]
1526         sfi_logger.info("Checking for authorities that we are PI for")
1527         my_auths = my_record['reg-pi-authorities']
1528         sfi_logger.debug("Found %d authorities: %s"%(len(my_auths),my_auths))
1529
1530         # (c) get the set of slices that we are in
1531         sfi_logger.info("Checking for slices that we are member of")
1532         my_slices=my_record['reg-slices']
1533         sfi_logger.debug("Found %d slices: %s"%(len(my_slices),my_slices))
1534
1535         # (d) make sure we have *valid* credentials for all these
1536         hrn_credentials=[]
1537         hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1538         for auth_hrn in my_auths:
1539             hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1540         for slice_hrn in my_slices:
1541             hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1542
1543         # (e) check for the delegated version of these
1544         # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever 
1545         # switch to myslice using an authority instead of a user
1546         delegatee_type='user'
1547         delegatee_hrn=myslice_dict['delegate']
1548         hrn_delegated_credentials = [
1549             (hrn, htype, self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type),)
1550             for (hrn, htype, credential) in hrn_credentials ]
1551
1552         # (f) and finally upload them to manifold server
1553         # xxx todo add an option so the password can be set on the command line
1554         # (but *NOT* in the config file) so other apps can leverage this
1555         uploader = ManifoldUploader (logger=sfi_logger,
1556                                      url=myslice_dict['backend'],
1557                                      platform=myslice_dict['platform'],
1558                                      username=myslice_dict['username'])
1559         for (hrn,htype,delegated_credential) in hrn_delegated_credentials:
1560             sfi_logger.info("Uploading delegated credential for %s (%s)"%(hrn,htype))
1561             uploader.upload(delegated_credential,message=hrn)
1562         # at first I thought we would want to save these,
1563         # like 'sfi delegate does' but on second thought
1564         # it is probably not helpful as people would not
1565         # need to run 'sfi delegate' at all anymore
1566         return
1567
1568 # Thierry: I'm turning this off as a command, no idea what it's used for
1569 #    @register_command("cred","")
1570     def trusted(self, options, args):
1571         """
1572         return the trusted certs at this interface (get_trusted_certs)
1573         """ 
1574         trusted_certs = self.registry().get_trusted_certs()
1575         for trusted_cert in trusted_certs:
1576             gid = GID(string=trusted_cert)
1577             gid.dump()
1578             cert = Certificate(string=trusted_cert)
1579             self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1580         return 
1581