cache server version. Only pass call id to interfaces that support it
[sfa.git] / sfa / client / sfi.py
1 #! /usr/bin/env python
2
3 # sfi -- slice-based facility interface
4
5 import sys
6 sys.path.append('.')
7 import os, os.path
8 import tempfile
9 import traceback
10 import socket
11 import random
12 import datetime
13 import zlib
14 from lxml import etree
15 from StringIO import StringIO
16 from types import StringTypes, ListType
17 from optparse import OptionParser
18 from sfa.util.sfalogging import sfa_logger,sfa_logger_goes_to_console
19 from sfa.trust.certificate import Keypair, Certificate
20 from sfa.trust.gid import GID
21 from sfa.trust.credential import Credential
22 from sfa.util.sfaticket import SfaTicket
23 from sfa.util.record import SfaRecord, UserRecord, SliceRecord, NodeRecord, AuthorityRecord
24 from sfa.util.xrn import Xrn, get_leaf, get_authority, hrn_to_urn
25 import sfa.util.xmlrpcprotocol as xmlrpcprotocol
26 from sfa.util.config import Config
27 from sfa.util.version import version_core
28 from sfa.util.cache import Cache
29
30 AGGREGATE_PORT=12346
31 CM_PORT=12346
32
33 # utility methods here
34 # display methods
35 def display_rspec(rspec, format='rspec'):
36     if format in ['dns']:
37         tree = etree.parse(StringIO(rspec))
38         root = tree.getroot()
39         result = root.xpath("./network/site/node/hostname/text()")
40     elif format in ['ip']:
41         # The IP address is not yet part of the new RSpec
42         # so this doesn't do anything yet.
43         tree = etree.parse(StringIO(rspec))
44         root = tree.getroot()
45         result = root.xpath("./network/site/node/ipv4/text()")
46     else:
47         result = rspec
48
49     print result
50     return
51
52 def display_list(results):
53     for result in results:
54         print result
55
56 def display_records(recordList, dump=False):
57     ''' Print all fields in the record'''
58     for record in recordList:
59         display_record(record, dump)
60
61 def display_record(record, dump=False):
62     if dump:
63         record.dump()
64     else:
65         info = record.getdict()
66         print "%s (%s)" % (info['hrn'], info['type'])
67     return
68
69
70 def filter_records(type, records):
71     filtered_records = []
72     for record in records:
73         if (record['type'] == type) or (type == "all"):
74             filtered_records.append(record)
75     return filtered_records
76
77
78 # save methods
79 def save_rspec_to_file(rspec, filename):
80     if not filename.endswith(".rspec"):
81         filename = filename + ".rspec"
82
83     f = open(filename, 'w')
84     f.write(rspec)
85     f.close()
86     return
87
88 def save_records_to_file(filename, recordList):
89     index = 0
90     for record in recordList:
91         if index > 0:
92             save_record_to_file(filename + "." + str(index), record)
93         else:
94             save_record_to_file(filename, record)
95         index = index + 1
96
97 def save_record_to_file(filename, record):
98     if record['type'] in ['user']:
99         record = UserRecord(dict=record)
100     elif record['type'] in ['slice']:
101         record = SliceRecord(dict=record)
102     elif record['type'] in ['node']:
103         record = NodeRecord(dict=record)
104     elif record['type'] in ['authority', 'ma', 'sa']:
105         record = AuthorityRecord(dict=record)
106     else:
107         record = SfaRecord(dict=record)
108     str = record.save_to_string()
109     file(filename, "w").write(str)
110     return
111
112
113 # load methods
114 def load_record_from_file(filename):
115     str = file(filename, "r").read()
116     record = SfaRecord(string=str)
117     return record
118
119
120 import uuid
121 def unique_call_id(): return uuid.uuid4().urn
122
123 class Sfi:
124     
125     required_options=['verbose',  'debug',  'registry',  'sm',  'auth',  'user']
126
127     # dummy to meet Sfi's expectations for its 'options' field
128     # i.e. s/t we can do setattr on
129     class DummyOptions:
130         pass
131
132     def __init__ (self,options=None):
133         if options is None: options=Sfi.DummyOptions()
134         for opt in Sfi.required_options:
135             if not hasattr(options,opt): setattr(options,opt,None)
136         if not hasattr(options,'sfi_dir'): options.sfi_dir=os.path.expanduser("~/.sfi/")
137         self.sfi_dir = options.sfi_dir
138         self.options = options
139         self.slicemgr = None
140         self.registry = None
141         self.user = None
142         self.authority = None
143         self.hashrequest = False
144         sfa_logger_goes_to_console()
145         self.logger=sfa_logger()
146    
147     def create_cmd_parser(self, command, additional_cmdargs=None):
148         cmdargs = {"list": "authority",
149                   "show": "name",
150                   "remove": "name",
151                   "add": "record",
152                   "update": "record",
153                   "aggregates": "[name]",
154                   "registries": "[name]",
155                   "get_gid": [],  
156                   "get_trusted_certs": "cred",
157                   "slices": "",
158                   "resources": "[name]",
159                   "create": "name rspec",
160                   "get_ticket": "name rspec",
161                   "redeem_ticket": "ticket",
162                   "delete": "name",
163                   "reset": "name",
164                   "start": "name",
165                   "stop": "name",
166                   "delegate": "name",
167                   "status": "name",
168                   "renew": "name",
169                   "shutdown": "name",
170                   "version": "",  
171                  }
172
173         if additional_cmdargs:
174             cmdargs.update(additional_cmdargs)
175
176         if command not in cmdargs:
177             msg="Invalid command\n"
178             msg+="Commands: "
179             msg += ','.join(cmdargs.keys())            
180             self.logger.critical(msg)
181             sys.exit(2)
182
183         parser = OptionParser(usage="sfi [sfi_options] %s [options] %s" \
184                                      % (command, cmdargs[command]))
185
186         # user specifies remote aggregate/sm/component                          
187         if command in ("resources", "slices", "create", "delete", "start", "stop", 
188                        "restart", "shutdown",  "get_ticket", "renew", "status"):
189             parser.add_option("-a", "--aggregate", dest="aggregate",
190                              default=None, help="aggregate host")
191             parser.add_option("-p", "--port", dest="port",
192                              default=AGGREGATE_PORT, help="aggregate port")
193             parser.add_option("-c", "--component", dest="component", default=None,
194                              help="component hrn")
195             parser.add_option("-d", "--delegate", dest="delegate", default=None, 
196                              action="store_true",
197                              help="Include a credential delegated to the user's root"+\
198                                   "authority in set of credentials for this call")  
199         
200         # registy filter option    
201         if command in ("list", "show", "remove"):
202             parser.add_option("-t", "--type", dest="type", type="choice",
203                             help="type filter ([all]|user|slice|authority|node|aggregate)",
204                             choices=("all", "user", "slice", "authority", "node", "aggregate"),
205                             default="all")
206         # display formats
207         if command in ("resources"):
208             parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
209                               help="schema type and version of resulting RSpec")
210             parser.add_option("-f", "--format", dest="format", type="choice",
211                              help="display format ([xml]|dns|ip)", default="xml",
212                              choices=("xml", "dns", "ip"))
213             #panos: a new option to define the type of information about resources a user is interested in
214             parser.add_option("-i", "--info", dest="info",
215                                 help="optional component information", default=None)
216
217
218         if command in ("resources", "show", "list"):
219            parser.add_option("-o", "--output", dest="file",
220                             help="output XML to file", metavar="FILE", default=None)
221         
222         if command in ("show", "list"):
223            parser.add_option("-f", "--format", dest="format", type="choice",
224                              help="display format ([text]|xml)", default="text",
225                              choices=("text", "xml"))
226
227         if command in ("delegate"):
228            parser.add_option("-u", "--user",
229                             action="store_true", dest="delegate_user", default=False,
230                             help="delegate user credential")
231            parser.add_option("-s", "--slice", dest="delegate_slice",
232                             help="delegate slice credential", metavar="HRN", default=None)
233         
234         if command in ("version"):
235             parser.add_option("-a", "--aggregate", dest="aggregate",
236                              default=None, help="aggregate host")
237             parser.add_option("-p", "--port", dest="port",
238                              default=AGGREGATE_PORT, help="aggregate port")
239             parser.add_option("-R","--registry-version",
240                               action="store_true", dest="version_registry", default=False,
241                               help="probe registry version instead of slicemgr")
242             parser.add_option("-l","--local",
243                               action="store_true", dest="version_local", default=False,
244                               help="display version of the local client")
245
246         return parser
247
248         
249     def create_parser(self):
250
251         # Generate command line parser
252         parser = OptionParser(usage="sfi [options] command [command_options] [command_args]",
253                              description="Commands: gid,list,show,remove,add,update,nodes,slices,resources,create,delete,start,stop,reset")
254         parser.add_option("-r", "--registry", dest="registry",
255                          help="root registry", metavar="URL", default=None)
256         parser.add_option("-s", "--slicemgr", dest="sm",
257                          help="slice manager", metavar="URL", default=None)
258         default_sfi_dir = os.path.expanduser("~/.sfi/")
259         parser.add_option("-d", "--dir", dest="sfi_dir",
260                          help="config & working directory - default is " + default_sfi_dir,
261                          metavar="PATH", default=default_sfi_dir)
262         parser.add_option("-u", "--user", dest="user",
263                          help="user name", metavar="HRN", default=None)
264         parser.add_option("-a", "--auth", dest="auth",
265                          help="authority name", metavar="HRN", default=None)
266         parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
267                          help="verbose mode - cumulative")
268         parser.add_option("-D", "--debug",
269                           action="store_true", dest="debug", default=False,
270                           help="Debug (xml-rpc) protocol messages")
271         parser.add_option("-p", "--protocol", dest="protocol", default="xmlrpc",
272                          help="RPC protocol (xmlrpc or soap)")
273         parser.add_option("-k", "--hashrequest",
274                          action="store_true", dest="hashrequest", default=False,
275                          help="Create a hash of the request that will be authenticated on the server")
276         parser.disable_interspersed_args()
277
278         return parser
279         
280
281     def read_config(self):
282        config_file = self.options.sfi_dir + os.sep + "sfi_config"
283        try:
284           config = Config (config_file)
285        except:
286           self.logger.critical("Failed to read configuration file %s"%config_file)
287           self.logger.info("Make sure to remove the export clauses and to add quotes")
288           if self.options.verbose==0:
289               self.logger.info("Re-run with -v for more details")
290           else:
291               self.logger.log_exc("Could not read config file %s"%config_file)
292           sys.exit(1)
293     
294        errors = 0
295        # Set SliceMgr URL
296        if (self.options.sm is not None):
297           self.sm_url = self.options.sm
298        elif hasattr(config, "SFI_SM"):
299           self.sm_url = config.SFI_SM
300        else:
301           self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
302           errors += 1 
303     
304        # Set Registry URL
305        if (self.options.registry is not None):
306           self.reg_url = self.options.registry
307        elif hasattr(config, "SFI_REGISTRY"):
308           self.reg_url = config.SFI_REGISTRY
309        else:
310           self.logger.errors("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
311           errors += 1 
312           
313
314        # Set user HRN
315        if (self.options.user is not None):
316           self.user = self.options.user
317        elif hasattr(config, "SFI_USER"):
318           self.user = config.SFI_USER
319        else:
320           self.logger.errors("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
321           errors += 1 
322     
323        # Set authority HRN
324        if (self.options.auth is not None):
325           self.authority = self.options.auth
326        elif hasattr(config, "SFI_AUTH"):
327           self.authority = config.SFI_AUTH
328        else:
329           self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
330           errors += 1 
331     
332        if errors:
333           sys.exit(1)
334
335
336     #
337     # Establish Connection to SliceMgr and Registry Servers
338     #
339     def set_servers(self):
340
341        self.read_config() 
342        # Get key and certificate
343        key_file = self.get_key_file()
344        cert_file = self.get_cert_file(key_file)
345        self.key = Keypair(filename=key_file) 
346        self.key_file = key_file
347        self.cert_file = cert_file
348        self.cert = GID(filename=cert_file) 
349        # Establish connection to server(s)
350        self.logger.info("Contacting Registry at: %s"%self.reg_url)
351        self.registry = xmlrpcprotocol.get_server(self.reg_url, key_file, cert_file, self.options)  
352        self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
353        self.slicemgr = xmlrpcprotocol.get_server(self.sm_url, key_file, cert_file, self.options)
354
355        return
356
357     def get_cached_server_version(self, server):
358         # check local cache first
359         cache = None
360         version = None 
361         cache_file = self.sfi_dir + os.path.sep + 'sfi_cache.dat'
362         cache_key = server.url + "-version"
363         try:
364             cache = Cache(cache_file)
365         except IOError:
366             cache = Cache()
367             self.logger.info("Local cache not found at: %s" % cache_file)
368
369         if cache:
370             version = cache.get(cache_key)
371             
372         if not version: 
373             version = server.GetVersion()
374             # cache version for 24 hours
375             cache.add(cache_key, version, ttl= 60*60*24)
376
377
378         return version   
379         
380
381     def server_supports_call_id_arg(self, server):
382         """
383         Returns true if server support the optional call_id arg, false otherwise. 
384         """
385         server_version = self.get_cached_server_version(server)
386         if 'sfa' in server_version:
387             code_tag = server_version['code_tag']
388             code_tag_parts = code_tag.split("-")
389             
390             version_parts = code_tag_parts[0].split(".")
391             major, minor = version_parts[0], version_parts[1]
392             rev = code_tag_parts[1]
393             if int(major) > 1:
394                 if int(minor) > 0 or int(rev) > 20:
395                     return True
396         return False                
397              
398     #
399     # Get various credential and spec files
400     #
401     # Establishes limiting conventions
402     #   - conflates MAs and SAs
403     #   - assumes last token in slice name is unique
404     #
405     # Bootstraps credentials
406     #   - bootstrap user credential from self-signed certificate
407     #   - bootstrap authority credential from user credential
408     #   - bootstrap slice credential from user credential
409     #
410     
411     
412     def get_key_file(self):
413        file = os.path.join(self.options.sfi_dir, self.user.replace(self.authority + '.', '') + ".pkey")
414        if (os.path.isfile(file)):
415           return file
416        else:
417           self.logger.error("Key file %s does not exist"%file)
418           sys.exit(-1)
419        return
420     
421     def get_cert_file(self, key_file):
422     
423         cert_file = os.path.join(self.options.sfi_dir, self.user.replace(self.authority + '.', '') + ".cert")
424         if (os.path.isfile(cert_file)):
425             # we'd perfer to use Registry issued certs instead of self signed certs. 
426             # if this is a Registry cert (GID) then we are done 
427             gid = GID(filename=cert_file)
428             if gid.get_urn():
429                 return cert_file
430
431         # generate self signed certificate
432         k = Keypair(filename=key_file)
433         cert = Certificate(subject=self.user)
434         cert.set_pubkey(k)
435         cert.set_issuer(k, self.user)
436         cert.sign()
437         self.logger.info("Writing self-signed certificate to %s"%cert_file)
438         cert.save_to_file(cert_file)
439         # try to get registry issued cert
440         try:
441             self.logger.info("Getting Registry issued cert")
442             self.read_config()
443             # *hack.  need to set registyr before _get_gid() is called 
444             self.registry = xmlrpcprotocol.get_server(self.reg_url, key_file, cert_file, self.options)
445             gid = self._get_gid(type='user')
446             self.registry = None 
447             self.logger.info("Writing certificate to %s"%cert_file)
448             gid.save_to_file(cert_file)
449         except:
450             self.logger.info("Failed to download Registry issued cert")
451  
452         return cert_file
453
454     def get_cached_gid(self, file):
455         """
456         Return a cached gid    
457         """
458         gid = None 
459         if (os.path.isfile(file)):
460             gid = GID(filename=file)
461         return gid
462
463     # xxx opts unused
464     def get_gid(self, opts, args):
465         """
466         Get the specify gid and save it to file
467         """
468         hrn = None
469         if args:
470             hrn = args[0]
471         gid = self._get_gid(hrn)
472         self.logger.debug("Sfi.get_gid-> %s",gid.save_to_string(save_parents=True))
473         return gid
474
475     def _get_gid(self, hrn=None, type=None):
476         """
477         git_gid helper. Retrive the gid from the registry and save it to file.
478         """
479         
480         if not hrn:
481             hrn = self.user
482  
483         gidfile = os.path.join(self.options.sfi_dir, hrn + ".gid")
484         gid = self.get_cached_gid(gidfile)
485         if not gid:
486             user_cred = self.get_user_cred()
487             records = self.registry.Resolve(hrn, user_cred.save_to_string(save_parents=True))
488             record = None
489             if type:
490                 for rec in records:
491                    if type == record['type']:
492                         record = rec 
493             if not record:
494                 raise RecordNotFound(args[0])
495             gid = GID(string=records[0]['gid'])
496             self.logger.info("Writing gid to %s"%gidfile)
497             gid.save_to_file(filename=gidfile)
498         return gid   
499                 
500      
501     def get_cached_credential(self, file):
502         """
503         Return a cached credential only if it hasn't expired.
504         """
505         if (os.path.isfile(file)):
506             credential = Credential(filename=file)
507             # make sure it isnt expired 
508             if not credential.get_expiration or \
509                datetime.datetime.today() < credential.get_expiration():
510                 return credential
511         return None 
512  
513     def get_user_cred(self):
514         file = os.path.join(self.options.sfi_dir, self.user.replace(self.authority + '.', '') + ".cred")
515         return self.get_cred(file, 'user', self.user)
516
517     def get_auth_cred(self):
518         if not self.authority:
519             self.logger.critical("no authority specified. Use -a or set SF_AUTH")
520             sys.exit(-1)
521         file = os.path.join(self.options.sfi_dir, self.authority + ".cred")
522         return self.get_cred(file, 'authority', self.authority)
523
524     def get_slice_cred(self, name):
525         file = os.path.join(self.options.sfi_dir, "slice_" + get_leaf(name) + ".cred")
526         return self.get_cred(file, 'slice', name)
527  
528     def get_cred(self, file, type, hrn):
529         # attempt to load a cached credential 
530         cred = self.get_cached_credential(file)    
531         if not cred:
532             if type in ['user']:
533                 cert_string = self.cert.save_to_string(save_parents=True)
534                 user_name = self.user.replace(self.authority + ".", '')
535                 if user_name.count(".") > 0:
536                     user_name = user_name.replace(".", '_')
537                     self.user = self.authority + "." + user_name
538                 cred_str = self.registry.GetSelfCredential(cert_string, hrn, "user")
539             else:
540                 # bootstrap slice credential from user credential
541                 user_cred = self.get_user_cred().save_to_string(save_parents=True)
542                 cred_str = self.registry.GetCredential(user_cred, hrn, type)
543             
544             if not cred_str:
545                 self.logger.critical("Failed to get %s credential" % type)
546                 sys.exit(-1)
547                 
548             cred = Credential(string=cred_str)
549             cred.save_to_file(file, save_parents=True)
550             self.logger.info("Writing %s credential to %s" %(type, file))
551
552         return cred
553  
554     
555     def get_rspec_file(self, rspec):
556        if (os.path.isabs(rspec)):
557           file = rspec
558        else:
559           file = os.path.join(self.options.sfi_dir, rspec)
560        if (os.path.isfile(file)):
561           return file
562        else:
563           self.logger.critical("No such rspec file %s"%rspec)
564           sys.exit(1)
565     
566     def get_record_file(self, record):
567        if (os.path.isabs(record)):
568           file = record
569        else:
570           file = os.path.join(self.options.sfi_dir, record)
571        if (os.path.isfile(file)):
572           return file
573        else:
574           self.logger.critical("No such registry record file %s"%record)
575           sys.exit(1)
576     
577     def load_publickey_string(self, fn):
578        f = file(fn, "r")
579        key_string = f.read()
580     
581        # if the filename is a private key file, then extract the public key
582        if "PRIVATE KEY" in key_string:
583            outfn = tempfile.mktemp()
584            cmd = "openssl rsa -in " + fn + " -pubout -outform PEM -out " + outfn
585            os.system(cmd)
586            f = file(outfn, "r")
587            key_string = f.read()
588            os.remove(outfn)
589     
590        return key_string
591
592     # xxx opts undefined
593     def get_component_server_from_hrn(self, hrn):
594         # direct connection to the nodes component manager interface
595         user_cred = self.get_user_cred().save_to_string(save_parents=True)
596         records = self.registry.Resolve(hrn, user_cred)
597         records = filter_records('node', records)
598         if not records:
599             self.logger.warning("No such component:%r"% opts.component)
600         record = records[0]
601   
602         return self.get_server(record['hostname'], CM_PORT, self.key_file, self.cert_file)
603  
604     def get_server(self, host, port, keyfile, certfile):
605         """
606         Return an instance of an xmlrpc server connection    
607         """
608         # port is appended onto the domain, before the path. Should look like:
609         # http://domain:port/path
610         host_parts = host.split('/')
611         host_parts[0] = host_parts[0] + ":" + str(port)
612         url =  "http://%s" %  "/".join(host_parts)    
613         return xmlrpcprotocol.get_server(url, keyfile, certfile, self.options)
614
615     # xxx opts could be retrieved in self.options
616     def get_server_from_opts(self, opts):
617         """
618         Return instance of an xmlrpc connection to a slice manager, aggregate
619         or component server depending on the specified opts
620         """
621         server = self.slicemgr
622         # direct connection to an aggregate
623         if hasattr(opts, 'aggregate') and opts.aggregate:
624             server = self.get_server(opts.aggregate, opts.port, self.key_file, self.cert_file)
625         # direct connection to the nodes component manager interface
626         if hasattr(opts, 'component') and opts.component:
627             server = self.get_component_server_from_hrn(opts.component)    
628  
629         return server
630     #==========================================================================
631     # Following functions implement the commands
632     #
633     # Registry-related commands
634     #==========================================================================
635   
636     def dispatch(self, command, cmd_opts, cmd_args):
637         return getattr(self, command)(cmd_opts, cmd_args)
638  
639     # list entires in named authority registry
640     def list(self, opts, args):
641         if len(args)!= 1:
642             self.print_help()
643             sys.exit(1)
644         hrn = args[0]
645         user_cred = self.get_user_cred().save_to_string(save_parents=True)
646         try:
647             list = self.registry.List(hrn, user_cred)
648         except IndexError:
649             raise Exception, "Not enough parameters for the 'list' command"
650           
651         # filter on person, slice, site, node, etc.  
652         # THis really should be in the self.filter_records funct def comment...
653         list = filter_records(opts.type, list)
654         for record in list:
655             print "%s (%s)" % (record['hrn'], record['type'])     
656         if opts.file:
657             file = opts.file
658             if not file.startswith(os.sep):
659                 file = os.path.join(self.options.sfi_dir, file)
660             save_records_to_file(file, list)
661         return
662     
663     # show named registry record
664     def show(self, opts, args):
665         if len(args)!= 1:
666             self.print_help()
667             sys.exit(1)
668         hrn = args[0]
669         user_cred = self.get_user_cred().save_to_string(save_parents=True)
670         records = self.registry.Resolve(hrn, user_cred)
671         records = filter_records(opts.type, records)
672         if not records:
673             print "No record of type", opts.type
674         for record in records:
675             if record['type'] in ['user']:
676                 record = UserRecord(dict=record)
677             elif record['type'] in ['slice']:
678                 record = SliceRecord(dict=record)
679             elif record['type'] in ['node']:
680                 record = NodeRecord(dict=record)
681             elif record['type'] in ['authority', 'ma', 'sa']:
682                 record = AuthorityRecord(dict=record)
683             else:
684                 record = SfaRecord(dict=record)
685             if (opts.format == "text"): 
686                 record.dump()  
687             else:
688                 print record.save_to_string() 
689        
690         if opts.file:
691             file = opts.file
692             if not file.startswith(os.sep):
693                 file = os.path.join(self.options.sfi_dir, file)
694             save_records_to_file(file, records)
695         return
696     
697     def delegate(self, opts, args):
698
699         delegee_hrn = args[0]
700         if opts.delegate_user:
701             user_cred = self.get_user_cred()
702             cred = self.delegate_cred(user_cred, delegee_hrn)
703         elif opts.delegate_slice:
704             slice_cred = self.get_slice_cred(opts.delegate_slice)
705             cred = self.delegate_cred(slice_cred, delegee_hrn)
706         else:
707             self.logger.warning("Must specify either --user or --slice <hrn>")
708             return
709         delegated_cred = Credential(string=cred)
710         object_hrn = delegated_cred.get_gid_object().get_hrn()
711         if opts.delegate_user:
712             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
713                                   + get_leaf(object_hrn) + ".cred")
714         elif opts.delegate_slice:
715             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
716                                   + get_leaf(object_hrn) + ".cred")
717
718         delegated_cred.save_to_file(dest_fn, save_parents=True)
719
720         self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
721     
722     def delegate_cred(self, object_cred, hrn):
723         # the gid and hrn of the object we are delegating
724         if isinstance(object_cred, str):
725             object_cred = Credential(string=object_cred) 
726         object_gid = object_cred.get_gid_object()
727         object_hrn = object_gid.get_hrn()
728     
729         if not object_cred.get_privileges().get_all_delegate():
730             self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
731             return
732
733         # the delegating user's gid
734         caller_gid = self._get_gid(self.user)
735         caller_gidfile = os.path.join(self.options.sfi_dir, self.user + ".gid")
736   
737         # the gid of the user who will be delegated to
738         delegee_gid = self._get_gid(hrn)
739         delegee_hrn = delegee_gid.get_hrn()
740         delegee_gidfile = os.path.join(self.options.sfi_dir, delegee_hrn + ".gid")
741         delegee_gid.save_to_file(filename=delegee_gidfile)
742         dcred = object_cred.delegate(delegee_gidfile, self.get_key_file(), caller_gidfile)
743         return dcred.save_to_string(save_parents=True)
744      
745     # removed named registry record
746     #   - have to first retrieve the record to be removed
747     def remove(self, opts, args):
748         auth_cred = self.get_auth_cred().save_to_string(save_parents=True)
749         if len(args)!=1:
750             self.print_help()
751             sys.exit(1)
752         hrn = args[0]
753         type = opts.type 
754         if type in ['all']:
755             type = '*'
756         return self.registry.Remove(hrn, auth_cred, type)
757     
758     # add named registry record
759     def add(self, opts, args):
760         auth_cred = self.get_auth_cred().save_to_string(save_parents=True)
761         if len(args)!=1:
762             self.print_help()
763             sys.exit(1)
764         record_filepath = args[0]
765         rec_file = self.get_record_file(record_filepath)
766         record = load_record_from_file(rec_file).as_dict()
767         return self.registry.Register(record, auth_cred)
768     
769     # update named registry entry
770     def update(self, opts, args):
771         user_cred = self.get_user_cred()
772         if len(args)!=1:
773             self.print_help()
774             sys.exit(1)
775         rec_file = self.get_record_file(args[0])
776         record = load_record_from_file(rec_file)
777         if record['type'] == "user":
778             if record.get_name() == user_cred.get_gid_object().get_hrn():
779                 cred = user_cred.save_to_string(save_parents=True)
780             else:
781                 cred = self.get_auth_cred().save_to_string(save_parents=True)
782         elif record['type'] in ["slice"]:
783             try:
784                 cred = self.get_slice_cred(record.get_name()).save_to_string(save_parents=True)
785             except xmlrpcprotocol.ServerException, e:
786                # XXX smbaker -- once we have better error return codes, update this
787                # to do something better than a string compare
788                if "Permission error" in e.args[0]:
789                    cred = self.get_auth_cred().save_to_string(save_parents=True)
790                else:
791                    raise
792         elif record.get_type() in ["authority"]:
793             cred = self.get_auth_cred().save_to_string(save_parents=True)
794         elif record.get_type() == 'node':
795             cred = self.get_auth_cred().save_to_string(save_parents=True)
796         else:
797             raise "unknown record type" + record.get_type()
798         record = record.as_dict()
799         return self.registry.Update(record, cred)
800   
801     def get_trusted_certs(self, opts, args):
802         """
803         return uhe trusted certs at this interface 
804         """ 
805         trusted_certs = self.registry.get_trusted_certs()
806         for trusted_cert in trusted_certs:
807             cert = Certificate(string=trusted_cert)
808             self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())
809         return 
810
811     def aggregates(self, opts, args):
812         """
813         return a list of details about known aggregates
814         """
815         user_cred = self.get_user_cred().save_to_string(save_parents=True)
816         hrn = None
817         if args:
818             hrn = args[0]
819
820         result = self.registry.get_aggregates(user_cred, hrn)
821         display_list(result)
822         return 
823
824     def registries(self, opts, args):
825         """
826         return a list of details about known registries
827         """
828         user_cred = self.get_user_cred().save_to_string(save_parents=True)
829         hrn = None
830         if args:
831             hrn = args[0]
832         result = self.registry.get_registries(user_cred, hrn)
833         display_list(result)
834         return
835
836  
837     # ==================================================================
838     # Slice-related commands
839     # ==================================================================
840
841     def version(self, opts, args):
842         if opts.version_local:
843             version=version_core()
844         else:
845             if opts.version_registry:
846                 server=self.registry
847             else:
848                 server = self.get_server_from_opts(opts)
849             version=server.GetVersion()
850         for (k,v) in version.iteritems():
851             print "%-20s: %s"%(k,v)
852
853     # list instantiated slices
854     def slices(self, opts, args):
855         """
856         list instantiated slices
857         """
858         user_cred = self.get_user_cred().save_to_string(save_parents=True)
859         creds = [user_cred]
860         if opts.delegate:
861             delegated_cred = self.delegate_cred(user_cred, get_authority(self.authority))
862             creds.append(delegated_cred)  
863         server = self.get_server_from_opts(opts)
864         #results = server.ListSlices(creds, unique_call_id())
865         results = server.ListSlices(creds)
866         display_list(results)
867         return
868     
869     # show rspec for named slice
870     def resources(self, opts, args):
871         user_cred = self.get_user_cred().save_to_string(save_parents=True)
872         server = self.slicemgr
873         call_options = {}
874         server = self.get_server_from_opts(opts)
875         
876         if args:
877             cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
878             hrn = args[0]
879             call_options = {'geni_slice_urn': hrn_to_urn(hrn, 'slice')}
880         else:
881             cred = user_cred
882             hrn = None
883      
884         creds = [cred]
885         if opts.delegate:
886             delegated_cred = self.delegate_cred(cred, get_authority(self.authority))
887             creds.append(delegated_cred)
888         if opts.rspec_version:
889             call_options['rspec_version'] = opts.rspec_version 
890         #panos add info options
891         if opts.info:
892             call_options['info'] = opts.info 
893
894         call_args = [creds, call_options]
895         if self.server_supports_call_id_arg(server):
896             call_args.append(unique_call_id())
897         result = server.ListResources(*call_args)
898         format = opts.format
899         if opts.file is None:
900             display_rspec(result, format)
901         else:
902             file = opts.file
903             if not file.startswith(os.sep):
904                 file = os.path.join(self.options.sfi_dir, file)
905             save_rspec_to_file(result, file)
906         return
907     
908     # created named slice with given rspec
909     def create(self, opts, args):
910         slice_hrn = args[0]
911         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
912         user_cred = self.get_user_cred()
913         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
914         creds = [slice_cred]
915         if opts.delegate:
916             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
917             creds.append(delegated_cred)
918         rspec_file = self.get_rspec_file(args[1])
919         rspec = open(rspec_file).read()
920
921         # users = [
922         #  { urn: urn:publicid:IDN+emulab.net+user+alice
923         #    keys: [<ssh key A>, <ssh key B>] 
924         #  }]
925         users = []
926         server = self.get_server_from_opts(opts)
927         version = server.GetVersion()
928         if 'sfa' not in version:
929             # need to pass along user keys if this request is going to a ProtoGENI aggregate 
930             # ProtoGeni Aggregates will only install the keys of the user that is issuing the
931             # request. So we will only pass in one user that contains the keys for all
932             # users of the slice 
933             user = {'urn': user_cred.get_gid_caller().get_urn(),
934                     'keys': []}
935             slice_record = self.registry.Resolve(slice_urn, creds)
936             if slice_record and 'researchers' in slice_record:
937                 user_hrns = slice_record['researchers']
938                 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns] 
939                 user_records = self.registry.Resolve(user_urns, creds)
940                 for user_record in user_records:
941                     if 'keys' in user_record:
942                         user['keys'].extend(user_record['keys'])
943             users.append(user)
944
945         call_args = [slice_urn, creds, rspec, users]
946         if self.server_supports_call_id_arg(server):
947             call_args.append(unique_call_id())
948              
949         result =  server.CreateSliver(*call_args)
950         print result
951         return result
952
953     # get a ticket for the specified slice
954     def get_ticket(self, opts, args):
955         slice_hrn, rspec_path = args[0], args[1]
956         slice_urn = hrn_to_urn(slice_hrn, 'slice')
957         user_cred = self.get_user_cred()
958         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
959         creds = [slice_cred]
960         if opts.delegate:
961             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
962             creds.append(delegated_cred)
963         rspec_file = self.get_rspec_file(rspec_path) 
964         rspec = open(rspec_file).read()
965         server = self.get_server_from_opts(opts)
966         ticket_string = server.GetTicket(slice_urn, creds, rspec, [])
967         file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
968         self.logger.info("writing ticket to %s"%file)
969         ticket = SfaTicket(string=ticket_string)
970         ticket.save_to_file(filename=file, save_parents=True)
971
972     def redeem_ticket(self, opts, args):
973         ticket_file = args[0]
974         
975         # get slice hrn from the ticket
976         # use this to get the right slice credential 
977         ticket = SfaTicket(filename=ticket_file)
978         ticket.decode()
979         slice_hrn = ticket.gidObject.get_hrn()
980         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
981         #slice_hrn = ticket.attributes['slivers'][0]['hrn']
982         user_cred = self.get_user_cred()
983         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
984         
985         # get a list of node hostnames from the RSpec 
986         tree = etree.parse(StringIO(ticket.rspec))
987         root = tree.getroot()
988         hostnames = root.xpath("./network/site/node/hostname/text()")
989         
990         # create an xmlrpc connection to the component manager at each of these
991         # components and gall redeem_ticket
992         connections = {}
993         for hostname in hostnames:
994             try:
995                 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
996                 server = self.get_server(hostname, CM_PORT, self.key_file, \
997                                          self.cert_file, self.options.debug)
998                 server.RedeemTicket(ticket.save_to_string(save_parents=True), slice_cred)
999                 self.logger.info("Success")
1000             except socket.gaierror:
1001                 self.logger.error("redeem_ticket failed: Component Manager not accepting requests")
1002             except Exception, e:
1003                 self.logger.log_exc(e.message)
1004         return
1005  
1006     # delete named slice
1007     def delete(self, opts, args):
1008         slice_hrn = args[0]
1009         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1010         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1011         creds = [slice_cred]
1012         if opts.delegate:
1013             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1014             creds.append(delegated_cred)
1015         server = self.get_server_from_opts(opts)
1016
1017         call_args = [slice_urn, creds]
1018         if self.server_supports_call_id_arg(server):
1019             call_args.append(unique_call_id())
1020         return server.DeleteSliver(*call_args) 
1021   
1022     # start named slice
1023     def start(self, opts, args):
1024         slice_hrn = args[0]
1025         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1026         slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
1027         creds = [slice_cred]
1028         if opts.delegate:
1029             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1030             creds.append(delegated_cred)
1031         server = self.get_server_from_opts(opts)
1032         return server.Start(slice_urn, creds)
1033     
1034     # stop named slice
1035     def stop(self, opts, args):
1036         slice_hrn = args[0]
1037         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1038         slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
1039         creds = [slice_cred]
1040         if opts.delegate:
1041             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1042             creds.append(delegated_cred)
1043         server = self.get_server_from_opts(opts)
1044         return server.Stop(slice_urn, creds)
1045     
1046     # reset named slice
1047     def reset(self, opts, args):
1048         slice_hrn = args[0]
1049         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1050         server = self.get_server_from_opts(opts)
1051         slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
1052         creds = [slice_cred]
1053         if opts.delegate:
1054             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1055             creds.append(delegated_cred)
1056         return server.reset_slice(creds, slice_urn)
1057
1058     def renew(self, opts, args):
1059         slice_hrn = args[0]
1060         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1061         server = self.get_server_from_opts(opts)
1062         slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
1063         creds = [slice_cred]
1064         if opts.delegate:
1065             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1066             creds.append(delegated_cred)
1067         time = args[1]
1068         
1069         call_args = [slice_urn, creds, time]
1070         if self.server_supports_call_id_arg(server):
1071             call_args.append(unique_call_id())
1072         return server.RenewSliver(*call_args)
1073
1074
1075     def status(self, opts, args):
1076         slice_hrn = args[0]
1077         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1078         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1079         creds = [slice_cred]
1080         if opts.delegate:
1081             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1082             creds.append(delegated_cred)
1083         server = self.get_server_from_opts(opts)
1084         call_args = [slice_urn, creds]
1085         if self.server_supports_call_id_arg(server):
1086             call_args.append(unique_call_id())
1087         print server.SliverStatus(*call_args)
1088
1089
1090     def shutdown(self, opts, args):
1091         slice_hrn = args[0]
1092         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1093         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1094         creds = [slice_cred]
1095         if opts.delegate:
1096             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1097             creds.append(delegated_cred)
1098         server = self.get_server_from_opts(opts)
1099         return server.Shutdown(slice_urn, creds)         
1100     
1101     def print_help (self):
1102         self.sfi_parser.print_help()
1103         self.cmd_parser.print_help()
1104
1105     #
1106     # Main: parse arguments and dispatch to command
1107     #
1108     def main(self):
1109         self.sfi_parser = self.create_parser()
1110         (options, args) = self.sfi_parser.parse_args()
1111         self.options = options
1112
1113         self.logger.setLevelFromOptVerbose(self.options.verbose)
1114         if options.hashrequest:
1115             self.hashrequest = True
1116  
1117         if len(args) <= 0:
1118             self.logger.critical("No command given. Use -h for help.")
1119             return -1
1120     
1121         command = args[0]
1122         self.cmd_parser = self.create_cmd_parser(command)
1123         (cmd_opts, cmd_args) = self.cmd_parser.parse_args(args[1:])
1124
1125         self.set_servers()
1126     
1127         self.logger.info("Command=%s" % command)
1128         if command in ("resources"):
1129             self.logger.debug("resources cmd_opts %s" % cmd_opts.format)
1130         elif command in ("list", "show", "remove"):
1131             self.logger.debug("cmd_opts.type %s" % cmd_opts.type)
1132         self.logger.debug('cmd_args %s',cmd_args)
1133
1134         try:
1135             self.dispatch(command, cmd_opts, cmd_args)
1136         except KeyError:
1137             self.logger.critical ("Unknown command %s"%command)
1138             sys.exit(1)
1139     
1140         return
1141     
1142 if __name__ == "__main__":
1143     Sfi().main()