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