summaryrefslogtreecommitdiff
path: root/cloudinit/tests
diff options
context:
space:
mode:
authorChad Smith <chad.smith@canonical.com>2018-06-21 14:32:27 -0600
committerChad Smith <chad.smith@canonical.com>2018-06-21 14:32:27 -0600
commitba53ceb5a8a30c10951ec3ac49b8d6ebbe09a524 (patch)
tree5b68f0602daea648d48b3dc16809b0d66fb565d1 /cloudinit/tests
parent7d1e8976bba629f30da45e814a5a97e2f4b7de3d (diff)
parent2d6e4219db73e80c135efd83753f9302f778f08d (diff)
downloadvyos-cloud-init-ba53ceb5a8a30c10951ec3ac49b8d6ebbe09a524.tar.gz
vyos-cloud-init-ba53ceb5a8a30c10951ec3ac49b8d6ebbe09a524.zip
merge from master at 18.3
Diffstat (limited to 'cloudinit/tests')
-rw-r--r--cloudinit/tests/helpers.py86
-rw-r--r--cloudinit/tests/test_netinfo.py233
-rw-r--r--cloudinit/tests/test_url_helper.py28
-rw-r--r--cloudinit/tests/test_util.py129
-rw-r--r--cloudinit/tests/test_version.py31
5 files changed, 388 insertions, 119 deletions
diff --git a/cloudinit/tests/helpers.py b/cloudinit/tests/helpers.py
index 999b1d7c..5bfe7fa4 100644
--- a/cloudinit/tests/helpers.py
+++ b/cloudinit/tests/helpers.py
@@ -3,11 +3,13 @@
from __future__ import print_function
import functools
+import httpretty
import logging
import os
import shutil
import sys
import tempfile
+import time
import unittest
import mock
@@ -24,6 +26,8 @@ try:
except ImportError:
from ConfigParser import ConfigParser
+from cloudinit.config.schema import (
+ SchemaValidationError, validate_cloudconfig_schema)
from cloudinit import helpers as ch
from cloudinit import util
@@ -108,12 +112,12 @@ class TestCase(unittest2.TestCase):
super(TestCase, self).setUp()
self.reset_global_state()
- def add_patch(self, target, attr, **kwargs):
+ def add_patch(self, target, attr, *args, **kwargs):
"""Patches specified target object and sets it as attr on test
instance also schedules cleanup"""
if 'autospec' not in kwargs:
kwargs['autospec'] = True
- m = mock.patch(target, **kwargs)
+ m = mock.patch(target, *args, **kwargs)
p = m.start()
self.addCleanup(m.stop)
setattr(self, attr, p)
@@ -190,35 +194,11 @@ class ResourceUsingTestCase(CiTestCase):
super(ResourceUsingTestCase, self).setUp()
self.resource_path = None
- def resourceLocation(self, subname=None):
- if self.resource_path is None:
- paths = [
- os.path.join('tests', 'data'),
- os.path.join('data'),
- os.path.join(os.pardir, 'tests', 'data'),
- os.path.join(os.pardir, 'data'),
- ]
- for p in paths:
- if os.path.isdir(p):
- self.resource_path = p
- break
- self.assertTrue((self.resource_path and
- os.path.isdir(self.resource_path)),
- msg="Unable to locate test resource data path!")
- if not subname:
- return self.resource_path
- return os.path.join(self.resource_path, subname)
-
- def readResource(self, name):
- where = self.resourceLocation(name)
- with open(where, 'r') as fh:
- return fh.read()
-
def getCloudPaths(self, ds=None):
tmpdir = tempfile.mkdtemp()
self.addCleanup(shutil.rmtree, tmpdir)
cp = ch.Paths({'cloud_dir': tmpdir,
- 'templates_dir': self.resourceLocation()},
+ 'templates_dir': resourceLocation()},
ds=ds)
return cp
@@ -234,7 +214,7 @@ class FilesystemMockingTestCase(ResourceUsingTestCase):
ResourceUsingTestCase.tearDown(self)
def replicateTestRoot(self, example_root, target_root):
- real_root = self.resourceLocation()
+ real_root = resourceLocation()
real_root = os.path.join(real_root, 'roots', example_root)
for (dir_path, _dirnames, filenames) in os.walk(real_root):
real_path = dir_path
@@ -285,7 +265,8 @@ class FilesystemMockingTestCase(ResourceUsingTestCase):
os.path: [('isfile', 1), ('exists', 1),
('islink', 1), ('isdir', 1), ('lexists', 1)],
os: [('listdir', 1), ('mkdir', 1),
- ('lstat', 1), ('symlink', 2)]
+ ('lstat', 1), ('symlink', 2),
+ ('stat', 1)]
}
if hasattr(os, 'scandir'):
@@ -323,19 +304,43 @@ class FilesystemMockingTestCase(ResourceUsingTestCase):
class HttprettyTestCase(CiTestCase):
# necessary as http_proxy gets in the way of httpretty
# https://github.com/gabrielfalcao/HTTPretty/issues/122
+ # Also make sure that allow_net_connect is set to False.
+ # And make sure reset and enable/disable are done.
def setUp(self):
self.restore_proxy = os.environ.get('http_proxy')
if self.restore_proxy is not None:
del os.environ['http_proxy']
super(HttprettyTestCase, self).setUp()
+ httpretty.HTTPretty.allow_net_connect = False
+ httpretty.reset()
+ httpretty.enable()
def tearDown(self):
+ httpretty.disable()
+ httpretty.reset()
if self.restore_proxy:
os.environ['http_proxy'] = self.restore_proxy
super(HttprettyTestCase, self).tearDown()
+class SchemaTestCaseMixin(unittest2.TestCase):
+
+ def assertSchemaValid(self, cfg, msg="Valid Schema failed validation."):
+ """Assert the config is valid per self.schema.
+
+ If there is only one top level key in the schema properties, then
+ the cfg will be put under that key."""
+ props = list(self.schema.get('properties'))
+ # put cfg under top level key if there is only one in the schema
+ if len(props) == 1:
+ cfg = {props[0]: cfg}
+ try:
+ validate_cloudconfig_schema(cfg, self.schema, strict=True)
+ except SchemaValidationError:
+ self.fail(msg)
+
+
def populate_dir(path, files):
if not os.path.exists(path):
os.makedirs(path)
@@ -354,11 +359,20 @@ def populate_dir(path, files):
return ret
+def populate_dir_with_ts(path, data):
+ """data is {'file': ('contents', mtime)}. mtime relative to now."""
+ populate_dir(path, dict((k, v[0]) for k, v in data.items()))
+ btime = time.time()
+ for fpath, (_contents, mtime) in data.items():
+ ts = btime + mtime if mtime else btime
+ os.utime(os.path.sep.join((path, fpath)), (ts, ts))
+
+
def dir2dict(startdir, prefix=None):
flist = {}
if prefix is None:
prefix = startdir
- for root, dirs, files in os.walk(startdir):
+ for root, _dirs, files in os.walk(startdir):
for fname in files:
fpath = os.path.join(root, fname)
key = fpath[len(prefix):]
@@ -399,6 +413,18 @@ def wrap_and_call(prefix, mocks, func, *args, **kwargs):
p.stop()
+def resourceLocation(subname=None):
+ path = os.path.join('tests', 'data')
+ if not subname:
+ return path
+ return os.path.join(path, subname)
+
+
+def readResource(name, mode='r'):
+ with open(resourceLocation(name), mode) as fh:
+ return fh.read()
+
+
try:
skipIf = unittest.skipIf
except AttributeError:
diff --git a/cloudinit/tests/test_netinfo.py b/cloudinit/tests/test_netinfo.py
index 7dea2e41..d76e768e 100644
--- a/cloudinit/tests/test_netinfo.py
+++ b/cloudinit/tests/test_netinfo.py
@@ -2,105 +2,166 @@
"""Tests netinfo module functions and classes."""
-from cloudinit.netinfo import netdev_pformat, route_pformat
-from cloudinit.tests.helpers import CiTestCase, mock
+from copy import copy
+
+from cloudinit.netinfo import netdev_info, netdev_pformat, route_pformat
+from cloudinit.tests.helpers import CiTestCase, mock, readResource
# Example ifconfig and route output
-SAMPLE_IFCONFIG_OUT = """\
-enp0s25 Link encap:Ethernet HWaddr 50:7b:9d:2c:af:91
- inet addr:192.168.2.18 Bcast:192.168.2.255 Mask:255.255.255.0
- inet6 addr: fe80::8107:2b92:867e:f8a6/64 Scope:Link
- UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
- RX packets:8106427 errors:55 dropped:0 overruns:0 frame:37
- TX packets:9339739 errors:0 dropped:0 overruns:0 carrier:0
- collisions:0 txqueuelen:1000
- RX bytes:4953721719 (4.9 GB) TX bytes:7731890194 (7.7 GB)
- Interrupt:20 Memory:e1200000-e1220000
-
-lo Link encap:Local Loopback
- inet addr:127.0.0.1 Mask:255.0.0.0
- inet6 addr: ::1/128 Scope:Host
- UP LOOPBACK RUNNING MTU:65536 Metric:1
- RX packets:579230851 errors:0 dropped:0 overruns:0 frame:0
- TX packets:579230851 errors:0 dropped:0 overruns:0 carrier:0
- collisions:0 txqueuelen:1
-"""
-
-SAMPLE_ROUTE_OUT = '\n'.join([
- '0.0.0.0 192.168.2.1 0.0.0.0 UG 0 0 0'
- ' enp0s25',
- '0.0.0.0 192.168.2.1 0.0.0.0 UG 0 0 0'
- ' wlp3s0',
- '192.168.2.0 0.0.0.0 255.255.255.0 U 0 0 0'
- ' enp0s25'])
-
-
-NETDEV_FORMATTED_OUT = '\n'.join([
- '+++++++++++++++++++++++++++++++++++++++Net device info+++++++++++++++++++'
- '++++++++++++++++++++',
- '+---------+------+------------------------------+---------------+-------+'
- '-------------------+',
- '| Device | Up | Address | Mask | Scope |'
- ' Hw-Address |',
- '+---------+------+------------------------------+---------------+-------+'
- '-------------------+',
- '| enp0s25 | True | 192.168.2.18 | 255.255.255.0 | . |'
- ' 50:7b:9d:2c:af:91 |',
- '| enp0s25 | True | fe80::8107:2b92:867e:f8a6/64 | . | link |'
- ' 50:7b:9d:2c:af:91 |',
- '| lo | True | 127.0.0.1 | 255.0.0.0 | . |'
- ' . |',
- '| lo | True | ::1/128 | . | host |'
- ' . |',
- '+---------+------+------------------------------+---------------+-------+'
- '-------------------+'])
-
-ROUTE_FORMATTED_OUT = '\n'.join([
- '+++++++++++++++++++++++++++++Route IPv4 info++++++++++++++++++++++++++'
- '+++',
- '+-------+-------------+-------------+---------------+-----------+-----'
- '--+',
- '| Route | Destination | Gateway | Genmask | Interface | Flags'
- ' |',
- '+-------+-------------+-------------+---------------+-----------+'
- '-------+',
- '| 0 | 0.0.0.0 | 192.168.2.1 | 0.0.0.0 | wlp3s0 |'
- ' UG |',
- '| 1 | 192.168.2.0 | 0.0.0.0 | 255.255.255.0 | enp0s25 |'
- ' U |',
- '+-------+-------------+-------------+---------------+-----------+'
- '-------+',
- '++++++++++++++++++++++++++++++++++++++++Route IPv6 info++++++++++'
- '++++++++++++++++++++++++++++++',
- '+-------+-------------+-------------+---------------+---------------+'
- '-----------------+-------+',
- '| Route | Proto | Recv-Q | Send-Q | Local Address |'
- ' Foreign Address | State |',
- '+-------+-------------+-------------+---------------+---------------+'
- '-----------------+-------+',
- '| 0 | 0.0.0.0 | 192.168.2.1 | 0.0.0.0 | UG |'
- ' 0 | 0 |',
- '| 1 | 192.168.2.0 | 0.0.0.0 | 255.255.255.0 | U |'
- ' 0 | 0 |',
- '+-------+-------------+-------------+---------------+---------------+'
- '-----------------+-------+'])
+SAMPLE_OLD_IFCONFIG_OUT = readResource("netinfo/old-ifconfig-output")
+SAMPLE_NEW_IFCONFIG_OUT = readResource("netinfo/new-ifconfig-output")
+SAMPLE_IPADDRSHOW_OUT = readResource("netinfo/sample-ipaddrshow-output")
+SAMPLE_ROUTE_OUT_V4 = readResource("netinfo/sample-route-output-v4")
+SAMPLE_ROUTE_OUT_V6 = readResource("netinfo/sample-route-output-v6")
+SAMPLE_IPROUTE_OUT_V4 = readResource("netinfo/sample-iproute-output-v4")
+SAMPLE_IPROUTE_OUT_V6 = readResource("netinfo/sample-iproute-output-v6")
+NETDEV_FORMATTED_OUT = readResource("netinfo/netdev-formatted-output")
+ROUTE_FORMATTED_OUT = readResource("netinfo/route-formatted-output")
class TestNetInfo(CiTestCase):
maxDiff = None
+ with_logs = True
+ @mock.patch('cloudinit.netinfo.util.which')
@mock.patch('cloudinit.netinfo.util.subp')
- def test_netdev_pformat(self, m_subp):
- """netdev_pformat properly rendering network device information."""
- m_subp.return_value = (SAMPLE_IFCONFIG_OUT, '')
+ def test_netdev_old_nettools_pformat(self, m_subp, m_which):
+ """netdev_pformat properly rendering old nettools info."""
+ m_subp.return_value = (SAMPLE_OLD_IFCONFIG_OUT, '')
+ m_which.side_effect = lambda x: x if x == 'ifconfig' else None
content = netdev_pformat()
self.assertEqual(NETDEV_FORMATTED_OUT, content)
+ @mock.patch('cloudinit.netinfo.util.which')
+ @mock.patch('cloudinit.netinfo.util.subp')
+ def test_netdev_new_nettools_pformat(self, m_subp, m_which):
+ """netdev_pformat properly rendering netdev new nettools info."""
+ m_subp.return_value = (SAMPLE_NEW_IFCONFIG_OUT, '')
+ m_which.side_effect = lambda x: x if x == 'ifconfig' else None
+ content = netdev_pformat()
+ self.assertEqual(NETDEV_FORMATTED_OUT, content)
+
+ @mock.patch('cloudinit.netinfo.util.which')
+ @mock.patch('cloudinit.netinfo.util.subp')
+ def test_netdev_iproute_pformat(self, m_subp, m_which):
+ """netdev_pformat properly rendering ip route info."""
+ m_subp.return_value = (SAMPLE_IPADDRSHOW_OUT, '')
+ m_which.side_effect = lambda x: x if x == 'ip' else None
+ content = netdev_pformat()
+ new_output = copy(NETDEV_FORMATTED_OUT)
+ # ip route show describes global scopes on ipv4 addresses
+ # whereas ifconfig does not. Add proper global/host scope to output.
+ new_output = new_output.replace('| . | 50:7b', '| global | 50:7b')
+ new_output = new_output.replace(
+ '255.0.0.0 | . |', '255.0.0.0 | host |')
+ self.assertEqual(new_output, content)
+
+ @mock.patch('cloudinit.netinfo.util.which')
+ @mock.patch('cloudinit.netinfo.util.subp')
+ def test_netdev_warn_on_missing_commands(self, m_subp, m_which):
+ """netdev_pformat warns when missing both ip and 'netstat'."""
+ m_which.return_value = None # Niether ip nor netstat found
+ content = netdev_pformat()
+ self.assertEqual('\n', content)
+ self.assertEqual(
+ "WARNING: Could not print networks: missing 'ip' and 'ifconfig'"
+ " commands\n",
+ self.logs.getvalue())
+ m_subp.assert_not_called()
+
+ @mock.patch('cloudinit.netinfo.util.which')
+ @mock.patch('cloudinit.netinfo.util.subp')
+ def test_netdev_info_nettools_down(self, m_subp, m_which):
+ """test netdev_info using nettools and down interfaces."""
+ m_subp.return_value = (
+ readResource("netinfo/new-ifconfig-output-down"), "")
+ m_which.side_effect = lambda x: x if x == 'ifconfig' else None
+ self.assertEqual(
+ {'eth0': {'ipv4': [], 'ipv6': [],
+ 'hwaddr': '00:16:3e:de:51:a6', 'up': False},
+ 'lo': {'ipv4': [{'ip': '127.0.0.1', 'mask': '255.0.0.0'}],
+ 'ipv6': [{'ip': '::1/128', 'scope6': 'host'}],
+ 'hwaddr': '.', 'up': True}},
+ netdev_info("."))
+
+ @mock.patch('cloudinit.netinfo.util.which')
+ @mock.patch('cloudinit.netinfo.util.subp')
+ def test_netdev_info_iproute_down(self, m_subp, m_which):
+ """Test netdev_info with ip and down interfaces."""
+ m_subp.return_value = (
+ readResource("netinfo/sample-ipaddrshow-output-down"), "")
+ m_which.side_effect = lambda x: x if x == 'ip' else None
+ self.assertEqual(
+ {'lo': {'ipv4': [{'ip': '127.0.0.1', 'bcast': '.',
+ 'mask': '255.0.0.0', 'scope': 'host'}],
+ 'ipv6': [{'ip': '::1/128', 'scope6': 'host'}],
+ 'hwaddr': '.', 'up': True},
+ 'eth0': {'ipv4': [], 'ipv6': [],
+ 'hwaddr': '00:16:3e:de:51:a6', 'up': False}},
+ netdev_info("."))
+
+ @mock.patch('cloudinit.netinfo.netdev_info')
+ def test_netdev_pformat_with_down(self, m_netdev_info):
+ """test netdev_pformat when netdev_info returns 'down' interfaces."""
+ m_netdev_info.return_value = (
+ {'lo': {'ipv4': [{'ip': '127.0.0.1', 'mask': '255.0.0.0',
+ 'scope': 'host'}],
+ 'ipv6': [{'ip': '::1/128', 'scope6': 'host'}],
+ 'hwaddr': '.', 'up': True},
+ 'eth0': {'ipv4': [], 'ipv6': [],
+ 'hwaddr': '00:16:3e:de:51:a6', 'up': False}})
+ self.assertEqual(
+ readResource("netinfo/netdev-formatted-output-down"),
+ netdev_pformat())
+
+ @mock.patch('cloudinit.netinfo.util.which')
+ @mock.patch('cloudinit.netinfo.util.subp')
+ def test_route_nettools_pformat(self, m_subp, m_which):
+ """route_pformat properly rendering nettools route info."""
+
+ def subp_netstat_route_selector(*args, **kwargs):
+ if args[0] == ['netstat', '--route', '--numeric', '--extend']:
+ return (SAMPLE_ROUTE_OUT_V4, '')
+ if args[0] == ['netstat', '-A', 'inet6', '--route', '--numeric']:
+ return (SAMPLE_ROUTE_OUT_V6, '')
+ raise Exception('Unexpected subp call %s' % args[0])
+
+ m_subp.side_effect = subp_netstat_route_selector
+ m_which.side_effect = lambda x: x if x == 'netstat' else None
+ content = route_pformat()
+ self.assertEqual(ROUTE_FORMATTED_OUT, content)
+
+ @mock.patch('cloudinit.netinfo.util.which')
@mock.patch('cloudinit.netinfo.util.subp')
- def test_route_pformat(self, m_subp):
- """netdev_pformat properly rendering network device information."""
- m_subp.return_value = (SAMPLE_ROUTE_OUT, '')
+ def test_route_iproute_pformat(self, m_subp, m_which):
+ """route_pformat properly rendering ip route info."""
+
+ def subp_iproute_selector(*args, **kwargs):
+ if ['ip', '-o', 'route', 'list'] == args[0]:
+ return (SAMPLE_IPROUTE_OUT_V4, '')
+ v6cmd = ['ip', '--oneline', '-6', 'route', 'list', 'table', 'all']
+ if v6cmd == args[0]:
+ return (SAMPLE_IPROUTE_OUT_V6, '')
+ raise Exception('Unexpected subp call %s' % args[0])
+
+ m_subp.side_effect = subp_iproute_selector
+ m_which.side_effect = lambda x: x if x == 'ip' else None
content = route_pformat()
self.assertEqual(ROUTE_FORMATTED_OUT, content)
+
+ @mock.patch('cloudinit.netinfo.util.which')
+ @mock.patch('cloudinit.netinfo.util.subp')
+ def test_route_warn_on_missing_commands(self, m_subp, m_which):
+ """route_pformat warns when missing both ip and 'netstat'."""
+ m_which.return_value = None # Niether ip nor netstat found
+ content = route_pformat()
+ self.assertEqual('\n', content)
+ self.assertEqual(
+ "WARNING: Could not print routes: missing 'ip' and 'netstat'"
+ " commands\n",
+ self.logs.getvalue())
+ m_subp.assert_not_called()
+
+# vi: ts=4 expandtab
diff --git a/cloudinit/tests/test_url_helper.py b/cloudinit/tests/test_url_helper.py
index b778a3a7..113249d9 100644
--- a/cloudinit/tests/test_url_helper.py
+++ b/cloudinit/tests/test_url_helper.py
@@ -1,7 +1,10 @@
# This file is part of cloud-init. See LICENSE file for license information.
-from cloudinit.url_helper import oauth_headers
+from cloudinit.url_helper import oauth_headers, read_file_or_url
from cloudinit.tests.helpers import CiTestCase, mock, skipIf
+from cloudinit import util
+
+import httpretty
try:
@@ -38,3 +41,26 @@ class TestOAuthHeaders(CiTestCase):
'url', 'consumer_key', 'token_key', 'token_secret',
'consumer_secret')
self.assertEqual('url', return_value)
+
+
+class TestReadFileOrUrl(CiTestCase):
+ def test_read_file_or_url_str_from_file(self):
+ """Test that str(result.contents) on file is text version of contents.
+ It should not be "b'data'", but just "'data'" """
+ tmpf = self.tmp_path("myfile1")
+ data = b'This is my file content\n'
+ util.write_file(tmpf, data, omode="wb")
+ result = read_file_or_url("file://%s" % tmpf)
+ self.assertEqual(result.contents, data)
+ self.assertEqual(str(result), data.decode('utf-8'))
+
+ @httpretty.activate
+ def test_read_file_or_url_str_from_url(self):
+ """Test that str(result.contents) on url is text version of contents.
+ It should not be "b'data'", but just "'data'" """
+ url = 'http://hostname/path'
+ data = b'This is my url content\n'
+ httpretty.register_uri(httpretty.GET, url, data)
+ result = read_file_or_url(url)
+ self.assertEqual(result.contents, data)
+ self.assertEqual(str(result), data.decode('utf-8'))
diff --git a/cloudinit/tests/test_util.py b/cloudinit/tests/test_util.py
index 3f37dbb6..17853fc7 100644
--- a/cloudinit/tests/test_util.py
+++ b/cloudinit/tests/test_util.py
@@ -3,11 +3,12 @@
"""Tests for cloudinit.util"""
import logging
-from textwrap import dedent
+import platform
import cloudinit.util as util
from cloudinit.tests.helpers import CiTestCase, mock
+from textwrap import dedent
LOG = logging.getLogger(__name__)
@@ -16,6 +17,29 @@ MOUNT_INFO = [
'153 68 254:0 / /home rw,relatime shared:101 - xfs /dev/sda2 rw,attr2'
]
+OS_RELEASE_SLES = dedent("""\
+ NAME="SLES"\n
+ VERSION="12-SP3"\n
+ VERSION_ID="12.3"\n
+ PRETTY_NAME="SUSE Linux Enterprise Server 12 SP3"\n
+ ID="sles"\nANSI_COLOR="0;32"\n
+ CPE_NAME="cpe:/o:suse:sles:12:sp3"\n
+""")
+
+OS_RELEASE_UBUNTU = dedent("""\
+ NAME="Ubuntu"\n
+ VERSION="16.04.3 LTS (Xenial Xerus)"\n
+ ID=ubuntu\n
+ ID_LIKE=debian\n
+ PRETTY_NAME="Ubuntu 16.04.3 LTS"\n
+ VERSION_ID="16.04"\n
+ HOME_URL="http://www.ubuntu.com/"\n
+ SUPPORT_URL="http://help.ubuntu.com/"\n
+ BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"\n
+ VERSION_CODENAME=xenial\n
+ UBUNTU_CODENAME=xenial\n
+""")
+
class FakeCloud(object):
@@ -135,7 +159,7 @@ class TestGetHostnameFqdn(CiTestCase):
def test_get_hostname_fqdn_from_passes_metadata_only_to_cloud(self):
"""Calls to cloud.get_hostname pass the metadata_only parameter."""
mycloud = FakeCloud('cloudhost', 'cloudhost.mycloud.com')
- hostname, fqdn = util.get_hostname_fqdn(
+ _hn, _fqdn = util.get_hostname_fqdn(
cfg={}, cloud=mycloud, metadata_only=True)
self.assertEqual(
[{'fqdn': True, 'metadata_only': True},
@@ -212,4 +236,105 @@ class TestBlkid(CiTestCase):
capture=True, decode="replace")
+@mock.patch('cloudinit.util.subp')
+class TestUdevadmSettle(CiTestCase):
+ def test_with_no_params(self, m_subp):
+ """called with no parameters."""
+ util.udevadm_settle()
+ m_subp.called_once_with(mock.call(['udevadm', 'settle']))
+
+ def test_with_exists_and_not_exists(self, m_subp):
+ """with exists=file where file does not exist should invoke subp."""
+ mydev = self.tmp_path("mydev")
+ util.udevadm_settle(exists=mydev)
+ m_subp.called_once_with(
+ ['udevadm', 'settle', '--exit-if-exists=%s' % mydev])
+
+ def test_with_exists_and_file_exists(self, m_subp):
+ """with exists=file where file does exist should not invoke subp."""
+ mydev = self.tmp_path("mydev")
+ util.write_file(mydev, "foo\n")
+ util.udevadm_settle(exists=mydev)
+ self.assertIsNone(m_subp.call_args)
+
+ def test_with_timeout_int(self, m_subp):
+ """timeout can be an integer."""
+ timeout = 9
+ util.udevadm_settle(timeout=timeout)
+ m_subp.called_once_with(
+ ['udevadm', 'settle', '--timeout=%s' % timeout])
+
+ def test_with_timeout_string(self, m_subp):
+ """timeout can be a string."""
+ timeout = "555"
+ util.udevadm_settle(timeout=timeout)
+ m_subp.assert_called_once_with(
+ ['udevadm', 'settle', '--timeout=%s' % timeout])
+
+ def test_with_exists_and_timeout(self, m_subp):
+ """test call with both exists and timeout."""
+ mydev = self.tmp_path("mydev")
+ timeout = "3"
+ util.udevadm_settle(exists=mydev)
+ m_subp.called_once_with(
+ ['udevadm', 'settle', '--exit-if-exists=%s' % mydev,
+ '--timeout=%s' % timeout])
+
+ def test_subp_exception_raises_to_caller(self, m_subp):
+ m_subp.side_effect = util.ProcessExecutionError("BOOM")
+ self.assertRaises(util.ProcessExecutionError, util.udevadm_settle)
+
+
+@mock.patch('os.path.exists')
+class TestGetLinuxDistro(CiTestCase):
+
+ @classmethod
+ def os_release_exists(self, path):
+ """Side effect function"""
+ if path == '/etc/os-release':
+ return 1
+
+ @mock.patch('cloudinit.util.load_file')
+ def test_get_linux_distro_quoted_name(self, m_os_release, m_path_exists):
+ """Verify we get the correct name if the os-release file has
+ the distro name in quotes"""
+ m_os_release.return_value = OS_RELEASE_SLES
+ m_path_exists.side_effect = TestGetLinuxDistro.os_release_exists
+ dist = util.get_linux_distro()
+ self.assertEqual(('sles', '12.3', platform.machine()), dist)
+
+ @mock.patch('cloudinit.util.load_file')
+ def test_get_linux_distro_bare_name(self, m_os_release, m_path_exists):
+ """Verify we get the correct name if the os-release file does not
+ have the distro name in quotes"""
+ m_os_release.return_value = OS_RELEASE_UBUNTU
+ m_path_exists.side_effect = TestGetLinuxDistro.os_release_exists
+ dist = util.get_linux_distro()
+ self.assertEqual(('ubuntu', '16.04', platform.machine()), dist)
+
+ @mock.patch('platform.dist')
+ def test_get_linux_distro_no_data(self, m_platform_dist, m_path_exists):
+ """Verify we get no information if os-release does not exist"""
+ m_platform_dist.return_value = ('', '', '')
+ m_path_exists.return_value = 0
+ dist = util.get_linux_distro()
+ self.assertEqual(('', '', ''), dist)
+
+ @mock.patch('platform.dist')
+ def test_get_linux_distro_no_impl(self, m_platform_dist, m_path_exists):
+ """Verify we get an empty tuple when no information exists and
+ Exceptions are not propagated"""
+ m_platform_dist.side_effect = Exception()
+ m_path_exists.return_value = 0
+ dist = util.get_linux_distro()
+ self.assertEqual(('', '', ''), dist)
+
+ @mock.patch('platform.dist')
+ def test_get_linux_distro_plat_data(self, m_platform_dist, m_path_exists):
+ """Verify we get the correct platform information"""
+ m_platform_dist.return_value = ('foo', '1.1', 'aarch64')
+ m_path_exists.return_value = 0
+ dist = util.get_linux_distro()
+ self.assertEqual(('foo', '1.1', 'aarch64'), dist)
+
# vi: ts=4 expandtab
diff --git a/cloudinit/tests/test_version.py b/cloudinit/tests/test_version.py
new file mode 100644
index 00000000..a96c2a47
--- /dev/null
+++ b/cloudinit/tests/test_version.py
@@ -0,0 +1,31 @@
+# This file is part of cloud-init. See LICENSE file for license information.
+
+from cloudinit.tests.helpers import CiTestCase
+from cloudinit import version
+
+import mock
+
+
+class TestExportsFeatures(CiTestCase):
+ def test_has_network_config_v1(self):
+ self.assertIn('NETWORK_CONFIG_V1', version.FEATURES)
+
+ def test_has_network_config_v2(self):
+ self.assertIn('NETWORK_CONFIG_V2', version.FEATURES)
+
+
+class TestVersionString(CiTestCase):
+ @mock.patch("cloudinit.version._PACKAGED_VERSION",
+ "17.2-3-gb05b9972-0ubuntu1")
+ def test_package_version_respected(self):
+ """If _PACKAGED_VERSION is filled in, then it should be returned."""
+ self.assertEqual("17.2-3-gb05b9972-0ubuntu1", version.version_string())
+
+ @mock.patch("cloudinit.version._PACKAGED_VERSION", "@@PACKAGED_VERSION@@")
+ @mock.patch("cloudinit.version.__VERSION__", "17.2")
+ def test_package_version_skipped(self):
+ """If _PACKAGED_VERSION is not modified, then return __VERSION__."""
+ self.assertEqual("17.2", version.version_string())
+
+
+# vi: ts=4 expandtab