def test__runs_popen_with_expected_parameters(self): popen = self.patch(scan_network_module.subprocess, "Popen") popen.return_value.poll = Mock() popen.return_value.poll.return_value = None popen.return_value.returncode = 0 interface = factory.make_name("eth") ip = factory.make_ip_address(ipv6=False) params = PingParameters(interface, ip) run_ping(params) self.assertThat( popen, MockCalledOnceWith( get_ping_arguments(params), stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, env=get_env_with_locale(), ), )
def test__sends_event_later(self): send_event = self.patch(tftp_module, "send_node_event_ip_address") ip = factory.make_ip_address() self.patch(tftp_module.tftp, "get_remote_address").return_value = ( ip, sentinel.port, ) clock = Clock() log_request(sentinel.filename, clock) self.assertThat(send_event, MockNotCalled()) clock.advance(0.0) self.assertThat( send_event, MockCalledOnceWith( ip_address=ip, description=sentinel.filename, event_type=EVENT_TYPES.NODE_TFTP_REQUEST, ), )
def test__event_is_sent_to_region(self): protocol, connecting = self.patch_rpc_methods() self.addCleanup((yield connecting)) ip_address = factory.make_ip_address() description = factory.make_name("description") event_name = random.choice(list(map_enum(EVENT_TYPES))) yield NodeEventHub().logByIP(event_name, ip_address, description) self.assertThat( protocol.SendEventIPAddress, MockCalledOnceWith( ANY, type_name=event_name, ip_address=ip_address, description=description, ), )
def test_modify_raises_when_omshell_fails(self): # If the call to omshell doesn't result in output containing the # magic string 'hardware-type' it means the set of commands # failed. server_address = factory.make_string() shared_key = factory.make_string() ip_address = factory.make_ip_address(ipv6=self.ipv6) mac_address = factory.make_mac_address() shell = Omshell(server_address, shared_key, ipv6=self.ipv6) # Fake a call that results in a failure with random output. random_output = factory.make_bytes() recorder = FakeMethod(result=(0, random_output)) shell._run = recorder exc = self.assertRaises(ExternalProcessError, shell.modify, ip_address, mac_address) self.assertEqual(random_output, exc.output)
def test_updates_cache_if_event_type_not_found(self): protocol, connecting = self.patch_rpc_methods( side_effect=[succeed({}), fail(NoSuchEventType())]) self.addCleanup((yield connecting)) ip_address = factory.make_ip_address() description = factory.make_name("description") event_name = random.choice(list(map_enum(EVENT_TYPES))) event_hub = NodeEventHub() # Fine the first time. yield event_hub.logByIP(event_name, ip_address, description) # The cache has been populated with the event name. self.assertThat(event_hub._types_registered, Equals({event_name})) # Second time it crashes. with ExpectedException(NoSuchEventType): yield event_hub.logByIP(event_name, ip_address, description) # The event has been removed from the cache. self.assertThat(event_hub._types_registered, HasLength(0))
def test_create_calls_omshell_correctly(self): server_address = factory.make_string() shared_key = factory.make_string() ip_address = factory.make_ip_address(ipv6=self.ipv6) mac_address = factory.make_mac_address() shell = Omshell(server_address, shared_key, ipv6=self.ipv6) # Instead of calling a real omshell, we'll just record the # parameters passed to Popen. recorder = FakeMethod(result=(0, b"hardware-type")) shell._run = recorder shell.create(ip_address, mac_address) expected_script = dedent("""\ server {server} port {port} key omapi_key {key} connect new host set ip-address = {ip} set hardware-address = {mac} set hardware-type = 1 set name = "{name}" create """) expected_script = expected_script.format( server=server_address, port=self.port, key=shared_key, ip=ip_address, mac=mac_address, name=mac_address.replace(":", "-"), ) # Check that the 'stdin' arg contains the correct set of # commands. self.assertEqual( [1, (expected_script.encode("utf-8"), )], [recorder.call_count, recorder.extract_args()[0]], )
def test_reports_mdns_to_region(self): fixture = self.useFixture(MockLiveClusterToRegionRPCFixture()) protocol, connecting = fixture.makeEventLoop(region.UpdateInterfaces, region.ReportMDNSEntries) self.addCleanup((yield connecting)) rpc_service = services.getServiceNamed('rpc') service = RackNetworksMonitoringService(rpc_service, Clock(), enable_monitoring=False) mdns = [{ 'interface': 'eth0', 'hostname': 'boggle.example.com', 'address': factory.make_ip_address(), }] yield service.reportMDNSEntries(mdns) self.assertThat( protocol.ReportMDNSEntries, MockCalledOnceWith(protocol, system_id=rpc_service.getClient().localIdent, mdns=mdns))
def test_render_GET_400_when_no_local_addr(self): path = factory.make_name('path') ip = factory.make_ip_address() request = DummyRequest([path.encode('utf-8')]) request.requestHeaders = Headers({ 'X-Forwarded-For': [ip], 'X-Forwarded-Port': ['%s' % factory.pick_port()], }) self.patch(http.log, 'info') mock_deferLater = self.patch(http, 'deferLater') mock_deferLater.side_effect = always_succeed_with(None) resource = http.HTTPBootResource() yield self.render_GET(resource, request) self.assertEquals(400, request.responseCode) self.assertEquals( b'Missing X-Server-Addr and X-Forwarded-For HTTP headers.', b''.join(request.written))
def test__write_local_and_forwarders(self): forwarders = [ { 'ip': factory.make_ip_address(), 'name': factory.make_name('name') } for _ in range(3) ] config.write_config(True, forwarders) with self.syslog_path.open() as syslog_file: lines = [line.strip() for line in syslog_file.readlines()] self.assertIn( ':fromhost-ip, !isequal, "127.0.0.1" ?MAASenlist', lines) self.assertIn( ':fromhost-ip, !isequal, "127.0.0.1" ?MAASboot', lines) for host in forwarders: self.assertLinesContain( 'target="%s"' % host['ip'], lines) self.assertLinesContain( 'queue.filename="%s"' % host['name'], lines)
def test_failure_is_suppressed_if_node_not_found(self): protocol, connecting = self.patch_rpc_methods( side_effect=[fail(NoSuchNode())]) self.addCleanup((yield connecting)) ip_address = factory.make_ip_address() description = factory.make_name("description") event_name = random.choice(list(map_enum(EVENT_TYPES))) yield NodeEventHub().logByIP(event_name, ip_address, description) self.assertThat( protocol.SendEventIPAddress, MockCalledOnceWith( ANY, type_name=event_name, ip_address=ip_address, description=description, ), )
def test_raises_error_when_omshell_crashes(self): error_message = factory.make_name("error").encode("ascii") omshell = Mock() omshell.create.side_effect = ExternalProcessError( returncode=2, cmd=("omshell",), output=error_message) mac = factory.make_mac_address() ip = factory.make_ip_address() with FakeLogger("maas.dhcp") as logger: error = self.assertRaises( exceptions.CannotCreateHostMap, dhcp._create_host_map, omshell, mac, ip) # The CannotCreateHostMap exception includes a message describing the # problematic mapping. self.assertDocTestMatches( "Could not create host map for %s -> %s: ..." % (mac, ip), str(error)) # A message is also written to the maas.dhcp logger that describes the # problematic mapping. self.assertDocTestMatches( "Could not create host map for %s -> %s: ..." % (mac, ip), logger.output)
def test__runs_nmap_e2e(self): ip = factory.make_ip_address(ipv6=False) # Force the use of `nmap` by ensuring it is reported as available. self.has_command_available_mock.return_value = True cidr = '%s/32' % ip slow = random.choice([True, False]) args = ['eth0', cidr] if slow is True: args.append('--slow') self.run_command(*args) expected_params = NmapParameters(interface='eth0', cidr=cidr, slow=slow) self.assertThat( self.popen, MockCalledOnceWith(get_nmap_arguments(expected_params), stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, env=select_c_utf8_locale(), preexec_fn=os.setsid))
def test_render_GET_400_when_no_local_addr(self): path = factory.make_name("path") ip = factory.make_ip_address() request = DummyRequest([path.encode("utf-8")]) request.requestHeaders = Headers({ "X-Forwarded-For": [ip], "X-Forwarded-Port": ["%s" % factory.pick_port()], }) self.patch(http.log, "info") mock_deferLater = self.patch(http, "deferLater") mock_deferLater.side_effect = always_succeed_with(None) resource = http.HTTPBootResource() yield self.render_GET(resource, request) self.assertEqual(400, request.responseCode) self.assertEqual( b"Missing X-Server-Addr and X-Forwarded-For HTTP headers.", b"".join(request.written), )
def test_reports_neighbours_to_region(self): fixture = self.useFixture(MockLiveClusterToRegionRPCFixture()) protocol, connecting = fixture.makeEventLoop(region.ReportNeighbours) self.addCleanup((yield connecting)) rpc_service = services.getServiceNamed("rpc") service = RackNetworksMonitoringService( rpc_service, Clock(), enable_monitoring=False, enable_beaconing=False, ) neighbours = [{"ip": factory.make_ip_address()}] yield service.reportNeighbours(neighbours) self.assertThat( protocol.ReportNeighbours, MockCalledOnceWith( protocol, system_id=rpc_service.getClient().localIdent, neighbours=neighbours, ), )
def test_render_GET_404_file_not_found(self): path = factory.make_name("path") ip = factory.make_ip_address() request = DummyRequest([path.encode("utf-8")]) request.requestHeaders = Headers({ "X-Server-Addr": ["192.168.1.1"], "X-Server-Port": ["5248"], "X-Forwarded-For": [ip], "X-Forwarded-Port": ["%s" % factory.pick_port()], }) self.patch(http.log, "info") mock_deferLater = self.patch(http, "deferLater") mock_deferLater.side_effect = always_succeed_with(None) self.tftp.backend.get_reader.return_value = fail(FileNotFound(path)) resource = http.HTTPBootResource() yield self.render_GET(resource, request) self.assertEqual(404, request.responseCode) self.assertEqual(b"", b"".join(request.written))
def test_render_GET_404_file_not_found(self): path = factory.make_name('path') ip = factory.make_ip_address() request = DummyRequest([path.encode('utf-8')]) request.requestHeaders = Headers({ 'X-Server-Addr': ['192.168.1.1'], 'X-Server-Port': ['5248'], 'X-Forwarded-For': [ip], 'X-Forwarded-Port': ['%s' % factory.pick_port()], }) self.patch(http.log, 'info') mock_deferLater = self.patch(http, 'deferLater') mock_deferLater.side_effect = always_succeed_with(None) self.tftp.backend.get_reader.return_value = fail(FileNotFound(path)) resource = http.HTTPBootResource() yield self.render_GET(resource, request) self.assertEquals(404, request.responseCode) self.assertEquals(b'', b''.join(request.written))
def test_raises_error_when_omshell_not_connected(self): error = ExternalProcessError(returncode=2, cmd=("omshell", ), output="") self.patch(ExternalProcessError, 'output_as_unicode', 'not connected.') omshell = Mock() omshell.create.side_effect = error mac = factory.make_mac_address() ip = factory.make_ip_address() with FakeLogger("maas.dhcp") as logger: error = self.assertRaises(exceptions.CannotCreateHostMap, dhcp._create_host_map, omshell, mac, ip) # The CannotCreateHostMap exception includes a message describing the # problematic mapping. self.assertDocTestMatches( "Could not create host map for %s -> %s: " "The DHCP server could not be reached." % (mac, ip), str(error)) # A message is also written to the maas.dhcp logger that describes the # problematic mapping. self.assertDocTestMatches( "Could not create host map for %s -> %s: " "The DHCP server could not be reached." % (mac, ip), logger.output)
def test_render_GET_logs_node_event_with_original_path_ip(self): path = factory.make_name("path") ip = factory.make_ip_address() request = DummyRequest([path.encode("utf-8")]) request.requestHeaders = Headers( { "X-Server-Addr": ["192.168.1.1"], "X-Server-Port": ["5248"], "X-Forwarded-For": [ip], "X-Forwarded-Port": ["%s" % factory.pick_port()], } ) log_info = self.patch(http.log, "info") mock_deferLater = self.patch(http, "deferLater") mock_deferLater.side_effect = always_succeed_with(None) self.tftp.backend.get_reader.return_value = fail(AccessViolation()) resource = http.HTTPBootResource() yield self.render_GET(resource, request) self.assertThat( log_info, MockCalledOnceWith( "{path} requested by {remoteHost}", path=path, remoteHost=ip ), ) self.assertThat( mock_deferLater, MockCalledOnceWith( ANY, 0, http.send_node_event_ip_address, event_type=EVENT_TYPES.NODE_HTTP_REQUEST, ip_address=ip, description=path, ), )
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 make_host(hostname=None, interface_name=None, mac_address=None, ip=None, ipv6=False, dhcp_snippets=None): """Return a host entry for a subnet from network.""" if hostname is None: hostname = factory.make_name("host") if interface_name is None: interface_name = factory.make_name("eth") if mac_address is None: mac_address = factory.make_mac_address() if ip is None: ip = factory.make_ip_address(ipv6=ipv6) if dhcp_snippets is None: dhcp_snippets = make_host_dhcp_snippets() return { "host": "%s-%s" % (hostname, interface_name), "mac": mac_address, "ip": ip, "dhcp_snippets": dhcp_snippets, }
def test_runs_nmap_single_threaded(self): ip = factory.make_ip_address(ipv6=False) # Force the use of `nmap` by ensuring it is reported as available. self.has_command_available_mock.return_value = True cidr = "%s/32" % ip slow = random.choice([True, False]) args = ["--threads", "1", "eth0", cidr] if slow is True: args.append("--slow") self.run_command(*args) expected_params = NmapParameters(interface="eth0", cidr=cidr, slow=slow) self.assertThat( self.popen, MockCalledOnceWith( get_nmap_arguments(expected_params), stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, env=get_env_with_locale(), preexec_fn=os.setsid, ), )
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, )
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_calls_omshell_create(self): omshell = Mock() mac = factory.make_mac_address() ip = factory.make_ip_address() dhcp._create_host_map(omshell, mac, ip) self.assertThat(omshell.create, MockCalledOnceWith(ip, mac))