← Back to team overview

cloud-init-dev team mailing list archive

[Merge] ~powersj/cloud-init:cii-move-ssh into cloud-init:master

 

Joshua Powers has proposed merging ~powersj/cloud-init:cii-move-ssh into cloud-init:master.

Commit message:
tests: Move SSH execution to instance super class

The nocloud-kvm instance is not the only instance that will eventually
make use of the SSH operations. Therefore, this moves those functions
up and out to the instance super class.

This also adds paramiko to test-requirements.txt to allow a tox
run of pylint to pass.


Requested reviews:
  cloud-init commiters (cloud-init-dev)

For more details, see:
https://code.launchpad.net/~powersj/cloud-init/+git/cloud-init/+merge/335053
-- 
Your team cloud-init commiters is requested to review the proposed merge of ~powersj/cloud-init:cii-move-ssh into cloud-init:master.
diff --git a/test-requirements.txt b/test-requirements.txt
index d9d41b5..d7a9de5 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -4,6 +4,7 @@ mock
 nose
 unittest2
 coverage
+paramiko
 
 # Only needed if you want to know the test times
 # nose-timer
diff --git a/tests/cloud_tests/platforms/instances.py b/tests/cloud_tests/platforms/instances.py
index 8c59d62..d09f96b 100644
--- a/tests/cloud_tests/platforms/instances.py
+++ b/tests/cloud_tests/platforms/instances.py
@@ -1,14 +1,22 @@
 # 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 util
 
 
 class Instance(TargetBase):
     """Base instance object."""
 
     platform_name = None
+    _ssh_client = None
 
     def __init__(self, platform, name, properties, config, features):
         """Set up instance.
@@ -26,6 +34,10 @@ class Instance(TargetBase):
         self.features = features
         self._tmp_count = 0
 
+        self.ssh_ip = None
+        self.ssh_port = None
+        self.ssh_key_file = None
+
     def console_log(self):
         """Instance console.
 
@@ -49,6 +61,55 @@ class Instance(TargetBase):
         """Clean up instance."""
         pass
 
+    def _ssh_connect(self):
+        """Connect via SSH."""
+        if self._ssh_client:
+            return self._ssh_client
+
+        client = paramiko.SSHClient()
+        client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+        private_key = paramiko.RSAKey.from_private_key_file(self.ssh_key_file)
+
+        retries = 10
+        while retries:
+            try:
+                client.connect(username='ubuntu', hostname=self.ssh_ip,
+                               port=self.ssh_port, pkey=private_key,
+                               banner_timeout=30)
+                self._ssh_client = client
+                return client
+            except (ConnectionRefusedError, AuthenticationException,
+                    BadHostKeyException, ConnectionResetError, SSHException,
+                    OSError) as e:
+                retries -= 1
+                time.sleep(1)
+
+        ssh_cmd = 'Failed command to: ubuntu@%s:%s' % (
+            self.ssh_ip, self.ssh_port
+        )
+        raise util.InTargetExecuteError(b'', b'', 1, ssh_cmd, 'ssh')
+
+    def ssh(self, command, stdin=None):
+        """Run a command via SSH."""
+        client = self._ssh_connect()
+
+        cmd = util.shell_pack(command)
+        try:
+            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()
+        except SSHException as e:
+            raise util.InTargetExecuteError(b'', b'', 1, command, 'ssh',
+                                            reason=e)
+
+        return (fp_out.read(), fp_err.read(), rc)
+
     def _wait_for_system(self, wait_for_cloud_init):
         """Wait until system has fully booted and cloud-init has finished.
 
diff --git a/tests/cloud_tests/platforms/nocloudkvm/instance.py b/tests/cloud_tests/platforms/nocloudkvm/instance.py
index 9bb2425..a2fc974 100644
--- a/tests/cloud_tests/platforms/nocloudkvm/instance.py
+++ b/tests/cloud_tests/platforms/nocloudkvm/instance.py
@@ -4,7 +4,6 @@
 
 import copy
 import os
-import paramiko
 import socket
 import subprocess
 import time
@@ -26,7 +25,6 @@ class NoCloudKVMInstance(Instance):
     """NoCloud KVM backed instance."""
 
     platform_name = "nocloud-kvm"
-    _ssh_client = None
 
     def __init__(self, platform, name, image_path, properties, config,
                  features, user_data, meta_data):
@@ -39,6 +37,10 @@ class NoCloudKVMInstance(Instance):
         @param config: dictionary of configuration values
         @param features: dictionary of supported feature flags
         """
+        super(NoCloudKVMInstance, self).__init__(
+            platform, name, properties, config, features
+        )
+
         self.user_data = user_data
         if meta_data:
             meta_data = copy.deepcopy(meta_data)
@@ -66,6 +68,7 @@ class NoCloudKVMInstance(Instance):
                 meta_data['public-keys'] = []
             meta_data['public-keys'].append(self.ssh_pubkey)
 
+        self.ssh_ip = '127.0.0.1'
         self.ssh_port = None
         self.pid = None
         self.pid_file = None
@@ -73,9 +76,6 @@ class NoCloudKVMInstance(Instance):
         self.disk = image_path
         self.meta_data = meta_data
 
-        super(NoCloudKVMInstance, self).__init__(
-            platform, name, properties, config, features)
-
     def destroy(self):
         """Clean up instance."""
         if self.pid:
@@ -125,50 +125,6 @@ class NoCloudKVMInstance(Instance):
         s.close()
         return num
 
-    def ssh(self, command, stdin=None):
-        """Run a command via SSH."""
-        client = self._ssh_connect()
-
-        cmd = util.shell_pack(command)
-        try:
-            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)
-        except paramiko.SSHException as e:
-            raise util.InTargetExecuteError(
-                b'', b'', -1, command, self.name, reason=e)
-
-    def _ssh_connect(self, hostname='localhost', username='ubuntu',
-                     banner_timeout=120, retry_attempts=30):
-        """Connect via SSH."""
-        if self._ssh_client:
-            return self._ssh_client
-
-        private_key = paramiko.RSAKey.from_private_key_file(self.ssh_key_file)
-        client = paramiko.SSHClient()
-        client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
-        while retry_attempts:
-            try:
-                client.connect(hostname=hostname, username=username,
-                               port=self.ssh_port, pkey=private_key,
-                               banner_timeout=banner_timeout)
-                self._ssh_client = client
-                return client
-            except (paramiko.SSHException, TypeError):
-                time.sleep(1)
-                retry_attempts = retry_attempts - 1
-
-        error_desc = 'Failed command to: %s@%s:%s' % (username, hostname,
-                                                      self.ssh_port)
-        raise util.InTargetExecuteError('', '', -1, 'ssh connect',
-                                        self.name, error_desc)
-
     def start(self, wait=True, wait_for_cloud_init=False):
         """Start instance."""
         tmpdir = self.platform.config['data_dir']

Follow ups