Skip to content

Commit fe99f7d

Browse files
committed
Trunk merged and ported.
2 parents fabff4a + 78915c9 commit fe99f7d

File tree

8 files changed

+175
-91
lines changed

8 files changed

+175
-91
lines changed

ChangeLog

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
- systemd: make init stage run before login prompts shown [Steve Langasek]
2121
- hostname: on first boot apply hostname to be same as is written for
2222
persistent hostname. (LP: #1246485)
23+
- remove usage of dmidecode on linux in favor of /sys interface [Ben Howard]
2324
0.7.6:
2425
- open 0.7.6
2526
- Enable vendordata on CloudSigma datasource (LP: #1303986)

cloudinit/sources/DataSourceAltCloud.py

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
CLOUD_INFO_FILE = '/etc/sysconfig/cloud-info'
4141

4242
# Shell command lists
43-
CMD_DMI_SYSTEM = ['/usr/sbin/dmidecode', '--string', 'system-product-name']
4443
CMD_PROBE_FLOPPY = ['/sbin/modprobe', 'floppy']
4544
CMD_UDEVADM_SETTLE = ['/sbin/udevadm', 'settle', '--quiet', '--timeout=5']
4645

@@ -100,11 +99,7 @@ def get_cloud_type(self):
10099
'''
101100
Description:
102101
Get the type for the cloud back end this instance is running on
103-
by examining the string returned by:
104-
dmidecode --string system-product-name
105-
106-
On VMWare/vSphere dmidecode returns: RHEV Hypervisor
107-
On VMWare/vSphere dmidecode returns: VMware Virtual Platform
102+
by examining the string returned by reading the dmi data.
108103
109104
Input:
110105
None
@@ -117,26 +112,20 @@ def get_cloud_type(self):
117112

118113
uname_arch = os.uname()[4]
119114
if uname_arch.startswith("arm") or uname_arch == "aarch64":
120-
# Disabling because dmidecode in CMD_DMI_SYSTEM crashes kvm process
115+
# Disabling because dmi data is not available on ARM processors
121116
LOG.debug("Disabling AltCloud datasource on arm (LP: #1243287)")
122117
return 'UNKNOWN'
123118

124-
cmd = CMD_DMI_SYSTEM
125-
try:
126-
(cmd_out, _err) = util.subp(cmd)
127-
except ProcessExecutionError as _err:
128-
LOG.debug(('Failed command: %s\n%s') % \
129-
(' '.join(cmd), _err.message))
130-
return 'UNKNOWN'
131-
except OSError as _err:
132-
LOG.debug(('Failed command: %s\n%s') % \
133-
(' '.join(cmd), _err.message))
119+
system_name = util.read_dmi_data("system-product-name")
120+
if not system_name:
134121
return 'UNKNOWN'
135122

136-
if cmd_out.upper().startswith('RHEV'):
123+
sys_name = system_name.upper()
124+
125+
if sys_name.startswith('RHEV'):
137126
return 'RHEV'
138127

139-
if cmd_out.upper().startswith('VMWARE'):
128+
if sys_name.startswith('VMWARE'):
140129
return 'VSPHERE'
141130

142131
return 'UNKNOWN'

cloudinit/sources/DataSourceCloudSigma.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -44,27 +44,25 @@ def __init__(self, sys_cfg, distro, paths):
4444

4545
def is_running_in_cloudsigma(self):
4646
"""
47-
Uses dmidecode to detect if this instance of cloud-init is running
47+
Uses dmi data to detect if this instance of cloud-init is running
4848
in the CloudSigma's infrastructure.
4949
"""
5050
uname_arch = os.uname()[4]
5151
if uname_arch.startswith("arm") or uname_arch == "aarch64":
52-
# Disabling because dmidecode in CMD_DMI_SYSTEM crashes kvm process
52+
# Disabling because dmi data on ARM processors
5353
LOG.debug("Disabling CloudSigma datasource on arm (LP: #1243287)")
5454
return False
5555

56-
dmidecode_path = util.which('dmidecode')
57-
if not dmidecode_path:
56+
LOG.debug("determining hypervisor product name via dmi data")
57+
sys_product_name = util.read_dmi_data("system-product-name")
58+
if not sys_product_name:
59+
LOG.warn("failed to get hypervisor product name via dmi data")
5860
return False
61+
else:
62+
LOG.debug("detected hypervisor as {}".format(sys_product_name))
63+
return 'cloudsigma' in sys_product_name.lower()
5964

60-
LOG.debug("Determining hypervisor product name via dmidecode")
61-
try:
62-
cmd = [dmidecode_path, "--string", "system-product-name"]
63-
system_product_name, _ = util.subp(cmd)
64-
return 'cloudsigma' in system_product_name.lower()
65-
except:
66-
LOG.warn("Failed to get hypervisor product name via dmidecode")
67-
65+
LOG.warn("failed to query dmi data for system product name")
6866
return False
6967

7068
def get_data(self):

cloudinit/sources/DataSourceSmartOS.py

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -370,26 +370,13 @@ def query_data(noun, seed_device, seed_timeout, strip=False, default=None,
370370

371371

372372
def dmi_data():
373-
sys_uuid, sys_type = None, None
374-
dmidecode_path = util.which('dmidecode')
375-
if not dmidecode_path:
376-
return False
373+
sys_uuid = util.read_dmi_data("system-uuid")
374+
sys_type = util.read_dmi_data("system-product-name")
375+
376+
if not sys_uuid or not sys_type:
377+
return None
377378

378-
sys_uuid_cmd = [dmidecode_path, "-s", "system-uuid"]
379-
try:
380-
LOG.debug("Getting hostname from dmidecode")
381-
(sys_uuid, _err) = util.subp(sys_uuid_cmd)
382-
except Exception as e:
383-
util.logexc(LOG, "Failed to get system UUID", e)
384-
385-
sys_type_cmd = [dmidecode_path, "-s", "system-product-name"]
386-
try:
387-
LOG.debug("Determining hypervisor product name via dmidecode")
388-
(sys_type, _err) = util.subp(sys_type_cmd)
389-
except Exception as e:
390-
util.logexc(LOG, "Failed to get system UUID", e)
391-
392-
return (sys_uuid.lower().strip(), sys_type.strip())
379+
return (sys_uuid.lower(), sys_type)
393380

394381

395382
def write_boot_content(content, content_f, link=None, shebang=False,

cloudinit/util.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ def encode_text(text, encoding='utf-8'):
9090
return text
9191
return text.encode(encoding)
9292

93+
# Path for DMI Data
94+
DMI_SYS_PATH = "/sys/class/dmi/id"
95+
9396

9497
class ProcessExecutionError(IOError):
9598

@@ -2049,3 +2052,61 @@ def human2bytes(size):
20492052
raise ValueError("'%s': cannot be negative" % size_in)
20502053

20512054
return int(num * mpliers[mplier])
2055+
2056+
2057+
def _read_dmi_syspath(key):
2058+
"""
2059+
Reads dmi data with from /sys/class/dmi/id
2060+
"""
2061+
2062+
dmi_key = "{}/{}".format(DMI_SYS_PATH, key)
2063+
LOG.debug("querying dmi data {}".format(dmi_key))
2064+
try:
2065+
if not os.path.exists(dmi_key):
2066+
LOG.debug("did not find {}".format(dmi_key))
2067+
return None
2068+
2069+
key_data = load_file(dmi_key)
2070+
if not key_data:
2071+
LOG.debug("{} did not return any data".format(key))
2072+
return None
2073+
2074+
LOG.debug("dmi data {} returned {}".format(dmi_key, key_data))
2075+
return key_data.strip()
2076+
2077+
except Exception as e:
2078+
logexc(LOG, "failed read of {}".format(dmi_key), e)
2079+
return None
2080+
2081+
2082+
def _call_dmidecode(key, dmidecode_path):
2083+
"""
2084+
Calls out to dmidecode to get the data out. This is mostly for supporting
2085+
OS's without /sys/class/dmi/id support.
2086+
"""
2087+
try:
2088+
cmd = [dmidecode_path, "--string", key]
2089+
(result, _err) = subp(cmd)
2090+
LOG.debug("dmidecode returned '{}' for '{}'".format(result, key))
2091+
return result
2092+
except OSError as _err:
2093+
LOG.debug('failed dmidecode cmd: {}\n{}'.format(cmd, _err.message))
2094+
return None
2095+
2096+
2097+
def read_dmi_data(key):
2098+
"""
2099+
Wrapper for reading DMI data. This tries to determine whether the DMI
2100+
Data can be read directly, otherwise it will fallback to using dmidecode.
2101+
"""
2102+
if os.path.exists(DMI_SYS_PATH):
2103+
return _read_dmi_syspath(key)
2104+
2105+
dmidecode_path = which('dmidecode')
2106+
if dmidecode_path:
2107+
return _call_dmidecode(key, dmidecode_path)
2108+
2109+
LOG.warn("did not find either path {} or dmidecode command".format(
2110+
DMI_SYS_PATH))
2111+
2112+
return None

tests/unittests/helpers.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,10 @@ class FilesystemMockingTestCase(ResourceUsingTestCase):
169169
def setUp(self):
170170
ResourceUsingTestCase.setUp(self)
171171
self.patched_funcs = ExitStack()
172-
self.addCleanup(self.patched_funcs.close)
172+
173+
def tearDown(self):
174+
self.patched_funcs.close()
175+
ResourceUsingTestCase.tearDown(self)
173176

174177
def replicateTestRoot(self, example_root, target_root):
175178
real_root = self.resourceLocation()

tests/unittests/test_datasource/test_altcloud.py

Lines changed: 26 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import tempfile
2727

2828
from cloudinit import helpers
29+
from cloudinit import util
2930
from unittest import TestCase
3031

3132
# Get the cloudinit.sources.DataSourceAltCloud import items needed.
@@ -98,6 +99,16 @@ def _remove_user_data_files(mount_dir,
9899
pass
99100

100101

102+
def _dmi_data(expected):
103+
'''
104+
Spoof the data received over DMI
105+
'''
106+
def _data(key):
107+
return expected
108+
109+
return _data
110+
111+
101112
class TestGetCloudType(TestCase):
102113
'''
103114
Test to exercise method: DataSourceAltCloud.get_cloud_type()
@@ -106,66 +117,42 @@ class TestGetCloudType(TestCase):
106117
def setUp(self):
107118
'''Set up.'''
108119
self.paths = helpers.Paths({'cloud_dir': '/tmp'})
120+
self.dmi_data = util.read_dmi_data
109121
# We have a different code path for arm to deal with LP1243287
110122
# We have to switch arch to x86_64 to avoid test failure
111123
force_arch('x86_64')
112124

113125
def tearDown(self):
114126
# Reset
115-
cloudinit.sources.DataSourceAltCloud.CMD_DMI_SYSTEM = \
116-
['dmidecode', '--string', 'system-product-name']
117-
# Return back to original arch
127+
util.read_dmi_data = self.dmi_data
118128
force_arch()
119129

120130
def test_rhev(self):
121131
'''
122132
Test method get_cloud_type() for RHEVm systems.
123-
Forcing dmidecode return to match a RHEVm system: RHEV Hypervisor
133+
Forcing read_dmi_data return to match a RHEVm system: RHEV Hypervisor
124134
'''
125-
cloudinit.sources.DataSourceAltCloud.CMD_DMI_SYSTEM = \
126-
['echo', 'RHEV Hypervisor']
135+
util.read_dmi_data = _dmi_data('RHEV')
127136
dsrc = DataSourceAltCloud({}, None, self.paths)
128137
self.assertEquals('RHEV', \
129138
dsrc.get_cloud_type())
130139

131140
def test_vsphere(self):
132141
'''
133142
Test method get_cloud_type() for vSphere systems.
134-
Forcing dmidecode return to match a vSphere system: RHEV Hypervisor
143+
Forcing read_dmi_data return to match a vSphere system: RHEV Hypervisor
135144
'''
136-
cloudinit.sources.DataSourceAltCloud.CMD_DMI_SYSTEM = \
137-
['echo', 'VMware Virtual Platform']
145+
util.read_dmi_data = _dmi_data('VMware Virtual Platform')
138146
dsrc = DataSourceAltCloud({}, None, self.paths)
139147
self.assertEquals('VSPHERE', \
140148
dsrc.get_cloud_type())
141149

142150
def test_unknown(self):
143151
'''
144152
Test method get_cloud_type() for unknown systems.
145-
Forcing dmidecode return to match an unrecognized return.
146-
'''
147-
cloudinit.sources.DataSourceAltCloud.CMD_DMI_SYSTEM = \
148-
['echo', 'Unrecognized Platform']
149-
dsrc = DataSourceAltCloud({}, None, self.paths)
150-
self.assertEquals('UNKNOWN', \
151-
dsrc.get_cloud_type())
152-
153-
def test_exception1(self):
154-
'''
155-
Test method get_cloud_type() where command dmidecode fails.
156-
'''
157-
cloudinit.sources.DataSourceAltCloud.CMD_DMI_SYSTEM = \
158-
['ls', 'bad command']
159-
dsrc = DataSourceAltCloud({}, None, self.paths)
160-
self.assertEquals('UNKNOWN', \
161-
dsrc.get_cloud_type())
162-
163-
def test_exception2(self):
164-
'''
165-
Test method get_cloud_type() where command dmidecode is not available.
153+
Forcing read_dmi_data return to match an unrecognized return.
166154
'''
167-
cloudinit.sources.DataSourceAltCloud.CMD_DMI_SYSTEM = \
168-
['bad command']
155+
util.read_dmi_data = _dmi_data('Unrecognized Platform')
169156
dsrc = DataSourceAltCloud({}, None, self.paths)
170157
self.assertEquals('UNKNOWN', \
171158
dsrc.get_cloud_type())
@@ -180,6 +167,7 @@ def setUp(self):
180167
'''Set up.'''
181168
self.paths = helpers.Paths({'cloud_dir': '/tmp'})
182169
self.cloud_info_file = tempfile.mkstemp()[1]
170+
self.dmi_data = util.read_dmi_data
183171
cloudinit.sources.DataSourceAltCloud.CLOUD_INFO_FILE = \
184172
self.cloud_info_file
185173

@@ -192,6 +180,7 @@ def tearDown(self):
192180
except OSError:
193181
pass
194182

183+
util.read_dmi_data = self.dmi_data
195184
cloudinit.sources.DataSourceAltCloud.CLOUD_INFO_FILE = \
196185
'/etc/sysconfig/cloud-info'
197186

@@ -243,6 +232,7 @@ class TestGetDataNoCloudInfoFile(TestCase):
243232
def setUp(self):
244233
'''Set up.'''
245234
self.paths = helpers.Paths({'cloud_dir': '/tmp'})
235+
self.dmi_data = util.read_dmi_data
246236
cloudinit.sources.DataSourceAltCloud.CLOUD_INFO_FILE = \
247237
'no such file'
248238
# We have a different code path for arm to deal with LP1243287
@@ -253,34 +243,30 @@ def tearDown(self):
253243
# Reset
254244
cloudinit.sources.DataSourceAltCloud.CLOUD_INFO_FILE = \
255245
'/etc/sysconfig/cloud-info'
256-
cloudinit.sources.DataSourceAltCloud.CMD_DMI_SYSTEM = \
257-
['dmidecode', '--string', 'system-product-name']
246+
util.read_dmi_data = self.dmi_data
258247
# Return back to original arch
259248
force_arch()
260249

261250
def test_rhev_no_cloud_file(self):
262251
'''Test No cloud info file module get_data() forcing RHEV.'''
263252

264-
cloudinit.sources.DataSourceAltCloud.CMD_DMI_SYSTEM = \
265-
['echo', 'RHEV Hypervisor']
253+
util.read_dmi_data = _dmi_data('RHEV Hypervisor')
266254
dsrc = DataSourceAltCloud({}, None, self.paths)
267255
dsrc.user_data_rhevm = lambda: True
268256
self.assertEquals(True, dsrc.get_data())
269257

270258
def test_vsphere_no_cloud_file(self):
271259
'''Test No cloud info file module get_data() forcing VSPHERE.'''
272260

273-
cloudinit.sources.DataSourceAltCloud.CMD_DMI_SYSTEM = \
274-
['echo', 'VMware Virtual Platform']
261+
util.read_dmi_data = _dmi_data('VMware Virtual Platform')
275262
dsrc = DataSourceAltCloud({}, None, self.paths)
276263
dsrc.user_data_vsphere = lambda: True
277264
self.assertEquals(True, dsrc.get_data())
278265

279266
def test_failure_no_cloud_file(self):
280267
'''Test No cloud info file module get_data() forcing unrecognized.'''
281268

282-
cloudinit.sources.DataSourceAltCloud.CMD_DMI_SYSTEM = \
283-
['echo', 'Unrecognized Platform']
269+
util.read_dmi_data = _dmi_data('Unrecognized Platform')
284270
dsrc = DataSourceAltCloud({}, None, self.paths)
285271
self.assertEquals(False, dsrc.get_data())
286272

0 commit comments

Comments
 (0)