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