From cdde77512ac14277bf1de06194bcb0f183c9604e Mon Sep 17 00:00:00 2001 From: Frank Brehm Date: Mon, 4 Jul 2022 17:21:19 +0200 Subject: [PATCH] Considering, that a MAC address in DHCP could have multiple leases. --- lib/cr_vmware_tpl/__init__.py | 2 +- lib/cr_vmware_tpl/cobbler.py | 47 ++++++++++++- lib/cr_vmware_tpl/handler.py | 128 ++++++++++++++++++++-------------- 3 files changed, 124 insertions(+), 53 deletions(-) diff --git a/lib/cr_vmware_tpl/__init__.py b/lib/cr_vmware_tpl/__init__.py index d35d395..a591ccd 100644 --- a/lib/cr_vmware_tpl/__init__.py +++ b/lib/cr_vmware_tpl/__init__.py @@ -3,7 +3,7 @@ import time -__version__ = '2.6.0' +__version__ = '2.6.1' DEFAULT_CONFIG_DIR = 'pixelpark' DEFAULT_DISTRO_ARCH = 'x86_64' diff --git a/lib/cr_vmware_tpl/cobbler.py b/lib/cr_vmware_tpl/cobbler.py index 53d7f17..ed52ac6 100644 --- a/lib/cr_vmware_tpl/cobbler.py +++ b/lib/cr_vmware_tpl/cobbler.py @@ -48,7 +48,7 @@ from .config import CrTplConfiguration from .xlate import XLATOR -__version__ = '0.8.9' +__version__ = '0.9.0' LOG = logging.getLogger(__name__) @@ -949,6 +949,7 @@ class Cobbler(BaseHandler): ks_meta_list.append("SWAP_SIZE_MB={}".format(self.cfg.swap_size_mb)) ks_meta_list.append("SYSTEM_STATUS={}".format(status)) ks_meta_list.append("WS_REL_FILESDIR={}".format(self.cfg.cobbler_ws_rel_filesdir)) + ks_meta_list.append("COBBLER_URL=http://{}".format(self.cfg.cobbler_host)) ks_meta = None if ks_meta_list: @@ -1151,6 +1152,50 @@ class Cobbler(BaseHandler): return assigments[mac] return None + # ------------------------------------------------------------------------- + def get_dhcp_ips(self, mac_address): + + mac = mac_address.lower() + LOG.debug(_("Trying to get IP of MAC address {!r} given by DHCP ...").format(mac)) + all_leases = self.get_remote_filecontent(self.dhcpd_leases_file) + + ips = [] + + cur_ip = None + assigments = {} + re_lease_start = re.compile(r'^\s*lease\s+((?:\d{1,3}\.){3}\d{1,3})\s+', re.IGNORECASE) + re_mac = re.compile( + r'^\s*hardware\s+ethernet\s+((?:[0-9a-f]{2}:){5}[0-9a-f]{2})\s*;', re.IGNORECASE) + + for line in all_leases.splitlines(): + match = re_lease_start.match(line) + if match: + try: + ip = ipaddress.ip_address(match.group(1)) + cur_ip = str(ip) + except ValueError as e: + msg = _("Found invalid IP address {ip!r} in leases file: {err}").format( + ip=match.group(1), err=e) + LOG.error(msg) + continue + + match = re_mac.match(line) + if match: + found_mac = match.group(1).lower() + if cur_ip: + assigments[cur_ip] = found_mac + continue + + for ip in assigments.keys(): + found_mac = assigments[ip] + if mac == found_mac: + ips.append(ip) + + if self.verbose > 2: + LOG.debug(_("Found DHCP IP assignments:") + "\n" + pp(assigments)) + + return ips + # ------------------------------------------------------------------------- def ensure_webroot(self): diff --git a/lib/cr_vmware_tpl/handler.py b/lib/cr_vmware_tpl/handler.py index adb24a9..fc89a22 100644 --- a/lib/cr_vmware_tpl/handler.py +++ b/lib/cr_vmware_tpl/handler.py @@ -35,10 +35,9 @@ from ldap3.core.exceptions import LDAPException, LDAPBindError # Own modules from fb_tools.common import pp, to_str, is_sequence - from fb_tools.errors import HandlerError, ExpectedHandlerError - from fb_tools.handler import BaseHandler +from fb_tools.xlate import format_list from fb_vmware.errors import VSphereExpectedError from fb_vmware.errors import VSphereDatacenterNotFoundError @@ -57,7 +56,7 @@ from .cobbler import Cobbler from .xlate import XLATOR -__version__ = '2.2.5' +__version__ = '2.3.0' LOG = logging.getLogger(__name__) TZ = pytz.timezone('Europe/Berlin') @@ -144,6 +143,7 @@ class CrTplHandler(BaseHandler): self.tpl_vm_hostname = None self.tpl_macaddress = None self.tpl_ip = None + self.tpl_ips = [] self.ts_start_install = None self.ts_finish_install = None self.initial_sleep = 60 @@ -437,7 +437,7 @@ class CrTplHandler(BaseHandler): self.vsphere.poweron_vm(self.tpl_vm, max_wait=self.cfg.max_wait_for_poweron_vm) self.ts_start_install = time.time() - self.eval_tpl_ip() + self.eval_tpl_ips() self.wait_for_finish_install() self.show_install_log() @@ -730,11 +730,11 @@ class CrTplHandler(BaseHandler): pool=self.cluster.resource_pool, max_wait=self.cfg.max_wait_for_create_vm) # ------------------------------------------------------------------------- - def eval_tpl_ip(self): + def eval_tpl_ips(self): LOG.info(_("Trying to evaluate the IP address of the template VM ...")) - initial_delay = self.vm_boot_delay_secs + 10 + initial_delay = (2 * self.vm_boot_delay_secs) + 120 LOG.debug(_("Waiting initially for {} seconds:").format(initial_delay)) print(' ==> ', end='', flush=True) @@ -750,14 +750,14 @@ class CrTplHandler(BaseHandler): cur_duration = cur_time - start_time print('', flush=True) - self.tpl_ip = self.cobbler.get_dhcp_ip(self.tpl_macaddress) - if not self.tpl_ip: + self.tpl_ips = self.cobbler.get_dhcp_ips(self.tpl_macaddress) + if not self.tpl_ips: msg = _( "Did not got the IP address of MAC address {mac!r} after " "{delay} seconds.").format(mac=self.tpl_macaddress, delay=initial_delay) raise ExpectedHandlerError(msg) - LOG.info(_("Got IP address {!r} for template VM.").format(self.tpl_ip)) + LOG.info(_("Got IP addresses for template VM:") + ' ' + format_list(self.tpl_ips)) # ------------------------------------------------------------------------- def wait_for_finish_install(self): @@ -782,19 +782,21 @@ class CrTplHandler(BaseHandler): LOG.debug(_("Waiting for SSH available ...")) - addr_infos = socket.getaddrinfo(self.tpl_ip, 22, socket.AF_INET, socket.SOCK_STREAM) - if self.verbose > 1: - msg = _("Got following address_infos for {h!r}, IPv4 TCP port {p}:").format( - h=self.tpl_ip, p=22) - msg += '\n' + pp(addr_infos) - LOG.debug(msg) - if not addr_infos: - raise HandlerError(_("Did not get address infos for {h!r}, IPv4 TCP port {p}.").format( - h=self.tpl_ip, p=22)) + addr_infos = {} + for ip in self.tpl_ips: + ai = socket.getaddrinfo(ip, 22, socket.AF_INET, socket.SOCK_STREAM) + if self.verbose > 1: + msg = _("Got following address_infos for {h!r}, IPv4 TCP port {p}:").format( + h=ip, p=22) + msg += '\n' + pp(ai) + LOG.debug(msg) + if not ai: + raise HandlerError(_("Did not get address infos for {h!r}, IPv4 TCP port {p}.").format( + h=ip, p=22)) - addr_info = random.choice(addr_infos) - LOG.debug(_("Using address info: {}").format(pp(addr_info))) - family, socktype, proto, canonname, sockaddr = addr_info + addr_info = random.choice(ai) + LOG.debug(_("Using address info: {}").format(pp(addr_info))) + addr_infos[ip] = addr_info if self.verbose <= 3: print(' ==> ', end='', flush=True) @@ -823,38 +825,19 @@ class CrTplHandler(BaseHandler): i = 0 last_dot = cur_time - if self.verbose > 3: - LOG.debug(_("Trying to connect to {a} via TCP port {p} ...").format( - a=sockaddr[0], p=sockaddr[1])) - - try: - sock = socket.socket(family, socktype, proto) - except socket.error as e: - sock = None - LOG.warn(_("Error creating socket: {}").format(e)) - continue + for ip in addr_infos.keys(): - try: - sock.connect(sockaddr) - except socket.error as e: - sock.close() - sock = None - if self.verbose > 3: - LOG.debug(_("Could not connect: {}").format(e)) + addr_info = addr_infos[ip] + if self.check_ssh_available(addr_info): + ssh_available = True + self.tpl_ip = ip + break + + if not ssh_available: continue if self.verbose <= 3: print('', flush=True) - - LOG.info(_("Connected to {a} via TCP port {p}.").format( - a=sockaddr[0], p=sockaddr[1])) - data = sock.recv(4096) - if data: - msg = to_str(data).strip() - LOG.info(_("Got SSHD banner: {}").format(msg)) - sock.close() - sock = None - ssh_available = True self.ts_finish_install = time.time() self.ts_finish_install = time.time() @@ -870,10 +853,52 @@ class CrTplHandler(BaseHandler): _("SSH not available after {:0.1f} seconds, giving up.").format(duration)) # ------------------------------------------------------------------------- - def exec_remote(self, cmd): + def check_ssh_available(self, addr_info): + + family, socktype, proto, canonname, sockaddr = addr_info + + if self.verbose > 3: + LOG.debug(_("Trying to connect to {a} via TCP port {p} ...").format( + a=sockaddr[0], p=sockaddr[1])) + + try: + sock = socket.socket(family, socktype, proto) + except socket.error as e: + sock = None + LOG.warn(_("Error creating socket: {}").format(e)) + return False + + try: + sock.connect(sockaddr) + except socket.error as e: + sock.close() + sock = None + if self.verbose > 3: + LOG.debug(_("Could not connect: {}").format(e)) + return False + + LOG.info(_("Connected to {a} via TCP port {p}.").format( + a=sockaddr[0], p=sockaddr[1])) + + data = sock.recv(4096) + if data: + msg = to_str(data).strip() + LOG.info(_("Got SSHD banner: {}").format(msg)) + + sock.close() + sock = None + + return True + + # ------------------------------------------------------------------------- + def exec_remote(self, cmd, strict_host_key_checking=False): ssh = None result = {'out': None, 'err': None} + if strict_host_key_checking: + policy = paramiko.client.AutoAddPolicy() + else: + policy = paramiko.client.MissingHostKeyPolicy() try: @@ -881,8 +906,9 @@ class CrTplHandler(BaseHandler): ssh = paramiko.SSHClient() LOG.debug(_("Loading SSH system host keys.")) ssh.load_system_host_keys() - LOG.debug(_("Setting SSH missing host key policy to {}.").format('AutoAddPolicy')) - ssh.set_missing_host_key_policy(paramiko.client.AutoAddPolicy()) + LOG.debug(_("Setting SSH missing host key policy to {}.").format( + policy.__class__.__name__)) + ssh.set_missing_host_key_policy(policy) LOG.debug(_("Connecting to {h!r}, port {p} as {u!r} per SSH ...").format( h=self.tpl_ip, p=self.ssh_port, u=self.ssh_user)) -- 2.39.5