save cache file
[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
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 = sfi_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             self.logger.info("Updating cache file %s" % cache_file)
374             cache.save_to_file(cache_file)
375
376
377         return version   
378         
379
380     def server_supports_call_id_arg(self, server):
381         """
382         Returns true if server support the optional call_id arg, false otherwise. 
383         """
384         server_version = self.get_cached_server_version(server)
385         if 'sfa' in server_version:
386             code_tag = server_version['code_tag']
387             code_tag_parts = code_tag.split("-")
388             
389             version_parts = code_tag_parts[0].split(".")
390             major, minor = version_parts[0], version_parts[1]
391             rev = code_tag_parts[1]
392             if int(major) > 1:
393                 if int(minor) > 0 or int(rev) > 20:
394                     return True
395         return False                
396              
397     #
398     # Get various credential and spec files
399     #
400     # Establishes limiting conventions
401     #   - conflates MAs and SAs
402     #   - assumes last token in slice name is unique
403     #
404     # Bootstraps credentials
405     #   - bootstrap user credential from self-signed certificate
406     #   - bootstrap authority credential from user credential
407     #   - bootstrap slice credential from user credential
408     #
409     
410     
411     def get_key_file(self):
412        file = os.path.join(self.options.sfi_dir, self.user.replace(self.authority + '.', '') + ".pkey")
413        if (os.path.isfile(file)):
414           return file
415        else:
416           self.logger.error("Key file %s does not exist"%file)
417           sys.exit(-1)
418        return
419     
420     def get_cert_file(self, key_file):
421     
422         cert_file = os.path.join(self.options.sfi_dir, self.user.replace(self.authority + '.', '') + ".cert")
423         if (os.path.isfile(cert_file)):
424             # we'd perfer to use Registry issued certs instead of self signed certs. 
425             # if this is a Registry cert (GID) then we are done 
426             gid = GID(filename=cert_file)
427             if gid.get_urn():
428                 return cert_file
429
430         # generate self signed certificate
431         k = Keypair(filename=key_file)
432         cert = Certificate(subject=self.user)
433         cert.set_pubkey(k)
434         cert.set_issuer(k, self.user)
435         cert.sign()
436         self.logger.info("Writing self-signed certificate to %s"%cert_file)
437         cert.save_to_file(cert_file)
438         self.cert = cert
439         # try to get registry issued cert
440         try:
441             self.logger.info("Getting Registry issued cert")
442             self.read_config()
443             # *hack.  need to set registyr before _get_gid() is called 
444             self.registry = xmlrpcprotocol.get_server(self.reg_url, key_file, cert_file, self.options)
445             gid = self._get_gid(type='user')
446             self.registry = None 
447             self.logger.info("Writing certificate to %s"%cert_file)
448             gid.save_to_file(cert_file)
449         except:
450             self.logger.info("Failed to download Registry issued cert")
451
452         return cert_file
453
454     def get_cached_gid(self, file):
455         """
456         Return a cached gid    
457         """
458         gid = None 
459         if (os.path.isfile(file)):
460             gid = GID(filename=file)
461         return gid
462
463     # xxx opts unused
464     def get_gid(self, opts, args):
465         """
466         Get the specify gid and save it to file
467         """
468         hrn = None
469         if args:
470             hrn = args[0]
471         gid = self._get_gid(hrn)
472         self.logger.debug("Sfi.get_gid-> %s",gid.save_to_string(save_parents=True))
473         return gid
474
475     def _get_gid(self, hrn=None, type=None):
476         """
477         git_gid helper. Retrive the gid from the registry and save it to file.
478         """
479         
480         if not hrn:
481             hrn = self.user
482  
483         gidfile = os.path.join(self.options.sfi_dir, hrn + ".gid")
484         print gidfile
485         gid = self.get_cached_gid(gidfile)
486         if not gid:
487             user_cred = self.get_user_cred()
488             records = self.registry.Resolve(hrn, user_cred.save_to_string(save_parents=True))
489             if not records:
490                 raise RecordNotFound(args[0])
491             record = records[0]
492             if type:
493                 record=None
494                 for rec in records:
495                    if type == rec['type']:
496                         record = rec 
497             if not record:
498                 raise RecordNotFound(args[0])
499             
500             gid = GID(string=record['gid'])
501             self.logger.info("Writing gid to %s"%gidfile)
502             gid.save_to_file(filename=gidfile)
503         return gid   
504                 
505      
506     def get_cached_credential(self, file):
507         """
508         Return a cached credential only if it hasn't expired.
509         """
510         if (os.path.isfile(file)):
511             credential = Credential(filename=file)
512             # make sure it isnt expired 
513             if not credential.get_expiration or \
514                datetime.datetime.today() < credential.get_expiration():
515                 return credential
516         return None 
517  
518     def get_user_cred(self):
519         file = os.path.join(self.options.sfi_dir, self.user.replace(self.authority + '.', '') + ".cred")
520         return self.get_cred(file, 'user', self.user)
521
522     def get_auth_cred(self):
523         if not self.authority:
524             self.logger.critical("no authority specified. Use -a or set SF_AUTH")
525             sys.exit(-1)
526         file = os.path.join(self.options.sfi_dir, self.authority + ".cred")
527         return self.get_cred(file, 'authority', self.authority)
528
529     def get_slice_cred(self, name):
530         file = os.path.join(self.options.sfi_dir, "slice_" + get_leaf(name) + ".cred")
531         return self.get_cred(file, 'slice', name)
532  
533     def get_cred(self, file, type, hrn):
534         # attempt to load a cached credential 
535         cred = self.get_cached_credential(file)    
536         if not cred:
537             if type in ['user']:
538                 cert_string = self.cert.save_to_string(save_parents=True)
539                 user_name = self.user.replace(self.authority + ".", '')
540                 if user_name.count(".") > 0:
541                     user_name = user_name.replace(".", '_')
542                     self.user = self.authority + "." + user_name
543                 cred_str = self.registry.GetSelfCredential(cert_string, hrn, "user")
544             else:
545                 # bootstrap slice credential from user credential
546                 user_cred = self.get_user_cred().save_to_string(save_parents=True)
547                 cred_str = self.registry.GetCredential(user_cred, hrn, type)
548             
549             if not cred_str:
550                 self.logger.critical("Failed to get %s credential" % type)
551                 sys.exit(-1)
552                 
553             cred = Credential(string=cred_str)
554             cred.save_to_file(file, save_parents=True)
555             self.logger.info("Writing %s credential to %s" %(type, file))
556
557         return cred
558  
559     
560     def get_rspec_file(self, rspec):
561        if (os.path.isabs(rspec)):
562           file = rspec
563        else:
564           file = os.path.join(self.options.sfi_dir, rspec)
565        if (os.path.isfile(file)):
566           return file
567        else:
568           self.logger.critical("No such rspec file %s"%rspec)
569           sys.exit(1)
570     
571     def get_record_file(self, record):
572        if (os.path.isabs(record)):
573           file = record
574        else:
575           file = os.path.join(self.options.sfi_dir, record)
576        if (os.path.isfile(file)):
577           return file
578        else:
579           self.logger.critical("No such registry record file %s"%record)
580           sys.exit(1)
581     
582     def load_publickey_string(self, fn):
583        f = file(fn, "r")
584        key_string = f.read()
585     
586        # if the filename is a private key file, then extract the public key
587        if "PRIVATE KEY" in key_string:
588            outfn = tempfile.mktemp()
589            cmd = "openssl rsa -in " + fn + " -pubout -outform PEM -out " + outfn
590            os.system(cmd)
591            f = file(outfn, "r")
592            key_string = f.read()
593            os.remove(outfn)
594     
595        return key_string
596
597     # xxx opts undefined
598     def get_component_server_from_hrn(self, hrn):
599         # direct connection to the nodes component manager interface
600         user_cred = self.get_user_cred().save_to_string(save_parents=True)
601         records = self.registry.Resolve(hrn, user_cred)
602         records = filter_records('node', records)
603         if not records:
604             self.logger.warning("No such component:%r"% opts.component)
605         record = records[0]
606   
607         return self.get_server(record['hostname'], CM_PORT, self.key_file, self.cert_file)
608  
609     def get_server(self, host, port, keyfile, certfile):
610         """
611         Return an instance of an xmlrpc server connection    
612         """
613         # port is appended onto the domain, before the path. Should look like:
614         # http://domain:port/path
615         host_parts = host.split('/')
616         host_parts[0] = host_parts[0] + ":" + str(port)
617         url =  "http://%s" %  "/".join(host_parts)    
618         return xmlrpcprotocol.get_server(url, keyfile, certfile, self.options)
619
620     # xxx opts could be retrieved in self.options
621     def get_server_from_opts(self, opts):
622         """
623         Return instance of an xmlrpc connection to a slice manager, aggregate
624         or component server depending on the specified opts
625         """
626         server = self.slicemgr
627         # direct connection to an aggregate
628         if hasattr(opts, 'aggregate') and opts.aggregate:
629             server = self.get_server(opts.aggregate, opts.port, self.key_file, self.cert_file)
630         # direct connection to the nodes component manager interface
631         if hasattr(opts, 'component') and opts.component:
632             server = self.get_component_server_from_hrn(opts.component)    
633  
634         return server
635     #==========================================================================
636     # Following functions implement the commands
637     #
638     # Registry-related commands
639     #==========================================================================
640   
641     def dispatch(self, command, cmd_opts, cmd_args):
642         return getattr(self, command)(cmd_opts, cmd_args)
643  
644     # list entires in named authority registry
645     def list(self, opts, args):
646         if len(args)!= 1:
647             self.print_help()
648             sys.exit(1)
649         hrn = args[0]
650         user_cred = self.get_user_cred().save_to_string(save_parents=True)
651         try:
652             list = self.registry.List(hrn, user_cred)
653         except IndexError:
654             raise Exception, "Not enough parameters for the 'list' command"
655           
656         # filter on person, slice, site, node, etc.  
657         # THis really should be in the self.filter_records funct def comment...
658         list = filter_records(opts.type, list)
659         for record in list:
660             print "%s (%s)" % (record['hrn'], record['type'])     
661         if opts.file:
662             file = opts.file
663             if not file.startswith(os.sep):
664                 file = os.path.join(self.options.sfi_dir, file)
665             save_records_to_file(file, list)
666         return
667     
668     # show named registry record
669     def show(self, opts, args):
670         if len(args)!= 1:
671             self.print_help()
672             sys.exit(1)
673         hrn = args[0]
674         user_cred = self.get_user_cred().save_to_string(save_parents=True)
675         records = self.registry.Resolve(hrn, user_cred)
676         records = filter_records(opts.type, records)
677         if not records:
678             print "No record of type", opts.type
679         for record in records:
680             if record['type'] in ['user']:
681                 record = UserRecord(dict=record)
682             elif record['type'] in ['slice']:
683                 record = SliceRecord(dict=record)
684             elif record['type'] in ['node']:
685                 record = NodeRecord(dict=record)
686             elif record['type'].startswith('authority'):
687                 record = AuthorityRecord(dict=record)
688             else:
689                 record = SfaRecord(dict=record)
690             if (opts.format == "text"): 
691                 record.dump()  
692             else:
693                 print record.save_to_string() 
694  
695         if opts.file:
696             file = opts.file
697             if not file.startswith(os.sep):
698                 file = os.path.join(self.options.sfi_dir, file)
699             save_records_to_file(file, records)
700         return
701     
702     def delegate(self, opts, args):
703
704         delegee_hrn = args[0]
705         if opts.delegate_user:
706             user_cred = self.get_user_cred()
707             cred = self.delegate_cred(user_cred, delegee_hrn)
708         elif opts.delegate_slice:
709             slice_cred = self.get_slice_cred(opts.delegate_slice)
710             cred = self.delegate_cred(slice_cred, delegee_hrn)
711         else:
712             self.logger.warning("Must specify either --user or --slice <hrn>")
713             return
714         delegated_cred = Credential(string=cred)
715         object_hrn = delegated_cred.get_gid_object().get_hrn()
716         if opts.delegate_user:
717             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
718                                   + get_leaf(object_hrn) + ".cred")
719         elif opts.delegate_slice:
720             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
721                                   + get_leaf(object_hrn) + ".cred")
722
723         delegated_cred.save_to_file(dest_fn, save_parents=True)
724
725         self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
726     
727     def delegate_cred(self, object_cred, hrn):
728         # the gid and hrn of the object we are delegating
729         if isinstance(object_cred, str):
730             object_cred = Credential(string=object_cred) 
731         object_gid = object_cred.get_gid_object()
732         object_hrn = object_gid.get_hrn()
733     
734         if not object_cred.get_privileges().get_all_delegate():
735             self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
736             return
737
738         # the delegating user's gid
739         caller_gid = self._get_gid(self.user)
740         caller_gidfile = os.path.join(self.options.sfi_dir, self.user + ".gid")
741   
742         # the gid of the user who will be delegated to
743         delegee_gid = self._get_gid(hrn)
744         delegee_hrn = delegee_gid.get_hrn()
745         delegee_gidfile = os.path.join(self.options.sfi_dir, delegee_hrn + ".gid")
746         delegee_gid.save_to_file(filename=delegee_gidfile)
747         dcred = object_cred.delegate(delegee_gidfile, self.get_key_file(), caller_gidfile)
748         return dcred.save_to_string(save_parents=True)
749      
750     # removed named registry record
751     #   - have to first retrieve the record to be removed
752     def remove(self, opts, args):
753         auth_cred = self.get_auth_cred().save_to_string(save_parents=True)
754         if len(args)!=1:
755             self.print_help()
756             sys.exit(1)
757         hrn = args[0]
758         type = opts.type 
759         if type in ['all']:
760             type = '*'
761         return self.registry.Remove(hrn, auth_cred, type)
762     
763     # add named registry record
764     def add(self, opts, args):
765         auth_cred = self.get_auth_cred().save_to_string(save_parents=True)
766         if len(args)!=1:
767             self.print_help()
768             sys.exit(1)
769         record_filepath = args[0]
770         rec_file = self.get_record_file(record_filepath)
771         record = load_record_from_file(rec_file).as_dict()
772         return self.registry.Register(record, auth_cred)
773     
774     # update named registry entry
775     def update(self, opts, args):
776         user_cred = self.get_user_cred()
777         if len(args)!=1:
778             self.print_help()
779             sys.exit(1)
780         rec_file = self.get_record_file(args[0])
781         record = load_record_from_file(rec_file)
782         if record['type'] == "user":
783             if record.get_name() == user_cred.get_gid_object().get_hrn():
784                 cred = user_cred.save_to_string(save_parents=True)
785             else:
786                 cred = self.get_auth_cred().save_to_string(save_parents=True)
787         elif record['type'] in ["slice"]:
788             try:
789                 cred = self.get_slice_cred(record.get_name()).save_to_string(save_parents=True)
790             except xmlrpcprotocol.ServerException, e:
791                # XXX smbaker -- once we have better error return codes, update this
792                # to do something better than a string compare
793                if "Permission error" in e.args[0]:
794                    cred = self.get_auth_cred().save_to_string(save_parents=True)
795                else:
796                    raise
797         elif record.get_type() in ["authority"]:
798             cred = self.get_auth_cred().save_to_string(save_parents=True)
799         elif record.get_type() == 'node':
800             cred = self.get_auth_cred().save_to_string(save_parents=True)
801         else:
802             raise "unknown record type" + record.get_type()
803         record = record.as_dict()
804         return self.registry.Update(record, cred)
805   
806     def get_trusted_certs(self, opts, args):
807         """
808         return uhe trusted certs at this interface 
809         """ 
810         trusted_certs = self.registry.get_trusted_certs()
811         for trusted_cert in trusted_certs:
812             gid = GID(string=trusted_cert)
813             gid.dump()
814             cert = Certificate(string=trusted_cert)
815             self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())
816         return 
817
818     def aggregates(self, opts, args):
819         """
820         return a list of details about known aggregates
821         """
822         user_cred = self.get_user_cred().save_to_string(save_parents=True)
823         hrn = None
824         if args:
825             hrn = args[0]
826
827         result = self.registry.get_aggregates(user_cred, hrn)
828         display_list(result)
829         return 
830
831     def registries(self, opts, args):
832         """
833         return a list of details about known registries
834         """
835         user_cred = self.get_user_cred().save_to_string(save_parents=True)
836         hrn = None
837         if args:
838             hrn = args[0]
839         result = self.registry.get_registries(user_cred, hrn)
840         display_list(result)
841         return
842
843  
844     # ==================================================================
845     # Slice-related commands
846     # ==================================================================
847
848     def version(self, opts, args):
849         if opts.version_local:
850             version=version_core()
851         else:
852             if opts.version_registry:
853                 server=self.registry
854             else:
855                 server = self.get_server_from_opts(opts)
856             version=server.GetVersion()
857         for (k,v) in version.iteritems():
858             print "%-20s: %s"%(k,v)
859
860     # list instantiated slices
861     def slices(self, opts, args):
862         """
863         list instantiated slices
864         """
865         user_cred = self.get_user_cred().save_to_string(save_parents=True)
866         creds = [user_cred]
867         if opts.delegate:
868             delegated_cred = self.delegate_cred(user_cred, get_authority(self.authority))
869             creds.append(delegated_cred)  
870         server = self.get_server_from_opts(opts)
871         #results = server.ListSlices(creds, unique_call_id())
872         results = server.ListSlices(creds)
873         display_list(results)
874         return
875     
876     # show rspec for named slice
877     def resources(self, opts, args):
878         user_cred = self.get_user_cred().save_to_string(save_parents=True)
879         server = self.slicemgr
880         call_options = {}
881         server = self.get_server_from_opts(opts)
882         
883         if args:
884             cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
885             hrn = args[0]
886             call_options = {'geni_slice_urn': hrn_to_urn(hrn, 'slice')}
887         else:
888             cred = user_cred
889             hrn = None
890      
891         creds = [cred]
892         if opts.delegate:
893             delegated_cred = self.delegate_cred(cred, get_authority(self.authority))
894             creds.append(delegated_cred)
895         if opts.rspec_version:
896             call_options['rspec_version'] = opts.rspec_version 
897         #panos add info options
898         if opts.info:
899             call_options['info'] = opts.info 
900
901         call_args = [creds, call_options]
902         if self.server_supports_call_id_arg(server):
903             call_args.append(unique_call_id())
904         result = server.ListResources(*call_args)
905         format = opts.format
906         if opts.file is None:
907             display_rspec(result, format)
908         else:
909             file = opts.file
910             if not file.startswith(os.sep):
911                 file = os.path.join(self.options.sfi_dir, file)
912             save_rspec_to_file(result, file)
913         return
914     
915     # created named slice with given rspec
916     def create(self, opts, args):
917         slice_hrn = args[0]
918         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
919         user_cred = self.get_user_cred()
920         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
921         creds = [slice_cred]
922         if opts.delegate:
923             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
924             creds.append(delegated_cred)
925         rspec_file = self.get_rspec_file(args[1])
926         rspec = open(rspec_file).read()
927
928         # users = [
929         #  { urn: urn:publicid:IDN+emulab.net+user+alice
930         #    keys: [<ssh key A>, <ssh key B>] 
931         #  }]
932         users = []
933         server = self.get_server_from_opts(opts)
934         version = server.GetVersion()
935         if 'sfa' not in version:
936             # need to pass along user keys if this request is going to a ProtoGENI aggregate 
937             # ProtoGeni Aggregates will only install the keys of the user that is issuing the
938             # request. So we will only pass in one user that contains the keys for all
939             # users of the slice 
940             user = {'urn': user_cred.get_gid_caller().get_urn(),
941                     'keys': []}
942             slice_record = self.registry.Resolve(slice_urn, creds)
943             if slice_record and 'researchers' in slice_record:
944                 user_hrns = slice_record['researchers']
945                 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns] 
946                 user_records = self.registry.Resolve(user_urns, creds)
947                 for user_record in user_records:
948                     if 'keys' in user_record:
949                         user['keys'].extend(user_record['keys'])
950             users.append(user)
951
952         call_args = [slice_urn, creds, rspec, users]
953         if self.server_supports_call_id_arg(server):
954             call_args.append(unique_call_id())
955              
956         result =  server.CreateSliver(*call_args)
957         print result
958         return result
959
960     # get a ticket for the specified slice
961     def get_ticket(self, opts, args):
962         slice_hrn, rspec_path = args[0], args[1]
963         slice_urn = hrn_to_urn(slice_hrn, 'slice')
964         user_cred = self.get_user_cred()
965         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
966         creds = [slice_cred]
967         if opts.delegate:
968             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
969             creds.append(delegated_cred)
970         rspec_file = self.get_rspec_file(rspec_path) 
971         rspec = open(rspec_file).read()
972         server = self.get_server_from_opts(opts)
973         ticket_string = server.GetTicket(slice_urn, creds, rspec, [])
974         file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
975         self.logger.info("writing ticket to %s"%file)
976         ticket = SfaTicket(string=ticket_string)
977         ticket.save_to_file(filename=file, save_parents=True)
978
979     def redeem_ticket(self, opts, args):
980         ticket_file = args[0]
981         
982         # get slice hrn from the ticket
983         # use this to get the right slice credential 
984         ticket = SfaTicket(filename=ticket_file)
985         ticket.decode()
986         slice_hrn = ticket.gidObject.get_hrn()
987         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
988         #slice_hrn = ticket.attributes['slivers'][0]['hrn']
989         user_cred = self.get_user_cred()
990         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
991         
992         # get a list of node hostnames from the RSpec 
993         tree = etree.parse(StringIO(ticket.rspec))
994         root = tree.getroot()
995         hostnames = root.xpath("./network/site/node/hostname/text()")
996         
997         # create an xmlrpc connection to the component manager at each of these
998         # components and gall redeem_ticket
999         connections = {}
1000         for hostname in hostnames:
1001             try:
1002                 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1003                 server = self.get_server(hostname, CM_PORT, self.key_file, \
1004                                          self.cert_file, self.options.debug)
1005                 server.RedeemTicket(ticket.save_to_string(save_parents=True), slice_cred)
1006                 self.logger.info("Success")
1007             except socket.gaierror:
1008                 self.logger.error("redeem_ticket failed: Component Manager not accepting requests")
1009             except Exception, e:
1010                 self.logger.log_exc(e.message)
1011         return
1012  
1013     # delete named slice
1014     def delete(self, opts, args):
1015         slice_hrn = args[0]
1016         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1017         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1018         creds = [slice_cred]
1019         if opts.delegate:
1020             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1021             creds.append(delegated_cred)
1022         server = self.get_server_from_opts(opts)
1023
1024         call_args = [slice_urn, creds]
1025         if self.server_supports_call_id_arg(server):
1026             call_args.append(unique_call_id())
1027         return server.DeleteSliver(*call_args) 
1028   
1029     # start named slice
1030     def start(self, opts, args):
1031         slice_hrn = args[0]
1032         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1033         slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
1034         creds = [slice_cred]
1035         if opts.delegate:
1036             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1037             creds.append(delegated_cred)
1038         server = self.get_server_from_opts(opts)
1039         return server.Start(slice_urn, creds)
1040     
1041     # stop named slice
1042     def stop(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.Stop(slice_urn, creds)
1052     
1053     # reset named slice
1054     def reset(self, opts, args):
1055         slice_hrn = args[0]
1056         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1057         server = self.get_server_from_opts(opts)
1058         slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
1059         creds = [slice_cred]
1060         if opts.delegate:
1061             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1062             creds.append(delegated_cred)
1063         return server.reset_slice(creds, slice_urn)
1064
1065     def renew(self, opts, args):
1066         slice_hrn = args[0]
1067         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1068         server = self.get_server_from_opts(opts)
1069         slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True)
1070         creds = [slice_cred]
1071         if opts.delegate:
1072             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1073             creds.append(delegated_cred)
1074         time = args[1]
1075         
1076         call_args = [slice_urn, creds, time]
1077         if self.server_supports_call_id_arg(server):
1078             call_args.append(unique_call_id())
1079         return server.RenewSliver(*call_args)
1080
1081
1082     def status(self, opts, args):
1083         slice_hrn = args[0]
1084         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1085         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1086         creds = [slice_cred]
1087         if opts.delegate:
1088             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1089             creds.append(delegated_cred)
1090         server = self.get_server_from_opts(opts)
1091         call_args = [slice_urn, creds]
1092         if self.server_supports_call_id_arg(server):
1093             call_args.append(unique_call_id())
1094         print server.SliverStatus(*call_args)
1095
1096
1097     def shutdown(self, opts, args):
1098         slice_hrn = args[0]
1099         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1100         slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True)
1101         creds = [slice_cred]
1102         if opts.delegate:
1103             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1104             creds.append(delegated_cred)
1105         server = self.get_server_from_opts(opts)
1106         return server.Shutdown(slice_urn, creds)         
1107     
1108     def print_help (self):
1109         self.sfi_parser.print_help()
1110         self.cmd_parser.print_help()
1111
1112     #
1113     # Main: parse arguments and dispatch to command
1114     #
1115     def main(self):
1116         self.sfi_parser = self.create_parser()
1117         (options, args) = self.sfi_parser.parse_args()
1118         self.options = options
1119
1120         self.logger.setLevelFromOptVerbose(self.options.verbose)
1121         if options.hashrequest:
1122             self.hashrequest = True
1123  
1124         if len(args) <= 0:
1125             self.logger.critical("No command given. Use -h for help.")
1126             return -1
1127     
1128         command = args[0]
1129         self.cmd_parser = self.create_cmd_parser(command)
1130         (cmd_opts, cmd_args) = self.cmd_parser.parse_args(args[1:])
1131
1132         self.set_servers()
1133         self.logger.info("Command=%s" % command)
1134         if command in ("resources"):
1135             self.logger.debug("resources cmd_opts %s" % cmd_opts.format)
1136         elif command in ("list", "show", "remove"):
1137             self.logger.debug("cmd_opts.type %s" % cmd_opts.type)
1138         self.logger.debug('cmd_args %s' % cmd_args)
1139
1140         try:
1141             self.dispatch(command, cmd_opts, cmd_args)
1142         except KeyError:
1143             self.logger.critical ("Unknown command %s"%command)
1144             sys.exit(1)
1145     
1146         return
1147     
1148 if __name__ == "__main__":
1149     Sfi().main()