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