Files
vmware-tools/vCert-6.1.1-20260401/lib/host_utils.py
T
2026-06-23 11:27:54 +08:00

503 lines
17 KiB
Python

# Copyright (c) 2024-2025 Broadcom. All Rights Reserved.
# The term "Broadcom" refers to Broadcom Inc.
# and/or its subsidiaries.
import ipaddress
import glob
import logging
import os
import pathlib
import re
import shutil
import socket
import sys
import xml.etree.ElementTree as ET
from enum import Enum
from lib.console import print_text_warning
from lib.constants import RHTTPPROXY_CONFIG_FILE_PATH, STS_SERVER_CONFIG_FILE_PATH, STS_SERVER_CONFIG_PROPERTY_FILE_PATH
from lib.environment import Environment
from lib.execution_replay import ReplayContext
from lib.command_runner import CommandRunner
from lib.java_utils import load_property_file
from lib.text_utils import TextFilter
VMAFD_CLI = '/usr/lib/vmware-vmafd/bin/vmafd-cli'
LWREGSHELL_CLI = '/opt/likewise/bin/lwregshell'
TEE_CMD = '/usr/bin/tee'
FILE_CMD = '/usr/bin/file'
CHMOD_CMD = '/bin/chmod'
logger = logging.getLogger(__name__)
class VcVersion(Enum):
"""
Define the recognized versions of VC.
"""
# We assume the needed version values are only major and minor releases,
# and not patches for example. This allows us to represent the versions as
# floating point numbers for relative comparison. For code specific to
# patch releases, the tool resorts to build numbers, which are checked as
# needed.
#
# Also, the order of enum definitions must be kept in increasing order,
# including minor releases.
Invalid = None
V7 = '7'
V8 = '8'
V9 = '9'
@classmethod
def min(cls):
"""Minimum supported vCenter version"""
return min([float(v) for v in cls if v.value is not None])
@classmethod
def max(cls):
"""Maximum supported vCenter version"""
return max([float(v) for v in cls if v.value is not None])
@property
def major(self):
"""Return the major release number for the release."""
if self.value is None:
return None
# Floats that are actually integers always end with ".0" as a string.
return str(float(self)).split('.')[0]
@property
def minor(self):
"""Return the minor release number for the release."""
if self.value is None:
return None
# Floats that are actually integers always end with ".0" as a string.
return str(float(self)).split('.')[1]
def _validate(self, other):
if not isinstance(other, VcVersion):
raise ValueError(other)
def __float__(self):
# If there is no value (for the Invalid entry) then return float NaN
# so all comparison operations return False. (However, we provide
# special direct comparison overrides for != and == so that comparisons
# with VcVersion.Invalid work as expected.)
return float('nan') if self.value is None else float(self.value)
def __lt__(self, other):
self._validate(other)
return float(self) < float(other)
def __le__(self, other):
self._validate(other)
return float(self) <= float(other)
def __eq__(self, other):
self._validate(other)
return self.value == other.value
def __ne__(self, other):
self._validate(other)
return self.value != other.value
def __gt__(self, other):
self._validate(other)
return float(self) > float(other)
def __ge__(self, other):
self._validate(other)
return float(self) >= float(other)
def init_env_host():
"""
Obtain basic information from VC server
"""
vpxd_info = CommandRunner('vpxd', '-v').run_and_get_output().strip().split(' ')
vc_version_long = vpxd_info[2]
vc_version = '.'.join(vc_version_long.split('.')[:-1])
vc_build = vpxd_info[3].split('-')[1]
hostname = CommandRunner('hostname', '-f').run_and_get_output().strip()
pnid = CommandRunner(VMAFD_CLI, 'get-pnid', '--server-name', 'localhost').run_and_get_output().strip()
machine_id = CommandRunner(VMAFD_CLI, 'get-machine-id', '--server-name', 'localhost').run_and_get_output().strip()
ifconfig_output = CommandRunner('/usr/sbin/ifconfig', 'eth0').run_and_get_output()
ip_address = TextFilter(ifconfig_output).head(2).tail(1).get_text().strip().split(' ')[0].replace(':', ' ').split(' ')[-1]
sso_domain = CommandRunner(VMAFD_CLI, 'get-domain-name', '--server-name', 'localhost').run_and_get_output().strip()
sso_site = CommandRunner(VMAFD_CLI, 'get-site-name', '--server-name', 'localhost').run_and_get_output().strip()
sso_domain_dn = "dc={}".format(",dc=".join(sso_domain.split('.')))
vmdir_account_dn, vmdir_account_password = get_vmdir_machine_account()
local_hostname = socket.getfqdn()
env = Environment.get_environment()
env.set_value('VC_VERSION', vc_version)
env.set_value('VC_VERSION_LONG', vc_version_long)
env.set_value('VC_BUILD', vc_build)
env.set_value('HOSTNAME', hostname)
env.set_value('LOCAL_HOSTNAME', local_hostname)
env.set_value('PNID', pnid)
env.set_value('MACHINE_ID', machine_id)
env.set_value('IP_ADDRESS', ip_address)
env.set_value('SSO_DOMAIN', sso_domain)
env.set_value('SSO_SITE', sso_site)
env.set_value('SSO_DOMAIN_DN', sso_domain_dn)
env.set_value('VMDIR_MACHINE_ACCOUNT_DN', vmdir_account_dn)
env.set_value('VMDIR_MACHINE_ACCOUNT_PASSWORD', vmdir_account_password)
smart_card_filter_file = get_smart_card_filter_file()
env.set_value('SMART_CARD_FILTER_FILE', smart_card_filter_file)
init_env_solution_users()
def get_vmdir_machine_account():
"""
Get machine account from likewise
:return: a tupple of ( account_dn, account_password)
"""
output = CommandRunner(LWREGSHELL_CLI, 'list_values',
r'[HKEY_THIS_MACHINE\services\vmdir]').run_and_get_output()
account_dn = TextFilter(output).contain('"dcAccountDN"').cut('REG_SZ', [1]).get_text().strip()[1:-1]\
.replace('\\"', r'"').replace('\\\\', '\\')
account_password = TextFilter(output).contain('dcAccountPassword').cut('REG_SZ', [1]).get_text().strip()[1:-1]\
.replace('\\"', r'"').replace('\\\\', '\\')
return account_dn, account_password
def is_file_exists(file):
"""
Wrapper for checking file existence.
This wrapper is required to redirect operation when VCERT_REMOTE_EXEC is True
:param file: file path to be checked
:return: True if file is exist
"""
if not is_remote_exec():
return os.path.exists(file)
return_code, _, _ = CommandRunner('/bin/ls', file, expected_return_code=None).run()
return return_code == 0
def get_config_file_path(config_file):
env = Environment.get_environment()
if os.path.isabs(config_file):
if is_file_exists(config_file):
return config_file
else:
print_text_warning("Unable to find configuration file '{}'".format(config_file))
print_text_warning('Script cannot continue. Exiting...')
sys.exit(1)
else:
vcert_relative_config_path = "{}/{}".format(env.get_value('SCRIPT_DIR'), config_file)
cwd_relative_config_path = "{}/{}".format(os.getcwd(), config_file)
if is_file_exists(vcert_relative_config_path):
return vcert_relative_config_path
elif is_file_exists(cwd_relative_config_path):
return cwd_relative_config_path
else:
print_text_warning("Configuration file does not exist in the vCert script directory ({}),".format(vcert_relative_config_path))
print_text_warning("nor in the current working directory ({}).".format(cwd_relative_config_path))
print_text_warning('Script cannot continue. Exiting...')
sys.exit(1)
def make_directory(path):
"""
Wrapper for making directory.
This wrapper is required to redirect operation when VCERT_REMOTE_EXEC is True
:param path: directory path to be created
:return: True if the operation success, otherwise False
"""
if is_remote_exec():
command_runner = CommandRunner('/bin/mkdir', '-p', path, expected_return_code=None)
return_code, _, _ = command_runner.run()
return return_code == 0
try:
pathlib.Path(path).mkdir(parents=True, exist_ok=True)
return True
except Exception:
return False
def remove_directory(path, remove_all=False):
"""
Wrapper for removing directory
This wrapper is required to redirect operation when VCERT_REMOTE_EXEC is True
:param path: Directory path to remove
:param remove_all: if True, this method will remove the directory contents before trying to
remove the directory
:return: True if the operation success, otherwise False
"""
if is_remote_exec():
if remove_all:
command_runner = CommandRunner('/bin/rmdir', '-r', path, expected_return_code=None)
else:
command_runner = CommandRunner('/bin/rmdir', path, expected_return_code=None)
return_code, _, _ = command_runner.run()
return return_code == 0
try:
if remove_all:
shutil.rmtree(path)
else:
pathlib.Path(path).rmdir()
except Exception:
return False
def get_file_contents(file):
"""
Wrapper for reading file contents.
This wrapper is required to redirect operation when VCERT_REMOTE_EXEC is True
:param file: File path to load
:return: File contents
"""
if not is_remote_exec():
with open(file, 'r') as f:
return f.read()
command_runner = CommandRunner('/bin/cat', file, expected_return_code=0)
_, stdout, _ = command_runner.run()
return stdout
def get_hostname():
"""
Wrapper obtaining remote hostname
This wrapper is required to redirect operation when VCERT_REMOTE_EXEC is True
:return: return VCERT_REMOTE_HOSTNAME value if VCERT_REMOTE_EXEC is True,
otherwise return 'localhost'
"""
if is_remote_exec():
hostname = Environment.get_environment().get_value('VCERT_REMOTE_HOSTNAME')
else:
hostname = 'localhost'
return hostname
def is_remote_exec():
"""
Check if remote execution is set
:return: True if remote execution is set, otherwise False
"""
return Environment.get_environment().get_value('VCERT_REMOTE_EXEC') is True
def get_vc_version():
"""
Get the VC version. The value is obtained from environment variable VC_VERSION
:return: VC version as VcVersion enum
"""
vc_version = Environment.get_environment().get_value('VC_VERSION')
# Versions in VcVersion are kept in increasing order. Thus we search the
# versions in reverse order to bulletproof the code against versions that
# are represented just as a major release versus ones that are represented
# as major.minor.
for definedVcVersion in reversed(VcVersion):
if ( definedVcVersion.value is not None
and vc_version.startswith(definedVcVersion.value)):
return definedVcVersion
return VcVersion.Invalid
def init_env_solution_users():
"""
Initialize set of solution users to be checked, saved the list into environment
variable 'SOLUTION_USERS'
"""
solutionUserDB = {
# Solution User Version Constraint (None means all)
# ------------------- -----------------------------------
'machine': None,
'vsphere-webclient': None,
'vpxd': None,
'vpxd-extension': None,
'hvc': None,
'wcp': [VcVersion.V7, VcVersion.V8],
'wcpsvc': [VcVersion.V9],
}
env = Environment.get_environment()
vc_version = get_vc_version()
solution_users = []
for solutionUser, versionConstraint in solutionUserDB.items():
if versionConstraint and not vc_version in versionConstraint:
continue
solution_users.append(solutionUser)
env.set_value('SOLUTION_USERS', solution_users)
def is_valid_ip_address(hostname_or_ip):
try:
ipaddress.ip_address(hostname_or_ip)
return True
except ValueError:
return False
def get_ip_address(hostname_or_ip):
if is_valid_ip_address(hostname_or_ip):
return hostname_or_ip
else:
return socket_gethostbyname(hostname_or_ip)
def socket_gethostbyname(hostname):
category = 'other'
command_args = ['socket', 'gethostbyname', 'hostname', hostname]
context = ReplayContext.get_replay_context()
if context and context.is_replaying:
result = context.get_execution_result(category, command_args, None)
if result is not None:
_, ip_address, _ = result
return ip_address
try:
ip_address = socket.gethostbyname(hostname)
except socket.gaierror:
ip_address = ''
if context and context.is_capturing:
context.store_result(category, command_args, None, 0, ip_address, None)
return ip_address
def save_text_to_file(text, filename):
"""
A wrapper to write {text} to file {filename}
:param filename: the output filename
:param text: file contents
"""
if is_remote_exec():
CommandRunner(TEE_CMD, filename, command_input=text, expected_return_code=0).run()
return
with open(filename, 'w') as file:
file.write(text)
def append_text_to_file(text, filename, start_new_line=True):
"""
A wrapper to append {text} to file {filename}
:param text: text to append
:param file: the existing filename
:param start_new_line: start appending data on a new line
"""
if is_remote_exec():
CommandRunner(TEE_CMD, '-a', filename, command_input=text, expected_return_code=0).run()
return
with open(filename, 'a') as file:
if start_new_line:
file.write("\n{}".format(text))
else:
file.write(text)
def find_files(pattern):
"""
Wrapper for glob.glob({pattern}) method
:param pattern: glob patter for matching
"""
if is_remote_exec():
output = CommandRunner('/usr/bin/sh', '-c', "eval ls -1 '{}'".format(pattern)).run_and_get_output()
return output.splitlines()
return glob.glob(pattern)
def get_file_type(filename):
"""
Get file type returned by /usr/bin/file command
"""
output = CommandRunner(FILE_CMD, filename,
expected_return_code =0).run_and_get_output()
return TextFilter(output).cut(delimiter=':', fields=[1]).get_text().strip()
def set_file_mode(file_path, mode):
"""
A wrapper for setting file mode on {filename}
:param file_path: File path to be modified
:param mode: permission value, the exact permission value to be passed to os.chmod()
"""
if is_remote_exec():
mode_string = "{:04o}".format(mode)
CommandRunner(CHMOD_CMD, mode_string, file_path, expected_return_code=0).run()
else:
os.chmod(file_path, mode)
def get_smart_card_filter_file():
def revproxy_get_file():
# Before 7.0 U3i the location of the Smart Card filter file is defined
# in the reverse proxy config.
config = ET.parse(RHTTPPROXY_CONFIG_FILE_PATH)
root = config.getroot()
client_ca_list_file = root.find('clientCAListFile')
if client_ca_list_file is None:
filter_file = ''
else:
filter_file = client_ca_list_file.text
filter_file = '/etc/vmware-rhttpproxy/{}'.format(filter_file) if filter_file.startswith('/') is False else filter_file
return filter_file
def xml_config_get_file():
# After 7.0 U3i the location of the Smart Card filter file is
# hard-coded in the STS server config which is an XML file.
config = ET.parse(STS_SERVER_CONFIG_FILE_PATH)
root = config.getroot()
for connector in root.iter('Connector'):
if connector.attrib['port'] == '${bio-ssl-clientauth.https.port}':
return connector.find('SSLHostConfig').attrib['truststoreFile']
return '' # Not found
def prop_config_get_file():
# Starting with 9.0 the Smart Card filter file is in the STS server
# configuration file which is a Java property file.
config = load_property_file(STS_SERVER_CONFIG_PROPERTY_FILE_PATH)
return config.get('vmidentity.server.connector.ssl.client.auth.truststore.file', '')
# In the following DB, search stops on first match.
# Order of entries in the DB must be from most specific to least specific
# (for example an entry with a version specifying a build must be before
# the same version with no build).
smartCardConfigDB = [
# Version Build Retrieval Function
# ------------- -------- ---------------------
(VcVersion.V7, 20845200, xml_config_get_file),
(VcVersion.V7, None, revproxy_get_file),
(VcVersion.V8, None, xml_config_get_file),
(VcVersion.V9, None, prop_config_get_file),
]
env = Environment.get_environment()
vc_version = get_vc_version()
vc_build = int(env.get_value('VC_BUILD'))
# Resolve the retrieval function. We break on first match.
get_file = None
for versionConstraint, buildConstraint, func in smartCardConfigDB:
if vc_version != versionConstraint:
continue
if buildConstraint and vc_build < buildConstraint:
continue
# Found a match.
get_file = func
break
# If found, execute the retrieval function; otherwise return an empty
# string.
return get_file() if get_file else ''