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