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