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