From f1930b8ae5c62c45d5fb1decd98f0e997e56fd59 Mon Sep 17 00:00:00 2001 From: akutz Date: Mon, 18 Sep 2023 15:34:15 -0500 Subject: [PATCH] vmware: Fall back to vmtoolsd if vmware-rpctool errs This patch udpates the ds-identify script and the VMware datasource to fall back to using the vmtoolsd program if vmware-rpctool errors. --- cloudinit/sources/DataSourceOVF.py | 57 +++++++++--- cloudinit/sources/DataSourceVMware.py | 108 ++++++++++++++++------- doc/rtd/reference/datasources/ovf.rst | 27 ++++++ doc/rtd/reference/datasources/vmware.rst | 24 +++++ tests/unittests/sources/test_ovf.py | 50 +++++++++-- tests/unittests/sources/test_vmware.py | 10 +-- tests/unittests/test_ds_identify.py | 93 +++++++++++++++---- tools/ds-identify | 100 +++++++++++++++------ 8 files changed, 365 insertions(+), 104 deletions(-) diff --git a/cloudinit/sources/DataSourceOVF.py b/cloudinit/sources/DataSourceOVF.py index 7baef3a53d52..7b9bf65af9b4 100644 --- a/cloudinit/sources/DataSourceOVF.py +++ b/cloudinit/sources/DataSourceOVF.py @@ -8,6 +8,18 @@ # # This file is part of cloud-init. See LICENSE file for license information. +"""Cloud-Init DataSource for OVF + +This module provides a cloud-init datasource for OVF data. + +This module may be unit tested by running the following command from the root +of the Cloud-Init repository: + + make clean_pyc && PYTHONPATH="$(pwd)" \ + python3 -m pytest -v --log-level=DEBUG \ + tests/unittests/sources/test_ovf.py +""" + import base64 import os import re @@ -20,7 +32,6 @@ class DataSourceOVF(sources.DataSource): - dsname = "OVF" def __init__(self, sys_cfg, distro, paths): @@ -142,7 +153,7 @@ def read_ovf_environment(contents, read_network=False): cfg_props = ["password"] md_props = ["seedfrom", "local-hostname", "public-keys", "instance-id"] network_props = ["network-config"] - for (prop, val) in props.items(): + for prop, val in props.items(): if prop == "hostname": prop = "local-hostname" if prop in md_props: @@ -220,10 +231,9 @@ def maybe_cdrom_device(devname): # Transport functions are called with no arguments and return # either None (indicating not present) or string content of an ovf-env.xml def transport_iso9660(require_iso=True): - # Go through mounts to see if it was already mounted mounts = util.mounts() - for (dev, info) in mounts.items(): + for dev, info in mounts.items(): fstype = info["fstype"] if fstype != "iso9660" and require_iso: continue @@ -259,21 +269,43 @@ def transport_iso9660(require_iso=True): def transport_vmware_guestinfo(): - rpctool = "vmware-rpctool" - not_found = None - if not subp.which(rpctool): - return not_found - cmd = [rpctool, "info-get guestinfo.ovfEnv"] + is_vmtoolsd = False + rpctool = subp.which("vmware-rpctool") + + if not rpctool: + rpctool = subp.which("vmtoolsd") + is_vmtoolsd = True + + if not rpctool: + return None + + try: + return transport_vmware_guestinfo_rpctool(rpctool, is_vmtoolsd) + except subp.ProcessExecutionError: + rpctool = subp.which("vmtoolsd") + is_vmtoolsd = True + return transport_vmware_guestinfo_rpctool(rpctool, is_vmtoolsd) + + +def transport_vmware_guestinfo_rpctool(rpctool, is_vmtoolsd=False): + args = [rpctool] + if is_vmtoolsd: + args.append("--cmd") + args.append("info-get guestinfo.ovfEnv") + try: - out, _err = subp.subp(cmd) + out, _err = subp.subp(args) if out: return out - LOG.debug("cmd %s exited 0 with empty stdout: %s", cmd, out) + LOG.debug("cmd %s exited 0 with empty stdout: %s", args, out) except subp.ProcessExecutionError as e: if e.exit_code != 1: LOG.warning("%s exited with code %d", rpctool, e.exit_code) LOG.debug(e) - return not_found + if not is_vmtoolsd: + raise e + + return None def find_child(node, filter_func): @@ -287,7 +319,6 @@ def find_child(node, filter_func): def get_properties(contents): - dom = minidom.parseString(contents) if dom.documentElement.localName != "Environment": raise XmlError("No Environment Node") diff --git a/cloudinit/sources/DataSourceVMware.py b/cloudinit/sources/DataSourceVMware.py index 616a08f97f57..d2b7016fa69b 100644 --- a/cloudinit/sources/DataSourceVMware.py +++ b/cloudinit/sources/DataSourceVMware.py @@ -61,6 +61,13 @@ why netifaces was used in the first place in order to either smooth the transition away from netifaces or embrace it further up the cloud-init stack. + +This module may be unit tested by running the following command from the root +of the Cloud-Init repository: + + make clean_pyc && PYTHONPATH="$(pwd)" \ + python3 -m pytest -v --log-level=DEBUG \ + tests/unittests/sources/test_vmware.py """ import collections @@ -89,6 +96,7 @@ DATA_ACCESS_METHOD_GUESTINFO = "guestinfo" DATA_ACCESS_METHOD_IMC = "imc" +VMTOOLSD = which("vmtoolsd") VMWARE_RPCTOOL = which("vmware-rpctool") REDACT = "redact" CLEANUP_GUESTINFO = "cleanup-guestinfo" @@ -146,7 +154,12 @@ def __init__(self, sys_cfg, distro, paths, ud_proc=None): self.cfg = {} self.data_access_method = None - self.vmware_rpctool = VMWARE_RPCTOOL + if VMWARE_RPCTOOL: + self.rpctool = VMWARE_RPCTOOL + elif VMTOOLSD: + self.rpctool = VMTOOLSD + else: + self.rpctool = None # A list includes all possible data transports, each tuple represents # one data transport type. This datasource will try to get data from @@ -237,7 +250,7 @@ def setup(self, is_new_instance): # Reflect any possible local IPv4 or IPv6 addresses in the guest # info. - advertise_local_ip_addrs(host_info) + advertise_local_ip_addrs(host_info, self.rpctool) # Ensure the metadata gets updated with information about the # host, including the network interfaces, default IP addresses, @@ -313,7 +326,7 @@ def redact_keys(self): keys_to_redact = self.metadata[CLEANUP_GUESTINFO] if self.data_access_method == DATA_ACCESS_METHOD_GUESTINFO: - guestinfo_redact_keys(keys_to_redact, self.vmware_rpctool) + guestinfo_redact_keys(keys_to_redact, self.rpctool) def get_envvar_data_fn(self): """ @@ -331,11 +344,23 @@ def get_guestinfo_data_fn(self): """ check to see if there is data via the guestinfo transport """ + if not self.rpctool: + return (None, None, None) + md, ud, vd = None, None, None - if self.vmware_rpctool: - md = guestinfo("metadata", self.vmware_rpctool) - ud = guestinfo("userdata", self.vmware_rpctool) - vd = guestinfo("vendordata", self.vmware_rpctool) + try: + md = guestinfo("metadata", self.rpctool) + ud = guestinfo("userdata", self.rpctool) + vd = guestinfo("vendordata", self.rpctool) + except ProcessExecutionError: + self.rpctool = VMTOOLSD + + if not self.rpctool: + return (None, None, None) + + md = guestinfo("metadata", self.rpctool) + ud = guestinfo("userdata", self.rpctool) + vd = guestinfo("vendordata", self.rpctool) return (md, ud, vd) @@ -447,7 +472,7 @@ def get_none_if_empty_val(val): return val -def advertise_local_ip_addrs(host_info): +def advertise_local_ip_addrs(host_info, rpctool=VMWARE_RPCTOOL): """ advertise_local_ip_addrs gets the local IP address information from the provided host_info map and sets the addresses in the guestinfo @@ -460,12 +485,12 @@ def advertise_local_ip_addrs(host_info): # info. local_ipv4 = host_info.get(LOCAL_IPV4) if local_ipv4: - guestinfo_set_value(LOCAL_IPV4, local_ipv4) + guestinfo_set_value(LOCAL_IPV4, local_ipv4, rpctool) LOG.info("advertised local ipv4 address %s in guestinfo", local_ipv4) local_ipv6 = host_info.get(LOCAL_IPV6) if local_ipv6: - guestinfo_set_value(LOCAL_IPV6, local_ipv6) + guestinfo_set_value(LOCAL_IPV6, local_ipv6, rpctool) LOG.info("advertised local ipv6 address %s in guestinfo", local_ipv6) @@ -507,46 +532,53 @@ def guestinfo_envvar_get_value(key): return handle_returned_guestinfo_val(key, os.environ.get(env_key, "")) -def guestinfo(key, vmware_rpctool=VMWARE_RPCTOOL): +def guestinfo(key, rpctool=VMWARE_RPCTOOL): """ guestinfo returns the guestinfo value for the provided key, decoding the value when required """ - val = guestinfo_get_value(key, vmware_rpctool) + val = guestinfo_get_value(key, rpctool) if not val: return None - enc_type = guestinfo_get_value(key + ".encoding", vmware_rpctool) + enc_type = guestinfo_get_value(key + ".encoding", rpctool) return decode(get_guestinfo_key_name(key), enc_type, val) -def guestinfo_get_value(key, vmware_rpctool=VMWARE_RPCTOOL): +def guestinfo_get_value(key, rpctool=VMWARE_RPCTOOL): """ Returns a guestinfo value for the specified key. """ - LOG.debug("Getting guestinfo value for key %s", key) + LOG.debug("Getting guestinfo value for key %s with %s", key, rpctool) + + args = [rpctool] + if rpctool == VMTOOLSD: + args.append("--cmd") + args.append("info-get " + get_guestinfo_key_name(key)) try: - (stdout, stderr) = subp( - [ - vmware_rpctool, - "info-get " + get_guestinfo_key_name(key), - ] - ) + (stdout, stderr) = subp(args) if stderr == NOVAL: LOG.debug("No value found for key %s", key) elif not stdout: LOG.error("Failed to get guestinfo value for key %s", key) return handle_returned_guestinfo_val(key, stdout) except ProcessExecutionError as error: + # No matter the tool used to access the data, if NOVAL was returned on + # stderr, do not raise an exception. if error.stderr == NOVAL: LOG.debug("No value found for key %s", key) else: + # Any other result gets logged as an error, and if the tool was + # VMWARE_RPCTOOL, then raise the exception so the caller can try + # again with VMTOOLSD. util.logexc( LOG, "Failed to get guestinfo value for key %s: %s", key, error, ) + if rpctool == VMWARE_RPCTOOL: + raise error except Exception: util.logexc( LOG, @@ -558,7 +590,7 @@ def guestinfo_get_value(key, vmware_rpctool=VMWARE_RPCTOOL): return None -def guestinfo_set_value(key, value, vmware_rpctool=VMWARE_RPCTOOL): +def guestinfo_set_value(key, value, rpctool=VMWARE_RPCTOOL): """ Sets a guestinfo value for the specified key. Set value to an empty string to clear an existing guestinfo key. @@ -571,17 +603,25 @@ def guestinfo_set_value(key, value, vmware_rpctool=VMWARE_RPCTOOL): if value == "": value = " " - LOG.debug("Setting guestinfo key=%s to value=%s", key, value) + LOG.debug( + "Setting guestinfo key=%s to value=%s with %s", + key, + value, + rpctool, + ) + + args = [rpctool] + if rpctool == VMTOOLSD: + args.append("--cmd") + args.append("info-set %s %s" % (get_guestinfo_key_name(key), value)) try: - subp( - [ - vmware_rpctool, - "info-set %s %s" % (get_guestinfo_key_name(key), value), - ] - ) + subp(args) return True except ProcessExecutionError as error: + # Any error result gets logged as an error, and if the tool was + # VMWARE_RPCTOOL, then raise the exception so the caller can try + # again with VMTOOLSD. util.logexc( LOG, "Failed to set guestinfo key=%s to value=%s: %s", @@ -589,6 +629,8 @@ def guestinfo_set_value(key, value, vmware_rpctool=VMWARE_RPCTOOL): value, error, ) + if rpctool == VMWARE_RPCTOOL: + raise error except Exception: util.logexc( LOG, @@ -601,7 +643,7 @@ def guestinfo_set_value(key, value, vmware_rpctool=VMWARE_RPCTOOL): return None -def guestinfo_redact_keys(keys, vmware_rpctool=VMWARE_RPCTOOL): +def guestinfo_redact_keys(keys, rpctool=VMWARE_RPCTOOL): """ guestinfo_redact_keys redacts guestinfo of all of the keys in the given list. each key will have its value set to "---". Since the value is valid @@ -614,12 +656,10 @@ def guestinfo_redact_keys(keys, vmware_rpctool=VMWARE_RPCTOOL): for key in keys: key_name = get_guestinfo_key_name(key) LOG.info("clearing %s", key_name) - if not guestinfo_set_value( - key, GUESTINFO_EMPTY_YAML_VAL, vmware_rpctool - ): + if not guestinfo_set_value(key, GUESTINFO_EMPTY_YAML_VAL, rpctool): LOG.error("failed to clear %s", key_name) LOG.info("clearing %s.encoding", key_name) - if not guestinfo_set_value(key + ".encoding", "", vmware_rpctool): + if not guestinfo_set_value(key + ".encoding", "", rpctool): LOG.error("failed to clear %s.encoding", key_name) diff --git a/doc/rtd/reference/datasources/ovf.rst b/doc/rtd/reference/datasources/ovf.rst index 5a7af4fc8fd0..65e9ec60a6ed 100644 --- a/doc/rtd/reference/datasources/ovf.rst +++ b/doc/rtd/reference/datasources/ovf.rst @@ -6,6 +6,33 @@ OVF The OVF datasource provides a datasource for reading data from an `Open Virtualization Format`_ ISO transport. +Graceful rpctool fallback +------------------------- + +The datasource initially attempts to use the program ``vmware-rpctool`` if it +is available. However, if the program returns a non-zero exit code, then the +datasource falls back to using the program ``vmtoolsd`` with the ``--cmd`` +argument. + +On some older versions of ESXi and open-vm-tools, the ``vmware-rpctool`` +program is much more performant than ``vmtoolsd``. While this gap was +closed, it is not reasonable to expect the guest where Cloud-Init is running to +know whether the underlying hypervisor has the patch. + +Additionally, vSphere VMs may have the following present in their VMX file: + +.. code-block:: ini + + guest_rpc.rpci.auth.cmd.info-set = "TRUE" + guest_rpc.rpci.auth.cmd.info-get = "TRUE" + +The above configuration causes the ``vmware-rpctool`` command to return a +non-zero exit code with the error message ``Permission denied``. If this should +occur, the datasource falls back to using ``vmtoolsd``. + +Additional information +---------------------- + For further information see a full working example in ``cloud-init``'s source code tree in :file:`doc/sources/ovf`. diff --git a/doc/rtd/reference/datasources/vmware.rst b/doc/rtd/reference/datasources/vmware.rst index 401a52cb0239..399e53e6591b 100644 --- a/doc/rtd/reference/datasources/vmware.rst +++ b/doc/rtd/reference/datasources/vmware.rst @@ -148,6 +148,30 @@ Features This section reviews several features available in this datasource. +Graceful rpctool fallback +------------------------- + +The datasource initially attempts to use the program ``vmware-rpctool`` if it +is available. However, if the program returns a non-zero exit code, then the +datasource falls back to using the program ``vmtoolsd`` with the ``--cmd`` +argument. + +On some older versions of ESXi and open-vm-tools, the ``vmware-rpctool`` +program is much more performant than ``vmtoolsd``. While this gap was +closed, it is not reasonable to expect the guest where Cloud-Init is running to +know whether the underlying hypervisor has the patch. + +Additionally, vSphere VMs may have the following present in their VMX file: + +.. code-block:: ini + + guest_rpc.rpci.auth.cmd.info-set = "TRUE" + guest_rpc.rpci.auth.cmd.info-get = "TRUE" + +The above configuration causes the ``vmware-rpctool`` command to return a +non-zero exit code with the error message ``Permission denied``. If this should +occur, the datasource falls back to using ``vmtoolsd``. + Instance data and lazy networks ------------------------------- diff --git a/tests/unittests/sources/test_ovf.py b/tests/unittests/sources/test_ovf.py index 109d8889ab32..09c2d5669da8 100644 --- a/tests/unittests/sources/test_ovf.py +++ b/tests/unittests/sources/test_ovf.py @@ -198,7 +198,6 @@ def test_with_b64_network_config_disable_read_network(self): class TestDatasourceOVF(CiTestCase): - with_logs = True def setUp(self): @@ -452,9 +451,12 @@ class TestTransportVmwareGuestinfo(CiTestCase): rpctool = "vmware-rpctool" with_logs = True rpctool_path = "/not/important/vmware-rpctool" + vmtoolsd_path = "/not/important/vmtoolsd" - def test_without_vmware_rpctool_returns_notfound(self, m_subp, m_which): - m_which.return_value = None + def test_without_vmware_rpctool_and_without_vmtoolsd_returns_notfound( + self, m_subp, m_which + ): + m_which.side_effect = [None, None] self.assertEqual(NOT_FOUND, dsovf.transport_vmware_guestinfo()) self.assertEqual( 0, @@ -462,6 +464,18 @@ def test_without_vmware_rpctool_returns_notfound(self, m_subp, m_which): "subp should not be called if no rpctool in path.", ) + def test_without_vmware_rpctool_and_with_vmtoolsd_returns_found( + self, m_subp, m_which + ): + m_which.side_effect = [None, self.vmtoolsd_path] + m_subp.side_effect = [(fill_properties({}), "")] + self.assertNotEqual(NOT_FOUND, dsovf.transport_vmware_guestinfo()) + self.assertEqual( + 1, + m_subp.call_count, + "subp should be called once bc/ rpctool missing.", + ) + def test_notfound_on_exit_code_1(self, m_subp, m_which): """If vmware-rpctool exits 1, then must return not found.""" m_which.return_value = self.rpctool_path @@ -469,7 +483,7 @@ def test_notfound_on_exit_code_1(self, m_subp, m_which): stdout="", stderr="No value found", exit_code=1, cmd=["unused"] ) self.assertEqual(NOT_FOUND, dsovf.transport_vmware_guestinfo()) - self.assertEqual(1, m_subp.call_count) + self.assertEqual(2, m_subp.call_count) self.assertNotIn( "WARNING", self.logs.getvalue(), @@ -495,7 +509,7 @@ def test_notfound_and_warns_on_unexpected_exit_code(self, m_subp, m_which): stdout=None, stderr="No value found", exit_code=2, cmd=["unused"] ) self.assertEqual(NOT_FOUND, dsovf.transport_vmware_guestinfo()) - self.assertEqual(1, m_subp.call_count) + self.assertEqual(2, m_subp.call_count) self.assertIn( "WARNING", self.logs.getvalue(), @@ -510,6 +524,32 @@ def test_found_when_guestinfo_present(self, m_subp, m_which): self.assertEqual(content, dsovf.transport_vmware_guestinfo()) self.assertEqual(1, m_subp.call_count) + def test_vmware_rpctool_fails_and_vmtoolsd_fails(self, m_subp, m_which): + """When vmware-rpctool fails and vmtoolsd fails""" + m_which.side_effect = [self.rpctool_path, self.vmtoolsd_path] + m_subp.side_effect = [ + subp.ProcessExecutionError( + stdout="", stderr="No value found", exit_code=1, cmd=["unused"] + ), + subp.ProcessExecutionError( + stdout="", stderr="No value found", exit_code=1, cmd=["unused"] + ), + ] + self.assertEqual(NOT_FOUND, dsovf.transport_vmware_guestinfo()) + self.assertEqual(2, m_subp.call_count) + + def test_vmware_rpctool_fails_and_vmtoolsd_success(self, m_subp, m_which): + """When vmware-rpctool fails but vmtoolsd succeeds""" + m_which.side_effect = [self.rpctool_path, self.vmtoolsd_path] + m_subp.side_effect = [ + subp.ProcessExecutionError( + stdout="", stderr="No value found", exit_code=1, cmd=["unused"] + ), + (fill_properties({}), ""), + ] + self.assertNotEqual(NOT_FOUND, dsovf.transport_vmware_guestinfo()) + self.assertEqual(2, m_subp.call_count) + # # vi: ts=4 expandtab diff --git a/tests/unittests/sources/test_vmware.py b/tests/unittests/sources/test_vmware.py index a2c43b1bc54c..bade2600ea16 100644 --- a/tests/unittests/sources/test_vmware.py +++ b/tests/unittests/sources/test_vmware.py @@ -100,7 +100,7 @@ def setUp(self): def test_no_data_access_method(self): ds = get_ds(self.tmp) - ds.vmware_rpctool = None + ds.rpctool = None with mock.patch( "cloudinit.sources.DataSourceVMware.is_vmware_platform", return_value=False, @@ -252,7 +252,7 @@ def create_system_files(self): def assert_get_data_ok(self, m_fn, m_fn_call_count=6): ds = get_ds(self.tmp) - ds.vmware_rpctool = None + ds.rpctool = None ret = ds.get_data() self.assertTrue(ret) self.assertEqual(m_fn_call_count, m_fn.call_count) @@ -381,7 +381,7 @@ def create_system_files(self): def assert_get_data_ok(self, m_fn, m_fn_call_count=6): ds = get_ds(self.tmp) - ds.vmware_rpctool = "vmware-rpctool" + ds.rpctool = "vmware-rpctool" ret = ds.get_data() self.assertTrue(ret) self.assertEqual(m_fn_call_count, m_fn.call_count) @@ -494,7 +494,7 @@ def test_ds_invalid_on_non_vmware_platform(self, m_fn): m_fn.side_effect = [VMW_METADATA_YAML, "", "", "", "", ""] ds = get_ds(self.tmp) - ds.vmware_rpctool = "vmware-rpctool" + ds.rpctool = "vmware-rpctool" ret = ds.get_data() self.assertFalse(ret) @@ -1217,7 +1217,7 @@ def get_ds(temp_dir): ds = DataSourceVMware.DataSourceVMware( settings.CFG_BUILTIN, None, helpers.Paths({"run_dir": temp_dir}) ) - ds.vmware_rpctool = "vmware-rpctool" + ds.rpctool = "vmware-rpctool" return ds diff --git a/tests/unittests/test_ds_identify.py b/tests/unittests/test_ds_identify.py index 66dae60e4a1b..a273e2b9159d 100644 --- a/tests/unittests/test_ds_identify.py +++ b/tests/unittests/test_ds_identify.py @@ -1013,7 +1013,8 @@ def test_vmware_envvar_activated_by_vendordata(self): def test_vmware_guestinfo_no_data(self): """VMware: guestinfo transport no data""" - self._test_ds_not_found("VMware-GuestInfo-NoData") + self._test_ds_not_found("VMware-GuestInfo-NoData-Rpctool") + self._test_ds_not_found("VMware-GuestInfo-NoData-Vmtoolsd") def test_vmware_guestinfo_no_virt_id(self): """VMware: guestinfo transport fails if no virt id""" @@ -1800,6 +1801,11 @@ def _print_run_output(rc, out, err, cfg, files): "ret": 0, "out": "/usr/bin/vmware-rpctool", }, + { + "name": "vmware_has_vmtoolsd", + "ret": 1, + "out": "/usr/bin/vmtoolsd", + }, ], "files": { # Setup vmware customization enabled @@ -1917,7 +1923,7 @@ def _print_run_output(rc, out, err, cfg, files): MOCK_VIRT_IS_VMWARE, ], }, - "VMware-GuestInfo-NoData": { + "VMware-GuestInfo-NoData-Rpctool": { "ds": "VMware", "policy_dmi": POLICY_FOUND_ONLY, "mocks": [ @@ -1927,15 +1933,49 @@ def _print_run_output(rc, out, err, cfg, files): "out": "/usr/bin/vmware-rpctool", }, { - "name": "vmware_rpctool_guestinfo_metadata", + "name": "vmware_has_vmtoolsd", + "ret": 1, + "out": "/usr/bin/vmtoolsd", + }, + { + "name": "vmware_guestinfo_metadata", + "ret": 1, + }, + { + "name": "vmware_guestinfo_userdata", + "ret": 1, + }, + { + "name": "vmware_guestinfo_vendordata", + "ret": 1, + }, + MOCK_VIRT_IS_VMWARE, + ], + }, + "VMware-GuestInfo-NoData-Vmtoolsd": { + "ds": "VMware", + "policy_dmi": POLICY_FOUND_ONLY, + "mocks": [ + { + "name": "vmware_has_rpctool", + "ret": 1, + "out": "/usr/bin/vmware-rpctool", + }, + { + "name": "vmware_has_vmtoolsd", + "ret": 0, + "out": "/usr/bin/vmtoolsd", + }, + { + "name": "vmware_guestinfo_metadata", "ret": 1, }, { - "name": "vmware_rpctool_guestinfo_userdata", + "name": "vmware_guestinfo_userdata", "ret": 1, }, { - "name": "vmware_rpctool_guestinfo_vendordata", + "name": "vmware_guestinfo_vendordata", "ret": 1, }, MOCK_VIRT_IS_VMWARE, @@ -1950,16 +1990,16 @@ def _print_run_output(rc, out, err, cfg, files): "out": "/usr/bin/vmware-rpctool", }, { - "name": "vmware_rpctool_guestinfo_metadata", + "name": "vmware_guestinfo_metadata", "ret": 0, "out": "---", }, { - "name": "vmware_rpctool_guestinfo_userdata", + "name": "vmware_guestinfo_userdata", "ret": 1, }, { - "name": "vmware_rpctool_guestinfo_vendordata", + "name": "vmware_guestinfo_vendordata", "ret": 1, }, ], @@ -1969,20 +2009,25 @@ def _print_run_output(rc, out, err, cfg, files): "mocks": [ { "name": "vmware_has_rpctool", - "ret": 0, + "ret": 1, "out": "/usr/bin/vmware-rpctool", }, { - "name": "vmware_rpctool_guestinfo_metadata", + "name": "vmware_has_vmtoolsd", + "ret": 0, + "out": "/usr/bin/vmtoolsd", + }, + { + "name": "vmware_guestinfo_metadata", "ret": 0, "out": "---", }, { - "name": "vmware_rpctool_guestinfo_userdata", + "name": "vmware_guestinfo_userdata", "ret": 1, }, { - "name": "vmware_rpctool_guestinfo_vendordata", + "name": "vmware_guestinfo_vendordata", "ret": 1, }, MOCK_VIRT_IS_VMWARE, @@ -1997,16 +2042,21 @@ def _print_run_output(rc, out, err, cfg, files): "out": "/usr/bin/vmware-rpctool", }, { - "name": "vmware_rpctool_guestinfo_metadata", + "name": "vmware_has_vmtoolsd", + "ret": 1, + "out": "/usr/bin/vmtoolsd", + }, + { + "name": "vmware_guestinfo_metadata", "ret": 1, }, { - "name": "vmware_rpctool_guestinfo_userdata", + "name": "vmware_guestinfo_userdata", "ret": 0, "out": "---", }, { - "name": "vmware_rpctool_guestinfo_vendordata", + "name": "vmware_guestinfo_vendordata", "ret": 1, }, MOCK_VIRT_IS_VMWARE, @@ -2017,19 +2067,24 @@ def _print_run_output(rc, out, err, cfg, files): "mocks": [ { "name": "vmware_has_rpctool", - "ret": 0, + "ret": 1, "out": "/usr/bin/vmware-rpctool", }, { - "name": "vmware_rpctool_guestinfo_metadata", + "name": "vmware_has_vmtoolsd", + "ret": 0, + "out": "/usr/bin/vmtoolsd", + }, + { + "name": "vmware_guestinfo_metadata", "ret": 1, }, { - "name": "vmware_rpctool_guestinfo_userdata", + "name": "vmware_guestinfo_userdata", "ret": 1, }, { - "name": "vmware_rpctool_guestinfo_vendordata", + "name": "vmware_guestinfo_vendordata", "ret": 0, "out": "---", }, diff --git a/tools/ds-identify b/tools/ds-identify index 16376ef05c5b..834d8a906315 100755 --- a/tools/ds-identify +++ b/tools/ds-identify @@ -972,14 +972,78 @@ vmware_guest_customization() { return 1 } +vmware_has_rpctool() { + command -v vmware-rpctool >/dev/null 2>&1 +} + +vmware_rpctool_guestinfo() { + vmware-rpctool "info-get guestinfo.${1}" 2>/dev/null | grep "[[:alnum:]]" +} + +vmware_rpctool_guestinfo_err() { + vmware-rpctool "info-get guestinfo.${1}" 2>&1 | grep "[[:alnum:]]" +} + +vmware_has_vmtoolsd() { + command -v vmtoolsd >/dev/null 2>&1 +} + +vmware_vmtoolsd_guestinfo() { + vmtoolsd --cmd "info-get guestinfo.${1}" 2>/dev/null | grep "[[:alnum:]]" +} + +vmware_vmtoolsd_guestinfo_err() { + vmtoolsd --cmd "info-get guestinfo.${1}" 2>&1 | grep "[[:alnum:]]" +} + +vmware_guestinfo() { + vmware_rpctool_guestinfo "${1}" || vmware_vmtoolsd_guestinfo "${1}" +} + +vmware_guestinfo_err() { + vmware_rpctool_guestinfo_err "${1}" || vmware_vmtoolsd_guestinfo_err "${1}" +} + +vmware_guestinfo_ovfenv() { + if [ "${1:-}" = "err" ]; then + vmware_guestinfo_err "ovfEnv" + else + vmware_guestinfo "ovfEnv" + fi +} + +vmware_guestinfo_metadata() { + if [ "${1:-}" = "err" ]; then + vmware_guestinfo_err "metadata" + else + vmware_guestinfo "metadata" + fi +} + +vmware_guestinfo_userdata() { + if [ "${1:-}" = "err" ]; then + vmware_guestinfo_err "userdata" + else + vmware_guestinfo "userdata" + fi +} + +vmware_guestinfo_vendordata() { + if [ "${1:-}" = "err" ]; then + vmware_guestinfo_err "vendordata" + else + vmware_guestinfo "vendordata" + fi +} + ovf_vmware_transport_guestinfo() { [ "${DI_VIRT}" = "vmware" ] || return 1 - command -v vmware-rpctool >/dev/null 2>&1 || return 1 + vmware_has_rpctool || vmware_has_vmtoolsd || return 1 local out="" ret="" - out=$(vmware-rpctool "info-get guestinfo.ovfEnv" 2>&1) + out=$(vmware_guestinfo_ovfenv "err") ret=$? if [ $ret -ne 0 ]; then - debug 1 "Running on vmware but rpctool query returned $ret: $out" + debug 1 "Running on vmware but query returned $ret: $out" return 1 fi case "$out" in @@ -1439,26 +1503,6 @@ vmware_has_envvar_vmx_guestinfo_vendordata() { [ -n "${VMX_GUESTINFO_VENDORDATA:-}" ] } -vmware_has_rpctool() { - command -v vmware-rpctool >/dev/null 2>&1 -} - -vmware_rpctool_guestinfo() { - vmware-rpctool "info-get guestinfo.${1}" 2>/dev/null | grep "[[:alnum:]]" -} - -vmware_rpctool_guestinfo_metadata() { - vmware_rpctool_guestinfo "metadata" -} - -vmware_rpctool_guestinfo_userdata() { - vmware_rpctool_guestinfo "userdata" -} - -vmware_rpctool_guestinfo_vendordata() { - vmware_rpctool_guestinfo "vendordata" -} - dscheck_VMware() { # Checks to see if there is valid data for the VMware datasource. # The data transports are checked in the following order: @@ -1486,16 +1530,16 @@ dscheck_VMware() { return "${DS_NOT_FOUND}" fi - # Do not proceed if the vmware-rpctool command is not present. - if ! vmware_has_rpctool; then + # Do not proceed if neither the vmware-rpctool or vmtoolsd command exists. + if [ ! vmware_has_rpctool ] && [ ! vmware_has_vmtoolsd ]; then return "${DS_NOT_FOUND}" fi # Activate the VMware datasource only if any of the fields used # by the datasource are present in the guestinfo table. - if { vmware_rpctool_guestinfo_metadata || \ - vmware_rpctool_guestinfo_userdata || \ - vmware_rpctool_guestinfo_vendordata; } >/dev/null 2>&1; then + if { vmware_guestinfo_metadata || \ + vmware_guestinfo_userdata || \ + vmware_guestinfo_vendordata; } >/dev/null 2>&1; then return "${DS_FOUND}" fi