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