minor cleanup
[sfa.git] / sfa / server / sfa-server.py
1 #!/usr/bin/python
2 #
3 # PlanetLab SFA implementation
4 #
5 # This implements the SFA Registry and Slice Interfaces on PLC.
6 # Depending on command line options, it starts some combination of a
7 # Registry, an Aggregate Manager, and a Slice Manager.
8 #
9 # There are several items that need to be done before starting the servers.
10 #
11 # NOTE:  Many configuration settings, including the PLC maintenance account
12 # credentials, URI of the PLCAPI, and PLC DB URI and admin credentials are initialized
13 # from your MyPLC configuration (/etc/planetlab/plc_config*).  Please make sure this information
14 # is up to date and accurate.
15 #
16 # 1) Import the existing planetlab database, creating the
17 #    appropriate SFA records. This is done by running the "sfa-import-plc.py" tool.
18 #
19 # 2) Create a "trusted_roots" directory and place the certificate of the root
20 #    authority in that directory. Given the defaults in sfa-import-plc.py, this
21 #    certificate would be named "planetlab.gid". For example,
22 #
23 #    mkdir trusted_roots; cp authorities/planetlab.gid trusted_roots/
24 #
25 # TODO: Can all three servers use the same "registry" certificate?
26 ##
27
28 # TCP ports for the three servers
29 #registry_port=12345
30 #aggregate_port=12346
31 #slicemgr_port=12347
32 ### xxx todo not in the config yet
33 component_port=12346
34 import os, os.path
35 import traceback
36 import sys
37 import sfa.util.xmlrpcprotocol as xmlrpcprotocol
38 from optparse import OptionParser
39
40 from sfa.util.sfalogging import logger
41 from sfa.trust.certificate import Keypair, Certificate
42 from sfa.trust.hierarchy import Hierarchy
43 from sfa.trust.gid import GID
44 from sfa.util.config import Config
45 from sfa.plc.api import SfaAPI
46 from sfa.server.registry import Registries
47 from sfa.server.aggregate import Aggregates
48 from sfa.util.xrn import get_authority, hrn_to_urn
49 from sfa.util.sfalogging import logger
50
51 from sfa.managers.import_manager import import_manager
52
53 # after http://www.erlenstar.demon.co.uk/unix/faq_2.html
54 def daemon():
55     """Daemonize the current process."""
56     if os.fork() != 0: os._exit(0)
57     os.setsid()
58     if os.fork() != 0: os._exit(0)
59     os.umask(0)
60     devnull = os.open(os.devnull, os.O_RDWR)
61     os.dup2(devnull, 0)
62     # xxx fixme - this is just to make sure that nothing gets stupidly lost - should use devnull
63     crashlog = os.open('/var/log/httpd/sfa_access_log', os.O_RDWR | os.O_APPEND | os.O_CREAT, 0644)
64     os.dup2(crashlog, 1)
65     os.dup2(crashlog, 2)
66
67 def init_server_key(server_key_file, server_cert_file, config, hierarchy):
68
69     hrn = config.SFA_INTERFACE_HRN.lower()
70     # check if the server's private key exists. If it doesnt,
71     # get the right one from the authorities directory. If it cant be
72     # found in the authorities directory, generate a random one
73     if not os.path.exists(server_key_file):
74         hrn = config.SFA_INTERFACE_HRN.lower()
75         hrn_parts = hrn.split(".")
76         rel_key_path = hrn
77         pkey_filename = hrn+".pkey"
78
79         # sub authority's have "." in their hrn. This must
80         # be converted to os.path separator
81         if len(hrn_parts) > 0:
82             rel_key_path = hrn.replace(".", os.sep)
83             pkey_filename= hrn_parts[-1]+".pkey"
84
85         key_file = os.sep.join([hierarchy.basedir, rel_key_path, pkey_filename])
86         if not os.path.exists(key_file):
87             # if it doesnt exist then this is probably a fresh interface
88             # with no records. Generate a random keypair for now
89             logger.debug("server's public key not found in %s" % key_file)
90
91             logger.debug("generating a random server key pair")
92             key = Keypair(create=True)
93             key.save_to_file(server_key_file)
94             init_server_cert(hrn, key, server_cert_file, self_signed=True)    
95
96         else:
97             # the pkey was found in the authorites directory. lets 
98             # copy it to where the server key should be and generate
99             # the cert
100             key = Keypair(filename=key_file)
101             key.save_to_file(server_key_file)
102             init_server_cert(hrn, key, server_cert_file)    
103
104     # If private key exists and cert doesnt, recreate cert
105     if (os.path.exists(server_key_file)) and (not os.path.exists(server_cert_file)):
106         key = Keypair(filename=server_key_file)
107         init_server_cert(hrn, key, server_cert_file)    
108
109
110 def init_server_cert(hrn, key, server_cert_file, self_signed=False):
111     """
112     Setup the certificate for this server. Attempt to use gid before 
113     creating a self signed cert 
114     """
115     if self_signed:
116         init_self_signed_cert(hrn, key, server_cert_file)
117     else:
118         try:
119             # look for gid file
120             logger.debug("generating server cert from gid: %s"% hrn)
121             hierarchy = Hierarchy()
122             auth_info = hierarchy.get_auth_info(hrn)
123             gid = GID(filename=auth_info.gid_filename)
124             gid.save_to_file(filename=server_cert_file)
125         except:
126             # fall back to self signed cert
127             logger.debug("gid for %s not found" % hrn)
128             init_self_signed_cert(hrn, key, server_cert_file)        
129         
130 def init_self_signed_cert(hrn, key, server_cert_file):
131     logger.debug("generating self signed cert")
132     # generate self signed certificate
133     cert = Certificate(subject=hrn)
134     cert.set_issuer(key=key, subject=hrn)
135     cert.set_pubkey(key)
136     cert.sign()
137     cert.save_to_file(server_cert_file)
138
139 def init_server(options, config):
140     """
141     Locate the manager based on config.*TYPE
142     Execute the init_server method (well in fact function, sigh) if defined in that module
143     In order to migrate to a more generic approach:
144     * search for <>_manager_<type>.py
145     * if not found, try <>_manager.py (and issue a warning if <type>!='pl')
146     """
147     if options.registry:
148         manager=import_manager ("registry",       config.SFA_REGISTRY_TYPE)
149         if manager and hasattr(manager, 'init_server'): manager.init_server()
150     if options.am:      
151         manager=import_manager ("aggregate",      config.SFA_AGGREGATE_TYPE)
152         if manager and hasattr(manager, 'init_server'): manager.init_server()
153     if options.sm:
154         manager=import_manager ("slice",          config.SFA_SM_TYPE)
155         if manager and hasattr(manager, 'init_server'): manager.init_server()
156     if options.cm:
157         manager=import_manager ("component",      config.SFA_CM_TYPE)
158         if manager and hasattr(manager, 'init_server'): manager.init_server()
159
160
161 def install_peer_certs(server_key_file, server_cert_file):
162     """
163     Attempt to install missing trusted gids and db records for 
164     our federated interfaces
165     """
166     # Attempt to get any missing peer gids
167     # There should be a gid file in /etc/sfa/trusted_roots for every
168     # peer registry found in in the registries.xml config file. If there
169     # are any missing gids, request a new one from the peer registry.
170     api = SfaAPI(key_file = server_key_file, cert_file = server_cert_file)
171     registries = Registries()
172     aggregates = Aggregates()
173     interfaces = dict(registries.items() + aggregates.items())
174     gids_current = api.auth.trusted_cert_list
175     hrns_current = [gid.get_hrn() for gid in gids_current]
176     hrns_expected = set([hrn for hrn in interfaces])
177     new_hrns = set(hrns_expected).difference(hrns_current)
178     #gids = self.get_peer_gids(new_hrns) + gids_current
179     peer_gids = []
180     if not new_hrns:
181         return 
182
183     trusted_certs_dir = api.config.get_trustedroots_dir()
184     for new_hrn in new_hrns:
185         if not new_hrn: continue
186         # the gid for this interface should already be installed
187         if new_hrn == api.config.SFA_INTERFACE_HRN: continue
188         try:
189             # get gid from the registry
190             url = interfaces[new_hrn].get_url()
191             interface = interfaces[new_hrn].get_server(server_key_file, server_cert_file, timeout=30)
192             # skip non sfa aggregates
193             server_version = api.get_cached_server_version(interface)
194             if 'sfa' not in server_version:
195                 logger.info("get_trusted_certs: skipping non sfa aggregate: %s" % new_hrn)
196                 continue
197       
198             trusted_gids = interface.get_trusted_certs()
199             if trusted_gids:
200                 # the gid we want should be the first one in the list,
201                 # but lets make sure
202                 for trusted_gid in trusted_gids:
203                     # default message
204                     message = "interface: %s\t" % (api.interface)
205                     message += "unable to install trusted gid for %s" % \
206                                (new_hrn)
207                     gid = GID(string=trusted_gids[0])
208                     peer_gids.append(gid)
209                     if gid.get_hrn() == new_hrn:
210                         gid_filename = os.path.join(trusted_certs_dir, '%s.gid' % new_hrn)
211                         gid.save_to_file(gid_filename, save_parents=True)
212                         message = "installed trusted cert for %s" % new_hrn
213                     # log the message
214                     api.logger.info(message)
215         except:
216             message = "interface: %s\tunable to install trusted gid for %s" % \
217                         (api.interface, new_hrn)
218             api.logger.log_exc(message)
219     # doesnt matter witch one
220     update_cert_records(peer_gids)
221
222 def update_cert_records(gids):
223     """
224     Make sure there is a record in the registry for the specified gids. 
225     Removes old records from the db.
226     """
227     # import SfaTable here so this module can be loaded by ComponentAPI
228     from sfa.util.table import SfaTable
229     from sfa.util.record import SfaRecord
230     if not gids:
231         return
232     table = SfaTable()
233     # get records that actually exist in the db
234     gid_urns = [gid.get_urn() for gid in gids]
235     hrns_expected = [gid.get_hrn() for gid in gids]
236     records_found = table.find({'hrn': hrns_expected, 'pointer': -1}) 
237
238     # remove old records
239     for record in records_found:
240         if record['hrn'] not in hrns_expected and \
241             record['hrn'] != self.api.config.SFA_INTERFACE_HRN:
242             table.remove(record)
243
244     # TODO: store urn in the db so we do this in 1 query 
245     for gid in gids:
246         hrn, type = gid.get_hrn(), gid.get_type()
247         record = table.find({'hrn': hrn, 'type': type, 'pointer': -1})
248         if not record:
249             record = {
250                 'hrn': hrn, 'type': type, 'pointer': -1,
251                 'authority': get_authority(hrn),
252                 'gid': gid.save_to_string(save_parents=True),
253             }
254             record = SfaRecord(dict=record)
255             table.insert(record)
256         
257 def main():
258     # Generate command line parser
259     parser = OptionParser(usage="sfa-server [options]")
260     parser.add_option("-r", "--registry", dest="registry", action="store_true",
261          help="run registry server", default=False)
262     parser.add_option("-s", "--slicemgr", dest="sm", action="store_true",
263          help="run slice manager", default=False)
264     parser.add_option("-a", "--aggregate", dest="am", action="store_true",
265          help="run aggregate manager", default=False)
266     parser.add_option("-c", "--component", dest="cm", action="store_true",
267          help="run component server", default=False)
268     parser.add_option("-t", "--trusted-certs", dest="trusted_certs", action="store_true",
269          help="refresh trusted certs", default=False)
270     parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
271          help="verbose mode - cumulative")
272     parser.add_option("-d", "--daemon", dest="daemon", action="store_true",
273          help="Run as daemon.", default=False)
274     (options, args) = parser.parse_args()
275     
276     config = Config()
277     if config.SFA_API_DEBUG: pass
278     hierarchy = Hierarchy()
279     server_key_file = os.path.join(hierarchy.basedir, "server.key")
280     server_cert_file = os.path.join(hierarchy.basedir, "server.cert")
281
282     init_server_key(server_key_file, server_cert_file, config, hierarchy)
283     init_server(options, config)
284  
285     if (options.daemon):  daemon()
286     
287     if options.trusted_certs:
288         install_peer_certs(server_key_file, server_cert_file)   
289     
290     # start registry server
291     if (options.registry):
292         from sfa.server.registry import Registry
293         r = Registry("", config.SFA_REGISTRY_PORT, server_key_file, server_cert_file)
294         r.start()
295
296     if (options.am):
297         from sfa.server.aggregate import Aggregate
298         a = Aggregate("", config.SFA_AGGREGATE_PORT, server_key_file, server_cert_file)
299         a.start()
300
301     # start slice manager
302     if (options.sm):
303         from sfa.server.slicemgr import SliceMgr
304         s = SliceMgr("", config.SFA_SM_PORT, server_key_file, server_cert_file)
305         s.start()
306
307     if (options.cm):
308         from sfa.server.component import Component
309         c = Component("", config.component_port, server_key_file, server_cert_file)
310 #        c = Component("", config.SFA_COMPONENT_PORT, server_key_file, server_cert_file)
311         c.start()
312
313 if __name__ == "__main__":
314     try:
315         main()
316     except:
317         logger.log_exc_critical("SFA server is exiting")