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