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