Import all the stacktester stuff as-is (s/stacktester/kong/, though).
diff --git a/kong/tests/test_flavors.py b/kong/tests/test_flavors.py
new file mode 100644
index 0000000..c717530
--- /dev/null
+++ b/kong/tests/test_flavors.py
@@ -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)
diff --git a/kong/tests/test_images.py b/kong/tests/test_images.py
new file mode 100644
index 0000000..2b8c19e
--- /dev/null
+++ b/kong/tests/test_images.py
@@ -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)
diff --git a/kong/tests/test_server_actions.py b/kong/tests/test_server_actions.py
new file mode 100644
index 0000000..7e58db7
--- /dev/null
+++ b/kong/tests/test_server_actions.py
@@ -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)
diff --git a/kong/tests/test_server_addresses.py b/kong/tests/test_server_addresses.py
new file mode 100644
index 0000000..cc18974
--- /dev/null
+++ b/kong/tests/test_server_addresses.py
@@ -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']))
diff --git a/kong/tests/test_server_meta.py b/kong/tests/test_server_meta.py
new file mode 100644
index 0000000..226e0fb
--- /dev/null
+++ b/kong/tests/test_server_meta.py
@@ -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)
diff --git a/kong/tests/test_servers.py b/kong/tests/test_servers.py
new file mode 100644
index 0000000..c16b514
--- /dev/null
+++ b/kong/tests/test_servers.py
@@ -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)