summaryrefslogtreecommitdiff
path: root/tests/integration_tests/modules
diff options
context:
space:
mode:
Diffstat (limited to 'tests/integration_tests/modules')
-rw-r--r--tests/integration_tests/modules/test_combined.py46
-rw-r--r--tests/integration_tests/modules/test_disk_setup.py10
-rw-r--r--tests/integration_tests/modules/test_hotplug.py4
-rw-r--r--tests/integration_tests/modules/test_jinja_templating.py30
-rw-r--r--tests/integration_tests/modules/test_lxd_bridge.py4
-rw-r--r--tests/integration_tests/modules/test_ntp_servers.py8
-rw-r--r--tests/integration_tests/modules/test_set_password.py12
-rw-r--r--tests/integration_tests/modules/test_ssh_keysfile.py10
-rw-r--r--tests/integration_tests/modules/test_user_events.py23
-rw-r--r--tests/integration_tests/modules/test_version_change.py25
10 files changed, 122 insertions, 50 deletions
diff --git a/tests/integration_tests/modules/test_combined.py b/tests/integration_tests/modules/test_combined.py
index 27f3c074..9cd1648a 100644
--- a/tests/integration_tests/modules/test_combined.py
+++ b/tests/integration_tests/modules/test_combined.py
@@ -8,14 +8,15 @@ here.
import json
import pytest
import re
-from datetime import date
from tests.integration_tests.clouds import ImageSpecification
from tests.integration_tests.instances import IntegrationInstance
-from tests.integration_tests.util import verify_ordered_items_in_text
+from tests.integration_tests.util import (
+ verify_clean_log,
+ verify_ordered_items_in_text,
+)
USER_DATA = """\
-## template: jinja
#cloud-config
apt:
primary:
@@ -33,8 +34,7 @@ locale_configfile: /etc/default/locale
ntp:
servers: ['ntp.ubuntu.com']
runcmd:
- - echo {{ds.meta_data.local_hostname}} > /var/tmp/runcmd_output
- - echo {{merged_cfg.def_log_file}} >> /var/tmp/runcmd_output
+ - echo 'hello world' > /var/tmp/runcmd_output
"""
@@ -44,11 +44,17 @@ class TestCombined:
def test_final_message(self, class_client: IntegrationInstance):
"""Test that final_message module works as expected.
- Also tests LP 1511485: final_message is silent
+ Also tests LP 1511485: final_message is silent.
+
+ It's possible that if this test is run within a minute or so of
+ midnight that we'll see a failure because the day in the logs
+ is different from the day specified in the test definition.
"""
client = class_client
log = client.read_from_file('/var/log/cloud-init.log')
- today = date.today().strftime('%a, %d %b %Y')
+ # Get date on host rather than locally as our host could be in a
+ # wildly different timezone (or more likely recording UTC)
+ today = client.execute('date "+%a, %d %b %Y"')
expected = (
'This is my final message!\n'
r'\d+\.\d+.*\n'
@@ -96,21 +102,10 @@ class TestCombined:
'en_US.UTF-8'
], locale_gen)
- def test_runcmd_with_variable_substitution(
- self, class_client: IntegrationInstance
- ):
- """Test runcmd, while including jinja substitution.
-
- Ensure we can also substitue variables from instance-data-sensitive
- LP: #1931392
- """
+ def test_runcmd(self, class_client: IntegrationInstance):
+ """Test runcmd works as expected"""
client = class_client
- expected = [
- client.execute('hostname').stdout.strip(),
- '/var/log/cloud-init.log',
- ]
- output = client.read_from_file('/var/tmp/runcmd_output')
- verify_ordered_items_in_text(expected, output)
+ assert 'hello world' == client.read_from_file('/var/tmp/runcmd_output')
def test_no_problems(self, class_client: IntegrationInstance):
"""Test no errors, warnings, or tracebacks"""
@@ -124,8 +119,7 @@ class TestCombined:
assert result_json['errors'] == []
log = client.read_from_file('/var/log/cloud-init.log')
- assert 'WARN' not in log
- assert 'Traceback' not in log
+ verify_clean_log(log)
def _check_common_metadata(self, data):
assert data['base64_encoded_keys'] == []
@@ -171,8 +165,10 @@ class TestCombined:
v1_data = data['v1']
assert v1_data['cloud_name'] == 'unknown'
assert v1_data['platform'] == 'lxd'
- assert v1_data['subplatform'] == (
- 'seed-dir (/var/lib/cloud/seed/nocloud-net)')
+ assert any([
+ '/var/lib/cloud/seed/nocloud-net' in v1_data['subplatform'],
+ '/dev/sr0' in v1_data['subplatform']
+ ])
assert v1_data['availability_zone'] is None
assert v1_data['instance_id'] == client.instance.name
assert v1_data['local_hostname'] == client.instance.name
diff --git a/tests/integration_tests/modules/test_disk_setup.py b/tests/integration_tests/modules/test_disk_setup.py
index 1fc96c52..9c9edc46 100644
--- a/tests/integration_tests/modules/test_disk_setup.py
+++ b/tests/integration_tests/modules/test_disk_setup.py
@@ -6,6 +6,7 @@ from pycloudlib.lxd.instance import LXDInstance
from cloudinit.subp import subp
from tests.integration_tests.instances import IntegrationInstance
+from tests.integration_tests.util import verify_clean_log
DISK_PATH = '/tmp/test_disk_setup_{}'.format(uuid4())
@@ -59,8 +60,7 @@ class TestDeviceAliases:
) in log
assert 'changed my_alias.1 => /dev/sdb1' in log
assert 'changed my_alias.2 => /dev/sdb2' in log
- assert 'WARN' not in log
- assert 'Traceback' not in log
+ verify_clean_log(log)
lsblk = json.loads(client.execute('lsblk --json'))
sdb = [x for x in lsblk['blockdevices'] if x['name'] == 'sdb'][0]
@@ -120,8 +120,7 @@ class TestPartProbeAvailability:
"""
def _verify_first_disk_setup(self, client, log):
- assert 'Traceback' not in log
- assert 'WARN' not in log
+ verify_clean_log(log)
lsblk = json.loads(client.execute('lsblk --json'))
sdb = [x for x in lsblk['blockdevices'] if x['name'] == 'sdb'][0]
assert len(sdb['children']) == 2
@@ -167,8 +166,7 @@ class TestPartProbeAvailability:
client.restart()
# Assert new setup works as expected
- assert 'Traceback' not in log
- assert 'WARN' not in log
+ verify_clean_log(log)
lsblk = json.loads(client.execute('lsblk --json'))
sdb = [x for x in lsblk['blockdevices'] if x['name'] == 'sdb'][0]
diff --git a/tests/integration_tests/modules/test_hotplug.py b/tests/integration_tests/modules/test_hotplug.py
index a42d1c8c..88cd8c16 100644
--- a/tests/integration_tests/modules/test_hotplug.py
+++ b/tests/integration_tests/modules/test_hotplug.py
@@ -40,6 +40,10 @@ def _get_ip_addr(client):
@pytest.mark.openstack
+# On Bionic, we traceback when attempting to detect the hotplugged
+# device in the updated metadata. This is because Bionic is specifically
+# configured not to provide network metadata.
+@pytest.mark.not_bionic
@pytest.mark.user_data(USER_DATA)
def test_hotplug_add_remove(client: IntegrationInstance):
ips_before = _get_ip_addr(client)
diff --git a/tests/integration_tests/modules/test_jinja_templating.py b/tests/integration_tests/modules/test_jinja_templating.py
new file mode 100644
index 00000000..35b8ee2d
--- /dev/null
+++ b/tests/integration_tests/modules/test_jinja_templating.py
@@ -0,0 +1,30 @@
+# This file is part of cloud-init. See LICENSE file for license information.
+import pytest
+
+from tests.integration_tests.instances import IntegrationInstance
+from tests.integration_tests.util import verify_ordered_items_in_text
+
+
+USER_DATA = """\
+## template: jinja
+#cloud-config
+runcmd:
+ - echo {{v1.local_hostname}} > /var/tmp/runcmd_output
+ - echo {{merged_cfg._doc}} >> /var/tmp/runcmd_output
+"""
+
+
+@pytest.mark.user_data(USER_DATA)
+def test_runcmd_with_variable_substitution(client: IntegrationInstance):
+ """Test jinja substitution.
+
+ Ensure we can also substitute variables from instance-data-sensitive
+ LP: #1931392
+ """
+ expected = [
+ client.execute('hostname').stdout.strip(),
+ ('Merged cloud-init system config from /etc/cloud/cloud.cfg and '
+ '/etc/cloud/cloud.cfg.d/')
+ ]
+ output = client.read_from_file('/var/tmp/runcmd_output')
+ verify_ordered_items_in_text(expected, output)
diff --git a/tests/integration_tests/modules/test_lxd_bridge.py b/tests/integration_tests/modules/test_lxd_bridge.py
index cbf11179..65dce3c7 100644
--- a/tests/integration_tests/modules/test_lxd_bridge.py
+++ b/tests/integration_tests/modules/test_lxd_bridge.py
@@ -6,6 +6,8 @@
import pytest
import yaml
+from tests.integration_tests.util import verify_clean_log
+
USER_DATA = """\
#cloud-config
@@ -38,7 +40,7 @@ class TestLxdBridge:
def test_bridge(self, class_client):
"""Check that the given bridge is configured"""
cloud_init_log = class_client.read_from_file("/var/log/cloud-init.log")
- assert "WARN" not in cloud_init_log
+ verify_clean_log(cloud_init_log)
# The bridge should exist
assert class_client.execute("ip addr show lxdbr0")
diff --git a/tests/integration_tests/modules/test_ntp_servers.py b/tests/integration_tests/modules/test_ntp_servers.py
index 7a799139..59241faa 100644
--- a/tests/integration_tests/modules/test_ntp_servers.py
+++ b/tests/integration_tests/modules/test_ntp_servers.py
@@ -78,6 +78,8 @@ CHRONY_DATA = """\
ntp:
enabled: true
ntp_client: chrony
+ servers:
+ - 172.16.15.14
"""
@@ -89,7 +91,7 @@ def test_chrony(client: IntegrationInstance):
else:
chrony_conf = '/etc/chrony/chrony.conf'
contents = client.read_from_file(chrony_conf)
- assert '.pool.ntp.org' in contents
+ assert 'server 172.16.15.14' in contents
TIMESYNCD_DATA = """\
@@ -97,6 +99,8 @@ TIMESYNCD_DATA = """\
ntp:
enabled: true
ntp_client: systemd-timesyncd
+ servers:
+ - 172.16.15.14
"""
@@ -106,7 +110,7 @@ def test_timesyncd(client: IntegrationInstance):
contents = client.read_from_file(
'/etc/systemd/timesyncd.conf.d/cloud-init.conf'
)
- assert '.pool.ntp.org' in contents
+ assert 'NTP=172.16.15.14' in contents
EMPTY_NTP = """\
diff --git a/tests/integration_tests/modules/test_set_password.py b/tests/integration_tests/modules/test_set_password.py
index d7cf91a5..ac9db19d 100644
--- a/tests/integration_tests/modules/test_set_password.py
+++ b/tests/integration_tests/modules/test_set_password.py
@@ -13,6 +13,8 @@ import crypt
import pytest
import yaml
+from tests.integration_tests.util import retry
+
COMMON_USER_DATA = """\
#cloud-config
@@ -129,6 +131,7 @@ class Mixin:
assert "dick:" not in cloud_init_output
assert "harry:" not in cloud_init_output
+ @retry(tries=30, delay=1)
def test_random_passwords_emitted_to_serial_console(self, class_client):
"""We should emit passwords to the serial console. (LP: #1918303)"""
try:
@@ -137,6 +140,15 @@ class Mixin:
# Assume that an exception here means that we can't use the console
# log
pytest.skip("NotImplementedError when requesting console log")
+ return
+ if console_log.lower() == 'no console output':
+ # This test retries because we might not have the full console log
+ # on the first fetch. However, if we have no console output
+ # at all, we don't want to keep retrying as that would trigger
+ # another 5 minute wait on the pycloudlib side, which could
+ # leave us waiting for a couple hours
+ pytest.fail('no console output')
+ return
assert "dick:" in console_log
assert "harry:" in console_log
diff --git a/tests/integration_tests/modules/test_ssh_keysfile.py b/tests/integration_tests/modules/test_ssh_keysfile.py
index 5c720578..b39454e6 100644
--- a/tests/integration_tests/modules/test_ssh_keysfile.py
+++ b/tests/integration_tests/modules/test_ssh_keysfile.py
@@ -38,9 +38,15 @@ def common_verify(client, expected_keys):
# Ensure key is in the key file
contents = client.read_from_file(filename)
if user in ['ubuntu', 'root']:
- # Our personal public key gets added by pycloudlib
lines = contents.split('\n')
- assert len(lines) == 2
+ if user == 'root':
+ # Our personal public key gets added by pycloudlib in
+ # addition to the default `ssh_authorized_keys`
+ assert len(lines) == 2
+ else:
+ # Clouds will insert the keys we've added to our accounts
+ # or for our launches
+ assert len(lines) >= 2
assert keys.public_key.strip() in contents
else:
assert contents.strip() == keys.public_key.strip()
diff --git a/tests/integration_tests/modules/test_user_events.py b/tests/integration_tests/modules/test_user_events.py
index a45cad72..ee8f05ae 100644
--- a/tests/integration_tests/modules/test_user_events.py
+++ b/tests/integration_tests/modules/test_user_events.py
@@ -31,9 +31,12 @@ def _add_dummy_bridge_to_netplan(client: IntegrationInstance):
@pytest.mark.gce
@pytest.mark.oci
@pytest.mark.openstack
+@pytest.mark.azure
@pytest.mark.not_xenial
def test_boot_event_disabled_by_default(client: IntegrationInstance):
log = client.read_from_file('/var/log/cloud-init.log')
+ if 'network config is disabled' in log:
+ pytest.skip("network config disabled. Test doesn't apply")
assert 'Applying network configuration' in log
assert 'dummy0' not in client.execute('ls /sys/class/net')
@@ -43,6 +46,12 @@ def test_boot_event_disabled_by_default(client: IntegrationInstance):
client.restart()
log2 = client.read_from_file('/var/log/cloud-init.log')
+ if 'cache invalid in datasource' in log2:
+ # Invalid cache will get cleared, meaning we'll create a new
+ # "instance" and apply networking config, so events aren't
+ # really relevant here
+ pytest.skip("Test only valid for existing instances")
+
# We attempt to apply network config twice on every boot.
# Ensure neither time works.
assert 2 == len(
@@ -62,25 +71,27 @@ def test_boot_event_disabled_by_default(client: IntegrationInstance):
def _test_network_config_applied_on_reboot(client: IntegrationInstance):
log = client.read_from_file('/var/log/cloud-init.log')
+ if 'network config is disabled' in log:
+ pytest.skip("network config disabled. Test doesn't apply")
assert 'Applying network configuration' in log
assert 'dummy0' not in client.execute('ls /sys/class/net')
_add_dummy_bridge_to_netplan(client)
client.execute('rm /var/log/cloud-init.log')
client.restart()
+
log = client.read_from_file('/var/log/cloud-init.log')
+ if 'cache invalid in datasource' in log:
+ # Invalid cache will get cleared, meaning we'll create a new
+ # "instance" and apply networking config, so events aren't
+ # really relevant here
+ pytest.skip("Test only valid for existing instances")
assert 'Event Allowed: scope=network EventType=boot' in log
assert 'Applying network configuration' in log
assert 'dummy0' not in client.execute('ls /sys/class/net')
-@pytest.mark.azure
-@pytest.mark.not_xenial
-def test_boot_event_enabled_by_default(client: IntegrationInstance):
- _test_network_config_applied_on_reboot(client)
-
-
USER_DATA = """\
#cloud-config
updates:
diff --git a/tests/integration_tests/modules/test_version_change.py b/tests/integration_tests/modules/test_version_change.py
index 4e9ab63f..ffea794a 100644
--- a/tests/integration_tests/modules/test_version_change.py
+++ b/tests/integration_tests/modules/test_version_change.py
@@ -1,7 +1,7 @@
from pathlib import Path
from tests.integration_tests.instances import IntegrationInstance
-from tests.integration_tests.util import ASSETS_DIR
+from tests.integration_tests.util import ASSETS_DIR, verify_clean_log
PICKLE_PATH = Path('/var/lib/cloud/instance/obj.pkl')
@@ -10,8 +10,7 @@ TEST_PICKLE = ASSETS_DIR / 'test_version_change.pkl'
def _assert_no_pickle_problems(log):
assert 'Failed loading pickled blob' not in log
- assert 'Traceback' not in log
- assert 'WARN' not in log
+ verify_clean_log(log)
def test_reboot_without_version_change(client: IntegrationInstance):
@@ -30,7 +29,13 @@ def test_reboot_without_version_change(client: IntegrationInstance):
client.push_file(TEST_PICKLE, PICKLE_PATH)
client.restart()
log = client.read_from_file('/var/log/cloud-init.log')
- assert 'Failed loading pickled blob from {}'.format(PICKLE_PATH) in log
+
+ # no cache found is an "expected" upgrade error, and
+ # "Failed" means we're unable to load the pickle
+ assert any([
+ 'Failed loading pickled blob from {}'.format(PICKLE_PATH) in log,
+ 'no cache found' in log
+ ])
def test_cache_purged_on_version_change(client: IntegrationInstance):
@@ -48,9 +53,13 @@ def test_log_message_on_missing_version_file(client: IntegrationInstance):
# Start by pushing a pickle so we can see the log message
client.push_file(TEST_PICKLE, PICKLE_PATH)
client.execute("rm /var/lib/cloud/data/python-version")
+ client.execute("rm /var/log/cloud-init.log")
client.restart()
log = client.read_from_file('/var/log/cloud-init.log')
- assert (
- 'Writing python-version file. '
- 'Cache compatibility status is currently unknown.'
- ) in log
+ if 'no cache found' not in log:
+ # We don't expect the python version file to exist if we have no
+ # pre-existing cache
+ assert (
+ 'Writing python-version file. '
+ 'Cache compatibility status is currently unknown.'
+ ) in log