← Back to team overview

cloud-init-dev team mailing list archive

[Merge] lp:~utlemming/cloud-init/no_demidecode into lp:cloud-init

 

Ben Howard has proposed merging lp:~utlemming/cloud-init/no_demidecode into lp:cloud-init.

Requested reviews:
  cloud init development team (cloud-init-dev)

For more details, see:
https://code.launchpad.net/~utlemming/cloud-init/no_demidecode/+merge/246486

Drop the reliance on /sbin/dmidecode.

include test cases. 
-- 
Your team cloud init development team is requested to review the proposed merge of lp:~utlemming/cloud-init/no_demidecode into lp:cloud-init.
=== modified file 'cloudinit/sources/DataSourceAltCloud.py'
--- cloudinit/sources/DataSourceAltCloud.py	2014-02-27 14:04:17 +0000
+++ cloudinit/sources/DataSourceAltCloud.py	2015-01-14 19:26:20 +0000
@@ -40,7 +40,6 @@
 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 @@
         '''
         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 @@
 
         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, _err:
-            LOG.debug(('Failed command: %s\n%s') % \
-                (' '.join(cmd), _err.message))
-            return 'UNKNOWN'
-        except OSError, _err:
-            LOG.debug(('Failed command: %s\n%s') % \
-                (' '.join(cmd), _err.message))
-            return 'UNKNOWN'
-
-        if cmd_out.upper().startswith('RHEV'):
+        system_name = util.read_dmi_data("system-product-name")
+        if not system_name:
+            return 'UNKNOWN'
+
+        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'

=== modified file 'cloudinit/sources/DataSourceCloudSigma.py'
--- cloudinit/sources/DataSourceCloudSigma.py	2014-05-30 18:50:57 +0000
+++ cloudinit/sources/DataSourceCloudSigma.py	2015-01-14 19:26:20 +0000
@@ -44,27 +44,25 @@
 
     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
-
-        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")
-
+        else:
+            LOG.debug("detected hypervisor as {}".format(sys_product_name))
+            return 'cloudsigma' in sys_product_name.lower()
+
+        LOG.warn("failed to query dmi data for system product name")
         return False
 
     def get_data(self):

=== modified file 'cloudinit/sources/DataSourceSmartOS.py'
--- cloudinit/sources/DataSourceSmartOS.py	2014-08-26 18:50:11 +0000
+++ cloudinit/sources/DataSourceSmartOS.py	2015-01-14 19:26:20 +0000
@@ -358,26 +358,13 @@
 
 
 def dmi_data():
-    sys_uuid, sys_type = None, None
-    dmidecode_path = util.which('dmidecode')
-    if not dmidecode_path:
-        return False
-
-    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())
+    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
+
+    return (sys_uuid.lower(), sys_type)
 
 
 def write_boot_content(content, content_f, link=None, shebang=False,

=== modified file 'cloudinit/util.py'
--- cloudinit/util.py	2015-01-06 17:02:38 +0000
+++ cloudinit/util.py	2015-01-14 19:26:20 +0000
@@ -72,6 +72,9 @@
 # Helper utils to see if running in a container
 CONTAINER_TESTS = ['running-in-container', 'lxc-is-container']
 
+# Path for DMI Data
+DMI_SYS_PATH = "/sys/class/dmi/id"
+
 
 class ProcessExecutionError(IOError):
 
@@ -2011,3 +2014,28 @@
         raise ValueError("'%s': cannot be negative" % size_in)
 
     return int(num * mpliers[mplier])
+
+
+def read_dmi_data(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

=== modified file 'tests/unittests/test_datasource/test_altcloud.py'
--- tests/unittests/test_datasource/test_altcloud.py	2014-02-27 19:12:01 +0000
+++ tests/unittests/test_datasource/test_altcloud.py	2015-01-14 19:26:20 +0000
@@ -26,6 +26,7 @@
 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 @@
             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 @@
     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 @@
     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 @@
     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.
-        '''
-        cloudinit.sources.DataSourceAltCloud.CMD_DMI_SYSTEM = \
-            ['bad command']
+        Forcing read_dmi_data return to match an unrecognized return.
+        '''
+        util.read_dmi_data = _dmi_data('Unrecognized Platform')
         dsrc = DataSourceAltCloud({}, None, self.paths)
         self.assertEquals('UNKNOWN', \
             dsrc.get_cloud_type())
@@ -180,6 +167,7 @@
         '''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 @@
         except OSError:
             pass
 
+        util.read_dmi_data = self.dmi_data
         cloudinit.sources.DataSourceAltCloud.CLOUD_INFO_FILE = \
             '/etc/sysconfig/cloud-info'
 
@@ -243,6 +232,7 @@
     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 @@
         # 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 @@
     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 @@
     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())
 

=== modified file 'tests/unittests/test_util.py'
--- tests/unittests/test_util.py	2014-08-26 19:53:41 +0000
+++ tests/unittests/test_util.py	2015-01-14 19:26:20 +0000
@@ -310,4 +310,32 @@
         expected = ('none', 'tmpfs', '/run/lock')
         self.assertEqual(expected, util.parse_mount_info('/run/lock', lines))
 
+
+class TestReadDMIData(helpers.FilesystemMockingTestCase):
+
+    def _patchIn(self, root):
+        self.restore()
+        self.patchOS(root)
+        self.patchUtils(root)
+
+    def _write_key(self, key, content):
+        new_root = self.makeDir()
+        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 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.assertFalse(util.read_dmi_data("key"))
+
 # vi: ts=4 expandtab


Follow ups