summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog1
-rw-r--r--cloudinit/sources/DataSourceAltCloud.py27
-rw-r--r--cloudinit/sources/DataSourceCloudSigma.py22
-rw-r--r--cloudinit/sources/DataSourceSmartOS.py25
-rw-r--r--cloudinit/util.py61
-rw-r--r--tests/unittests/helpers.py5
-rw-r--r--tests/unittests/test_datasource/test_altcloud.py66
-rw-r--r--tests/unittests/test_util.py59
8 files changed, 175 insertions, 91 deletions
diff --git a/ChangeLog b/ChangeLog
index ddde383a..abcadb4d 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -20,6 +20,7 @@
- systemd: make init stage run before login prompts shown [Steve Langasek]
- hostname: on first boot apply hostname to be same as is written for
persistent hostname. (LP: #1246485)
+ - remove usage of dmidecode on linux in favor of /sys interface [Ben Howard]
0.7.6:
- open 0.7.6
- Enable vendordata on CloudSigma datasource (LP: #1303986)
diff --git a/cloudinit/sources/DataSourceAltCloud.py b/cloudinit/sources/DataSourceAltCloud.py
index 69053d0b..fb528ae5 100644
--- a/cloudinit/sources/DataSourceAltCloud.py
+++ b/cloudinit/sources/DataSourceAltCloud.py
@@ -40,7 +40,6 @@ LOG = logging.getLogger(__name__)
CLOUD_INFO_FILE = '/etc/sysconfig/cloud-info'
# Shell command lists
-CMD_DMI_SYSTEM = ['/usr/sbin/dmidecode', '--string', 'system-product-name']
CMD_PROBE_FLOPPY = ['/sbin/modprobe', 'floppy']
CMD_UDEVADM_SETTLE = ['/sbin/udevadm', 'settle', '--quiet', '--timeout=5']
@@ -100,11 +99,7 @@ class DataSourceAltCloud(sources.DataSource):
'''
Description:
Get the type for the cloud back end this instance is running on
- by examining the string returned by:
- dmidecode --string system-product-name
-
- On VMWare/vSphere dmidecode returns: RHEV Hypervisor
- On VMWare/vSphere dmidecode returns: VMware Virtual Platform
+ by examining the string returned by reading the dmi data.
Input:
None
@@ -117,26 +112,20 @@ class DataSourceAltCloud(sources.DataSource):
uname_arch = os.uname()[4]
if uname_arch.startswith("arm") or uname_arch == "aarch64":
- # Disabling because dmidecode in CMD_DMI_SYSTEM crashes kvm process
+ # Disabling because dmi data is not available on ARM processors
LOG.debug("Disabling AltCloud datasource on arm (LP: #1243287)")
return 'UNKNOWN'
- cmd = CMD_DMI_SYSTEM
- try:
- (cmd_out, _err) = util.subp(cmd)
- except ProcessExecutionError as _err:
- LOG.debug(('Failed command: %s\n%s') % \
- (' '.join(cmd), _err.message))
- return 'UNKNOWN'
- except OSError as _err:
- LOG.debug(('Failed command: %s\n%s') % \
- (' '.join(cmd), _err.message))
+ system_name = util.read_dmi_data("system-product-name")
+ if not system_name:
return 'UNKNOWN'
- if cmd_out.upper().startswith('RHEV'):
+ sys_name = system_name.upper()
+
+ if sys_name.startswith('RHEV'):
return 'RHEV'
- if cmd_out.upper().startswith('VMWARE'):
+ if sys_name.startswith('VMWARE'):
return 'VSPHERE'
return 'UNKNOWN'
diff --git a/cloudinit/sources/DataSourceCloudSigma.py b/cloudinit/sources/DataSourceCloudSigma.py
index 707cd0ce..76597116 100644
--- a/cloudinit/sources/DataSourceCloudSigma.py
+++ b/cloudinit/sources/DataSourceCloudSigma.py
@@ -44,27 +44,25 @@ class DataSourceCloudSigma(sources.DataSource):
def is_running_in_cloudsigma(self):
"""
- Uses dmidecode to detect if this instance of cloud-init is running
+ Uses dmi data to detect if this instance of cloud-init is running
in the CloudSigma's infrastructure.
"""
uname_arch = os.uname()[4]
if uname_arch.startswith("arm") or uname_arch == "aarch64":
- # Disabling because dmidecode in CMD_DMI_SYSTEM crashes kvm process
+ # Disabling because dmi data on ARM processors
LOG.debug("Disabling CloudSigma datasource on arm (LP: #1243287)")
return False
- dmidecode_path = util.which('dmidecode')
- if not dmidecode_path:
+ LOG.debug("determining hypervisor product name via dmi data")
+ sys_product_name = util.read_dmi_data("system-product-name")
+ if not sys_product_name:
+ LOG.warn("failed to get hypervisor product name via dmi data")
return False
+ else:
+ LOG.debug("detected hypervisor as {}".format(sys_product_name))
+ return 'cloudsigma' in sys_product_name.lower()
- LOG.debug("Determining hypervisor product name via dmidecode")
- try:
- cmd = [dmidecode_path, "--string", "system-product-name"]
- system_product_name, _ = util.subp(cmd)
- return 'cloudsigma' in system_product_name.lower()
- except:
- LOG.warn("Failed to get hypervisor product name via dmidecode")
-
+ LOG.warn("failed to query dmi data for system product name")
return False
def get_data(self):
diff --git a/cloudinit/sources/DataSourceSmartOS.py b/cloudinit/sources/DataSourceSmartOS.py
index d3ed40c5..f59ad3d6 100644
--- a/cloudinit/sources/DataSourceSmartOS.py
+++ b/cloudinit/sources/DataSourceSmartOS.py
@@ -370,26 +370,13 @@ def query_data(noun, seed_device, seed_timeout, strip=False, default=None,
def dmi_data():
- sys_uuid, sys_type = None, None
- dmidecode_path = util.which('dmidecode')
- if not dmidecode_path:
- return False
+ sys_uuid = util.read_dmi_data("system-uuid")
+ sys_type = util.read_dmi_data("system-product-name")
+
+ if not sys_uuid or not sys_type:
+ return None
- sys_uuid_cmd = [dmidecode_path, "-s", "system-uuid"]
- try:
- LOG.debug("Getting hostname from dmidecode")
- (sys_uuid, _err) = util.subp(sys_uuid_cmd)
- except Exception as e:
- util.logexc(LOG, "Failed to get system UUID", e)
-
- sys_type_cmd = [dmidecode_path, "-s", "system-product-name"]
- try:
- LOG.debug("Determining hypervisor product name via dmidecode")
- (sys_type, _err) = util.subp(sys_type_cmd)
- except Exception as e:
- util.logexc(LOG, "Failed to get system UUID", e)
-
- return (sys_uuid.lower().strip(), sys_type.strip())
+ return (sys_uuid.lower(), sys_type)
def write_boot_content(content, content_f, link=None, shebang=False,
diff --git a/cloudinit/util.py b/cloudinit/util.py
index d594b611..32c19ba2 100644
--- a/cloudinit/util.py
+++ b/cloudinit/util.py
@@ -90,6 +90,9 @@ def encode_text(text, encoding='utf-8'):
return text
return text.encode(encoding)
+# Path for DMI Data
+DMI_SYS_PATH = "/sys/class/dmi/id"
+
class ProcessExecutionError(IOError):
@@ -2049,3 +2052,61 @@ def human2bytes(size):
raise ValueError("'%s': cannot be negative" % size_in)
return int(num * mpliers[mplier])
+
+
+def _read_dmi_syspath(key):
+ """
+ Reads dmi data with from /sys/class/dmi/id
+ """
+
+ dmi_key = "{}/{}".format(DMI_SYS_PATH, key)
+ LOG.debug("querying dmi data {}".format(dmi_key))
+ try:
+ if not os.path.exists(dmi_key):
+ LOG.debug("did not find {}".format(dmi_key))
+ return None
+
+ key_data = load_file(dmi_key)
+ if not key_data:
+ LOG.debug("{} did not return any data".format(key))
+ return None
+
+ LOG.debug("dmi data {} returned {}".format(dmi_key, key_data))
+ return key_data.strip()
+
+ except Exception as e:
+ logexc(LOG, "failed read of {}".format(dmi_key), 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(cmd)
+ LOG.debug("dmidecode returned '{}' for '{}'".format(result, key))
+ return result
+ except OSError as _err:
+ LOG.debug('failed dmidecode cmd: {}\n{}'.format(cmd, _err.message))
+ return None
+
+
+def read_dmi_data(key):
+ """
+ Wrapper for reading DMI data. This tries to determine whether the DMI
+ Data can be read directly, otherwise it will fallback to using dmidecode.
+ """
+ if os.path.exists(DMI_SYS_PATH):
+ return _read_dmi_syspath(key)
+
+ dmidecode_path = which('dmidecode')
+ if dmidecode_path:
+ return _call_dmidecode(key, dmidecode_path)
+
+ LOG.warn("did not find either path {} or dmidecode command".format(
+ DMI_SYS_PATH))
+
+ return None
diff --git a/tests/unittests/helpers.py b/tests/unittests/helpers.py
index 4b8dcc5c..828579e8 100644
--- a/tests/unittests/helpers.py
+++ b/tests/unittests/helpers.py
@@ -169,7 +169,10 @@ class FilesystemMockingTestCase(ResourceUsingTestCase):
def setUp(self):
ResourceUsingTestCase.setUp(self)
self.patched_funcs = ExitStack()
- self.addCleanup(self.patched_funcs.close)
+
+ def tearDown(self):
+ self.patched_funcs.close()
+ ResourceUsingTestCase.tearDown(self)
def replicateTestRoot(self, example_root, target_root):
real_root = self.resourceLocation()
diff --git a/tests/unittests/test_datasource/test_altcloud.py b/tests/unittests/test_datasource/test_altcloud.py
index c74562d7..e9cd2fa5 100644
--- a/tests/unittests/test_datasource/test_altcloud.py
+++ b/tests/unittests/test_datasource/test_altcloud.py
@@ -26,6 +26,7 @@ import shutil
import tempfile
from cloudinit import helpers
+from cloudinit import util
from unittest import TestCase
# Get the cloudinit.sources.DataSourceAltCloud import items needed.
@@ -98,6 +99,16 @@ def _remove_user_data_files(mount_dir,
pass
+def _dmi_data(expected):
+ '''
+ Spoof the data received over DMI
+ '''
+ def _data(key):
+ return expected
+
+ return _data
+
+
class TestGetCloudType(TestCase):
'''
Test to exercise method: DataSourceAltCloud.get_cloud_type()
@@ -106,24 +117,22 @@ class TestGetCloudType(TestCase):
def setUp(self):
'''Set up.'''
self.paths = helpers.Paths({'cloud_dir': '/tmp'})
+ self.dmi_data = util.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
- cloudinit.sources.DataSourceAltCloud.CMD_DMI_SYSTEM = \
- ['dmidecode', '--string', 'system-product-name']
- # Return back to original arch
+ util.read_dmi_data = self.dmi_data
force_arch()
def test_rhev(self):
'''
Test method get_cloud_type() for RHEVm systems.
- Forcing dmidecode return to match a RHEVm system: RHEV Hypervisor
+ Forcing read_dmi_data return to match a RHEVm system: RHEV Hypervisor
'''
- cloudinit.sources.DataSourceAltCloud.CMD_DMI_SYSTEM = \
- ['echo', 'RHEV Hypervisor']
+ util.read_dmi_data = _dmi_data('RHEV')
dsrc = DataSourceAltCloud({}, None, self.paths)
self.assertEquals('RHEV', \
dsrc.get_cloud_type())
@@ -131,10 +140,9 @@ class TestGetCloudType(TestCase):
def test_vsphere(self):
'''
Test method get_cloud_type() for vSphere systems.
- Forcing dmidecode return to match a vSphere system: RHEV Hypervisor
+ Forcing read_dmi_data return to match a vSphere system: RHEV Hypervisor
'''
- cloudinit.sources.DataSourceAltCloud.CMD_DMI_SYSTEM = \
- ['echo', 'VMware Virtual Platform']
+ util.read_dmi_data = _dmi_data('VMware Virtual Platform')
dsrc = DataSourceAltCloud({}, None, self.paths)
self.assertEquals('VSPHERE', \
dsrc.get_cloud_type())
@@ -142,30 +150,9 @@ class TestGetCloudType(TestCase):
def test_unknown(self):
'''
Test method get_cloud_type() for unknown systems.
- Forcing dmidecode return to match an unrecognized return.
- '''
- cloudinit.sources.DataSourceAltCloud.CMD_DMI_SYSTEM = \
- ['echo', 'Unrecognized Platform']
- dsrc = DataSourceAltCloud({}, None, self.paths)
- self.assertEquals('UNKNOWN', \
- dsrc.get_cloud_type())
-
- def test_exception1(self):
- '''
- Test method get_cloud_type() where command dmidecode fails.
- '''
- cloudinit.sources.DataSourceAltCloud.CMD_DMI_SYSTEM = \
- ['ls', 'bad command']
- dsrc = DataSourceAltCloud({}, None, self.paths)
- self.assertEquals('UNKNOWN', \
- dsrc.get_cloud_type())
-
- def test_exception2(self):
- '''
- Test method get_cloud_type() where command dmidecode is not available.
+ Forcing read_dmi_data return to match an unrecognized return.
'''
- cloudinit.sources.DataSourceAltCloud.CMD_DMI_SYSTEM = \
- ['bad command']
+ util.read_dmi_data = _dmi_data('Unrecognized Platform')
dsrc = DataSourceAltCloud({}, None, self.paths)
self.assertEquals('UNKNOWN', \
dsrc.get_cloud_type())
@@ -180,6 +167,7 @@ class TestGetDataCloudInfoFile(TestCase):
'''Set up.'''
self.paths = helpers.Paths({'cloud_dir': '/tmp'})
self.cloud_info_file = tempfile.mkstemp()[1]
+ self.dmi_data = util.read_dmi_data
cloudinit.sources.DataSourceAltCloud.CLOUD_INFO_FILE = \
self.cloud_info_file
@@ -192,6 +180,7 @@ class TestGetDataCloudInfoFile(TestCase):
except OSError:
pass
+ util.read_dmi_data = self.dmi_data
cloudinit.sources.DataSourceAltCloud.CLOUD_INFO_FILE = \
'/etc/sysconfig/cloud-info'
@@ -243,6 +232,7 @@ class TestGetDataNoCloudInfoFile(TestCase):
def setUp(self):
'''Set up.'''
self.paths = helpers.Paths({'cloud_dir': '/tmp'})
+ self.dmi_data = util.read_dmi_data
cloudinit.sources.DataSourceAltCloud.CLOUD_INFO_FILE = \
'no such file'
# We have a different code path for arm to deal with LP1243287
@@ -253,16 +243,14 @@ class TestGetDataNoCloudInfoFile(TestCase):
# Reset
cloudinit.sources.DataSourceAltCloud.CLOUD_INFO_FILE = \
'/etc/sysconfig/cloud-info'
- cloudinit.sources.DataSourceAltCloud.CMD_DMI_SYSTEM = \
- ['dmidecode', '--string', 'system-product-name']
+ util.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.'''
- cloudinit.sources.DataSourceAltCloud.CMD_DMI_SYSTEM = \
- ['echo', 'RHEV Hypervisor']
+ util.read_dmi_data = _dmi_data('RHEV Hypervisor')
dsrc = DataSourceAltCloud({}, None, self.paths)
dsrc.user_data_rhevm = lambda: True
self.assertEquals(True, dsrc.get_data())
@@ -270,8 +258,7 @@ class TestGetDataNoCloudInfoFile(TestCase):
def test_vsphere_no_cloud_file(self):
'''Test No cloud info file module get_data() forcing VSPHERE.'''
- cloudinit.sources.DataSourceAltCloud.CMD_DMI_SYSTEM = \
- ['echo', 'VMware Virtual Platform']
+ util.read_dmi_data = _dmi_data('VMware Virtual Platform')
dsrc = DataSourceAltCloud({}, None, self.paths)
dsrc.user_data_vsphere = lambda: True
self.assertEquals(True, dsrc.get_data())
@@ -279,8 +266,7 @@ class TestGetDataNoCloudInfoFile(TestCase):
def test_failure_no_cloud_file(self):
'''Test No cloud info file module get_data() forcing unrecognized.'''
- cloudinit.sources.DataSourceAltCloud.CMD_DMI_SYSTEM = \
- ['echo', 'Unrecognized Platform']
+ util.read_dmi_data = _dmi_data('Unrecognized Platform')
dsrc = DataSourceAltCloud({}, None, self.paths)
self.assertEquals(False, dsrc.get_data())
diff --git a/tests/unittests/test_util.py b/tests/unittests/test_util.py
index f537d332..7a224230 100644
--- a/tests/unittests/test_util.py
+++ b/tests/unittests/test_util.py
@@ -1,3 +1,5 @@
+from __future__ import print_function
+
import os
import stat
import yaml
@@ -319,4 +321,61 @@ class TestMountinfoParsing(helpers.ResourceUsingTestCase):
expected = ('none', 'tmpfs', '/run/lock')
self.assertEqual(expected, util.parse_mount_info('/run/lock', lines))
+
+class TestReadDMIData(helpers.FilesystemMockingTestCase):
+
+ def _patchIn(self, root):
+ self.patchOS(root)
+ self.patchUtils(root)
+
+ def _write_key(self, key, content):
+ """Mocks the sys path found on Linux systems."""
+ new_root = tempfile.mkdtemp()
+ self.addCleanup(shutil.rmtree, new_root)
+ self._patchIn(new_root)
+ util.ensure_dir(os.path.join('sys', 'class', 'dmi', 'id'))
+
+ dmi_key = "/sys/class/dmi/id/{}".format(key)
+ util.write_file(dmi_key, content)
+
+ def _no_syspath(self, key, content):
+ """
+ In order to test a missing sys path and call outs to dmidecode, this
+ function fakes the results of dmidecode to test the results.
+ """
+ new_root = tempfile.mkdtemp()
+ self.addCleanup(shutil.rmtree, new_root)
+ self._patchIn(new_root)
+ self.real_which = util.which
+ self.real_subp = util.subp
+
+ def _which(key):
+ return True
+ util.which = _which
+
+ def _cdd(_key, error=None):
+ return (content, error)
+ util.subp = _cdd
+
+ def test_key(self):
+ key_content = "TEST-KEY-DATA"
+ self._write_key("key", key_content)
+ self.assertEquals(key_content, util.read_dmi_data("key"))
+
+ def test_key_mismatch(self):
+ self._write_key("test", "ABC")
+ self.assertNotEqual("123", util.read_dmi_data("test"))
+
+ def test_no_key(self):
+ self._no_syspath(None, None)
+ self.assertFalse(util.read_dmi_data("key"))
+
+ def test_callout_dmidecode(self):
+ """test to make sure that dmidecode is used when no syspath"""
+ self._no_syspath("key", "stuff")
+ self.assertEquals("stuff", util.read_dmi_data("key"))
+ self._no_syspath("key", None)
+ self.assertFalse(None, util.read_dmi_data("key"))
+
+
# vi: ts=4 expandtab