Adds instance_utils library and initial SSH tests

* Provides an instance util class for common server queries
* Refactored the create server smoke test as an example
* Added ssh tag to tests requiring SSH to allow them to be
  skipped if needed

Change-Id: Ia34d7c75ad05f7658d1abb7bebeb1bbd271fd089
This commit is contained in:
Daryl Walleck
2012-05-20 23:36:52 -05:00
parent b93fcd29cd
commit 6b9b288aee
10 changed files with 191 additions and 40 deletions
+17
View File
@@ -57,6 +57,23 @@ build_interval = 10
# to build or reach an expected status
build_timeout = 600
# Run additional tests that use SSH for instance validation?
# This requires the instances be routable from the host
# executing the tests
run_ssh = false
# Name of a user used to authenticated to an instance
ssh_user = {$SSH_USER}
# Network id used for SSH (public, private, etc)
network_for_ssh = {$SSH_NETWORK}
# IP version of the address used for SSH
ip_version_for_ssh = {$SSH_IP_VERSION}
# Number of seconds to wait to authenticate to an instance
ssh_timeout = 300
# The type of endpoint for a Compute API service. Unless you have a
# custom Keystone service catalog implementation, you probably want to leave
# this value as "compute"
+6 -4
View File
@@ -1,6 +1,7 @@
import time
import socket
import warnings
from tempest import exceptions
with warnings.catch_warnings():
warnings.simplefilter("ignore")
@@ -9,7 +10,7 @@ with warnings.catch_warnings():
class Client(object):
def __init__(self, host, username, password, timeout=300):
def __init__(self, host, username, password, timeout=60):
self.host = host
self.username = username
self.password = password
@@ -26,8 +27,7 @@ class Client(object):
while not self._is_timed_out(self.timeout, _start_time):
try:
ssh.connect(self.host, username=self.username,
password=self.password, look_for_keys=False,
timeout=20)
password=self.password, timeout=20)
_timeout = False
break
except socket.error:
@@ -36,7 +36,9 @@ class Client(object):
time.sleep(15)
continue
if _timeout:
raise socket.error("SSH connect timed out")
raise exceptions.SSHTimeout(host=self.host,
user=self.username,
password=self.password)
return ssh
def _is_timed_out(self, timeout, start_time):
+4
View File
@@ -0,0 +1,4 @@
LAST_REBOOT_TIME_FORMAT = '%Y-%m-%d %H:%M'
PING_IPV4_COMMAND = 'ping -c 3 '
PING_IPV6_COMMAND = 'ping6 -c 3 '
PING_PACKET_LOSS_REGEX = '(\d{1,3})\.?\d*\% packet loss'
@@ -0,0 +1,48 @@
from tempest.common.ssh import Client
from tempest.config import TempestConfig
from tempest.exceptions import SSHTimeout, ServerUnreachable
class RemoteClient():
def __init__(self, server, username, password):
ssh_timeout = TempestConfig().compute.ssh_timeout
network = TempestConfig().compute.network_for_ssh
ip_version = TempestConfig().compute.ip_version_for_ssh
addresses = server['addresses'][network]
for address in addresses:
if address['version'] == ip_version:
ip_address = address['addr']
break
if ip_address is None:
raise ServerUnreachable()
self.ssh_client = Client(ip_address, username, password, ssh_timeout)
if not self.ssh_client.test_connection_auth():
raise SSHTimeout()
def can_authenticate(self):
# Re-authenticate
return self.ssh_client.test_connection_auth()
def hostname_equals_servername(self, expected_hostname):
# Get hostname using command "hostname"
actual_hostname = self.ssh_client.exec_command("hostname").rstrip()
return expected_hostname == actual_hostname
def get_files(self, path):
# Return a list of comma seperated files
command = "ls -m " + path
return self.ssh_client.exec_command(command).rstrip('\n').split(', ')
def get_ram_size_in_mb(self):
output = self.ssh_client.exec_command('free -m | grep Mem')
if output:
return output.split()[1]
def get_number_of_vcpus(self):
command = 'cat /proc/cpuinfo | grep processor | wc -l'
output = self.ssh_client.exec_command(command)
return int(output)
+25
View File
@@ -164,6 +164,31 @@ class ComputeConfig(BaseConfig):
"""Timeout in seconds to wait for an entity to build."""
return float(self.get("build_timeout", 300))
@property
def run_ssh(self):
"""Does the test environment support snapshots?"""
return self.get("run_ssh", 'false').lower() != 'false'
@property
def ssh_user(self):
"""User name used to authenticate to an instance."""
return self.get("ssh_user", "root")
@property
def ssh_timeout(self):
"""Timeout in seconds to wait for authentcation to succeed."""
return float(self.get("ssh_timeout", 300))
@property
def network_for_ssh(self):
"""Network used for SSH connections."""
return self.get("network_for_ssh", "public")
@property
def ip_version_for_ssh(self):
"""IP version used for SSH connections."""
return int(self.get("ip_version_for_ssh", 4))
@property
def catalog_type(self):
"""Catalog type of the Compute service."""
+9
View File
@@ -75,3 +75,12 @@ class ComputeFault(TempestException):
class Duplicate(TempestException):
message = "An object with that identifier already exists"
class SSHTimeout(TempestException):
message = ("Connection to the %(host)s via SSH timed out.\n"
"User: %(user)s, Password: %(password)s")
class ServerUnreachable(TempestException):
message = "The server is not reachable via the configured network"
+1
View File
@@ -20,6 +20,7 @@ class BaseComputeTest(unittest.TestCase):
config = os.config
build_interval = config.compute.build_interval
build_timeout = config.compute.build_timeout
ssh_user = config.compute.ssh_user
# Validate reference data exists
# If not, attempt to auto-configure
@@ -0,0 +1,81 @@
import base64
import unittest2 as unittest
from nose.plugins.attrib import attr
from tempest import openstack
import tempest.config
from tempest.common.utils.data_utils import rand_name
from tempest.common.utils.linux.remote_client import RemoteClient
from tempest.tests.base_compute_test import BaseComputeTest
class ServersTest(BaseComputeTest):
run_ssh = tempest.config.TempestConfig().compute.run_ssh
@classmethod
def setUpClass(cls):
cls.meta = {'hello': 'world'}
cls.accessIPv4 = '1.1.1.1'
cls.accessIPv6 = '::babe:220.12.22.2'
cls.name = rand_name('server')
file_contents = 'This is a test file.'
personality = [{'path': '/etc/test.txt',
'contents': base64.b64encode(file_contents)}]
cls.client = cls.servers_client
cls.resp, cls.server_initial = cls.client.create_server(cls.name,
cls.image_ref,
cls.flavor_ref,
meta=cls.meta,
accessIPv4=cls.accessIPv4,
accessIPv6=cls.accessIPv6,
personality=personality)
cls.password = cls.server_initial['adminPass']
cls.client.wait_for_server_status(cls.server_initial['id'], 'ACTIVE')
resp, cls.server = cls.client.get_server(cls.server_initial['id'])
@classmethod
def tearDownClass(cls):
cls.client.delete_server(cls.server_initial['id'])
@attr(type='smoke')
def test_create_server_response(self):
"""Check that the required fields are returned with values"""
self.assertEqual(202, self.resp.status)
self.assertTrue(self.server_initial['id'] is not None)
self.assertTrue(self.server_initial['adminPass'] is not None)
@attr(type='smoke')
def test_created_server_fields(self):
"""Verify the specified server attributes are set correctly"""
self.assertEqual(self.accessIPv4, self.server['accessIPv4'])
self.assertEqual(self.accessIPv6, self.server['accessIPv6'])
self.assertEqual(self.name, self.server['name'])
self.assertEqual(self.image_ref, self.server['image']['id'])
self.assertEqual(str(self.flavor_ref), self.server['flavor']['id'])
self.assertEqual(self.meta, self.server['metadata'])
@attr(type='positive')
@unittest.skipIf(not run_ssh, 'Instance validation tests are disabled.')
def test_can_log_into_created_server(self):
"""Check that the user can authenticate with the generated password"""
linux_client = RemoteClient(self.server, self.ssh_user, self.password)
self.assertTrue(linux_client.can_authenticate())
@attr(type='positive')
@unittest.skipIf(not run_ssh, 'Instance validation tests are disabled.')
def test_verify_created_server_vcpus(self):
"""
Verify that the number of vcpus reported by the instance matches
the amount stated by the flavor
"""
resp, flavor = self.flavors_client.get_flavor_details(self.flavor_ref)
linux_client = RemoteClient(self.server, self.ssh_user, self.password)
self.assertEqual(flavor['vcpus'], linux_client.get_number_of_vcpus())
@attr(type='positive')
@unittest.skipIf(not run_ssh, 'Instance validation tests are disabled.')
def test_host_name_is_same_as_server_name(self):
"""Verify the instance host name is the same as the server name"""
linux_client = RemoteClient(self.server, self.ssh_user, self.password)
self.assertTrue(linux_client.hostname_equals_servername(self.name))
-36
View File
@@ -11,42 +11,6 @@ class ServersTest(BaseComputeTest):
def setUpClass(cls):
cls.client = cls.servers_client
@attr(type='smoke')
def test_create_delete_server(self):
meta = {'hello': 'world'}
accessIPv4 = '1.1.1.1'
accessIPv6 = '::babe:220.12.22.2'
name = rand_name('server')
file_contents = 'This is a test file.'
personality = [{'path': '/etc/test.txt',
'contents': base64.b64encode(file_contents)}]
resp, server = self.client.create_server(name,
self.image_ref,
self.flavor_ref,
meta=meta,
accessIPv4=accessIPv4,
accessIPv6=accessIPv6,
personality=personality)
#Check the initial response
self.assertEqual(202, resp.status)
self.assertTrue(server['id'] is not None)
self.assertTrue(server['adminPass'] is not None)
#Wait for the server to become active
self.client.wait_for_server_status(server['id'], 'ACTIVE')
#Verify the specified attributes are set correctly
resp, server = self.client.get_server(server['id'])
self.assertEqual('1.1.1.1', server['accessIPv4'])
self.assertEqual('::babe:220.12.22.2', server['accessIPv6'])
self.assertEqual(name, server['name'])
self.assertEqual(self.image_ref, server['image']['id'])
self.assertEqual(str(self.flavor_ref), server['flavor']['id'])
#Delete the server
resp, body = self.client.delete_server(server['id'])
self.assertEqual(204, resp.status)
@attr(type='smoke')
def test_create_server_with_admin_password(self):
"""