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