summaryrefslogtreecommitdiff
path: root/cloudinit/sources/helpers/vmware/imc/guestcust_util.py
diff options
context:
space:
mode:
Diffstat (limited to 'cloudinit/sources/helpers/vmware/imc/guestcust_util.py')
-rw-r--r--cloudinit/sources/helpers/vmware/imc/guestcust_util.py470
1 files changed, 463 insertions, 7 deletions
diff --git a/cloudinit/sources/helpers/vmware/imc/guestcust_util.py b/cloudinit/sources/helpers/vmware/imc/guestcust_util.py
index 5b5f02ca..6ffbae40 100644
--- a/cloudinit/sources/helpers/vmware/imc/guestcust_util.py
+++ b/cloudinit/sources/helpers/vmware/imc/guestcust_util.py
@@ -1,7 +1,8 @@
# Copyright (C) 2016 Canonical Ltd.
-# Copyright (C) 2016 VMware Inc.
+# Copyright (C) 2016-2022 VMware Inc.
#
# Author: Sankar Tanguturi <stanguturi@vmware.com>
+# Pengpeng Sun <pegnpengs@vmware.com>
#
# This file is part of cloud-init. See LICENSE file for license information.
@@ -10,13 +11,20 @@ import os
import re
import time
-from cloudinit import subp
-from cloudinit.sources.helpers.vmware.imc.guestcust_event import (
- GuestCustEventEnum,
-)
-from cloudinit.sources.helpers.vmware.imc.guestcust_state import (
- GuestCustStateEnum,
+from cloudinit import subp, util
+
+from .config import Config
+from .config_custom_script import (
+ CustomScriptNotFound,
+ PostCustomScript,
+ PreCustomScript,
)
+from .config_file import ConfigFile
+from .config_nic import NicConfigurator
+from .config_passwd import PasswordConfigurator
+from .guestcust_error import GuestCustErrorEnum
+from .guestcust_event import GuestCustEventEnum
+from .guestcust_state import GuestCustStateEnum
logger = logging.getLogger(__name__)
@@ -24,6 +32,11 @@ logger = logging.getLogger(__name__)
CLOUDINIT_LOG_FILE = "/var/log/cloud-init.log"
QUERY_NICS_SUPPORTED = "queryNicsSupported"
NICS_STATUS_CONNECTED = "connected"
+# Path to the VMware IMC directory
+IMC_DIR_PATH = "/var/run/vmware-imc"
+# Customization script configuration in tools conf
+IMC_TOOLS_CONF_GROUPNAME = "deployPkg"
+IMC_TOOLS_CONF_ENABLE_CUST_SCRIPTS = "enable-custom-scripts"
# This will send a RPC command to the underlying
@@ -183,4 +196,447 @@ def set_gc_status(config, gcMsg):
return None
+def get_imc_dir_path():
+ return IMC_DIR_PATH
+
+
+def get_data_from_imc_cust_cfg(
+ cloud_dir,
+ scripts_cpath,
+ cust_cfg,
+ cust_cfg_dir,
+ distro,
+):
+ md, ud, vd, cfg = {}, None, None, {}
+ set_gc_status(cust_cfg, "Started")
+ (md, cfg) = get_non_network_data_from_vmware_cust_cfg(cust_cfg)
+ is_special_customization = check_markers(cloud_dir, cust_cfg)
+ if is_special_customization:
+ if not do_special_customization(
+ scripts_cpath, cust_cfg, cust_cfg_dir, distro
+ ):
+ return (None, None, None, None)
+ if not recheck_markers(cloud_dir, cust_cfg):
+ return (None, None, None, None)
+ try:
+ logger.debug("Preparing the Network configuration")
+ md["network"] = get_network_data_from_vmware_cust_cfg(
+ cust_cfg, True, True, distro.osfamily
+ )
+ except Exception as e:
+ set_cust_error_status(
+ "Error preparing Network Configuration",
+ str(e),
+ GuestCustEventEnum.GUESTCUST_EVENT_NETWORK_SETUP_FAILED,
+ cust_cfg,
+ )
+ return (None, None, None, None)
+ connect_nics(cust_cfg_dir)
+ set_customization_status(
+ GuestCustStateEnum.GUESTCUST_STATE_DONE,
+ GuestCustErrorEnum.GUESTCUST_ERROR_SUCCESS,
+ )
+ set_gc_status(cust_cfg, "Successful")
+ return (md, ud, vd, cfg)
+
+
+def get_data_from_imc_raw_data_cust_cfg(cust_cfg):
+ set_gc_status(cust_cfg, "Started")
+ md, ud, vd = None, None, None
+ md_file = cust_cfg.meta_data_name
+ if md_file:
+ md_path = os.path.join(get_imc_dir_path(), md_file)
+ if not os.path.exists(md_path):
+ set_cust_error_status(
+ "Error locating the cloud-init meta data file",
+ "Meta data file is not found: %s" % md_path,
+ GuestCustEventEnum.GUESTCUST_EVENT_CUSTOMIZE_FAILED,
+ cust_cfg,
+ )
+ return (None, None, None)
+ try:
+ md = util.load_file(md_path)
+ except Exception as e:
+ set_cust_error_status(
+ "Error loading cloud-init meta data file",
+ str(e),
+ GuestCustEventEnum.GUESTCUST_EVENT_CUSTOMIZE_FAILED,
+ cust_cfg,
+ )
+ return (None, None, None)
+
+ ud_file = cust_cfg.user_data_name
+ if ud_file:
+ ud_path = os.path.join(get_imc_dir_path(), ud_file)
+ if not os.path.exists(ud_path):
+ set_cust_error_status(
+ "Error locating the cloud-init userdata file",
+ "Userdata file is not found: %s" % ud_path,
+ GuestCustEventEnum.GUESTCUST_EVENT_CUSTOMIZE_FAILED,
+ cust_cfg,
+ )
+ return (None, None, None)
+ try:
+ ud = util.load_file(ud_path).replace("\r", "")
+ except Exception as e:
+ set_cust_error_status(
+ "Error loading cloud-init userdata file",
+ str(e),
+ GuestCustEventEnum.GUESTCUST_EVENT_CUSTOMIZE_FAILED,
+ cust_cfg,
+ )
+ return (None, None, None)
+
+ set_customization_status(
+ GuestCustStateEnum.GUESTCUST_STATE_DONE,
+ GuestCustErrorEnum.GUESTCUST_ERROR_SUCCESS,
+ )
+ set_gc_status(cust_cfg, "Successful")
+ return (md, ud, vd)
+
+
+def get_non_network_data_from_vmware_cust_cfg(cust_cfg):
+ md, cfg = {}, {}
+ if cust_cfg.host_name:
+ if cust_cfg.domain_name:
+ md["local-hostname"] = (
+ cust_cfg.host_name + "." + cust_cfg.domain_name
+ )
+ else:
+ md["local-hostname"] = cust_cfg.host_name
+ if cust_cfg.timezone:
+ cfg["timezone"] = cust_cfg.timezone
+ if cust_cfg.instance_id:
+ md["instance-id"] = cust_cfg.instance_id
+ return (md, cfg)
+
+
+def get_network_data_from_vmware_cust_cfg(
+ cust_cfg, use_system_devices=True, configure=False, osfamily=None
+):
+ nicConfigurator = NicConfigurator(cust_cfg.nics, use_system_devices)
+ nics_cfg_list = nicConfigurator.generate(configure, osfamily)
+
+ return get_v1_network_config(
+ nics_cfg_list, cust_cfg.name_servers, cust_cfg.dns_suffixes
+ )
+
+
+def get_v1_network_config(nics_cfg_list=None, nameservers=None, search=None):
+ config_list = nics_cfg_list
+
+ if nameservers or search:
+ config_list.append(
+ {"type": "nameserver", "address": nameservers, "search": search}
+ )
+
+ return {"version": 1, "config": config_list}
+
+
+def connect_nics(cust_cfg_dir):
+ nics_file = os.path.join(cust_cfg_dir, "nics.txt")
+ if os.path.exists(nics_file):
+ logger.debug("%s file found, to connect nics", nics_file)
+ enable_nics(get_nics_to_enable(nics_file))
+
+
+def is_vmware_cust_enabled(sys_cfg):
+ return not util.get_cfg_option_bool(
+ sys_cfg, "disable_vmware_customization", True
+ )
+
+
+def is_raw_data_cust_enabled(ds_cfg):
+ return util.get_cfg_option_bool(ds_cfg, "allow_raw_data", True)
+
+
+def get_cust_cfg_file(ds_cfg):
+ # When the VM is powered on, the "VMware Tools" daemon
+ # copies the customization specification file to
+ # /var/run/vmware-imc directory. cloud-init code needs
+ # to search for the file in that directory which indicates
+ # that required metadata and userdata files are now
+ # present.
+ max_wait = get_max_wait_from_cfg(ds_cfg)
+ cust_cfg_file_path = util.log_time(
+ logfunc=logger.debug,
+ msg="Waiting for VMware customization configuration file",
+ func=wait_for_cust_cfg_file,
+ args=("cust.cfg", max_wait),
+ )
+ if cust_cfg_file_path:
+ logger.debug(
+ "Found VMware customization configuration file at %s",
+ cust_cfg_file_path,
+ )
+ return cust_cfg_file_path
+ else:
+ logger.debug("No VMware customization configuration file found")
+ return None
+
+
+def wait_for_cust_cfg_file(
+ filename, maxwait=180, naplen=5, dirpath="/var/run/vmware-imc"
+):
+ waited = 0
+ if maxwait <= naplen:
+ naplen = 1
+
+ while waited < maxwait:
+ fileFullPath = os.path.join(dirpath, filename)
+ if os.path.isfile(fileFullPath):
+ return fileFullPath
+ logger.debug("Waiting for VMware customization configuration file")
+ time.sleep(naplen)
+ waited += naplen
+ return None
+
+
+def get_max_wait_from_cfg(ds_cfg):
+ default_max_wait = 15
+ max_wait_cfg_option = "vmware_cust_file_max_wait"
+ max_wait = default_max_wait
+ if not ds_cfg:
+ return max_wait
+ try:
+ max_wait = int(ds_cfg.get(max_wait_cfg_option, default_max_wait))
+ except ValueError:
+ logger.warning(
+ "Failed to get '%s', using %s",
+ max_wait_cfg_option,
+ default_max_wait,
+ )
+ if max_wait < 0:
+ logger.warning(
+ "Invalid value '%s' for '%s', using '%s' instead",
+ max_wait,
+ max_wait_cfg_option,
+ default_max_wait,
+ )
+ max_wait = default_max_wait
+ return max_wait
+
+
+def check_markers(cloud_dir, cust_cfg):
+ product_marker = cust_cfg.marker_id
+ has_marker_file = check_marker_exists(
+ product_marker, os.path.join(cloud_dir, "data")
+ )
+ return product_marker and not has_marker_file
+
+
+def check_marker_exists(markerid, marker_dir):
+ """
+ Check the existence of a marker file.
+ Presence of marker file determines whether a certain code path is to be
+ executed. It is needed for partial guest customization in VMware.
+ @param markerid: is an unique string representing a particular product
+ marker.
+ @param: marker_dir: The directory in which markers exist.
+ """
+ if not markerid:
+ return False
+ markerfile = os.path.join(marker_dir, ".markerfile-" + markerid + ".txt")
+ if os.path.exists(markerfile):
+ return True
+ return False
+
+
+def recheck_markers(cloud_dir, cust_cfg):
+ product_marker = cust_cfg.marker_id
+ if product_marker:
+ if not create_marker_file(cloud_dir, cust_cfg):
+ return False
+ return True
+
+
+def create_marker_file(cloud_dir, cust_cfg):
+ try:
+ setup_marker_files(cust_cfg.marker_id, os.path.join(cloud_dir, "data"))
+ except Exception as e:
+ set_cust_error_status(
+ "Error creating marker files",
+ str(e),
+ GuestCustEventEnum.GUESTCUST_EVENT_CUSTOMIZE_FAILED,
+ cust_cfg,
+ )
+ return False
+ return True
+
+
+def setup_marker_files(marker_id, marker_dir):
+ """
+ Create a new marker file.
+ Marker files are unique to a full customization workflow in VMware
+ environment.
+ @param marker_id: is an unique string representing a particular product
+ marker.
+ @param: marker_dir: The directory in which markers exist.
+ """
+ logger.debug("Handle marker creation")
+ marker_file = os.path.join(marker_dir, ".markerfile-" + marker_id + ".txt")
+ for fname in os.listdir(marker_dir):
+ if fname.startswith(".markerfile"):
+ util.del_file(os.path.join(marker_dir, fname))
+ open(marker_file, "w").close()
+
+
+def do_special_customization(scripts_cpath, cust_cfg, cust_cfg_dir, distro):
+ is_pre_custom_successful = False
+ is_password_custom_successful = False
+ is_post_custom_successful = False
+ is_custom_script_enabled = False
+ custom_script = cust_cfg.custom_script_name
+ if custom_script:
+ is_custom_script_enabled = check_custom_script_enablement(cust_cfg)
+ if is_custom_script_enabled:
+ is_pre_custom_successful = do_pre_custom_script(
+ cust_cfg, custom_script, cust_cfg_dir
+ )
+ is_password_custom_successful = do_password_customization(cust_cfg, distro)
+ if custom_script and is_custom_script_enabled:
+ ccScriptsDir = os.path.join(scripts_cpath, "per-instance")
+ is_post_custom_successful = do_post_custom_script(
+ cust_cfg, custom_script, cust_cfg_dir, ccScriptsDir
+ )
+ if custom_script:
+ return (
+ is_pre_custom_successful
+ and is_password_custom_successful
+ and is_post_custom_successful
+ )
+ return is_password_custom_successful
+
+
+def do_pre_custom_script(cust_cfg, custom_script, cust_cfg_dir):
+ try:
+ precust = PreCustomScript(custom_script, cust_cfg_dir)
+ precust.execute()
+ except CustomScriptNotFound as e:
+ set_cust_error_status(
+ "Error executing pre-customization script",
+ str(e),
+ GuestCustEventEnum.GUESTCUST_EVENT_CUSTOMIZE_FAILED,
+ cust_cfg,
+ )
+ return False
+ return True
+
+
+def do_post_custom_script(cust_cfg, custom_script, cust_cfg_dir, ccScriptsDir):
+ try:
+ postcust = PostCustomScript(custom_script, cust_cfg_dir, ccScriptsDir)
+ postcust.execute()
+ except CustomScriptNotFound as e:
+ set_cust_error_status(
+ "Error executing post-customization script",
+ str(e),
+ GuestCustEventEnum.GUESTCUST_EVENT_CUSTOMIZE_FAILED,
+ cust_cfg,
+ )
+ return False
+ return True
+
+
+def check_custom_script_enablement(cust_cfg):
+ is_custom_script_enabled = False
+ default_value = "false"
+ if cust_cfg.default_run_post_script:
+ logger.debug(
+ "Set default value to true due to customization configuration."
+ )
+ default_value = "true"
+ custom_script_enablement = get_tools_config(
+ IMC_TOOLS_CONF_GROUPNAME,
+ IMC_TOOLS_CONF_ENABLE_CUST_SCRIPTS,
+ default_value,
+ )
+ if custom_script_enablement.lower() != "true":
+ set_cust_error_status(
+ "Custom script is disabled by VM Administrator",
+ "Error checking custom script enablement",
+ GuestCustErrorEnum.GUESTCUST_ERROR_SCRIPT_DISABLED,
+ cust_cfg,
+ )
+ else:
+ is_custom_script_enabled = True
+ return is_custom_script_enabled
+
+
+def do_password_customization(cust_cfg, distro):
+ logger.debug("Applying password customization")
+ pwdConfigurator = PasswordConfigurator()
+ admin_pwd = cust_cfg.admin_password
+ try:
+ reset_pwd = cust_cfg.reset_password
+ if admin_pwd or reset_pwd:
+ pwdConfigurator.configure(admin_pwd, reset_pwd, distro)
+ else:
+ logger.debug("Changing password is not needed")
+ except Exception as e:
+ set_cust_error_status(
+ "Error applying password configuration",
+ str(e),
+ GuestCustEventEnum.GUESTCUST_EVENT_CUSTOMIZE_FAILED,
+ cust_cfg,
+ )
+ return False
+ return True
+
+
+def parse_cust_cfg(cfg_file):
+ return Config(ConfigFile(cfg_file))
+
+
+def get_cust_cfg_type(cust_cfg):
+ is_vmware_cust_cfg, is_raw_data_cust_cfg = False, False
+ if cust_cfg.meta_data_name:
+ is_raw_data_cust_cfg = True
+ logger.debug("raw cloudinit data cust cfg found")
+ else:
+ is_vmware_cust_cfg = True
+ logger.debug("vmware cust cfg found")
+ return (is_vmware_cust_cfg, is_raw_data_cust_cfg)
+
+
+def is_cust_plugin_available():
+ search_paths = (
+ "/usr/lib/vmware-tools",
+ "/usr/lib64/vmware-tools",
+ "/usr/lib/open-vm-tools",
+ "/usr/lib64/open-vm-tools",
+ "/usr/lib/x86_64-linux-gnu/open-vm-tools",
+ "/usr/lib/aarch64-linux-gnu/open-vm-tools",
+ )
+ cust_plugin = "libdeployPkgPlugin.so"
+ for path in search_paths:
+ cust_plugin_path = search_file(path, cust_plugin)
+ if cust_plugin_path:
+ logger.debug(
+ "Found the customization plugin at %s", cust_plugin_path
+ )
+ return True
+ return False
+
+
+def search_file(dirpath, filename):
+ if not dirpath or not filename:
+ return None
+
+ for root, _dirs, files in os.walk(dirpath):
+ if filename in files:
+ return os.path.join(root, filename)
+
+ return None
+
+
+def set_cust_error_status(prefix, error, event, cust_cfg):
+ """
+ Set customization status to the underlying VMware Virtualization Platform
+ """
+ util.logexc(logger, "%s: %s", prefix, error)
+ set_customization_status(GuestCustStateEnum.GUESTCUST_STATE_RUNNING, event)
+ set_gc_status(cust_cfg, prefix)
+
+
# vi: ts=4 expandtab