Import all the stacktester stuff as-is (s/stacktester/kong/, though).

This commit is contained in:
Soren Hansen
2011-09-08 13:33:17 +02:00
parent 4ad76ef7e5
commit bc1d3a044b
14 changed files with 1614 additions and 0 deletions
View File
+57
View File
@@ -0,0 +1,57 @@
from kong import exceptions
import httplib2
import os
import time
class Client(object):
USER_AGENT = 'python-nova_test_client'
def __init__(self, host='localhost', port=80, base_url=''):
#TODO: join these more robustly
self.base_url = "http://%s:%s/%s" % (host, port, base_url)
def poll_request(self, method, url, check_response, **kwargs):
timeout = kwargs.pop('timeout', 180)
interval = kwargs.pop('interval', 2)
# Start timestamp
start_ts = int(time.time())
while True:
resp, body = self.request(method, url, **kwargs)
if (check_response(resp, body)):
break
if (int(time.time()) - start_ts >= timeout):
raise exceptions.TimeoutException
time.sleep(interval)
def poll_request_status(self, method, url, status=200, **kwargs):
def check_response(resp, body):
return resp['status'] == str(status)
self.poll_request(method, url, check_response, **kwargs)
def request(self, method, url, **kwargs):
# Default to management_url, but can be overridden here
# (for auth requests)
base_url = kwargs.get('base_url', self.management_url)
self.http_obj = httplib2.Http()
params = {}
params['headers'] = {'User-Agent': self.USER_AGENT}
params['headers'].update(kwargs.get('headers', {}))
if 'Content-Type' not in params.get('headers',{}):
params['headers']['Content-Type'] = 'application/json'
if 'body' in kwargs:
params['body'] = kwargs.get('body')
req_url = os.path.join(base_url, url.strip('/'))
resp, body = self.http_obj.request(req_url, method, **params)
return resp, body
+79
View File
@@ -0,0 +1,79 @@
import time
import socket
import warnings
with warnings.catch_warnings():
warnings.simplefilter("ignore")
import paramiko
class Client(object):
def __init__(self, host, username, password, timeout=300):
self.host = host
self.username = username
self.password = password
self.timeout = timeout
def _get_ssh_connection(self):
"""Returns an ssh connection to the specified host"""
_timeout = True
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(
paramiko.AutoAddPolicy())
_start_time = time.time()
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=self.timeout)
_timeout = False
break
except socket.error:
continue
except paramiko.AuthenticationException:
time.sleep(15)
continue
if _timeout:
raise socket.error("SSH connect timed out")
return ssh
def _is_timed_out(self, timeout, start_time):
return (time.time() - timeout) > start_time
def connect_until_closed(self):
"""Connect to the server and wait until connection is lost"""
try:
ssh = self._get_ssh_connection()
_transport = ssh.get_transport()
_start_time = time.time()
_timed_out = self._is_timed_out(self.timeout, _start_time)
while _transport.is_active() and not _timed_out:
time.sleep(5)
_timed_out = self._is_timed_out(self.timeout, _start_time)
ssh.close()
except (EOFError, paramiko.AuthenticationException, socket.error):
return
def exec_command(self, cmd):
"""Execute the specified command on the server.
:returns: data read from standard output of the command
"""
ssh = self._get_ssh_connection()
stdin, stdout, stderr = ssh.exec_command(cmd)
output = stdout.read()
ssh.close()
return output
def test_connection_auth(self):
""" Returns true if ssh can connect to server"""
try:
connection = self._get_ssh_connection()
connection.close()
except paramiko.AuthenticationException:
return False
return True
+112
View File
@@ -0,0 +1,112 @@
import ConfigParser
class NovaConfig(object):
"""Provides configuration information for connecting to Nova."""
def __init__(self, conf):
"""Initialize a Nova-specific configuration object."""
self.conf = conf
def get(self, item_name, default_value):
try:
return self.conf.get("nova", item_name)
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
return default_value
@property
def host(self):
"""Host for the Nova HTTP API. Defaults to 127.0.0.1."""
return self.get("host", "127.0.0.1")
@property
def port(self):
"""Port for the Nova HTTP API. Defaults to 8774."""
return int(self.get("port", 8774))
@property
def username(self):
"""Username to use for Nova API requests. Defaults to 'admin'."""
return self.get("user", "admin")
@property
def base_url(self):
"""Base of the HTTP API URL. Defaults to '/v1.1'."""
return self.get("base_url", "/v1.1")
@property
def project_id(self):
"""Base of the HTTP API URL. Defaults to '/v1.1'."""
return self.get("project_id", "admin")
@property
def api_key(self):
"""API key to use when authenticating. Defaults to 'admin_key'."""
return self.get("api_key", "admin_key")
@property
def ssh_timeout(self):
"""Timeout in seconds to use when connecting via ssh."""
return float(self.get("ssh_timeout", 300))
@property
def build_timeout(self):
"""Timeout in seconds to use when connecting via ssh."""
return float(self.get("build_timeout", 300))
class EnvironmentConfig(object):
def __init__(self, conf):
"""Initialize a Environment-specific configuration object."""
self.conf = conf
def get(self, item_name, default_value):
try:
return self.conf.get("environment", item_name)
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
return default_value
@property
def image_ref(self):
"""Valid imageRef to use """
return self.get("image_ref", 3);
@property
def image_ref_alt(self):
"""Valid imageRef to rebuild images with"""
return self.get("image_ref_alt", 3);
@property
def flavor_ref(self):
"""Valid flavorRef to use"""
return self.get("flavor_ref", 1);
@property
def flavor_ref_alt(self):
"""Valid flavorRef to resize images with"""
return self.get("flavor_ref_alt", 2);
@property
def multi_node(self):
""" Does the test environment have more than one compute node """
return self.get("multi_node", 'false') != 'false'
class StackConfig(object):
"""Provides `kong` configuration information."""
_path = None
def __init__(self, path=None):
"""Initialize a configuration from a path."""
self._path = path or self._path
self._conf = self.load_config(self._path)
self.nova = NovaConfig(self._conf)
self.env = EnvironmentConfig(self._conf)
def load_config(self, path=None):
"""Read configuration from given path and return a config object."""
config = ConfigParser.SafeConfigParser()
config.read(path)
return config
+9
View File
@@ -0,0 +1,9 @@
class TimeoutException(Exception):
""" Exception on timeout """
def __repr__(self):
return "Request Timed Out"
class ServerNotFound(KeyError):
pass
+25
View File
@@ -0,0 +1,25 @@
import re
class KnownIssuesFinder(object):
def __init__(self):
self.count = 0
self._pattern = re.compile('# *KNOWN-ISSUE')
def find_known_issues(self, package):
for file in self._find_test_module_files(package):
self._count_known_issues(file)
def _find_test_module_files(self, package):
for name in dir(package):
if name.startswith('test'):
module = getattr(package, name)
yield module.__file__
def _count_known_issues(self, file):
if file.endswith('.pyc') or file.endswith('.pyo'):
file = file[0:-1]
for line in open(file):
if self._pattern.search(line) is not None:
self.count += 1
+155
View File
@@ -0,0 +1,155 @@
import json
import logging
import subprocess
import kong.common.http
from kong import exceptions
class API(kong.common.http.Client):
"""Barebones Nova HTTP API client."""
def __init__(self, host, port, base_url, user, api_key, project_id=''):
"""Initialize Nova HTTP API client.
:param host: Hostname/IP of the Nova API to test.
:param port: Port of the Nova API to test.
:param base_url: Version identifier (normally /v1.0 or /v1.1)
:param user: The username to use for tests.
:param api_key: The API key of the user.
:returns: None
"""
super(API, self).__init__(host, port, base_url)
self.user = user
self.api_key = api_key
self.project_id = project_id
# Default to same as base_url, but will be change on auth
self.management_url = self.base_url
def authenticate(self, user, api_key, project_id):
"""Request and return an authentication token from Nova.
:param user: The username we're authenticating.
:param api_key: The API key for the user we're authenticating.
:returns: Authentication token (string)
:raises: KeyError if authentication fails.
"""
headers = {
'X-Auth-User': user,
'X-Auth-Key': api_key,
'X-Auth-Project-Id': project_id,
}
resp, body = super(API, self).request('GET', '', headers=headers,
base_url=self.base_url)
try:
self.management_url = resp['x-server-management-url']
return resp['x-auth-token']
except KeyError:
print "Failed to authenticate user"
raise
def _wait_for_entity_status(self, url, entity_name, status, **kwargs):
"""Poll the provided url until expected entity status is returned"""
def check_response(resp, body):
try:
data = json.loads(body)
return data[entity_name]['status'] == status
except (ValueError, KeyError):
return False
try:
self.poll_request('GET', url, check_response, **kwargs)
except exceptions.TimeoutException:
msg = "%s failed to reach status %s" % (entity_name, status)
raise AssertionError(msg)
def wait_for_server_status(self, server_id, status='ACTIVE', **kwargs):
"""Wait for the server status to be equal to the status passed in.
:param server_id: Server ID to query.
:param status: The status string to look for.
:returns: None
:raises: AssertionError if request times out
"""
url = '/servers/%s' % server_id
return self._wait_for_entity_status(url, 'server', status, **kwargs)
def wait_for_image_status(self, image_id, status='ACTIVE', **kwargs):
"""Wait for the image status to be equal to the status passed in.
:param image_id: Image ID to query.
:param status: The status string to look for.
:returns: None
:raises: AssertionError if request times out
"""
url = '/images/%s' % image_id
return self._wait_for_entity_status(url, 'image', status, **kwargs)
def request(self, method, url, **kwargs):
"""Generic HTTP request on the Nova API.
:param method: Request verb to use (GET, PUT, POST, etc.)
:param url: The API resource to request.
:param kwargs: Additional keyword arguments to pass to the request.
:returns: HTTP response object.
"""
headers = kwargs.get('headers', {})
project_id = kwargs.get('project_id', self.project_id)
headers['X-Auth-Token'] = self.authenticate(self.user, self.api_key,
self.project_id)
kwargs['headers'] = headers
return super(API, self).request(method, url, **kwargs)
def get_server(self, server_id):
"""Fetch a server by id
:param server_id: dict of server attributes
:returns: dict of server attributes
:raises: ServerNotFound if server does not exist
"""
resp, body = self.request('GET', '/servers/%s' % server_id)
try:
assert resp['status'] == '200'
data = json.loads(body)
return data['server']
except (AssertionError, ValueError, TypeError, KeyError):
raise exceptions.ServerNotFound(server_id)
def create_server(self, entity):
"""Attempt to create a new server.
:param entity: dict of server attributes
:returns: dict of server attributes after creation
:raises: AssertionError if server creation fails
"""
post_body = json.dumps({
'server': entity,
})
resp, body = self.request('POST', '/servers', body=post_body)
try:
assert resp['status'] == '202'
data = json.loads(body)
return data['server']
except (AssertionError, ValueError, TypeError, KeyError):
raise AssertionError("Failed to create server")
def delete_server(self, server_id):
"""Attempt to delete a server.
:param server_id: server identifier
:returns: None
"""
url = '/servers/%s' % server_id
response, body = self.request('DELETE', url)
+15
View File
@@ -0,0 +1,15 @@
import kong.config
import kong.nova
class Manager(object):
"""Top-level object to access OpenStack resources."""
def __init__(self):
self.config = kong.config.StackConfig()
self.nova = kong.nova.API(self.config.nova.host,
self.config.nova.port,
self.config.nova.base_url,
self.config.nova.username,
self.config.nova.api_key,
self.config.nova.project_id)
+102
View File
@@ -0,0 +1,102 @@
import json
import os
import unittest2 as unittest
from kong import openstack
class FlavorsTest(unittest.TestCase):
def setUp(self):
self.os = openstack.Manager()
def tearDown(self):
pass
def _index_flavors(self):
url = '/flavors'
response, body = self.os.nova.request('GET', url)
self.assertEqual(response['status'], '200')
body_dict = json.loads(body)
self.assertEqual(body_dict.keys(), ['flavors'])
return body_dict['flavors']
def _show_flavor(self, flavor_id):
url = '/flavors/%s' % flavor_id
response, body = self.os.nova.request('GET', url)
self.assertEqual(response['status'], '200')
body_dict = json.loads(body)
self.assertEqual(body_dict.keys(), ['flavor'])
return body_dict['flavor']
def _assert_flavor_entity_basic(self, flavor):
actual_keys = set(flavor.keys())
expected_keys = set(('id', 'name', 'links'))
self.assertEqual(actual_keys, expected_keys)
self._assert_flavor_links(flavor)
def _assert_flavor_entity_detailed(self, flavor):
actual_keys = set(flavor.keys())
expected_keys = set(('id', 'name', 'ram', 'disk', 'links'))
self.assertEqual(actual_keys, expected_keys)
self.assertEqual(type(flavor['ram']), int)
self.assertEqual(type(flavor['disk']), int)
self._assert_flavor_links(flavor)
def _assert_flavor_links(self, flavor):
actual_links = flavor['links']
flavor_id = str(flavor['id'])
host = self.os.config.nova.host
port = self.os.config.nova.port
api_url = '%s:%s' % (host, port)
base_url = os.path.join(api_url, self.os.config.nova.base_url,
self.os.config.nova.project_id)
api_url = os.path.join(api_url, self.os.config.nova.project_id)
self_link = 'http://' + os.path.join(base_url, 'flavors', flavor_id)
bookmark_link = 'http://' + os.path.join(api_url, 'flavors', flavor_id)
expected_links = [
{
'rel': 'self',
'href': self_link,
},
{
'rel': 'bookmark',
'href': bookmark_link,
},
]
self.assertEqual(actual_links, expected_links)
def test_show_flavor(self):
"""Retrieve a single flavor"""
flavors = self._index_flavors()
for flavor in flavors:
detailed_flavor = self._show_flavor(flavor['id'])
self._assert_flavor_entity_detailed(detailed_flavor)
def test_index_flavors_basic(self):
"""List all flavors"""
flavors = self._index_flavors()
for flavor in flavors:
self._assert_flavor_entity_basic(flavor)
def test_index_flavors_detailed(self):
"""List all flavors in detail"""
url = '/flavors/detail'
response, body = self.os.nova.request('GET', url)
self.assertEqual(response['status'], '200')
body_dict = json.loads(body)
self.assertEqual(body_dict.keys(), ['flavors'])
flavors = body_dict['flavors']
for flavor in flavors:
self._assert_flavor_entity_detailed(flavor)
+97
View File
@@ -0,0 +1,97 @@
import json
import os
import unittest2 as unittest
from kong import openstack
class ImagesTest(unittest.TestCase):
def setUp(self):
self.os = openstack.Manager()
host = self.os.config.nova.host
port = self.os.config.nova.port
self.base_url = '%s:%s' % (host, port)
self.api_url = os.path.join(self.base_url, self.os.config.nova.base_url)
def tearDown(self):
pass
def _assert_image_links(self, image):
image_id = str(image['id'])
self_link = 'http://' + os.path.join(self.api_url,
self.os.config.nova.project_id,
'images', image_id)
bookmark_link = 'http://' + os.path.join(self.base_url,
self.os.config.nova.project_id,
'images', image_id)
expected_links = [
{
'rel': 'self',
'href': self_link,
},
{
'rel': 'bookmark',
'href': bookmark_link,
},
]
self.assertEqual(image['links'], expected_links)
def _assert_image_entity_basic(self, image):
actual_keys = set(image.keys())
expected_keys = set((
'id',
'name',
'links',
))
self.assertEqual(actual_keys, expected_keys)
self._assert_image_links(image)
def _assert_image_entity_detailed(self, image):
keys = image.keys()
if 'server' in keys:
keys.remove('server')
actual_keys = set(keys)
expected_keys = set((
'id',
'name',
'progress',
'created',
'updated',
'status',
'metadata',
'links',
))
self.assertEqual(actual_keys, expected_keys)
self._assert_image_links(image)
def test_index(self):
"""List all images"""
response, body = self.os.nova.request('GET', '/images')
self.assertEqual(response['status'], '200')
resp_body = json.loads(body)
self.assertEqual(resp_body.keys(), ['images'])
for image in resp_body['images']:
self._assert_image_entity_basic(image)
def test_detail(self):
"""List all images in detail"""
response, body = self.os.nova.request('GET', '/images/detail')
self.assertEqual(response['status'], '200')
resp_body = json.loads(body)
self.assertEqual(resp_body.keys(), ['images'])
for image in resp_body['images']:
self._assert_image_entity_detailed(image)
+354
View File
@@ -0,0 +1,354 @@
import json
import time
from kong import exceptions
from kong import openstack
from kong.common import ssh
import unittest2 as unittest
class ServerActionsTest(unittest.TestCase):
multi_node = openstack.Manager().config.env.multi_node
def setUp(self):
self.os = openstack.Manager()
self.image_ref = self.os.config.env.image_ref
self.image_ref_alt = self.os.config.env.image_ref_alt
self.flavor_ref = self.os.config.env.flavor_ref
self.flavor_ref_alt = self.os.config.env.flavor_ref_alt
self.ssh_timeout = self.os.config.nova.ssh_timeout
self.server_password = 'testpwd'
self.server_name = 'testserver'
expected_server = {
'name' : self.server_name,
'imageRef' : self.image_ref,
'flavorRef' : self.flavor_ref,
'adminPass' : self.server_password,
}
created_server = self.os.nova.create_server(expected_server)
self.server_id = created_server['id']
self._wait_for_status(self.server_id, 'ACTIVE')
server = self.os.nova.get_server(self.server_id)
# KNOWN-ISSUE lp?
#self.access_ip = server['accessIPv4']
self.access_ip = server['addresses']['public'][0]['addr']
# Ensure server came up
self._assert_ssh_password()
def tearDown(self):
self.os.nova.delete_server(self.server_id)
def _get_ssh_client(self, password):
return ssh.Client(self.access_ip, 'root', password, self.ssh_timeout)
def _assert_ssh_password(self, password=None):
_password = password or self.server_password
client = self._get_ssh_client(_password)
self.assertTrue(client.test_connection_auth())
def _wait_for_status(self, server_id, status):
try:
self.os.nova.wait_for_server_status(server_id, status)
except exceptions.TimeoutException:
self.fail("Server failed to change status to %s" % status)
def _get_boot_time(self):
"""Return the time the server was started"""
output = self._read_file("/proc/uptime")
uptime = float(output.split().pop(0))
return time.time() - uptime
def _write_file(self, filename, contents):
return self._exec_command("echo -n %s > %s" % (contents, filename))
def _read_file(self, filename):
return self._exec_command("cat %s" % filename)
def _exec_command(self, command):
client = self._get_ssh_client(self.server_password)
return client.exec_command(command)
def test_reboot_server_soft(self):
"""Reboot a server (SOFT)"""
# SSH and get the uptime
initial_time_started = self._get_boot_time()
# Make reboot request
post_body = json.dumps({
'reboot' : {
'type' : 'SOFT',
}
})
url = "/servers/%s/action" % self.server_id
response, body = self.os.nova.request('POST', url, body=post_body)
self.assertEqual(response['status'], '202')
# Assert status transition
# KNOWN-ISSUE
#self.os.nova.wait_for_server_status(self.server_id, 'REBOOT')
ssh_client = self._get_ssh_client(self.server_password)
ssh_client.connect_until_closed()
self.os.nova.wait_for_server_status(self.server_id, 'ACTIVE')
# SSH and verify uptime is less than before
post_reboot_time_started = self._get_boot_time()
self.assertTrue(initial_time_started < post_reboot_time_started)
def test_reboot_server_hard(self):
"""Reboot a server (HARD)"""
# SSH and get the uptime
initial_time_started = self._get_boot_time()
# Make reboot request
post_body = json.dumps({
'reboot' : {
'type' : 'HARD',
}
})
url = "/servers/%s/action" % self.server_id
response, body = self.os.nova.request('POST', url, body=post_body)
self.assertEqual(response['status'], '202')
# Assert status transition
# KNOWN-ISSUE
#self.os.nova.wait_for_server_status(self.server_id, 'HARD_REBOOT')
ssh_client = self._get_ssh_client(self.server_password)
ssh_client.connect_until_closed()
self.os.nova.wait_for_server_status(self.server_id, 'ACTIVE')
# SSH and verify uptime is less than before
post_reboot_time_started = self._get_boot_time()
self.assertTrue(initial_time_started < post_reboot_time_started)
def test_change_server_password(self):
"""Change root password of a server"""
# SSH into server using original password
self._assert_ssh_password()
# Change server password
post_body = json.dumps({
'changePassword' : {
'adminPass' : 'test123',
}
})
url = '/servers/%s/action' % self.server_id
response, body = self.os.nova.request('POST', url, body=post_body)
# Assert status transition
self.assertEqual('202', response['status'])
# KNOWN-ISSUE
#self.os.nova.wait_for_server_status(self.server_id, 'PASSWORD')
self.os.nova.wait_for_server_status(self.server_id, 'ACTIVE')
# SSH into server using new password
self._assert_ssh_password('test123')
def test_rebuild_server(self):
"""Rebuild a server"""
filename = '/tmp/testfile'
contents = 'WORDS'
self._write_file(filename, contents)
self.assertEqual(self._read_file(filename), contents)
# Make rebuild request
post_body = json.dumps({
'rebuild' : {
'imageRef' : self.image_ref_alt,
}
})
url = '/servers/%s/action' % self.server_id
response, body = self.os.nova.request('POST', url, body=post_body)
# Ensure correct status transition
self.assertEqual('202', response['status'])
# KNOWN-ISSUE
#self.os.nova.wait_for_server_status(self.server_id, 'REBUILD')
self.os.nova.wait_for_server_status(self.server_id, 'BUILD')
self.os.nova.wait_for_server_status(self.server_id, 'ACTIVE')
# Treats an issue where we ssh'd in too soon after rebuild
time.sleep(30)
# Check that the instance's imageRef matches the new imageRef
server = self.os.nova.get_server(self.server_id)
ref_match = self.image_ref_alt == server['image']['links'][0]['href']
id_match = self.image_ref_alt == server['image']['id']
self.assertTrue(ref_match or id_match)
# SSH into the server to ensure it came back up
self._assert_ssh_password()
# make sure file is gone
self.assertEqual(self._read_file(filename), '')
@unittest.skipIf(not multi_node, 'Multiple compute nodes required')
def test_resize_server_confirm(self):
"""Resize a server"""
# Make resize request
post_body = json.dumps({
'resize' : {
'flavorRef': self.flavor_ref_alt,
}
})
url = '/servers/%s/action' % self.server_id
response, body = self.os.nova.request('POST', url, body=post_body)
# Wait for status transition
self.assertEqual('202', response['status'])
# KNOWN-ISSUE
#self.os.nova.wait_for_server_status(self.server_id, 'VERIFY_RESIZE')
self.os.nova.wait_for_server_status(self.server_id, 'RESIZE-CONFIRM')
# Ensure API reports new flavor
server = self.os.nova.get_server(self.server_id)
self.assertEqual(self.flavor_ref_alt, server['flavor']['id'])
#SSH into the server to ensure it came back up
self._assert_ssh_password()
# Make confirmResize request
post_body = json.dumps({
'confirmResize' : 'null'
})
url = '/servers/%s/action' % self.server_id
response, body = self.os.nova.request('POST', url, body=post_body)
# Wait for status transition
self.assertEqual('204', response['status'])
self.os.nova.wait_for_server_status(self.server_id, 'ACTIVE')
# Ensure API still reports new flavor
server = self.os.nova.get_server(self.server_id)
self.assertEqual(self.flavor_ref_alt, server['flavor']['id'])
@unittest.skipIf(not multi_node, 'Multiple compute nodes required')
def test_resize_server_revert(self):
"""Resize a server, then revert"""
# Make resize request
post_body = json.dumps({
'resize' : {
'flavorRef': self.flavor_ref_alt,
}
})
url = '/servers/%s/action' % self.server_id
response, body = self.os.nova.request('POST', url, body=post_body)
# Wait for status transition
self.assertEqual('202', response['status'])
# KNOWN-ISSUE
#self.os.nova.wait_for_server_status(self.server_id, 'VERIFY_RESIZE')
self.os.nova.wait_for_server_status(self.server_id, 'RESIZE-CONFIRM')
# SSH into the server to ensure it came back up
self._assert_ssh_password()
# Ensure API reports new flavor
server = self.os.nova.get_server(self.server_id)
self.assertEqual(self.flavor_ref_alt, server['flavor']['id'])
# Make revertResize request
post_body = json.dumps({
'revertResize' : 'null'
})
url = '/servers/%s/action' % self.server_id
response, body = self.os.nova.request('POST', url, body=post_body)
# Assert status transition
self.assertEqual('202', response['status'])
self.os.nova.wait_for_server_status(self.server_id, 'ACTIVE')
# Ensure flavor ref was reverted to original
server = self.os.nova.get_server(self.server_id)
self.assertEqual(self.flavor_ref, server['flavor']['id'])
class SnapshotTests(unittest.TestCase):
def setUp(self):
self.os = openstack.Manager()
self.image_ref = self.os.config.env.image_ref
self.flavor_ref = self.os.config.env.flavor_ref
self.ssh_timeout = self.os.config.nova.ssh_timeout
self.server_name = 'testserver'
expected_server = {
'name' : self.server_name,
'imageRef' : self.image_ref,
'flavorRef' : self.flavor_ref,
}
created_server = self.os.nova.create_server(expected_server)
self.server_id = created_server['id']
def tearDown(self):
self.os.nova.delete_server(self.server_id)
def _wait_for_status(self, server_id, status):
try:
self.os.nova.wait_for_server_status(server_id, status)
except exceptions.TimeoutException:
self.fail("Server failed to change status to %s" % status)
def test_snapshot_server_active(self):
"""Create image from an existing server"""
# Wait for server to come up before running this test
self._wait_for_status(self.server_id, 'ACTIVE')
# Create snapshot
image_data = {'name' : 'backup'}
req_body = json.dumps({'createImage': image_data})
url = '/servers/%s/action' % self.server_id
response, body = self.os.nova.request('POST', url, body=req_body)
print response
print body
self.assertEqual(response['status'], '202')
image_ref = response['location']
snapshot_id = image_ref.rsplit('/',1)[1]
# Get snapshot and check its attributes
resp, body = self.os.nova.request('GET', '/images/%s' % snapshot_id)
snapshot = json.loads(body)['image']
self.assertEqual(snapshot['name'], image_data['name'])
server_ref = snapshot['server']['links'][0]['href']
self.assertTrue(server_ref.endswith('/%s' % self.server_id))
# Ensure image is actually created
self.os.nova.wait_for_image_status(snapshot['id'], 'ACTIVE')
# Cleaning up
self.os.nova.request('DELETE', '/images/%s' % snapshot_id)
def test_snapshot_server_inactive(self):
"""Ensure inability to snapshot server in BUILD state"""
# Create snapshot
req_body = json.dumps({'createImage': {'name' : 'backup'}})
url = '/servers/%s/action' % self.server_id
response, body = self.os.nova.request('POST', url, body=req_body)
# KNOWN-ISSUE - we shouldn't be able to snapshot a building server
#self.assertEqual(response['status'], '400') # what status code?
self.assertEqual(response['status'], '202')
snapshot_id = response['location'].rsplit('/', 1)[1]
# Delete image for now, won't need this once correct status code is in
self.os.nova.request('DELETE', '/images/%s' % snapshot_id)
+63
View File
@@ -0,0 +1,63 @@
import json
import os
import unittest2 as unittest
from kong import openstack
from kong import exceptions
class ServerAddressesTest(unittest.TestCase):
@classmethod
def setUpClass(self):
self.os = openstack.Manager()
self.image_ref = self.os.config.env.image_ref
self.flavor_ref = self.os.config.env.flavor_ref
def setUp(self):
server = {
'name' : 'testserver',
'imageRef' : self.image_ref,
'flavorRef' : self.flavor_ref,
}
created_server = self.os.nova.create_server(server)
self.server_id = created_server['id']
self.os.nova.wait_for_server_status(self.server_id, 'ACTIVE')
def tearDown(self):
self.os.nova.delete_server(self.server_id)
def test_server_addresses(self):
"""Retrieve server addresses information"""
url = '/servers/%s' % self.server_id
response, body = self.os.nova.request('GET', url)
self.assertEqual(response.status, 200)
body = json.loads(body)
self.assertTrue('addresses' in body['server'].keys())
server_addresses = body['server']['addresses']
url = '/servers/%s/ips' % self.server_id
response, body = self.os.nova.request('GET', url)
self.assertEqual(response.status, 200)
body = json.loads(body)
self.assertEqual(body.keys(), ['addresses'])
ips_addresses = body['addresses']
self.assertEqual(server_addresses, ips_addresses)
# Now validate entities within addresses containers if available
for (network, network_data) in ips_addresses.items():
# Ensure we can query for each particular network
url = '/servers/%s/ips/%s' % (self.server_id, network)
response, body = self.os.nova.request('GET', url)
self.assertEqual(response.status, 200)
body = json.loads(body)
self.assertEqual(body.keys(), [network])
self.assertEqual(body[network], network_data)
for ip_data in network_data:
self.assertEqual(set(ip_data.keys()),
set(['addr', 'version']))
+173
View File
@@ -0,0 +1,173 @@
import json
import unittest2 as unittest
from kong import openstack
class ServersMetadataTest(unittest.TestCase):
@classmethod
def setUpClass(self):
self.os = openstack.Manager()
self.image_ref = self.os.config.env.image_ref
self.flavor_ref = self.os.config.env.flavor_ref
def setUp(self):
server = {
'name' : 'testserver',
'imageRef' : self.image_ref,
'flavorRef' : self.flavor_ref,
'metadata' : {
'testEntry' : 'testValue',
},
}
created_server = self.os.nova.create_server(server)
self.server_id = created_server['id']
def tearDown(self):
self.os.nova.delete_server(self.server_id)
def test_get_server_metadata(self):
"""Retrieve metadata for a server"""
url = '/servers/%s/metadata' % self.server_id
response, body = self.os.nova.request('GET', url)
self.assertEqual(200, response.status)
result = json.loads(body)
expected = {
'metadata' : {
'testEntry' : 'testValue',
},
}
self.assertEqual(expected, result)
def test_post_server_metadata(self):
"""Create or update metadata for a server"""
post_metadata = {
'metadata' : {
'new_entry1' : 'new_value1',
'new_entry2' : 'new_value2',
},
}
post_body = json.dumps(post_metadata)
url = '/servers/%s/metadata' % self.server_id
response, body = self.os.nova.request('POST', url, body=post_body)
self.assertEqual(200, response.status)
url = '/servers/%s/metadata' % self.server_id
response, body = self.os.nova.request('GET', url)
self.assertEqual(200, response.status)
result = json.loads(body)
expected = post_metadata
expected['metadata']['testEntry'] = 'testValue'
self.assertEqual(expected, result)
def test_put_server_metadata(self):
"""Overwrite all metadata for a server"""
expected = {
'metadata' : {
'new_entry1' : 'new_value1',
'new_entry2' : 'new_value2',
},
}
url = '/servers/%s/metadata' % self.server_id
post_body = json.dumps(expected)
response, body = self.os.nova.request('PUT', url, body=post_body)
self.assertEqual(200, response.status)
url = '/servers/%s/metadata' % self.server_id
response, body = self.os.nova.request('GET', url)
self.assertEqual(200, response.status)
result = json.loads(body)
# We want to make sure 'testEntry' was removed
self.assertEqual(expected, result)
def test_get_server_metadata_key(self):
"""Retrieve specific metadata key for a server"""
url = '/servers/%s/metadata/testEntry' % self.server_id
response, body = self.os.nova.request('GET', url)
self.assertEqual(200, response.status)
result = json.loads(body)
expected = {
'meta':{
'testEntry':'testValue',
},
}
self.assertDictEqual(expected, result)
def test_add_server_metadata_key(self):
"""Set specific metadata key on a server"""
expected_meta = {
'meta' : {
'new_meta1' : 'new_value1',
},
}
put_body = json.dumps(expected_meta)
url = '/servers/%s/metadata/new_meta1' % self.server_id
response, body = self.os.nova.request('PUT', url, body=put_body)
self.assertEqual(200, response.status)
result = json.loads(body)
self.assertDictEqual(expected_meta, result)
expected_metadata = {
'metadata' : {
'testEntry' : 'testValue',
'new_meta1' : 'new_value1',
},
}
# Now check all metadata to make sure the other values are there
url = '/servers/%s/metadata' % self.server_id
response, body = self.os.nova.request('GET', url)
result = json.loads(body)
self.assertDictEqual(expected_metadata, result)
def test_update_server_metadata_key(self):
"""Update specific metadata key for a server"""
expected_meta = {
'meta' : {
'testEntry' : 'testValue2',
},
}
put_body = json.dumps(expected_meta)
url = '/servers/%s/metadata/testEntry' % self.server_id
response, body = self.os.nova.request('PUT', url, body=put_body)
self.assertEqual(200, response.status)
result = json.loads(body)
self.assertEqual(expected_meta, result)
def test_delete_server_metadata_key(self):
"""Delete metadata for a server"""
url = '/servers/%s/metadata/testEntry' % self.server_id
response, body = self.os.nova.request('DELETE', url)
self.assertEquals(204, response.status)
url = '/servers/%s/metadata/testEntry' % self.server_id
response, body = self.os.nova.request('GET', url)
self.assertEquals(404, response.status)
url = '/servers/%s/metadata' % self.server_id
response, body = self.os.nova.request('GET', url)
self.assertEquals(200, response.status)
result = json.loads(body)
self.assertDictEqual({'metadata':{}}, result)
+373
View File
@@ -0,0 +1,373 @@
import base64
import json
import os
import unittest2 as unittest
from kong import openstack
from kong import exceptions
from kong.common import ssh
class ServersTest(unittest.TestCase):
@classmethod
def setUpClass(self):
self.os = openstack.Manager()
self.image_ref = self.os.config.env.image_ref
self.flavor_ref = self.os.config.env.flavor_ref
self.ssh_timeout = self.os.config.nova.ssh_timeout
self.build_timeout = self.os.config.nova.build_timeout
def _assert_server_entity(self, server):
actual_keys = set(server.keys())
expected_keys = set((
'id',
'name',
'hostId',
'status',
'metadata',
'addresses',
'links',
'progress',
'image',
'flavor',
'created',
'updated',
'accessIPv4',
'accessIPv6',
#KNOWN-ISSUE lp804093
'uuid',
))
self.assertTrue(expected_keys <= actual_keys)
server_id = str(server['id'])
host = self.os.config.nova.host
port = self.os.config.nova.port
api_url = '%s:%s' % (host, port)
base_url = os.path.join(api_url, self.os.config.nova.base_url)
self_link = 'http://' + os.path.join(base_url,
self.os.config.nova.project_id,
'servers', server_id)
bookmark_link = 'http://' + os.path.join(api_url,
self.os.config.nova.project_id,
'servers', server_id)
expected_links = [
{
'rel': 'self',
'href': self_link,
},
{
'rel': 'bookmark',
'href': bookmark_link,
},
]
self.assertEqual(server['links'], expected_links)
def test_build_server(self):
"""Build a server"""
expected_server = {
'name': 'testserver',
'metadata': {
'key1': 'value1',
'key2': 'value2',
},
'imageRef': self.image_ref,
'flavorRef': self.flavor_ref,
}
post_body = json.dumps({'server': expected_server})
response, body = self.os.nova.request('POST',
'/servers',
body=post_body)
self.assertEqual(response.status, 202)
_body = json.loads(body)
self.assertEqual(_body.keys(), ['server'])
created_server = _body['server']
admin_pass = created_server.pop('adminPass')
self._assert_server_entity(created_server)
self.assertEqual(expected_server['name'], created_server['name'])
self.assertEqual(expected_server['metadata'],
created_server['metadata'])
self.os.nova.wait_for_server_status(created_server['id'],
'ACTIVE',
timeout=self.build_timeout)
server = self.os.nova.get_server(created_server['id'])
# Find IP of server
try:
(_, network) = server['addresses'].popitem()
ip = network[0]['addr']
except KeyError:
self.fail("Failed to retrieve IP address from server entity")
# Assert password works
client = ssh.Client(ip, 'root', admin_pass, self.ssh_timeout)
self.assertTrue(client.test_connection_auth())
self.os.nova.delete_server(server['id'])
def test_build_server_with_file(self):
"""Build a server with an injected file"""
file_contents = 'testing'
expected_server = {
'name': 'testserver',
'metadata': {
'key1': 'value1',
'key2': 'value2',
},
'personality': [
{
'path': '/etc/test.txt',
'contents': base64.b64encode(file_contents),
},
],
'imageRef': self.image_ref,
'flavorRef': self.flavor_ref,
}
post_body = json.dumps({'server': expected_server})
response, body = self.os.nova.request('POST',
'/servers',
body=post_body)
self.assertEqual(response.status, 202)
_body = json.loads(body)
self.assertEqual(_body.keys(), ['server'])
created_server = _body['server']
admin_pass = created_server.pop('adminPass', None)
self._assert_server_entity(created_server)
self.assertEqual(expected_server['name'], created_server['name'])
self.assertEqual(expected_server['metadata'],
created_server['metadata'])
self.os.nova.wait_for_server_status(created_server['id'],
'ACTIVE',
timeout=self.build_timeout)
server = self.os.nova.get_server(created_server['id'])
# Find IP of server
try:
(_, network) = server['addresses'].popitem()
ip = network[0]['addr']
except KeyError:
self.fail("Failed to retrieve IP address from server entity")
# Assert injected file is on instance, also verifying password works
client = ssh.Client(ip, 'root', admin_pass, self.ssh_timeout)
injected_file = client.exec_command('cat /etc/test.txt')
self.assertEqual(injected_file, file_contents)
self.os.nova.delete_server(server['id'])
def test_build_server_with_password(self):
"""Build a server with a password"""
server_password = 'testpwd'
expected_server = {
'name': 'testserver',
'metadata': {
'key1': 'value1',
'key2': 'value2',
},
'adminPass': server_password,
'imageRef': self.image_ref,
'flavorRef': self.flavor_ref,
}
post_body = json.dumps({'server': expected_server})
response, body = self.os.nova.request('POST',
'/servers',
body=post_body)
self.assertEqual(response.status, 202)
_body = json.loads(body)
self.assertEqual(_body.keys(), ['server'])
created_server = _body['server']
admin_pass = created_server.pop('adminPass', None)
self._assert_server_entity(created_server)
self.assertEqual(expected_server['name'], created_server['name'])
self.assertEqual(expected_server['adminPass'], admin_pass)
self.assertEqual(expected_server['metadata'],
created_server['metadata'])
self.os.nova.wait_for_server_status(created_server['id'],
'ACTIVE',
timeout=self.build_timeout)
server = self.os.nova.get_server(created_server['id'])
# Find IP of server
try:
(_, network) = server['addresses'].popitem()
ip = network[0]['addr']
except KeyError:
self.fail("Failed to retrieve IP address from server entity")
# Assert password was set to that in request
client = ssh.Client(ip, 'root', server_password, self.ssh_timeout)
self.assertTrue(client.test_connection_auth())
self.os.nova.delete_server(server['id'])
def test_delete_server_building(self):
"""Delete a server while building"""
# Make create server request
server = {
'name' : 'testserver',
'imageRef' : self.image_ref,
'flavorRef' : self.flavor_ref,
}
created_server = self.os.nova.create_server(server)
# Server should immediately be accessible, but in have building status
server = self.os.nova.get_server(created_server['id'])
self.assertEqual(server['status'], 'BUILD')
self.os.nova.delete_server(created_server['id'])
# Poll server until deleted
try:
url = '/servers/%s' % created_server['id']
self.os.nova.poll_request_status('GET', url, 404)
except exceptions.TimeoutException:
self.fail("Server deletion timed out")
def test_delete_server_active(self):
"""Delete a server after fully built"""
expected_server = {
'name' : 'testserver',
'imageRef' : self.image_ref,
'flavorRef' : self.flavor_ref,
}
created_server = self.os.nova.create_server(expected_server)
server_id = created_server['id']
self.os.nova.wait_for_server_status(server_id,
'ACTIVE',
timeout=self.build_timeout)
self.os.nova.delete_server(server_id)
# Poll server until deleted
try:
url = '/servers/%s' % server_id
self.os.nova.poll_request_status('GET', url, 404)
except exceptions.TimeoutException:
self.fail("Server deletion timed out")
def test_update_server_name(self):
"""Change the name of a server"""
expected_server = {
'name' : 'testserver',
'imageRef' : self.image_ref,
'flavorRef' : self.flavor_ref,
}
created_server = self.os.nova.create_server(expected_server)
self.assertTrue(expected_server['name'], created_server['name'])
server_id = created_server['id']
# Wait for it to be built
self.os.nova.wait_for_server_status(server_id,
'ACTIVE',
timeout=self.build_timeout)
# Update name
new_server = {'name': 'updatedtestserver'}
put_body = json.dumps({
'server': new_server,
})
url = '/servers/%s' % server_id
resp, body = self.os.nova.request('PUT', url, body=put_body)
self.assertEqual(resp.status, 200)
data = json.loads(body)
self.assertEqual(data.keys(), ['server'])
self._assert_server_entity(data['server'])
self.assertEqual('updatedtestserver', data['server']['name'])
# Get Server information
resp, body = self.os.nova.request('GET', '/servers/%s' % server_id)
self.assertEqual(200, resp.status)
data = json.loads(body)
self.assertEqual(data.keys(), ['server'])
self._assert_server_entity(data['server'])
self.assertEqual('updatedtestserver', data['server']['name'])
self.os.nova.delete_server(server_id)
def test_create_server_invalid_image(self):
"""Create a server with an unknown image"""
post_body = json.dumps({
'server' : {
'name' : 'testserver',
'imageRef' : -1,
'flavorRef' : self.flavor_ref,
}
})
resp, body = self.os.nova.request('POST', '/servers', body=post_body)
self.assertEqual(400, resp.status)
fault = json.loads(body)
expected_fault = {
"badRequest": {
"message": "Cannot find requested image",
"code": 400,
},
}
# KNOWN-ISSUE - The error message is confusing and should be improved
#self.assertEqual(fault, expected_fault)
def test_create_server_invalid_flavor(self):
"""Create a server with an unknown flavor"""
post_body = json.dumps({
'server' : {
'name' : 'testserver',
'imageRef' : self.image_ref,
'flavorRef' : -1,
}
})
resp, body = self.os.nova.request('POST', '/servers', body=post_body)
self.assertEqual(400, resp.status)
fault = json.loads(body)
expected_fault = {
"badRequest": {
"message": "Cannot find requested flavor",
"code": 400,
},
}
# KNOWN-ISSUE lp804084
#self.assertEqual(fault, expected_fault)