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