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