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