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