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