From f5a25fe997b19ff71a335ece755c8904dc8136c8 Mon Sep 17 00:00:00 2001 From: Anna Babich Date: Mon, 15 Feb 2016 12:33:22 +0200 Subject: [PATCH] Functional tests for trigger-crash-dump (microversion 2.17) It's a resource-consuming task to implement full-flow (up to getting and reading a dump file) functional test for trigger-crash-dump. We need to upload Ubuntu image for booting an instance based on it, and to install kdump with its further configuring on this instance. Here, the "light" version of functional test is proposed. It's based on knowledge that trigger-crash-dump uses a NMI injection, and when the 'trigger-crash-dump' operation is executed, instance's kernel receives the MNI signal, and an appropriate message will appear in the instance's log. Wait_for_server_os_boot() method has been added to ClientTestBase to check if instance's operating system is completely booted. The _create_server() method has been removed since the change which puts this method to the base test class has been merged. Change-Id: I2313c5d37a7cf87a8d75e37c93aab136cf028ec1 --- novaclient/tests/functional/base.py | 20 +++ .../functional/v2/test_trigger_crash_dump.py | 146 ++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 novaclient/tests/functional/v2/test_trigger_crash_dump.py diff --git a/novaclient/tests/functional/base.py b/novaclient/tests/functional/base.py index 16e5f3781..45d6c27dd 100644 --- a/novaclient/tests/functional/base.py +++ b/novaclient/tests/functional/base.py @@ -24,6 +24,9 @@ import novaclient import novaclient.api_versions import novaclient.client +BOOT_IS_COMPLETE = ("login as 'cirros' user. default password: " + "'cubswin:)'. use 'sudo' for root.") + # The following are simple filter functions that filter our available # image / flavor list so that they can be used in standard testing. @@ -202,6 +205,23 @@ class ClientTestBase(testtools.TestCase): self.fail("Volume %s did not reach status %s after %d s" % (volume.id, status, timeout)) + def wait_for_server_os_boot(self, server_id, timeout=60, + poll_interval=1): + """Wait until instance's operating system is completely booted. + + :param server_id: uuid4 id of given instance + :param timeout: timeout in seconds + :param poll_interval: poll interval in seconds + """ + start_time = time.time() + while time.time() - start_time < timeout: + if BOOT_IS_COMPLETE in self.nova('console-log %s ' % server_id): + break + time.sleep(poll_interval) + else: + self.fail("Server %s did not boot after %d s" + % (server_id, timeout)) + def wait_for_resource_delete(self, resource, manager, timeout=60, poll_interval=1): """Wait until getting the resource raises NotFound exception. diff --git a/novaclient/tests/functional/v2/test_trigger_crash_dump.py b/novaclient/tests/functional/v2/test_trigger_crash_dump.py new file mode 100644 index 000000000..a41d7315d --- /dev/null +++ b/novaclient/tests/functional/v2/test_trigger_crash_dump.py @@ -0,0 +1,146 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import time + +from novaclient.tests.functional import base +from novaclient.v2 import shell + + +class TestTriggerCrashDumpNovaClientV217(base.TenantTestBase): + """Functional tests for trigger crash dump""" + + COMPUTE_API_VERSION = "2.17" + + # It's a resource-consuming task to implement full-flow (up to getting + # and reading a dump file) functional test for trigger-crash-dump. + # We need to upload Ubuntu image for booting an instance based on it, + # and to install kdump with its further configuring on this instance. + # Here, the "light" version of functional test is proposed. + # It's based on knowledge that trigger-crash-dump uses a NMI injection, + # and when the 'trigger-crash-dump' operation is executed, + # instance's kernel receives the NMI signal, and an appropriate + # message will appear in the instance's log. + + # The server status must be ACTIVE, PAUSED, RESCUED, RESIZED or ERROR. + # If not, the conflictingRequest(409) code is returned + + def _wait_for_nmi(self, server_id, timeout=60, + poll_interval=1): + start_time = time.time() + while time.time() - start_time < timeout: + if 'trigger_crash_dump' in self.nova('instance-action-list %s ' % + server_id): + break + time.sleep(poll_interval) + else: + self.fail("Trigger crash dump hasn't been executed for server %s" + % (server_id, timeout)) + + def test_trigger_crash_dump_in_active_state(self): + server = self._create_server() + self.wait_for_server_os_boot(server.id) + self.nova('trigger-crash-dump %s ' % server.id) + self._wait_for_nmi(server.id) + output = self.nova('console-log %s ' % server.id) + self.assertIn("Uhhuh. NMI received for unknown reason ", output) + + def test_trigger_crash_dump_in_error_state(self): + server = self._create_server() + self.wait_for_server_os_boot(server.id) + self.nova('reset-state %s ' % server.id) + shell._poll_for_status( + self.client.servers.get, server.id, + 'active', ['error']) + self.nova('trigger-crash-dump %s ' % server.id) + self._wait_for_nmi(server.id) + output = self.nova('console-log %s ' % server.id) + self.assertIn("Uhhuh. NMI received for unknown reason ", output) + + def test_trigger_crash_dump_in_paused_state(self): + server = self._create_server() + self.wait_for_server_os_boot(server.id) + self.nova('pause %s ' % server.id) + shell._poll_for_status( + self.client.servers.get, server.id, + 'active', ['paused']) + self.nova('trigger-crash-dump %s ' % server.id) + self._wait_for_nmi(server.id) + output = self.nova('console-log %s ' % server.id) + # In PAUSED state a server's kernel shouldn't react onto NMI + self.assertNotIn("Uhhuh. NMI received for unknown reason ", output) + + def test_trigger_crash_dump_in_rescued_state(self): + server = self._create_server() + self.wait_for_server_os_boot(server.id) + self.nova('rescue %s ' % server.id) + shell._poll_for_status( + self.client.servers.get, server.id, + 'active', ['rescue']) + self.wait_for_server_os_boot(server.id) + self.nova('trigger-crash-dump %s ' % server.id) + self._wait_for_nmi(server.id) + output = self.nova('console-log %s ' % server.id) + self.assertIn("Uhhuh. NMI received for unknown reason ", output) + + def test_trigger_crash_dump_in_resized_state(self): + server = self._create_server() + self.wait_for_server_os_boot(server.id) + self.nova('resize %s %s' % (server.id, 'm1.small')) + shell._poll_for_status( + self.client.servers.get, server.id, + 'active', ['verify_resize']) + self.nova('trigger-crash-dump %s ' % server.id) + self._wait_for_nmi(server.id) + output = self.nova('console-log %s ' % server.id) + self.assertIn("Uhhuh. NMI received for unknown reason ", output) + + def test_trigger_crash_dump_in_shutoff_state(self): + server = self._create_server() + self.wait_for_server_os_boot(server.id) + self.nova('stop %s ' % server.id) + shell._poll_for_status( + self.client.servers.get, server.id, + 'active', ['shutoff']) + output = self.nova('trigger-crash-dump %s ' % + server.id, fail_ok=True, merge_stderr=True) + self.assertIn("ERROR (Conflict): " + "Cannot 'trigger_crash_dump' instance %s " + "while it is in vm_state stopped (HTTP 409) " % + server.id, output) + + # If the specified server is locked, the conflictingRequest(409) code + # is returned to a user without administrator privileges. + def test_trigger_crash_dump_in_locked_state_admin(self): + server = self._create_server() + self.wait_for_server_os_boot(server.id) + self.nova('lock %s ' % server.id) + self.nova('trigger-crash-dump %s ' % server.id) + self._wait_for_nmi(server.id) + output = self.nova('console-log %s ' % server.id) + self.assertIn("Uhhuh. NMI received for unknown reason ", output) + + def test_trigger_crash_dump_in_locked_state_nonadmin(self): + name = self.name_generate(prefix='server') + server = self.another_nova('boot --flavor %s --image %s --poll %s' % + (self.flavor.name, self.image.name, name)) + self.addCleanup(self.another_nova, 'delete', params=name) + server_id = self._get_value_from_the_table( + server, 'id') + self.wait_for_server_os_boot(server_id) + self.another_nova('lock %s ' % server_id) + self.addCleanup(self.another_nova, 'unlock', params=name) + output = self.another_nova('trigger-crash-dump %s ' % + server_id, fail_ok=True, merge_stderr=True) + self.assertIn("ERROR (Conflict): Instance %s is in an invalid " + "state for 'trigger_crash_dump' (HTTP 409) " % + server_id, output)