# This file is part of cloud-init. See LICENSE file for license information. """Base instance.""" import time import paramiko from paramiko.ssh_exception import ( BadHostKeyException, AuthenticationException, SSHException) from ..util import TargetBase from tests.cloud_tests import LOG, util class Instance(TargetBase): """Base instance object.""" platform_name = None _ssh_client = None def __init__(self, platform, name, properties, config, features): """Set up instance. @param platform: platform object @param name: hostname of instance @param properties: image properties @param config: image config @param features: supported feature flags """ self.platform = platform self.name = name self.properties = properties self.config = config self.features = features self._tmp_count = 0 self.ssh_ip = None self.ssh_port = None self.ssh_key_file = None self.ssh_username = 'ubuntu' def console_log(self): """Instance console. @return_value: bytes of this instance’s console """ raise NotImplementedError def reboot(self, wait=True): """Reboot instance.""" raise NotImplementedError def shutdown(self, wait=True): """Shutdown instance.""" raise NotImplementedError def start(self, wait=True, wait_for_cloud_init=False): """Start instance.""" raise NotImplementedError def destroy(self): """Clean up instance.""" self._ssh_close() def _ssh(self, command, stdin=None): """Run a command via SSH.""" client = self._ssh_connect() cmd = util.shell_pack(command) fp_in, fp_out, fp_err = client.exec_command(cmd) channel = fp_in.channel if stdin is not None: fp_in.write(stdin) fp_in.close() channel.shutdown_write() rc = channel.recv_exit_status() return (fp_out.read(), fp_err.read(), rc) def _ssh_close(self): if self._ssh_client: try: self._ssh_client.close() except SSHException: LOG.warning('Failed to close SSH connection.') self._ssh_client = None def _ssh_connect(self): """Connect via SSH. Attempt to SSH to the client on the specific IP and port. If it fails in some manner, then retry 2 more times for a total of 3 attempts; sleeping a few seconds between attempts. """ if self._ssh_client: return self._ssh_client if not self.ssh_ip or not self.ssh_port: raise ValueError("Cannot ssh_connect, ssh_ip=%s ssh_port=%s" % (self.ssh_ip, self.ssh_port)) client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) private_key = paramiko.RSAKey.from_private_key_file(self.ssh_key_file) retries = 3 while retries: try: client.connect(username=self.ssh_username, hostname=self.ssh_ip, port=self.ssh_port, pkey=private_key) self._ssh_client = client return client except (ConnectionRefusedError, AuthenticationException, BadHostKeyException, ConnectionResetError, SSHException, OSError): retries -= 1 LOG.debug('Retrying ssh connection on connect failure') time.sleep(3) ssh_cmd = 'Failed ssh connection to %s@%s:%s after 3 retries' % ( self.ssh_username, self.ssh_ip, self.ssh_port ) raise util.InTargetExecuteError(b'', b'', 1, ssh_cmd, 'ssh') def _wait_for_system(self, wait_for_cloud_init): """Wait until system has fully booted and cloud-init has finished. @param wait_time: maximum time to wait @return_value: None, may raise OSError if wait_time exceeded """ def clean_test(test): """Clean formatting for system ready test testcase.""" return ' '.join(line for line in test.strip().splitlines() if not line.lstrip().startswith('#')) boot_timeout = self.config['boot_timeout'] tests = [self.config['system_ready_script']] if wait_for_cloud_init: tests.append(self.config['cloud_init_ready_script']) formatted_tests = ' && '.join(clean_test(t) for t in tests) cmd = ('i=0; while [ $i -lt {time} ] && i=$(($i+1)); do {test} && ' 'exit 0; sleep 1; done; exit 1').format(time=boot_timeout, test=formatted_tests) end_time = time.time() + boot_timeout while True: try: return_code = self.execute( cmd, rcs=(0, 1), description='wait for instance start' )[-1] if return_code == 0: break except util.InTargetExecuteError: LOG.warning("failed to connect via SSH") if time.time() < end_time: time.sleep(3) else: raise util.PlatformError('ssh', 'after %ss instance is not ' 'reachable' % boot_timeout) # vi: ts=4 expandtab