Import all the stacktester stuff as-is (s/stacktester/kong/, though).
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)