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