def test__writes_config_and_doesnt_use_omapi_when_was_off(self): write_file = self.patch_sudo_write_file() get_service_state = self.patch_getServiceState() get_service_state.return_value = ServiceState( SERVICE_STATE.OFF, "dead") restart_service = self.patch_restartService() ensure_service = self.patch_ensureService() update_hosts = self.patch_update_hosts() failover_peers = make_failover_peer_config() shared_network = make_shared_network() [shared_network] = fix_shared_networks_failover( [shared_network], [failover_peers]) host = make_host(dhcp_snippets=[]) interface = make_interface() global_dhcp_snippets = make_global_dhcp_snippets() expected_config = factory.make_name('config') self.patch_get_config().return_value = expected_config dhcp_service = dhcp.service_monitor.getServiceByName( self.server.dhcp_service) on = self.patch_autospec(dhcp_service, "on") omapi_key = factory.make_name('omapi_key') old_host = make_host(dhcp_snippets=[]) old_state = dhcp.DHCPState( omapi_key, [failover_peers], [shared_network], [old_host], [interface], global_dhcp_snippets) dhcp._current_server_state[self.server.dhcp_service] = old_state yield self.configure( omapi_key, [failover_peers], [shared_network], [host], [interface], global_dhcp_snippets) self.assertThat( write_file, MockCallsMatch( call( self.server.config_filename, expected_config.encode("utf-8")), call( self.server.interfaces_filename, interface["name"].encode("utf-8")), )) self.assertThat(on, MockCalledOnceWith()) self.assertThat( get_service_state, MockCalledOnceWith(self.server.dhcp_service, now=True)) self.assertThat( restart_service, MockNotCalled()) self.assertThat( ensure_service, MockCalledOnceWith(self.server.dhcp_service)) self.assertThat( update_hosts, MockNotCalled()) self.assertEquals( dhcp._current_server_state[self.server.dhcp_service], dhcp.DHCPState( omapi_key, [failover_peers], [shared_network], [host], [interface], global_dhcp_snippets))
def test_performs_operations(self): remove_host = make_host() add_host = make_host() modify_host = make_host() omapi_cli = Mock() self.patch(dhcp, "OmapiClient").return_value = omapi_cli dhcp._update_hosts(Mock(), [remove_host], [add_host], [modify_host]) self.assertEqual( omapi_cli.mock_calls, [ call.del_host(remove_host["mac"]), call.add_host(add_host["mac"], add_host["ip"]), call.update_host(modify_host["mac"], modify_host["ip"]), ], )
def make_sample_params_only(ipv6=False, with_interface=False): """Return a dict of arbitrary DHCP configuration parameters. :param ipv6: When true, prepare configuration for a DHCPv6 server, otherwise prepare configuration for a DHCPv4 server. :return: A dictionary of sample configuration. """ failover_peers = [ make_failover_peer_config() for _ in range(3) ] shared_networks = [ make_shared_network(ipv6=ipv6, with_interface=with_interface) for _ in range(3) ] shared_networks = fix_shared_networks_failover( shared_networks, failover_peers) return { 'omapi_key': b64encode(factory.make_bytes()).decode("ascii"), 'failover_peers': failover_peers, 'shared_networks': shared_networks, 'hosts': [make_host(ipv6=ipv6) for _ in range(3)], 'global_dhcp_snippets': make_global_dhcp_snippets(), 'ipv6': ipv6, }
def test_requires_restart_returns_False_when_hosts_are_ipv6_and_server_is_ipv6( self, ): ( omapi_key, failover_peers, shared_networks, _, interfaces, global_dhcp_snippets, ) = self.make_args() state = dhcp.DHCPState( omapi_key, failover_peers, shared_networks, [], interfaces, global_dhcp_snippets, ) new_hosts = [make_host(dhcp_snippets=[], ipv6=True) for _ in range(3)] new_state = dhcp.DHCPState( omapi_key, copy.deepcopy(failover_peers), copy.deepcopy(shared_networks), new_hosts, copy.deepcopy(interfaces), copy.deepcopy(global_dhcp_snippets), ) self.assertFalse( new_state.requires_restart(state, is_dhcpv6_server=True))
def test_requires_restart_returns_False_when_hosts_different(self): ( omapi_key, failover_peers, shared_networks, hosts, interfaces, global_dhcp_snippets, ) = self.make_args() state = dhcp.DHCPState( omapi_key, failover_peers, shared_networks, hosts, interfaces, global_dhcp_snippets, ) changed_hosts = copy.deepcopy(hosts) changed_hosts.append(make_host(dhcp_snippets=[])) new_state = dhcp.DHCPState( omapi_key, copy.deepcopy(failover_peers), copy.deepcopy(shared_networks), changed_hosts, copy.deepcopy(interfaces), copy.deepcopy(global_dhcp_snippets), ) self.assertFalse(new_state.requires_restart(state))
def test_host_diff_returns_removal_added_and_modify(self): ( omapi_key, failover_peers, shared_networks, hosts, interfaces, global_dhcp_snippets, ) = self.make_args() state = dhcp.DHCPState( omapi_key, failover_peers, shared_networks, hosts, interfaces, global_dhcp_snippets, ) changed_hosts = copy.deepcopy(hosts) removed_host = changed_hosts.pop() modified_host = changed_hosts[0] modified_host["ip"] = factory.make_ip_address() added_host = make_host() changed_hosts.append(added_host) new_state = dhcp.DHCPState( omapi_key, copy.deepcopy(failover_peers), copy.deepcopy(shared_networks), changed_hosts, copy.deepcopy(interfaces), copy.deepcopy(global_dhcp_snippets), ) self.assertEqual( ([removed_host], [added_host], [modified_host]), new_state.host_diff(state), )
def test__writes_config_and_calls_restart_when_no_current_state(self): write_file = self.patch_sudo_write_file() restart_service = self.patch_restartService() failover_peers = make_failover_peer_config() shared_network = make_shared_network() [shared_network] = fix_shared_networks_failover( [shared_network], [failover_peers] ) host = make_host() interface = make_interface() global_dhcp_snippets = make_global_dhcp_snippets() expected_config = factory.make_name("config") self.patch_get_config().return_value = expected_config dhcp_service = dhcp.service_monitor.getServiceByName( self.server.dhcp_service ) on = self.patch_autospec(dhcp_service, "on") omapi_key = factory.make_name("omapi_key") yield self.configure( omapi_key, [failover_peers], [shared_network], [host], [interface], global_dhcp_snippets, ) self.assertThat( write_file, MockCallsMatch( call( self.server.config_filename, expected_config.encode("utf-8"), mode=0o640, ), call( self.server.interfaces_filename, interface["name"].encode("utf-8"), mode=0o640, ), ), ) self.assertThat(on, MockCalledOnceWith()) self.assertThat( restart_service, MockCalledOnceWith(self.server.dhcp_service) ) self.assertEquals( dhcp._current_server_state[self.server.dhcp_service], dhcp.DHCPState( omapi_key, [failover_peers], [shared_network], [host], [interface], global_dhcp_snippets, ), )
def make_sample_params_only(ipv6=False, with_interface=False, disabled_boot_architectures=None): """Return a dict of arbitrary DHCP configuration parameters. :param ipv6: When true, prepare configuration for a DHCPv6 server, otherwise prepare configuration for a DHCPv4 server. :return: A dictionary of sample configuration. """ failover_peers = [make_failover_peer_config() for _ in range(3)] shared_networks = [ make_shared_network( ipv6=ipv6, with_interface=with_interface, disabled_boot_architectures=disabled_boot_architectures, ) for _ in range(3) ] shared_networks = fix_shared_networks_failover(shared_networks, failover_peers) return { "omapi_key": b64encode(factory.make_bytes()).decode("ascii"), "failover_peers": failover_peers, "shared_networks": shared_networks, "hosts": [make_host(ipv6=ipv6) for _ in range(3)], "global_dhcp_snippets": make_global_dhcp_snippets(), "ipv6": ipv6, }
def test__performs_operations(self): omshell = Mock() self.patch(dhcp, "Omshell").return_value = omshell remove_host = make_host() add_host = make_host() modify_host = make_host() server = Mock() server.ipv6 = factory.pick_bool() dhcp._update_hosts(server, [remove_host], [add_host], [modify_host]) self.assertThat(omshell.remove, MockCallsMatch(call(remove_host["mac"]), )) self.assertThat( omshell.create, MockCallsMatch(call(add_host["ip"], add_host["mac"]), )) self.assertThat( omshell.modify, MockCallsMatch(call(modify_host["ip"], modify_host["mac"]), ))
def make_args(self): omapi_key = factory.make_name("omapi_key") failover_peers = [make_failover_peer_config() for _ in range(3)] shared_networks = [make_shared_network() for _ in range(3)] shared_networks = fix_shared_networks_failover(shared_networks, failover_peers) hosts = [make_host() for _ in range(3)] interfaces = [make_interface() for _ in range(3)] return (omapi_key, failover_peers, shared_networks, hosts, interfaces, make_global_dhcp_snippets())
def test__converts_dhcp_restart_failure_to_CannotConfigureDHCP(self): self.patch_sudo_write_file() self.patch_sudo_delete_file() self.patch_restartService().side_effect = ServiceActionError() failover_peers = [make_failover_peer_config()] shared_networks = fix_shared_networks_failover( [make_shared_network()], failover_peers) with ExpectedException(exceptions.CannotConfigureDHCP): yield self.configure( factory.make_name('key'), failover_peers, shared_networks, [make_host()], [make_interface()], make_global_dhcp_snippets())
def test__bad_config(self): omapi_key = factory.make_name("omapi_key") failover_peers = make_failover_peer_config() shared_network = make_shared_network() [shared_network] = fix_shared_networks_failover( [shared_network], [failover_peers] ) host = make_host() interface = make_interface() global_dhcp_snippets = make_global_dhcp_snippets() dhcpd_error = ( "Internet Systems Consortium DHCP Server 4.3.3\n" "Copyright 2004-2015 Internet Systems Consortium.\n" "All rights reserved.\n" "For info, please visit https://www.isc.org/software/dhcp/\n" "/tmp/maas-dhcpd-z5c7hfzt line 14: semicolon expected.\n" "ignore \n" "^\n" "Configuration file errors encountered -- exiting\n" "\n" "If you think you have received this message due to a bug rather\n" "than a configuration issue please read the section on submitting" "\n" "bugs on either our web page at www.isc.org or in the README file" "\n" "before submitting a bug. These pages explain the proper\n" "process and the information we find helpful for debugging..\n" "\n" "exiting." ) self.mock_call_and_check.side_effect = ExternalProcessError( returncode=1, cmd=("dhcpd",), output=dhcpd_error ) self.assertEqual( [ { "error": "semicolon expected.", "line_num": 14, "line": "ignore ", "position": "^", } ], self.validate( omapi_key, [failover_peers], [shared_network], [host], [interface], global_dhcp_snippets, ), )
def test__does_not_log_ServiceActionError_when_restarting(self): self.patch_sudo_write_file() self.patch_restartService().side_effect = ServiceActionError() failover_peers = [make_failover_peer_config()] shared_networks = fix_shared_networks_failover([make_shared_network()], failover_peers) with FakeLogger("maas") as logger: with ExpectedException(exceptions.CannotConfigureDHCP): yield self.configure(factory.make_name('key'), failover_peers, shared_networks, [make_host()], [make_interface()], make_global_dhcp_snippets()) self.assertDocTestMatches("", logger.output)
def test_fail_modify(self): host = make_host() omapi_cli = Mock() omapi_cli.update_host.side_effect = OmapiError("Fail") self.patch(dhcp, "OmapiClient").return_value = omapi_cli err = self.assertRaises( exceptions.CannotModifyHostMap, dhcp._update_hosts, Mock(), [], [], [host], ) self.assertEqual(str(err), "Fail")
def test__good_config(self): omapi_key = factory.make_name('omapi_key') failover_peers = make_failover_peer_config() shared_network = make_shared_network() [shared_network] = fix_shared_networks_failover([shared_network], [failover_peers]) host = make_host() interface = make_interface() global_dhcp_snippets = make_global_dhcp_snippets() self.assertEqual( None, self.validate(omapi_key, [failover_peers], [shared_network], [host], [interface], global_dhcp_snippets))
def test_converts_failure_writing_file_to_CannotConfigureDHCP(self): self.patch_sudo_delete_file() self.patch_sudo_write_file().side_effect = ExternalProcessError( 1, "sudo something") self.patch_restartService() failover_peers = [make_failover_peer_config()] shared_networks = fix_shared_networks_failover([make_shared_network()], failover_peers) with ExpectedException(exceptions.CannotConfigureDHCP): yield self.configure( factory.make_name("key"), failover_peers, shared_networks, [make_host()], [make_interface()], make_global_dhcp_snippets(), )
def test__bad_config(self): omapi_key = factory.make_name('omapi_key') failover_peers = make_failover_peer_config() shared_network = make_shared_network() [shared_network] = fix_shared_networks_failover( [shared_network], [failover_peers]) host = make_host() interface = make_interface() global_dhcp_snippets = make_global_dhcp_snippets() dhcpd_error = ( 'Internet Systems Consortium DHCP Server 4.3.3\n' 'Copyright 2004-2015 Internet Systems Consortium.\n' 'All rights reserved.\n' 'For info, please visit https://www.isc.org/software/dhcp/\n' '/tmp/maas-dhcpd-z5c7hfzt line 14: semicolon expected.\n' 'ignore \n' '^\n' 'Configuration file errors encountered -- exiting\n' '\n' 'If you think you have received this message due to a bug rather\n' 'than a configuration issue please read the section on submitting' '\n' 'bugs on either our web page at www.isc.org or in the README file' '\n' 'before submitting a bug. These pages explain the proper\n' 'process and the information we find helpful for debugging..\n' '\n' 'exiting.' ) self.patch(dhcp, 'call_and_check').side_effect = ExternalProcessError( returncode=1, cmd=("dhcpd",), output=dhcpd_error) self.assertEqual([{ 'error': 'semicolon expected.', 'line_num': 14, 'line': 'ignore ', 'position': '^', }], self.validate( omapi_key, [failover_peers], [shared_network], [host], [interface], global_dhcp_snippets))
def test__does_log_other_exceptions_when_restarting(self): self.patch_sudo_write_file() self.patch_restartService().side_effect = factory.make_exception( "DHCP is on strike today") failover_peers = [make_failover_peer_config()] shared_networks = fix_shared_networks_failover([make_shared_network()], failover_peers) with FakeLogger("maas") as logger: with ExpectedException(exceptions.CannotConfigureDHCP): yield self.configure( factory.make_name("key"), failover_peers, shared_networks, [make_host()], [make_interface()], make_global_dhcp_snippets(), ) self.assertDocTestMatches( "DHCPv... server failed to restart: " "DHCP is on strike today", logger.output, )
def test__writes_config_and_uses_omapi_to_update_hosts(self): write_file = self.patch_sudo_write_file() get_service_state = self.patch_getServiceState() get_service_state.return_value = ServiceState(SERVICE_STATE.ON, "running") restart_service = self.patch_restartService() ensure_service = self.patch_ensureService() update_hosts = self.patch_update_hosts() failover_peers = make_failover_peer_config() shared_network = make_shared_network() [shared_network] = fix_shared_networks_failover([shared_network], [failover_peers]) old_hosts = [make_host(dhcp_snippets=[]) for _ in range(3)] interface = make_interface() global_dhcp_snippets = make_global_dhcp_snippets() expected_config = factory.make_name("config") self.patch_get_config().return_value = expected_config dhcp_service = dhcp.service_monitor.getServiceByName( self.server.dhcp_service) on = self.patch_autospec(dhcp_service, "on") omapi_key = factory.make_name("omapi_key") old_state = dhcp.DHCPState( omapi_key, [failover_peers], [shared_network], old_hosts, [interface], global_dhcp_snippets, ) dhcp._current_server_state[self.server.dhcp_service] = old_state new_hosts = copy.deepcopy(old_hosts) removed_host = new_hosts.pop() modified_host = new_hosts[0] modified_host["ip"] = factory.make_ip_address() added_host = make_host(dhcp_snippets=[]) new_hosts.append(added_host) yield self.configure( omapi_key, [failover_peers], [shared_network], new_hosts, [interface], global_dhcp_snippets, ) self.assertThat( write_file, MockCallsMatch( call( self.server.config_filename, expected_config.encode("utf-8"), mode=0o640, ), call( self.server.interfaces_filename, interface["name"].encode("utf-8"), mode=0o640, ), ), ) self.assertThat(on, MockCalledOnceWith()) self.assertThat( get_service_state, MockCalledOnceWith(self.server.dhcp_service, now=True), ) self.assertThat(restart_service, MockNotCalled()) self.assertThat(ensure_service, MockCalledOnceWith(self.server.dhcp_service)) self.assertThat( update_hosts, MockCalledOnceWith(ANY, [removed_host], [added_host], [modified_host]), ) self.assertEquals( dhcp._current_server_state[self.server.dhcp_service], dhcp.DHCPState( omapi_key, [failover_peers], [shared_network], new_hosts, [interface], global_dhcp_snippets, ), )
def test_writes_config_and_restarts_when_omapi_fails(self): write_file = self.patch_sudo_write_file() get_service_state = self.patch_getServiceState() get_service_state.return_value = ServiceState(SERVICE_STATE.ON, "running") restart_service = self.patch_restartService() ensure_service = self.patch_ensureService() update_hosts = self.patch_update_hosts() update_hosts.side_effect = factory.make_exception() failover_peers = make_failover_peer_config() shared_network = make_shared_network() [shared_network] = fix_shared_networks_failover([shared_network], [failover_peers]) old_hosts = [make_host(dhcp_snippets=[]) for _ in range(3)] interface = make_interface() global_dhcp_snippets = make_global_dhcp_snippets() expected_config = factory.make_name("config") self.patch_get_config().return_value = expected_config dhcp_service = dhcp.service_monitor.getServiceByName( self.server.dhcp_service) on = self.patch_autospec(dhcp_service, "on") omapi_key = factory.make_name("omapi_key") old_state = dhcp.DHCPState( omapi_key, [failover_peers], [shared_network], old_hosts, [interface], global_dhcp_snippets, ) dhcp._current_server_state[self.server.dhcp_service] = old_state new_hosts = copy.deepcopy(old_hosts) removed_host = new_hosts.pop() modified_host = new_hosts[0] modified_host["ip"] = factory.make_ip_address( ipv6=self.server.dhcp_service == "DHCPv6") added_host = make_host(dhcp_snippets=[]) new_hosts.append(added_host) with FakeLogger("maas") as logger: yield self.configure( omapi_key, [failover_peers], [shared_network], new_hosts, [interface], global_dhcp_snippets, ) self.assertThat( write_file, MockCallsMatch( call( self.server.config_filename, expected_config.encode("utf-8"), mode=0o640, ), call( self.server.interfaces_filename, interface["name"].encode("utf-8"), mode=0o640, ), ), ) self.assertThat(on, MockCalledOnceWith()) self.assertThat( get_service_state, MockCalledOnceWith(self.server.dhcp_service, now=True), ) self.assertThat(restart_service, MockCalledOnceWith(self.server.dhcp_service)) self.assertThat(ensure_service, MockCalledOnceWith(self.server.dhcp_service)) self.assertThat( update_hosts, MockCalledOnceWith(ANY, [removed_host], [added_host], [modified_host]), ) self.assertEqual( dhcp._current_server_state[self.server.dhcp_service], dhcp.DHCPState( omapi_key, [failover_peers], [shared_network], new_hosts, [interface], global_dhcp_snippets, ), ) self.assertDocTestMatches( "Failed to update all host maps. Restarting DHCPv... " "service to ensure host maps are in-sync.", logger.output, )