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