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