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