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