Skip to content

Commit

Permalink
vmware: Fall back to vmtoolsd if vmware-rpctool errs (#4444)
Browse files Browse the repository at this point in the history
This patch updates the ds-identify script and the
VMware datasource to fall back to using the
vmtoolsd program if vmware-rpctool errors.

Fixes GH-4436
  • Loading branch information
akutz authored Sep 26, 2023
1 parent dfe6170 commit 3a031a7
Show file tree
Hide file tree
Showing 8 changed files with 497 additions and 121 deletions.
103 changes: 84 additions & 19 deletions cloudinit/sources/DataSourceOVF.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
#
# 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.
"""

import base64
import logging
import os
Expand All @@ -20,7 +25,6 @@


class DataSourceOVF(sources.DataSource):

dsname = "OVF"

def __init__(self, sys_cfg, distro, paths):
Expand Down Expand Up @@ -142,7 +146,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:
Expand Down Expand Up @@ -220,10 +224,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
Expand Down Expand Up @@ -258,22 +261,85 @@ def transport_iso9660(require_iso=True):
return None


def exec_vmware_rpctool(rpctool, arg):
cmd = [rpctool, arg]
(stdout, stderr) = subp.subp(cmd)
return (cmd, stdout, stderr)


def exec_vmtoolsd(rpctool, arg):
cmd = [rpctool, "--cmd", arg]
(stdout, stderr) = subp.subp(cmd)
return (cmd, stdout, stderr)


def transport_vmware_guestinfo():
rpctool = "vmware-rpctool"
not_found = None
if not subp.which(rpctool):
return not_found
cmd = [rpctool, "info-get guestinfo.ovfEnv"]
rpctool, rpctool_fn = None, None
vmtoolsd = subp.which("vmtoolsd")
vmware_rpctool = subp.which("vmware-rpctool")

# Default to using vmware-rpctool if it is available.
if vmware_rpctool:
rpctool, rpctool_fn = vmware_rpctool, exec_vmware_rpctool
LOG.debug("discovered vmware-rpctool: %s", vmware_rpctool)

if vmtoolsd:
# Default to using vmtoolsd if it is available and vmware-rpctool is
# not.
if not vmware_rpctool:
rpctool, rpctool_fn = vmtoolsd, exec_vmtoolsd
LOG.debug("discovered vmtoolsd: %s", vmtoolsd)

# If neither vmware-rpctool nor vmtoolsd are available, then nothing can
# be done.
if not rpctool:
LOG.debug("no rpctool discovered")
return None

def query_guestinfo(rpctool, rpctool_fn):
LOG.info("query guestinfo.ovfEnv with %s", rpctool)
try:
cmd, stdout, _ = rpctool_fn(rpctool, "info-get guestinfo.ovfEnv")
if stdout:
return stdout
LOG.debug("cmd %s exited 0 with empty stdout", cmd)
return None
except subp.ProcessExecutionError as error:
if error.exit_code != 1:
LOG.warning("%s exited with code %d", rpctool, error.exit_code)
raise error

try:
out, _err = subp.subp(cmd)
if out:
return out
LOG.debug("cmd %s exited 0 with empty stdout: %s", cmd, 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
# The first attempt to query guestinfo could occur via either
# vmware-rpctool *or* vmtoolsd.
return query_guestinfo(rpctool, rpctool_fn)
except subp.ProcessExecutionError as error:
# The second attempt to query guestinfo can only occur with
# vmtoolsd.

# If the first attempt at getting the data was with vmtoolsd, then
# no second attempt is made.
if vmtoolsd and rpctool == vmtoolsd:
# The fallback failed, log the error.
util.logexc(
LOG, "vmtoolsd failed to get guestinfo.ovfEnv: %s", error
)
return None

if not vmtoolsd:
LOG.info("vmtoolsd fallback option not present")
return None

try:
LOG.info("fallback to vmtoolsd")
return query_guestinfo(vmtoolsd, exec_vmtoolsd)
except subp.ProcessExecutionError as error:
# The fallback failed, log the error.
util.logexc(
LOG, "vmtoolsd failed to get guestinfo.ovfEnv: %s", error
)

return None


def find_child(node, filter_func):
Expand All @@ -287,7 +353,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")
Expand Down
Loading

0 comments on commit 3a031a7

Please sign in to comment.