Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions tests/integration_tests/clouds.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,12 @@ def _get_initial_image(self):

def _perform_launch(self, launch_kwargs):
pycloudlib_instance = self.cloud_instance.launch(**launch_kwargs)
pycloudlib_instance.wait(raise_on_cloudinit_failure=False)
return pycloudlib_instance

def launch(self, user_data=None, launch_kwargs=None,
def launch(self, user_data=None, launch_kwargs=None, wait=True,
settings=integration_settings):
if launch_kwargs is None:
launch_kwargs = {}
if self.settings.EXISTING_INSTANCE_ID:
log.info(
'Not launching instance due to EXISTING_INSTANCE_ID. '
Expand All @@ -137,21 +138,24 @@ def launch(self, user_data=None, launch_kwargs=None,
self.settings.EXISTING_INSTANCE_ID
)
return
if 'wait' in launch_kwargs:
raise Exception("Specify 'wait' directly to launch, "
"not in 'launch_kwargs'")
kwargs = {
'image_id': self.image_id,
'user_data': user_data,
'wait': False,
}
if launch_kwargs:
kwargs.update(launch_kwargs)
kwargs.update(launch_kwargs)
log.info(
"Launching instance with launch_kwargs:\n{}".format(
"\n".join("{}={}".format(*item) for item in kwargs.items())
)
)

pycloudlib_instance = self._perform_launch(kwargs)

if wait:
pycloudlib_instance.wait(raise_on_cloudinit_failure=False)
log.info('Launched instance: %s', pycloudlib_instance)
return self.get_instance(pycloudlib_instance, settings)

Expand Down Expand Up @@ -275,7 +279,6 @@ def _perform_launch(self, launch_kwargs):
if self.settings.CLOUD_INIT_SOURCE == 'IN_PLACE':
self._mount_source(pycloudlib_instance)
pycloudlib_instance.start(wait=False)
pycloudlib_instance.wait(raise_on_cloudinit_failure=False)
return pycloudlib_instance


Expand Down
4 changes: 3 additions & 1 deletion tests/integration_tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ def pytest_runtest_setup(item):
supported_os_set = set(os_list).intersection(test_marks)
if current_os and supported_os_set and current_os not in supported_os_set:
pytest.skip("Cannot run on OS {}".format(current_os))
if 'unstable' in test_marks and not integration_settings.RUN_UNSTABLE:
pytest.skip('Test marked unstable. Manually remove mark to run it')


# disable_subp_usage is defined at a higher level, but we don't
Expand Down Expand Up @@ -176,7 +178,7 @@ def _collect_logs(instance: IntegrationInstance, node_id: str,


@contextmanager
def _client(request, fixture_utils, session_cloud):
def _client(request, fixture_utils, session_cloud: IntegrationCloud):
"""Fixture implementation for the client fixtures.

Launch the dynamic IntegrationClient instance using any provided
Expand Down
2 changes: 2 additions & 0 deletions tests/integration_tests/integration_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
KEEP_INSTANCE = False
# Keep snapshot image (mostly for debugging) when test is finished
KEEP_IMAGE = False
# Run tests marked as unstable. Expect failures and dragons.
RUN_UNSTABLE = False

# One of:
# lxd_container
Expand Down
13 changes: 13 additions & 0 deletions tests/integration_tests/log_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
def ordered_items_in_text(to_verify: list, text: str) -> bool:
"""Return if all items in list appear in order in text.

Examples:
ordered_items_in_text(['a', '1'], 'ab1') # Returns True
ordered_items_in_text(['1', 'a'], 'ab1') # Returns False
"""
index = 0
for item in to_verify:
index = text[index:].find(item)
if index < 0:
return False
return True
91 changes: 91 additions & 0 deletions tests/integration_tests/modules/test_power_state_change.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
"""Integration test of the cc_power_state_change module.

Test that the power state config options work as expected.
"""

import time

import pytest

from tests.integration_tests.clouds import IntegrationCloud
from tests.integration_tests.instances import IntegrationInstance
from tests.integration_tests.log_utils import ordered_items_in_text

USER_DATA = """\
#cloud-config
power_state:
delay: {delay}
mode: {mode}
message: msg
timeout: {timeout}
condition: {condition}
"""


def _detect_reboot(instance: IntegrationInstance):
# We'll wait for instance up here, but we don't know if we're
# detecting the first boot or second boot, so we also check
# the logs to ensure we've booted twice. If the logs show we've
# only booted once, wait until we've booted twice
instance.instance.wait(raise_on_cloudinit_failure=False)
for _ in range(600):
try:
log = instance.read_from_file('/var/log/cloud-init.log')
Comment thread
OddBloke marked this conversation as resolved.
boot_count = log.count("running 'init-local'")
if boot_count == 1:
instance.instance.wait(raise_on_cloudinit_failure=False)
elif boot_count > 1:
break
except Exception:
pass
time.sleep(1)
else:
raise Exception('Could not detect reboot')


def _can_connect(instance):
return instance.execute('true').ok


# This test is marked unstable because even though it should be able to
# run anywhere, I can only get it to run in an lxd container, and even then
# occasionally some timing issues will crop up.
@pytest.mark.unstable
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be nice to have a timed element like we have in the curtin tests: unstable_until("2021/02/01") to force us to fix it at some point. Not needed for now, though.

@pytest.mark.sru_2020_11
@pytest.mark.ubuntu
@pytest.mark.lxd_container
class TestPowerChange:
@pytest.mark.parametrize('mode,delay,timeout,expected', [
('poweroff', 'now', '10', 'will execute: shutdown -P now msg'),
('reboot', 'now', '0', 'will execute: shutdown -r now msg'),
('halt', '+1', '0', 'will execute: shutdown -H +1 msg'),
Comment thread
OddBloke marked this conversation as resolved.
])
def test_poweroff(self, session_cloud: IntegrationCloud,
mode, delay, timeout, expected):
with session_cloud.launch(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a nice example of the flexibility of the framework we have; good job!

user_data=USER_DATA.format(
delay=delay, mode=mode, timeout=timeout, condition='true'),
wait=False
) as instance:
if mode == 'reboot':
_detect_reboot(instance)
else:
instance.instance.wait_for_stop()
instance.instance.start(wait=True)
log = instance.read_from_file('/var/log/cloud-init.log')
assert _can_connect(instance)
lines_to_check = [
'Running module power-state-change',
expected,
"running 'init-local'",
'config-power-state-change already ran',
]
assert ordered_items_in_text(lines_to_check, log), (
'Expected data not in logs')

@pytest.mark.user_data(USER_DATA.format(delay='0', mode='poweroff',
timeout='0', condition='false'))
def test_poweroff_false_condition(self, client: IntegrationInstance):
log = client.read_from_file('/var/log/cloud-init.log')
assert _can_connect(client)
assert 'Condition was false. Will not perform state change' in log
1 change: 1 addition & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,4 @@ markers =
instance_name: the name to be used for the test instance
sru_2020_11: test is part of the 2020/11 SRU verification
ubuntu: this test should run on Ubuntu
unstable: skip this test because it is flakey