From 8ef425060f3f4e48932b2059001c46300e962a5a Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrange" Date: Sat, 28 Oct 2017 15:47:22 +0100 Subject: [PATCH] NoCloud: support seed of nocloud from SMBIOS OEM strings data Previously support was added to seed from the SMBIOS data field 'system-serial-number'. Relying on this field is a bad idea as it has a defined purpose which is nothing todo with cloud-init. SMBIOS provides a general purpose data table "OEM Strings" (type 11) which can be used for providing adhoc vendor defined data to operating systems. This is a good fit for seeding cloud-init from the host. Support for populating this table has recently been added to QEMU. e.g. $QEMU -smbios type=11,value=cloud-init:ds=nocloud-net;s=http://10.10.0.1:8000/ and also to libvirt. e.g. .... cloud-init:ds=nocloud-net;s=http://10.10.0.1:8000/ Notice that to avoid clash with other usage of the "OEM strings" table, the code looks specifically for a string entry that has a prefix 'cloud-init:'. Signed-off-by: Daniel P. Berrange --- cloudinit/sources/DataSourceNoCloud.py | 6 ++-- cloudinit/util.py | 52 ++++++++++++++++++++++++++++++++-- doc/rtd/topics/datasources/nocloud.rst | 16 +++++++++-- 3 files changed, 66 insertions(+), 8 deletions(-) diff --git a/cloudinit/sources/DataSourceNoCloud.py b/cloudinit/sources/DataSourceNoCloud.py index e641244d..75517a07 100644 --- a/cloudinit/sources/DataSourceNoCloud.py +++ b/cloudinit/sources/DataSourceNoCloud.py @@ -46,8 +46,10 @@ 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') - if serial and load_cmdline_data(md, serial): + data = util.get_dmi_oem_string("cloud-init:") + if data is None: + data = util.read_dmi_data('system-serial-number') + if data and load_cmdline_data(md, data): found.append("dmi") mydata = _merge_new_seed(mydata, {'meta-data': md}) except Exception: diff --git a/cloudinit/util.py b/cloudinit/util.py index e1290aa8..8b579592 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -2374,13 +2374,13 @@ def _read_dmi_syspath(key): return None -def _call_dmidecode(key, dmidecode_path): +def _call_dmidecode(arg, 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] + cmd = [dmidecode_path, arg, key] (result, _err) = subp(cmd) LOG.debug("dmidecode returned '%s' for '%s'", result, key) result = result.strip() @@ -2428,13 +2428,59 @@ def read_dmi_data(key): dmidecode_path = which('dmidecode') if dmidecode_path: - return _call_dmidecode(key, dmidecode_path) + return _call_dmidecode("--string", key, dmidecode_path) LOG.warning("did not find either path %s or dmidecode command", DMI_SYS_PATH) return None +def read_dmi_oem_strings(): + if is_container(): + return [] + + #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 (uname_arch == "x86_64" or + (uname_arch.startswith("i") and uname_arch[2:] == "86") or + uname_arch == 'aarch64' or + uname_arch == 'amd64'): + LOG.debug("dmidata is not supported on %s", uname_arch) + return [] + + dmidecode_path = which('dmidecode') + if dmidecode_path: + countstr = _call_dmidecode("--oem-string", "count", dmidecode_path) + if countstr is None: + return [] + try: + strings = [] + count = int(countstr) + for i in range(count): + val = _call_dmidecode("--oem-string", str(i), dmidecode_path) + if val is not None: + strings.append(val) + return strings + except: + return [] + + LOG.warning("did not find either path %s or dmidecode command", + DMI_SYS_PATH) + return [] + + +def get_dmi_oem_string(prefix): + vals = read_dmi_oem_strings() + for val in vals: + if val.startswith(prefix): + return val[len(prefix):] + return None + + def message_from_string(string): if sys.version_info[:2] < (2, 7): return email.message_from_file(six.StringIO(string)) diff --git a/doc/rtd/topics/datasources/nocloud.rst b/doc/rtd/topics/datasources/nocloud.rst index 08578e86..a3d310f0 100644 --- a/doc/rtd/topics/datasources/nocloud.rst +++ b/doc/rtd/topics/datasources/nocloud.rst @@ -12,7 +12,9 @@ You can provide meta-data and user-data to a local vm boot via files on a ``cidata``. Alternatively, you can provide meta-data via kernel command line or SMBIOS -"serial number" option. The data must be passed in the form of a string: +"OEM strings" (type 11) data table. For backwards compatibility the SMBIOS +"serial number" option is also supported, but its usage is discouraged +if "OEM strings" is available. The data must be passed in the form of a string: :: @@ -34,13 +36,21 @@ With ``ds=nocloud``, the ``seedfrom`` value must start with ``/`` or ``file://``. With ``ds=nocloud-net``, the ``seedfrom`` value must start with ``http://``, ``https://`` or ``ftp://`` -e.g. you can pass this option to QEMU: +To pass this to QEMU as an OEM string, prefix the value with the string +'cloud-init:'. e.g. + +:: + + -smbios type=11,value=cloud-init:ds=nocloud-net;s=http://10.10.0.1:8000/ + +When using the (now discouraged) serial number option, no prefix is +required. e.g. :: -smbios type=1,serial=ds=nocloud-net;s=http://10.10.0.1:8000/ -to cause NoCloud to fetch the full meta-data from http://10.10.0.1:8000/meta-data +These will cause NoCloud to fetch the full meta-data from http://10.10.0.1:8000/meta-data after the network initialization is complete. These user-data and meta-data files are expected to be in the following format. -- 2.14.3