Drop reliance on dmidecode executable.

This commit is contained in:
Ben Howard 2015-01-14 12:24:09 -07:00
parent 49c849fdf0
commit e24371042e
6 changed files with 105 additions and 89 deletions

View File

@ -40,7 +40,6 @@ LOG = logging.getLogger(__name__)
CLOUD_INFO_FILE = '/etc/sysconfig/cloud-info' CLOUD_INFO_FILE = '/etc/sysconfig/cloud-info'
# Shell command lists # Shell command lists
CMD_DMI_SYSTEM = ['/usr/sbin/dmidecode', '--string', 'system-product-name']
CMD_PROBE_FLOPPY = ['/sbin/modprobe', 'floppy'] CMD_PROBE_FLOPPY = ['/sbin/modprobe', 'floppy']
CMD_UDEVADM_SETTLE = ['/sbin/udevadm', 'settle', '--quiet', '--timeout=5'] CMD_UDEVADM_SETTLE = ['/sbin/udevadm', 'settle', '--quiet', '--timeout=5']
@ -100,11 +99,7 @@ class DataSourceAltCloud(sources.DataSource):
''' '''
Description: Description:
Get the type for the cloud back end this instance is running on Get the type for the cloud back end this instance is running on
by examining the string returned by: by examining the string returned by reading the dmi data.
dmidecode --string system-product-name
On VMWare/vSphere dmidecode returns: RHEV Hypervisor
On VMWare/vSphere dmidecode returns: VMware Virtual Platform
Input: Input:
None None
@ -117,26 +112,20 @@ class DataSourceAltCloud(sources.DataSource):
uname_arch = os.uname()[4] uname_arch = os.uname()[4]
if uname_arch.startswith("arm") or uname_arch == "aarch64": 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)") LOG.debug("Disabling AltCloud datasource on arm (LP: #1243287)")
return 'UNKNOWN' return 'UNKNOWN'
cmd = CMD_DMI_SYSTEM system_name = util.read_dmi_data("system-product-name")
try: if not system_name:
(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' return 'UNKNOWN'
if cmd_out.upper().startswith('RHEV'): sys_name = system_name.upper()
if sys_name.startswith('RHEV'):
return 'RHEV' return 'RHEV'
if cmd_out.upper().startswith('VMWARE'): if sys_name.startswith('VMWARE'):
return 'VSPHERE' return 'VSPHERE'
return 'UNKNOWN' return 'UNKNOWN'

View File

@ -44,27 +44,25 @@ class DataSourceCloudSigma(sources.DataSource):
def is_running_in_cloudsigma(self): 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. in the CloudSigma's infrastructure.
""" """
uname_arch = os.uname()[4] uname_arch = os.uname()[4]
if uname_arch.startswith("arm") or uname_arch == "aarch64": 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)") LOG.debug("Disabling CloudSigma datasource on arm (LP: #1243287)")
return False return False
dmidecode_path = util.which('dmidecode') LOG.debug("determining hypervisor product name via dmi data")
if not dmidecode_path: 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 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") LOG.warn("failed to query dmi data for system product name")
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")
return False return False
def get_data(self): def get_data(self):

View File

@ -358,26 +358,13 @@ def query_data(noun, seed_device, seed_timeout, strip=False, default=None,
def dmi_data(): def dmi_data():
sys_uuid, sys_type = None, None sys_uuid = util.read_dmi_data("system-uuid")
dmidecode_path = util.which('dmidecode') sys_type = util.read_dmi_data("system-product-name")
if not dmidecode_path:
return False
sys_uuid_cmd = [dmidecode_path, "-s", "system-uuid"] if not sys_uuid or not sys_type:
try: return None
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"] return (sys_uuid.lower(), sys_type)
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())
def write_boot_content(content, content_f, link=None, shebang=False, def write_boot_content(content, content_f, link=None, shebang=False,

View File

@ -72,6 +72,9 @@ FN_ALLOWED = ('_-.()' + string.digits + string.ascii_letters)
# Helper utils to see if running in a container # Helper utils to see if running in a container
CONTAINER_TESTS = ['running-in-container', 'lxc-is-container'] CONTAINER_TESTS = ['running-in-container', 'lxc-is-container']
# Path for DMI Data
DMI_SYS_PATH = "/sys/class/dmi/id"
class ProcessExecutionError(IOError): class ProcessExecutionError(IOError):
@ -2011,3 +2014,28 @@ def human2bytes(size):
raise ValueError("'%s': cannot be negative" % size_in) raise ValueError("'%s': cannot be negative" % size_in)
return int(num * mpliers[mplier]) 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

View File

@ -26,6 +26,7 @@ import shutil
import tempfile import tempfile
from cloudinit import helpers from cloudinit import helpers
from cloudinit import util
from unittest import TestCase from unittest import TestCase
# Get the cloudinit.sources.DataSourceAltCloud import items needed. # Get the cloudinit.sources.DataSourceAltCloud import items needed.
@ -98,6 +99,16 @@ def _remove_user_data_files(mount_dir,
pass pass
def _dmi_data(expected):
'''
Spoof the data received over DMI
'''
def _data(key):
return expected
return _data
class TestGetCloudType(TestCase): class TestGetCloudType(TestCase):
''' '''
Test to exercise method: DataSourceAltCloud.get_cloud_type() Test to exercise method: DataSourceAltCloud.get_cloud_type()
@ -106,24 +117,22 @@ class TestGetCloudType(TestCase):
def setUp(self): def setUp(self):
'''Set up.''' '''Set up.'''
self.paths = helpers.Paths({'cloud_dir': '/tmp'}) 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 a different code path for arm to deal with LP1243287
# We have to switch arch to x86_64 to avoid test failure # We have to switch arch to x86_64 to avoid test failure
force_arch('x86_64') force_arch('x86_64')
def tearDown(self): def tearDown(self):
# Reset # Reset
cloudinit.sources.DataSourceAltCloud.CMD_DMI_SYSTEM = \ util.read_dmi_data = self.dmi_data
['dmidecode', '--string', 'system-product-name']
# Return back to original arch
force_arch() force_arch()
def test_rhev(self): def test_rhev(self):
''' '''
Test method get_cloud_type() for RHEVm systems. 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 = \ util.read_dmi_data = _dmi_data('RHEV')
['echo', 'RHEV Hypervisor']
dsrc = DataSourceAltCloud({}, None, self.paths) dsrc = DataSourceAltCloud({}, None, self.paths)
self.assertEquals('RHEV', \ self.assertEquals('RHEV', \
dsrc.get_cloud_type()) dsrc.get_cloud_type())
@ -131,10 +140,9 @@ class TestGetCloudType(TestCase):
def test_vsphere(self): def test_vsphere(self):
''' '''
Test method get_cloud_type() for vSphere systems. 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 = \ util.read_dmi_data = _dmi_data('VMware Virtual Platform')
['echo', 'VMware Virtual Platform']
dsrc = DataSourceAltCloud({}, None, self.paths) dsrc = DataSourceAltCloud({}, None, self.paths)
self.assertEquals('VSPHERE', \ self.assertEquals('VSPHERE', \
dsrc.get_cloud_type()) dsrc.get_cloud_type())
@ -142,30 +150,9 @@ class TestGetCloudType(TestCase):
def test_unknown(self): def test_unknown(self):
''' '''
Test method get_cloud_type() for unknown systems. Test method get_cloud_type() for unknown systems.
Forcing dmidecode return to match an unrecognized return. Forcing read_dmi_data return to match an unrecognized return.
''' '''
cloudinit.sources.DataSourceAltCloud.CMD_DMI_SYSTEM = \ util.read_dmi_data = _dmi_data('Unrecognized Platform')
['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']
dsrc = DataSourceAltCloud({}, None, self.paths) dsrc = DataSourceAltCloud({}, None, self.paths)
self.assertEquals('UNKNOWN', \ self.assertEquals('UNKNOWN', \
dsrc.get_cloud_type()) dsrc.get_cloud_type())
@ -180,6 +167,7 @@ class TestGetDataCloudInfoFile(TestCase):
'''Set up.''' '''Set up.'''
self.paths = helpers.Paths({'cloud_dir': '/tmp'}) self.paths = helpers.Paths({'cloud_dir': '/tmp'})
self.cloud_info_file = tempfile.mkstemp()[1] self.cloud_info_file = tempfile.mkstemp()[1]
self.dmi_data = util.read_dmi_data
cloudinit.sources.DataSourceAltCloud.CLOUD_INFO_FILE = \ cloudinit.sources.DataSourceAltCloud.CLOUD_INFO_FILE = \
self.cloud_info_file self.cloud_info_file
@ -192,6 +180,7 @@ class TestGetDataCloudInfoFile(TestCase):
except OSError: except OSError:
pass pass
util.read_dmi_data = self.dmi_data
cloudinit.sources.DataSourceAltCloud.CLOUD_INFO_FILE = \ cloudinit.sources.DataSourceAltCloud.CLOUD_INFO_FILE = \
'/etc/sysconfig/cloud-info' '/etc/sysconfig/cloud-info'
@ -243,6 +232,7 @@ class TestGetDataNoCloudInfoFile(TestCase):
def setUp(self): def setUp(self):
'''Set up.''' '''Set up.'''
self.paths = helpers.Paths({'cloud_dir': '/tmp'}) self.paths = helpers.Paths({'cloud_dir': '/tmp'})
self.dmi_data = util.read_dmi_data
cloudinit.sources.DataSourceAltCloud.CLOUD_INFO_FILE = \ cloudinit.sources.DataSourceAltCloud.CLOUD_INFO_FILE = \
'no such file' 'no such file'
# We have a different code path for arm to deal with LP1243287 # We have a different code path for arm to deal with LP1243287
@ -253,16 +243,14 @@ class TestGetDataNoCloudInfoFile(TestCase):
# Reset # Reset
cloudinit.sources.DataSourceAltCloud.CLOUD_INFO_FILE = \ cloudinit.sources.DataSourceAltCloud.CLOUD_INFO_FILE = \
'/etc/sysconfig/cloud-info' '/etc/sysconfig/cloud-info'
cloudinit.sources.DataSourceAltCloud.CMD_DMI_SYSTEM = \ util.read_dmi_data = self.dmi_data
['dmidecode', '--string', 'system-product-name']
# Return back to original arch # Return back to original arch
force_arch() force_arch()
def test_rhev_no_cloud_file(self): def test_rhev_no_cloud_file(self):
'''Test No cloud info file module get_data() forcing RHEV.''' '''Test No cloud info file module get_data() forcing RHEV.'''
cloudinit.sources.DataSourceAltCloud.CMD_DMI_SYSTEM = \ util.read_dmi_data = _dmi_data('RHEV Hypervisor')
['echo', 'RHEV Hypervisor']
dsrc = DataSourceAltCloud({}, None, self.paths) dsrc = DataSourceAltCloud({}, None, self.paths)
dsrc.user_data_rhevm = lambda: True dsrc.user_data_rhevm = lambda: True
self.assertEquals(True, dsrc.get_data()) self.assertEquals(True, dsrc.get_data())
@ -270,8 +258,7 @@ class TestGetDataNoCloudInfoFile(TestCase):
def test_vsphere_no_cloud_file(self): def test_vsphere_no_cloud_file(self):
'''Test No cloud info file module get_data() forcing VSPHERE.''' '''Test No cloud info file module get_data() forcing VSPHERE.'''
cloudinit.sources.DataSourceAltCloud.CMD_DMI_SYSTEM = \ util.read_dmi_data = _dmi_data('VMware Virtual Platform')
['echo', 'VMware Virtual Platform']
dsrc = DataSourceAltCloud({}, None, self.paths) dsrc = DataSourceAltCloud({}, None, self.paths)
dsrc.user_data_vsphere = lambda: True dsrc.user_data_vsphere = lambda: True
self.assertEquals(True, dsrc.get_data()) self.assertEquals(True, dsrc.get_data())
@ -279,8 +266,7 @@ class TestGetDataNoCloudInfoFile(TestCase):
def test_failure_no_cloud_file(self): def test_failure_no_cloud_file(self):
'''Test No cloud info file module get_data() forcing unrecognized.''' '''Test No cloud info file module get_data() forcing unrecognized.'''
cloudinit.sources.DataSourceAltCloud.CMD_DMI_SYSTEM = \ util.read_dmi_data = _dmi_data('Unrecognized Platform')
['echo', 'Unrecognized Platform']
dsrc = DataSourceAltCloud({}, None, self.paths) dsrc = DataSourceAltCloud({}, None, self.paths)
self.assertEquals(False, dsrc.get_data()) self.assertEquals(False, dsrc.get_data())

View File

@ -310,4 +310,32 @@ class TestMountinfoParsing(helpers.ResourceUsingTestCase):
expected = ('none', 'tmpfs', '/run/lock') expected = ('none', 'tmpfs', '/run/lock')
self.assertEqual(expected, util.parse_mount_info('/run/lock', lines)) 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 # vi: ts=4 expandtab