more cosmetic
[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 version(self, options, args):
845         """
846         display an SFA server version (GetVersion)
847   or version information about sfi itself
848         """
849         if options.version_local:
850             version=version_core()
851         else:
852             if options.version_registry:
853                 server=self.registry()
854             else:
855                 server = self.sliceapi()
856             result = server.GetVersion()
857             version = ReturnValue.get_value(result)
858         if self.options.raw:
859             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
860         else:
861             pprinter = PrettyPrinter(indent=4)
862             pprinter.pprint(version)
863
864     @register_command("authority","")
865     def list(self, options, args):
866         """
867         list entries in named authority registry (List)
868         """
869         if len(args)!= 1:
870             self.print_help()
871             sys.exit(1)
872         hrn = args[0]
873         opts = {}
874         if options.recursive:
875             opts['recursive'] = options.recursive
876         
877         if options.show_credential:
878             show_credentials(self.my_credential_string)
879         try:
880             list = self.registry().List(hrn, self.my_credential_string, options)
881         except IndexError:
882             raise Exception, "Not enough parameters for the 'list' command"
883
884         # filter on person, slice, site, node, etc.
885         # This really should be in the self.filter_records funct def comment...
886         list = filter_records(options.type, list)
887         terminal_render (list, options)
888         if options.file:
889             save_records_to_file(options.file, list, options.fileformat)
890         return
891     
892     @register_command("name","")
893     def show(self, options, args):
894         """
895         show details about named registry record (Resolve)
896         """
897         if len(args)!= 1:
898             self.print_help()
899             sys.exit(1)
900         hrn = args[0]
901         # explicitly require Resolve to run in details mode
902         record_dicts = self.registry().Resolve(hrn, self.my_credential_string, {'details':True})
903         record_dicts = filter_records(options.type, record_dicts)
904         if not record_dicts:
905             self.logger.error("No record of type %s"% options.type)
906             return
907         # user has required to focus on some keys
908         if options.keys:
909             def project (record):
910                 projected={}
911                 for key in options.keys:
912                     try: projected[key]=record[key]
913                     except: pass
914                 return projected
915             record_dicts = [ project (record) for record in record_dicts ]
916         records = [ Record(dict=record_dict) for record_dict in record_dicts ]
917         for record in records:
918             if (options.format == "text"):      record.dump(sort=True)  
919             else:                               print record.save_as_xml() 
920         if options.file:
921             save_records_to_file(options.file, record_dicts, options.fileformat)
922         return
923     
924     @register_command("[xml-filename]","")
925     def add(self, options, args):
926         """add record into registry (Register) 
927   from command line options (recommended) 
928   old-school method involving an xml file still supported"""
929
930         auth_cred = self.my_authority_credential_string()
931         if options.show_credential:
932             show_credentials(auth_cred)
933         record_dict = {}
934         if len(args) > 1:
935             self.print_help()
936             sys.exit(1)
937         if len(args)==1:
938             try:
939                 record_filepath = args[0]
940                 rec_file = self.get_record_file(record_filepath)
941                 record_dict.update(load_record_from_file(rec_file).todict())
942             except:
943                 print "Cannot load record file %s"%record_filepath
944                 sys.exit(1)
945         if options:
946             record_dict.update(load_record_from_opts(options).todict())
947         # we should have a type by now
948         if 'type' not in record_dict :
949             self.print_help()
950             sys.exit(1)
951         # this is still planetlab dependent.. as plc will whine without that
952         # also, it's only for adding
953         if record_dict['type'] == 'user':
954             if not 'first_name' in record_dict:
955                 record_dict['first_name'] = record_dict['hrn']
956             if 'last_name' not in record_dict:
957                 record_dict['last_name'] = record_dict['hrn'] 
958         return self.registry().Register(record_dict, auth_cred)
959     
960     @register_command("[xml-filename]","")
961     def update(self, options, args):
962         """update record into registry (Update) 
963   from command line options (recommended) 
964   old-school method involving an xml file still supported"""
965         record_dict = {}
966         if len(args) > 0:
967             record_filepath = args[0]
968             rec_file = self.get_record_file(record_filepath)
969             record_dict.update(load_record_from_file(rec_file).todict())
970         if options:
971             record_dict.update(load_record_from_opts(options).todict())
972         # at the very least we need 'type' here
973         if 'type' not in record_dict:
974             self.print_help()
975             sys.exit(1)
976
977         # don't translate into an object, as this would possibly distort
978         # user-provided data; e.g. add an 'email' field to Users
979         if record_dict['type'] == "user":
980             if record_dict['hrn'] == self.user:
981                 cred = self.my_credential_string
982             else:
983                 cred = self.my_authority_credential_string()
984         elif record_dict['type'] in ["slice"]:
985             try:
986                 cred = self.slice_credential_string(record_dict['hrn'])
987             except ServerException, e:
988                # XXX smbaker -- once we have better error return codes, update this
989                # to do something better than a string compare
990                if "Permission error" in e.args[0]:
991                    cred = self.my_authority_credential_string()
992                else:
993                    raise
994         elif record_dict['type'] in ["authority"]:
995             cred = self.my_authority_credential_string()
996         elif record_dict['type'] == 'node':
997             cred = self.my_authority_credential_string()
998         else:
999             raise "unknown record type" + record_dict['type']
1000         if options.show_credential:
1001             show_credentials(cred)
1002         return self.registry().Update(record_dict, cred)
1003   
1004     @register_command("name","")
1005     def remove(self, options, args):
1006         "remove registry record by name (Remove)"
1007         auth_cred = self.my_authority_credential_string()
1008         if len(args)!=1:
1009             self.print_help()
1010             sys.exit(1)
1011         hrn = args[0]
1012         type = options.type 
1013         if type in ['all']:
1014             type = '*'
1015         if options.show_credential:
1016             show_credentials(auth_cred)
1017         return self.registry().Remove(hrn, auth_cred, type)
1018     
1019     # ==================================================================
1020     # Slice-related commands
1021     # ==================================================================
1022
1023     @register_command("","")
1024     def slices(self, options, args):
1025         "list instantiated slices (ListSlices) - returns urn's"
1026         server = self.sliceapi()
1027         # creds
1028         creds = [self.my_credential_string]
1029         # options and call_id when supported
1030         api_options = {}
1031         api_options['call_id']=unique_call_id()
1032         if options.show_credential:
1033             show_credentials(creds)
1034         result = server.ListSlices(creds, *self.ois(server,api_options))
1035         value = ReturnValue.get_value(result)
1036         if self.options.raw:
1037             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1038         else:
1039             display_list(value)
1040         return
1041
1042     # show rspec for named slice
1043     @register_command("","")
1044     def resources(self, options, args):
1045         """
1046         discover available resources (ListResources)
1047         """
1048         server = self.sliceapi()
1049
1050         # set creds
1051         creds = [self.my_credential]
1052         if options.delegate:
1053             creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1054         if options.show_credential:
1055             show_credentials(creds)
1056
1057         # no need to check if server accepts the options argument since the options has
1058         # been a required argument since v1 API
1059         api_options = {}
1060         # always send call_id to v2 servers
1061         api_options ['call_id'] = unique_call_id()
1062         # ask for cached value if available
1063         api_options ['cached'] = True
1064         if options.info:
1065             api_options['info'] = options.info
1066         if options.list_leases:
1067             api_options['list_leases'] = options.list_leases
1068         if options.current:
1069             if options.current == True:
1070                 api_options['cached'] = False
1071             else:
1072                 api_options['cached'] = True
1073         if options.rspec_version:
1074             version_manager = VersionManager()
1075             server_version = self.get_cached_server_version(server)
1076             if 'sfa' in server_version:
1077                 # just request the version the client wants
1078                 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1079             else:
1080                 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1081         else:
1082             api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1083         result = server.ListResources (creds, api_options)
1084         value = ReturnValue.get_value(result)
1085         if self.options.raw:
1086             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1087         if options.file is not None:
1088             save_rspec_to_file(value, options.file)
1089         if (self.options.raw is None) and (options.file is None):
1090             display_rspec(value, options.format)
1091
1092         return
1093
1094     @register_command("slice_hrn","")
1095     def describe(self, options, args):
1096         """
1097         shows currently allocated/provisioned resources 
1098         of the named slice or set of slivers (Describe) 
1099         """
1100         server = self.sliceapi()
1101
1102         # set creds
1103         creds = [self.slice_credential(args[0])]
1104         if options.delegate:
1105             creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1106         if options.show_credential:
1107             show_credentials(creds)
1108
1109         api_options = {'call_id': unique_call_id(),
1110                        'cached': True,
1111                        'info': options.info,
1112                        'list_leases': options.list_leases,
1113                        'geni_rspec_version': {'type': 'geni', 'version': '3.0'},
1114                       }
1115         if options.rspec_version:
1116             version_manager = VersionManager()
1117             server_version = self.get_cached_server_version(server)
1118             if 'sfa' in server_version:
1119                 # just request the version the client wants
1120                 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1121             else:
1122                 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1123         urn = Xrn(args[0], type='slice').get_urn()        
1124         result = server.Describe([urn], creds, api_options)
1125         value = ReturnValue.get_value(result)
1126         if self.options.raw:
1127             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1128         if options.file is not None:
1129             save_rspec_to_file(value, options.file)
1130         if (self.options.raw is None) and (options.file is None):
1131             display_rspec(value, options.format)
1132
1133         return 
1134
1135     @register_command("slice_hrn","")
1136     def delete(self, options, args):
1137         """
1138         de-allocate and de-provision all or named slivers of the slice (Delete)
1139         """
1140         server = self.sliceapi()
1141
1142         # slice urn
1143         slice_hrn = args[0]
1144         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1145
1146         # creds
1147         slice_cred = self.slice_credential(slice_hrn)
1148         creds = [slice_cred]
1149         
1150         # options and call_id when supported
1151         api_options = {}
1152         api_options ['call_id'] = unique_call_id()
1153         if options.show_credential:
1154             show_credentials(creds)
1155         result = server.Delete([slice_urn], creds, *self.ois(server, api_options ) )
1156         value = ReturnValue.get_value(result)
1157         if self.options.raw:
1158             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1159         else:
1160             print value
1161         return value
1162
1163     @register_command("slice_hrn rspec","")
1164     def allocate(self, options, args):
1165         """
1166          allocate resources to the named slice (Allocate)
1167         """
1168         server = self.sliceapi()
1169         server_version = self.get_cached_server_version(server)
1170         slice_hrn = args[0]
1171         slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1172
1173         # credentials
1174         creds = [self.slice_credential(slice_hrn)]
1175
1176         delegated_cred = None
1177         if server_version.get('interface') == 'slicemgr':
1178             # delegate our cred to the slice manager
1179             # do not delegate cred to slicemgr...not working at the moment
1180             pass
1181             #if server_version.get('hrn'):
1182             #    delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1183             #elif server_version.get('urn'):
1184             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1185
1186         if options.show_credential:
1187             show_credentials(creds)
1188
1189         # rspec
1190         rspec_file = self.get_rspec_file(args[1])
1191         rspec = open(rspec_file).read()
1192         api_options = {}
1193         api_options ['call_id'] = unique_call_id()
1194         result = server.Allocate(slice_urn, creds, rspec, api_options)
1195         value = ReturnValue.get_value(result)
1196         if self.options.raw:
1197             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1198         if options.file is not None:
1199             save_rspec_to_file (value, options.file)
1200         if (self.options.raw is None) and (options.file is None):
1201             print value
1202         return value
1203         
1204
1205     @register_command("slice_hrn","")
1206     def provision(self, options, args):
1207         """
1208         provision already allocated resources of named slice (Provision)
1209         """
1210         server = self.sliceapi()
1211         server_version = self.get_cached_server_version(server)
1212         slice_hrn = args[0]
1213         slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1214
1215         # credentials
1216         creds = [self.slice_credential(slice_hrn)]
1217         delegated_cred = None
1218         if server_version.get('interface') == 'slicemgr':
1219             # delegate our cred to the slice manager
1220             # do not delegate cred to slicemgr...not working at the moment
1221             pass
1222             #if server_version.get('hrn'):
1223             #    delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1224             #elif server_version.get('urn'):
1225             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1226
1227         if options.show_credential:
1228             show_credentials(creds)
1229
1230         api_options = {}
1231         api_options ['call_id'] = unique_call_id()
1232
1233         # set the requtested rspec version
1234         version_manager = VersionManager()
1235         rspec_version = version_manager._get_version('geni', '3.0').to_dict()
1236         api_options['geni_rspec_version'] = rspec_version
1237
1238         # users
1239         # need to pass along user keys to the aggregate.
1240         # users = [
1241         #  { urn: urn:publicid:IDN+emulab.net+user+alice
1242         #    keys: [<ssh key A>, <ssh key B>]
1243         #  }]
1244         users = []
1245         slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1246         if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
1247             slice_record = slice_records[0]
1248             user_hrns = slice_record['researcher']
1249             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1250             user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1251             users = pg_users_arg(user_records)
1252         
1253         api_options['geni_users'] = users
1254         result = server.Provision([slice_urn], creds, api_options)
1255         value = ReturnValue.get_value(result)
1256         if self.options.raw:
1257             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1258         if options.file is not None:
1259             save_rspec_to_file (value, options.file)
1260         if (self.options.raw is None) and (options.file is None):
1261             print value
1262         return value     
1263
1264     @register_command("slice_hrn","")
1265     def status(self, options, args):
1266         """
1267         retrieve the status of the slivers belonging to tne named slice (Status)
1268         """
1269         server = self.sliceapi()
1270
1271         # slice urn
1272         slice_hrn = args[0]
1273         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1274
1275         # creds 
1276         slice_cred = self.slice_credential(slice_hrn)
1277         creds = [slice_cred]
1278
1279         # options and call_id when supported
1280         api_options = {}
1281         api_options['call_id']=unique_call_id()
1282         if options.show_credential:
1283             show_credentials(creds)
1284         result = server.Status([slice_urn], creds, *self.ois(server,api_options))
1285         value = ReturnValue.get_value(result)
1286         if self.options.raw:
1287             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1288         else:
1289             print value
1290         # Thierry: seemed to be missing
1291         return value
1292
1293     @register_command("slice_hrn action","")
1294     def action(self, options, args):
1295         """
1296         Perform the named operational action on these slivers
1297         """
1298         server = self.sliceapi()
1299         api_options = {}
1300         # slice urn
1301         slice_hrn = args[0]
1302         action = args[1]
1303         slice_urn = Xrn(slice_hrn, type='slice').get_urn() 
1304         # cred
1305         slice_cred = self.slice_credential(args[0])
1306         creds = [slice_cred]
1307         if options.delegate:
1308             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1309             creds.append(delegated_cred)
1310         
1311         result = server.PerformOperationalAction([slice_urn], creds, action , api_options)
1312         value = ReturnValue.get_value(result)
1313         if self.options.raw:
1314             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1315         else:
1316             print value
1317         return value
1318
1319     @register_command("slice_hrn time","")
1320     def renew(self, options, args):
1321         """
1322         renew slice (RenewSliver)
1323         """
1324         server = self.sliceapi()
1325         if len(args) != 2:
1326             self.print_help()
1327             sys.exit(1)
1328         [ slice_hrn, input_time ] = args
1329         # slice urn    
1330         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1331         # time: don't try to be smart on the time format, server-side will
1332         # creds
1333         slice_cred = self.slice_credential(args[0])
1334         creds = [slice_cred]
1335         # options and call_id when supported
1336         api_options = {}
1337         api_options['call_id']=unique_call_id()
1338         if options.show_credential:
1339             show_credentials(creds)
1340         result =  server.Renew([slice_urn], creds, input_time, *self.ois(server,api_options))
1341         value = ReturnValue.get_value(result)
1342         if self.options.raw:
1343             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1344         else:
1345             print value
1346         return value
1347
1348
1349     @register_command("slice_hrn","")
1350     def shutdown(self, options, args):
1351         """
1352         shutdown named slice (Shutdown)
1353         """
1354         server = self.sliceapi()
1355         # slice urn
1356         slice_hrn = args[0]
1357         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1358         # creds
1359         slice_cred = self.slice_credential(slice_hrn)
1360         creds = [slice_cred]
1361         result = server.Shutdown(slice_urn, creds)
1362         value = ReturnValue.get_value(result)
1363         if self.options.raw:
1364             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1365         else:
1366             print value
1367         return value         
1368     
1369
1370     @register_command("[name]","")
1371     def gid(self, options, args):
1372         """
1373         Create a GID (CreateGid)
1374         """
1375         if len(args) < 1:
1376             self.print_help()
1377             sys.exit(1)
1378         target_hrn = args[0]
1379         my_gid_string = open(self.client_bootstrap.my_gid()).read() 
1380         gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1381         if options.file:
1382             filename = options.file
1383         else:
1384             filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1385         self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1386         GID(string=gid).save_to_file(filename)
1387          
1388
1389     @register_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1390
1391   will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1392   the set of credentials in the scope for this call would be
1393   (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1394       as per -u/--user
1395   (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1396       as per -p/--pi
1397   (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1398   (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1399       because of the two -s options
1400
1401 """)
1402     def delegate (self, options, args):
1403         """
1404         (locally) create delegate credential for use by given hrn
1405   make sure to check for 'sfi myslice' instead if you plan
1406   on using MySlice
1407         """
1408         if len(args) != 1:
1409             self.print_help()
1410             sys.exit(1)
1411         to_hrn = args[0]
1412         # support for several delegations in the same call
1413         # so first we gather the things to do
1414         tuples=[]
1415         for slice_hrn in options.delegate_slices:
1416             message="%s.slice"%slice_hrn
1417             original = self.slice_credential_string(slice_hrn)
1418             tuples.append ( (message, original,) )
1419         if options.delegate_pi:
1420             my_authority=self.authority
1421             message="%s.pi"%my_authority
1422             original = self.my_authority_credential_string()
1423             tuples.append ( (message, original,) )
1424         for auth_hrn in options.delegate_auths:
1425             message="%s.auth"%auth_hrn
1426             original=self.authority_credential_string(auth_hrn)
1427             tuples.append ( (message, original, ) )
1428         # if nothing was specified at all at this point, let's assume -u
1429         if not tuples: options.delegate_user=True
1430         # this user cred
1431         if options.delegate_user:
1432             message="%s.user"%self.user
1433             original = self.my_credential_string
1434             tuples.append ( (message, original, ) )
1435
1436         # default type for beneficial is user unless -A
1437         if options.delegate_to_authority:       to_type='authority'
1438         else:                                   to_type='user'
1439
1440         # let's now handle all this
1441         # it's all in the filenaming scheme
1442         for (message,original) in tuples:
1443             delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1444             delegated_credential = Credential (string=delegated_string)
1445             filename = os.path.join ( self.options.sfi_dir,
1446                                       "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1447             delegated_credential.save_to_file(filename, save_parents=True)
1448             self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1449     
1450     ####################
1451     @register_command("","""$ less +/myslice sfi_config
1452 [myslice]
1453 backend  = 'http://manifold.pl.sophia.inria.fr:7080'
1454 # the HRN that myslice uses, so that we are delegating to
1455 delegate = 'ple.upmc.slicebrowser'
1456 # platform - this is a myslice concept
1457 platform = 'ple'
1458 # username - as of this writing (May 2013) a simple login name
1459 user     = 'thierry'
1460
1461 $ sfi myslice
1462
1463   will first collect the slices that you are part of, then make sure
1464   all your credentials are up-to-date (read: refresh expired ones)
1465   then compute delegated credentials for user 'ple.upmc.slicebrowser'
1466   and upload them all on myslice backend, using 'platform' and 'user'.
1467   A password will be prompted for the upload part.
1468 """
1469 ) # register_command
1470     def myslice (self, options, args):
1471
1472         """ This helper is for refreshing your credentials at myslice; it will
1473   * compute all the slices that you currently have credentials on
1474   * refresh all your credentials (you as a user and pi, your slices)
1475   * upload them to the manifold backend server
1476   for last phase, sfi_config is read to look for the [myslice] section, 
1477   and namely the 'backend', 'delegate' and 'user' settings"""
1478
1479         ##########
1480         if len(args)>0:
1481             self.print_help()
1482             sys.exit(1)
1483         # ManifoldUploader
1484         pass
1485
1486     @register_command("cred","")
1487     def trusted(self, options, args):
1488         """
1489         return the trusted certs at this interface (get_trusted_certs)
1490         """ 
1491         trusted_certs = self.registry().get_trusted_certs()
1492         for trusted_cert in trusted_certs:
1493             gid = GID(string=trusted_cert)
1494             gid.dump()
1495             cert = Certificate(string=trusted_cert)
1496             self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1497         return 
1498
1499     @register_command("","")
1500     def config (self, options, args):
1501         "Display contents of current config"
1502         self.show_config()