import SfaRecord
[sfa.git] / sfa / server / sfa-server.py
1 #!/usr/bin/python
2 #
3 # SFA PLC Wrapper
4 #
5 # This wrapper 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 wrapper
10 # server.
11 #
12 # NOTE:  Many configuration settings, including the PLC maintenance account
13 # credentials, URI of the PLCAPI, and PLC DB URI and admin credentials are initialized
14 # from your MyPLC configuration (/etc/planetlab/plc_config*).  Please make sure this information
15 # is up to date and accurate.
16 #
17 # 1) Import the existing planetlab database, creating the
18 #    appropriate SFA records. This is done by running the "sfa-import-plc.py" tool.
19 #
20 # 2) Create a "trusted_roots" directory and place the certificate of the root
21 #    authority in that directory. Given the defaults in sfa-import-plc.py, this
22 #    certificate would be named "planetlab.gid". For example,
23 #
24 #    mkdir trusted_roots; cp authorities/planetlab.gid trusted_roots/
25 #
26 # TODO: Can all three servers use the same "registry" certificate?
27 ##
28
29 # TCP ports for the three servers
30 #registry_port=12345
31 #aggregate_port=12346
32 #slicemgr_port=12347
33 ### xxx todo not in the config yet
34 component_port=12346
35 import os, os.path
36 import traceback
37 import sys
38 import sfa.util.xmlrpcprotocol as xmlrpcprotocol
39 from optparse import OptionParser
40
41 from sfa.util.sfalogging import logger
42 from sfa.trust.trustedroot import TrustedRootList
43 from sfa.trust.certificate import Keypair, Certificate
44 from sfa.trust.hierarchy import Hierarchy
45 from sfa.trust.gid import GID
46 from sfa.util.config import Config
47 from sfa.plc.api import SfaAPI
48 from sfa.server.registry import Registries
49 from sfa.server.aggregate import Aggregates
50 from sfa.util.xrn import get_authority, hrn_to_urn
51 from sfa.util.sfalogging import logger
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     Execute the init method defined in the manager file 
142     """
143     def init_manager(manager_module, manager_base):
144         try: manager = __import__(manager_module, fromlist=[manager_base])
145         except: manager = None
146         if manager and hasattr(manager, 'init_server'):
147             manager.init_server()
148     
149     manager_base = 'sfa.managers'
150     if options.registry:
151         mgr_type = config.SFA_REGISTRY_TYPE
152         manager_module = manager_base + ".registry_manager_%s" % mgr_type
153         init_manager(manager_module, manager_base)    
154     if options.am:
155         mgr_type = config.SFA_AGGREGATE_TYPE
156         manager_module = manager_base + ".aggregate_manager_%s" % mgr_type
157         init_manager(manager_module, manager_base)    
158     if options.sm:
159         mgr_type = config.SFA_SM_TYPE
160         manager_module = manager_base + ".slice_manager_%s" % mgr_type
161         init_manager(manager_module, manager_base)    
162     if options.cm:
163         mgr_type = config.SFA_CM_TYPE
164         manager_module = manager_base + ".component_manager_%s" % mgr_type
165         init_manager(manager_module, manager_base)    
166
167 def install_peer_certs(server_key_file, server_cert_file):
168     """
169     Attempt to install missing trusted gids and db records for 
170     our federated interfaces
171     """
172     # Attempt to get any missing peer gids
173     # There should be a gid file in /etc/sfa/trusted_roots for every
174     # peer registry found in in the registries.xml config file. If there
175     # are any missing gids, request a new one from the peer registry.
176     api = SfaAPI(key_file = server_key_file, cert_file = server_cert_file)
177     registries = Registries(api)
178     aggregates = Aggregates(api)
179     interfaces = dict(registries.interfaces.items() + aggregates.interfaces.items())
180     gids_current = api.auth.trusted_cert_list
181     hrns_current = [gid.get_hrn() for gid in gids_current]
182     hrns_expected = interfaces.keys()
183     new_hrns = set(hrns_expected).difference(hrns_current)
184     #gids = self.get_peer_gids(new_hrns) + gids_current
185     peer_gids = []
186     if not new_hrns:
187         return 
188
189     trusted_certs_dir = api.config.get_trustedroots_dir()
190     for new_hrn in new_hrns:
191         if not new_hrn: continue
192         # the gid for this interface should already be installed
193         if new_hrn == api.config.SFA_INTERFACE_HRN: continue
194         try:
195             # get gid from the registry
196             url = interfaces[new_hrn]['url']
197             interface = xmlrpcprotocol.get_server(url, server_key_file, server_cert_file, timeout=30)
198             # skip non sfa aggregates
199             server_version = api.get_cached_server_version(interface)
200             if 'sfa' not in server_version:
201                 logger.info("get_trusted_certs: skipping non sfa aggregate: %s" % new_hrn)
202                 continue
203       
204             trusted_gids = interface.get_trusted_certs()
205             if trusted_gids:
206                 # the gid we want should be the first one in the list,
207                 # but lets make sure
208                 for trusted_gid in trusted_gids:
209                     # default message
210                     message = "interface: %s\t" % (api.interface)
211                     message += "unable to install trusted gid for %s" % \
212                                (new_hrn)
213                     gid = GID(string=trusted_gids[0])
214                     peer_gids.append(gid)
215                     if gid.get_hrn() == new_hrn:
216                         gid_filename = os.path.join(trusted_certs_dir, '%s.gid' % new_hrn)
217                         gid.save_to_file(gid_filename, save_parents=True)
218                         message = "installed trusted cert for %s" % new_hrn
219                     # log the message
220                     api.logger.info(message)
221         except:
222             message = "interface: %s\tunable to install trusted gid for %s" % \
223                         (api.interface, new_hrn)
224             api.logger.log_exc(message)
225     # doesnt matter witch one
226     update_cert_records(peer_gids)
227
228 def update_cert_records(gids):
229     """
230     Make sure there is a record in the registry for the specified gids. 
231     Removes old records from the db.
232     """
233     # import SfaTable here so this module can be loaded by ComponentAPI
234     from sfa.util.table import SfaTable
235     from sfa.util.record import SfaRecord
236     if not gids:
237         return
238     table = SfaTable()
239     # get records that actually exist in the db
240     gid_urns = [gid.get_urn() for gid in gids]
241     hrns_expected = [gid.get_hrn() for gid in gids]
242     records_found = table.find({'hrn': hrns_expected, 'pointer': -1}) 
243
244     # remove old records
245     for record in records_found:
246         if record['hrn'] not in hrns_expected and \
247             record['hrn'] != self.api.config.SFA_INTERFACE_HRN:
248             table.remove(record)
249
250     # TODO: store urn in the db so we do this in 1 query 
251     for gid in gids:
252         hrn, type = gid.get_hrn(), gid.get_type()
253         record = table.find({'hrn': hrn, 'type': type, 'pointer': -1})
254         if not record:
255             record = {
256                 'hrn': hrn, 'type': type, 'pointer': -1,
257                 'authority': get_authority(hrn),
258                 'gid': gid.save_to_string(save_parents=True),
259             }
260             record = SfaRecord(dict=record)
261             table.insert(record)
262         
263 def main():
264     # Generate command line parser
265     parser = OptionParser(usage="sfa-server [options]")
266     parser.add_option("-r", "--registry", dest="registry", action="store_true",
267          help="run registry server", default=False)
268     parser.add_option("-s", "--slicemgr", dest="sm", action="store_true",
269          help="run slice manager", default=False)
270     parser.add_option("-a", "--aggregate", dest="am", action="store_true",
271          help="run aggregate manager", default=False)
272     parser.add_option("-c", "--component", dest="cm", action="store_true",
273          help="run component server", default=False)
274     parser.add_option("-t", "--trusted-certs", dest="trusted_certs", action="store_true",
275          help="refresh trusted certs", default=False)
276     parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
277          help="verbose mode - cumulative")
278     parser.add_option("-d", "--daemon", dest="daemon", action="store_true",
279          help="Run as daemon.", default=False)
280     (options, args) = parser.parse_args()
281     
282     config = Config()
283     if config.SFA_API_DEBUG: pass
284     hierarchy = Hierarchy()
285     server_key_file = os.path.join(hierarchy.basedir, "server.key")
286     server_cert_file = os.path.join(hierarchy.basedir, "server.cert")
287
288     init_server_key(server_key_file, server_cert_file, config, hierarchy)
289     init_server(options, config)
290  
291     if (options.daemon):  daemon()
292     
293     if options.trusted_certs:
294         install_peer_certs(server_key_file, server_cert_file)   
295     
296     # start registry server
297     if (options.registry):
298         from sfa.server.registry import Registry
299         r = Registry("", config.SFA_REGISTRY_PORT, server_key_file, server_cert_file)
300         r.start()
301
302     if (options.am):
303         from sfa.server.aggregate import Aggregate
304         a = Aggregate("", config.SFA_AGGREGATE_PORT, server_key_file, server_cert_file)
305         a.start()
306
307     # start slice manager
308     if (options.sm):
309         from sfa.server.slicemgr import SliceMgr
310         s = SliceMgr("", config.SFA_SM_PORT, server_key_file, server_cert_file)
311         s.start()
312
313     if (options.cm):
314         from sfa.server.component import Component
315         c = Component("", config.component_port, server_key_file, server_cert_file)
316 #        c = Component("", config.SFA_COMPONENT_PORT, server_key_file, server_cert_file)
317         c.start()
318
319 if __name__ == "__main__":
320     try:
321         main()
322     except:
323         logger.log_exc_critical("SFA server is exiting")