summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cloudinit/dmi.py131
-rw-r--r--cloudinit/sources/DataSourceAliYun.py4
-rw-r--r--cloudinit/sources/DataSourceAltCloud.py3
-rwxr-xr-xcloudinit/sources/DataSourceAzure.py5
-rw-r--r--cloudinit/sources/DataSourceCloudSigma.py4
-rw-r--r--cloudinit/sources/DataSourceEc2.py9
-rw-r--r--cloudinit/sources/DataSourceExoscale.py3
-rw-r--r--cloudinit/sources/DataSourceGCE.py5
-rw-r--r--cloudinit/sources/DataSourceHetzner.py5
-rw-r--r--cloudinit/sources/DataSourceNoCloud.py3
-rw-r--r--cloudinit/sources/DataSourceOVF.py5
-rw-r--r--cloudinit/sources/DataSourceOpenStack.py5
-rw-r--r--cloudinit/sources/DataSourceOracle.py5
-rw-r--r--cloudinit/sources/DataSourceScaleway.py3
-rw-r--r--cloudinit/sources/DataSourceSmartOS.py3
-rw-r--r--cloudinit/sources/__init__.py3
-rw-r--r--cloudinit/sources/helpers/digitalocean.py5
-rw-r--r--cloudinit/sources/tests/test_oracle.py6
-rw-r--r--cloudinit/tests/test_dmi.py131
-rw-r--r--cloudinit/util.py119
-rw-r--r--tests/unittests/test_datasource/test_aliyun.py6
-rw-r--r--tests/unittests/test_datasource/test_altcloud.py21
-rw-r--r--tests/unittests/test_datasource/test_azure.py10
-rw-r--r--tests/unittests/test_datasource/test_nocloud.py3
-rw-r--r--tests/unittests/test_datasource/test_openstack.py16
-rw-r--r--tests/unittests/test_datasource/test_ovf.py16
-rw-r--r--tests/unittests/test_datasource/test_scaleway.py8
-rw-r--r--tests/unittests/test_util.py123
28 files changed, 348 insertions, 312 deletions
diff --git a/cloudinit/dmi.py b/cloudinit/dmi.py
new file mode 100644
index 00000000..96e0e423
--- /dev/null
+++ b/cloudinit/dmi.py
@@ -0,0 +1,131 @@
+# This file is part of cloud-init. See LICENSE file for license information.
+from cloudinit import log as logging
+from cloudinit import subp
+from cloudinit.util import is_container
+
+import os
+
+LOG = logging.getLogger(__name__)
+
+# Path for DMI Data
+DMI_SYS_PATH = "/sys/class/dmi/id"
+
+# dmidecode and /sys/class/dmi/id/* use different names for the same value,
+# this allows us to refer to them by one canonical name
+DMIDECODE_TO_DMI_SYS_MAPPING = {
+ 'baseboard-asset-tag': 'board_asset_tag',
+ 'baseboard-manufacturer': 'board_vendor',
+ 'baseboard-product-name': 'board_name',
+ 'baseboard-serial-number': 'board_serial',
+ 'baseboard-version': 'board_version',
+ 'bios-release-date': 'bios_date',
+ 'bios-vendor': 'bios_vendor',
+ 'bios-version': 'bios_version',
+ 'chassis-asset-tag': 'chassis_asset_tag',
+ 'chassis-manufacturer': 'chassis_vendor',
+ 'chassis-serial-number': 'chassis_serial',
+ 'chassis-version': 'chassis_version',
+ 'system-manufacturer': 'sys_vendor',
+ 'system-product-name': 'product_name',
+ 'system-serial-number': 'product_serial',
+ 'system-uuid': 'product_uuid',
+ 'system-version': 'product_version',
+}
+
+
+def _read_dmi_syspath(key):
+ """
+ Reads dmi data with from /sys/class/dmi/id
+ """
+ if key not in DMIDECODE_TO_DMI_SYS_MAPPING:
+ return None
+ mapped_key = DMIDECODE_TO_DMI_SYS_MAPPING[key]
+ dmi_key_path = "{0}/{1}".format(DMI_SYS_PATH, mapped_key)
+
+ LOG.debug("querying dmi data %s", dmi_key_path)
+ if not os.path.exists(dmi_key_path):
+ LOG.debug("did not find %s", dmi_key_path)
+ return None
+
+ try:
+ with open(dmi_key_path, "rb") as fp:
+ key_data = fp.read()
+ except PermissionError:
+ LOG.debug("Could not read %s", dmi_key_path)
+ return None
+
+ # uninitialized dmi values show as all \xff and /sys appends a '\n'.
+ # in that event, return empty string.
+ if key_data == b'\xff' * (len(key_data) - 1) + b'\n':
+ key_data = b""
+
+ try:
+ return key_data.decode('utf8').strip()
+ except UnicodeDecodeError as e:
+ LOG.error("utf-8 decode of content (%s) in %s failed: %s",
+ dmi_key_path, key_data, e)
+
+ return None
+
+
+def _call_dmidecode(key, dmidecode_path):
+ """
+ Calls out to dmidecode to get the data out. This is mostly for supporting
+ OS's without /sys/class/dmi/id support.
+ """
+ try:
+ cmd = [dmidecode_path, "--string", key]
+ (result, _err) = subp.subp(cmd)
+ result = result.strip()
+ LOG.debug("dmidecode returned '%s' for '%s'", result, key)
+ if result.replace(".", "") == "":
+ return ""
+ return result
+ except (IOError, OSError) as e:
+ LOG.debug('failed dmidecode cmd: %s\n%s', cmd, e)
+ return None
+
+
+def read_dmi_data(key):
+ """
+ Wrapper for reading DMI data.
+
+ If running in a container return None. This is because DMI data is
+ assumed to be not useful in a container as it does not represent the
+ container but rather the host.
+
+ This will do the following (returning the first that produces a
+ result):
+ 1) Use a mapping to translate `key` from dmidecode naming to
+ sysfs naming and look in /sys/class/dmi/... for a value.
+ 2) Use `key` as a sysfs key directly and look in /sys/class/dmi/...
+ 3) Fall-back to passing `key` to `dmidecode --string`.
+
+ If all of the above fail to find a value, None will be returned.
+ """
+
+ if is_container():
+ return None
+
+ syspath_value = _read_dmi_syspath(key)
+ if syspath_value is not None:
+ return syspath_value
+
+ def is_x86(arch):
+ return (arch == 'x86_64' or (arch[0] == 'i' and arch[2:] == '86'))
+
+ # running dmidecode can be problematic on some arches (LP: #1243287)
+ uname_arch = os.uname()[4]
+ if not (is_x86(uname_arch) or uname_arch in ('aarch64', 'amd64')):
+ LOG.debug("dmidata is not supported on %s", uname_arch)
+ return None
+
+ dmidecode_path = subp.which('dmidecode')
+ if dmidecode_path:
+ return _call_dmidecode(key, dmidecode_path)
+
+ LOG.warning("did not find either path %s or dmidecode command",
+ DMI_SYS_PATH)
+ return None
+
+# vi: ts=4 expandtab
diff --git a/cloudinit/sources/DataSourceAliYun.py b/cloudinit/sources/DataSourceAliYun.py
index 45cc9f00..09052873 100644
--- a/cloudinit/sources/DataSourceAliYun.py
+++ b/cloudinit/sources/DataSourceAliYun.py
@@ -1,8 +1,8 @@
# This file is part of cloud-init. See LICENSE file for license information.
+from cloudinit import dmi
from cloudinit import sources
from cloudinit.sources import DataSourceEc2 as EC2
-from cloudinit import util
ALIYUN_PRODUCT = "Alibaba Cloud ECS"
@@ -30,7 +30,7 @@ class DataSourceAliYun(EC2.DataSourceEc2):
def _is_aliyun():
- return util.read_dmi_data('system-product-name') == ALIYUN_PRODUCT
+ return dmi.read_dmi_data('system-product-name') == ALIYUN_PRODUCT
def parse_public_keys(public_keys):
diff --git a/cloudinit/sources/DataSourceAltCloud.py b/cloudinit/sources/DataSourceAltCloud.py
index ac3ecc3d..cd93412a 100644
--- a/cloudinit/sources/DataSourceAltCloud.py
+++ b/cloudinit/sources/DataSourceAltCloud.py
@@ -16,6 +16,7 @@ import errno
import os
import os.path
+from cloudinit import dmi
from cloudinit import log as logging
from cloudinit import sources
from cloudinit import subp
@@ -109,7 +110,7 @@ class DataSourceAltCloud(sources.DataSource):
CLOUD_INFO_FILE)
return 'UNKNOWN'
return cloud_type
- system_name = util.read_dmi_data("system-product-name")
+ system_name = dmi.read_dmi_data("system-product-name")
if not system_name:
return 'UNKNOWN'
diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py
index 70e32f46..fa3e0a2b 100755
--- a/cloudinit/sources/DataSourceAzure.py
+++ b/cloudinit/sources/DataSourceAzure.py
@@ -15,6 +15,7 @@ from time import time
from xml.dom import minidom
import xml.etree.ElementTree as ET
+from cloudinit import dmi
from cloudinit import log as logging
from cloudinit import net
from cloudinit.event import EventType
@@ -630,7 +631,7 @@ class DataSourceAzure(sources.DataSource):
def _iid(self, previous=None):
prev_iid_path = os.path.join(
self.paths.get_cpath('data'), 'instance-id')
- iid = util.read_dmi_data('system-uuid')
+ iid = dmi.read_dmi_data('system-uuid')
if os.path.exists(prev_iid_path):
previous = util.load_file(prev_iid_path).strip()
if is_byte_swapped(previous, iid):
@@ -1630,7 +1631,7 @@ def _is_platform_viable(seed_dir):
description="found azure asset tag",
parent=azure_ds_reporter
) as evt:
- asset_tag = util.read_dmi_data('chassis-asset-tag')
+ asset_tag = dmi.read_dmi_data('chassis-asset-tag')
if asset_tag == AZURE_CHASSIS_ASSET_TAG:
return True
msg = "Non-Azure DMI asset tag '%s' discovered." % asset_tag
diff --git a/cloudinit/sources/DataSourceCloudSigma.py b/cloudinit/sources/DataSourceCloudSigma.py
index df88f677..f63baf74 100644
--- a/cloudinit/sources/DataSourceCloudSigma.py
+++ b/cloudinit/sources/DataSourceCloudSigma.py
@@ -9,9 +9,9 @@ import re
from cloudinit.cs_utils import Cepko, SERIAL_PORT
+from cloudinit import dmi
from cloudinit import log as logging
from cloudinit import sources
-from cloudinit import util
LOG = logging.getLogger(__name__)
@@ -38,7 +38,7 @@ class DataSourceCloudSigma(sources.DataSource):
"""
LOG.debug("determining hypervisor product name via dmi data")
- sys_product_name = util.read_dmi_data("system-product-name")
+ sys_product_name = dmi.read_dmi_data("system-product-name")
if not sys_product_name:
LOG.debug("system-product-name not available in dmi data")
return False
diff --git a/cloudinit/sources/DataSourceEc2.py b/cloudinit/sources/DataSourceEc2.py
index 1d09c12a..1930a509 100644
--- a/cloudinit/sources/DataSourceEc2.py
+++ b/cloudinit/sources/DataSourceEc2.py
@@ -11,6 +11,7 @@
import os
import time
+from cloudinit import dmi
from cloudinit import ec2_utils as ec2
from cloudinit import log as logging
from cloudinit import net
@@ -699,26 +700,26 @@ def _collect_platform_data():
uuid = util.load_file("/sys/hypervisor/uuid").strip()
data['uuid_source'] = 'hypervisor'
except Exception:
- uuid = util.read_dmi_data('system-uuid')
+ uuid = dmi.read_dmi_data('system-uuid')
data['uuid_source'] = 'dmi'
if uuid is None:
uuid = ''
data['uuid'] = uuid.lower()
- serial = util.read_dmi_data('system-serial-number')
+ serial = dmi.read_dmi_data('system-serial-number')
if serial is None:
serial = ''
data['serial'] = serial.lower()
- asset_tag = util.read_dmi_data('chassis-asset-tag')
+ asset_tag = dmi.read_dmi_data('chassis-asset-tag')
if asset_tag is None:
asset_tag = ''
data['asset_tag'] = asset_tag.lower()
- vendor = util.read_dmi_data('system-manufacturer')
+ vendor = dmi.read_dmi_data('system-manufacturer')
data['vendor'] = (vendor if vendor else '').lower()
return data
diff --git a/cloudinit/sources/DataSourceExoscale.py b/cloudinit/sources/DataSourceExoscale.py
index d59aefd1..adee6d79 100644
--- a/cloudinit/sources/DataSourceExoscale.py
+++ b/cloudinit/sources/DataSourceExoscale.py
@@ -3,6 +3,7 @@
#
# This file is part of cloud-init. See LICENSE file for license information.
+from cloudinit import dmi
from cloudinit import ec2_utils as ec2
from cloudinit import log as logging
from cloudinit import sources
@@ -135,7 +136,7 @@ class DataSourceExoscale(sources.DataSource):
return self.extra_config
def _is_platform_viable(self):
- return util.read_dmi_data('system-product-name').startswith(
+ return dmi.read_dmi_data('system-product-name').startswith(
EXOSCALE_DMI_NAME)
diff --git a/cloudinit/sources/DataSourceGCE.py b/cloudinit/sources/DataSourceGCE.py
index 0ec5f6ec..746caddb 100644
--- a/cloudinit/sources/DataSourceGCE.py
+++ b/cloudinit/sources/DataSourceGCE.py
@@ -7,6 +7,7 @@ import json
from base64 import b64decode
+from cloudinit import dmi
from cloudinit.distros import ug_util
from cloudinit import log as logging
from cloudinit import sources
@@ -248,12 +249,12 @@ def read_md(address=None, platform_check=True):
def platform_reports_gce():
- pname = util.read_dmi_data('system-product-name') or "N/A"
+ pname = dmi.read_dmi_data('system-product-name') or "N/A"
if pname == "Google Compute Engine":
return True
# system-product-name is not always guaranteed (LP: #1674861)
- serial = util.read_dmi_data('system-serial-number') or "N/A"
+ serial = dmi.read_dmi_data('system-serial-number') or "N/A"
if serial.startswith("GoogleCloud-"):
return True
diff --git a/cloudinit/sources/DataSourceHetzner.py b/cloudinit/sources/DataSourceHetzner.py
index 8e4d4b69..c7c88dd7 100644
--- a/cloudinit/sources/DataSourceHetzner.py
+++ b/cloudinit/sources/DataSourceHetzner.py
@@ -6,6 +6,7 @@
"""Hetzner Cloud API Documentation
https://docs.hetzner.cloud/"""
+from cloudinit import dmi
from cloudinit import log as logging
from cloudinit import net as cloudnet
from cloudinit import sources
@@ -113,11 +114,11 @@ class DataSourceHetzner(sources.DataSource):
def get_hcloud_data():
- vendor_name = util.read_dmi_data('system-manufacturer')
+ vendor_name = dmi.read_dmi_data('system-manufacturer')
if vendor_name != "Hetzner":
return (False, None)
- serial = util.read_dmi_data("system-serial-number")
+ serial = dmi.read_dmi_data("system-serial-number")
if serial:
LOG.debug("Running on Hetzner Cloud: serial=%s", serial)
else:
diff --git a/cloudinit/sources/DataSourceNoCloud.py b/cloudinit/sources/DataSourceNoCloud.py
index d4a175e8..a126aad3 100644
--- a/cloudinit/sources/DataSourceNoCloud.py
+++ b/cloudinit/sources/DataSourceNoCloud.py
@@ -11,6 +11,7 @@
import errno
import os
+from cloudinit import dmi
from cloudinit import log as logging
from cloudinit.net import eni
from cloudinit import sources
@@ -61,7 +62,7 @@ class DataSourceNoCloud(sources.DataSource):
# Parse the system serial label from dmi. If not empty, try parsing
# like the commandline
md = {}
- serial = util.read_dmi_data('system-serial-number')
+ serial = dmi.read_dmi_data('system-serial-number')
if serial and load_cmdline_data(md, serial):
found.append("dmi")
mydata = _merge_new_seed(mydata, {'meta-data': md})
diff --git a/cloudinit/sources/DataSourceOVF.py b/cloudinit/sources/DataSourceOVF.py
index a5ccb8f6..741c140a 100644
--- a/cloudinit/sources/DataSourceOVF.py
+++ b/cloudinit/sources/DataSourceOVF.py
@@ -14,6 +14,7 @@ import re
import time
from xml.dom import minidom
+from cloudinit import dmi
from cloudinit import log as logging
from cloudinit import sources
from cloudinit import subp
@@ -83,7 +84,7 @@ class DataSourceOVF(sources.DataSource):
(seedfile, contents) = get_ovf_env(self.paths.seed_dir)
- system_type = util.read_dmi_data("system-product-name")
+ system_type = dmi.read_dmi_data("system-product-name")
if system_type is None:
LOG.debug("No system-product-name found")
@@ -322,7 +323,7 @@ class DataSourceOVF(sources.DataSource):
return True
def _get_subplatform(self):
- system_type = util.read_dmi_data("system-product-name").lower()
+ system_type = dmi.read_dmi_data("system-product-name").lower()
if system_type == 'vmware':
return 'vmware (%s)' % self.seed
return 'ovf (%s)' % self.seed
diff --git a/cloudinit/sources/DataSourceOpenStack.py b/cloudinit/sources/DataSourceOpenStack.py
index 0ede0a0e..b3406c67 100644
--- a/cloudinit/sources/DataSourceOpenStack.py
+++ b/cloudinit/sources/DataSourceOpenStack.py
@@ -6,6 +6,7 @@
import time
+from cloudinit import dmi
from cloudinit import log as logging
from cloudinit.net.dhcp import EphemeralDHCPv4, NoDHCPLeaseError
from cloudinit import sources
@@ -225,10 +226,10 @@ def detect_openstack(accept_oracle=False):
"""Return True when a potential OpenStack platform is detected."""
if not util.is_x86():
return True # Non-Intel cpus don't properly report dmi product names
- product_name = util.read_dmi_data('system-product-name')
+ product_name = dmi.read_dmi_data('system-product-name')
if product_name in VALID_DMI_PRODUCT_NAMES:
return True
- elif util.read_dmi_data('chassis-asset-tag') in VALID_DMI_ASSET_TAGS:
+ elif dmi.read_dmi_data('chassis-asset-tag') in VALID_DMI_ASSET_TAGS:
return True
elif accept_oracle and oracle._is_platform_viable():
return True
diff --git a/cloudinit/sources/DataSourceOracle.py b/cloudinit/sources/DataSourceOracle.py
index 20d6487d..bf81b10b 100644
--- a/cloudinit/sources/DataSourceOracle.py
+++ b/cloudinit/sources/DataSourceOracle.py
@@ -17,6 +17,7 @@ import base64
from collections import namedtuple
from contextlib import suppress as noop
+from cloudinit import dmi
from cloudinit import log as logging
from cloudinit import net, sources, util
from cloudinit.net import (
@@ -273,12 +274,12 @@ class DataSourceOracle(sources.DataSource):
def _read_system_uuid():
- sys_uuid = util.read_dmi_data('system-uuid')
+ sys_uuid = dmi.read_dmi_data('system-uuid')
return None if sys_uuid is None else sys_uuid.lower()
def _is_platform_viable():
- asset_tag = util.read_dmi_data('chassis-asset-tag')
+ asset_tag = dmi.read_dmi_data('chassis-asset-tag')
return asset_tag == CHASSIS_ASSET_TAG
diff --git a/cloudinit/sources/DataSourceScaleway.py b/cloudinit/sources/DataSourceScaleway.py
index 83c2bf65..41be7665 100644
--- a/cloudinit/sources/DataSourceScaleway.py
+++ b/cloudinit/sources/DataSourceScaleway.py
@@ -25,6 +25,7 @@ import requests
from requests.packages.urllib3.connection import HTTPConnection
from requests.packages.urllib3.poolmanager import PoolManager
+from cloudinit import dmi
from cloudinit import log as logging
from cloudinit import sources
from cloudinit import url_helper
@@ -56,7 +57,7 @@ def on_scaleway():
* the initrd created the file /var/run/scaleway.
* "scaleway" is in the kernel cmdline.
"""
- vendor_name = util.read_dmi_data('system-manufacturer')
+ vendor_name = dmi.read_dmi_data('system-manufacturer')
if vendor_name == 'Scaleway':
return True
diff --git a/cloudinit/sources/DataSourceSmartOS.py b/cloudinit/sources/DataSourceSmartOS.py
index f1f903bc..fd292baa 100644
--- a/cloudinit/sources/DataSourceSmartOS.py
+++ b/cloudinit/sources/DataSourceSmartOS.py
@@ -30,6 +30,7 @@ import random
import re
import socket
+from cloudinit import dmi
from cloudinit import log as logging
from cloudinit import serial
from cloudinit import sources
@@ -767,7 +768,7 @@ def get_smartos_environ(uname_version=None, product_name=None):
return SMARTOS_ENV_LX_BRAND
if product_name is None:
- system_type = util.read_dmi_data("system-product-name")
+ system_type = dmi.read_dmi_data("system-product-name")
else:
system_type = product_name
diff --git a/cloudinit/sources/__init__.py b/cloudinit/sources/__init__.py
index c4d60fff..9dccc687 100644
--- a/cloudinit/sources/__init__.py
+++ b/cloudinit/sources/__init__.py
@@ -14,6 +14,7 @@ import json
import os
from collections import namedtuple
+from cloudinit import dmi
from cloudinit import importer
from cloudinit import log as logging
from cloudinit import net
@@ -809,7 +810,7 @@ def instance_id_matches_system_uuid(instance_id, field='system-uuid'):
if not instance_id:
return False
- dmi_value = util.read_dmi_data(field)
+ dmi_value = dmi.read_dmi_data(field)
if not dmi_value:
return False
return instance_id.lower() == dmi_value.lower()
diff --git a/cloudinit/sources/helpers/digitalocean.py b/cloudinit/sources/helpers/digitalocean.py
index b545c4d6..f9be4ecb 100644
--- a/cloudinit/sources/helpers/digitalocean.py
+++ b/cloudinit/sources/helpers/digitalocean.py
@@ -5,6 +5,7 @@
import json
import random
+from cloudinit import dmi
from cloudinit import log as logging
from cloudinit import net as cloudnet
from cloudinit import url_helper
@@ -195,11 +196,11 @@ def read_sysinfo():
# SMBIOS information
# Detect if we are on DigitalOcean and return the Droplet's ID
- vendor_name = util.read_dmi_data("system-manufacturer")
+ vendor_name = dmi.read_dmi_data("system-manufacturer")
if vendor_name != "DigitalOcean":
return (False, None)
- droplet_id = util.read_dmi_data("system-serial-number")
+ droplet_id = dmi.read_dmi_data("system-serial-number")
if droplet_id:
LOG.debug("system identified via SMBIOS as DigitalOcean Droplet: %s",
droplet_id)
diff --git a/cloudinit/sources/tests/test_oracle.py b/cloudinit/sources/tests/test_oracle.py
index 7bd23813..a7bbdfd9 100644
--- a/cloudinit/sources/tests/test_oracle.py
+++ b/cloudinit/sources/tests/test_oracle.py
@@ -153,20 +153,20 @@ class TestDataSourceOracle:
class TestIsPlatformViable(test_helpers.CiTestCase):
- @mock.patch(DS_PATH + ".util.read_dmi_data",
+ @mock.patch(DS_PATH + ".dmi.read_dmi_data",
return_value=oracle.CHASSIS_ASSET_TAG)
def test_expected_viable(self, m_read_dmi_data):
"""System with known chassis tag is viable."""
self.assertTrue(oracle._is_platform_viable())
m_read_dmi_data.assert_has_calls([mock.call('chassis-asset-tag')])
- @mock.patch(DS_PATH + ".util.read_dmi_data", return_value=None)
+ @mock.patch(DS_PATH + ".dmi.read_dmi_data", return_value=None)
def test_expected_not_viable_dmi_data_none(self, m_read_dmi_data):
"""System without known chassis tag is not viable."""
self.assertFalse(oracle._is_platform_viable())
m_read_dmi_data.assert_has_calls([mock.call('chassis-asset-tag')])
- @mock.patch(DS_PATH + ".util.read_dmi_data", return_value="LetsGoCubs")
+ @mock.patch(DS_PATH + ".dmi.read_dmi_data", return_value="LetsGoCubs")
def test_expected_not_viable_other(self, m_read_dmi_data):
"""System with unnown chassis tag is not viable."""
self.assertFalse(oracle._is_platform_viable())
diff --git a/cloudinit/tests/test_dmi.py b/cloudinit/tests/test_dmi.py
new file mode 100644
index 00000000..4a8af257
--- /dev/null
+++ b/cloudinit/tests/test_dmi.py
@@ -0,0 +1,131 @@
+from cloudinit.tests import helpers
+from cloudinit import dmi
+from cloudinit import util
+from cloudinit import subp
+
+import os
+import tempfile
+import shutil
+from unittest import mock
+
+
+class TestReadDMIData(helpers.FilesystemMockingTestCase):
+
+ def setUp(self):
+ super(TestReadDMIData, self).setUp()
+ self.new_root = tempfile.mkdtemp()
+ self.addCleanup(shutil.rmtree, self.new_root)
+ self.reRoot(self.new_root)
+ p = mock.patch("cloudinit.dmi.is_container", return_value=False)
+ self.addCleanup(p.stop)
+ self._m_is_container = p.start()
+
+ def _create_sysfs_parent_directory(self):
+ util.ensure_dir(os.path.join('sys', 'class', 'dmi', 'id'))
+
+ def _create_sysfs_file(self, key, content):
+ """Mocks the sys path found on Linux systems."""
+ self._create_sysfs_parent_directory()
+ dmi_key = "/sys/class/dmi/id/{0}".format(key)
+ util.write_file(dmi_key, content)
+
+ def _configure_dmidecode_return(self, key, content, error=None):
+ """
+ In order to test a missing sys path and call outs to dmidecode, this
+ function fakes the results of dmidecode to test the results.
+ """
+ def _dmidecode_subp(cmd):
+ if cmd[-1] != key:
+ raise subp.ProcessExecutionError()
+ return (content, error)
+
+ self.patched_funcs.enter_context(
+ mock.patch("cloudinit.dmi.subp.which", side_effect=lambda _: True))
+ self.patched_funcs.enter_context(
+ mock.patch("cloudinit.dmi.subp.subp", side_effect=_dmidecode_subp))
+
+ def patch_mapping(self, new_mapping):
+ self.patched_funcs.enter_context(
+ mock.patch('cloudinit.dmi.DMIDECODE_TO_DMI_SYS_MAPPING',
+ new_mapping))
+
+ def test_sysfs_used_with_key_in_mapping_and_file_on_disk(self):
+ self.patch_mapping({'mapped-key': 'mapped-value'})
+ expected_dmi_value = 'sys-used-correctly'
+ self._create_sysfs_file('mapped-value', expected_dmi_value)
+ self._configure_dmidecode_return('mapped-key', 'wrong-wrong-wrong')
+ self.assertEqual(expected_dmi_value, dmi.read_dmi_data('mapped-key'))
+
+ def test_dmidecode_used_if_no_sysfs_file_on_disk(self):
+ self.patch_mapping({})
+ self._create_sysfs_parent_directory()
+ expected_dmi_value = 'dmidecode-used'
+ self._configure_dmidecode_return('use-dmidecode', expected_dmi_value)
+ with mock.patch("cloudinit.util.os.uname") as m_uname:
+ m_uname.return_value = ('x-sysname', 'x-nodename',
+ 'x-release', 'x-version', 'x86_64')
+ self.assertEqual(expected_dmi_value,
+ dmi.read_dmi_data('use-dmidecode'))
+
+ def test_dmidecode_not_used_on_arm(self):
+ self.patch_mapping({})
+ print("current =%s", subp)
+ self._create_sysfs_parent_directory()
+ dmi_val = 'from-dmidecode'
+ dmi_name = 'use-dmidecode'
+ self._configure_dmidecode_return(dmi_name, dmi_val)
+ print("now =%s", subp)
+
+ expected = {'armel': None, 'aarch64': dmi_val, 'x86_64': dmi_val}
+ found = {}
+ # we do not run the 'dmi-decode' binary on some arches
+ # verify that anything requested that is not in the sysfs dir
+ # will return None on those arches.
+ with mock.patch("cloudinit.util.os.uname") as m_uname:
+ for arch in expected:
+ m_uname.return_value = ('x-sysname', 'x-nodename',
+ 'x-release', 'x-version', arch)
+ print("now2 =%s", subp)
+ found[arch] = dmi.read_dmi_data(dmi_name)
+ self.assertEqual(expected, found)
+
+ def test_none_returned_if_neither_source_has_data(self):
+ self.patch_mapping({})
+ self._configure_dmidecode_return('key', 'value')
+ self.assertIsNone(dmi.read_dmi_data('expect-fail'))
+
+ def test_none_returned_if_dmidecode_not_in_path(self):
+ self.patched_funcs.enter_context(
+ mock.patch.object(subp, 'which', lambda _: False))
+ self.patch_mapping({})
+ self.assertIsNone(dmi.read_dmi_data('expect-fail'))
+
+ def test_empty_string_returned_instead_of_foxfox(self):
+ # uninitialized dmi values show as \xff, return empty string
+ my_len = 32
+ dmi_value = b'\xff' * my_len + b'\n'
+ expected = ""
+ dmi_key = 'system-product-name'
+ sysfs_key = 'product_name'
+ self._create_sysfs_file(sysfs_key, dmi_value)
+ self.assertEqual(expected, dmi.read_dmi_data(dmi_key))
+
+ def test_container_returns_none(self):
+ """In a container read_dmi_data should always return None."""
+
+ # first verify we get the value if not in container
+ self._m_is_container.return_value = False
+ key, val = ("system-product-name", "my_product")
+ self._create_sysfs_file('product_name', val)
+ self.assertEqual(val, dmi.read_dmi_data(key))
+
+ # then verify in container returns None
+ self._m_is_container.return_value = True
+ self.assertIsNone(dmi.read_dmi_data(key))
+
+ def test_container_returns_none_on_unknown(self):
+ """In a container even bogus keys return None."""
+ self._m_is_container.return_value = True
+ self._create_sysfs_file('product_name', "should-be-ignored")
+ self.assertIsNone(dmi.read_dmi_data("bogus"))
+ self.assertIsNone(dmi.read_dmi_data("system-product-name"))
diff --git a/cloudinit/util.py b/cloudinit/util.py
index b8856af1..bdb3694d 100644
--- a/cloudinit/util.py
+++ b/cloudinit/util.py
@@ -159,32 +159,6 @@ def fully_decoded_payload(part):
return cte_payload
-# Path for DMI Data
-DMI_SYS_PATH = "/sys/class/dmi/id"
-
-# dmidecode and /sys/class/dmi/id/* use different names for the same value,
-# this allows us to refer to them by one canonical name
-DMIDECODE_TO_DMI_SYS_MAPPING = {
- 'baseboard-asset-tag': 'board_asset_tag',
- 'baseboard-manufacturer': 'board_vendor',
- 'baseboard-product-name': 'board_name',
- 'baseboard-serial-number': 'board_serial',
- 'baseboard-version': 'board_version',
- 'bios-release-date': 'bios_date',
- 'bios-vendor': 'bios_vendor',
- 'bios-version': 'bios_version',
- 'chassis-asset-tag': 'chassis_asset_tag',
- 'chassis-manufacturer': 'chassis_vendor',
- 'chassis-serial-number': 'chassis_serial',
- 'chassis-version': 'chassis_version',
- 'system-manufacturer': 'sys_vendor',
- 'system-product-name': 'product_name',
- 'system-serial-number': 'product_serial',
- 'system-uuid': 'product_uuid',
- 'system-version': 'product_version',
-}
-
-
class SeLinuxGuard(object):
def __init__(self, path, recursive=False):
# Late import since it might not always
@@ -2421,57 +2395,6 @@ def human2bytes(size):
return int(num * mpliers[mplier])
-def _read_dmi_syspath(key):
- """
- Reads dmi data with from /sys/class/dmi/id
- """
- if key not in DMIDECODE_TO_DMI_SYS_MAPPING:
- return None
- mapped_key = DMIDECODE_TO_DMI_SYS_MAPPING[key]
- dmi_key_path = "{0}/{1}".format(DMI_SYS_PATH, mapped_key)
- LOG.debug("querying dmi data %s", dmi_key_path)
- try:
- if not os.path.exists(dmi_key_path):
- LOG.debug("did not find %s", dmi_key_path)
- return None
-
- key_data = load_file(dmi_key_path, decode=False)
- if not key_data:
- LOG.debug("%s did not return any data", dmi_key_path)
- return None
-
- # uninitialized dmi values show as all \xff and /sys appends a '\n'.
- # in that event, return a string of '.' in the same length.
- if key_data == b'\xff' * (len(key_data) - 1) + b'\n':
- key_data = b""
-
- str_data = key_data.decode('utf8').strip()
- LOG.debug("dmi data %s returned %s", dmi_key_path, str_data)
- return str_data
-
- except Exception:
- logexc(LOG, "failed read of %s", dmi_key_path)
- return None
-
-
-def _call_dmidecode(key, dmidecode_path):
- """
- Calls out to dmidecode to get the data out. This is mostly for supporting
- OS's without /sys/class/dmi/id support.
- """
- try:
- cmd = [dmidecode_path, "--string", key]
- (result, _err) = subp.subp(cmd)
- result = result.strip()
- LOG.debug("dmidecode returned '%s' for '%s'", result, key)
- if result.replace(".", "") == "":
- return ""
- return result
- except (IOError, OSError) as e:
- LOG.debug('failed dmidecode cmd: %s\n%s', cmd, e)
- return None
-
-
def is_x86(uname_arch=None):
"""Return True if platform is x86-based"""
if uname_arch is None:
@@ -2482,48 +2405,6 @@ def is_x86(uname_arch=None):
return x86_arch_match
-def read_dmi_data(key):
- """
- Wrapper for reading DMI data.
-
- If running in a container return None. This is because DMI data is
- assumed to be not useful in a container as it does not represent the
- container but rather the host.
-
- This will do the following (returning the first that produces a
- result):
- 1) Use a mapping to translate `key` from dmidecode naming to
- sysfs naming and look in /sys/class/dmi/... for a value.
- 2) Use `key` as a sysfs key directly and look in /sys/class/dmi/...
- 3) Fall-back to passing `key` to `dmidecode --string`.
-
- If all of the above fail to find a value, None will be returned.
- """
-
- if is_container():
- return None
-
- syspath_value = _read_dmi_syspath(key)
- if syspath_value is not None:
- return syspath_value
-
- # running dmidecode can be problematic on some arches (LP: #1243287)
- uname_arch = os.uname()[4]
- if not (is_x86(uname_arch) or
- uname_arch == 'aarch64' or
- uname_arch == 'amd64'):
- LOG.debug("dmidata is not supported on %s", uname_arch)
- return None
-
- dmidecode_path = subp.which('dmidecode')
- if dmidecode_path:
- return _call_dmidecode(key, dmidecode_path)
-
- LOG.warning("did not find either path %s or dmidecode command",
- DMI_SYS_PATH)
- return None
-
-
def message_from_string(string):
if sys.version_info[:2] < (2, 7):
return email.message_from_file(io.StringIO(string))
diff --git a/tests/unittests/test_datasource/test_aliyun.py b/tests/unittests/test_datasource/test_aliyun.py
index b626229e..eb2828d5 100644
--- a/tests/unittests/test_datasource/test_aliyun.py
+++ b/tests/unittests/test_datasource/test_aliyun.py
@@ -188,7 +188,7 @@ class TestIsAliYun(test_helpers.CiTestCase):
ALIYUN_PRODUCT = 'Alibaba Cloud ECS'
read_dmi_data_expected = [mock.call('system-product-name')]
- @mock.patch("cloudinit.sources.DataSourceAliYun.util.read_dmi_data")
+ @mock.patch("cloudinit.sources.DataSourceAliYun.dmi.read_dmi_data")
def test_true_on_aliyun_product(self, m_read_dmi_data):
"""Should return true if the dmi product data has expected value."""
m_read_dmi_data.return_value = self.ALIYUN_PRODUCT
@@ -197,7 +197,7 @@ class TestIsAliYun(test_helpers.CiTestCase):
m_read_dmi_data.call_args_list)
self.assertEqual(True, ret)
- @mock.patch("cloudinit.sources.DataSourceAliYun.util.read_dmi_data")
+ @mock.patch("cloudinit.sources.DataSourceAliYun.dmi.read_dmi_data")
def test_false_on_empty_string(self, m_read_dmi_data):
"""Should return false on empty value returned."""
m_read_dmi_data.return_value = ""
@@ -206,7 +206,7 @@ class TestIsAliYun(test_helpers.CiTestCase):
m_read_dmi_data.call_args_list)
self.assertEqual(False, ret)
- @mock.patch("cloudinit.sources.DataSourceAliYun.util.read_dmi_data")
+ @mock.patch("cloudinit.sources.DataSourceAliYun.dmi.read_dmi_data")
def test_false_on_unknown_string(self, m_read_dmi_data):
"""Should return false on an unrelated string."""
m_read_dmi_data.return_value = "cubs win"
diff --git a/tests/unittests/test_datasource/test_altcloud.py b/tests/unittests/test_datasource/test_altcloud.py
index fc59d1d5..7a5393ac 100644
--- a/tests/unittests/test_datasource/test_altcloud.py
+++ b/tests/unittests/test_datasource/test_altcloud.py
@@ -14,6 +14,7 @@ import os
import shutil
import tempfile
+from cloudinit import dmi
from cloudinit import helpers
from cloudinit import subp
from cloudinit import util
@@ -88,14 +89,14 @@ class TestGetCloudType(CiTestCase):
super(TestGetCloudType, self).setUp()
self.tmp = self.tmp_dir()
self.paths = helpers.Paths({'cloud_dir': self.tmp})
- self.dmi_data = util.read_dmi_data
+ self.dmi_data = dmi.read_dmi_data
# We have a different code path for arm to deal with LP1243287
# We have to switch arch to x86_64 to avoid test failure
force_arch('x86_64')
def tearDown(self):
# Reset
- util.read_dmi_data = self.dmi_data
+ dmi.read_dmi_data = self.dmi_data
force_arch()
def test_cloud_info_file_ioerror(self):
@@ -123,7 +124,7 @@ class TestGetCloudType(CiTestCase):
Test method get_cloud_type() for RHEVm systems.
Forcing read_dmi_data return to match a RHEVm system: RHEV Hypervisor
'''
- util.read_dmi_data = _dmi_data('RHEV')
+ dmi.read_dmi_data = _dmi_data('RHEV')
dsrc = dsac.DataSourceAltCloud({}, None, self.paths)
self.assertEqual('RHEV', dsrc.get_cloud_type())
@@ -132,7 +133,7 @@ class TestGetCloudType(CiTestCase):
Test method get_cloud_type() for vSphere systems.
Forcing read_dmi_data return to match a vSphere system: RHEV Hypervisor
'''
- util.read_dmi_data = _dmi_data('VMware Virtual Platform')
+ dmi.read_dmi_data = _dmi_data('VMware Virtual Platform')
dsrc = dsac.DataSourceAltCloud({}, None, self.paths)
self.assertEqual('VSPHERE', dsrc.get_cloud_type())
@@ -141,7 +142,7 @@ class TestGetCloudType(CiTestCase):
Test method get_cloud_type() for unknown systems.
Forcing read_dmi_data return to match an unrecognized return.
'''
- util.read_dmi_data = _dmi_data('Unrecognized Platform')
+ dmi.read_dmi_data = _dmi_data('Unrecognized Platform')
dsrc = dsac.DataSourceAltCloud({}, None, self.paths)
self.assertEqual('UNKNOWN', dsrc.get_cloud_type())
@@ -219,7 +220,7 @@ class TestGetDataNoCloudInfoFile(CiTestCase):
self.tmp = self.tmp_dir()
self.paths = helpers.Paths(
{'cloud_dir': self.tmp, 'run_dir': self.tmp})
- self.dmi_data = util.read_dmi_data
+ self.dmi_data = dmi.read_dmi_data
dsac.CLOUD_INFO_FILE = \
'no such file'
# We have a different code path for arm to deal with LP1243287
@@ -230,14 +231,14 @@ class TestGetDataNoCloudInfoFile(CiTestCase):
# Reset
dsac.CLOUD_INFO_FILE = \
'/etc/sysconfig/cloud-info'
- util.read_dmi_data = self.dmi_data
+ dmi.read_dmi_data = self.dmi_data
# Return back to original arch
force_arch()
def test_rhev_no_cloud_file(self):
'''Test No cloud info file module get_data() forcing RHEV.'''
- util.read_dmi_data = _dmi_data('RHEV Hypervisor')
+ dmi.read_dmi_data = _dmi_data('RHEV Hypervisor')
dsrc = dsac.DataSourceAltCloud({}, None, self.paths)
dsrc.user_data_rhevm = lambda: True
self.assertEqual(True, dsrc.get_data())
@@ -245,7 +246,7 @@ class TestGetDataNoCloudInfoFile(CiTestCase):
def test_vsphere_no_cloud_file(self):
'''Test No cloud info file module get_data() forcing VSPHERE.'''
- util.read_dmi_data = _dmi_data('VMware Virtual Platform')
+ dmi.read_dmi_data = _dmi_data('VMware Virtual Platform')
dsrc = dsac.DataSourceAltCloud({}, None, self.paths)
dsrc.user_data_vsphere = lambda: True
self.assertEqual(True, dsrc.get_data())
@@ -253,7 +254,7 @@ class TestGetDataNoCloudInfoFile(CiTestCase):
def test_failure_no_cloud_file(self):
'''Test No cloud info file module get_data() forcing unrecognized.'''
- util.read_dmi_data = _dmi_data('Unrecognized Platform')
+ dmi.read_dmi_data = _dmi_data('Unrecognized Platform')
dsrc = dsac.DataSourceAltCloud({}, None, self.paths)
self.assertEqual(False, dsrc.get_data())
diff --git a/tests/unittests/test_datasource/test_azure.py b/tests/unittests/test_datasource/test_azure.py
index 56c1cf18..433fbc66 100644
--- a/tests/unittests/test_datasource/test_azure.py
+++ b/tests/unittests/test_datasource/test_azure.py
@@ -570,7 +570,7 @@ scbus-1 on xpt0 bus 0
(dsaz, 'set_hostname', mock.MagicMock()),
(dsaz, 'get_metadata_from_fabric', self.get_metadata_from_fabric),
(dsaz.subp, 'which', lambda x: True),
- (dsaz.util, 'read_dmi_data', mock.MagicMock(
+ (dsaz.dmi, 'read_dmi_data', mock.MagicMock(
side_effect=_dmi_mocks)),
(dsaz.util, 'wait_for_files', mock.MagicMock(
side_effect=_wait_for_files)),
@@ -1427,7 +1427,7 @@ class TestAzureBounce(CiTestCase):
raise RuntimeError('should not get here')
self.patches.enter_context(
- mock.patch.object(dsaz.util, 'read_dmi_data',
+ mock.patch.object(dsaz.dmi, 'read_dmi_data',
mock.MagicMock(side_effect=_dmi_mocks)))
def setUp(self):
@@ -2294,14 +2294,14 @@ class TestWBIsPlatformViable(CiTestCase):
"""White box tests for _is_platform_viable."""
with_logs = True
- @mock.patch(MOCKPATH + 'util.read_dmi_data')
+ @mock.patch(MOCKPATH + 'dmi.read_dmi_data')
def test_true_on_non_azure_chassis(self, m_read_dmi_data):
"""Return True if DMI chassis-asset-tag is AZURE_CHASSIS_ASSET_TAG."""
m_read_dmi_data.return_value = dsaz.AZURE_CHASSIS_ASSET_TAG
self.assertTrue(dsaz._is_platform_viable('doesnotmatter'))
@mock.patch(MOCKPATH + 'os.path.exists')
- @mock.patch(MOCKPATH + 'util.read_dmi_data')
+ @mock.patch(MOCKPATH + 'dmi.read_dmi_data')
def test_true_on_azure_ovf_env_in_seed_dir(self, m_read_dmi_data, m_exist):
"""Return True if ovf-env.xml exists in known seed dirs."""
# Non-matching Azure chassis-asset-tag
@@ -2322,7 +2322,7 @@ class TestWBIsPlatformViable(CiTestCase):
MOCKPATH,
{'os.path.exists': False,
# Non-matching Azure chassis-asset-tag
- 'util.read_dmi_data': dsaz.AZURE_CHASSIS_ASSET_TAG + 'X',
+ 'dmi.read_dmi_data': dsaz.AZURE_CHASSIS_ASSET_TAG + 'X',
'subp.which': None},
dsaz._is_platform_viable, 'doesnotmatter'))
self.assertIn(
diff --git a/tests/unittests/test_datasource/test_nocloud.py b/tests/unittests/test_datasource/test_nocloud.py
index 2e6b53ff..02cc9b38 100644
--- a/tests/unittests/test_datasource/test_nocloud.py
+++ b/tests/unittests/test_datasource/test_nocloud.py
@@ -1,5 +1,6 @@
# This file is part of cloud-init. See LICENSE file for license information.
+from cloudinit import dmi
from cloudinit import helpers
from cloudinit.sources.DataSourceNoCloud import (
DataSourceNoCloud as dsNoCloud,
@@ -30,7 +31,7 @@ class TestNoCloudDataSource(CiTestCase):
self.mocks.enter_context(
mock.patch.object(util, 'get_cmdline', return_value=self.cmdline))
self.mocks.enter_context(
- mock.patch.object(util, 'read_dmi_data', return_value=None))
+ mock.patch.object(dmi, 'read_dmi_data', return_value=None))
def _test_fs_config_is_read(self, fs_label, fs_label_to_search):
vfat_device = 'device-1'
diff --git a/tests/unittests/test_datasource/test_openstack.py b/tests/unittests/test_datasource/test_openstack.py
index 9b0c1b8a..415755aa 100644
--- a/tests/unittests/test_datasource/test_openstack.py
+++ b/tests/unittests/test_datasource/test_openstack.py
@@ -459,7 +459,7 @@ class TestDetectOpenStack(test_helpers.CiTestCase):
ds.detect_openstack(), 'Expected detect_openstack == True')
@test_helpers.mock.patch(MOCK_PATH + 'util.get_proc_env')
- @test_helpers.mock.patch(MOCK_PATH + 'util.read_dmi_data')
+ @test_helpers.mock.patch(MOCK_PATH + 'dmi.read_dmi_data')
def test_not_detect_openstack_intel_x86_ec2(self, m_dmi, m_proc_env,
m_is_x86):
"""Return False on EC2 platforms."""
@@ -479,7 +479,7 @@ class TestDetectOpenStack(test_helpers.CiTestCase):
ds.detect_openstack(), 'Expected detect_openstack == False on EC2')
m_proc_env.assert_called_with(1)
- @test_helpers.mock.patch(MOCK_PATH + 'util.read_dmi_data')
+ @test_helpers.mock.patch(MOCK_PATH + 'dmi.read_dmi_data')
def test_detect_openstack_intel_product_name_compute(self, m_dmi,
m_is_x86):
"""Return True on OpenStack compute and nova instances."""
@@ -491,7 +491,7 @@ class TestDetectOpenStack(test_helpers.CiTestCase):
self.assertTrue(
ds.detect_openstack(), 'Failed to detect_openstack')
- @test_helpers.mock.patch(MOCK_PATH + 'util.read_dmi_data')
+ @test_helpers.mock.patch(MOCK_PATH + 'dmi.read_dmi_data')
def test_detect_openstack_opentelekomcloud_chassis_asset_tag(self, m_dmi,
m_is_x86):
"""Return True on OpenStack reporting OpenTelekomCloud asset-tag."""
@@ -509,7 +509,7 @@ class TestDetectOpenStack(test_helpers.CiTestCase):
ds.detect_openstack(),
'Expected detect_openstack == True on OpenTelekomCloud')
- @test_helpers.mock.patch(MOCK_PATH + 'util.read_dmi_data')
+ @test_helpers.mock.patch(MOCK_PATH + 'dmi.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."""
@@ -527,7 +527,7 @@ class TestDetectOpenStack(test_helpers.CiTestCase):
ds.detect_openstack(),
'Expected detect_openstack == True on SAP CCloud VM')
- @test_helpers.mock.patch(MOCK_PATH + 'util.read_dmi_data')
+ @test_helpers.mock.patch(MOCK_PATH + 'dmi.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."""
@@ -566,20 +566,20 @@ class TestDetectOpenStack(test_helpers.CiTestCase):
ds.detect_openstack(),
'Expected detect_openstack == True on Generic OpenStack Platform')
- @test_helpers.mock.patch(MOCK_PATH + 'util.read_dmi_data')
+ @test_helpers.mock.patch(MOCK_PATH + 'dmi.read_dmi_data')
def test_detect_openstack_nova_chassis_asset_tag(self, m_dmi,
m_is_x86):
self._test_detect_openstack_nova_compute_chassis_asset_tag(
m_dmi, m_is_x86, 'OpenStack Nova')
- @test_helpers.mock.patch(MOCK_PATH + 'util.read_dmi_data')
+ @test_helpers.mock.patch(MOCK_PATH + 'dmi.read_dmi_data')
def test_detect_openstack_compute_chassis_asset_tag(self, m_dmi,
m_is_x86):
self._test_detect_openstack_nova_compute_chassis_asset_tag(
m_dmi, m_is_x86, 'OpenStack Compute')
@test_helpers.mock.patch(MOCK_PATH + 'util.get_proc_env')
- @test_helpers.mock.patch(MOCK_PATH + 'util.read_dmi_data')
+ @test_helpers.mock.patch(MOCK_PATH + 'dmi.read_dmi_data')
def test_detect_openstack_by_proc_1_environ(self, m_dmi, m_proc_env,
m_is_x86):
"""Return True when nova product_name specified in /proc/1/environ."""
diff --git a/tests/unittests/test_datasource/test_ovf.py b/tests/unittests/test_datasource/test_ovf.py
index 1d088577..16773de5 100644
--- a/tests/unittests/test_datasource/test_ovf.py
+++ b/tests/unittests/test_datasource/test_ovf.py
@@ -129,7 +129,7 @@ class TestDatasourceOVF(CiTestCase):
ds = self.datasource(sys_cfg={}, distro={}, paths=paths)
retcode = wrap_and_call(
'cloudinit.sources.DataSourceOVF',
- {'util.read_dmi_data': None,
+ {'dmi.read_dmi_data': None,
'transport_iso9660': NOT_FOUND,
'transport_vmware_guestinfo': NOT_FOUND},
ds.get_data)
@@ -145,7 +145,7 @@ class TestDatasourceOVF(CiTestCase):
paths=paths)
retcode = wrap_and_call(
'cloudinit.sources.DataSourceOVF',
- {'util.read_dmi_data': 'vmware',
+ {'dmi.read_dmi_data': 'vmware',
'transport_iso9660': NOT_FOUND,
'transport_vmware_guestinfo': NOT_FOUND},
ds.get_data)
@@ -174,7 +174,7 @@ class TestDatasourceOVF(CiTestCase):
with self.assertRaises(CustomScriptNotFound) as context:
wrap_and_call(
'cloudinit.sources.DataSourceOVF',
- {'util.read_dmi_data': 'vmware',
+ {'dmi.read_dmi_data': 'vmware',
'util.del_dir': True,
'search_file': self.tdir,
'wait_for_imc_cfg_file': conf_file,
@@ -211,7 +211,7 @@ class TestDatasourceOVF(CiTestCase):
with self.assertRaises(RuntimeError) as context:
wrap_and_call(
'cloudinit.sources.DataSourceOVF',
- {'util.read_dmi_data': 'vmware',
+ {'dmi.read_dmi_data': 'vmware',
'util.del_dir': True,
'search_file': self.tdir,
'wait_for_imc_cfg_file': conf_file,
@@ -246,7 +246,7 @@ class TestDatasourceOVF(CiTestCase):
with self.assertRaises(CustomScriptNotFound) as context:
wrap_and_call(
'cloudinit.sources.DataSourceOVF',
- {'util.read_dmi_data': 'vmware',
+ {'dmi.read_dmi_data': 'vmware',
'util.del_dir': True,
'search_file': self.tdir,
'wait_for_imc_cfg_file': conf_file,
@@ -290,7 +290,7 @@ class TestDatasourceOVF(CiTestCase):
with self.assertRaises(CustomScriptNotFound) as context:
wrap_and_call(
'cloudinit.sources.DataSourceOVF',
- {'util.read_dmi_data': 'vmware',
+ {'dmi.read_dmi_data': 'vmware',
'util.del_dir': True,
'search_file': self.tdir,
'wait_for_imc_cfg_file': conf_file,
@@ -313,7 +313,7 @@ class TestDatasourceOVF(CiTestCase):
self.assertEqual('ovf', ds.cloud_name)
self.assertEqual('ovf', ds.platform_type)
- with mock.patch(MPATH + 'util.read_dmi_data', return_value='!VMware'):
+ with mock.patch(MPATH + 'dmi.read_dmi_data', return_value='!VMware'):
with mock.patch(MPATH + 'transport_vmware_guestinfo') as m_guestd:
with mock.patch(MPATH + 'transport_iso9660') as m_iso9660:
m_iso9660.return_value = NOT_FOUND
@@ -334,7 +334,7 @@ class TestDatasourceOVF(CiTestCase):
self.assertEqual('ovf', ds.cloud_name)
self.assertEqual('ovf', ds.platform_type)
- with mock.patch(MPATH + 'util.read_dmi_data', return_value='VMWare'):
+ with mock.patch(MPATH + 'dmi.read_dmi_data', return_value='VMWare'):
with mock.patch(MPATH + 'transport_vmware_guestinfo') as m_guestd:
with mock.patch(MPATH + 'transport_iso9660') as m_iso9660:
m_iso9660.return_value = NOT_FOUND
diff --git a/tests/unittests/test_datasource/test_scaleway.py b/tests/unittests/test_datasource/test_scaleway.py
index 9d82bda9..32f3274a 100644
--- a/tests/unittests/test_datasource/test_scaleway.py
+++ b/tests/unittests/test_datasource/test_scaleway.py
@@ -87,7 +87,7 @@ class TestOnScaleway(CiTestCase):
@mock.patch('cloudinit.util.get_cmdline')
@mock.patch('os.path.exists')
- @mock.patch('cloudinit.util.read_dmi_data')
+ @mock.patch('cloudinit.dmi.read_dmi_data')
def test_not_on_scaleway(self, m_read_dmi_data, m_file_exists,
m_get_cmdline):
self.install_mocks(
@@ -105,7 +105,7 @@ class TestOnScaleway(CiTestCase):
@mock.patch('cloudinit.util.get_cmdline')
@mock.patch('os.path.exists')
- @mock.patch('cloudinit.util.read_dmi_data')
+ @mock.patch('cloudinit.dmi.read_dmi_data')
def test_on_scaleway_dmi(self, m_read_dmi_data, m_file_exists,
m_get_cmdline):
"""
@@ -121,7 +121,7 @@ class TestOnScaleway(CiTestCase):
@mock.patch('cloudinit.util.get_cmdline')
@mock.patch('os.path.exists')
- @mock.patch('cloudinit.util.read_dmi_data')
+ @mock.patch('cloudinit.dmi.read_dmi_data')
def test_on_scaleway_var_run_scaleway(self, m_read_dmi_data, m_file_exists,
m_get_cmdline):
"""
@@ -136,7 +136,7 @@ class TestOnScaleway(CiTestCase):
@mock.patch('cloudinit.util.get_cmdline')
@mock.patch('os.path.exists')
- @mock.patch('cloudinit.util.read_dmi_data')
+ @mock.patch('cloudinit.dmi.read_dmi_data')
def test_on_scaleway_cmdline(self, m_read_dmi_data, m_file_exists,
m_get_cmdline):
"""
diff --git a/tests/unittests/test_util.py b/tests/unittests/test_util.py
index cca53123..857629f1 100644
--- a/tests/unittests/test_util.py
+++ b/tests/unittests/test_util.py
@@ -492,129 +492,6 @@ class TestIsX86(helpers.CiTestCase):
self.assertTrue(util.is_x86())
-class TestReadDMIData(helpers.FilesystemMockingTestCase):
-
- def setUp(self):
- super(TestReadDMIData, self).setUp()
- self.new_root = tempfile.mkdtemp()
- self.addCleanup(shutil.rmtree, self.new_root)
- self.patchOS(self.new_root)
- self.patchUtils(self.new_root)
- p = mock.patch("cloudinit.util.is_container", return_value=False)
- self.addCleanup(p.stop)
- self._m_is_container = p.start()
-
- def _create_sysfs_parent_directory(self):
- util.ensure_dir(os.path.join('sys', 'class', 'dmi', 'id'))
-
- def _create_sysfs_file(self, key, content):
- """Mocks the sys path found on Linux systems."""
- self._create_sysfs_parent_directory()
- dmi_key = "/sys/class/dmi/id/{0}".format(key)
- util.write_file(dmi_key, content)
-
- def _configure_dmidecode_return(self, key, content, error=None):
- """
- In order to test a missing sys path and call outs to dmidecode, this
- function fakes the results of dmidecode to test the results.
- """
- def _dmidecode_subp(cmd):
- if cmd[-1] != key:
- raise subp.ProcessExecutionError()
- return (content, error)
-
- self.patched_funcs.enter_context(
- mock.patch("cloudinit.subp.which", side_effect=lambda _: True))
- self.patched_funcs.enter_context(
- mock.patch("cloudinit.subp.subp", side_effect=_dmidecode_subp))
-
- def patch_mapping(self, new_mapping):
- self.patched_funcs.enter_context(
- mock.patch('cloudinit.util.DMIDECODE_TO_DMI_SYS_MAPPING',
- new_mapping))
-
- def test_sysfs_used_with_key_in_mapping_and_file_on_disk(self):
- self.patch_mapping({'mapped-key': 'mapped-value'})
- expected_dmi_value = 'sys-used-correctly'
- self._create_sysfs_file('mapped-value', expected_dmi_value)
- self._configure_dmidecode_return('mapped-key', 'wrong-wrong-wrong')
- self.assertEqual(expected_dmi_value, util.read_dmi_data('mapped-key'))
-
- def test_dmidecode_used_if_no_sysfs_file_on_disk(self):
- self.patch_mapping({})
- self._create_sysfs_parent_directory()
- expected_dmi_value = 'dmidecode-used'
- self._configure_dmidecode_return('use-dmidecode', expected_dmi_value)
- with mock.patch("cloudinit.util.os.uname") as m_uname:
- m_uname.return_value = ('x-sysname', 'x-nodename',
- 'x-release', 'x-version', 'x86_64')
- self.assertEqual(expected_dmi_value,
- util.read_dmi_data('use-dmidecode'))
-
- def test_dmidecode_not_used_on_arm(self):
- self.patch_mapping({})
- print("current =%s", subp)
- self._create_sysfs_parent_directory()
- dmi_val = 'from-dmidecode'
- dmi_name = 'use-dmidecode'
- self._configure_dmidecode_return(dmi_name, dmi_val)
- print("now =%s", subp)
-
- expected = {'armel': None, 'aarch64': dmi_val, 'x86_64': dmi_val}
- found = {}
- # we do not run the 'dmi-decode' binary on some arches
- # verify that anything requested that is not in the sysfs dir
- # will return None on those arches.
- with mock.patch("cloudinit.util.os.uname") as m_uname:
- for arch in expected:
- m_uname.return_value = ('x-sysname', 'x-nodename',
- 'x-release', 'x-version', arch)
- print("now2 =%s", subp)
- found[arch] = util.read_dmi_data(dmi_name)
- self.assertEqual(expected, found)
-
- def test_none_returned_if_neither_source_has_data(self):
- self.patch_mapping({})
- self._configure_dmidecode_return('key', 'value')
- self.assertIsNone(util.read_dmi_data('expect-fail'))
-
- def test_none_returned_if_dmidecode_not_in_path(self):
- self.patched_funcs.enter_context(
- mock.patch.object(subp, 'which', lambda _: False))
- self.patch_mapping({})
- self.assertIsNone(util.read_dmi_data('expect-fail'))
-
- def test_dots_returned_instead_of_foxfox(self):
- # uninitialized dmi values show as \xff, return those as .
- my_len = 32
- dmi_value = b'\xff' * my_len + b'\n'
- expected = ""
- dmi_key = 'system-product-name'
- sysfs_key = 'product_name'
- self._create_sysfs_file(sysfs_key, dmi_value)
- self.assertEqual(expected, util.read_dmi_data(dmi_key))
-
- def test_container_returns_none(self):
- """In a container read_dmi_data should always return None."""
-
- # first verify we get the value if not in container
- self._m_is_container.return_value = False
- key, val = ("system-product-name", "my_product")
- self._create_sysfs_file('product_name', val)
- self.assertEqual(val, util.read_dmi_data(key))
-
- # then verify in container returns None
- self._m_is_container.return_value = True
- self.assertIsNone(util.read_dmi_data(key))
-
- def test_container_returns_none_on_unknown(self):
- """In a container even bogus keys return None."""
- self._m_is_container.return_value = True
- self._create_sysfs_file('product_name', "should-be-ignored")
- self.assertIsNone(util.read_dmi_data("bogus"))
- self.assertIsNone(util.read_dmi_data("system-product-name"))
-
-
class TestGetConfigLogfiles(helpers.CiTestCase):
def test_empty_cfg_returns_empty_list(self):