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