summaryrefslogtreecommitdiff
path: root/tests/unittests/test_datasource
diff options
context:
space:
mode:
Diffstat (limited to 'tests/unittests/test_datasource')
-rw-r--r--tests/unittests/test_datasource/test_aliyun.py2
-rw-r--r--tests/unittests/test_datasource/test_altcloud.py7
-rw-r--r--tests/unittests/test_datasource/test_azure.py114
-rw-r--r--tests/unittests/test_datasource/test_azure_helper.py406
-rw-r--r--tests/unittests/test_datasource/test_cloudsigma.py6
-rw-r--r--tests/unittests/test_datasource/test_cloudstack.py2
-rw-r--r--tests/unittests/test_datasource/test_ec2.py395
-rw-r--r--tests/unittests/test_datasource/test_gce.py3
-rw-r--r--tests/unittests/test_datasource/test_hetzner.py23
-rw-r--r--tests/unittests/test_datasource/test_ibmcloud.py7
-rw-r--r--tests/unittests/test_datasource/test_maas.py1
-rw-r--r--tests/unittests/test_datasource/test_nocloud.py17
-rw-r--r--tests/unittests/test_datasource/test_opennebula.py157
-rw-r--r--tests/unittests/test_datasource/test_openstack.py20
-rw-r--r--tests/unittests/test_datasource/test_ovf.py93
-rw-r--r--tests/unittests/test_datasource/test_rbx.py34
-rw-r--r--tests/unittests/test_datasource/test_scaleway.py81
-rw-r--r--tests/unittests/test_datasource/test_smartos.py20
18 files changed, 1087 insertions, 301 deletions
diff --git a/tests/unittests/test_datasource/test_aliyun.py b/tests/unittests/test_datasource/test_aliyun.py
index 1e66fcdb..b626229e 100644
--- a/tests/unittests/test_datasource/test_aliyun.py
+++ b/tests/unittests/test_datasource/test_aliyun.py
@@ -143,7 +143,7 @@ class TestAliYunDatasource(test_helpers.HttprettyTestCase):
self.assertEqual('aliyun', self.ds.cloud_name)
self.assertEqual('ec2', self.ds.platform)
self.assertEqual(
- 'metadata (http://100.100.100.200)', self.ds.subplatform)
+ 'metadata (http://100.100.100.200)', self.ds.subplatform)
@mock.patch("cloudinit.sources.DataSourceAliYun._is_aliyun")
def test_returns_false_when_not_on_aliyun(self, m_is_aliyun):
diff --git a/tests/unittests/test_datasource/test_altcloud.py b/tests/unittests/test_datasource/test_altcloud.py
index 3119bfac..fc59d1d5 100644
--- a/tests/unittests/test_datasource/test_altcloud.py
+++ b/tests/unittests/test_datasource/test_altcloud.py
@@ -15,6 +15,7 @@ import shutil
import tempfile
from cloudinit import helpers
+from cloudinit import subp
from cloudinit import util
from cloudinit.tests.helpers import CiTestCase, mock
@@ -286,7 +287,7 @@ class TestUserDataRhevm(CiTestCase):
def test_modprobe_fails(self):
'''Test user_data_rhevm() where modprobe fails.'''
- self.m_modprobe_floppy.side_effect = util.ProcessExecutionError(
+ self.m_modprobe_floppy.side_effect = subp.ProcessExecutionError(
"Failed modprobe")
dsrc = dsac.DataSourceAltCloud({}, None, self.paths)
self.assertEqual(False, dsrc.user_data_rhevm())
@@ -294,7 +295,7 @@ class TestUserDataRhevm(CiTestCase):
def test_no_modprobe_cmd(self):
'''Test user_data_rhevm() with no modprobe command.'''
- self.m_modprobe_floppy.side_effect = util.ProcessExecutionError(
+ self.m_modprobe_floppy.side_effect = subp.ProcessExecutionError(
"No such file or dir")
dsrc = dsac.DataSourceAltCloud({}, None, self.paths)
self.assertEqual(False, dsrc.user_data_rhevm())
@@ -302,7 +303,7 @@ class TestUserDataRhevm(CiTestCase):
def test_udevadm_fails(self):
'''Test user_data_rhevm() where udevadm fails.'''
- self.m_udevadm_settle.side_effect = util.ProcessExecutionError(
+ self.m_udevadm_settle.side_effect = subp.ProcessExecutionError(
"Failed settle.")
dsrc = dsac.DataSourceAltCloud({}, None, self.paths)
self.assertEqual(False, dsrc.user_data_rhevm())
diff --git a/tests/unittests/test_datasource/test_azure.py b/tests/unittests/test_datasource/test_azure.py
index a809fd87..47e03bd1 100644
--- a/tests/unittests/test_datasource/test_azure.py
+++ b/tests/unittests/test_datasource/test_azure.py
@@ -114,14 +114,14 @@ NETWORK_METADATA = {
"ipv4": {
"subnet": [
{
- "prefix": "24",
- "address": "10.0.0.0"
+ "prefix": "24",
+ "address": "10.0.0.0"
}
],
"ipAddress": [
{
- "privateIpAddress": "10.0.0.4",
- "publicIpAddress": "104.46.124.81"
+ "privateIpAddress": "10.0.0.4",
+ "publicIpAddress": "104.46.124.81"
}
]
}
@@ -278,6 +278,23 @@ class TestParseNetworkConfig(CiTestCase):
}
self.assertEqual(expected, dsaz.parse_network_config(imds_data))
+ @mock.patch('cloudinit.sources.DataSourceAzure.device_driver',
+ return_value='hv_netvsc')
+ def test_match_driver_for_netvsc(self, m_driver):
+ """parse_network_config emits driver when using netvsc."""
+ expected = {'ethernets': {
+ 'eth0': {
+ 'dhcp4': True,
+ 'dhcp4-overrides': {'route-metric': 100},
+ 'dhcp6': False,
+ 'match': {
+ 'macaddress': '00:0d:3a:04:75:98',
+ 'driver': 'hv_netvsc',
+ },
+ 'set-name': 'eth0'
+ }}, 'version': 2}
+ self.assertEqual(expected, dsaz.parse_network_config(NETWORK_METADATA))
+
class TestGetMetadataFromIMDS(HttprettyTestCase):
@@ -383,8 +400,6 @@ class TestGetMetadataFromIMDS(HttprettyTestCase):
class TestAzureDataSource(CiTestCase):
- with_logs = True
-
def setUp(self):
super(TestAzureDataSource, self).setUp()
self.tmp = self.tmp_dir()
@@ -493,7 +508,7 @@ scbus-1 on xpt0 bus 0
(dsaz, 'get_hostname', mock.MagicMock()),
(dsaz, 'set_hostname', mock.MagicMock()),
(dsaz, 'get_metadata_from_fabric', self.get_metadata_from_fabric),
- (dsaz.util, 'which', lambda x: True),
+ (dsaz.subp, 'which', lambda x: True),
(dsaz.util, 'read_dmi_data', mock.MagicMock(
side_effect=_dmi_mocks)),
(dsaz.util, 'wait_for_files', mock.MagicMock(
@@ -530,14 +545,14 @@ scbus-1 on xpt0 bus 0
def tags_exists(x, y):
for tag in x.keys():
- self.assertIn(tag, y)
+ assert tag in y
for tag in y.keys():
- self.assertIn(tag, x)
+ assert tag in x
def tags_equal(x, y):
for x_val in x.values():
y_val = y.get(x_val.tag)
- self.assertEqual(x_val.text, y_val.text)
+ assert x_val.text == y_val.text
old_cnt = create_tag_index(oxml)
new_cnt = create_tag_index(nxml)
@@ -651,7 +666,7 @@ scbus-1 on xpt0 bus 0
crawled_metadata = dsrc.crawl_metadata()
- self.assertItemsEqual(
+ self.assertCountEqual(
crawled_metadata.keys(),
['cfg', 'files', 'metadata', 'userdata_raw'])
self.assertEqual(crawled_metadata['cfg'], expected_cfg)
@@ -685,15 +700,17 @@ scbus-1 on xpt0 bus 0
'cloudinit.sources.DataSourceAzure.DataSourceAzure._report_ready')
@mock.patch('cloudinit.sources.DataSourceAzure.DataSourceAzure._poll_imds')
def test_crawl_metadata_on_reprovision_reports_ready(
- self, poll_imds_func,
- report_ready_func,
- m_write, m_dhcp):
+ self, poll_imds_func, report_ready_func, m_write, m_dhcp
+ ):
"""If reprovisioning, report ready at the end"""
ovfenv = construct_valid_ovf_env(
- platform_settings={"PreprovisionedVm": "True"})
+ platform_settings={"PreprovisionedVm": "True"}
+ )
- data = {'ovfcontent': ovfenv,
- 'sys_cfg': {}}
+ data = {
+ 'ovfcontent': ovfenv,
+ 'sys_cfg': {}
+ }
dsrc = self._get_ds(data)
poll_imds_func.return_value = ovfenv
dsrc.crawl_metadata()
@@ -708,15 +725,18 @@ scbus-1 on xpt0 bus 0
@mock.patch('cloudinit.net.dhcp.maybe_perform_dhcp_discovery')
@mock.patch('cloudinit.sources.DataSourceAzure.readurl')
def test_crawl_metadata_on_reprovision_reports_ready_using_lease(
- self, m_readurl, m_dhcp,
- m_net, report_ready_func,
- m_media_switch, m_write):
+ self, m_readurl, m_dhcp, m_net, report_ready_func,
+ m_media_switch, m_write
+ ):
"""If reprovisioning, report ready using the obtained lease"""
ovfenv = construct_valid_ovf_env(
- platform_settings={"PreprovisionedVm": "True"})
+ platform_settings={"PreprovisionedVm": "True"}
+ )
- data = {'ovfcontent': ovfenv,
- 'sys_cfg': {}}
+ data = {
+ 'ovfcontent': ovfenv,
+ 'sys_cfg': {}
+ }
dsrc = self._get_ds(data)
lease = {
@@ -1269,20 +1289,20 @@ scbus-1 on xpt0 bus 0
expected_config['config'].append(blacklist_config)
self.assertEqual(netconfig, expected_config)
- @mock.patch(MOCKPATH + 'util.subp')
- def test_get_hostname_with_no_args(self, subp):
+ @mock.patch(MOCKPATH + 'subp.subp')
+ def test_get_hostname_with_no_args(self, m_subp):
dsaz.get_hostname()
- subp.assert_called_once_with(("hostname",), capture=True)
+ m_subp.assert_called_once_with(("hostname",), capture=True)
- @mock.patch(MOCKPATH + 'util.subp')
- def test_get_hostname_with_string_arg(self, subp):
+ @mock.patch(MOCKPATH + 'subp.subp')
+ def test_get_hostname_with_string_arg(self, m_subp):
dsaz.get_hostname(hostname_command="hostname")
- subp.assert_called_once_with(("hostname",), capture=True)
+ m_subp.assert_called_once_with(("hostname",), capture=True)
- @mock.patch(MOCKPATH + 'util.subp')
- def test_get_hostname_with_iterable_arg(self, subp):
+ @mock.patch(MOCKPATH + 'subp.subp')
+ def test_get_hostname_with_iterable_arg(self, m_subp):
dsaz.get_hostname(hostname_command=("hostname",))
- subp.assert_called_once_with(("hostname",), capture=True)
+ m_subp.assert_called_once_with(("hostname",), capture=True)
class TestAzureBounce(CiTestCase):
@@ -1304,7 +1324,7 @@ class TestAzureBounce(CiTestCase):
mock.patch.object(dsaz, 'get_metadata_from_imds',
mock.MagicMock(return_value={})))
self.patches.enter_context(
- mock.patch.object(dsaz.util, 'which', lambda x: True))
+ mock.patch.object(dsaz.subp, 'which', lambda x: True))
self.patches.enter_context(mock.patch.object(
dsaz, '_get_random_seed', return_value='wild'))
@@ -1333,7 +1353,7 @@ class TestAzureBounce(CiTestCase):
self.set_hostname = self.patches.enter_context(
mock.patch.object(dsaz, 'set_hostname'))
self.subp = self.patches.enter_context(
- mock.patch(MOCKPATH + 'util.subp'))
+ mock.patch(MOCKPATH + 'subp.subp'))
self.find_fallback_nic = self.patches.enter_context(
mock.patch('cloudinit.net.find_fallback_nic', return_value='eth9'))
@@ -1416,7 +1436,7 @@ class TestAzureBounce(CiTestCase):
cfg = {'hostname_bounce': {'policy': 'force'}}
dsrc = self._get_ds(self.get_ovf_env_with_dscfg(host_name, cfg),
agent_command=['not', '__builtin__'])
- patch_path = MOCKPATH + 'util.which'
+ patch_path = MOCKPATH + 'subp.which'
with mock.patch(patch_path) as m_which:
m_which.return_value = None
ret = self._get_and_setup(dsrc)
@@ -1521,6 +1541,17 @@ class TestAzureBounce(CiTestCase):
self.assertEqual(0, self.set_hostname.call_count)
+ @mock.patch(MOCKPATH + 'perform_hostname_bounce')
+ def test_set_hostname_failed_disable_bounce(
+ self, perform_hostname_bounce):
+ cfg = {'set_hostname': True, 'hostname_bounce': {'policy': 'force'}}
+ self.get_hostname.return_value = "old-hostname"
+ self.set_hostname.side_effect = Exception
+ data = self.get_ovf_env_with_dscfg('some-hostname', cfg)
+ self._get_ds(data).get_data()
+
+ self.assertEqual(0, perform_hostname_bounce.call_count)
+
class TestLoadAzureDsDir(CiTestCase):
"""Tests for load_azure_ds_dir."""
@@ -1953,11 +1984,12 @@ class TestPreprovisioningPollIMDS(CiTestCase):
self.tries += 1
if self.tries == 1:
raise requests.Timeout('Fake connection timeout')
- elif self.tries == 2:
+ elif self.tries in (2, 3):
response = requests.Response()
- response.status_code = 404
+ response.status_code = 404 if self.tries == 2 else 410
raise requests.exceptions.HTTPError(
- "fake 404", response=response)
+ "fake {}".format(response.status_code), response=response
+ )
# Third try should succeed and stop retries or redhcp
return mock.MagicMock(status_code=200, text="good", content="good")
@@ -1969,7 +2001,7 @@ class TestPreprovisioningPollIMDS(CiTestCase):
self.assertEqual(report_ready_func.call_count, 1)
report_ready_func.assert_called_with(lease=lease)
self.assertEqual(3, m_dhcpv4.call_count, 'Expected 3 DHCP calls')
- self.assertEqual(3, self.tries, 'Expected 3 total reads from IMDS')
+ self.assertEqual(4, self.tries, 'Expected 4 total reads from IMDS')
def test_poll_imds_report_ready_false(self,
report_ready_func, fake_resp,
@@ -1989,7 +2021,7 @@ class TestPreprovisioningPollIMDS(CiTestCase):
self.assertEqual(report_ready_func.call_count, 0)
-@mock.patch(MOCKPATH + 'util.subp')
+@mock.patch(MOCKPATH + 'subp.subp')
@mock.patch(MOCKPATH + 'util.write_file')
@mock.patch(MOCKPATH + 'util.is_FreeBSD')
@mock.patch('cloudinit.sources.helpers.netlink.'
@@ -2160,7 +2192,7 @@ class TestWBIsPlatformViable(CiTestCase):
{'os.path.exists': False,
# Non-matching Azure chassis-asset-tag
'util.read_dmi_data': dsaz.AZURE_CHASSIS_ASSET_TAG + 'X',
- 'util.which': None},
+ 'subp.which': None},
dsaz._is_platform_viable, 'doesnotmatter'))
self.assertIn(
"DEBUG: Non-Azure DMI asset tag '{0}' discovered.\n".format(
diff --git a/tests/unittests/test_datasource/test_azure_helper.py b/tests/unittests/test_datasource/test_azure_helper.py
index 007df09f..5e6d3d2d 100644
--- a/tests/unittests/test_datasource/test_azure_helper.py
+++ b/tests/unittests/test_datasource/test_azure_helper.py
@@ -1,8 +1,10 @@
# This file is part of cloud-init. See LICENSE file for license information.
import os
-import unittest2
+import re
+import unittest
from textwrap import dedent
+from xml.etree import ElementTree
from cloudinit.sources.helpers import azure as azure_helper
from cloudinit.tests.helpers import CiTestCase, ExitStack, mock, populate_dir
@@ -48,6 +50,30 @@ GOAL_STATE_TEMPLATE = """\
</GoalState>
"""
+HEALTH_REPORT_XML_TEMPLATE = '''\
+<?xml version="1.0" encoding="utf-8"?>
+<Health xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+ <GoalStateIncarnation>{incarnation}</GoalStateIncarnation>
+ <Container>
+ <ContainerId>{container_id}</ContainerId>
+ <RoleInstanceList>
+ <Role>
+ <InstanceId>{instance_id}</InstanceId>
+ <Health>
+ <State>{health_status}</State>
+ {health_detail_subsection}
+ </Health>
+ </Role>
+ </RoleInstanceList>
+ </Container>
+</Health>
+'''
+
+
+class SentinelException(Exception):
+ pass
+
class TestFindEndpoint(CiTestCase):
@@ -140,9 +166,7 @@ class TestGoalStateParsing(CiTestCase):
'certificates_url': 'MyCertificatesUrl',
}
- def _get_goal_state(self, http_client=None, **kwargs):
- if http_client is None:
- http_client = mock.MagicMock()
+ def _get_formatted_goal_state_xml_string(self, **kwargs):
parameters = self.default_parameters.copy()
parameters.update(kwargs)
xml = GOAL_STATE_TEMPLATE.format(**parameters)
@@ -153,7 +177,13 @@ class TestGoalStateParsing(CiTestCase):
continue
new_xml_lines.append(line)
xml = '\n'.join(new_xml_lines)
- return azure_helper.GoalState(xml, http_client)
+ return xml
+
+ def _get_goal_state(self, m_azure_endpoint_client=None, **kwargs):
+ if m_azure_endpoint_client is None:
+ m_azure_endpoint_client = mock.MagicMock()
+ xml = self._get_formatted_goal_state_xml_string(**kwargs)
+ return azure_helper.GoalState(xml, m_azure_endpoint_client)
def test_incarnation_parsed_correctly(self):
incarnation = '123'
@@ -190,25 +220,55 @@ class TestGoalStateParsing(CiTestCase):
azure_helper.is_byte_swapped(previous_iid, current_iid))
def test_certificates_xml_parsed_and_fetched_correctly(self):
- http_client = mock.MagicMock()
+ m_azure_endpoint_client = mock.MagicMock()
certificates_url = 'TestCertificatesUrl'
goal_state = self._get_goal_state(
- http_client=http_client, certificates_url=certificates_url)
+ m_azure_endpoint_client=m_azure_endpoint_client,
+ certificates_url=certificates_url)
certificates_xml = goal_state.certificates_xml
- self.assertEqual(1, http_client.get.call_count)
- self.assertEqual(certificates_url, http_client.get.call_args[0][0])
- self.assertTrue(http_client.get.call_args[1].get('secure', False))
- self.assertEqual(http_client.get.return_value.contents,
- certificates_xml)
+ self.assertEqual(1, m_azure_endpoint_client.get.call_count)
+ self.assertEqual(
+ certificates_url,
+ m_azure_endpoint_client.get.call_args[0][0])
+ self.assertTrue(
+ m_azure_endpoint_client.get.call_args[1].get(
+ 'secure', False))
+ self.assertEqual(
+ m_azure_endpoint_client.get.return_value.contents,
+ certificates_xml)
def test_missing_certificates_skips_http_get(self):
- http_client = mock.MagicMock()
+ m_azure_endpoint_client = mock.MagicMock()
goal_state = self._get_goal_state(
- http_client=http_client, certificates_url=None)
+ m_azure_endpoint_client=m_azure_endpoint_client,
+ certificates_url=None)
certificates_xml = goal_state.certificates_xml
- self.assertEqual(0, http_client.get.call_count)
+ self.assertEqual(0, m_azure_endpoint_client.get.call_count)
self.assertIsNone(certificates_xml)
+ def test_invalid_goal_state_xml_raises_parse_error(self):
+ xml = 'random non-xml data'
+ with self.assertRaises(ElementTree.ParseError):
+ azure_helper.GoalState(xml, mock.MagicMock())
+
+ def test_missing_container_id_in_goal_state_xml_raises_exc(self):
+ xml = self._get_formatted_goal_state_xml_string()
+ xml = re.sub('<ContainerId>.*</ContainerId>', '', xml)
+ with self.assertRaises(azure_helper.InvalidGoalStateXMLException):
+ azure_helper.GoalState(xml, mock.MagicMock())
+
+ def test_missing_instance_id_in_goal_state_xml_raises_exc(self):
+ xml = self._get_formatted_goal_state_xml_string()
+ xml = re.sub('<InstanceId>.*</InstanceId>', '', xml)
+ with self.assertRaises(azure_helper.InvalidGoalStateXMLException):
+ azure_helper.GoalState(xml, mock.MagicMock())
+
+ def test_missing_incarnation_in_goal_state_xml_raises_exc(self):
+ xml = self._get_formatted_goal_state_xml_string()
+ xml = re.sub('<Incarnation>.*</Incarnation>', '', xml)
+ with self.assertRaises(azure_helper.InvalidGoalStateXMLException):
+ azure_helper.GoalState(xml, mock.MagicMock())
+
class TestAzureEndpointHttpClient(CiTestCase):
@@ -222,61 +282,95 @@ class TestAzureEndpointHttpClient(CiTestCase):
patches = ExitStack()
self.addCleanup(patches.close)
- self.read_file_or_url = patches.enter_context(
- mock.patch.object(azure_helper.url_helper, 'read_file_or_url'))
+ self.readurl = patches.enter_context(
+ mock.patch.object(azure_helper.url_helper, 'readurl'))
+ patches.enter_context(
+ mock.patch.object(azure_helper.time, 'sleep', mock.MagicMock()))
def test_non_secure_get(self):
client = azure_helper.AzureEndpointHttpClient(mock.MagicMock())
url = 'MyTestUrl'
response = client.get(url, secure=False)
- self.assertEqual(1, self.read_file_or_url.call_count)
- self.assertEqual(self.read_file_or_url.return_value, response)
+ self.assertEqual(1, self.readurl.call_count)
+ self.assertEqual(self.readurl.return_value, response)
self.assertEqual(
- mock.call(url, headers=self.regular_headers, retries=10,
- timeout=5),
- self.read_file_or_url.call_args)
+ mock.call(url, headers=self.regular_headers,
+ timeout=5, retries=10, sec_between=5),
+ self.readurl.call_args)
+
+ def test_non_secure_get_raises_exception(self):
+ client = azure_helper.AzureEndpointHttpClient(mock.MagicMock())
+ self.readurl.side_effect = SentinelException
+ url = 'MyTestUrl'
+ with self.assertRaises(SentinelException):
+ client.get(url, secure=False)
def test_secure_get(self):
url = 'MyTestUrl'
- certificate = mock.MagicMock()
+ m_certificate = mock.MagicMock()
expected_headers = self.regular_headers.copy()
expected_headers.update({
"x-ms-cipher-name": "DES_EDE3_CBC",
- "x-ms-guest-agent-public-x509-cert": certificate,
+ "x-ms-guest-agent-public-x509-cert": m_certificate,
})
- client = azure_helper.AzureEndpointHttpClient(certificate)
+ client = azure_helper.AzureEndpointHttpClient(m_certificate)
response = client.get(url, secure=True)
- self.assertEqual(1, self.read_file_or_url.call_count)
- self.assertEqual(self.read_file_or_url.return_value, response)
+ self.assertEqual(1, self.readurl.call_count)
+ self.assertEqual(self.readurl.return_value, response)
self.assertEqual(
- mock.call(url, headers=expected_headers, retries=10,
- timeout=5),
- self.read_file_or_url.call_args)
+ mock.call(url, headers=expected_headers,
+ timeout=5, retries=10, sec_between=5),
+ self.readurl.call_args)
+
+ def test_secure_get_raises_exception(self):
+ url = 'MyTestUrl'
+ client = azure_helper.AzureEndpointHttpClient(mock.MagicMock())
+ self.readurl.side_effect = SentinelException
+ with self.assertRaises(SentinelException):
+ client.get(url, secure=True)
def test_post(self):
- data = mock.MagicMock()
+ m_data = mock.MagicMock()
url = 'MyTestUrl'
client = azure_helper.AzureEndpointHttpClient(mock.MagicMock())
- response = client.post(url, data=data)
- self.assertEqual(1, self.read_file_or_url.call_count)
- self.assertEqual(self.read_file_or_url.return_value, response)
+ response = client.post(url, data=m_data)
+ self.assertEqual(1, self.readurl.call_count)
+ self.assertEqual(self.readurl.return_value, response)
self.assertEqual(
- mock.call(url, data=data, headers=self.regular_headers, retries=10,
- timeout=5),
- self.read_file_or_url.call_args)
+ mock.call(url, data=m_data, headers=self.regular_headers,
+ timeout=5, retries=10, sec_between=5),
+ self.readurl.call_args)
+
+ def test_post_raises_exception(self):
+ m_data = mock.MagicMock()
+ url = 'MyTestUrl'
+ client = azure_helper.AzureEndpointHttpClient(mock.MagicMock())
+ self.readurl.side_effect = SentinelException
+ with self.assertRaises(SentinelException):
+ client.post(url, data=m_data)
def test_post_with_extra_headers(self):
url = 'MyTestUrl'
client = azure_helper.AzureEndpointHttpClient(mock.MagicMock())
extra_headers = {'test': 'header'}
client.post(url, extra_headers=extra_headers)
- self.assertEqual(1, self.read_file_or_url.call_count)
expected_headers = self.regular_headers.copy()
expected_headers.update(extra_headers)
+ self.assertEqual(1, self.readurl.call_count)
self.assertEqual(
mock.call(mock.ANY, data=mock.ANY, headers=expected_headers,
- retries=10, timeout=5),
- self.read_file_or_url.call_args)
+ timeout=5, retries=10, sec_between=5),
+ self.readurl.call_args)
+
+ def test_post_with_sleep_with_extra_headers_raises_exception(self):
+ m_data = mock.MagicMock()
+ url = 'MyTestUrl'
+ extra_headers = {'test': 'header'}
+ client = azure_helper.AzureEndpointHttpClient(mock.MagicMock())
+ self.readurl.side_effect = SentinelException
+ with self.assertRaises(SentinelException):
+ client.post(
+ url, data=m_data, extra_headers=extra_headers)
class TestOpenSSLManager(CiTestCase):
@@ -287,7 +381,7 @@ class TestOpenSSLManager(CiTestCase):
self.addCleanup(patches.close)
self.subp = patches.enter_context(
- mock.patch.object(azure_helper.util, 'subp'))
+ mock.patch.object(azure_helper.subp, 'subp'))
try:
self.open = patches.enter_context(
mock.patch('__builtin__.open'))
@@ -332,7 +426,7 @@ class TestOpenSSLManagerActions(CiTestCase):
path = 'tests/data/azure'
return os.path.join(path, name)
- @unittest2.skip("todo move to cloud_test")
+ @unittest.skip("todo move to cloud_test")
def test_pubkey_extract(self):
cert = load_file(self._data_file('pubkey_extract_cert'))
good_key = load_file(self._data_file('pubkey_extract_ssh_key'))
@@ -344,7 +438,7 @@ class TestOpenSSLManagerActions(CiTestCase):
fingerprint = sslmgr._get_fingerprint_from_cert(cert)
self.assertEqual(good_fingerprint, fingerprint)
- @unittest2.skip("todo move to cloud_test")
+ @unittest.skip("todo move to cloud_test")
@mock.patch.object(azure_helper.OpenSSLManager, '_decrypt_certs_from_xml')
def test_parse_certificates(self, mock_decrypt_certs):
"""Azure control plane puts private keys as well as certificates
@@ -365,6 +459,131 @@ class TestOpenSSLManagerActions(CiTestCase):
self.assertIn(fp, keys_by_fp)
+class TestGoalStateHealthReporter(CiTestCase):
+
+ default_parameters = {
+ 'incarnation': 1634,
+ 'container_id': 'MyContainerId',
+ 'instance_id': 'MyInstanceId'
+ }
+
+ test_endpoint = 'TestEndpoint'
+ test_url = 'http://{0}/machine?comp=health'.format(test_endpoint)
+ test_default_headers = {'Content-Type': 'text/xml; charset=utf-8'}
+
+ provisioning_success_status = 'Ready'
+
+ def setUp(self):
+ super(TestGoalStateHealthReporter, self).setUp()
+ patches = ExitStack()
+ self.addCleanup(patches.close)
+
+ patches.enter_context(
+ mock.patch.object(azure_helper.time, 'sleep', mock.MagicMock()))
+ self.read_file_or_url = patches.enter_context(
+ mock.patch.object(azure_helper.url_helper, 'read_file_or_url'))
+
+ self.post = patches.enter_context(
+ mock.patch.object(azure_helper.AzureEndpointHttpClient,
+ 'post'))
+
+ self.GoalState = patches.enter_context(
+ mock.patch.object(azure_helper, 'GoalState'))
+ self.GoalState.return_value.container_id = \
+ self.default_parameters['container_id']
+ self.GoalState.return_value.instance_id = \
+ self.default_parameters['instance_id']
+ self.GoalState.return_value.incarnation = \
+ self.default_parameters['incarnation']
+
+ def _get_formatted_health_report_xml_string(self, **kwargs):
+ return HEALTH_REPORT_XML_TEMPLATE.format(**kwargs)
+
+ def _get_report_ready_health_document(self):
+ return self._get_formatted_health_report_xml_string(
+ incarnation=self.default_parameters['incarnation'],
+ container_id=self.default_parameters['container_id'],
+ instance_id=self.default_parameters['instance_id'],
+ health_status=self.provisioning_success_status,
+ health_detail_subsection='')
+
+ def test_send_ready_signal_sends_post_request(self):
+ with mock.patch.object(
+ azure_helper.GoalStateHealthReporter,
+ 'build_report') as m_build_report:
+ client = azure_helper.AzureEndpointHttpClient(mock.MagicMock())
+ reporter = azure_helper.GoalStateHealthReporter(
+ azure_helper.GoalState(mock.MagicMock(), mock.MagicMock()),
+ client, self.test_endpoint)
+ reporter.send_ready_signal()
+
+ self.assertEqual(1, self.post.call_count)
+ self.assertEqual(
+ mock.call(
+ self.test_url,
+ data=m_build_report.return_value,
+ extra_headers=self.test_default_headers),
+ self.post.call_args)
+
+ def test_build_report_for_health_document(self):
+ health_document = self._get_report_ready_health_document()
+ reporter = azure_helper.GoalStateHealthReporter(
+ azure_helper.GoalState(mock.MagicMock(), mock.MagicMock()),
+ azure_helper.AzureEndpointHttpClient(mock.MagicMock()),
+ self.test_endpoint)
+ generated_health_document = reporter.build_report(
+ incarnation=self.default_parameters['incarnation'],
+ container_id=self.default_parameters['container_id'],
+ instance_id=self.default_parameters['instance_id'],
+ status=self.provisioning_success_status)
+ self.assertEqual(health_document, generated_health_document)
+ self.assertIn(
+ '<GoalStateIncarnation>{}</GoalStateIncarnation>'.format(
+ str(self.default_parameters['incarnation'])),
+ generated_health_document)
+ self.assertIn(
+ ''.join([
+ '<ContainerId>',
+ self.default_parameters['container_id'],
+ '</ContainerId>']),
+ generated_health_document)
+ self.assertIn(
+ ''.join([
+ '<InstanceId>',
+ self.default_parameters['instance_id'],
+ '</InstanceId>']),
+ generated_health_document)
+ self.assertIn(
+ ''.join([
+ '<State>',
+ self.provisioning_success_status,
+ '</State>']),
+ generated_health_document
+ )
+ self.assertNotIn('<Details>', generated_health_document)
+ self.assertNotIn('<SubStatus>', generated_health_document)
+ self.assertNotIn('<Description>', generated_health_document)
+
+ def test_send_ready_signal_calls_build_report(self):
+ with mock.patch.object(
+ azure_helper.GoalStateHealthReporter, 'build_report'
+ ) as m_build_report:
+ reporter = azure_helper.GoalStateHealthReporter(
+ azure_helper.GoalState(mock.MagicMock(), mock.MagicMock()),
+ azure_helper.AzureEndpointHttpClient(mock.MagicMock()),
+ self.test_endpoint)
+ reporter.send_ready_signal()
+
+ self.assertEqual(1, m_build_report.call_count)
+ self.assertEqual(
+ mock.call(
+ incarnation=self.default_parameters['incarnation'],
+ container_id=self.default_parameters['container_id'],
+ instance_id=self.default_parameters['instance_id'],
+ status=self.provisioning_success_status),
+ m_build_report.call_args)
+
+
class TestWALinuxAgentShim(CiTestCase):
def setUp(self):
@@ -383,14 +602,21 @@ class TestWALinuxAgentShim(CiTestCase):
patches.enter_context(
mock.patch.object(azure_helper.time, 'sleep', mock.MagicMock()))
- def test_http_client_uses_certificate(self):
+ self.test_incarnation = 'TestIncarnation'
+ self.test_container_id = 'TestContainerId'
+ self.test_instance_id = 'TestInstanceId'
+ self.GoalState.return_value.incarnation = self.test_incarnation
+ self.GoalState.return_value.container_id = self.test_container_id
+ self.GoalState.return_value.instance_id = self.test_instance_id
+
+ def test_azure_endpoint_client_uses_certificate_during_report_ready(self):
shim = wa_shim()
shim.register_with_azure_and_fetch_data()
self.assertEqual(
[mock.call(self.OpenSSLManager.return_value.certificate)],
self.AzureEndpointHttpClient.call_args_list)
- def test_correct_url_used_for_goalstate(self):
+ def test_correct_url_used_for_goalstate_during_report_ready(self):
self.find_endpoint.return_value = 'test_endpoint'
shim = wa_shim()
shim.register_with_azure_and_fetch_data()
@@ -404,11 +630,10 @@ class TestWALinuxAgentShim(CiTestCase):
self.GoalState.call_args_list)
def test_certificates_used_to_determine_public_keys(self):
+ # if register_with_azure_and_fetch_data() isn't passed some info about
+ # the user's public keys, there's no point in even trying to parse the
+ # certificates
shim = wa_shim()
- """if register_with_azure_and_fetch_data() isn't passed some info about
- the user's public keys, there's no point in even trying to parse
- the certificates
- """
mypk = [{'fingerprint': 'fp1', 'path': 'path1'},
{'fingerprint': 'fp3', 'path': 'path3', 'value': ''}]
certs = {'fp1': 'expected-key',
@@ -439,43 +664,67 @@ class TestWALinuxAgentShim(CiTestCase):
expected_url = 'http://test_endpoint/machine?comp=health'
self.assertEqual(
[mock.call(expected_url, data=mock.ANY, extra_headers=mock.ANY)],
- self.AzureEndpointHttpClient.return_value.post.call_args_list)
+ self.AzureEndpointHttpClient.return_value.post
+ .call_args_list)
def test_goal_state_values_used_for_report_ready(self):
- self.GoalState.return_value.incarnation = 'TestIncarnation'
- self.GoalState.return_value.container_id = 'TestContainerId'
- self.GoalState.return_value.instance_id = 'TestInstanceId'
shim = wa_shim()
shim.register_with_azure_and_fetch_data()
posted_document = (
- self.AzureEndpointHttpClient.return_value.post.call_args[1]['data']
+ self.AzureEndpointHttpClient.return_value.post
+ .call_args[1]['data']
)
- self.assertIn('TestIncarnation', posted_document)
- self.assertIn('TestContainerId', posted_document)
- self.assertIn('TestInstanceId', posted_document)
+ self.assertIn(self.test_incarnation, posted_document)
+ self.assertIn(self.test_container_id, posted_document)
+ self.assertIn(self.test_instance_id, posted_document)
+
+ def test_xml_elems_in_report_ready(self):
+ shim = wa_shim()
+ shim.register_with_azure_and_fetch_data()
+ health_document = HEALTH_REPORT_XML_TEMPLATE.format(
+ incarnation=self.test_incarnation,
+ container_id=self.test_container_id,
+ instance_id=self.test_instance_id,
+ health_status='Ready',
+ health_detail_subsection='')
+ posted_document = (
+ self.AzureEndpointHttpClient.return_value.post
+ .call_args[1]['data'])
+ self.assertEqual(health_document, posted_document)
def test_clean_up_can_be_called_at_any_time(self):
shim = wa_shim()
shim.clean_up()
- def test_clean_up_will_clean_up_openssl_manager_if_instantiated(self):
+ def test_clean_up_after_report_ready(self):
shim = wa_shim()
shim.register_with_azure_and_fetch_data()
shim.clean_up()
self.assertEqual(
1, self.OpenSSLManager.return_value.clean_up.call_count)
- def test_failure_to_fetch_goalstate_bubbles_up(self):
- class SentinelException(Exception):
- pass
- self.AzureEndpointHttpClient.return_value.get.side_effect = (
- SentinelException)
+ def test_fetch_goalstate_during_report_ready_raises_exc_on_get_exc(self):
+ self.AzureEndpointHttpClient.return_value.get \
+ .side_effect = (SentinelException)
shim = wa_shim()
self.assertRaises(SentinelException,
shim.register_with_azure_and_fetch_data)
+ def test_fetch_goalstate_during_report_ready_raises_exc_on_parse_exc(self):
+ self.GoalState.side_effect = SentinelException
+ shim = wa_shim()
+ self.assertRaises(SentinelException,
+ shim.register_with_azure_and_fetch_data)
-class TestGetMetadataFromFabric(CiTestCase):
+ def test_failure_to_send_report_ready_health_doc_bubbles_up(self):
+ self.AzureEndpointHttpClient.return_value.post \
+ .side_effect = SentinelException
+ shim = wa_shim()
+ self.assertRaises(SentinelException,
+ shim.register_with_azure_and_fetch_data)
+
+
+class TestGetMetadataGoalStateXMLAndReportReadyToFabric(CiTestCase):
@mock.patch.object(azure_helper, 'WALinuxAgentShim')
def test_data_from_shim_returned(self, shim):
@@ -491,14 +740,39 @@ class TestGetMetadataFromFabric(CiTestCase):
@mock.patch.object(azure_helper, 'WALinuxAgentShim')
def test_failure_in_registration_calls_clean_up(self, shim):
- class SentinelException(Exception):
- pass
shim.return_value.register_with_azure_and_fetch_data.side_effect = (
SentinelException)
self.assertRaises(SentinelException,
azure_helper.get_metadata_from_fabric)
self.assertEqual(1, shim.return_value.clean_up.call_count)
+ @mock.patch.object(azure_helper, 'WALinuxAgentShim')
+ def test_calls_shim_register_with_azure_and_fetch_data(self, shim):
+ m_pubkey_info = mock.MagicMock()
+ azure_helper.get_metadata_from_fabric(pubkey_info=m_pubkey_info)
+ self.assertEqual(
+ 1,
+ shim.return_value
+ .register_with_azure_and_fetch_data.call_count)
+ self.assertEqual(
+ mock.call(pubkey_info=m_pubkey_info),
+ shim.return_value
+ .register_with_azure_and_fetch_data.call_args)
+
+ @mock.patch.object(azure_helper, 'WALinuxAgentShim')
+ def test_instantiates_shim_with_kwargs(self, shim):
+ m_fallback_lease_file = mock.MagicMock()
+ m_dhcp_options = mock.MagicMock()
+ azure_helper.get_metadata_from_fabric(
+ fallback_lease_file=m_fallback_lease_file,
+ dhcp_opts=m_dhcp_options)
+ self.assertEqual(1, shim.call_count)
+ self.assertEqual(
+ mock.call(
+ fallback_lease_file=m_fallback_lease_file,
+ dhcp_options=m_dhcp_options),
+ shim.call_args)
+
class TestExtractIpAddressFromNetworkd(CiTestCase):
diff --git a/tests/unittests/test_datasource/test_cloudsigma.py b/tests/unittests/test_datasource/test_cloudsigma.py
index d62d542b..7aa3b1d1 100644
--- a/tests/unittests/test_datasource/test_cloudsigma.py
+++ b/tests/unittests/test_datasource/test_cloudsigma.py
@@ -3,6 +3,7 @@
import copy
from cloudinit.cs_utils import Cepko
+from cloudinit import distros
from cloudinit import helpers
from cloudinit import sources
from cloudinit.sources import DataSourceCloudSigma
@@ -47,8 +48,11 @@ class DataSourceCloudSigmaTest(test_helpers.CiTestCase):
self.paths = helpers.Paths({'run_dir': self.tmp_dir()})
self.add_patch(DS_PATH + '.is_running_in_cloudsigma',
"m_is_container", return_value=True)
+
+ distro_cls = distros.fetch("ubuntu")
+ distro = distro_cls("ubuntu", cfg={}, paths=self.paths)
self.datasource = DataSourceCloudSigma.DataSourceCloudSigma(
- "", "", paths=self.paths)
+ sys_cfg={}, distro=distro, paths=self.paths)
self.datasource.cepko = CepkoMock(SERVER_CONTEXT)
def test_get_hostname(self):
diff --git a/tests/unittests/test_datasource/test_cloudstack.py b/tests/unittests/test_datasource/test_cloudstack.py
index 83c2f753..e68168f2 100644
--- a/tests/unittests/test_datasource/test_cloudstack.py
+++ b/tests/unittests/test_datasource/test_cloudstack.py
@@ -41,7 +41,7 @@ class TestCloudStackPasswordFetching(CiTestCase):
def _set_password_server_response(self, response_string):
subp = mock.MagicMock(return_value=(response_string, ''))
self.patches.enter_context(
- mock.patch('cloudinit.sources.DataSourceCloudStack.util.subp',
+ mock.patch('cloudinit.sources.DataSourceCloudStack.subp.subp',
subp))
return subp
diff --git a/tests/unittests/test_datasource/test_ec2.py b/tests/unittests/test_datasource/test_ec2.py
index 2a96122f..a93f2195 100644
--- a/tests/unittests/test_datasource/test_ec2.py
+++ b/tests/unittests/test_datasource/test_ec2.py
@@ -3,6 +3,7 @@
import copy
import httpretty
import json
+import requests
from unittest import mock
from cloudinit import helpers
@@ -37,6 +38,8 @@ DYNAMIC_METADATA = {
# python3 -c 'import json
# from cloudinit.ec2_utils import get_instance_metadata as gm
# print(json.dumps(gm("2016-09-02"), indent=1, sort_keys=True))'
+# Note that the MAC addresses have been modified to sort in the opposite order
+# to the device-number attribute, to test LP: #1876312
DEFAULT_METADATA = {
"ami-id": "ami-8b92b4ee",
"ami-launch-index": "0",
@@ -76,7 +79,7 @@ DEFAULT_METADATA = {
"vpc-ipv4-cidr-blocks": "172.31.0.0/16",
"vpc-ipv6-cidr-blocks": "2600:1f16:aeb:b200::/56"
},
- "06:17:04:d7:26:0A": {
+ "06:17:04:d7:26:08": {
"device-number": "1", # Only IPv4 local config
"interface-id": "eni-e44ef49f",
"ipv4-associations": {"": "172.3.3.16"},
@@ -84,7 +87,7 @@ DEFAULT_METADATA = {
"local-hostname": ("ip-172-3-3-16.us-east-2."
"compute.internal"),
"local-ipv4s": "172.3.3.16",
- "mac": "06:17:04:d7:26:0A",
+ "mac": "06:17:04:d7:26:08",
"owner-id": "950047163771",
"public-hostname": ("ec2-172-3-3-16.us-east-2."
"compute.amazonaws.com"),
@@ -112,6 +115,122 @@ DEFAULT_METADATA = {
"services": {"domain": "amazonaws.com", "partition": "aws"},
}
+# collected from api version 2018-09-24/ with
+# python3 -c 'import json
+# from cloudinit.ec2_utils import get_instance_metadata as gm
+# print(json.dumps(gm("2018-09-24"), indent=1, sort_keys=True))'
+
+NIC1_MD_IPV4_IPV6_MULTI_IP = {
+ "device-number": "0",
+ "interface-id": "eni-0d6335689899ce9cc",
+ "ipv4-associations": {
+ "18.218.219.181": "172.31.44.13"
+ },
+ "ipv6s": [
+ "2600:1f16:292:100:c187:593c:4349:136",
+ "2600:1f16:292:100:f153:12a3:c37c:11f9",
+ "2600:1f16:292:100:f152:2222:3333:4444"
+ ],
+ "local-hostname": ("ip-172-31-44-13.us-east-2."
+ "compute.internal"),
+ "local-ipv4s": [
+ "172.31.44.13",
+ "172.31.45.70"
+ ],
+ "mac": "0a:07:84:3d:6e:38",
+ "owner-id": "329910648901",
+ "public-hostname": ("ec2-18-218-219-181.us-east-2."
+ "compute.amazonaws.com"),
+ "public-ipv4s": "18.218.219.181",
+ "security-group-ids": "sg-0c387755222ba8d2e",
+ "security-groups": "launch-wizard-4",
+ "subnet-id": "subnet-9d7ba0d1",
+ "subnet-ipv4-cidr-block": "172.31.32.0/20",
+ "subnet_ipv6_cidr_blocks": "2600:1f16:292:100::/64",
+ "vpc-id": "vpc-a07f62c8",
+ "vpc-ipv4-cidr-block": "172.31.0.0/16",
+ "vpc-ipv4-cidr-blocks": "172.31.0.0/16",
+ "vpc_ipv6_cidr_blocks": "2600:1f16:292:100::/56"
+}
+
+NIC2_MD = {
+ "device-number": "1",
+ "interface-id": "eni-043cdce36ded5e79f",
+ "local-hostname": "ip-172-31-47-221.us-east-2.compute.internal",
+ "local-ipv4s": "172.31.47.221",
+ "mac": "0a:75:69:92:e2:16",
+ "owner-id": "329910648901",
+ "security-group-ids": "sg-0d68fef37d8cc9b77",
+ "security-groups": "launch-wizard-17",
+ "subnet-id": "subnet-9d7ba0d1",
+ "subnet-ipv4-cidr-block": "172.31.32.0/20",
+ "vpc-id": "vpc-a07f62c8",
+ "vpc-ipv4-cidr-block": "172.31.0.0/16",
+ "vpc-ipv4-cidr-blocks": "172.31.0.0/16"
+}
+
+SECONDARY_IP_METADATA_2018_09_24 = {
+ "ami-id": "ami-0986c2ac728528ac2",
+ "ami-launch-index": "0",
+ "ami-manifest-path": "(unknown)",
+ "block-device-mapping": {
+ "ami": "/dev/sda1",
+ "root": "/dev/sda1"
+ },
+ "events": {
+ "maintenance": {
+ "history": "[]",
+ "scheduled": "[]"
+ }
+ },
+ "hostname": "ip-172-31-44-13.us-east-2.compute.internal",
+ "identity-credentials": {
+ "ec2": {
+ "info": {
+ "AccountId": "329910648901",
+ "Code": "Success",
+ "LastUpdated": "2019-07-06T14:22:56Z"
+ }
+ }
+ },
+ "instance-action": "none",
+ "instance-id": "i-069e01e8cc43732f8",
+ "instance-type": "t2.micro",
+ "local-hostname": "ip-172-31-44-13.us-east-2.compute.internal",
+ "local-ipv4": "172.31.44.13",
+ "mac": "0a:07:84:3d:6e:38",
+ "metrics": {
+ "vhostmd": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ },
+ "network": {
+ "interfaces": {
+ "macs": {
+ "0a:07:84:3d:6e:38": NIC1_MD_IPV4_IPV6_MULTI_IP,
+ }
+ }
+ },
+ "placement": {
+ "availability-zone": "us-east-2c"
+ },
+ "profile": "default-hvm",
+ "public-hostname": (
+ "ec2-18-218-219-181.us-east-2.compute.amazonaws.com"),
+ "public-ipv4": "18.218.219.181",
+ "public-keys": {
+ "yourkeyname,e": [
+ "ssh-rsa AAAAW...DZ yourkeyname"
+ ]
+ },
+ "reservation-id": "r-09b4917135cdd33be",
+ "security-groups": "launch-wizard-4",
+ "services": {
+ "domain": "amazonaws.com",
+ "partition": "aws"
+ }
+}
+
+M_PATH_NET = 'cloudinit.sources.DataSourceEc2.net.'
+
def _register_ssh_keys(rfunc, base_url, keys_data):
"""handle ssh key inconsistencies.
@@ -200,6 +319,7 @@ def register_mock_metaserver(base_url, data):
class TestEc2(test_helpers.HttprettyTestCase):
with_logs = True
+ maxDiff = None
valid_platform_data = {
'uuid': 'ec212f79-87d1-2f1d-588f-d86dc0fd5412',
@@ -265,30 +385,23 @@ class TestEc2(test_helpers.HttprettyTestCase):
register_mock_metaserver(instance_id_url, None)
return ds
- def test_network_config_property_returns_version_1_network_data(self):
- """network_config property returns network version 1 for metadata.
-
- Only one device is configured even when multiple exist in metadata.
- """
+ def test_network_config_property_returns_version_2_network_data(self):
+ """network_config property returns network version 2 for metadata"""
ds = self._setup_ds(
platform_data=self.valid_platform_data,
sys_cfg={'datasource': {'Ec2': {'strict_id': True}}},
md={'md': DEFAULT_METADATA})
- find_fallback_path = (
- 'cloudinit.sources.DataSourceEc2.net.find_fallback_nic')
+ find_fallback_path = M_PATH_NET + 'find_fallback_nic'
with mock.patch(find_fallback_path) as m_find_fallback:
m_find_fallback.return_value = 'eth9'
ds.get_data()
mac1 = '06:17:04:d7:26:09' # Defined in DEFAULT_METADATA
- expected = {'version': 1, 'config': [
- {'mac_address': '06:17:04:d7:26:09', 'name': 'eth9',
- 'subnets': [{'type': 'dhcp4'}, {'type': 'dhcp6'}],
- 'type': 'physical'}]}
- patch_path = (
- 'cloudinit.sources.DataSourceEc2.net.get_interfaces_by_mac')
- get_interface_mac_path = (
- 'cloudinit.sources.DataSourceEc2.net.get_interface_mac')
+ expected = {'version': 2, 'ethernets': {'eth9': {
+ 'match': {'macaddress': '06:17:04:d7:26:09'}, 'set-name': 'eth9',
+ 'dhcp4': True, 'dhcp6': True}}}
+ patch_path = M_PATH_NET + 'get_interfaces_by_mac'
+ get_interface_mac_path = M_PATH_NET + 'get_interface_mac'
with mock.patch(patch_path) as m_get_interfaces_by_mac:
with mock.patch(find_fallback_path) as m_find_fallback:
with mock.patch(get_interface_mac_path) as m_get_mac:
@@ -297,30 +410,59 @@ class TestEc2(test_helpers.HttprettyTestCase):
m_get_mac.return_value = mac1
self.assertEqual(expected, ds.network_config)
- def test_network_config_property_set_dhcp4_on_private_ipv4(self):
- """network_config property configures dhcp4 on private ipv4 nics.
+ def test_network_config_property_set_dhcp4(self):
+ """network_config property configures dhcp4 on nics with local-ipv4s.
- Only one device is configured even when multiple exist in metadata.
+ Only one device is configured based on get_interfaces_by_mac even when
+ multiple MACs exist in metadata.
"""
ds = self._setup_ds(
platform_data=self.valid_platform_data,
sys_cfg={'datasource': {'Ec2': {'strict_id': True}}},
md={'md': DEFAULT_METADATA})
- find_fallback_path = (
- 'cloudinit.sources.DataSourceEc2.net.find_fallback_nic')
+ find_fallback_path = M_PATH_NET + 'find_fallback_nic'
with mock.patch(find_fallback_path) as m_find_fallback:
m_find_fallback.return_value = 'eth9'
ds.get_data()
- mac1 = '06:17:04:d7:26:0A' # IPv4 only in DEFAULT_METADATA
- expected = {'version': 1, 'config': [
- {'mac_address': '06:17:04:d7:26:0A', 'name': 'eth9',
- 'subnets': [{'type': 'dhcp4'}],
- 'type': 'physical'}]}
- patch_path = (
- 'cloudinit.sources.DataSourceEc2.net.get_interfaces_by_mac')
- get_interface_mac_path = (
- 'cloudinit.sources.DataSourceEc2.net.get_interface_mac')
+ mac1 = '06:17:04:d7:26:08' # IPv4 only in DEFAULT_METADATA
+ expected = {'version': 2, 'ethernets': {'eth9': {
+ 'match': {'macaddress': mac1.lower()}, 'set-name': 'eth9',
+ 'dhcp4': True, 'dhcp6': False}}}
+ patch_path = M_PATH_NET + 'get_interfaces_by_mac'
+ get_interface_mac_path = M_PATH_NET + 'get_interface_mac'
+ with mock.patch(patch_path) as m_get_interfaces_by_mac:
+ with mock.patch(find_fallback_path) as m_find_fallback:
+ with mock.patch(get_interface_mac_path) as m_get_mac:
+ m_get_interfaces_by_mac.return_value = {mac1: 'eth9'}
+ m_find_fallback.return_value = 'eth9'
+ m_get_mac.return_value = mac1
+ self.assertEqual(expected, ds.network_config)
+
+ def test_network_config_property_secondary_private_ips(self):
+ """network_config property configures any secondary ipv4 addresses.
+
+ Only one device is configured based on get_interfaces_by_mac even when
+ multiple MACs exist in metadata.
+ """
+ ds = self._setup_ds(
+ platform_data=self.valid_platform_data,
+ sys_cfg={'datasource': {'Ec2': {'strict_id': True}}},
+ md={'md': SECONDARY_IP_METADATA_2018_09_24})
+ find_fallback_path = M_PATH_NET + 'find_fallback_nic'
+ with mock.patch(find_fallback_path) as m_find_fallback:
+ m_find_fallback.return_value = 'eth9'
+ ds.get_data()
+
+ mac1 = '0a:07:84:3d:6e:38' # 1 secondary IPv4 and 2 secondary IPv6
+ expected = {'version': 2, 'ethernets': {'eth9': {
+ 'match': {'macaddress': mac1}, 'set-name': 'eth9',
+ 'addresses': ['172.31.45.70/20',
+ '2600:1f16:292:100:f152:2222:3333:4444/128',
+ '2600:1f16:292:100:f153:12a3:c37c:11f9/128'],
+ 'dhcp4': True, 'dhcp6': True}}}
+ patch_path = M_PATH_NET + 'get_interfaces_by_mac'
+ get_interface_mac_path = M_PATH_NET + 'get_interface_mac'
with mock.patch(patch_path) as m_get_interfaces_by_mac:
with mock.patch(find_fallback_path) as m_find_fallback:
with mock.patch(get_interface_mac_path) as m_get_mac:
@@ -356,21 +498,18 @@ class TestEc2(test_helpers.HttprettyTestCase):
register_mock_metaserver(
'http://169.254.169.254/2009-04-04/meta-data/', DEFAULT_METADATA)
mac1 = '06:17:04:d7:26:09' # Defined in DEFAULT_METADATA
- get_interface_mac_path = (
- 'cloudinit.sources.DataSourceEc2.net.get_interface_mac')
+ get_interface_mac_path = M_PATH_NET + 'get_interfaces_by_mac'
ds.fallback_nic = 'eth9'
- with mock.patch(get_interface_mac_path) as m_get_interface_mac:
- m_get_interface_mac.return_value = mac1
+ with mock.patch(get_interface_mac_path) as m_get_interfaces_by_mac:
+ m_get_interfaces_by_mac.return_value = {mac1: 'eth9'}
nc = ds.network_config # Will re-crawl network metadata
self.assertIsNotNone(nc)
self.assertIn(
'Refreshing stale metadata from prior to upgrade',
self.logs.getvalue())
- expected = {'version': 1, 'config': [
- {'mac_address': '06:17:04:d7:26:09',
- 'name': 'eth9',
- 'subnets': [{'type': 'dhcp4'}, {'type': 'dhcp6'}],
- 'type': 'physical'}]}
+ expected = {'version': 2, 'ethernets': {'eth9': {
+ 'match': {'macaddress': mac1}, 'set-name': 'eth9',
+ 'dhcp4': True, 'dhcp6': True}}}
self.assertEqual(expected, ds.network_config)
def test_ec2_get_instance_id_refreshes_identity_on_upgrade(self):
@@ -429,6 +568,55 @@ class TestEc2(test_helpers.HttprettyTestCase):
self.assertTrue(ds.get_data())
self.assertFalse(ds.is_classic_instance())
+ def test_aws_inaccessible_imds_service_fails_with_retries(self):
+ """Inaccessibility of http://169.254.169.254 are retried."""
+ ds = self._setup_ds(
+ platform_data=self.valid_platform_data,
+ sys_cfg={'datasource': {'Ec2': {'strict_id': False}}},
+ md=None)
+
+ conn_error = requests.exceptions.ConnectionError(
+ '[Errno 113] no route to host'
+ )
+
+ mock_success = mock.MagicMock(contents=b'fakesuccess')
+ mock_success.ok.return_value = True
+
+ with mock.patch('cloudinit.url_helper.readurl') as m_readurl:
+ m_readurl.side_effect = (conn_error, conn_error, mock_success)
+ with mock.patch('cloudinit.url_helper.time.sleep'):
+ self.assertTrue(ds.wait_for_metadata_service())
+
+ # Just one /latest/api/token request
+ self.assertEqual(3, len(m_readurl.call_args_list))
+ for readurl_call in m_readurl.call_args_list:
+ self.assertIn('latest/api/token', readurl_call[0][0])
+
+ def test_aws_token_403_fails_without_retries(self):
+ """Verify that 403s fetching AWS tokens are not retried."""
+ ds = self._setup_ds(
+ platform_data=self.valid_platform_data,
+ sys_cfg={'datasource': {'Ec2': {'strict_id': False}}},
+ md=None)
+ token_url = self.data_url('latest', data_item='api/token')
+ httpretty.register_uri(httpretty.PUT, token_url, body={}, status=403)
+ self.assertFalse(ds.get_data())
+ # Just one /latest/api/token request
+ logs = self.logs.getvalue()
+ failed_put_log = '"PUT /latest/api/token HTTP/1.1" 403 0'
+ expected_logs = [
+ 'WARNING: Ec2 IMDS endpoint returned a 403 error. HTTP endpoint is'
+ ' disabled. Aborting.',
+ "WARNING: IMDS's HTTP endpoint is probably disabled",
+ failed_put_log
+ ]
+ for log in expected_logs:
+ self.assertIn(log, logs)
+ self.assertEqual(
+ 1,
+ len([line for line in logs.splitlines() if failed_put_log in line])
+ )
+
def test_aws_token_redacted(self):
"""Verify that aws tokens are redacted when logged."""
ds = self._setup_ds(
@@ -443,7 +631,7 @@ class TestEc2(test_helpers.HttprettyTestCase):
logs_with_redacted = [log for log in all_logs if REDACT_TOK in log]
logs_with_token = [log for log in all_logs if 'API-TOKEN' in log]
self.assertEqual(1, len(logs_with_redacted_ttl))
- self.assertEqual(79, len(logs_with_redacted))
+ self.assertEqual(81, len(logs_with_redacted))
self.assertEqual(0, len(logs_with_token))
@mock.patch('cloudinit.net.dhcp.maybe_perform_dhcp_discovery')
@@ -556,7 +744,7 @@ class TestEc2(test_helpers.HttprettyTestCase):
ret = ds.get_data()
self.assertTrue(ret)
- m_dhcp.assert_called_once_with('eth9')
+ m_dhcp.assert_called_once_with('eth9', None)
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',
@@ -564,23 +752,64 @@ class TestEc2(test_helpers.HttprettyTestCase):
self.assertIn('Crawl of metadata service took', self.logs.getvalue())
+class TestGetSecondaryAddresses(test_helpers.CiTestCase):
+
+ mac = '06:17:04:d7:26:ff'
+ with_logs = True
+
+ def test_md_with_no_secondary_addresses(self):
+ """Empty list is returned when nic metadata contains no secondary ip"""
+ self.assertEqual([], ec2.get_secondary_addresses(NIC2_MD, self.mac))
+
+ def test_md_with_secondary_v4_and_v6_addresses(self):
+ """All secondary addresses are returned from nic metadata"""
+ self.assertEqual(
+ ['172.31.45.70/20', '2600:1f16:292:100:f152:2222:3333:4444/128',
+ '2600:1f16:292:100:f153:12a3:c37c:11f9/128'],
+ ec2.get_secondary_addresses(NIC1_MD_IPV4_IPV6_MULTI_IP, self.mac))
+
+ def test_invalid_ipv4_ipv6_cidr_metadata_logged_with_defaults(self):
+ """Any invalid subnet-ipv(4|6)-cidr-block values use defaults"""
+ invalid_cidr_md = copy.deepcopy(NIC1_MD_IPV4_IPV6_MULTI_IP)
+ invalid_cidr_md['subnet-ipv4-cidr-block'] = "something-unexpected"
+ invalid_cidr_md['subnet-ipv6-cidr-block'] = "not/sure/what/this/is"
+ self.assertEqual(
+ ['172.31.45.70/24', '2600:1f16:292:100:f152:2222:3333:4444/128',
+ '2600:1f16:292:100:f153:12a3:c37c:11f9/128'],
+ ec2.get_secondary_addresses(invalid_cidr_md, self.mac))
+ expected_logs = [
+ "WARNING: Could not parse subnet-ipv4-cidr-block"
+ " something-unexpected for mac 06:17:04:d7:26:ff."
+ " ipv4 network config prefix defaults to /24",
+ "WARNING: Could not parse subnet-ipv6-cidr-block"
+ " not/sure/what/this/is for mac 06:17:04:d7:26:ff."
+ " ipv6 network config prefix defaults to /128"
+ ]
+ logs = self.logs.getvalue()
+ for log in expected_logs:
+ self.assertIn(log, logs)
+
+
class TestConvertEc2MetadataNetworkConfig(test_helpers.CiTestCase):
def setUp(self):
super(TestConvertEc2MetadataNetworkConfig, self).setUp()
self.mac1 = '06:17:04:d7:26:09'
+ interface_dict = copy.deepcopy(
+ DEFAULT_METADATA['network']['interfaces']['macs'][self.mac1])
+ # These tests are written assuming the base interface doesn't have IPv6
+ interface_dict.pop('ipv6s')
self.network_metadata = {
- 'interfaces': {'macs': {
- self.mac1: {'public-ipv4s': '172.31.2.16'}}}}
+ 'interfaces': {'macs': {self.mac1: interface_dict}}}
def test_convert_ec2_metadata_network_config_skips_absent_macs(self):
"""Any mac absent from metadata is skipped by network config."""
macs_to_nics = {self.mac1: 'eth9', 'DE:AD:BE:EF:FF:FF': 'vitualnic2'}
# DE:AD:BE:EF:FF:FF represented by OS but not in metadata
- expected = {'version': 1, 'config': [
- {'mac_address': self.mac1, 'type': 'physical',
- 'name': 'eth9', 'subnets': [{'type': 'dhcp4'}]}]}
+ expected = {'version': 2, 'ethernets': {'eth9': {
+ 'match': {'macaddress': self.mac1}, 'set-name': 'eth9',
+ 'dhcp4': True, 'dhcp6': False}}}
self.assertEqual(
expected,
ec2.convert_ec2_metadata_network_config(
@@ -594,15 +823,15 @@ class TestConvertEc2MetadataNetworkConfig(test_helpers.CiTestCase):
network_metadata_ipv6['interfaces']['macs'][self.mac1])
nic1_metadata['ipv6s'] = '2620:0:1009:fd00:e442:c88d:c04d:dc85/64'
nic1_metadata.pop('public-ipv4s')
- expected = {'version': 1, 'config': [
- {'mac_address': self.mac1, 'type': 'physical',
- 'name': 'eth9', 'subnets': [{'type': 'dhcp6'}]}]}
+ expected = {'version': 2, 'ethernets': {'eth9': {
+ 'match': {'macaddress': self.mac1}, 'set-name': 'eth9',
+ 'dhcp4': True, 'dhcp6': True}}}
self.assertEqual(
expected,
ec2.convert_ec2_metadata_network_config(
network_metadata_ipv6, macs_to_nics))
- def test_convert_ec2_metadata_network_config_handles_local_dhcp4(self):
+ def test_convert_ec2_metadata_network_config_local_only_dhcp4(self):
"""Config dhcp4 when there are no public addresses in public-ipv4s."""
macs_to_nics = {self.mac1: 'eth9'}
network_metadata_ipv6 = copy.deepcopy(self.network_metadata)
@@ -610,9 +839,9 @@ class TestConvertEc2MetadataNetworkConfig(test_helpers.CiTestCase):
network_metadata_ipv6['interfaces']['macs'][self.mac1])
nic1_metadata['local-ipv4s'] = '172.3.3.15'
nic1_metadata.pop('public-ipv4s')
- expected = {'version': 1, 'config': [
- {'mac_address': self.mac1, 'type': 'physical',
- 'name': 'eth9', 'subnets': [{'type': 'dhcp4'}]}]}
+ expected = {'version': 2, 'ethernets': {'eth9': {
+ 'match': {'macaddress': self.mac1}, 'set-name': 'eth9',
+ 'dhcp4': True, 'dhcp6': False}}}
self.assertEqual(
expected,
ec2.convert_ec2_metadata_network_config(
@@ -627,16 +856,16 @@ class TestConvertEc2MetadataNetworkConfig(test_helpers.CiTestCase):
nic1_metadata['public-ipv4s'] = ''
# When no ipv4 or ipv6 content but fallback_nic set, set dhcp4 config.
- expected = {'version': 1, 'config': [
- {'mac_address': self.mac1, 'type': 'physical',
- 'name': 'eth9', 'subnets': [{'type': 'dhcp4'}]}]}
+ expected = {'version': 2, 'ethernets': {'eth9': {
+ 'match': {'macaddress': self.mac1}, 'set-name': 'eth9',
+ 'dhcp4': True, 'dhcp6': False}}}
self.assertEqual(
expected,
ec2.convert_ec2_metadata_network_config(
network_metadata_ipv6, macs_to_nics, fallback_nic='eth9'))
def test_convert_ec2_metadata_network_config_handles_local_v4_and_v6(self):
- """When dhcp6 is public and dhcp4 is set to local enable both."""
+ """When ipv6s and local-ipv4s are non-empty, enable dhcp6 and dhcp4."""
macs_to_nics = {self.mac1: 'eth9'}
network_metadata_both = copy.deepcopy(self.network_metadata)
nic1_metadata = (
@@ -644,10 +873,35 @@ class TestConvertEc2MetadataNetworkConfig(test_helpers.CiTestCase):
nic1_metadata['ipv6s'] = '2620:0:1009:fd00:e442:c88d:c04d:dc85/64'
nic1_metadata.pop('public-ipv4s')
nic1_metadata['local-ipv4s'] = '10.0.0.42' # Local ipv4 only on vpc
- expected = {'version': 1, 'config': [
- {'mac_address': self.mac1, 'type': 'physical',
- 'name': 'eth9',
- 'subnets': [{'type': 'dhcp4'}, {'type': 'dhcp6'}]}]}
+ expected = {'version': 2, 'ethernets': {'eth9': {
+ 'match': {'macaddress': self.mac1}, 'set-name': 'eth9',
+ 'dhcp4': True, 'dhcp6': True}}}
+ self.assertEqual(
+ expected,
+ ec2.convert_ec2_metadata_network_config(
+ network_metadata_both, macs_to_nics))
+
+ def test_convert_ec2_metadata_network_config_handles_multiple_nics(self):
+ """DHCP route-metric increases on secondary NICs for IPv4 and IPv6."""
+ mac2 = '06:17:04:d7:26:08'
+ macs_to_nics = {self.mac1: 'eth9', mac2: 'eth10'}
+ network_metadata_both = copy.deepcopy(self.network_metadata)
+ # Add 2nd nic info
+ network_metadata_both['interfaces']['macs'][mac2] = NIC2_MD
+ nic1_metadata = (
+ network_metadata_both['interfaces']['macs'][self.mac1])
+ nic1_metadata['ipv6s'] = '2620:0:1009:fd00:e442:c88d:c04d:dc85/64'
+ nic1_metadata.pop('public-ipv4s') # No public-ipv4 IPs in cfg
+ nic1_metadata['local-ipv4s'] = '10.0.0.42' # Local ipv4 only on vpc
+ expected = {'version': 2, 'ethernets': {
+ 'eth9': {
+ 'match': {'macaddress': self.mac1}, 'set-name': 'eth9',
+ 'dhcp4': True, 'dhcp4-overrides': {'route-metric': 100},
+ 'dhcp6': True, 'dhcp6-overrides': {'route-metric': 100}},
+ 'eth10': {
+ 'match': {'macaddress': mac2}, 'set-name': 'eth10',
+ 'dhcp4': True, 'dhcp4-overrides': {'route-metric': 200},
+ 'dhcp6': False}}}
self.assertEqual(
expected,
ec2.convert_ec2_metadata_network_config(
@@ -660,10 +914,9 @@ class TestConvertEc2MetadataNetworkConfig(test_helpers.CiTestCase):
nic1_metadata = (
network_metadata_both['interfaces']['macs'][self.mac1])
nic1_metadata['ipv6s'] = '2620:0:1009:fd00:e442:c88d:c04d:dc85/64'
- expected = {'version': 1, 'config': [
- {'mac_address': self.mac1, 'type': 'physical',
- 'name': 'eth9',
- 'subnets': [{'type': 'dhcp4'}, {'type': 'dhcp6'}]}]}
+ expected = {'version': 2, 'ethernets': {'eth9': {
+ 'match': {'macaddress': self.mac1}, 'set-name': 'eth9',
+ 'dhcp4': True, 'dhcp6': True}}}
self.assertEqual(
expected,
ec2.convert_ec2_metadata_network_config(
@@ -671,12 +924,10 @@ class TestConvertEc2MetadataNetworkConfig(test_helpers.CiTestCase):
def test_convert_ec2_metadata_gets_macs_from_get_interfaces_by_mac(self):
"""Convert Ec2 Metadata calls get_interfaces_by_mac by default."""
- expected = {'version': 1, 'config': [
- {'mac_address': self.mac1, 'type': 'physical',
- 'name': 'eth9',
- 'subnets': [{'type': 'dhcp4'}]}]}
- patch_path = (
- 'cloudinit.sources.DataSourceEc2.net.get_interfaces_by_mac')
+ expected = {'version': 2, 'ethernets': {'eth9': {
+ 'match': {'macaddress': self.mac1},
+ 'set-name': 'eth9', 'dhcp4': True, 'dhcp6': False}}}
+ patch_path = M_PATH_NET + 'get_interfaces_by_mac'
with mock.patch(patch_path) as m_get_interfaces_by_mac:
m_get_interfaces_by_mac.return_value = {self.mac1: 'eth9'}
self.assertEqual(
diff --git a/tests/unittests/test_datasource/test_gce.py b/tests/unittests/test_datasource/test_gce.py
index 4afbccff..01f4cbd1 100644
--- a/tests/unittests/test_datasource/test_gce.py
+++ b/tests/unittests/test_datasource/test_gce.py
@@ -114,7 +114,8 @@ class TestDataSourceGCE(test_helpers.HttprettyTestCase):
self.assertTrue(success)
req_header = httpretty.last_request().headers
- self.assertDictContainsSubset(HEADERS, req_header)
+ for header_name, expected_value in HEADERS.items():
+ self.assertEqual(expected_value, req_header.get(header_name))
def test_metadata(self):
# UnicodeDecodeError if set to ds.userdata instead of userdata_raw
diff --git a/tests/unittests/test_datasource/test_hetzner.py b/tests/unittests/test_datasource/test_hetzner.py
index a9c12597..d0879545 100644
--- a/tests/unittests/test_datasource/test_hetzner.py
+++ b/tests/unittests/test_datasource/test_hetzner.py
@@ -5,10 +5,14 @@
# This file is part of cloud-init. See LICENSE file for license information.
from cloudinit.sources import DataSourceHetzner
+import cloudinit.sources.helpers.hetzner as hc_helper
from cloudinit import util, settings, helpers
from cloudinit.tests.helpers import mock, CiTestCase
+import base64
+import pytest
+
METADATA = util.load_yaml("""
hostname: cloudinit-test
instance-id: 123456
@@ -115,3 +119,22 @@ class TestDataSourceHetzner(CiTestCase):
# These are a white box attempt to ensure it did not search.
m_find_fallback.assert_not_called()
m_read_md.assert_not_called()
+
+
+class TestMaybeB64Decode:
+ """Test the maybe_b64decode helper function."""
+
+ @pytest.mark.parametrize("invalid_input", (str("not bytes"), int(4)))
+ def test_raises_error_on_non_bytes(self, invalid_input):
+ """maybe_b64decode should raise error if data is not bytes."""
+ with pytest.raises(TypeError):
+ hc_helper.maybe_b64decode(invalid_input)
+
+ @pytest.mark.parametrize("in_data,expected", [
+ # If data is not b64 encoded, then return value should be the same.
+ (b"this is my data", b"this is my data"),
+ # If data is b64 encoded, then return value should be decoded.
+ (base64.b64encode(b"data"), b"data"),
+ ])
+ def test_happy_path(self, in_data, expected):
+ assert expected == hc_helper.maybe_b64decode(in_data)
diff --git a/tests/unittests/test_datasource/test_ibmcloud.py b/tests/unittests/test_datasource/test_ibmcloud.py
index 0b54f585..9013ae9f 100644
--- a/tests/unittests/test_datasource/test_ibmcloud.py
+++ b/tests/unittests/test_datasource/test_ibmcloud.py
@@ -15,13 +15,6 @@ mock = test_helpers.mock
D_PATH = "cloudinit.sources.DataSourceIBMCloud."
-class TestIBMCloud(test_helpers.CiTestCase):
- """Test the datasource."""
- def setUp(self):
- super(TestIBMCloud, self).setUp()
- pass
-
-
@mock.patch(D_PATH + "_is_xen", return_value=True)
@mock.patch(D_PATH + "_is_ibm_provisioning")
@mock.patch(D_PATH + "util.blkid")
diff --git a/tests/unittests/test_datasource/test_maas.py b/tests/unittests/test_datasource/test_maas.py
index 2a81d3f5..41b6c27b 100644
--- a/tests/unittests/test_datasource/test_maas.py
+++ b/tests/unittests/test_datasource/test_maas.py
@@ -158,7 +158,6 @@ class TestMAASDataSource(CiTestCase):
@mock.patch("cloudinit.sources.DataSourceMAAS.url_helper.OauthUrlHelper")
class TestGetOauthHelper(CiTestCase):
- with_logs = True
base_cfg = {'consumer_key': 'FAKE_CONSUMER_KEY',
'token_key': 'FAKE_TOKEN_KEY',
'token_secret': 'FAKE_TOKEN_SECRET',
diff --git a/tests/unittests/test_datasource/test_nocloud.py b/tests/unittests/test_datasource/test_nocloud.py
index 18bea0b9..2e6b53ff 100644
--- a/tests/unittests/test_datasource/test_nocloud.py
+++ b/tests/unittests/test_datasource/test_nocloud.py
@@ -288,8 +288,23 @@ class TestNoCloudDataSource(CiTestCase):
self.mocks.enter_context(
mock.patch.object(util, 'is_FreeBSD', return_value=True))
+ def _mfind_devs_with_freebsd(
+ criteria=None, oformat='device',
+ tag=None, no_cache=False, path=None):
+ if not criteria:
+ return ["/dev/msdosfs/foo", "/dev/iso9660/foo"]
+ if criteria.startswith("LABEL="):
+ return ["/dev/msdosfs/foo", "/dev/iso9660/foo"]
+ elif criteria == "TYPE=vfat":
+ return ["/dev/msdosfs/foo"]
+ elif criteria == "TYPE=iso9660":
+ return ["/dev/iso9660/foo"]
+ return []
+
self.mocks.enter_context(
- mock.patch.object(os.path, 'exists', return_value=True))
+ mock.patch.object(
+ util, 'find_devs_with_freebsd',
+ side_effect=_mfind_devs_with_freebsd))
dsrc = dsNoCloud(sys_cfg=sys_cfg, distro=None, paths=self.paths)
ret = dsrc._get_devices('foo')
diff --git a/tests/unittests/test_datasource/test_opennebula.py b/tests/unittests/test_datasource/test_opennebula.py
index bb399f6d..9c6070a5 100644
--- a/tests/unittests/test_datasource/test_opennebula.py
+++ b/tests/unittests/test_datasource/test_opennebula.py
@@ -9,6 +9,8 @@ import os
import pwd
import unittest
+import pytest
+
TEST_VARS = {
'VAR1': 'single',
@@ -130,18 +132,18 @@ class TestOpenNebulaDataSource(CiTestCase):
def test_seed_dir_non_contextdisk(self):
self.assertRaises(ds.NonContextDiskDir, ds.read_context_disk_dir,
- self.seed_dir)
+ self.seed_dir, mock.Mock())
def test_seed_dir_empty1_context(self):
populate_dir(self.seed_dir, {'context.sh': ''})
- results = ds.read_context_disk_dir(self.seed_dir)
+ results = ds.read_context_disk_dir(self.seed_dir, mock.Mock())
self.assertIsNone(results['userdata'])
self.assertEqual(results['metadata'], {})
def test_seed_dir_empty2_context(self):
populate_context_dir(self.seed_dir, {})
- results = ds.read_context_disk_dir(self.seed_dir)
+ results = ds.read_context_disk_dir(self.seed_dir, mock.Mock())
self.assertIsNone(results['userdata'])
self.assertEqual(results['metadata'], {})
@@ -151,11 +153,11 @@ class TestOpenNebulaDataSource(CiTestCase):
self.assertRaises(ds.BrokenContextDiskDir,
ds.read_context_disk_dir,
- self.seed_dir)
+ self.seed_dir, mock.Mock())
def test_context_parser(self):
populate_context_dir(self.seed_dir, TEST_VARS)
- results = ds.read_context_disk_dir(self.seed_dir)
+ results = ds.read_context_disk_dir(self.seed_dir, mock.Mock())
self.assertTrue('metadata' in results)
self.assertEqual(TEST_VARS, results['metadata'])
@@ -166,7 +168,7 @@ class TestOpenNebulaDataSource(CiTestCase):
for k in ('SSH_KEY', 'SSH_PUBLIC_KEY'):
my_d = os.path.join(self.tmp, "%s-%i" % (k, c))
populate_context_dir(my_d, {k: '\n'.join(public_keys)})
- results = ds.read_context_disk_dir(my_d)
+ results = ds.read_context_disk_dir(my_d, mock.Mock())
self.assertTrue('metadata' in results)
self.assertTrue('public-keys' in results['metadata'])
@@ -180,7 +182,7 @@ class TestOpenNebulaDataSource(CiTestCase):
my_d = os.path.join(self.tmp, k)
populate_context_dir(my_d, {k: USER_DATA,
'USERDATA_ENCODING': ''})
- results = ds.read_context_disk_dir(my_d)
+ results = ds.read_context_disk_dir(my_d, mock.Mock())
self.assertTrue('userdata' in results)
self.assertEqual(USER_DATA, results['userdata'])
@@ -190,7 +192,7 @@ class TestOpenNebulaDataSource(CiTestCase):
for k in ('USER_DATA', 'USERDATA'):
my_d = os.path.join(self.tmp, k)
populate_context_dir(my_d, {k: b64userdata})
- results = ds.read_context_disk_dir(my_d)
+ results = ds.read_context_disk_dir(my_d, mock.Mock())
self.assertTrue('userdata' in results)
self.assertEqual(b64userdata, results['userdata'])
@@ -200,7 +202,7 @@ class TestOpenNebulaDataSource(CiTestCase):
my_d = os.path.join(self.tmp, k)
populate_context_dir(my_d, {k: util.b64e(USER_DATA),
'USERDATA_ENCODING': 'base64'})
- results = ds.read_context_disk_dir(my_d)
+ results = ds.read_context_disk_dir(my_d, mock.Mock())
self.assertTrue('userdata' in results)
self.assertEqual(USER_DATA, results['userdata'])
@@ -212,7 +214,7 @@ class TestOpenNebulaDataSource(CiTestCase):
for k in ('HOSTNAME', 'PUBLIC_IP', 'IP_PUBLIC', 'ETH0_IP'):
my_d = os.path.join(self.tmp, k)
populate_context_dir(my_d, {k: PUBLIC_IP})
- results = ds.read_context_disk_dir(my_d)
+ results = ds.read_context_disk_dir(my_d, mock.Mock())
self.assertTrue('metadata' in results)
self.assertTrue('local-hostname' in results['metadata'])
@@ -227,7 +229,7 @@ class TestOpenNebulaDataSource(CiTestCase):
# without ETH0_MAC
# for Older OpenNebula?
populate_context_dir(self.seed_dir, {'ETH0_IP': IP_BY_MACADDR})
- results = ds.read_context_disk_dir(self.seed_dir)
+ results = ds.read_context_disk_dir(self.seed_dir, mock.Mock())
self.assertTrue('network-interfaces' in results)
self.assertTrue(
@@ -237,7 +239,7 @@ class TestOpenNebulaDataSource(CiTestCase):
# ETH0_IP and ETH0_MAC
populate_context_dir(
self.seed_dir, {'ETH0_IP': IP_BY_MACADDR, 'ETH0_MAC': MACADDR})
- results = ds.read_context_disk_dir(self.seed_dir)
+ results = ds.read_context_disk_dir(self.seed_dir, mock.Mock())
self.assertTrue('network-interfaces' in results)
self.assertTrue(
@@ -249,7 +251,7 @@ class TestOpenNebulaDataSource(CiTestCase):
# "AR = [ TYPE = ETHER ]"
populate_context_dir(
self.seed_dir, {'ETH0_IP': '', 'ETH0_MAC': MACADDR})
- results = ds.read_context_disk_dir(self.seed_dir)
+ results = ds.read_context_disk_dir(self.seed_dir, mock.Mock())
self.assertTrue('network-interfaces' in results)
self.assertTrue(
@@ -263,7 +265,7 @@ class TestOpenNebulaDataSource(CiTestCase):
'ETH0_MAC': MACADDR,
'ETH0_MASK': '255.255.0.0'
})
- results = ds.read_context_disk_dir(self.seed_dir)
+ results = ds.read_context_disk_dir(self.seed_dir, mock.Mock())
self.assertTrue('network-interfaces' in results)
self.assertTrue(
@@ -277,7 +279,7 @@ class TestOpenNebulaDataSource(CiTestCase):
'ETH0_MAC': MACADDR,
'ETH0_MASK': ''
})
- results = ds.read_context_disk_dir(self.seed_dir)
+ results = ds.read_context_disk_dir(self.seed_dir, mock.Mock())
self.assertTrue('network-interfaces' in results)
self.assertTrue(
@@ -290,7 +292,7 @@ class TestOpenNebulaDataSource(CiTestCase):
'ETH0_IP6': IP6_GLOBAL,
'ETH0_MAC': MACADDR,
})
- results = ds.read_context_disk_dir(self.seed_dir)
+ results = ds.read_context_disk_dir(self.seed_dir, mock.Mock())
self.assertTrue('network-interfaces' in results)
self.assertTrue(
@@ -303,7 +305,7 @@ class TestOpenNebulaDataSource(CiTestCase):
'ETH0_IP6_ULA': IP6_ULA,
'ETH0_MAC': MACADDR,
})
- results = ds.read_context_disk_dir(self.seed_dir)
+ results = ds.read_context_disk_dir(self.seed_dir, mock.Mock())
self.assertTrue('network-interfaces' in results)
self.assertTrue(
@@ -317,7 +319,7 @@ class TestOpenNebulaDataSource(CiTestCase):
'ETH0_IP6_PREFIX_LENGTH': IP6_PREFIX,
'ETH0_MAC': MACADDR,
})
- results = ds.read_context_disk_dir(self.seed_dir)
+ results = ds.read_context_disk_dir(self.seed_dir, mock.Mock())
self.assertTrue('network-interfaces' in results)
self.assertTrue(
@@ -331,7 +333,7 @@ class TestOpenNebulaDataSource(CiTestCase):
'ETH0_IP6_PREFIX_LENGTH': '',
'ETH0_MAC': MACADDR,
})
- results = ds.read_context_disk_dir(self.seed_dir)
+ results = ds.read_context_disk_dir(self.seed_dir, mock.Mock())
self.assertTrue('network-interfaces' in results)
self.assertTrue(
@@ -355,6 +357,7 @@ class TestOpenNebulaDataSource(CiTestCase):
util.find_devs_with = orig_find_devs_with
+@mock.patch(DS_PATH + '.net.get_interfaces_by_mac', mock.Mock(return_value={}))
class TestOpenNebulaNetwork(unittest.TestCase):
system_nics = ('eth0', 'ens3')
@@ -367,7 +370,7 @@ class TestOpenNebulaNetwork(unittest.TestCase):
expected = {
'02:00:0a:12:01:01': 'ETH0',
'02:00:0a:12:0f:0f': 'ETH1', }
- net = ds.OpenNebulaNetwork(context)
+ net = ds.OpenNebulaNetwork(context, mock.Mock())
self.assertEqual(expected, net.context_devname)
def test_get_nameservers(self):
@@ -382,21 +385,21 @@ class TestOpenNebulaNetwork(unittest.TestCase):
expected = {
'addresses': ['1.2.3.6', '1.2.3.7', '1.2.3.8'],
'search': ['example.com', 'example.org']}
- net = ds.OpenNebulaNetwork(context)
+ net = ds.OpenNebulaNetwork(context, mock.Mock())
val = net.get_nameservers('eth0')
self.assertEqual(expected, val)
def test_get_mtu(self):
"""Verify get_mtu('device') correctly returns MTU size."""
context = {'ETH0_MTU': '1280'}
- net = ds.OpenNebulaNetwork(context)
+ net = ds.OpenNebulaNetwork(context, mock.Mock())
val = net.get_mtu('eth0')
self.assertEqual('1280', val)
def test_get_ip(self):
"""Verify get_ip('device') correctly returns IPv4 address."""
context = {'ETH0_IP': PUBLIC_IP}
- net = ds.OpenNebulaNetwork(context)
+ net = ds.OpenNebulaNetwork(context, mock.Mock())
val = net.get_ip('eth0', MACADDR)
self.assertEqual(PUBLIC_IP, val)
@@ -407,7 +410,7 @@ class TestOpenNebulaNetwork(unittest.TestCase):
string.
"""
context = {'ETH0_IP': ''}
- net = ds.OpenNebulaNetwork(context)
+ net = ds.OpenNebulaNetwork(context, mock.Mock())
val = net.get_ip('eth0', MACADDR)
self.assertEqual(IP_BY_MACADDR, val)
@@ -420,7 +423,7 @@ class TestOpenNebulaNetwork(unittest.TestCase):
'ETH0_IP6': IP6_GLOBAL,
'ETH0_IP6_ULA': '', }
expected = [IP6_GLOBAL]
- net = ds.OpenNebulaNetwork(context)
+ net = ds.OpenNebulaNetwork(context, mock.Mock())
val = net.get_ip6('eth0')
self.assertEqual(expected, val)
@@ -433,7 +436,7 @@ class TestOpenNebulaNetwork(unittest.TestCase):
'ETH0_IP6': '',
'ETH0_IP6_ULA': IP6_ULA, }
expected = [IP6_ULA]
- net = ds.OpenNebulaNetwork(context)
+ net = ds.OpenNebulaNetwork(context, mock.Mock())
val = net.get_ip6('eth0')
self.assertEqual(expected, val)
@@ -446,7 +449,7 @@ class TestOpenNebulaNetwork(unittest.TestCase):
'ETH0_IP6': IP6_GLOBAL,
'ETH0_IP6_ULA': IP6_ULA, }
expected = [IP6_GLOBAL, IP6_ULA]
- net = ds.OpenNebulaNetwork(context)
+ net = ds.OpenNebulaNetwork(context, mock.Mock())
val = net.get_ip6('eth0')
self.assertEqual(expected, val)
@@ -455,7 +458,7 @@ class TestOpenNebulaNetwork(unittest.TestCase):
Verify get_ip6_prefix('device') correctly returns IPv6 prefix.
"""
context = {'ETH0_IP6_PREFIX_LENGTH': IP6_PREFIX}
- net = ds.OpenNebulaNetwork(context)
+ net = ds.OpenNebulaNetwork(context, mock.Mock())
val = net.get_ip6_prefix('eth0')
self.assertEqual(IP6_PREFIX, val)
@@ -466,7 +469,7 @@ class TestOpenNebulaNetwork(unittest.TestCase):
string.
"""
context = {'ETH0_IP6_PREFIX_LENGTH': ''}
- net = ds.OpenNebulaNetwork(context)
+ net = ds.OpenNebulaNetwork(context, mock.Mock())
val = net.get_ip6_prefix('eth0')
self.assertEqual('64', val)
@@ -476,7 +479,7 @@ class TestOpenNebulaNetwork(unittest.TestCase):
address.
"""
context = {'ETH0_GATEWAY': '1.2.3.5'}
- net = ds.OpenNebulaNetwork(context)
+ net = ds.OpenNebulaNetwork(context, mock.Mock())
val = net.get_gateway('eth0')
self.assertEqual('1.2.3.5', val)
@@ -486,7 +489,7 @@ class TestOpenNebulaNetwork(unittest.TestCase):
address.
"""
context = {'ETH0_GATEWAY6': IP6_GW}
- net = ds.OpenNebulaNetwork(context)
+ net = ds.OpenNebulaNetwork(context, mock.Mock())
val = net.get_gateway6('eth0')
self.assertEqual(IP6_GW, val)
@@ -495,7 +498,7 @@ class TestOpenNebulaNetwork(unittest.TestCase):
Verify get_mask('device') correctly returns IPv4 subnet mask.
"""
context = {'ETH0_MASK': '255.255.0.0'}
- net = ds.OpenNebulaNetwork(context)
+ net = ds.OpenNebulaNetwork(context, mock.Mock())
val = net.get_mask('eth0')
self.assertEqual('255.255.0.0', val)
@@ -505,7 +508,7 @@ class TestOpenNebulaNetwork(unittest.TestCase):
It returns default value '255.255.255.0' if ETH0_MASK has empty string.
"""
context = {'ETH0_MASK': ''}
- net = ds.OpenNebulaNetwork(context)
+ net = ds.OpenNebulaNetwork(context, mock.Mock())
val = net.get_mask('eth0')
self.assertEqual('255.255.255.0', val)
@@ -514,7 +517,7 @@ class TestOpenNebulaNetwork(unittest.TestCase):
Verify get_network('device') correctly returns IPv4 network address.
"""
context = {'ETH0_NETWORK': '1.2.3.0'}
- net = ds.OpenNebulaNetwork(context)
+ net = ds.OpenNebulaNetwork(context, mock.Mock())
val = net.get_network('eth0', MACADDR)
self.assertEqual('1.2.3.0', val)
@@ -525,7 +528,7 @@ class TestOpenNebulaNetwork(unittest.TestCase):
empty string.
"""
context = {'ETH0_NETWORK': ''}
- net = ds.OpenNebulaNetwork(context)
+ net = ds.OpenNebulaNetwork(context, mock.Mock())
val = net.get_network('eth0', MACADDR)
self.assertEqual('10.18.1.0', val)
@@ -534,7 +537,7 @@ class TestOpenNebulaNetwork(unittest.TestCase):
Verify get_field('device', 'name') returns *context* value.
"""
context = {'ETH9_DUMMY': 'DUMMY_VALUE'}
- net = ds.OpenNebulaNetwork(context)
+ net = ds.OpenNebulaNetwork(context, mock.Mock())
val = net.get_field('eth9', 'dummy')
self.assertEqual('DUMMY_VALUE', val)
@@ -544,7 +547,7 @@ class TestOpenNebulaNetwork(unittest.TestCase):
value.
"""
context = {'ETH9_DUMMY': 'DUMMY_VALUE'}
- net = ds.OpenNebulaNetwork(context)
+ net = ds.OpenNebulaNetwork(context, mock.Mock())
val = net.get_field('eth9', 'dummy', 'DEFAULT_VALUE')
self.assertEqual('DUMMY_VALUE', val)
@@ -554,7 +557,7 @@ class TestOpenNebulaNetwork(unittest.TestCase):
value if context value is empty string.
"""
context = {'ETH9_DUMMY': ''}
- net = ds.OpenNebulaNetwork(context)
+ net = ds.OpenNebulaNetwork(context, mock.Mock())
val = net.get_field('eth9', 'dummy', 'DEFAULT_VALUE')
self.assertEqual('DEFAULT_VALUE', val)
@@ -564,7 +567,7 @@ class TestOpenNebulaNetwork(unittest.TestCase):
empty string.
"""
context = {'ETH9_DUMMY': ''}
- net = ds.OpenNebulaNetwork(context)
+ net = ds.OpenNebulaNetwork(context, mock.Mock())
val = net.get_field('eth9', 'dummy')
self.assertEqual(None, val)
@@ -574,7 +577,7 @@ class TestOpenNebulaNetwork(unittest.TestCase):
None.
"""
context = {'ETH9_DUMMY': None}
- net = ds.OpenNebulaNetwork(context)
+ net = ds.OpenNebulaNetwork(context, mock.Mock())
val = net.get_field('eth9', 'dummy')
self.assertEqual(None, val)
@@ -594,7 +597,7 @@ class TestOpenNebulaNetwork(unittest.TestCase):
'match': {'macaddress': MACADDR},
'addresses': [IP_BY_MACADDR + '/' + IP4_PREFIX]}}}
m_get_phys_by_mac.return_value = {MACADDR: nic}
- net = ds.OpenNebulaNetwork(context)
+ net = ds.OpenNebulaNetwork(context, mock.Mock())
self.assertEqual(net.gen_conf(), expected)
# set ETH0_GATEWAY
@@ -610,7 +613,7 @@ class TestOpenNebulaNetwork(unittest.TestCase):
'match': {'macaddress': MACADDR},
'addresses': [IP_BY_MACADDR + '/' + IP4_PREFIX]}}}
m_get_phys_by_mac.return_value = {MACADDR: nic}
- net = ds.OpenNebulaNetwork(context)
+ net = ds.OpenNebulaNetwork(context, mock.Mock())
self.assertEqual(net.gen_conf(), expected)
@mock.patch(DS_PATH + ".get_physical_nics_by_mac")
@@ -629,7 +632,7 @@ class TestOpenNebulaNetwork(unittest.TestCase):
'match': {'macaddress': MACADDR},
'addresses': [IP_BY_MACADDR + '/' + IP4_PREFIX]}}}
m_get_phys_by_mac.return_value = {MACADDR: nic}
- net = ds.OpenNebulaNetwork(context)
+ net = ds.OpenNebulaNetwork(context, mock.Mock())
self.assertEqual(net.gen_conf(), expected)
# set ETH0_GATEWAY6
@@ -645,7 +648,7 @@ class TestOpenNebulaNetwork(unittest.TestCase):
'match': {'macaddress': MACADDR},
'addresses': [IP_BY_MACADDR + '/' + IP4_PREFIX]}}}
m_get_phys_by_mac.return_value = {MACADDR: nic}
- net = ds.OpenNebulaNetwork(context)
+ net = ds.OpenNebulaNetwork(context, mock.Mock())
self.assertEqual(net.gen_conf(), expected)
@mock.patch(DS_PATH + ".get_physical_nics_by_mac")
@@ -666,7 +669,7 @@ class TestOpenNebulaNetwork(unittest.TestCase):
'match': {'macaddress': MACADDR},
'addresses': [IP_BY_MACADDR + '/' + IP4_PREFIX]}}}
m_get_phys_by_mac.return_value = {MACADDR: nic}
- net = ds.OpenNebulaNetwork(context)
+ net = ds.OpenNebulaNetwork(context, mock.Mock())
self.assertEqual(net.gen_conf(), expected)
# set ETH0_IP6, ETH0_IP6_ULA, ETH0_IP6_PREFIX_LENGTH
@@ -686,7 +689,7 @@ class TestOpenNebulaNetwork(unittest.TestCase):
IP6_GLOBAL + '/' + IP6_PREFIX,
IP6_ULA + '/' + IP6_PREFIX]}}}
m_get_phys_by_mac.return_value = {MACADDR: nic}
- net = ds.OpenNebulaNetwork(context)
+ net = ds.OpenNebulaNetwork(context, mock.Mock())
self.assertEqual(net.gen_conf(), expected)
@mock.patch(DS_PATH + ".get_physical_nics_by_mac")
@@ -707,7 +710,7 @@ class TestOpenNebulaNetwork(unittest.TestCase):
'match': {'macaddress': MACADDR},
'addresses': [IP_BY_MACADDR + '/' + IP4_PREFIX]}}}
m_get_phys_by_mac.return_value = {MACADDR: nic}
- net = ds.OpenNebulaNetwork(context)
+ net = ds.OpenNebulaNetwork(context, mock.Mock())
self.assertEqual(net.gen_conf(), expected)
# set DNS, ETH0_DNS, ETH0_SEARCH_DOMAIN
@@ -727,7 +730,7 @@ class TestOpenNebulaNetwork(unittest.TestCase):
'match': {'macaddress': MACADDR},
'addresses': [IP_BY_MACADDR + '/' + IP4_PREFIX]}}}
m_get_phys_by_mac.return_value = {MACADDR: nic}
- net = ds.OpenNebulaNetwork(context)
+ net = ds.OpenNebulaNetwork(context, mock.Mock())
self.assertEqual(net.gen_conf(), expected)
@mock.patch(DS_PATH + ".get_physical_nics_by_mac")
@@ -746,7 +749,7 @@ class TestOpenNebulaNetwork(unittest.TestCase):
'match': {'macaddress': MACADDR},
'addresses': [IP_BY_MACADDR + '/' + IP4_PREFIX]}}}
m_get_phys_by_mac.return_value = {MACADDR: nic}
- net = ds.OpenNebulaNetwork(context)
+ net = ds.OpenNebulaNetwork(context, mock.Mock())
self.assertEqual(net.gen_conf(), expected)
# set ETH0_MTU
@@ -762,14 +765,14 @@ class TestOpenNebulaNetwork(unittest.TestCase):
'match': {'macaddress': MACADDR},
'addresses': [IP_BY_MACADDR + '/' + IP4_PREFIX]}}}
m_get_phys_by_mac.return_value = {MACADDR: nic}
- net = ds.OpenNebulaNetwork(context)
+ net = ds.OpenNebulaNetwork(context, mock.Mock())
self.assertEqual(net.gen_conf(), expected)
@mock.patch(DS_PATH + ".get_physical_nics_by_mac")
def test_eth0(self, m_get_phys_by_mac):
for nic in self.system_nics:
m_get_phys_by_mac.return_value = {MACADDR: nic}
- net = ds.OpenNebulaNetwork({})
+ net = ds.OpenNebulaNetwork({}, mock.Mock())
expected = {
'version': 2,
'ethernets': {
@@ -779,6 +782,14 @@ class TestOpenNebulaNetwork(unittest.TestCase):
self.assertEqual(net.gen_conf(), expected)
+ @mock.patch(DS_PATH + ".get_physical_nics_by_mac")
+ def test_distro_passed_through(self, m_get_physical_nics_by_mac):
+ ds.OpenNebulaNetwork({}, mock.sentinel.distro)
+ self.assertEqual(
+ [mock.call(mock.sentinel.distro)],
+ m_get_physical_nics_by_mac.call_args_list,
+ )
+
def test_eth0_override(self):
self.maxDiff = None
context = {
@@ -797,7 +808,7 @@ class TestOpenNebulaNetwork(unittest.TestCase):
'ETH0_SEARCH_DOMAIN': '',
}
for nic in self.system_nics:
- net = ds.OpenNebulaNetwork(context,
+ net = ds.OpenNebulaNetwork(context, mock.Mock(),
system_nics_by_mac={MACADDR: nic})
expected = {
'version': 2,
@@ -829,7 +840,7 @@ class TestOpenNebulaNetwork(unittest.TestCase):
'ETH0_SEARCH_DOMAIN': 'example.com example.org',
}
for nic in self.system_nics:
- net = ds.OpenNebulaNetwork(context,
+ net = ds.OpenNebulaNetwork(context, mock.Mock(),
system_nics_by_mac={MACADDR: nic})
expected = {
@@ -883,7 +894,10 @@ class TestOpenNebulaNetwork(unittest.TestCase):
'ETH3_SEARCH_DOMAIN': 'third.example.com third.example.org',
}
net = ds.OpenNebulaNetwork(
- context, system_nics_by_mac={MAC_1: 'enp0s25', MAC_2: 'enp1s2'})
+ context,
+ mock.Mock(),
+ system_nics_by_mac={MAC_1: 'enp0s25', MAC_2: 'enp1s2'}
+ )
expected = {
'version': 2,
@@ -913,12 +927,43 @@ class TestOpenNebulaNetwork(unittest.TestCase):
self.assertEqual(expected, net.gen_conf())
-class TestParseShellConfig(unittest.TestCase):
+class TestParseShellConfig:
+ @pytest.mark.allow_subp_for("bash")
def test_no_seconds(self):
cfg = '\n'.join(["foo=bar", "SECONDS=2", "xx=foo"])
# we could test 'sleep 2', but that would make the test run slower.
ret = ds.parse_shell_config(cfg)
- self.assertEqual(ret, {"foo": "bar", "xx": "foo"})
+ assert ret == {"foo": "bar", "xx": "foo"}
+
+
+class TestGetPhysicalNicsByMac:
+ @pytest.mark.parametrize(
+ "interfaces_by_mac,physical_devs,expected_return",
+ [
+ # No interfaces => empty return
+ ({}, [], {}),
+ # Only virtual interface => empty return
+ ({"mac1": "virtual0"}, [], {}),
+ # Only physical interface => it is returned
+ ({"mac2": "physical0"}, ["physical0"], {"mac2": "physical0"}),
+ # Combination of physical and virtual => only physical returned
+ (
+ {"mac3": "physical1", "mac4": "virtual1"},
+ ["physical1"],
+ {"mac3": "physical1"},
+ ),
+ ],
+ )
+ def test(self, interfaces_by_mac, physical_devs, expected_return):
+ distro = mock.Mock()
+ distro.networking.is_physical.side_effect = (
+ lambda devname: devname in physical_devs
+ )
+ with mock.patch(
+ DS_PATH + ".net.get_interfaces_by_mac",
+ return_value=interfaces_by_mac,
+ ):
+ assert expected_return == ds.get_physical_nics_by_mac(distro)
def populate_context_dir(path, variables):
diff --git a/tests/unittests/test_datasource/test_openstack.py b/tests/unittests/test_datasource/test_openstack.py
index f754556f..3cfba74d 100644
--- a/tests/unittests/test_datasource/test_openstack.py
+++ b/tests/unittests/test_datasource/test_openstack.py
@@ -279,7 +279,7 @@ class TestOpenStackDataSource(test_helpers.HttprettyTestCase):
self.assertEqual(2, len(ds_os_local.files))
self.assertEqual(VENDOR_DATA, ds_os_local.vendordata_pure)
self.assertIsNone(ds_os_local.vendordata_raw)
- m_dhcp.assert_called_with('eth9')
+ m_dhcp.assert_called_with('eth9', None)
def test_bad_datasource_meta(self):
os_files = copy.deepcopy(OS_FILES)
@@ -510,6 +510,24 @@ class TestDetectOpenStack(test_helpers.CiTestCase):
'Expected detect_openstack == True on OpenTelekomCloud')
@test_helpers.mock.patch(MOCK_PATH + 'util.read_dmi_data')
+ def test_detect_openstack_sapccloud_chassis_asset_tag(self, m_dmi,
+ m_is_x86):
+ """Return True on OpenStack reporting SAP CCloud VM asset-tag."""
+ m_is_x86.return_value = True
+
+ def fake_dmi_read(dmi_key):
+ if dmi_key == 'system-product-name':
+ return 'VMware Virtual Platform' # SAP CCloud uses VMware
+ if dmi_key == 'chassis-asset-tag':
+ return 'SAP CCloud VM'
+ assert False, 'Unexpected dmi read of %s' % dmi_key
+
+ m_dmi.side_effect = fake_dmi_read
+ self.assertTrue(
+ ds.detect_openstack(),
+ 'Expected detect_openstack == True on SAP CCloud VM')
+
+ @test_helpers.mock.patch(MOCK_PATH + 'util.read_dmi_data')
def test_detect_openstack_oraclecloud_chassis_asset_tag(self, m_dmi,
m_is_x86):
"""Return True on OpenStack reporting Oracle cloud asset-tag."""
diff --git a/tests/unittests/test_datasource/test_ovf.py b/tests/unittests/test_datasource/test_ovf.py
index a19c35c8..1d088577 100644
--- a/tests/unittests/test_datasource/test_ovf.py
+++ b/tests/unittests/test_datasource/test_ovf.py
@@ -10,6 +10,7 @@ import os
from collections import OrderedDict
from textwrap import dedent
+from cloudinit import subp
from cloudinit import util
from cloudinit.tests.helpers import CiTestCase, mock, wrap_and_call
from cloudinit.helpers import Paths
@@ -48,7 +49,7 @@ def fill_properties(props, template=OVF_ENV_CONTENT):
for key, val in props.items():
lines.append(prop_tmpl.format(key=key, val=val))
indent = " "
- properties = ''.join([indent + l + "\n" for l in lines])
+ properties = ''.join([indent + line + "\n" for line in lines])
return template.format(properties=properties)
@@ -219,6 +220,88 @@ class TestDatasourceOVF(CiTestCase):
self.assertIn('Custom script is disabled by VM Administrator',
str(context.exception))
+ def test_get_data_cust_script_enabled(self):
+ """If custom script is enabled by VMware tools configuration,
+ execute the script.
+ """
+ paths = Paths({'cloud_dir': self.tdir})
+ ds = self.datasource(
+ sys_cfg={'disable_vmware_customization': False}, distro={},
+ paths=paths)
+ # Prepare the conf file
+ conf_file = self.tmp_path('test-cust', self.tdir)
+ conf_content = dedent("""\
+ [CUSTOM-SCRIPT]
+ SCRIPT-NAME = test-script
+ [MISC]
+ MARKER-ID = 12345346
+ """)
+ util.write_file(conf_file, conf_content)
+
+ # Mock custom script is enabled by return true when calling
+ # get_tools_config
+ with mock.patch(MPATH + 'get_tools_config', return_value="true"):
+ with mock.patch(MPATH + 'set_customization_status',
+ return_value=('msg', b'')):
+ with self.assertRaises(CustomScriptNotFound) as context:
+ wrap_and_call(
+ 'cloudinit.sources.DataSourceOVF',
+ {'util.read_dmi_data': 'vmware',
+ 'util.del_dir': True,
+ 'search_file': self.tdir,
+ 'wait_for_imc_cfg_file': conf_file,
+ 'get_nics_to_enable': ''},
+ ds.get_data)
+ # Verify custom script is trying to be executed
+ customscript = self.tmp_path('test-script', self.tdir)
+ self.assertIn('Script %s not found!!' % customscript,
+ str(context.exception))
+
+ def test_get_data_force_run_post_script_is_yes(self):
+ """If DEFAULT-RUN-POST-CUST-SCRIPT is yes, custom script could run if
+ enable-custom-scripts is not defined in VM Tools configuration
+ """
+ paths = Paths({'cloud_dir': self.tdir})
+ ds = self.datasource(
+ sys_cfg={'disable_vmware_customization': False}, distro={},
+ paths=paths)
+ # Prepare the conf file
+ conf_file = self.tmp_path('test-cust', self.tdir)
+ # set DEFAULT-RUN-POST-CUST-SCRIPT = yes so that enable-custom-scripts
+ # default value is TRUE
+ conf_content = dedent("""\
+ [CUSTOM-SCRIPT]
+ SCRIPT-NAME = test-script
+ [MISC]
+ MARKER-ID = 12345346
+ DEFAULT-RUN-POST-CUST-SCRIPT = yes
+ """)
+ util.write_file(conf_file, conf_content)
+
+ # Mock get_tools_config(section, key, defaultVal) to return
+ # defaultVal
+ def my_get_tools_config(*args, **kwargs):
+ return args[2]
+
+ with mock.patch(MPATH + 'get_tools_config',
+ side_effect=my_get_tools_config):
+ with mock.patch(MPATH + 'set_customization_status',
+ return_value=('msg', b'')):
+ with self.assertRaises(CustomScriptNotFound) as context:
+ wrap_and_call(
+ 'cloudinit.sources.DataSourceOVF',
+ {'util.read_dmi_data': 'vmware',
+ 'util.del_dir': True,
+ 'search_file': self.tdir,
+ 'wait_for_imc_cfg_file': conf_file,
+ 'get_nics_to_enable': ''},
+ ds.get_data)
+ # Verify custom script still runs although it is
+ # disabled by VMware Tools
+ customscript = self.tmp_path('test-script', self.tdir)
+ self.assertIn('Script %s not found!!' % customscript,
+ str(context.exception))
+
def test_get_data_non_vmware_seed_platform_info(self):
"""Platform info properly reports when on non-vmware platforms."""
paths = Paths({'cloud_dir': self.tdir, 'run_dir': self.tdir})
@@ -401,8 +484,8 @@ class TestTransportIso9660(CiTestCase):
self.assertTrue(dsovf.maybe_cdrom_device('xvdza1'))
-@mock.patch(MPATH + "util.which")
-@mock.patch(MPATH + "util.subp")
+@mock.patch(MPATH + "subp.which")
+@mock.patch(MPATH + "subp.subp")
class TestTransportVmwareGuestinfo(CiTestCase):
"""Test the com.vmware.guestInfo transport implemented in
transport_vmware_guestinfo."""
@@ -420,7 +503,7 @@ class TestTransportVmwareGuestinfo(CiTestCase):
def test_notfound_on_exit_code_1(self, m_subp, m_which):
"""If vmware-rpctool exits 1, then must return not found."""
m_which.return_value = self.rpctool_path
- m_subp.side_effect = util.ProcessExecutionError(
+ m_subp.side_effect = subp.ProcessExecutionError(
stdout="", stderr="No value found", exit_code=1, cmd=["unused"])
self.assertEqual(NOT_FOUND, dsovf.transport_vmware_guestinfo())
self.assertEqual(1, m_subp.call_count)
@@ -442,7 +525,7 @@ class TestTransportVmwareGuestinfo(CiTestCase):
def test_notfound_and_warns_on_unexpected_exit_code(self, m_subp, m_which):
"""If vmware-rpctool exits non zero or 1, warnings should be logged."""
m_which.return_value = self.rpctool_path
- m_subp.side_effect = util.ProcessExecutionError(
+ m_subp.side_effect = subp.ProcessExecutionError(
stdout=None, stderr="No value found", exit_code=2, cmd=["unused"])
self.assertEqual(NOT_FOUND, dsovf.transport_vmware_guestinfo())
self.assertEqual(1, m_subp.call_count)
diff --git a/tests/unittests/test_datasource/test_rbx.py b/tests/unittests/test_datasource/test_rbx.py
index aabf1f18..d017510e 100644
--- a/tests/unittests/test_datasource/test_rbx.py
+++ b/tests/unittests/test_datasource/test_rbx.py
@@ -4,6 +4,7 @@ from cloudinit import helpers
from cloudinit import distros
from cloudinit.sources import DataSourceRbxCloud as ds
from cloudinit.tests.helpers import mock, CiTestCase, populate_dir
+from cloudinit import subp
DS_PATH = "cloudinit.sources.DataSourceRbxCloud"
@@ -156,7 +157,7 @@ class TestRbxDataSource(CiTestCase):
expected
)
- @mock.patch(DS_PATH + '.util.subp')
+ @mock.patch(DS_PATH + '.subp.subp')
def test_gratuitous_arp_run_standard_arping(self, m_subp):
"""Test handle run arping & parameters."""
items = [
@@ -182,7 +183,7 @@ class TestRbxDataSource(CiTestCase):
], m_subp.call_args_list
)
- @mock.patch(DS_PATH + '.util.subp')
+ @mock.patch(DS_PATH + '.subp.subp')
def test_handle_rhel_like_arping(self, m_subp):
"""Test handle on RHEL-like distros."""
items = [
@@ -199,6 +200,35 @@ class TestRbxDataSource(CiTestCase):
m_subp.call_args_list
)
+ @mock.patch(
+ DS_PATH + '.subp.subp',
+ side_effect=subp.ProcessExecutionError()
+ )
+ def test_continue_on_arping_error(self, m_subp):
+ """Continue when command error"""
+ items = [
+ {
+ 'destination': '172.17.0.2',
+ 'source': '172.16.6.104'
+ },
+ {
+ 'destination': '172.17.0.2',
+ 'source': '172.16.6.104',
+ },
+ ]
+ ds.gratuitous_arp(items, self._fetch_distro('ubuntu'))
+ self.assertEqual([
+ mock.call([
+ 'arping', '-c', '2', '-S',
+ '172.16.6.104', '172.17.0.2'
+ ]),
+ mock.call([
+ 'arping', '-c', '2', '-S',
+ '172.16.6.104', '172.17.0.2'
+ ])
+ ], m_subp.call_args_list
+ )
+
def populate_cloud_metadata(path, data):
populate_dir(path, {'cloud.json': json.dumps(data)})
diff --git a/tests/unittests/test_datasource/test_scaleway.py b/tests/unittests/test_datasource/test_scaleway.py
index 1b4dd0ad..9d82bda9 100644
--- a/tests/unittests/test_datasource/test_scaleway.py
+++ b/tests/unittests/test_datasource/test_scaleway.py
@@ -353,12 +353,16 @@ class TestDataSourceScaleway(HttprettyTestCase):
self.datasource.metadata['ipv6'] = None
netcfg = self.datasource.network_config
- resp = {'version': 1,
- 'config': [{
- 'type': 'physical',
- 'name': 'ens2',
- 'subnets': [{'type': 'dhcp4'}]}]
+ resp = {
+ 'version': 1,
+ 'config': [
+ {
+ 'type': 'physical',
+ 'name': 'ens2',
+ 'subnets': [{'type': 'dhcp4'}]
}
+ ]
+ }
self.assertEqual(netcfg, resp)
@mock.patch('cloudinit.sources.DataSourceScaleway.net.find_fallback_nic')
@@ -371,25 +375,32 @@ class TestDataSourceScaleway(HttprettyTestCase):
m_get_cmdline.return_value = 'scaleway'
fallback_nic.return_value = 'ens2'
self.datasource.metadata['ipv6'] = {
- 'address': '2000:abc:4444:9876::42:999',
- 'gateway': '2000:abc:4444:9876::42:000',
- 'netmask': '127',
- }
+ 'address': '2000:abc:4444:9876::42:999',
+ 'gateway': '2000:abc:4444:9876::42:000',
+ 'netmask': '127',
+ }
netcfg = self.datasource.network_config
- resp = {'version': 1,
- 'config': [{
- 'type': 'physical',
- 'name': 'ens2',
- 'subnets': [{'type': 'dhcp4'},
- {'type': 'static',
- 'address': '2000:abc:4444:9876::42:999',
- 'gateway': '2000:abc:4444:9876::42:000',
- 'netmask': '127', }
- ]
-
- }]
+ resp = {
+ 'version': 1,
+ 'config': [
+ {
+ 'type': 'physical',
+ 'name': 'ens2',
+ 'subnets': [
+ {
+ 'type': 'dhcp4'
+ },
+ {
+ 'type': 'static',
+ 'address': '2000:abc:4444:9876::42:999',
+ 'gateway': '2000:abc:4444:9876::42:000',
+ 'netmask': '127',
+ }
+ ]
}
+ ]
+ }
self.assertEqual(netcfg, resp)
@mock.patch('cloudinit.sources.DataSourceScaleway.net.find_fallback_nic')
@@ -417,12 +428,16 @@ class TestDataSourceScaleway(HttprettyTestCase):
self.datasource.metadata['ipv6'] = None
self.datasource._network_config = sources.UNSET
- resp = {'version': 1,
- 'config': [{
- 'type': 'physical',
- 'name': 'ens2',
- 'subnets': [{'type': 'dhcp4'}]}]
+ resp = {
+ 'version': 1,
+ 'config': [
+ {
+ 'type': 'physical',
+ 'name': 'ens2',
+ 'subnets': [{'type': 'dhcp4'}]
}
+ ]
+ }
netcfg = self.datasource.network_config
self.assertEqual(netcfg, resp)
@@ -441,12 +456,16 @@ class TestDataSourceScaleway(HttprettyTestCase):
self.datasource.metadata['ipv6'] = None
self.datasource._network_config = None
- resp = {'version': 1,
- 'config': [{
- 'type': 'physical',
- 'name': 'ens2',
- 'subnets': [{'type': 'dhcp4'}]}]
+ resp = {
+ 'version': 1,
+ 'config': [
+ {
+ 'type': 'physical',
+ 'name': 'ens2',
+ 'subnets': [{'type': 'dhcp4'}]
}
+ ]
+ }
netcfg = self.datasource.network_config
self.assertEqual(netcfg, resp)
diff --git a/tests/unittests/test_datasource/test_smartos.py b/tests/unittests/test_datasource/test_smartos.py
index 62084de5..5847a384 100644
--- a/tests/unittests/test_datasource/test_smartos.py
+++ b/tests/unittests/test_datasource/test_smartos.py
@@ -12,8 +12,6 @@ order to validate return responses.
'''
-from __future__ import print_function
-
from binascii import crc32
import json
import multiprocessing
@@ -22,7 +20,7 @@ import os.path
import re
import signal
import stat
-import unittest2
+import unittest
import uuid
from cloudinit import serial
@@ -34,8 +32,8 @@ from cloudinit.sources.DataSourceSmartOS import (
from cloudinit.event import EventType
from cloudinit import helpers as c_helpers
-from cloudinit.util import (
- b64e, subp, ProcessExecutionError, which, write_file)
+from cloudinit.util import (b64e, write_file)
+from cloudinit.subp import (subp, ProcessExecutionError, which)
from cloudinit.tests.helpers import (
CiTestCase, mock, FilesystemMockingTestCase, skipIf)
@@ -669,7 +667,7 @@ class TestIdentifyFile(CiTestCase):
with self.allow_subp(["file"]):
self.assertEqual("text/plain", identify_file(fname))
- @mock.patch(DSMOS + ".util.subp")
+ @mock.patch(DSMOS + ".subp.subp")
def test_returns_none_on_error(self, m_subp):
"""On 'file' execution error, None should be returned."""
m_subp.side_effect = ProcessExecutionError("FILE_FAILED", exit_code=99)
@@ -1095,11 +1093,11 @@ class TestNetworkConversion(CiTestCase):
self.assertEqual(expected, found)
-@unittest2.skipUnless(get_smartos_environ() == SMARTOS_ENV_KVM,
- "Only supported on KVM and bhyve guests under SmartOS")
-@unittest2.skipUnless(os.access(SERIAL_DEVICE, os.W_OK),
- "Requires write access to " + SERIAL_DEVICE)
-@unittest2.skipUnless(HAS_PYSERIAL is True, "pyserial not available")
+@unittest.skipUnless(get_smartos_environ() == SMARTOS_ENV_KVM,
+ "Only supported on KVM and bhyve guests under SmartOS")
+@unittest.skipUnless(os.access(SERIAL_DEVICE, os.W_OK),
+ "Requires write access to " + SERIAL_DEVICE)
+@unittest.skipUnless(HAS_PYSERIAL is True, "pyserial not available")
class TestSerialConcurrency(CiTestCase):
"""
This class tests locking on an actual serial port, and as such can only