Commit bde035f1 authored by Michal 'vorner' Vaner's avatar Michal 'vorner' Vaner
Browse files

Merge branch 'trac213-incremental-config' into trac213-incremental

Conflicts:
	src/bin/bind10/bind10_messages.mes
	src/bin/bind10/bind10_src.py.in
	src/bin/bind10/tests/bind10_test.py.in
parents b85213cd 0f7a43ef
......@@ -20,14 +20,6 @@ The boss process is starting up and will now check if the message bus
daemon is already running. If so, it will not be able to start, as it
needs a dedicated message bus.
% BIND10_CONFIGURATION_START_AUTH start authoritative server: %1
This message shows whether or not the authoritative server should be
started according to the configuration.
% BIND10_CONFIGURATION_START_RESOLVER start resolver: %1
This message shows whether or not the resolver should be
started according to the configuration.
% BIND10_INVALID_STATISTICS_DATA invalid specification of statistics data specified
An error was encountered when the boss module specified
statistics data which is invalid for the boss specification file.
......
......@@ -311,51 +311,14 @@ class BoB:
# If this is initial update, don't do anything now, leave it to startup
if not self.runnable:
return
# Now we declare few functions used only internally here. Besides the
# benefit of not polluting the name space, they are closures, so we
# don't need to pass some variables
def start_stop(name, start, stop):
if 'start_' + name in new_config:
if new_config['start_' + name]:
start()
else:
stop()
# These four functions are passed to start_stop (smells like functional
# programming little bit)
def resolver_on():
self.component_config['b10-resolver'] = { 'kind': 'needed',
'special': 'resolver' }
def resolver_off():
if 'b10-resolver' in self.component_config:
del self.component_config['b10-resolver']
def auth_on():
self.component_config['b10-auth'] = { 'kind': 'needed',
'special': 'auth' }
self.component_config['b10-xfrout'] = { 'kind': 'dispensable',
'address': 'Xfrout' }
self.component_config['b10-xfrin'] = { 'kind': 'dispensable',
'special': 'xfrin' }
self.component_config['b10-zonemgr'] = { 'kind': 'dispensable',
'address': 'Zonemgr' }
def auth_off():
if 'b10-zonemgr' in self.component_config:
del self.component_config['b10-zonemgr']
if 'b10-xfrin' in self.component_config:
del self.component_config['b10-xfrin']
if 'b10-xfrout' in self.component_config:
del self.component_config['b10-xfrout']
if 'b10-auth' in self.component_config:
del self.component_config['b10-auth']
# The real code of the config handler function follows here
logger.debug(DBG_COMMANDS, BIND10_RECEIVED_NEW_CONFIGURATION,
new_config)
start_stop('resolver', resolver_on, resolver_off)
start_stop('auth', auth_on, auth_off)
self.__propagate_component_config(self.component_config)
answer = isc.config.ccsession.create_answer(0)
return answer
try:
if 'components' in new_config:
self.__propagate_component_config(new_config['components'])
return isc.config.ccsession.create_answer(0)
except Exception as e:
return isc.config.ccsession.create_answer(1, str(e))
def get_processes(self):
pids = list(self.components.keys())
......@@ -424,24 +387,20 @@ class BoB:
self.components[pid].kill(True)
self.components = {}
def read_bind10_config(self):
def _read_bind10_config(self):
"""
Reads the parameters associated with the BoB module itself.
At present these are the components to start although arguably this
information should be in the configuration for the appropriate
module itself. (However, this would cause difficulty in the case of
xfrin/xfrout and zone manager as we don't need to start those if we
are not running the authoritative server.)
This means the list of components we should start now.
This could easily be combined into start_all_processes, but
it stays because of historical reasons and because the tests
replace the method sometimes.
"""
logger.info(BIND10_READING_BOSS_CONFIGURATION)
config_data = self.ccs.get_full_config()
self.cfg_start_auth = config_data.get("start_auth")
self.cfg_start_resolver = config_data.get("start_resolver")
logger.info(BIND10_CONFIGURATION_START_AUTH, self.cfg_start_auth)
logger.info(BIND10_CONFIGURATION_START_RESOLVER, self.cfg_start_resolver)
self.__propagate_component_config(config_data['components'])
def log_starting(self, process, port = None, address = None):
"""
......@@ -720,60 +679,14 @@ class BoB:
# Connect to the msgq. This is not a process, so it's not handled
# inside the configurator.
c_channel_env = self.c_channel_env
self.start_ccsession(c_channel_env)
self.start_ccsession(self.c_channel_env)
# Extract the parameters associated with Bob. This can only be
# done after the CC Session is started. Note that the logging
# configuration may override the "-v" switch set on the command line.
self.read_bind10_config()
# Continue starting the components. The authoritative server (if
# selected):
component_config = {}
if self.cfg_start_auth:
component_config['b10-auth'] = { 'kind': 'needed',
'special': 'auth' }
self.__propagate_component_config(component_config)
# ... and resolver (if selected):
if self.cfg_start_resolver:
component_config['b10-resolver'] = { 'kind': 'needed',
'special': 'resolver' }
self.__propagate_component_config(component_config)
# Everything after the main components can run as non-root.
# TODO: this is only temporary - once the privileged socket creator is
# fully working, nothing else will run as root.
if self.uid is not None:
posix.setuid(self.uid)
# xfrin/xfrout and the zone manager are only meaningful if the
# authoritative server has been started.
if self.cfg_start_auth:
component_config['b10-xfrout'] = { 'kind': 'dispensable',
'address': 'Xfrout' }
component_config['b10-xfrin'] = { 'kind': 'dispensable',
'special': 'xfrin' }
component_config['b10-zonemgr'] = { 'kind': 'dispensable',
'address': 'Zonemgr' }
self.__propagate_component_config(component_config)
# ... and finally start the remaining components
component_config['b10-stats'] = { 'kind': 'dispensable',
'address': 'Stats' }
component_config['b10-stats-httpd'] = { 'kind': 'dispensable',
'address': 'StatsHttpd' }
component_config['b10-cmdctl'] = { 'kind': 'needed',
'special': 'cmdctl' }
if self.cfg_start_dhcp6:
component_config['b10-dhcp6'] = { 'kind': 'dispensable',
'address': 'DHCP6' }
self.__propagate_component_config(component_config)
self.component_config = component_config
self._read_bind10_config()
# TODO: Return the dropping of privileges
def startup(self):
"""
......@@ -838,22 +751,6 @@ class BoB:
else:
self.runnable = False
# Series of stop_process wrappers
def stop_resolver(self):
self.stop_process('b10-resolver', 'Resolver')
def stop_auth(self):
self.stop_process('b10-auth', 'Auth')
def stop_xfrout(self):
self.stop_process('b10-xfrout', 'Xfrout')
def stop_xfrin(self):
self.stop_process('b10-xfrin', 'Xfrin')
def stop_zonemgr(self):
self.stop_process('b10-zonemgr', 'Zonemgr')
def shutdown(self):
"""Stop the BoB instance."""
logger.info(BIND10_SHUTDOWN)
......
......@@ -4,16 +4,66 @@
"module_description": "Master process",
"config_data": [
{
"item_name": "start_auth",
"item_type": "boolean",
"item_name": "components",
"item_type": "named_set",
"item_optional": false,
"item_default": true
},
{
"item_name": "start_resolver",
"item_type": "boolean",
"item_optional": false,
"item_default": false
"item_default": {
"b10-xfrin": { "address": "Xfrin", "kind": "dispensable" },
"b10-xfrout": { "address": "Xfrout", "kind": "dispensable" },
"b10-auth": { "special": "auth", "kind": "needed" },
"b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
"b10-stats": { "address": "Stats", "kind": "dispensable" },
"b10-stats-httpd": {
"address": "StatsHttpd",
"kind": "dispensable"
},
"b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
},
"named_set_item_spec": {
"item_name": "component",
"item_type": "map",
"item_optional": false,
"item_default": { },
"map_item_spec": [
{
"item_name": "special",
"item_optional": true,
"item_type": "string"
},
{
"item_name": "process",
"item_optional": true,
"item_type": "string"
},
{
"item_name": "kind",
"item_optional": false,
"item_type": "string",
"item_default": "dispensable"
},
{
"item_name": "address",
"item_optional": true,
"item_type": "string"
},
{
"item_name": "params",
"item_optional": true,
"item_type": "list",
"list_item_spec": {
"item_name": "param",
"item_optional": false,
"item_type": "string",
"item_default": ""
}
},
{
"item_name": "priority",
"item_optional": true,
"item_type": "integer"
}
]
}
}
],
"commands": [
......
......@@ -244,7 +244,7 @@ class MockBob(BoB):
def stop_creator(self, kill=False):
self.creator = False
def read_bind10_config(self):
def _read_bind10_config(self):
# Configuration options are set directly
pass
......@@ -278,7 +278,6 @@ class MockBob(BoB):
def start_simple(self, name):
procmap = { 'b10-xfrout': self.start_xfrout,
'b10-xfrin': self.start_xfrin,
'b10-zonemgr': self.start_zonemgr,
'b10-stats': self.start_stats,
'b10-stats-httpd': self.start_stats_httpd,
......@@ -324,13 +323,13 @@ class MockBob(BoB):
return procinfo
def start_dhcp6(self):
self.stats = True
self.dhcp6 = True
procinfo = ProcessInfo('b10-dhcp6', ['/bin/false'])
procinfo.pid = 13
return procinfo
def start_dhcp4(self):
self.stats = True
self.dhcp4 = True
procinfo = ProcessInfo('b10-dhcp4', ['/bin/false'])
procinfo.pid = 14
return procinfo
......@@ -346,9 +345,7 @@ class MockBob(BoB):
'b10-cmdctl': self.stop_cmdctl }
procmap[process]()
# We don't really use all of these stop_ methods. But it might turn out
# someone would add some stop_ method to BoB and we want that one overriden
# in case he forgets to update the tests.
# Some functions to pretend we stop processes, use by stop_process
def stop_msgq(self):
if self.msgq:
del self.components[2]
......@@ -467,70 +464,61 @@ class TestStartStopProcessesBob(unittest.TestCase):
"""
Check if proper combinations of DHCPv4 and DHCpv6 can be started
"""
v4found = 'b10-dhcp4' in bob.component_config
v6found = 'b10-dhcp6' in bob.component_config
# there should be exactly one DHCPv4 daemon (if v4==True)
# there should be exactly one DHCPv6 daemon (if v6==True)
self.assertEqual(v4==True, v4found==1)
self.assertEqual(v6==True, v6found==1)
self.assertEqual(v4, bob.dhcp4)
self.assertEqual(v6, bob.dhcp6)
self.check_environment_unchanged()
# Checks the components started when starting neither auth nor resolver
# is specified.
def test_start_none(self):
# Create BoB and ensure correct initialization
bob = MockBob()
self.check_preconditions(bob)
# Start components and check what was started
bob.cfg_start_auth = False
bob.cfg_start_resolver = False
bob.start_all_components()
self.check_started_none(bob)
# Checks the components started when starting only the auth process
def test_start_auth(self):
# Create BoB and ensure correct initialization
def construct_config(self, start_auth, start_resolver):
# The things that are common, not turned on an off
config = {}
config['b10-stats'] = { 'kind': 'dispensable', 'address': 'Stats' }
config['b10-stats-httpd'] = { 'kind': 'dispensable',
'address': 'StatsHttpd' }
config['b10-cmdctl'] = { 'kind': 'needed', 'special': 'cmdctl' }
if start_auth:
config['b10-auth'] = { 'kind': 'needed', 'special': 'auth' }
config['b10-xfrout'] = { 'kind': 'dispensable',
'address': 'Xfrout' }
config['b10-xfrin'] = { 'kind': 'dispensable', 'special': 'xfrin' }
config['b10-zonemgr'] = { 'kind': 'dispensable',
'address': 'Zonemgr' }
if start_resolver:
config['b10-resolver'] = { 'kind': 'needed',
'special': 'resolver' }
return {'components': config}
def config_start_init(self, start_auth, start_resolver):
"""
Test the configuration is loaded at the startup.
"""
bob = MockBob()
self.check_preconditions(bob)
# Start components and check what was started
bob.cfg_start_auth = True
bob.cfg_start_resolver = False
config = self.construct_config(start_auth, start_resolver)
class CC:
def get_full_config(self):
return config
# Provide the fake CC with data
bob.ccs = CC()
# And make sure it's not overwritten
def start_ccsession():
bob.ccsession = True
bob.start_ccsession = lambda _: start_ccsession()
# We need to return the original _read_bind10_config
bob._read_bind10_config = lambda: BoB._read_bind10_config(bob)
bob.start_all_components()
self.check_started(bob, True, start_auth, start_resolver)
self.check_environment_unchanged()
self.check_started_auth(bob)
def test_start_none(self):
self.config_start_init(False, False)
# Checks the components started when starting only the resolver process
def test_start_resolver(self):
# Create BoB and ensure correct initialization
bob = MockBob()
self.check_preconditions(bob)
# Start components and check what was started
bob.cfg_start_auth = False
bob.cfg_start_resolver = True
bob.start_all_components()
self.config_start_init(False, True)
self.check_started_resolver(bob)
def test_start_auth(self):
self.config_start_init(True, False)
# Checks the components started when starting both auth and resolver process
def test_start_both(self):
# Create BoB and ensure correct initialization
bob = MockBob()
self.check_preconditions(bob)
# Start components and check what was started
bob.cfg_start_auth = True
bob.cfg_start_resolver = True
bob.start_all_components()
self.check_started_both(bob)
self.config_start_init(True, True)
def test_config_start(self):
"""
......@@ -542,17 +530,14 @@ class TestStartStopProcessesBob(unittest.TestCase):
bob = MockBob()
self.check_preconditions(bob)
# Start components (nothing much should be started, as in
# test_start_none)
bob.cfg_start_auth = False
bob.cfg_start_resolver = False
bob.start_all_components()
bob.runnable = True
bob._BoB_started = True
bob.config_handler(self.construct_config(False, False))
self.check_started_none(bob)
# Enable both at once
bob.config_handler({'start_auth': True, 'start_resolver': True})
bob.config_handler(self.construct_config(True, True))
self.check_started_both(bob)
# Not touched by empty change
......@@ -560,11 +545,11 @@ class TestStartStopProcessesBob(unittest.TestCase):
self.check_started_both(bob)
# Not touched by change to the same configuration
bob.config_handler({'start_auth': True, 'start_resolver': True})
bob.config_handler(self.construct_config(True, True))
self.check_started_both(bob)
# Turn them both off again
bob.config_handler({'start_auth': False, 'start_resolver': False})
bob.config_handler(self.construct_config(False, False))
self.check_started_none(bob)
# Not touched by empty change
......@@ -572,47 +557,46 @@ class TestStartStopProcessesBob(unittest.TestCase):
self.check_started_none(bob)
# Not touched by change to the same configuration
bob.config_handler({'start_auth': False, 'start_resolver': False})
bob.config_handler(self.construct_config(False, False))
self.check_started_none(bob)
# Start and stop auth separately
bob.config_handler({'start_auth': True})
bob.config_handler(self.construct_config(True, False))
self.check_started_auth(bob)
bob.config_handler({'start_auth': False})
bob.config_handler(self.construct_config(False, False))
self.check_started_none(bob)
# Start and stop resolver separately
bob.config_handler({'start_resolver': True})
bob.config_handler(self.construct_config(False, True))
self.check_started_resolver(bob)
bob.config_handler({'start_resolver': False})
bob.config_handler(self.construct_config(False, False))
self.check_started_none(bob)
# Alternate
bob.config_handler({'start_auth': True})
bob.config_handler(self.construct_config(True, False))
self.check_started_auth(bob)
bob.config_handler({'start_auth': False, 'start_resolver': True})
bob.config_handler(self.construct_config(False, True))
self.check_started_resolver(bob)
bob.config_handler({'start_auth': True, 'start_resolver': False})
bob.config_handler(self.construct_config(True, False))
self.check_started_auth(bob)
def test_config_start_once(self):
"""
Tests that a process is started only once.
Tests that a component is started only once.
"""
# Create BoB and ensure correct initialization
bob = MockBob()
self.check_preconditions(bob)
# Start components (both)
bob.cfg_start_auth = True
bob.cfg_start_resolver = True
bob.start_all_components()
bob._BoB_started = True
bob.runnable = True
bob.config_handler(self.construct_config(True, True))
self.check_started_both(bob)
bob.start_auth = lambda: self.fail("Started auth again")
......@@ -622,8 +606,7 @@ class TestStartStopProcessesBob(unittest.TestCase):
bob.start_resolver = lambda: self.fail("Started resolver again")
# Send again we want to start them. Should not do it, as they are.
bob.config_handler({'start_auth': True})
bob.config_handler({'start_resolver': True})
bob.config_handler(self.construct_config(True, True))
def test_config_not_started_early(self):
"""
......@@ -648,29 +631,24 @@ class TestStartStopProcessesBob(unittest.TestCase):
bob = MockBob()
self.check_preconditions(bob)
# don't care about DNS stuff
bob.cfg_start_auth = False
bob.cfg_start_resolver = False
# v4 and v6 disabled
bob.cfg_start_dhcp6 = False
bob.cfg_start_dhcp4 = False
bob.start_all_components()
bob._BoB_started = True
bob.runnable = True
bob.config_handler(self.construct_config(False, False))
self.check_started_dhcp(bob, False, False)
def test_start_dhcp_v6only(self):
# Create BoB and ensure correct initialization
bob = MockBob()
self.check_preconditions(bob)
# don't care about DNS stuff
bob.cfg_start_auth = False
bob.cfg_start_resolver = False
# v6 only enabled
bob.cfg_start_dhcp6 = True
bob.cfg_start_dhcp4 = False
bob.start_all_components()
bob.runnable = True
bob._BoB_started = True
config = self.construct_config(False, False)
config['components']['b10-dhcp6'] = { 'kind': 'needed',
'address': 'Dhcp6' }
bob.config_handler(config)
self.check_started_dhcp(bob, False, True)
# uncomment when dhcpv4 becomes implemented
......@@ -857,6 +835,159 @@ class TestBrittle(unittest.TestCase):
sys.stdout = old_stdout
self.assertFalse(bob.runnable)
class TestBossComponents(unittest.TestCase):
"""
Test the boss propagates component configuration properly to the
component configurator and acts sane.
"""
def setUp(self):
self.__param = None
self.__called = False
self.__compconfig = {
'comp': {
'kind': 'needed',
'process': 'cat'
}
}
def __unary_hook(self, param):
"""
A hook function that stores the parameter for later examination.
"""
self.__param = param
def __nullary_hook(self):
"""
A hook function that notes down it was called.
"""
self.__called = True
def __check_core(self, config):
"""
A function checking that the config contains parts for the valid
core component configuration.
"""
self.assertIsNotNone(config)
for component in ['sockcreator', 'msgq', 'cfgmgr']:
self.assertTrue(component in config)
self.assertEqual(component, config[component]['special'])
self.assertEqual('core', config[component]['kind'])
def __check_extended(self, config):
"""
This checks that the config contains the core and one more component.
"""
self.__check_core(config)
self.assertTrue('comp' in config)
self.assertEqual('cat', config['comp']['process'])
self.assertEqual('needed', config['comp']['kind'])
self.assertEqual(4, len(config))
def test_correct_run(self):
"""
Test the situation when we run in usual scenario, nothing fails,
we just start, reconfigure and then stop peacefully.
"""
bob = MockBob()
# Start it
orig = bob._component_configurator.startup
bob._component_configurator.startup = self.__unary_hook
bob.start_all_components()
bob._component_configurator.startup = orig
self.__check_core(self.__param)
self.assertEqual(3, len(self.__param))
# Reconfigure it
self.__param = None
orig = bob._component_configurator.reconfigure
bob._component_configurator.reconfigure = self.__unary_hook
# Otherwise it does not work
bob.runnable = True
bob.config_handler({'components': self.__compconfig})
self.__check_extended(self.__param)
currconfig = self.__param
# If we reconfigure it, but it does not contain the components part,
# nothing is called
bob.config_handler({})
self.assertEqual(self.__param, currconfig)