summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorRyan Harper <ryan.harper@canonical.com>2017-08-21 15:09:36 -0500
committergit-ubuntu importer <ubuntu-devel-discuss@lists.ubuntu.com>2017-08-21 20:49:10 +0000
commit622629b8856284b75d66b2fd5a30976cd8464479 (patch)
tree4270f415bd03d17e79590703eeb45d3a8ecae894 /tests
parentf10f6587ecaa9312524f808fa57aa7ce4dd5075c (diff)
downloadcloud-init-git-622629b8856284b75d66b2fd5a30976cd8464479.tar.gz
0.7.9-243-ge74d775-0ubuntu1 (patches unapplied)
Imported using git-ubuntu import.
Diffstat (limited to 'tests')
-rw-r--r--tests/cloud_tests/bddeb.py16
-rw-r--r--tests/unittests/helpers.py2
-rw-r--r--tests/unittests/test_cli.py87
-rw-r--r--tests/unittests/test_datasource/test_aliyun.py11
-rw-r--r--tests/unittests/test_datasource/test_common.py1
-rw-r--r--tests/unittests/test_datasource/test_ec2.py136
-rw-r--r--tests/unittests/test_distros/__init__.py21
-rw-r--r--tests/unittests/test_distros/test_arch.py45
-rw-r--r--tests/unittests/test_distros/test_netconfig.py4
-rw-r--r--tests/unittests/test_handler/test_handler_ntp.py105
-rw-r--r--tests/unittests/test_net.py118
-rw-r--r--tests/unittests/test_vmware_config_file.py32
12 files changed, 530 insertions, 48 deletions
diff --git a/tests/cloud_tests/bddeb.py b/tests/cloud_tests/bddeb.py
index 53dbf74e..fe805356 100644
--- a/tests/cloud_tests/bddeb.py
+++ b/tests/cloud_tests/bddeb.py
@@ -11,7 +11,7 @@ from tests.cloud_tests import (config, LOG)
from tests.cloud_tests import (platforms, images, snapshots, instances)
from tests.cloud_tests.stage import (PlatformComponent, run_stage, run_single)
-build_deps = ['devscripts', 'equivs', 'git', 'tar']
+pre_reqs = ['devscripts', 'equivs', 'git', 'tar']
def _out(cmd_res):
@@ -26,13 +26,10 @@ def build_deb(args, instance):
@return_value: tuple of results and fail count
"""
# update remote system package list and install build deps
- LOG.debug('installing build deps')
- pkgs = ' '.join(build_deps)
+ LOG.debug('installing pre-reqs')
+ pkgs = ' '.join(pre_reqs)
cmd = 'apt-get update && apt-get install --yes {}'.format(pkgs)
instance.execute(['/bin/sh', '-c', cmd])
- # TODO Remove this call once we have a ci-deps Makefile target
- instance.execute(['mk-build-deps', '--install', '-t',
- 'apt-get --no-install-recommends --yes', 'cloud-init'])
# local tmpfile that must be deleted
local_tarball = tempfile.NamedTemporaryFile().name
@@ -40,7 +37,7 @@ def build_deb(args, instance):
# paths to use in remote system
output_link = '/root/cloud-init_all.deb'
remote_tarball = _out(instance.execute(['mktemp']))
- extract_dir = _out(instance.execute(['mktemp', '--directory']))
+ extract_dir = '/root'
bddeb_path = os.path.join(extract_dir, 'packages', 'bddeb')
git_env = {'GIT_DIR': os.path.join(extract_dir, '.git'),
'GIT_WORK_TREE': extract_dir}
@@ -56,6 +53,11 @@ def build_deb(args, instance):
instance.execute(['git', 'commit', '-a', '-m', 'tmp', '--allow-empty'],
env=git_env)
+ LOG.debug('installing deps')
+ deps_path = os.path.join(extract_dir, 'tools', 'read-dependencies')
+ instance.execute([deps_path, '--install', '--test-distro',
+ '--distro', 'ubuntu', '--python-version', '3'])
+
LOG.debug('building deb in remote system at: %s', output_link)
bddeb_args = args.bddeb_args.split() if args.bddeb_args else []
instance.execute([bddeb_path, '-d'] + bddeb_args, env=git_env)
diff --git a/tests/unittests/helpers.py b/tests/unittests/helpers.py
index 08c5c469..bf1dc5df 100644
--- a/tests/unittests/helpers.py
+++ b/tests/unittests/helpers.py
@@ -278,7 +278,7 @@ class FilesystemMockingTestCase(ResourceUsingTestCase):
return root
-class HttprettyTestCase(TestCase):
+class HttprettyTestCase(CiTestCase):
# necessary as http_proxy gets in the way of httpretty
# https://github.com/gabrielfalcao/HTTPretty/issues/122
def setUp(self):
diff --git a/tests/unittests/test_cli.py b/tests/unittests/test_cli.py
index 06f366b2..7780f164 100644
--- a/tests/unittests/test_cli.py
+++ b/tests/unittests/test_cli.py
@@ -31,9 +31,90 @@ class TestCLI(test_helpers.FilesystemMockingTestCase):
def test_no_arguments_shows_error_message(self):
exit_code = self._call_main()
- self.assertIn('cloud-init: error: too few arguments',
- self.stderr.getvalue())
+ missing_subcommand_message = [
+ 'too few arguments', # python2.7 msg
+ 'the following arguments are required: subcommand' # python3 msg
+ ]
+ error = self.stderr.getvalue()
+ matches = ([msg in error for msg in missing_subcommand_message])
+ self.assertTrue(
+ any(matches), 'Did not find error message for missing subcommand')
self.assertEqual(2, exit_code)
+ def test_all_subcommands_represented_in_help(self):
+ """All known subparsers are represented in the cloud-int help doc."""
+ self._call_main()
+ error = self.stderr.getvalue()
+ expected_subcommands = ['analyze', 'init', 'modules', 'single',
+ 'dhclient-hook', 'features']
+ for subcommand in expected_subcommands:
+ self.assertIn(subcommand, error)
-# vi: ts=4 expandtab
+ @mock.patch('cloudinit.cmd.main.status_wrapper')
+ def test_init_subcommand_parser(self, m_status_wrapper):
+ """The subcommand 'init' calls status_wrapper passing init."""
+ self._call_main(['cloud-init', 'init'])
+ (name, parseargs) = m_status_wrapper.call_args_list[0][0]
+ self.assertEqual('init', name)
+ self.assertEqual('init', parseargs.subcommand)
+ self.assertEqual('init', parseargs.action[0])
+ self.assertEqual('main_init', parseargs.action[1].__name__)
+
+ @mock.patch('cloudinit.cmd.main.status_wrapper')
+ def test_modules_subcommand_parser(self, m_status_wrapper):
+ """The subcommand 'modules' calls status_wrapper passing modules."""
+ self._call_main(['cloud-init', 'modules'])
+ (name, parseargs) = m_status_wrapper.call_args_list[0][0]
+ self.assertEqual('modules', name)
+ self.assertEqual('modules', parseargs.subcommand)
+ self.assertEqual('modules', parseargs.action[0])
+ self.assertEqual('main_modules', parseargs.action[1].__name__)
+
+ def test_analyze_subcommand_parser(self):
+ """The subcommand cloud-init analyze calls the correct subparser."""
+ self._call_main(['cloud-init', 'analyze'])
+ # These subcommands only valid for cloud-init analyze script
+ expected_subcommands = ['blame', 'show', 'dump']
+ error = self.stderr.getvalue()
+ for subcommand in expected_subcommands:
+ self.assertIn(subcommand, error)
+
+ @mock.patch('cloudinit.cmd.main.main_single')
+ def test_single_subcommand(self, m_main_single):
+ """The subcommand 'single' calls main_single with valid args."""
+ self._call_main(['cloud-init', 'single', '--name', 'cc_ntp'])
+ (name, parseargs) = m_main_single.call_args_list[0][0]
+ self.assertEqual('single', name)
+ self.assertEqual('single', parseargs.subcommand)
+ self.assertEqual('single', parseargs.action[0])
+ self.assertFalse(parseargs.debug)
+ self.assertFalse(parseargs.force)
+ self.assertIsNone(parseargs.frequency)
+ self.assertEqual('cc_ntp', parseargs.name)
+ self.assertFalse(parseargs.report)
+
+ @mock.patch('cloudinit.cmd.main.dhclient_hook')
+ def test_dhclient_hook_subcommand(self, m_dhclient_hook):
+ """The subcommand 'dhclient-hook' calls dhclient_hook with args."""
+ self._call_main(['cloud-init', 'dhclient-hook', 'net_action', 'eth0'])
+ (name, parseargs) = m_dhclient_hook.call_args_list[0][0]
+ self.assertEqual('dhclient_hook', name)
+ self.assertEqual('dhclient-hook', parseargs.subcommand)
+ self.assertEqual('dhclient_hook', parseargs.action[0])
+ self.assertFalse(parseargs.debug)
+ self.assertFalse(parseargs.force)
+ self.assertEqual('net_action', parseargs.net_action)
+ self.assertEqual('eth0', parseargs.net_interface)
+
+ @mock.patch('cloudinit.cmd.main.main_features')
+ def test_features_hook_subcommand(self, m_features):
+ """The subcommand 'features' calls main_features with args."""
+ self._call_main(['cloud-init', 'features'])
+ (name, parseargs) = m_features.call_args_list[0][0]
+ self.assertEqual('features', name)
+ self.assertEqual('features', parseargs.subcommand)
+ self.assertEqual('features', parseargs.action[0])
+ self.assertFalse(parseargs.debug)
+ self.assertFalse(parseargs.force)
+
+# : ts=4 expandtab
diff --git a/tests/unittests/test_datasource/test_aliyun.py b/tests/unittests/test_datasource/test_aliyun.py
index 990bff2c..996560e4 100644
--- a/tests/unittests/test_datasource/test_aliyun.py
+++ b/tests/unittests/test_datasource/test_aliyun.py
@@ -70,7 +70,6 @@ class TestAliYunDatasource(test_helpers.HttprettyTestCase):
paths = helpers.Paths({})
self.ds = ay.DataSourceAliYun(cfg, distro, paths)
self.metadata_address = self.ds.metadata_urls[0]
- self.api_ver = self.ds.api_ver
@property
def default_metadata(self):
@@ -82,13 +81,15 @@ class TestAliYunDatasource(test_helpers.HttprettyTestCase):
@property
def metadata_url(self):
- return os.path.join(self.metadata_address,
- self.api_ver, 'meta-data') + '/'
+ return os.path.join(
+ self.metadata_address,
+ self.ds.min_metadata_version, 'meta-data') + '/'
@property
def userdata_url(self):
- return os.path.join(self.metadata_address,
- self.api_ver, 'user-data')
+ return os.path.join(
+ self.metadata_address,
+ self.ds.min_metadata_version, 'user-data')
def regist_default_server(self):
register_mock_metaserver(self.metadata_url, self.default_metadata)
diff --git a/tests/unittests/test_datasource/test_common.py b/tests/unittests/test_datasource/test_common.py
index 413e87ac..4802f105 100644
--- a/tests/unittests/test_datasource/test_common.py
+++ b/tests/unittests/test_datasource/test_common.py
@@ -35,6 +35,7 @@ DEFAULT_LOCAL = [
OpenNebula.DataSourceOpenNebula,
OVF.DataSourceOVF,
SmartOS.DataSourceSmartOS,
+ Ec2.DataSourceEc2Local,
]
DEFAULT_NETWORK = [
diff --git a/tests/unittests/test_datasource/test_ec2.py b/tests/unittests/test_datasource/test_ec2.py
index 12230ae2..33d02619 100644
--- a/tests/unittests/test_datasource/test_ec2.py
+++ b/tests/unittests/test_datasource/test_ec2.py
@@ -8,35 +8,67 @@ from cloudinit import helpers
from cloudinit.sources import DataSourceEc2 as ec2
-# collected from api version 2009-04-04/ with
+# collected from api version 2016-09-02/ with
# python3 -c 'import json
# from cloudinit.ec2_utils import get_instance_metadata as gm
-# print(json.dumps(gm("2009-04-04"), indent=1, sort_keys=True))'
+# print(json.dumps(gm("2016-09-02"), indent=1, sort_keys=True))'
DEFAULT_METADATA = {
- "ami-id": "ami-80861296",
+ "ami-id": "ami-8b92b4ee",
"ami-launch-index": "0",
"ami-manifest-path": "(unknown)",
"block-device-mapping": {"ami": "/dev/sda1", "root": "/dev/sda1"},
- "hostname": "ip-10-0-0-149",
+ "hostname": "ip-172-31-31-158.us-east-2.compute.internal",
"instance-action": "none",
- "instance-id": "i-0052913950685138c",
- "instance-type": "t2.micro",
- "local-hostname": "ip-10-0-0-149",
- "local-ipv4": "10.0.0.149",
- "placement": {"availability-zone": "us-east-1b"},
+ "instance-id": "i-0a33f80f09c96477f",
+ "instance-type": "t2.small",
+ "local-hostname": "ip-172-3-3-15.us-east-2.compute.internal",
+ "local-ipv4": "172.3.3.15",
+ "mac": "06:17:04:d7:26:09",
+ "metrics": {"vhostmd": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"},
+ "network": {
+ "interfaces": {
+ "macs": {
+ "06:17:04:d7:26:09": {
+ "device-number": "0",
+ "interface-id": "eni-e44ef49e",
+ "ipv4-associations": {"13.59.77.202": "172.3.3.15"},
+ "ipv6s": "2600:1f16:aeb:b20b:9d87:a4af:5cc9:73dc",
+ "local-hostname": ("ip-172-3-3-15.us-east-2."
+ "compute.internal"),
+ "local-ipv4s": "172.3.3.15",
+ "mac": "06:17:04:d7:26:09",
+ "owner-id": "950047163771",
+ "public-hostname": ("ec2-13-59-77-202.us-east-2."
+ "compute.amazonaws.com"),
+ "public-ipv4s": "13.59.77.202",
+ "security-group-ids": "sg-5a61d333",
+ "security-groups": "wide-open",
+ "subnet-id": "subnet-20b8565b",
+ "subnet-ipv4-cidr-block": "172.31.16.0/20",
+ "subnet-ipv6-cidr-blocks": "2600:1f16:aeb:b20b::/64",
+ "vpc-id": "vpc-87e72bee",
+ "vpc-ipv4-cidr-block": "172.31.0.0/16",
+ "vpc-ipv4-cidr-blocks": "172.31.0.0/16",
+ "vpc-ipv6-cidr-blocks": "2600:1f16:aeb:b200::/56"
+ }
+ }
+ }
+ },
+ "placement": {"availability-zone": "us-east-2b"},
"profile": "default-hvm",
- "public-hostname": "",
- "public-ipv4": "107.23.188.247",
+ "public-hostname": "ec2-13-59-77-202.us-east-2.compute.amazonaws.com",
+ "public-ipv4": "13.59.77.202",
"public-keys": {"brickies": ["ssh-rsa AAAAB3Nz....w== brickies"]},
- "reservation-id": "r-00a2c173fb5782a08",
- "security-groups": "wide-open"
+ "reservation-id": "r-01efbc9996bac1bd6",
+ "security-groups": "my-wide-open",
+ "services": {"domain": "amazonaws.com", "partition": "aws"}
}
def _register_ssh_keys(rfunc, base_url, keys_data):
"""handle ssh key inconsistencies.
- public-keys in the ec2 metadata is inconsistently formatted compared
+ public-keys in the ec2 metadata is inconsistently formated compared
to other entries.
Given keys_data of {name1: pubkey1, name2: pubkey2}
@@ -115,6 +147,8 @@ def register_mock_metaserver(base_url, data):
class TestEc2(test_helpers.HttprettyTestCase):
+ with_logs = True
+
valid_platform_data = {
'uuid': 'ec212f79-87d1-2f1d-588f-d86dc0fd5412',
'uuid_source': 'dmi',
@@ -123,16 +157,20 @@ class TestEc2(test_helpers.HttprettyTestCase):
def setUp(self):
super(TestEc2, self).setUp()
- self.metadata_addr = ec2.DataSourceEc2.metadata_urls[0]
- self.api_ver = '2009-04-04'
+ self.datasource = ec2.DataSourceEc2
+ self.metadata_addr = self.datasource.metadata_urls[0]
@property
def metadata_url(self):
- return '/'.join([self.metadata_addr, self.api_ver, 'meta-data', ''])
+ return '/'.join([
+ self.metadata_addr,
+ self.datasource.min_metadata_version, 'meta-data', ''])
@property
def userdata_url(self):
- return '/'.join([self.metadata_addr, self.api_ver, 'user-data'])
+ return '/'.join([
+ self.metadata_addr,
+ self.datasource.min_metadata_version, 'user-data'])
def _patch_add_cleanup(self, mpath, *args, **kwargs):
p = mock.patch(mpath, *args, **kwargs)
@@ -144,7 +182,7 @@ class TestEc2(test_helpers.HttprettyTestCase):
paths = helpers.Paths({})
if sys_cfg is None:
sys_cfg = {}
- ds = ec2.DataSourceEc2(sys_cfg=sys_cfg, distro=distro, paths=paths)
+ ds = self.datasource(sys_cfg=sys_cfg, distro=distro, paths=paths)
if platform_data is not None:
self._patch_add_cleanup(
"cloudinit.sources.DataSourceEc2._collect_platform_data",
@@ -157,14 +195,16 @@ class TestEc2(test_helpers.HttprettyTestCase):
return ds
@httpretty.activate
- def test_valid_platform_with_strict_true(self):
+ @mock.patch('cloudinit.net.dhcp.maybe_perform_dhcp_discovery')
+ def test_valid_platform_with_strict_true(self, m_dhcp):
"""Valid platform data should return true with strict_id true."""
ds = self._setup_ds(
platform_data=self.valid_platform_data,
sys_cfg={'datasource': {'Ec2': {'strict_id': True}}},
md=DEFAULT_METADATA)
ret = ds.get_data()
- self.assertEqual(True, ret)
+ self.assertTrue(ret)
+ self.assertEqual(0, m_dhcp.call_count)
@httpretty.activate
def test_valid_platform_with_strict_false(self):
@@ -174,7 +214,7 @@ class TestEc2(test_helpers.HttprettyTestCase):
sys_cfg={'datasource': {'Ec2': {'strict_id': False}}},
md=DEFAULT_METADATA)
ret = ds.get_data()
- self.assertEqual(True, ret)
+ self.assertTrue(ret)
@httpretty.activate
def test_unknown_platform_with_strict_true(self):
@@ -185,7 +225,7 @@ class TestEc2(test_helpers.HttprettyTestCase):
sys_cfg={'datasource': {'Ec2': {'strict_id': True}}},
md=DEFAULT_METADATA)
ret = ds.get_data()
- self.assertEqual(False, ret)
+ self.assertFalse(ret)
@httpretty.activate
def test_unknown_platform_with_strict_false(self):
@@ -196,7 +236,55 @@ class TestEc2(test_helpers.HttprettyTestCase):
sys_cfg={'datasource': {'Ec2': {'strict_id': False}}},
md=DEFAULT_METADATA)
ret = ds.get_data()
- self.assertEqual(True, ret)
+ self.assertTrue(ret)
+
+ @httpretty.activate
+ @mock.patch('cloudinit.sources.DataSourceEc2.util.is_FreeBSD')
+ def test_ec2_local_returns_false_on_bsd(self, m_is_freebsd):
+ """DataSourceEc2Local returns False on BSD.
+
+ FreeBSD dhclient doesn't support dhclient -sf to run in a sandbox.
+ """
+ m_is_freebsd.return_value = True
+ self.datasource = ec2.DataSourceEc2Local
+ ds = self._setup_ds(
+ platform_data=self.valid_platform_data,
+ sys_cfg={'datasource': {'Ec2': {'strict_id': False}}},
+ md=DEFAULT_METADATA)
+ ret = ds.get_data()
+ self.assertFalse(ret)
+ self.assertIn(
+ "FreeBSD doesn't support running dhclient with -sf",
+ self.logs.getvalue())
+
+ @httpretty.activate
+ @mock.patch('cloudinit.net.EphemeralIPv4Network')
+ @mock.patch('cloudinit.net.dhcp.maybe_perform_dhcp_discovery')
+ @mock.patch('cloudinit.sources.DataSourceEc2.util.is_FreeBSD')
+ def test_ec2_local_performs_dhcp_on_non_bsd(self, m_is_bsd, m_dhcp, m_net):
+ """Ec2Local returns True for valid platform data on non-BSD with dhcp.
+
+ DataSourceEc2Local will setup initial IPv4 network via dhcp discovery.
+ Then the metadata services is crawled for more network config info.
+ When the platform data is valid, return True.
+ """
+ m_is_bsd.return_value = False
+ m_dhcp.return_value = [{
+ 'interface': 'eth9', 'fixed-address': '192.168.2.9',
+ 'routers': '192.168.2.1', 'subnet-mask': '255.255.255.0',
+ 'broadcast-address': '192.168.2.255'}]
+ self.datasource = ec2.DataSourceEc2Local
+ ds = self._setup_ds(
+ platform_data=self.valid_platform_data,
+ sys_cfg={'datasource': {'Ec2': {'strict_id': False}}},
+ md=DEFAULT_METADATA)
+ ret = ds.get_data()
+ self.assertTrue(ret)
+ m_dhcp.assert_called_once_with()
+ m_net.assert_called_once_with(
+ broadcast='192.168.2.255', interface='eth9', ip='192.168.2.9',
+ prefix_or_mask='255.255.255.0', router='192.168.2.1')
+ self.assertIn('Crawl of metadata service took', self.logs.getvalue())
# vi: ts=4 expandtab
diff --git a/tests/unittests/test_distros/__init__.py b/tests/unittests/test_distros/__init__.py
index e69de29b..5394aa56 100644
--- a/tests/unittests/test_distros/__init__.py
+++ b/tests/unittests/test_distros/__init__.py
@@ -0,0 +1,21 @@
+# This file is part of cloud-init. See LICENSE file for license information.
+import copy
+
+from cloudinit import distros
+from cloudinit import helpers
+from cloudinit import settings
+
+
+def _get_distro(dtype, system_info=None):
+ """Return a Distro class of distro 'dtype'.
+
+ cfg is format of CFG_BUILTIN['system_info'].
+
+ example: _get_distro("debian")
+ """
+ if system_info is None:
+ system_info = copy.deepcopy(settings.CFG_BUILTIN['system_info'])
+ system_info['distro'] = dtype
+ paths = helpers.Paths(system_info['paths'])
+ distro_cls = distros.fetch(dtype)
+ return distro_cls(dtype, system_info, paths)
diff --git a/tests/unittests/test_distros/test_arch.py b/tests/unittests/test_distros/test_arch.py
new file mode 100644
index 00000000..3d4c9a70
--- /dev/null
+++ b/tests/unittests/test_distros/test_arch.py
@@ -0,0 +1,45 @@
+# This file is part of cloud-init. See LICENSE file for license information.
+
+from cloudinit.distros.arch import _render_network
+from cloudinit import util
+
+from ..helpers import (CiTestCase, dir2dict)
+
+from . import _get_distro
+
+
+class TestArch(CiTestCase):
+
+ def test_get_distro(self):
+ distro = _get_distro("arch")
+ hostname = "myhostname"
+ hostfile = self.tmp_path("hostfile")
+ distro._write_hostname(hostname, hostfile)
+ self.assertEqual(hostname + "\n", util.load_file(hostfile))
+
+
+class TestRenderNetwork(CiTestCase):
+ def test_basic_static(self):
+ """Just the most basic static config.
+
+ note 'lo' should not be rendered as an interface."""
+ entries = {'eth0': {'auto': True,
+ 'dns-nameservers': ['8.8.8.8'],
+ 'bootproto': 'static',
+ 'address': '10.0.0.2',
+ 'gateway': '10.0.0.1',
+ 'netmask': '255.255.255.0'},
+ 'lo': {'auto': True}}
+ target = self.tmp_dir()
+ devs = _render_network(entries, target=target)
+ files = dir2dict(target, prefix=target)
+ self.assertEqual(['eth0'], devs)
+ self.assertEqual(
+ {'/etc/netctl/eth0': '\n'.join([
+ "Address=10.0.0.2/255.255.255.0",
+ "Connection=ethernet",
+ "DNS=('8.8.8.8')",
+ "Gateway=10.0.0.1",
+ "IP=static",
+ "Interface=eth0", ""]),
+ '/etc/resolv.conf': 'nameserver 8.8.8.8\n'}, files)
diff --git a/tests/unittests/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py
index 2f505d93..6d89dba8 100644
--- a/tests/unittests/test_distros/test_netconfig.py
+++ b/tests/unittests/test_distros/test_netconfig.py
@@ -135,7 +135,7 @@ network:
V2_NET_CFG = {
'ethernets': {
'eth7': {
- 'addresses': ['192.168.1.5/255.255.255.0'],
+ 'addresses': ['192.168.1.5/24'],
'gateway4': '192.168.1.254'},
'eth9': {
'dhcp4': True}
@@ -151,7 +151,6 @@ V2_TO_V2_NET_CFG_OUTPUT = """
# /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg with the following:
# network: {config: disabled}
network:
- version: 2
ethernets:
eth7:
addresses:
@@ -159,6 +158,7 @@ network:
gateway4: 192.168.1.254
eth9:
dhcp4: true
+ version: 2
"""
diff --git a/tests/unittests/test_handler/test_handler_ntp.py b/tests/unittests/test_handler/test_handler_ntp.py
index 7f278646..83d5faa2 100644
--- a/tests/unittests/test_handler/test_handler_ntp.py
+++ b/tests/unittests/test_handler/test_handler_ntp.py
@@ -16,6 +16,14 @@ servers {{servers}}
pools {{pools}}
"""
+TIMESYNCD_TEMPLATE = b"""\
+## template:jinja
+[Time]
+{% if servers or pools -%}
+NTP={% for host in servers|list + pools|list %}{{ host }} {% endfor -%}
+{% endif -%}
+"""
+
try:
import jsonschema
assert jsonschema # avoid pyflakes error F401: import unused
@@ -59,6 +67,14 @@ class TestNtp(FilesystemMockingTestCase):
cc_ntp.install_ntp(install_func, packages=['ntp'], check_exe='ntpd')
install_func.assert_not_called()
+ @mock.patch("cloudinit.config.cc_ntp.util")
+ def test_ntp_install_no_op_with_empty_pkg_list(self, mock_util):
+ """ntp_install calls install_func with empty list"""
+ mock_util.which.return_value = None # check_exe not found
+ install_func = mock.MagicMock()
+ cc_ntp.install_ntp(install_func, packages=[], check_exe='timesyncd')
+ install_func.assert_called_once_with([])
+
def test_ntp_rename_ntp_conf(self):
"""When NTP_CONF exists, rename_ntp moves it."""
ntpconf = self.tmp_path("ntp.conf", self.new_root)
@@ -68,6 +84,30 @@ class TestNtp(FilesystemMockingTestCase):
self.assertFalse(os.path.exists(ntpconf))
self.assertTrue(os.path.exists("{0}.dist".format(ntpconf)))
+ @mock.patch("cloudinit.config.cc_ntp.util")
+ def test_reload_ntp_defaults(self, mock_util):
+ """Test service is restarted/reloaded (defaults)"""
+ service = 'ntp'
+ cmd = ['service', service, 'restart']
+ cc_ntp.reload_ntp(service)
+ mock_util.subp.assert_called_with(cmd, capture=True)
+
+ @mock.patch("cloudinit.config.cc_ntp.util")
+ def test_reload_ntp_systemd(self, mock_util):
+ """Test service is restarted/reloaded (systemd)"""
+ service = 'ntp'
+ cmd = ['systemctl', 'reload-or-restart', service]
+ cc_ntp.reload_ntp(service, systemd=True)
+ mock_util.subp.assert_called_with(cmd, capture=True)
+
+ @mock.patch("cloudinit.config.cc_ntp.util")
+ def test_reload_ntp_systemd_timesycnd(self, mock_util):
+ """Test service is restarted/reloaded (systemd/timesyncd)"""
+ service = 'systemd-timesycnd'
+ cmd = ['systemctl', 'reload-or-restart', service]
+ cc_ntp.reload_ntp(service, systemd=True)
+ mock_util.subp.assert_called_with(cmd, capture=True)
+
def test_ntp_rename_ntp_conf_skip_missing(self):
"""When NTP_CONF doesn't exist rename_ntp doesn't create a file."""
ntpconf = self.tmp_path("ntp.conf", self.new_root)
@@ -94,7 +134,7 @@ class TestNtp(FilesystemMockingTestCase):
with open('{0}.tmpl'.format(ntp_conf), 'wb') as stream:
stream.write(NTP_TEMPLATE)
with mock.patch('cloudinit.config.cc_ntp.NTP_CONF', ntp_conf):
- cc_ntp.write_ntp_config_template(cfg, mycloud)
+ cc_ntp.write_ntp_config_template(cfg, mycloud, ntp_conf)
content = util.read_file_or_url('file://' + ntp_conf).contents
self.assertEqual(
"servers ['192.168.2.1', '192.168.2.2']\npools []\n",
@@ -120,7 +160,7 @@ class TestNtp(FilesystemMockingTestCase):
with open('{0}.{1}.tmpl'.format(ntp_conf, distro), 'wb') as stream:
stream.write(NTP_TEMPLATE)
with mock.patch('cloudinit.config.cc_ntp.NTP_CONF', ntp_conf):
- cc_ntp.write_ntp_config_template(cfg, mycloud)
+ cc_ntp.write_ntp_config_template(cfg, mycloud, ntp_conf)
content = util.read_file_or_url('file://' + ntp_conf).contents
self.assertEqual(
"servers []\npools ['10.0.0.1', '10.0.0.2']\n",
@@ -139,7 +179,7 @@ class TestNtp(FilesystemMockingTestCase):
with open('{0}.tmpl'.format(ntp_conf), 'wb') as stream:
stream.write(NTP_TEMPLATE)
with mock.patch('cloudinit.config.cc_ntp.NTP_CONF', ntp_conf):
- cc_ntp.write_ntp_config_template({}, mycloud)
+ cc_ntp.write_ntp_config_template({}, mycloud, ntp_conf)
content = util.read_file_or_url('file://' + ntp_conf).contents
default_pools = [
"{0}.{1}.pool.ntp.org".format(x, distro)
@@ -152,7 +192,8 @@ class TestNtp(FilesystemMockingTestCase):
",".join(default_pools)),
self.logs.getvalue())
- def test_ntp_handler_mocked_template(self):
+ @mock.patch("cloudinit.config.cc_ntp.ntp_installable")
+ def test_ntp_handler_mocked_template(self, m_ntp_install):
"""Test ntp handler renders ubuntu ntp.conf template."""
pools = ['0.mycompany.pool.ntp.org', '3.mycompany.pool.ntp.org']
servers = ['192.168.23.3', '192.168.23.4']
@@ -164,6 +205,8 @@ class TestNtp(FilesystemMockingTestCase):
}
mycloud = self._get_cloud('ubuntu')
ntp_conf = self.tmp_path('ntp.conf', self.new_root) # Doesn't exist
+ m_ntp_install.return_value = True
+
# Create ntp.conf.tmpl
with open('{0}.tmpl'.format(ntp_conf), 'wb') as stream:
stream.write(NTP_TEMPLATE)
@@ -176,6 +219,34 @@ class TestNtp(FilesystemMockingTestCase):
'servers {0}\npools {1}\n'.format(servers, pools),
content.decode())
+ @mock.patch("cloudinit.config.cc_ntp.util")
+ def test_ntp_handler_mocked_template_snappy(self, m_util):
+ """Test ntp handler renders timesycnd.conf template on snappy."""
+ pools = ['0.mycompany.pool.ntp.org', '3.mycompany.pool.ntp.org']
+ servers = ['192.168.23.3', '192.168.23.4']
+ cfg = {
+ 'ntp': {
+ 'pools': pools,
+ 'servers': servers
+ }
+ }
+ mycloud = self._get_cloud('ubuntu')
+ m_util.system_is_snappy.return_value = True
+
+ # Create timesyncd.conf.tmpl
+ tsyncd_conf = self.tmp_path("timesyncd.conf", self.new_root)
+ template = '{0}.tmpl'.format(tsyncd_conf)
+ with open(template, 'wb') as stream:
+ stream.write(TIMESYNCD_TEMPLATE)
+
+ with mock.patch('cloudinit.config.cc_ntp.TIMESYNCD_CONF', tsyncd_conf):
+ cc_ntp.handle('notimportant', cfg, mycloud, None, None)
+
+ content = util.read_file_or_url('file://' + tsyncd_conf).contents
+ self.assertEqual(
+ "[Time]\nNTP=%s %s \n" % (" ".join(servers), " ".join(pools)),
+ content.decode())
+
def test_ntp_handler_real_distro_templates(self):
"""Test ntp handler renders the shipped distro ntp.conf templates."""
pools = ['0.mycompany.pool.ntp.org', '3.mycompany.pool.ntp.org']
@@ -333,4 +404,30 @@ class TestNtp(FilesystemMockingTestCase):
"pools ['0.mypool.org', '0.mypool.org']\n",
content)
+ @mock.patch("cloudinit.config.cc_ntp.ntp_installable")
+ def test_ntp_handler_timesyncd(self, m_ntp_install):
+ """Test ntp handler configures timesyncd"""
+ m_ntp_install.return_value = False
+ distro = 'ubuntu'
+ cfg = {
+ 'servers': ['192.168.2.1', '192.168.2.2'],
+ 'pools': ['0.mypool.org'],
+ }
+ mycloud = self._get_cloud(distro)
+ tsyncd_conf = self.tmp_path("timesyncd.conf", self.new_root)
+ # Create timesyncd.conf.tmpl
+ template = '{0}.tmpl'.format(tsyncd_conf)
+ print(template)
+ with open(template, 'wb') as stream:
+ stream.write(TIMESYNCD_TEMPLATE)
+ with mock.patch('cloudinit.config.cc_ntp.TIMESYNCD_CONF', tsyncd_conf):
+ cc_ntp.write_ntp_config_template(cfg, mycloud, tsyncd_conf,
+ template='timesyncd.conf')
+
+ content = util.read_file_or_url('file://' + tsyncd_conf).contents
+ self.assertEqual(
+ "[Time]\nNTP=192.168.2.1 192.168.2.2 0.mypool.org \n",
+ content.decode())
+
+
# vi: ts=4 expandtab
diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
index e49abcc4..f251024b 100644
--- a/tests/unittests/test_net.py
+++ b/tests/unittests/test_net.py
@@ -1059,6 +1059,100 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true
- type: static
address: 2001:1::1/92
"""),
+ 'expected_netplan': textwrap.dedent("""
+ network:
+ version: 2
+ ethernets:
+ bond0s0:
+ match:
+ macaddress: aa:bb:cc:dd:e8:00
+ set-name: bond0s0
+ bond0s1:
+ match:
+ macaddress: aa:bb:cc:dd:e8:01
+ set-name: bond0s1
+ bonds:
+ bond0:
+ addresses:
+ - 192.168.0.2/24
+ - 192.168.1.2/24
+ - 2001:1::1/92
+ gateway4: 192.168.0.1
+ interfaces:
+ - bond0s0
+ - bond0s1
+ parameters:
+ mii-monitor-interval: 100
+ mode: active-backup
+ transmit-hash-policy: layer3+4
+ routes:
+ - to: 10.1.3.0/24
+ via: 192.168.0.3
+ """),
+ 'yaml-v2': textwrap.dedent("""
+ version: 2
+ ethernets:
+ eth0:
+ match:
+ driver: "virtio_net"
+ macaddress: "aa:bb:cc:dd:e8:00"
+ vf0:
+ set-name: vf0
+ match:
+ driver: "e1000"
+ macaddress: "aa:bb:cc:dd:e8:01"
+ bonds:
+ bond0:
+ addresses:
+ - 192.168.0.2/24
+ - 192.168.1.2/24
+ - 2001:1::1/92
+ gateway4: 192.168.0.1
+ interfaces:
+ - eth0
+ - vf0
+ parameters:
+ mii-monitor-interval: 100
+ mode: active-backup
+ primary: vf0
+ transmit-hash-policy: "layer3+4"
+ routes:
+ - to: 10.1.3.0/24
+ via: 192.168.0.3
+ """),
+ 'expected_netplan-v2': textwrap.dedent("""
+ network:
+ bonds:
+ bond0:
+ addresses:
+ - 192.168.0.2/24
+ - 192.168.1.2/24
+ - 2001:1::1/92
+ gateway4: 192.168.0.1
+ interfaces:
+ - eth0
+ - vf0
+ parameters:
+ mii-monitor-interval: 100
+ mode: active-backup
+ primary: vf0
+ transmit-hash-policy: layer3+4
+ routes:
+ - to: 10.1.3.0/24
+ via: 192.168.0.3
+ ethernets:
+ eth0:
+ match:
+ driver: virtio_net
+ macaddress: aa:bb:cc:dd:e8:00
+ vf0:
+ match:
+ driver: e1000
+ macaddress: aa:bb:cc:dd:e8:01
+ set-name: vf0
+ version: 2
+ """),
+
'expected_sysconfig': {
'ifcfg-bond0': textwrap.dedent("""\
BONDING_MASTER=yes
@@ -1683,6 +1777,9 @@ USERCTL=no
ns = network_state.parse_net_config_data(network_cfg,
skip_broken=False)
renderer = sysconfig.Renderer()
+ # render a multiple times to simulate reboots
+ renderer.render_network_state(ns, render_dir)
+ renderer.render_network_state(ns, render_dir)
renderer.render_network_state(ns, render_dir)
for fn, expected_content in os_sample.get('out_sysconfig', []):
with open(os.path.join(render_dir, fn)) as fh:
@@ -2156,6 +2253,27 @@ class TestNetplanRoundTrip(CiTestCase):
renderer.render_network_state(ns, target)
return dir2dict(target)
+ def testsimple_render_bond_netplan(self):
+ entry = NETWORK_CONFIGS['bond']
+ files = self._render_and_read(network_config=yaml.load(entry['yaml']))
+ print(entry['expected_netplan'])
+ print('-- expected ^ | v rendered --')
+ print(files['/etc/netplan/50-cloud-init.yaml'])
+ self.assertEqual(
+ entry['expected_netplan'].splitlines(),
+ files['/etc/netplan/50-cloud-init.yaml'].splitlines())
+
+ def testsimple_render_bond_v2_input_netplan(self):
+ entry = NETWORK_CONFIGS['bond']
+ files = self._render_and_read(
+ network_config=yaml.load(entry['yaml-v2']))
+ print(entry['expected_netplan-v2'])
+ print('-- expected ^ | v rendered --')
+ print(files['/etc/netplan/50-cloud-init.yaml'])
+ self.assertEqual(
+ entry['expected_netplan-v2'].splitlines(),
+ files['/etc/netplan/50-cloud-init.yaml'].splitlines())
+
def testsimple_render_small_netplan(self):
entry = NETWORK_CONFIGS['small']
files = self._render_and_read(network_config=yaml.load(entry['yaml']))
diff --git a/tests/unittests/test_vmware_config_file.py b/tests/unittests/test_vmware_config_file.py
index 18475f10..03b36d31 100644
--- a/tests/unittests/test_vmware_config_file.py
+++ b/tests/unittests/test_vmware_config_file.py
@@ -7,8 +7,8 @@
import logging
import sys
-import unittest
+from .helpers import CiTestCase
from cloudinit.sources.helpers.vmware.imc.boot_proto import BootProtoEnum
from cloudinit.sources.helpers.vmware.imc.config import Config
from cloudinit.sources.helpers.vmware.imc.config_file import ConfigFile
@@ -17,7 +17,7 @@ logging.basicConfig(level=logging.DEBUG, stream=sys.stdout)
logger = logging.getLogger(__name__)
-class TestVmwareConfigFile(unittest.TestCase):
+class TestVmwareConfigFile(CiTestCase):
def test_utility_methods(self):
cf = ConfigFile("tests/data/vmware/cust-dhcp-2nic.cfg")
@@ -90,4 +90,32 @@ class TestVmwareConfigFile(unittest.TestCase):
self.assertEqual('00:50:56:a6:8c:08', nics[0].mac, "mac0")
self.assertEqual(BootProtoEnum.DHCP, nics[0].bootProto, "bootproto0")
+ def test_config_password(self):
+ cf = ConfigFile("tests/data/vmware/cust-dhcp-2nic.cfg")
+
+ cf._insertKey("PASSWORD|-PASS", "test-password")
+ cf._insertKey("PASSWORD|RESET", "no")
+
+ conf = Config(cf)
+ self.assertEqual('test-password', conf.admin_password, "password")
+ self.assertFalse(conf.reset_password, "do not reset password")
+
+ def test_config_reset_passwd(self):
+ cf = ConfigFile("tests/data/vmware/cust-dhcp-2nic.cfg")
+
+ cf._insertKey("PASSWORD|-PASS", "test-password")
+ cf._insertKey("PASSWORD|RESET", "random")
+
+ conf = Config(cf)
+ with self.assertRaises(ValueError):
+ conf.reset_password()
+
+ cf.clear()
+ cf._insertKey("PASSWORD|RESET", "yes")
+ self.assertEqual(1, len(cf), "insert size")
+
+ conf = Config(cf)
+ self.assertTrue(conf.reset_password, "reset password")
+
+
# vi: ts=4 expandtab