Merge "port test_images and test_server_actions into v3 part1"
diff --git a/tempest/api/compute/v3/images/test_images.py b/tempest/api/compute/v3/images/test_images.py
new file mode 100644
index 0000000..383ea1d
--- /dev/null
+++ b/tempest/api/compute/v3/images/test_images.py
@@ -0,0 +1,172 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack Foundation
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from tempest.api import compute
+from tempest.api.compute import base
+from tempest import clients
+from tempest.common.utils.data_utils import parse_image_id
+from tempest.common.utils.data_utils import rand_name
+from tempest import exceptions
+from tempest.test import attr
+
+
+class ImagesTestJSON(base.BaseV2ComputeTest):
+    _interface = 'json'
+
+    @classmethod
+    def setUpClass(cls):
+        super(ImagesTestJSON, cls).setUpClass()
+        if not cls.config.service_available.glance:
+            skip_msg = ("%s skipped as glance is not available" % cls.__name__)
+            raise cls.skipException(skip_msg)
+        cls.client = cls.images_client
+        cls.servers_client = cls.servers_client
+
+        cls.image_ids = []
+
+        if compute.MULTI_USER:
+            if cls.config.compute.allow_tenant_isolation:
+                creds = cls.isolated_creds.get_alt_creds()
+                username, tenant_name, password = creds
+                cls.alt_manager = clients.Manager(username=username,
+                                                  password=password,
+                                                  tenant_name=tenant_name)
+            else:
+                # Use the alt_XXX credentials in the config file
+                cls.alt_manager = clients.AltManager()
+            cls.alt_client = cls.alt_manager.images_client
+
+    def tearDown(self):
+        """Terminate test instances created after a test is executed."""
+        for image_id in self.image_ids:
+            self.client.delete_image(image_id)
+            self.image_ids.remove(image_id)
+        super(ImagesTestJSON, self).tearDown()
+
+    def __create_image__(self, server_id, name, meta=None):
+        resp, body = self.client.create_image(server_id, name, meta)
+        image_id = parse_image_id(resp['location'])
+        self.client.wait_for_image_status(image_id, 'ACTIVE')
+        self.image_ids.append(image_id)
+        return resp, body
+
+    @attr(type=['negative', 'gate'])
+    def test_create_image_from_deleted_server(self):
+        # An image should not be created if the server instance is removed
+        resp, server = self.create_server(wait_until='ACTIVE')
+
+        # Delete server before trying to create server
+        self.servers_client.delete_server(server['id'])
+        self.servers_client.wait_for_server_termination(server['id'])
+        # Create a new image after server is deleted
+        name = rand_name('image')
+        meta = {'image_type': 'test'}
+        self.assertRaises(exceptions.NotFound,
+                          self.__create_image__,
+                          server['id'], name, meta)
+
+    @attr(type=['negative', 'gate'])
+    def test_create_image_from_invalid_server(self):
+        # An image should not be created with invalid server id
+        # Create a new image with invalid server id
+        name = rand_name('image')
+        meta = {'image_type': 'test'}
+        resp = {}
+        resp['status'] = None
+        self.assertRaises(exceptions.NotFound, self.__create_image__,
+                          '!@#$%^&*()', name, meta)
+
+    @attr(type=['negative', 'gate'])
+    def test_create_image_from_stopped_server(self):
+        resp, server = self.create_server(wait_until='ACTIVE')
+        self.servers_client.stop(server['id'])
+        self.servers_client.wait_for_server_status(server['id'],
+                                                   'SHUTOFF')
+        self.addCleanup(self.servers_client.delete_server, server['id'])
+        snapshot_name = rand_name('test-snap-')
+        resp, image = self.create_image_from_server(server['id'],
+                                                    name=snapshot_name,
+                                                    wait_until='ACTIVE')
+        self.addCleanup(self.client.delete_image, image['id'])
+        self.assertEqual(snapshot_name, image['name'])
+
+    @attr(type='gate')
+    def test_delete_saving_image(self):
+        snapshot_name = rand_name('test-snap-')
+        resp, server = self.create_server(wait_until='ACTIVE')
+        self.addCleanup(self.servers_client.delete_server, server['id'])
+        resp, image = self.create_image_from_server(server['id'],
+                                                    name=snapshot_name,
+                                                    wait_until='SAVING')
+        resp, body = self.client.delete_image(image['id'])
+        self.assertEqual('204', resp['status'])
+
+    @attr(type=['negative', 'gate'])
+    def test_create_image_specify_uuid_35_characters_or_less(self):
+        # Return an error if Image ID passed is 35 characters or less
+        snapshot_name = rand_name('test-snap-')
+        test_uuid = ('a' * 35)
+        self.assertRaises(exceptions.NotFound, self.client.create_image,
+                          test_uuid, snapshot_name)
+
+    @attr(type=['negative', 'gate'])
+    def test_create_image_specify_uuid_37_characters_or_more(self):
+        # Return an error if Image ID passed is 37 characters or more
+        snapshot_name = rand_name('test-snap-')
+        test_uuid = ('a' * 37)
+        self.assertRaises(exceptions.NotFound, self.client.create_image,
+                          test_uuid, snapshot_name)
+
+    @attr(type=['negative', 'gate'])
+    def test_delete_image_with_invalid_image_id(self):
+        # An image should not be deleted with invalid image id
+        self.assertRaises(exceptions.NotFound, self.client.delete_image,
+                          '!@$%^&*()')
+
+    @attr(type=['negative', 'gate'])
+    def test_delete_non_existent_image(self):
+        # Return an error while trying to delete a non-existent image
+
+        non_existent_image_id = '11a22b9-12a9-5555-cc11-00ab112223fa'
+        self.assertRaises(exceptions.NotFound, self.client.delete_image,
+                          non_existent_image_id)
+
+    @attr(type=['negative', 'gate'])
+    def test_delete_image_blank_id(self):
+        # Return an error while trying to delete an image with blank Id
+        self.assertRaises(exceptions.NotFound, self.client.delete_image, '')
+
+    @attr(type=['negative', 'gate'])
+    def test_delete_image_non_hex_string_id(self):
+        # Return an error while trying to delete an image with non hex id
+        image_id = '11a22b9-120q-5555-cc11-00ab112223gj'
+        self.assertRaises(exceptions.NotFound, self.client.delete_image,
+                          image_id)
+
+    @attr(type=['negative', 'gate'])
+    def test_delete_image_negative_image_id(self):
+        # Return an error while trying to delete an image with negative id
+        self.assertRaises(exceptions.NotFound, self.client.delete_image, -1)
+
+    @attr(type=['negative', 'gate'])
+    def test_delete_image_id_is_over_35_character_limit(self):
+        # Return an error while trying to delete image with id over limit
+        self.assertRaises(exceptions.NotFound, self.client.delete_image,
+                          '11a22b9-12a9-5555-cc11-00ab112223fa-3fac')
+
+
+class ImagesTestXML(ImagesTestJSON):
+    _interface = 'xml'
diff --git a/tempest/api/compute/v3/servers/test_server_actions.py b/tempest/api/compute/v3/servers/test_server_actions.py
new file mode 100644
index 0000000..961737a
--- /dev/null
+++ b/tempest/api/compute/v3/servers/test_server_actions.py
@@ -0,0 +1,278 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import base64
+import time
+
+import testtools
+
+from tempest.api import compute
+from tempest.api.compute import base
+from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils.linux.remote_client import RemoteClient
+import tempest.config
+from tempest import exceptions
+from tempest.test import attr
+from tempest.test import skip_because
+
+
+class ServerActionsTestJSON(base.BaseV2ComputeTest):
+    _interface = 'json'
+    resize_available = tempest.config.TempestConfig().\
+        compute_feature_enabled.resize
+    run_ssh = tempest.config.TempestConfig().compute.run_ssh
+
+    def setUp(self):
+        # NOTE(afazekas): Normally we use the same server with all test cases,
+        # but if it has an issue, we build a new one
+        super(ServerActionsTestJSON, self).setUp()
+        # Check if the server is in a clean state after test
+        try:
+            self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+        except Exception:
+            # Rebuild server if something happened to it during a test
+            self.rebuild_server()
+
+    @classmethod
+    def setUpClass(cls):
+        super(ServerActionsTestJSON, cls).setUpClass()
+        cls.client = cls.servers_client
+        cls.rebuild_server()
+
+    @testtools.skipUnless(compute.CHANGE_PASSWORD_AVAILABLE,
+                          'Change password not available.')
+    @attr(type='gate')
+    def test_change_server_password(self):
+        # The server's password should be set to the provided password
+        new_password = 'Newpass1234'
+        resp, body = self.client.change_password(self.server_id, new_password)
+        self.assertEqual(202, resp.status)
+        self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+        if self.run_ssh:
+            # Verify that the user can authenticate with the new password
+            resp, server = self.client.get_server(self.server_id)
+            linux_client = RemoteClient(server, self.ssh_user, new_password)
+            self.assertTrue(linux_client.can_authenticate())
+
+    @attr(type='smoke')
+    def test_reboot_server_hard(self):
+        # The server should be power cycled
+        if self.run_ssh:
+            # Get the time the server was last rebooted,
+            resp, server = self.client.get_server(self.server_id)
+            linux_client = RemoteClient(server, self.ssh_user, self.password)
+            boot_time = linux_client.get_boot_time()
+
+        resp, body = self.client.reboot(self.server_id, 'HARD')
+        self.assertEqual(202, resp.status)
+        self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+        if self.run_ssh:
+            # Log in and verify the boot time has changed
+            linux_client = RemoteClient(server, self.ssh_user, self.password)
+            new_boot_time = linux_client.get_boot_time()
+            self.assertGreater(new_boot_time, boot_time)
+
+    @skip_because(bug="1014647")
+    @attr(type='smoke')
+    def test_reboot_server_soft(self):
+        # The server should be signaled to reboot gracefully
+        if self.run_ssh:
+            # Get the time the server was last rebooted,
+            resp, server = self.client.get_server(self.server_id)
+            linux_client = RemoteClient(server, self.ssh_user, self.password)
+            boot_time = linux_client.get_boot_time()
+
+        resp, body = self.client.reboot(self.server_id, 'SOFT')
+        self.assertEqual(202, resp.status)
+        self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+        if self.run_ssh:
+            # Log in and verify the boot time has changed
+            linux_client = RemoteClient(server, self.ssh_user, self.password)
+            new_boot_time = linux_client.get_boot_time()
+            self.assertGreater(new_boot_time, boot_time)
+
+    @attr(type='smoke')
+    def test_rebuild_server(self):
+        # The server should be rebuilt using the provided image and data
+        meta = {'rebuild': 'server'}
+        new_name = rand_name('server')
+        file_contents = 'Test server rebuild.'
+        personality = [{'path': 'rebuild.txt',
+                       'contents': base64.b64encode(file_contents)}]
+        password = 'rebuildPassw0rd'
+        resp, rebuilt_server = self.client.rebuild(self.server_id,
+                                                   self.image_ref_alt,
+                                                   name=new_name,
+                                                   metadata=meta,
+                                                   personality=personality,
+                                                   adminPass=password)
+
+        # Verify the properties in the initial response are correct
+        self.assertEqual(self.server_id, rebuilt_server['id'])
+        rebuilt_image_id = rebuilt_server['image']['id']
+        self.assertTrue(self.image_ref_alt.endswith(rebuilt_image_id))
+        self.assertEqual(self.flavor_ref, int(rebuilt_server['flavor']['id']))
+
+        # Verify the server properties after the rebuild completes
+        self.client.wait_for_server_status(rebuilt_server['id'], 'ACTIVE')
+        resp, server = self.client.get_server(rebuilt_server['id'])
+        rebuilt_image_id = rebuilt_server['image']['id']
+        self.assertTrue(self.image_ref_alt.endswith(rebuilt_image_id))
+        self.assertEqual(new_name, rebuilt_server['name'])
+
+        if self.run_ssh:
+            # Verify that the user can authenticate with the provided password
+            linux_client = RemoteClient(server, self.ssh_user, password)
+            self.assertTrue(linux_client.can_authenticate())
+
+    def _detect_server_image_flavor(self, server_id):
+        # Detects the current server image flavor ref.
+        resp, server = self.client.get_server(self.server_id)
+        current_flavor = server['flavor']['id']
+        new_flavor_ref = self.flavor_ref_alt \
+            if int(current_flavor) == self.flavor_ref else self.flavor_ref
+        return int(current_flavor), int(new_flavor_ref)
+
+    @testtools.skipIf(not resize_available, 'Resize not available.')
+    @attr(type='smoke')
+    def test_resize_server_confirm(self):
+        # The server's RAM and disk space should be modified to that of
+        # the provided flavor
+
+        previous_flavor_ref, new_flavor_ref = \
+            self._detect_server_image_flavor(self.server_id)
+
+        resp, server = self.client.resize(self.server_id, new_flavor_ref)
+        self.assertEqual(202, resp.status)
+        self.client.wait_for_server_status(self.server_id, 'VERIFY_RESIZE')
+
+        self.client.confirm_resize(self.server_id)
+        self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+        resp, server = self.client.get_server(self.server_id)
+        self.assertEqual(new_flavor_ref, int(server['flavor']['id']))
+
+    @testtools.skipIf(not resize_available, 'Resize not available.')
+    @attr(type='gate')
+    def test_resize_server_revert(self):
+        # The server's RAM and disk space should return to its original
+        # values after a resize is reverted
+
+        previous_flavor_ref, new_flavor_ref = \
+            self._detect_server_image_flavor(self.server_id)
+
+        resp, server = self.client.resize(self.server_id, new_flavor_ref)
+        self.assertEqual(202, resp.status)
+        self.client.wait_for_server_status(self.server_id, 'VERIFY_RESIZE')
+
+        self.client.revert_resize(self.server_id)
+        self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+        # Need to poll for the id change until lp#924371 is fixed
+        resp, server = self.client.get_server(self.server_id)
+        start = int(time.time())
+
+        while int(server['flavor']['id']) != previous_flavor_ref:
+            time.sleep(self.build_interval)
+            resp, server = self.client.get_server(self.server_id)
+
+            if int(time.time()) - start >= self.build_timeout:
+                message = 'Server %s failed to revert resize within the \
+                required time (%s s).' % (self.server_id, self.build_timeout)
+                raise exceptions.TimeoutException(message)
+
+    @attr(type='gate')
+    def test_get_console_output(self):
+        # Positive test:Should be able to GET the console output
+        # for a given server_id and number of lines
+        def get_output():
+            resp, output = self.servers_client.get_console_output(
+                self.server_id, 10)
+            self.assertEqual(200, resp.status)
+            self.assertTrue(output, "Console output was empty.")
+            lines = len(output.split('\n'))
+            self.assertEqual(lines, 10)
+        self.wait_for(get_output)
+
+    @skip_because(bug="1014683")
+    @attr(type='gate')
+    def test_get_console_output_server_id_in_reboot_status(self):
+        # Positive test:Should be able to GET the console output
+        # for a given server_id in reboot status
+        resp, output = self.servers_client.reboot(self.server_id, 'SOFT')
+        self.servers_client.wait_for_server_status(self.server_id,
+                                                   'REBOOT')
+        resp, output = self.servers_client.get_console_output(self.server_id,
+                                                              10)
+        self.assertEqual(200, resp.status)
+        self.assertIsNotNone(output)
+        lines = len(output.split('\n'))
+        self.assertEqual(lines, 10)
+
+    @attr(type='gate')
+    def test_pause_unpause_server(self):
+        resp, server = self.client.pause_server(self.server_id)
+        self.assertEqual(202, resp.status)
+        self.client.wait_for_server_status(self.server_id, 'PAUSED')
+        resp, server = self.client.unpause_server(self.server_id)
+        self.assertEqual(202, resp.status)
+        self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+    @attr(type='gate')
+    def test_suspend_resume_server(self):
+        resp, server = self.client.suspend_server(self.server_id)
+        self.assertEqual(202, resp.status)
+        self.client.wait_for_server_status(self.server_id, 'SUSPENDED')
+        resp, server = self.client.resume_server(self.server_id)
+        self.assertEqual(202, resp.status)
+        self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+    @attr(type='gate')
+    def test_stop_start_server(self):
+        resp, server = self.servers_client.stop(self.server_id)
+        self.assertEqual(202, resp.status)
+        self.servers_client.wait_for_server_status(self.server_id, 'SHUTOFF')
+        resp, server = self.servers_client.start(self.server_id)
+        self.assertEqual(202, resp.status)
+        self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+    @attr(type='gate')
+    def test_lock_unlock_server(self):
+        # Lock the server,try server stop(exceptions throw),unlock it and retry
+        resp, server = self.servers_client.lock_server(self.server_id)
+        self.assertEqual(202, resp.status)
+        resp, server = self.servers_client.get_server(self.server_id)
+        self.assertEqual(200, resp.status)
+        self.assertEqual(server['status'], 'ACTIVE')
+        # Locked server is not allowed to be stopped by non-admin user
+        self.assertRaises(exceptions.Conflict,
+                          self.servers_client.stop, self.server_id)
+        resp, server = self.servers_client.unlock_server(self.server_id)
+        self.assertEqual(202, resp.status)
+        resp, server = self.servers_client.stop(self.server_id)
+        self.assertEqual(202, resp.status)
+        self.servers_client.wait_for_server_status(self.server_id, 'SHUTOFF')
+        resp, server = self.servers_client.start(self.server_id)
+        self.assertEqual(202, resp.status)
+        self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+
+class ServerActionsTestXML(ServerActionsTestJSON):
+    _interface = 'xml'
diff --git a/tempest/services/compute/v3/json/servers_client.py b/tempest/services/compute/v3/json/servers_client.py
new file mode 100644
index 0000000..07bb6ce
--- /dev/null
+++ b/tempest/services/compute/v3/json/servers_client.py
@@ -0,0 +1,392 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack Foundation
+# Copyright 2013 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import json
+import time
+import urllib
+
+from tempest.common.rest_client import RestClient
+from tempest.common import waiters
+from tempest import exceptions
+
+
+class ServersClientJSON(RestClient):
+
+    def __init__(self, config, username, password, auth_url, tenant_name=None,
+                 auth_version='v2'):
+        super(ServersClientJSON, self).__init__(config, username, password,
+                                                auth_url, tenant_name,
+                                                auth_version=auth_version)
+        self.service = self.config.compute.catalog_type
+
+    def create_server(self, name, image_ref, flavor_ref, **kwargs):
+        """
+        Creates an instance of a server.
+        name (Required): The name of the server.
+        image_ref (Required): Reference to the image used to build the server.
+        flavor_ref (Required): The flavor used to build the server.
+        Following optional keyword arguments are accepted:
+        adminPass: Sets the initial root password.
+        key_name: Key name of keypair that was created earlier.
+        meta: A dictionary of values to be used as metadata.
+        personality: A list of dictionaries for files to be injected into
+        the server.
+        security_groups: A list of security group dicts.
+        networks: A list of network dicts with UUID and fixed_ip.
+        user_data: User data for instance.
+        availability_zone: Availability zone in which to launch instance.
+        accessIPv4: The IPv4 access address for the server.
+        accessIPv6: The IPv6 access address for the server.
+        min_count: Count of minimum number of instances to launch.
+        max_count: Count of maximum number of instances to launch.
+        disk_config: Determines if user or admin controls disk configuration.
+        return_reservation_id: Enable/Disable the return of reservation id
+        """
+        post_body = {
+            'name': name,
+            'imageRef': image_ref,
+            'flavorRef': flavor_ref
+        }
+
+        for option in ['personality', 'adminPass', 'key_name',
+                       'security_groups', 'networks', 'user_data',
+                       'availability_zone', 'accessIPv4', 'accessIPv6',
+                       'min_count', 'max_count', ('metadata', 'meta'),
+                       ('OS-DCF:diskConfig', 'disk_config'),
+                       'return_reservation_id']:
+            if isinstance(option, tuple):
+                post_param = option[0]
+                key = option[1]
+            else:
+                post_param = option
+                key = option
+            value = kwargs.get(key)
+            if value is not None:
+                post_body[post_param] = value
+        post_body = json.dumps({'server': post_body})
+        resp, body = self.post('servers', post_body, self.headers)
+
+        body = json.loads(body)
+        # NOTE(maurosr): this deals with the case of multiple server create
+        # with return reservation id set True
+        if 'reservation_id' in body:
+            return resp, body
+        return resp, body['server']
+
+    def update_server(self, server_id, name=None, meta=None, accessIPv4=None,
+                      accessIPv6=None, disk_config=None):
+        """
+        Updates the properties of an existing server.
+        server_id: The id of an existing server.
+        name: The name of the server.
+        personality: A list of files to be injected into the server.
+        accessIPv4: The IPv4 access address for the server.
+        accessIPv6: The IPv6 access address for the server.
+        """
+
+        post_body = {}
+
+        if meta is not None:
+            post_body['metadata'] = meta
+
+        if name is not None:
+            post_body['name'] = name
+
+        if accessIPv4 is not None:
+            post_body['accessIPv4'] = accessIPv4
+
+        if accessIPv6 is not None:
+            post_body['accessIPv6'] = accessIPv6
+
+        if disk_config is not None:
+            post_body['OS-DCF:diskConfig'] = disk_config
+
+        post_body = json.dumps({'server': post_body})
+        resp, body = self.put("servers/%s" % str(server_id),
+                              post_body, self.headers)
+        body = json.loads(body)
+        return resp, body['server']
+
+    def get_server(self, server_id):
+        """Returns the details of an existing server."""
+        resp, body = self.get("servers/%s" % str(server_id))
+        body = json.loads(body)
+        return resp, body['server']
+
+    def delete_server(self, server_id):
+        """Deletes the given server."""
+        return self.delete("servers/%s" % str(server_id))
+
+    def list_servers(self, params=None):
+        """Lists all servers for a user."""
+
+        url = 'servers'
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+
+        resp, body = self.get(url)
+        body = json.loads(body)
+        return resp, body
+
+    def list_servers_with_detail(self, params=None):
+        """Lists all servers in detail for a user."""
+
+        url = 'servers/detail'
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+
+        resp, body = self.get(url)
+        body = json.loads(body)
+        return resp, body
+
+    def wait_for_server_status(self, server_id, status):
+        """Waits for a server to reach a given status."""
+        return waiters.wait_for_server_status(self, server_id, status)
+
+    def wait_for_server_termination(self, server_id, ignore_error=False):
+        """Waits for server to reach termination."""
+        start_time = int(time.time())
+        while True:
+            try:
+                resp, body = self.get_server(server_id)
+            except exceptions.NotFound:
+                return
+
+            server_status = body['status']
+            if server_status == 'ERROR' and not ignore_error:
+                raise exceptions.BuildErrorException(server_id=server_id)
+
+            if int(time.time()) - start_time >= self.build_timeout:
+                raise exceptions.TimeoutException
+
+            time.sleep(self.build_interval)
+
+    def list_addresses(self, server_id):
+        """Lists all addresses for a server."""
+        resp, body = self.get("servers/%s/ips" % str(server_id))
+        body = json.loads(body)
+        return resp, body['addresses']
+
+    def list_addresses_by_network(self, server_id, network_id):
+        """Lists all addresses of a specific network type for a server."""
+        resp, body = self.get("servers/%s/ips/%s" %
+                              (str(server_id), network_id))
+        body = json.loads(body)
+        return resp, body
+
+    def action(self, server_id, action_name, response_key, **kwargs):
+        post_body = json.dumps({action_name: kwargs})
+        resp, body = self.post('servers/%s/action' % str(server_id),
+                               post_body, self.headers)
+        if response_key is not None:
+            body = json.loads(body)[response_key]
+        return resp, body
+
+    def change_password(self, server_id, adminPass):
+        """Changes the root password for the server."""
+        return self.action(server_id, 'changePassword', None,
+                           adminPass=adminPass)
+
+    def reboot(self, server_id, reboot_type):
+        """Reboots a server."""
+        return self.action(server_id, 'reboot', None, type=reboot_type)
+
+    def rebuild(self, server_id, image_ref, **kwargs):
+        """Rebuilds a server with a new image."""
+        kwargs['imageRef'] = image_ref
+        if 'disk_config' in kwargs:
+            kwargs['OS-DCF:diskConfig'] = kwargs['disk_config']
+            del kwargs['disk_config']
+        return self.action(server_id, 'rebuild', 'server', **kwargs)
+
+    def resize(self, server_id, flavor_ref, **kwargs):
+        """Changes the flavor of a server."""
+        kwargs['flavorRef'] = flavor_ref
+        if 'disk_config' in kwargs:
+            kwargs['OS-DCF:diskConfig'] = kwargs['disk_config']
+            del kwargs['disk_config']
+        return self.action(server_id, 'resize', None, **kwargs)
+
+    def confirm_resize(self, server_id, **kwargs):
+        """Confirms the flavor change for a server."""
+        return self.action(server_id, 'confirmResize', None, **kwargs)
+
+    def revert_resize(self, server_id, **kwargs):
+        """Reverts a server back to its original flavor."""
+        return self.action(server_id, 'revertResize', None, **kwargs)
+
+    def create_image(self, server_id, name):
+        """Creates an image of the given server."""
+        return self.action(server_id, 'createImage', None, name=name)
+
+    def list_server_metadata(self, server_id):
+        resp, body = self.get("servers/%s/metadata" % str(server_id))
+        body = json.loads(body)
+        return resp, body['metadata']
+
+    def set_server_metadata(self, server_id, meta, no_metadata_field=False):
+        if no_metadata_field:
+            post_body = ""
+        else:
+            post_body = json.dumps({'metadata': meta})
+        resp, body = self.put('servers/%s/metadata' % str(server_id),
+                              post_body, self.headers)
+        body = json.loads(body)
+        return resp, body['metadata']
+
+    def update_server_metadata(self, server_id, meta):
+        post_body = json.dumps({'metadata': meta})
+        resp, body = self.post('servers/%s/metadata' % str(server_id),
+                               post_body, self.headers)
+        body = json.loads(body)
+        return resp, body['metadata']
+
+    def get_server_metadata_item(self, server_id, key):
+        resp, body = self.get("servers/%s/metadata/%s" % (str(server_id), key))
+        body = json.loads(body)
+        return resp, body['meta']
+
+    def set_server_metadata_item(self, server_id, key, meta):
+        post_body = json.dumps({'meta': meta})
+        resp, body = self.put('servers/%s/metadata/%s' % (str(server_id), key),
+                              post_body, self.headers)
+        body = json.loads(body)
+        return resp, body['meta']
+
+    def delete_server_metadata_item(self, server_id, key):
+        resp, body = self.delete("servers/%s/metadata/%s" %
+                                 (str(server_id), key))
+        return resp, body
+
+    def stop(self, server_id, **kwargs):
+        return self.action(server_id, 'os-stop', None, **kwargs)
+
+    def start(self, server_id, **kwargs):
+        return self.action(server_id, 'os-start', None, **kwargs)
+
+    def attach_volume(self, server_id, volume_id, device='/dev/vdz'):
+        """Attaches a volume to a server instance."""
+        post_body = json.dumps({
+            'volumeAttachment': {
+                'volumeId': volume_id,
+                'device': device,
+            }
+        })
+        resp, body = self.post('servers/%s/os-volume_attachments' % server_id,
+                               post_body, self.headers)
+        return resp, body
+
+    def detach_volume(self, server_id, volume_id):
+        """Detaches a volume from a server instance."""
+        resp, body = self.delete('servers/%s/os-volume_attachments/%s' %
+                                 (server_id, volume_id))
+        return resp, body
+
+    def add_security_group(self, server_id, name):
+        """Adds a security group to the server."""
+        return self.action(server_id, 'addSecurityGroup', None, name=name)
+
+    def remove_security_group(self, server_id, name):
+        """Removes a security group from the server."""
+        return self.action(server_id, 'removeSecurityGroup', None, name=name)
+
+    def live_migrate_server(self, server_id, dest_host, use_block_migration):
+        """This should be called with administrator privileges ."""
+
+        migrate_params = {
+            "disk_over_commit": False,
+            "block_migration": use_block_migration,
+            "host": dest_host
+        }
+
+        req_body = json.dumps({'os-migrateLive': migrate_params})
+
+        resp, body = self.post("servers/%s/action" % str(server_id),
+                               req_body, self.headers)
+        return resp, body
+
+    def migrate_server(self, server_id, **kwargs):
+        """Migrates a server to a new host."""
+        return self.action(server_id, 'migrate', None, **kwargs)
+
+    def lock_server(self, server_id, **kwargs):
+        """Locks the given server."""
+        return self.action(server_id, 'lock', None, **kwargs)
+
+    def unlock_server(self, server_id, **kwargs):
+        """UNlocks the given server."""
+        return self.action(server_id, 'unlock', None, **kwargs)
+
+    def suspend_server(self, server_id, **kwargs):
+        """Suspends the provded server."""
+        return self.action(server_id, 'suspend', None, **kwargs)
+
+    def resume_server(self, server_id, **kwargs):
+        """Un-suspends the provded server."""
+        return self.action(server_id, 'resume', None, **kwargs)
+
+    def pause_server(self, server_id, **kwargs):
+        """Pauses the provded server."""
+        return self.action(server_id, 'pause', None, **kwargs)
+
+    def unpause_server(self, server_id, **kwargs):
+        """Un-pauses the provded server."""
+        return self.action(server_id, 'unpause', None, **kwargs)
+
+    def reset_state(self, server_id, state='error'):
+        """Resets the state of a server to active/error."""
+        return self.action(server_id, 'os-resetState', None, state=state)
+
+    def get_console_output(self, server_id, length):
+        return self.action(server_id, 'os-getConsoleOutput', 'output',
+                           length=length)
+
+    def list_virtual_interfaces(self, server_id):
+        """
+        List the virtual interfaces used in an instance.
+        """
+        resp, body = self.get('/'.join(['servers', server_id,
+                              'os-virtual-interfaces']))
+        return resp, json.loads(body)
+
+    def rescue_server(self, server_id, adminPass=None):
+        """Rescue the provided server."""
+        return self.action(server_id, 'rescue', None, adminPass=adminPass)
+
+    def unrescue_server(self, server_id):
+        """Unrescue the provided server."""
+        return self.action(server_id, 'unrescue', None)
+
+    def get_server_diagnostics(self, server_id):
+        """Get the usage data for a server."""
+        resp, body = self.get("servers/%s/diagnostics" % str(server_id))
+        return resp, json.loads(body)
+
+    def list_instance_actions(self, server_id):
+        """List the provided server action."""
+        resp, body = self.get("servers/%s/os-instance-actions" %
+                              str(server_id))
+        body = json.loads(body)
+        return resp, body['instanceActions']
+
+    def get_instance_action(self, server_id, request_id):
+        """Returns the action details of the provided server."""
+        resp, body = self.get("servers/%s/os-instance-actions/%s" %
+                              (str(server_id), str(request_id)))
+        body = json.loads(body)
+        return resp, body['instanceAction']
diff --git a/tempest/services/compute/v3/xml/servers_client.py b/tempest/services/compute/v3/xml/servers_client.py
new file mode 100644
index 0000000..43de4ef
--- /dev/null
+++ b/tempest/services/compute/v3/xml/servers_client.py
@@ -0,0 +1,602 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 IBM Corp.
+# Copyright 2013 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import time
+import urllib
+
+from lxml import etree
+
+from tempest.common.rest_client import RestClientXML
+from tempest.common import waiters
+from tempest import exceptions
+from tempest.openstack.common import log as logging
+from tempest.services.compute.xml.common import Document
+from tempest.services.compute.xml.common import Element
+from tempest.services.compute.xml.common import Text
+from tempest.services.compute.xml.common import xml_to_json
+from tempest.services.compute.xml.common import XMLNS_11
+
+
+LOG = logging.getLogger(__name__)
+
+
+def _translate_ip_xml_json(ip):
+    """
+    Convert the address version to int.
+    """
+    ip = dict(ip)
+    version = ip.get('version')
+    if version:
+        ip['version'] = int(version)
+    # NOTE(maurosr): just a fast way to avoid the xml version with the
+    # expanded xml namespace.
+    type_ns_prefix = ('{http://docs.openstack.org/compute/ext/extended_ips/'
+                      'api/v1.1}type')
+    mac_ns_prefix = ('{http://docs.openstack.org/compute/ext/extended_ips_mac'
+                     '/api/v1.1}mac_addr')
+
+    if type_ns_prefix in ip:
+        ip['OS-EXT-IPS:type'] = ip.pop(type_ns_prefix)
+
+    if mac_ns_prefix in ip:
+        ip['OS-EXT-IPS-MAC:mac_addr'] = ip.pop(mac_ns_prefix)
+    return ip
+
+
+def _translate_network_xml_to_json(network):
+    return [_translate_ip_xml_json(ip.attrib)
+            for ip in network.findall('{%s}ip' % XMLNS_11)]
+
+
+def _translate_addresses_xml_to_json(xml_addresses):
+    return dict((network.attrib['id'], _translate_network_xml_to_json(network))
+                for network in xml_addresses.findall('{%s}network' % XMLNS_11))
+
+
+def _translate_server_xml_to_json(xml_dom):
+    """Convert server XML to server JSON.
+
+    The addresses collection does not convert well by the dumb xml_to_json.
+    This method does some pre and post-processing to deal with that.
+
+    Translate XML addresses subtree to JSON.
+
+    Having xml_doc similar to
+    <api:server  xmlns:api="http://docs.openstack.org/compute/api/v1.1">
+        <api:addresses>
+            <api:network id="foo_novanetwork">
+                <api:ip version="4" addr="192.168.0.4"/>
+            </api:network>
+            <api:network id="bar_novanetwork">
+                <api:ip version="4" addr="10.1.0.4"/>
+                <api:ip version="6" addr="2001:0:0:1:2:3:4:5"/>
+            </api:network>
+        </api:addresses>
+    </api:server>
+
+    the _translate_server_xml_to_json(etree.fromstring(xml_doc)) should produce
+    something like
+
+    {'addresses': {'bar_novanetwork': [{'addr': '10.1.0.4', 'version': 4},
+                                       {'addr': '2001:0:0:1:2:3:4:5',
+                                        'version': 6}],
+                   'foo_novanetwork': [{'addr': '192.168.0.4', 'version': 4}]}}
+    """
+    nsmap = {'api': XMLNS_11}
+    addresses = xml_dom.xpath('/api:server/api:addresses', namespaces=nsmap)
+    if addresses:
+        if len(addresses) > 1:
+            raise ValueError('Expected only single `addresses` element.')
+        json_addresses = _translate_addresses_xml_to_json(addresses[0])
+        json = xml_to_json(xml_dom)
+        json['addresses'] = json_addresses
+    else:
+        json = xml_to_json(xml_dom)
+    diskConfig = ('{http://docs.openstack.org'
+                  '/compute/ext/disk_config/api/v1.1}diskConfig')
+    terminated_at = ('{http://docs.openstack.org/'
+                     'compute/ext/server_usage/api/v1.1}terminated_at')
+    launched_at = ('{http://docs.openstack.org'
+                   '/compute/ext/server_usage/api/v1.1}launched_at')
+    power_state = ('{http://docs.openstack.org'
+                   '/compute/ext/extended_status/api/v1.1}power_state')
+    availability_zone = ('{http://docs.openstack.org'
+                         '/compute/ext/extended_availability_zone/api/v2}'
+                         'availability_zone')
+    vm_state = ('{http://docs.openstack.org'
+                '/compute/ext/extended_status/api/v1.1}vm_state')
+    task_state = ('{http://docs.openstack.org'
+                  '/compute/ext/extended_status/api/v1.1}task_state')
+    if diskConfig in json:
+        json['OS-DCF:diskConfig'] = json.pop(diskConfig)
+    if terminated_at in json:
+        json['OS-SRV-USG:terminated_at'] = json.pop(terminated_at)
+    if launched_at in json:
+        json['OS-SRV-USG:launched_at'] = json.pop(launched_at)
+    if power_state in json:
+        json['OS-EXT-STS:power_state'] = json.pop(power_state)
+    if availability_zone in json:
+        json['OS-EXT-AZ:availability_zone'] = json.pop(availability_zone)
+    if vm_state in json:
+        json['OS-EXT-STS:vm_state'] = json.pop(vm_state)
+    if task_state in json:
+        json['OS-EXT-STS:task_state'] = json.pop(task_state)
+    return json
+
+
+class ServersClientXML(RestClientXML):
+
+    def __init__(self, config, username, password, auth_url, tenant_name=None,
+                 auth_version='v2'):
+        super(ServersClientXML, self).__init__(config, username, password,
+                                               auth_url, tenant_name,
+                                               auth_version=auth_version)
+        self.service = self.config.compute.catalog_type
+
+    def _parse_key_value(self, node):
+        """Parse <foo key='key'>value</foo> data into {'key': 'value'}."""
+        data = {}
+        for node in node.getchildren():
+            data[node.get('key')] = node.text
+        return data
+
+    def _parse_links(self, node, json):
+        del json['link']
+        json['links'] = []
+        for linknode in node.findall('{http://www.w3.org/2005/Atom}link'):
+            json['links'].append(xml_to_json(linknode))
+
+    def _parse_server(self, body):
+        json = _translate_server_xml_to_json(body)
+
+        if 'metadata' in json and json['metadata']:
+            # NOTE(danms): if there was metadata, we need to re-parse
+            # that as a special type
+            metadata_tag = body.find('{%s}metadata' % XMLNS_11)
+            json["metadata"] = self._parse_key_value(metadata_tag)
+        if 'link' in json:
+            self._parse_links(body, json)
+        for sub in ['image', 'flavor']:
+            if sub in json and 'link' in json[sub]:
+                self._parse_links(body, json[sub])
+        return json
+
+    def _parse_xml_virtual_interfaces(self, xml_dom):
+        """
+        Return server's virtual interfaces XML as JSON.
+        """
+        data = {"virtual_interfaces": []}
+        for iface in xml_dom.getchildren():
+            data["virtual_interfaces"].append(
+                {"id": iface.get("id"),
+                 "mac_address": iface.get("mac_address")})
+        return data
+
+    def get_server(self, server_id):
+        """Returns the details of an existing server."""
+        resp, body = self.get("servers/%s" % str(server_id), self.headers)
+        server = self._parse_server(etree.fromstring(body))
+        return resp, server
+
+    def lock_server(self, server_id, **kwargs):
+        """Locks the given server."""
+        return self.action(server_id, 'lock', None, **kwargs)
+
+    def unlock_server(self, server_id, **kwargs):
+        """Unlocks the given server."""
+        return self.action(server_id, 'unlock', None, **kwargs)
+
+    def suspend_server(self, server_id, **kwargs):
+        """Suspends the provided server."""
+        return self.action(server_id, 'suspend', None, **kwargs)
+
+    def resume_server(self, server_id, **kwargs):
+        """Un-suspends the provided server."""
+        return self.action(server_id, 'resume', None, **kwargs)
+
+    def pause_server(self, server_id, **kwargs):
+        """Pauses the provided server."""
+        return self.action(server_id, 'pause', None, **kwargs)
+
+    def unpause_server(self, server_id, **kwargs):
+        """Un-pauses the provided server."""
+        return self.action(server_id, 'unpause', None, **kwargs)
+
+    def reset_state(self, server_id, state='error'):
+        """Resets the state of a server to active/error."""
+        return self.action(server_id, 'os-resetState', None, state=state)
+
+    def delete_server(self, server_id):
+        """Deletes the given server."""
+        return self.delete("servers/%s" % str(server_id))
+
+    def _parse_array(self, node):
+        array = []
+        for child in node.getchildren():
+            array.append(xml_to_json(child))
+        return array
+
+    def list_servers(self, params=None):
+        url = 'servers'
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+
+        resp, body = self.get(url, self.headers)
+        servers = self._parse_array(etree.fromstring(body))
+        return resp, {"servers": servers}
+
+    def list_servers_with_detail(self, params=None):
+        url = 'servers/detail'
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+
+        resp, body = self.get(url, self.headers)
+        servers = self._parse_array(etree.fromstring(body))
+        return resp, {"servers": servers}
+
+    def update_server(self, server_id, name=None, meta=None, accessIPv4=None,
+                      accessIPv6=None, disk_config=None):
+        doc = Document()
+        server = Element("server")
+        doc.append(server)
+
+        if name is not None:
+            server.add_attr("name", name)
+        if accessIPv4 is not None:
+            server.add_attr("accessIPv4", accessIPv4)
+        if accessIPv6 is not None:
+            server.add_attr("accessIPv6", accessIPv6)
+        if disk_config is not None:
+            server.add_attr('xmlns:OS-DCF', "http://docs.openstack.org/"
+                            "compute/ext/disk_config/api/v1.1")
+            server.add_attr("OS-DCF:diskConfig", disk_config)
+        if meta is not None:
+            metadata = Element("metadata")
+            server.append(metadata)
+            for k, v in meta:
+                meta = Element("meta", key=k)
+                meta.append(Text(v))
+                metadata.append(meta)
+
+        resp, body = self.put('servers/%s' % str(server_id),
+                              str(doc), self.headers)
+        return resp, xml_to_json(etree.fromstring(body))
+
+    def create_server(self, name, image_ref, flavor_ref, **kwargs):
+        """
+        Creates an instance of a server.
+        name (Required): The name of the server.
+        image_ref (Required): Reference to the image used to build the server.
+        flavor_ref (Required): The flavor used to build the server.
+        Following optional keyword arguments are accepted:
+        adminPass: Sets the initial root password.
+        key_name: Key name of keypair that was created earlier.
+        meta: A dictionary of values to be used as metadata.
+        personality: A list of dictionaries for files to be injected into
+        the server.
+        security_groups: A list of security group dicts.
+        networks: A list of network dicts with UUID and fixed_ip.
+        user_data: User data for instance.
+        availability_zone: Availability zone in which to launch instance.
+        accessIPv4: The IPv4 access address for the server.
+        accessIPv6: The IPv6 access address for the server.
+        min_count: Count of minimum number of instances to launch.
+        max_count: Count of maximum number of instances to launch.
+        disk_config: Determines if user or admin controls disk configuration.
+        """
+        server = Element("server",
+                         xmlns=XMLNS_11,
+                         imageRef=image_ref,
+                         flavorRef=flavor_ref,
+                         name=name)
+
+        for attr in ["adminPass", "accessIPv4", "accessIPv6", "key_name",
+                     "user_data", "availability_zone", "min_count",
+                     "max_count", "return_reservation_id"]:
+            if attr in kwargs:
+                server.add_attr(attr, kwargs[attr])
+
+        if 'disk_config' in kwargs:
+            server.add_attr('xmlns:OS-DCF', "http://docs.openstack.org/"
+                            "compute/ext/disk_config/api/v1.1")
+            server.add_attr('OS-DCF:diskConfig', kwargs['disk_config'])
+
+        if 'security_groups' in kwargs:
+            secgroups = Element("security_groups")
+            server.append(secgroups)
+            for secgroup in kwargs['security_groups']:
+                s = Element("security_group", name=secgroup['name'])
+                secgroups.append(s)
+
+        if 'networks' in kwargs:
+            networks = Element("networks")
+            server.append(networks)
+            for network in kwargs['networks']:
+                s = Element("network", uuid=network['uuid'],
+                            fixed_ip=network['fixed_ip'])
+                networks.append(s)
+
+        if 'meta' in kwargs:
+            metadata = Element("metadata")
+            server.append(metadata)
+            for k, v in kwargs['meta'].items():
+                meta = Element("meta", key=k)
+                meta.append(Text(v))
+                metadata.append(meta)
+
+        if 'personality' in kwargs:
+            personality = Element('personality')
+            server.append(personality)
+            for k in kwargs['personality']:
+                temp = Element('file', path=k['path'])
+                temp.append(Text(k['contents']))
+                personality.append(temp)
+
+        resp, body = self.post('servers', str(Document(server)), self.headers)
+        server = self._parse_server(etree.fromstring(body))
+        return resp, server
+
+    def wait_for_server_status(self, server_id, status):
+        """Waits for a server to reach a given status."""
+        return waiters.wait_for_server_status(self, server_id, status)
+
+    def wait_for_server_termination(self, server_id, ignore_error=False):
+        """Waits for server to reach termination."""
+        start_time = int(time.time())
+        while True:
+            try:
+                resp, body = self.get_server(server_id)
+            except exceptions.NotFound:
+                return
+
+            server_status = body['status']
+            if server_status == 'ERROR' and not ignore_error:
+                raise exceptions.BuildErrorException
+
+            if int(time.time()) - start_time >= self.build_timeout:
+                raise exceptions.TimeoutException
+
+            time.sleep(self.build_interval)
+
+    def _parse_network(self, node):
+        addrs = []
+        for child in node.getchildren():
+            addrs.append({'version': int(child.get('version')),
+                         'addr': child.get('addr')})
+        return {node.get('id'): addrs}
+
+    def list_addresses(self, server_id):
+        """Lists all addresses for a server."""
+        resp, body = self.get("servers/%s/ips" % str(server_id), self.headers)
+
+        networks = {}
+        xml_list = etree.fromstring(body)
+        for child in xml_list.getchildren():
+            network = self._parse_network(child)
+            networks.update(**network)
+
+        return resp, networks
+
+    def list_addresses_by_network(self, server_id, network_id):
+        """Lists all addresses of a specific network type for a server."""
+        resp, body = self.get("servers/%s/ips/%s" % (str(server_id),
+                                                     network_id),
+                              self.headers)
+        network = self._parse_network(etree.fromstring(body))
+
+        return resp, network
+
+    def action(self, server_id, action_name, response_key, **kwargs):
+        if 'xmlns' not in kwargs:
+            kwargs['xmlns'] = XMLNS_11
+        doc = Document((Element(action_name, **kwargs)))
+        resp, body = self.post("servers/%s/action" % server_id,
+                               str(doc), self.headers)
+        if response_key is not None:
+            body = xml_to_json(etree.fromstring(body))
+        return resp, body
+
+    def change_password(self, server_id, password):
+        return self.action(server_id, "changePassword", None,
+                           adminPass=password)
+
+    def reboot(self, server_id, reboot_type):
+        return self.action(server_id, "reboot", None, type=reboot_type)
+
+    def rebuild(self, server_id, image_ref, **kwargs):
+        kwargs['imageRef'] = image_ref
+        if 'disk_config' in kwargs:
+            kwargs['OS-DCF:diskConfig'] = kwargs['disk_config']
+            del kwargs['disk_config']
+            kwargs['xmlns:OS-DCF'] = "http://docs.openstack.org/"\
+                                     "compute/ext/disk_config/api/v1.1"
+            kwargs['xmlns:atom'] = "http://www.w3.org/2005/Atom"
+        if 'xmlns' not in kwargs:
+            kwargs['xmlns'] = XMLNS_11
+
+        attrs = kwargs.copy()
+        if 'metadata' in attrs:
+            del attrs['metadata']
+        rebuild = Element("rebuild",
+                          **attrs)
+
+        if 'metadata' in kwargs:
+            metadata = Element("metadata")
+            rebuild.append(metadata)
+            for k, v in kwargs['metadata'].items():
+                meta = Element("meta", key=k)
+                meta.append(Text(v))
+                metadata.append(meta)
+
+        resp, body = self.post('servers/%s/action' % server_id,
+                               str(Document(rebuild)), self.headers)
+        server = self._parse_server(etree.fromstring(body))
+        return resp, server
+
+    def resize(self, server_id, flavor_ref, **kwargs):
+        if 'disk_config' in kwargs:
+            kwargs['OS-DCF:diskConfig'] = kwargs['disk_config']
+            del kwargs['disk_config']
+            kwargs['xmlns:OS-DCF'] = "http://docs.openstack.org/"\
+                                     "compute/ext/disk_config/api/v1.1"
+            kwargs['xmlns:atom'] = "http://www.w3.org/2005/Atom"
+        kwargs['flavorRef'] = flavor_ref
+        return self.action(server_id, 'resize', None, **kwargs)
+
+    def confirm_resize(self, server_id, **kwargs):
+        return self.action(server_id, 'confirmResize', None, **kwargs)
+
+    def revert_resize(self, server_id, **kwargs):
+        return self.action(server_id, 'revertResize', None, **kwargs)
+
+    def stop(self, server_id, **kwargs):
+        return self.action(server_id, 'os-stop', None, **kwargs)
+
+    def start(self, server_id, **kwargs):
+        return self.action(server_id, 'os-start', None, **kwargs)
+
+    def create_image(self, server_id, name):
+        return self.action(server_id, 'createImage', None, name=name)
+
+    def add_security_group(self, server_id, name):
+        return self.action(server_id, 'addSecurityGroup', None, name=name)
+
+    def remove_security_group(self, server_id, name):
+        return self.action(server_id, 'removeSecurityGroup', None, name=name)
+
+    def live_migrate_server(self, server_id, dest_host, use_block_migration):
+        """This should be called with administrator privileges ."""
+
+        req_body = Element("os-migrateLive",
+                           xmlns=XMLNS_11,
+                           disk_over_commit=False,
+                           block_migration=use_block_migration,
+                           host=dest_host)
+
+        resp, body = self.post("servers/%s/action" % str(server_id),
+                               str(Document(req_body)), self.headers)
+        return resp, body
+
+    def list_server_metadata(self, server_id):
+        resp, body = self.get("servers/%s/metadata" % str(server_id),
+                              self.headers)
+        body = self._parse_key_value(etree.fromstring(body))
+        return resp, body
+
+    def set_server_metadata(self, server_id, meta, no_metadata_field=False):
+        doc = Document()
+        if not no_metadata_field:
+            metadata = Element("metadata")
+            doc.append(metadata)
+            for k, v in meta.items():
+                meta_element = Element("meta", key=k)
+                meta_element.append(Text(v))
+                metadata.append(meta_element)
+        resp, body = self.put('servers/%s/metadata' % str(server_id),
+                              str(doc), self.headers)
+        return resp, xml_to_json(etree.fromstring(body))
+
+    def update_server_metadata(self, server_id, meta):
+        doc = Document()
+        metadata = Element("metadata")
+        doc.append(metadata)
+        for k, v in meta.items():
+            meta_element = Element("meta", key=k)
+            meta_element.append(Text(v))
+            metadata.append(meta_element)
+        resp, body = self.post("/servers/%s/metadata" % str(server_id),
+                               str(doc), headers=self.headers)
+        body = xml_to_json(etree.fromstring(body))
+        return resp, body
+
+    def get_server_metadata_item(self, server_id, key):
+        resp, body = self.get("servers/%s/metadata/%s" % (str(server_id), key),
+                              headers=self.headers)
+        return resp, dict([(etree.fromstring(body).attrib['key'],
+                            xml_to_json(etree.fromstring(body)))])
+
+    def set_server_metadata_item(self, server_id, key, meta):
+        doc = Document()
+        for k, v in meta.items():
+            meta_element = Element("meta", key=k)
+            meta_element.append(Text(v))
+            doc.append(meta_element)
+        resp, body = self.put('servers/%s/metadata/%s' % (str(server_id), key),
+                              str(doc), self.headers)
+        return resp, xml_to_json(etree.fromstring(body))
+
+    def delete_server_metadata_item(self, server_id, key):
+        resp, body = self.delete("servers/%s/metadata/%s" %
+                                 (str(server_id), key))
+        return resp, body
+
+    def get_console_output(self, server_id, length):
+        return self.action(server_id, 'os-getConsoleOutput', 'output',
+                           length=length)
+
+    def list_virtual_interfaces(self, server_id):
+        """
+        List the virtual interfaces used in an instance.
+        """
+        resp, body = self.get('/'.join(['servers', server_id,
+                              'os-virtual-interfaces']), self.headers)
+        virt_int = self._parse_xml_virtual_interfaces(etree.fromstring(body))
+        return resp, virt_int
+
+    def rescue_server(self, server_id, adminPass=None):
+        """Rescue the provided server."""
+        return self.action(server_id, 'rescue', None, adminPass=adminPass)
+
+    def unrescue_server(self, server_id):
+        """Unrescue the provided server."""
+        return self.action(server_id, 'unrescue', None)
+
+    def attach_volume(self, server_id, volume_id, device='/dev/vdz'):
+        post_body = Element("volumeAttachment", volumeId=volume_id,
+                            device=device)
+        resp, body = self.post('servers/%s/os-volume_attachments' % server_id,
+                               str(Document(post_body)), self.headers)
+        return resp, body
+
+    def detach_volume(self, server_id, volume_id):
+        headers = {'Content-Type': 'application/xml',
+                   'Accept': 'application/xml'}
+        resp, body = self.delete('servers/%s/os-volume_attachments/%s' %
+                                 (server_id, volume_id), headers)
+        return resp, body
+
+    def get_server_diagnostics(self, server_id):
+        """Get the usage data for a server."""
+        resp, body = self.get("servers/%s/diagnostics" % server_id,
+                              self.headers)
+        body = xml_to_json(etree.fromstring(body))
+        return resp, body
+
+    def list_instance_actions(self, server_id):
+        """List the provided server action."""
+        resp, body = self.get("servers/%s/os-instance-actions" % server_id,
+                              self.headers)
+        body = self._parse_array(etree.fromstring(body))
+        return resp, body
+
+    def get_instance_action(self, server_id, request_id):
+        """Returns the action details of the provided server."""
+        resp, body = self.get("servers/%s/os-instance-actions/%s" %
+                              (server_id, request_id), self.headers)
+        body = xml_to_json(etree.fromstring(body))
+        return resp, body