Import all the stacktester stuff as-is (s/stacktester/kong/, though).
diff --git a/kong/common/__init__.py b/kong/common/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/kong/common/__init__.py
diff --git a/kong/common/http.py b/kong/common/http.py
new file mode 100644
index 0000000..47ac058
--- /dev/null
+++ b/kong/common/http.py
@@ -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
diff --git a/kong/common/ssh.py b/kong/common/ssh.py
new file mode 100644
index 0000000..f650e64
--- /dev/null
+++ b/kong/common/ssh.py
@@ -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
diff --git a/kong/config.py b/kong/config.py
new file mode 100644
index 0000000..5c3771f
--- /dev/null
+++ b/kong/config.py
@@ -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
diff --git a/kong/exceptions.py b/kong/exceptions.py
new file mode 100644
index 0000000..214f43c
--- /dev/null
+++ b/kong/exceptions.py
@@ -0,0 +1,9 @@
+
+class TimeoutException(Exception):
+    """ Exception on timeout """
+    def __repr__(self):
+        return "Request Timed Out"
+
+
+class ServerNotFound(KeyError):
+    pass
diff --git a/kong/issues.py b/kong/issues.py
new file mode 100644
index 0000000..59f45b4
--- /dev/null
+++ b/kong/issues.py
@@ -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
diff --git a/kong/nova.py b/kong/nova.py
new file mode 100644
index 0000000..41b4b4e
--- /dev/null
+++ b/kong/nova.py
@@ -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)
diff --git a/kong/openstack.py b/kong/openstack.py
new file mode 100644
index 0000000..3575a8c
--- /dev/null
+++ b/kong/openstack.py
@@ -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)
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)