From 1b84d5c3855e1d1474bc23a3a307e789296a8659 Mon Sep 17 00:00:00 2001 From: Federico Ressi Date: Wed, 28 Oct 2020 10:11:34 +0100 Subject: [PATCH] Allow to specify a per-image SSH connection timeout Change-Id: Id67bee67b3f236eaaa48cfa4bde2ea0afc6cbd13 --- tobiko/openstack/glance/config.py | 7 ++++++- tobiko/openstack/stacks/_centos.py | 1 + tobiko/openstack/stacks/_cirros.py | 1 + tobiko/openstack/stacks/_nova.py | 12 ++++++++--- tobiko/openstack/stacks/_ubuntu.py | 1 + tobiko/shell/sh/_reboot.py | 9 +++++---- tobiko/shell/ssh/_client.py | 32 ++++++++++++++++++------------ 7 files changed, 42 insertions(+), 21 deletions(-) diff --git a/tobiko/openstack/glance/config.py b/tobiko/openstack/glance/config.py index 043c9c79c..272c07d0e 100644 --- a/tobiko/openstack/glance/config.py +++ b/tobiko/openstack/glance/config.py @@ -58,7 +58,12 @@ def get_images_options(): cfg.StrOpt('username', help="Default " + name + " username"), cfg.StrOpt('password', - help="Default " + name + " password")])] + help="Default " + name + " password"), + cfg.FloatOpt('connection_timeout', + default=None, + help=("Default " + name + + " SSH connection timeout (seconds)")), ] + )] return options diff --git a/tobiko/openstack/stacks/_centos.py b/tobiko/openstack/stacks/_centos.py index 11997074a..55bf8274e 100644 --- a/tobiko/openstack/stacks/_centos.py +++ b/tobiko/openstack/stacks/_centos.py @@ -36,6 +36,7 @@ class CentosImageFixture(glance.URLGlanceImageFixture): container_format = CONF.tobiko.centos.container_format or "bare" username = CONF.tobiko.centos.username or 'centos' password = CONF.tobiko.centos.password + connection_timeout = CONF.tobiko.centos.connection_timeout or 400. class CentosFlavorStackFixture(_nova.FlavorStackFixture): diff --git a/tobiko/openstack/stacks/_cirros.py b/tobiko/openstack/stacks/_cirros.py index 8f1793815..c47f03036 100644 --- a/tobiko/openstack/stacks/_cirros.py +++ b/tobiko/openstack/stacks/_cirros.py @@ -38,6 +38,7 @@ class CirrosImageFixture(glance.URLGlanceImageFixture): disk_format = CONF.tobiko.cirros.disk_format or "qcow2" username = CONF.tobiko.cirros.username or 'cirros' password = CONF.tobiko.cirros.password or 'gocubsgo' + connection_timeout = CONF.tobiko.cirros.connection_timeout or 200. class CirrosFlavorStackFixture(_nova.FlavorStackFixture): diff --git a/tobiko/openstack/stacks/_nova.py b/tobiko/openstack/stacks/_nova.py index 639b87366..61def44dd 100644 --- a/tobiko/openstack/stacks/_nova.py +++ b/tobiko/openstack/stacks/_nova.py @@ -114,6 +114,10 @@ class ServerStackFixture(heat.HeatStackFixture): """password used to login to a Nova server instance""" return self.image_fixture.password + @property + def connection_timeout(self): + return self.image_fixture.connection_timeout + # Stack used to create flavor for Nova server instance flavor_stack = None @@ -146,10 +150,11 @@ class ServerStackFixture(heat.HeatStackFixture): return bool(self.floating_network) @property - def ssh_client(self): + def ssh_client(self) -> ssh.SSHClientFixture: return ssh.ssh_client(host=self.ip_address, username=self.username, - password=self.password) + password=self.password, + connection_timeout=self.connection_timeout) @property def ssh_command(self): @@ -283,7 +288,7 @@ class ServerStackFixture(heat.HeatStackFixture): class PeerServerStackFixture(ServerStackFixture): - """Server witch networking access requires passing by a peer Nova server + """Server witch networking access requires passing by another Nova server """ has_floating_ip = False @@ -296,6 +301,7 @@ class PeerServerStackFixture(ServerStackFixture): return ssh.ssh_client(host=self.ip_address, username=self.username, password=self.password, + connection_timeout=self.connection_timeout, proxy_jump=self.peer_stack.ssh_client) @property diff --git a/tobiko/openstack/stacks/_ubuntu.py b/tobiko/openstack/stacks/_ubuntu.py index cc5d12470..b92838688 100644 --- a/tobiko/openstack/stacks/_ubuntu.py +++ b/tobiko/openstack/stacks/_ubuntu.py @@ -33,6 +33,7 @@ class UbuntuImageFixture(glance.URLGlanceImageFixture): container_format = CONF.tobiko.ubuntu.container_format or "bare" username = CONF.tobiko.ubuntu.username or 'ubuntu' password = CONF.tobiko.ubuntu.password + connection_timeout = CONF.tobiko.ubuntu.connection_timeout or 300. class UbuntuFlavorStackFixture(_nova.FlavorStackFixture): diff --git a/tobiko/shell/sh/_reboot.py b/tobiko/shell/sh/_reboot.py index 4a87db94c..13fb9120b 100644 --- a/tobiko/shell/sh/_reboot.py +++ b/tobiko/shell/sh/_reboot.py @@ -39,8 +39,8 @@ class RebootHostTimeoutError(RebootHostError): message = "host {hostname!r} not rebooted after {timeout!s} seconds" -def reboot_host(ssh_client, wait: bool = True, timeout: tobiko.Seconds = None, - method=soft_reset_method): +def reboot_host(ssh_client: ssh.SSHClientFixture, wait: bool = True, + timeout: tobiko.Seconds = None, method=soft_reset_method): reboot = RebootHostOperation(ssh_client=ssh_client, wait=wait, timeout=timeout, method=method) return tobiko.setup_fixture(reboot) @@ -58,6 +58,8 @@ class RebootHostOperation(tobiko.Operation): @property def ssh_client(self) -> ssh.SSHClientFixture: + if self._ssh_client is None: + raise ValueError(f"SSH client for object '{self}' is None") return self._ssh_client def __init__(self, @@ -66,8 +68,7 @@ class RebootHostOperation(tobiko.Operation): timeout: tobiko.Seconds = None, method=soft_reset_method): super(RebootHostOperation, self).__init__() - if ssh_client is not None: - self._ssh_client = ssh_client + self._ssh_client = ssh_client tobiko.check_valid_type(self.ssh_client, ssh.SSHClientFixture) self.wait = bool(wait) self.timeout = tobiko.to_seconds(timeout) diff --git a/tobiko/shell/ssh/_client.py b/tobiko/shell/ssh/_client.py index 7481b7894..803cd5434 100644 --- a/tobiko/shell/ssh/_client.py +++ b/tobiko/shell/ssh/_client.py @@ -452,7 +452,8 @@ class SSHClientManager(object): def get_client(self, host, hostname=None, username=None, port=None, proxy_jump=None, host_config=None, config_files=None, - proxy_client=None, **connect_parameters): + proxy_client=None, **connect_parameters) -> \ + SSHClientFixture: if isinstance(host, netaddr.IPAddress): host = str(host) @@ -468,17 +469,22 @@ class SSHClientManager(object): username = username or global_host_config.username host_key = hostname, port, username, proxy_jump - client = self.clients.get(host_key, UNDEFINED_CLIENT) - if client is UNDEFINED_CLIENT: - # Put a placeholder client to avoid infinite recursive lookup - self.clients[host_key] = None - proxy_client = proxy_client or self.get_proxy_client( - host=host, proxy_jump=proxy_jump, config_files=config_files) - self.clients[host_key] = client = SSHClientFixture( - host=host, hostname=hostname, port=port, username=username, - proxy_client=proxy_client, host_config=host_config, - **connect_parameters) - return client + existing_client = self.clients.get(host_key) + if isinstance(existing_client, SSHClientFixture): + return existing_client + + # Put a placeholder to avoid infinite recursive lookup + if existing_client is UNDEFINED_CLIENT: + raise RuntimeError('Recursive SSH proxy client definition') + self.clients[host_key] = UNDEFINED_CLIENT + + proxy_client = proxy_client or self.get_proxy_client( + host=host, proxy_jump=proxy_jump, config_files=config_files) + self.clients[host_key] = new_client = SSHClientFixture( + host=host, hostname=hostname, port=port, username=username, + proxy_client=proxy_client, host_config=host_config, + **connect_parameters) + return new_client def get_proxy_client(self, host=None, proxy_jump=None, host_config=None, config_files=None): @@ -495,7 +501,7 @@ CLIENTS = SSHClientManager() def ssh_client(host, port=None, username=None, proxy_jump=None, host_config=None, config_files=None, manager=None, - **connect_parameters): + **connect_parameters) -> SSHClientFixture: manager = manager or CLIENTS return manager.get_client(host=host, port=port, username=username, proxy_jump=proxy_jump, host_config=host_config,