You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@libcloud.apache.org by an...@apache.org on 2016/12/02 04:33:21 UTC

[19/40] libcloud git commit: Unit tests for 2.3

Unit tests for 2.3


Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/bb1b8104
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/bb1b8104
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/bb1b8104

Branch: refs/heads/trunk
Commit: bb1b810444ba33c11c1bac52151d00477a500123
Parents: e846f57
Author: Samuel Chong <sa...@gmail.com>
Authored: Wed Nov 16 08:15:09 2016 +1100
Committer: Samuel Chong <sa...@gmail.com>
Committed: Wed Nov 16 08:15:09 2016 +1100

----------------------------------------------------------------------
 libcloud/compute/drivers/dimensiondata.py       |   17 +-
 libcloud/test/backup/test_dimensiondata.py      |  502 ---
 libcloud/test/backup/test_dimensiondata_v2_3.py |  503 +++
 libcloud/test/backup/test_dimensiondata_v2_4.py |  503 +++
 .../dimensiondata/2.4/image_customerImage.xml   |   50 +
 ...age_2ffa36c8_1848_49eb_b4fa_9d908775f68c.xml |   17 +
 ...age_5234e5c7_01de_4411_8b6e_baeb8d91cf5d.xml |   17 +
 .../dimensiondata/2.4/image_osImage.xml         |   34 +
 ...age_6b4fb0c7_a57b_4f58_b59c_9958f94f971a.xml |   11 +
 ...age_c14b1a46_2428_44c1_9c1a_b20e6418d08c.xml |   12 +
 .../dimensiondata/image_customerImage.xml       |   59 -
 ...age_2ffa36c8_1848_49eb_b4fa_9d908775f68c.xml |   21 -
 ...age_5234e5c7_01de_4411_8b6e_baeb8d91cf5d.xml |   21 -
 .../fixtures/dimensiondata/image_osImage.xml    |   43 -
 ...age_6b4fb0c7_a57b_4f58_b59c_9958f94f971a.xml |   14 -
 ...age_c14b1a46_2428_44c1_9c1a_b20e6418d08c.xml |   17 -
 libcloud/test/compute/test_dimensiondata.py     | 3281 -----------------
 .../test/compute/test_dimensiondata_v2_3.py     | 3282 ++++++++++++++++++
 .../test/compute/test_dimensiondata_v2_4.py     | 3282 ++++++++++++++++++
 .../test/loadbalancer/test_dimensiondata.py     |  619 ----
 .../loadbalancer/test_dimensiondata_v2_3.py     |  620 ++++
 .../loadbalancer/test_dimensiondata_v2_4.py     |  620 ++++
 22 files changed, 8962 insertions(+), 4583 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/bb1b8104/libcloud/compute/drivers/dimensiondata.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/dimensiondata.py b/libcloud/compute/drivers/dimensiondata.py
index d869e0a..ab3a0a7 100644
--- a/libcloud/compute/drivers/dimensiondata.py
+++ b/libcloud/compute/drivers/dimensiondata.py
@@ -21,6 +21,7 @@ try:
 except ImportError:
     from xml.etree import ElementTree as ET
 
+from distutils.version import LooseVersion, StrictVersion
 from libcloud.common.exceptions import BaseHTTPError
 from libcloud.compute.base import NodeDriver, Node, NodeAuthPassword
 from libcloud.compute.base import NodeSize, NodeImage, NodeLocation
@@ -2431,7 +2432,8 @@ class DimensionDataNodeDriver(NodeDriver):
         node_id = self._node_to_node_id(node)
 
         # Version 2.3 and lower
-        if float(self.connection.active_api_version) < 2.4:
+        if LooseVersion(self.connection.active_api_version) < LooseVersion(
+                '2.4'):
             response = self.connection.request_with_orgId_api_1(
                 'server/%s?clone=%s&desc=%s' %
                 (node_id, image_name, image_description)).object
@@ -2469,7 +2471,8 @@ class DimensionDataNodeDriver(NodeDriver):
                 data=ET.tostring(clone_server_elem)).object
 
         # Version 2.3 and lower
-        if float(self.connection.active_api_version) < 2.4:
+        if LooseVersion(self.connection.active_api_version) < LooseVersion(
+                '2.4'):
             response_code = findtext(response, 'result', GENERAL_NS)
         else:
             response_code = findtext(response, 'responseCode', TYPES_URN)
@@ -3628,7 +3631,8 @@ class DimensionDataNodeDriver(NodeDriver):
                      is_guest_os_customization=None,
                      tagkey_name_value_dictionaries=None):
         # Unsupported for version lower than 2.4
-        if float(self.connection.active_api_version) < 2.4:
+        if LooseVersion(self.connection.active_api_version) < LooseVersion(
+                '2.4'):
             raise Exception("import image is feature is NOT supported in  " \
                             "api version earlier than 2.4")
         else:
@@ -3686,7 +3690,6 @@ class DimensionDataNodeDriver(NodeDriver):
         response_code = findtext(response, 'responseCode', TYPES_URN)
         return response_code in ['IN_PROGRESS', 'OK']
 
-
     def _format_csv(self, http_response):
         text = http_response.read()
         lines = str.splitlines(ensure_string(text))
@@ -3764,7 +3767,8 @@ class DimensionDataNodeDriver(NodeDriver):
 
         cpu_spec = self._to_cpu_spec(element.find(fixxpath('cpu', TYPES_URN)))
 
-        if float(self.connection.active_api_version) > 2.3:
+        if LooseVersion(self.connection.active_api_version) > LooseVersion(
+                '2.3'):
             os_el = element.find(fixxpath('guest/operatingSystem', TYPES_URN))
         else:
             os_el = element.find(fixxpath('operatingSystem', TYPES_URN))
@@ -4052,7 +4056,8 @@ class DimensionDataNodeDriver(NodeDriver):
         disks = self._to_disks(element)
 
         # Version 2.3 or earlier
-        if float(self.connection.active_api_version) < 2.4:
+        if LooseVersion(self.connection.active_api_version) < LooseVersion(
+                '2.4'):
             vmware_tools = self._to_vmware_tools(
                 element.find(fixxpath('vmwareTools', TYPES_URN)))
             operation_system = element.find(fixxpath(

http://git-wip-us.apache.org/repos/asf/libcloud/blob/bb1b8104/libcloud/test/backup/test_dimensiondata.py
----------------------------------------------------------------------
diff --git a/libcloud/test/backup/test_dimensiondata.py b/libcloud/test/backup/test_dimensiondata.py
deleted file mode 100644
index 3214cff..0000000
--- a/libcloud/test/backup/test_dimensiondata.py
+++ /dev/null
@@ -1,502 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You 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.
-
-try:
-    from lxml import etree as ET
-except ImportError:
-    from xml.etree import ElementTree as ET
-
-import sys
-from libcloud.utils.py3 import httplib
-
-from libcloud.common.dimensiondata import DimensionDataAPIException
-from libcloud.common.types import InvalidCredsError
-from libcloud.backup.base import BackupTargetJob
-from libcloud.backup.drivers.dimensiondata import DimensionDataBackupDriver as DimensionData
-from libcloud.backup.drivers.dimensiondata import DEFAULT_BACKUP_PLAN
-
-from libcloud.test import MockHttp, unittest
-from libcloud.test.backup import TestCaseMixin
-from libcloud.test.file_fixtures import BackupFileFixtures
-
-from libcloud.test.secrets import DIMENSIONDATA_PARAMS
-
-
-class DimensionDataTests(unittest.TestCase, TestCaseMixin):
-
-    def setUp(self):
-        DimensionData.connectionCls.conn_classes = (None, DimensionDataMockHttp)
-        DimensionDataMockHttp.type = None
-        self.driver = DimensionData(*DIMENSIONDATA_PARAMS)
-
-    def test_invalid_region(self):
-        with self.assertRaises(ValueError):
-            self.driver = DimensionData(*DIMENSIONDATA_PARAMS, region='blah')
-
-    def test_invalid_creds(self):
-        DimensionDataMockHttp.type = 'UNAUTHORIZED'
-        with self.assertRaises(InvalidCredsError):
-            self.driver.list_targets()
-
-    def test_list_targets(self):
-        targets = self.driver.list_targets()
-        self.assertEqual(len(targets), 2)
-        self.assertEqual(targets[0].id, '5579f3a7-4c32-4cf5-8a7e-b45c36a35c10')
-        self.assertEqual(targets[0].address, 'e75ead52-692f-4314-8725-c8a4f4d13a87')
-        self.assertEqual(targets[0].extra['servicePlan'], 'Enterprise')
-
-    def test_create_target(self):
-        target = self.driver.create_target(
-            'name',
-            'e75ead52-692f-4314-8725-c8a4f4d13a87',
-            extra={'servicePlan': 'Enterprise'})
-        self.assertEqual(target.id, 'ee7c4b64-f7af-4a4f-8384-be362273530f')
-        self.assertEqual(target.address, 'e75ead52-692f-4314-8725-c8a4f4d13a87')
-        self.assertEqual(target.extra['servicePlan'], 'Enterprise')
-
-    def test_create_target_DEFAULT(self):
-        DimensionDataMockHttp.type = 'DEFAULT'
-        target = self.driver.create_target(
-            'name',
-            'e75ead52-692f-4314-8725-c8a4f4d13a87')
-        self.assertEqual(target.id, 'ee7c4b64-f7af-4a4f-8384-be362273530f')
-        self.assertEqual(target.address, 'e75ead52-692f-4314-8725-c8a4f4d13a87')
-
-    def test_create_target_EXISTS(self):
-        DimensionDataMockHttp.type = 'EXISTS'
-        with self.assertRaises(DimensionDataAPIException) as context:
-            self.driver.create_target(
-                'name',
-                'e75ead52-692f-4314-8725-c8a4f4d13a87',
-                extra={'servicePlan': 'Enterprise'})
-        self.assertEqual(context.exception.code, 'ERROR')
-        self.assertEqual(context.exception.msg, 'Cloud backup for this server is already enabled or being enabled (state: NORMAL).')
-
-    def test_update_target(self):
-        target = self.driver.list_targets()[0]
-        extra = {'servicePlan': 'Essentials'}
-        new_target = self.driver.update_target(target, extra=extra)
-        self.assertEqual(new_target.extra['servicePlan'], 'Essentials')
-
-    def test_update_target_DEFAULT(self):
-        DimensionDataMockHttp.type = 'DEFAULT'
-        target = 'e75ead52-692f-4314-8725-c8a4f4d13a87'
-        self.driver.update_target(target)
-
-    def test_update_target_STR(self):
-        target = 'e75ead52-692f-4314-8725-c8a4f4d13a87'
-        extra = {'servicePlan': 'Essentials'}
-        new_target = self.driver.update_target(target, extra=extra)
-        self.assertEqual(new_target.extra['servicePlan'], 'Essentials')
-
-    def test_delete_target(self):
-        target = self.driver.list_targets()[0]
-        self.assertTrue(self.driver.delete_target(target))
-
-    def test_ex_add_client_to_target(self):
-        target = self.driver.list_targets()[0]
-        client = self.driver.ex_list_available_client_types(target)[0]
-        storage_policy = self.driver.ex_list_available_storage_policies(target)[0]
-        schedule_policy = self.driver.ex_list_available_schedule_policies(target)[0]
-        self.assertTrue(
-            self.driver.ex_add_client_to_target(target, client, storage_policy,
-                                                schedule_policy, 'ON_FAILURE', 'nobody@example.com')
-        )
-
-    def test_ex_add_client_to_target_STR(self):
-        self.assertTrue(
-            self.driver.ex_add_client_to_target('e75ead52-692f-4314-8725-c8a4f4d13a87', 'FA.Linux', '14 Day Storage Policy',
-                                                '12AM - 6AM', 'ON_FAILURE', 'nobody@example.com')
-        )
-
-    def test_ex_get_backup_details_for_target(self):
-        target = self.driver.list_targets()[0]
-        response = self.driver.ex_get_backup_details_for_target(target)
-        self.assertEqual(response.service_plan, 'Enterprise')
-        client = response.clients[0]
-        self.assertEqual(client.id, '30b1ff76-c76d-4d7c-b39d-3b72be0384c8')
-        self.assertEqual(client.type.type, 'FA.Linux')
-        self.assertEqual(client.running_job.progress, 5)
-        self.assertTrue(isinstance(client.running_job, BackupTargetJob))
-        self.assertEqual(len(client.alert.notify_list), 2)
-        self.assertTrue(isinstance(client.alert.notify_list, list))
-
-    def test_ex_get_backup_details_for_target_NOBACKUP(self):
-        target = self.driver.list_targets()[0].address
-        DimensionDataMockHttp.type = 'NOBACKUP'
-        response = self.driver.ex_get_backup_details_for_target(target)
-        self.assertTrue(response is None)
-
-    def test_ex_cancel_target_job(self):
-        target = self.driver.list_targets()[0]
-        response = self.driver.ex_get_backup_details_for_target(target)
-        client = response.clients[0]
-        self.assertTrue(isinstance(client.running_job, BackupTargetJob))
-        success = client.running_job.cancel()
-        self.assertTrue(success)
-
-    def test_ex_cancel_target_job_with_extras(self):
-        success = self.driver.cancel_target_job(
-            None,
-            ex_client='30b1ff76_c76d_4d7c_b39d_3b72be0384c8',
-            ex_target='e75ead52_692f_4314_8725_c8a4f4d13a87'
-        )
-        self.assertTrue(success)
-
-    def test_ex_cancel_target_job_FAIL(self):
-        DimensionDataMockHttp.type = 'FAIL'
-        with self.assertRaises(DimensionDataAPIException) as context:
-            self.driver.cancel_target_job(
-                None,
-                ex_client='30b1ff76_c76d_4d7c_b39d_3b72be0384c8',
-                ex_target='e75ead52_692f_4314_8725_c8a4f4d13a87'
-            )
-        self.assertEqual(context.exception.code, 'ERROR')
-
-    """Test a backup info for a target that does not have a client"""
-    def test_ex_get_backup_details_for_target_NO_CLIENT(self):
-        DimensionDataMockHttp.type = 'NOCLIENT'
-        response = self.driver.ex_get_backup_details_for_target('e75ead52-692f-4314-8725-c8a4f4d13a87')
-        self.assertEqual(response.service_plan, 'Essentials')
-        self.assertEqual(len(response.clients), 0)
-
-    """Test a backup details that has a client, but no alerting or running jobs"""
-    def test_ex_get_backup_details_for_target_NO_JOB_OR_ALERT(self):
-        DimensionDataMockHttp.type = 'NOJOB'
-        response = self.driver.ex_get_backup_details_for_target('e75ead52-692f-4314_8725-c8a4f4d13a87')
-        self.assertEqual(response.service_plan, 'Enterprise')
-        self.assertTrue(isinstance(response.clients, list))
-        self.assertEqual(len(response.clients), 1)
-        client = response.clients[0]
-        self.assertEqual(client.id, '30b1ff76-c76d-4d7c-b39d-3b72be0384c8')
-        self.assertEqual(client.type.type, 'FA.Linux')
-        self.assertIsNone(client.running_job)
-        self.assertIsNone(client.alert)
-
-    """Test getting backup info for a server that doesn't exist"""
-    def test_ex_get_backup_details_for_target_DISABLED(self):
-        DimensionDataMockHttp.type = 'DISABLED'
-        with self.assertRaises(DimensionDataAPIException) as context:
-            self.driver.ex_get_backup_details_for_target('e75ead52-692f-4314-8725-c8a4f4d13a87')
-        self.assertEqual(context.exception.code, 'ERROR')
-        self.assertEqual(context.exception.msg, 'Server e75ead52-692f-4314-8725-c8a4f4d13a87 has not been provisioned for backup')
-
-    def test_ex_list_available_client_types(self):
-        target = self.driver.list_targets()[0]
-        answer = self.driver.ex_list_available_client_types(target)
-        self.assertEqual(len(answer), 1)
-        self.assertEqual(answer[0].type, 'FA.Linux')
-        self.assertEqual(answer[0].is_file_system, True)
-        self.assertEqual(answer[0].description, 'Linux File system')
-
-    def test_ex_list_available_storage_policies(self):
-        target = self.driver.list_targets()[0]
-        answer = self.driver.ex_list_available_storage_policies(target)
-        self.assertEqual(len(answer), 1)
-        self.assertEqual(answer[0].name,
-                         '30 Day Storage Policy + Secondary Copy')
-        self.assertEqual(answer[0].retention_period, 30)
-        self.assertEqual(answer[0].secondary_location, 'Primary')
-
-    def test_ex_list_available_schedule_policies(self):
-        target = self.driver.list_targets()[0]
-        answer = self.driver.ex_list_available_schedule_policies(target)
-        self.assertEqual(len(answer), 1)
-        self.assertEqual(answer[0].name, '12AM - 6AM')
-        self.assertEqual(answer[0].description, 'Daily backup will start between 12AM - 6AM')
-
-    def test_ex_remove_client_from_target(self):
-        target = self.driver.list_targets()[0]
-        client = self.driver.ex_get_backup_details_for_target('e75ead52-692f-4314-8725-c8a4f4d13a87').clients[0]
-        self.assertTrue(self.driver.ex_remove_client_from_target(target, client))
-
-    def test_ex_remove_client_from_target_STR(self):
-        self.assertTrue(
-            self.driver.ex_remove_client_from_target(
-                'e75ead52-692f-4314-8725-c8a4f4d13a87',
-                '30b1ff76-c76d-4d7c-b39d-3b72be0384c8'
-            )
-        )
-
-    def test_ex_remove_client_from_target_FAIL(self):
-        DimensionDataMockHttp.type = 'FAIL'
-        with self.assertRaises(DimensionDataAPIException) as context:
-            self.driver.ex_remove_client_from_target(
-                'e75ead52-692f-4314-8725-c8a4f4d13a87',
-                '30b1ff76-c76d-4d7c-b39d-3b72be0384c8'
-            )
-        self.assertEqual(context.exception.code, 'ERROR')
-        self.assertTrue('Backup Client is currently performing another operation' in context.exception.msg)
-
-    def test_priv_target_to_target_address(self):
-        target = self.driver.list_targets()[0]
-        self.assertEqual(
-            self.driver._target_to_target_address(target),
-            'e75ead52-692f-4314-8725-c8a4f4d13a87'
-        )
-
-    def test_priv_target_to_target_address_STR(self):
-        self.assertEqual(
-            self.driver._target_to_target_address('e75ead52-692f-4314-8725-c8a4f4d13a87'),
-            'e75ead52-692f-4314-8725-c8a4f4d13a87'
-        )
-
-    def test_priv_target_to_target_address_TYPEERROR(self):
-        with self.assertRaises(TypeError):
-            self.driver._target_to_target_address([1, 2, 3])
-
-    def test_priv_client_to_client_id(self):
-        client = self.driver.ex_get_backup_details_for_target('e75ead52-692f-4314-8725-c8a4f4d13a87').clients[0]
-        self.assertEqual(
-            self.driver._client_to_client_id(client),
-            '30b1ff76-c76d-4d7c-b39d-3b72be0384c8'
-        )
-
-    def test_priv_client_to_client_id_STR(self):
-        self.assertEqual(
-            self.driver._client_to_client_id('30b1ff76-c76d-4d7c-b39d-3b72be0384c8'),
-            '30b1ff76-c76d-4d7c-b39d-3b72be0384c8'
-        )
-
-    def test_priv_client_to_client_id_TYPEERROR(self):
-        with self.assertRaises(TypeError):
-            self.driver._client_to_client_id([1, 2, 3])
-
-
-class InvalidRequestError(Exception):
-    def __init__(self, tag):
-        super(InvalidRequestError, self).__init__("Invalid Request - %s" % tag)
-
-
-class DimensionDataMockHttp(MockHttp):
-
-    fixtures = BackupFileFixtures('dimensiondata')
-
-    def _oec_0_9_myaccount_UNAUTHORIZED(self, method, url, body, headers):
-        return (httplib.UNAUTHORIZED, "", {}, httplib.responses[httplib.UNAUTHORIZED])
-
-    def _oec_0_9_myaccount(self, method, url, body, headers):
-        body = self.fixtures.load('oec_0_9_myaccount.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _oec_0_9_myaccount_EXISTS(self, method, url, body, headers):
-        body = self.fixtures.load('oec_0_9_myaccount.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _oec_0_9_myaccount_DEFAULT(self, method, url, body, headers):
-        body = self.fixtures.load('oec_0_9_myaccount.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _oec_0_9_myaccount_INPROGRESS(self, method, url, body, headers):
-        body = self.fixtures.load('oec_0_9_myaccount.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _oec_0_9_myaccount_FAIL(self, method, url, body, headers):
-        body = self.fixtures.load('oec_0_9_myaccount.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _oec_0_9_myaccount_NOCLIENT(self, method, url, body, headers):
-        body = self.fixtures.load('oec_0_9_myaccount.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _oec_0_9_myaccount_DISABLED(self, method, url, body, headers):
-        body = self.fixtures.load('oec_0_9_myaccount.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _oec_0_9_myaccount_NOJOB(self, method, url, body, headers):
-        body = self.fixtures.load('oec_0_9_myaccount.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _caas_2_4_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_server_e75ead52_692f_4314_8725_c8a4f4d13a87(self, method, url, body, headers):
-        body = self.fixtures.load(
-            'server_server_e75ead52_692f_4314_8725_c8a4f4d13a87.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _caas_2_4_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_server_e75ead52_692f_4314_8725_c8a4f4d13a87_DEFAULT(self, method, url, body, headers):
-        body = self.fixtures.load(
-            'server_server_e75ead52_692f_4314_8725_c8a4f4d13a87_DEFAULT.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _caas_2_4_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_server_e75ead52_692f_4314_8725_c8a4f4d13a87_NOCLIENT(self, method, url, body, headers):
-        body = self.fixtures.load(
-            'server_server_e75ead52_692f_4314_8725_c8a4f4d13a87_DEFAULT.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _caas_2_4_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_server_e75ead52_692f_4314_8725_c8a4f4d13a87_NOJOB(self, method, url, body, headers):
-        body = self.fixtures.load(
-            'server_server_e75ead52_692f_4314_8725_c8a4f4d13a87_DEFAULT.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _caas_2_4_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_server_e75ead52_692f_4314_8725_c8a4f4d13a87_DISABLED(self, method, url, body, headers):
-        body = self.fixtures.load(
-            'server_server_e75ead52_692f_4314_8725_c8a4f4d13a87_DEFAULT.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _caas_2_4_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_server(self, method, url, body, headers):
-        body = self.fixtures.load(
-            'server_server.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_client_type(self, method, url, body, headers):
-        body = self.fixtures.load(
-            '_backup_client_type.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_client_storagePolicy(
-            self, method, url, body, headers):
-        body = self.fixtures.load(
-            '_backup_client_storagePolicy.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_client_schedulePolicy(
-            self, method, url, body, headers):
-        body = self.fixtures.load(
-            '_backup_client_schedulePolicy.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_client(
-            self, method, url, body, headers):
-        if method == 'POST':
-            body = self.fixtures.load(
-                '_backup_client_SUCCESS_PUT.xml')
-            return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-        else:
-            raise ValueError("Unknown Method {0}".format(method))
-
-    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_NOCLIENT(
-            self, method, url, body, headers):
-        # only gets here are implemented
-        # If we get any other method something has gone wrong
-        assert(method == 'GET')
-        body = self.fixtures.load(
-            '_backup_INFO_NOCLIENT.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_DISABLED(
-            self, method, url, body, headers):
-        # only gets here are implemented
-        # If we get any other method something has gone wrong
-        assert(method == 'GET')
-        body = self.fixtures.load(
-            '_backup_INFO_DISABLED.xml')
-        return (httplib.BAD_REQUEST, body, {}, httplib.responses[httplib.OK])
-
-    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_NOJOB(
-            self, method, url, body, headers):
-        # only gets here are implemented
-        # If we get any other method something has gone wrong
-        assert(method == 'GET')
-        body = self.fixtures.load(
-            '_backup_INFO_NOJOB.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_DEFAULT(
-            self, method, url, body, headers):
-        if method != 'POST':
-            raise InvalidRequestError('Only POST is accepted for this test')
-        request = ET.fromstring(body)
-        service_plan = request.get('servicePlan')
-        if service_plan != DEFAULT_BACKUP_PLAN:
-            raise InvalidRequestError('The default plan %s should have been passed in.  Not %s' % (DEFAULT_BACKUP_PLAN, service_plan))
-        body = self.fixtures.load(
-            '_backup_ENABLE.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup(
-            self, method, url, body, headers):
-        if method == 'POST':
-            body = self.fixtures.load(
-                '_backup_ENABLE.xml')
-            return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-        elif method == 'GET':
-            if url.endswith('disable'):
-                body = self.fixtures.load(
-                    '_backup_DISABLE.xml')
-                return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-            body = self.fixtures.load(
-                '_backup_INFO.xml')
-            return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-        else:
-            raise ValueError("Unknown Method {0}".format(method))
-
-    def _caas_2_4_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_server_e75ead52_692f_4314_8725_c8a4f4d13a87_NOBACKUP(
-            self, method, url, body, headers):
-        assert(method == 'GET')
-        body = self.fixtures.load('server_server_NOBACKUP.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_EXISTS(
-            self, method, url, body, headers):
-        # only POSTs are implemented
-        # If we get any other method something has gone wrong
-        assert(method == 'POST')
-        body = self.fixtures.load(
-            '_backup_EXISTS.xml')
-        return (httplib.BAD_REQUEST, body, {}, httplib.responses[httplib.OK])
-
-    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_modify(
-            self, method, url, body, headers):
-        request = ET.fromstring(body)
-        service_plan = request.get('servicePlan')
-        if service_plan != 'Essentials':
-            raise InvalidRequestError("Expected Essentials backup plan in request")
-        body = self.fixtures.load('_backup_modify.xml')
-
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_modify_DEFAULT(
-            self, method, url, body, headers):
-        request = ET.fromstring(body)
-        service_plan = request.get('servicePlan')
-        if service_plan != DEFAULT_BACKUP_PLAN:
-            raise InvalidRequestError("Expected % backup plan in test" % DEFAULT_BACKUP_PLAN)
-        body = self.fixtures.load('_backup_modify.xml')
-
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_client_30b1ff76_c76d_4d7c_b39d_3b72be0384c8(
-            self, method, url, body, headers):
-        if url.endswith('disable'):
-            body = self.fixtures.load(
-                ('_remove_backup_client.xml')
-            )
-        elif url.endswith('cancelJob'):
-            body = self.fixtures.load(
-                (''
-                 '_backup_client_30b1ff76_c76d_4d7c_b39d_3b72be0384c8_cancelJob.xml')
-            )
-        else:
-            raise ValueError("Unknown URL: %s" % url)
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_client_30b1ff76_c76d_4d7c_b39d_3b72be0384c8_FAIL(
-            self, method, url, body, headers):
-        if url.endswith('disable'):
-            body = self.fixtures.load(
-                ('_remove_backup_client_FAIL.xml')
-            )
-        elif url.endswith('cancelJob'):
-            body = self.fixtures.load(
-                (''
-                 '_backup_client_30b1ff76_c76d_4d7c_b39d_3b72be0384c8_cancelJob_FAIL.xml')
-            )
-        else:
-            raise ValueError("Unknown URL: %s" % url)
-        return (httplib.BAD_REQUEST, body, {}, httplib.responses[httplib.OK])
-
-
-if __name__ == '__main__':
-    sys.exit(unittest.main())

http://git-wip-us.apache.org/repos/asf/libcloud/blob/bb1b8104/libcloud/test/backup/test_dimensiondata_v2_3.py
----------------------------------------------------------------------
diff --git a/libcloud/test/backup/test_dimensiondata_v2_3.py b/libcloud/test/backup/test_dimensiondata_v2_3.py
new file mode 100644
index 0000000..ae05316
--- /dev/null
+++ b/libcloud/test/backup/test_dimensiondata_v2_3.py
@@ -0,0 +1,503 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+try:
+    from lxml import etree as ET
+except ImportError:
+    from xml.etree import ElementTree as ET
+
+import sys
+from libcloud.utils.py3 import httplib
+
+from libcloud.common.dimensiondata import DimensionDataAPIException
+from libcloud.common.types import InvalidCredsError
+from libcloud.backup.base import BackupTargetJob
+from libcloud.backup.drivers.dimensiondata import DimensionDataBackupDriver as DimensionData
+from libcloud.backup.drivers.dimensiondata import DEFAULT_BACKUP_PLAN
+
+from libcloud.test import MockHttp, unittest
+from libcloud.test.backup import TestCaseMixin
+from libcloud.test.file_fixtures import BackupFileFixtures
+
+from libcloud.test.secrets import DIMENSIONDATA_PARAMS
+
+
+class DimensionDataTests(unittest.TestCase, TestCaseMixin):
+
+    def setUp(self):
+        DimensionData.connectionCls.active_api_version = '2.3'
+        DimensionData.connectionCls.conn_classes = (None, DimensionDataMockHttp)
+        DimensionDataMockHttp.type = None
+        self.driver = DimensionData(*DIMENSIONDATA_PARAMS)
+
+    def test_invalid_region(self):
+        with self.assertRaises(ValueError):
+            self.driver = DimensionData(*DIMENSIONDATA_PARAMS, region='blah')
+
+    def test_invalid_creds(self):
+        DimensionDataMockHttp.type = 'UNAUTHORIZED'
+        with self.assertRaises(InvalidCredsError):
+            self.driver.list_targets()
+
+    def test_list_targets(self):
+        targets = self.driver.list_targets()
+        self.assertEqual(len(targets), 2)
+        self.assertEqual(targets[0].id, '5579f3a7-4c32-4cf5-8a7e-b45c36a35c10')
+        self.assertEqual(targets[0].address, 'e75ead52-692f-4314-8725-c8a4f4d13a87')
+        self.assertEqual(targets[0].extra['servicePlan'], 'Enterprise')
+
+    def test_create_target(self):
+        target = self.driver.create_target(
+            'name',
+            'e75ead52-692f-4314-8725-c8a4f4d13a87',
+            extra={'servicePlan': 'Enterprise'})
+        self.assertEqual(target.id, 'ee7c4b64-f7af-4a4f-8384-be362273530f')
+        self.assertEqual(target.address, 'e75ead52-692f-4314-8725-c8a4f4d13a87')
+        self.assertEqual(target.extra['servicePlan'], 'Enterprise')
+
+    def test_create_target_DEFAULT(self):
+        DimensionDataMockHttp.type = 'DEFAULT'
+        target = self.driver.create_target(
+            'name',
+            'e75ead52-692f-4314-8725-c8a4f4d13a87')
+        self.assertEqual(target.id, 'ee7c4b64-f7af-4a4f-8384-be362273530f')
+        self.assertEqual(target.address, 'e75ead52-692f-4314-8725-c8a4f4d13a87')
+
+    def test_create_target_EXISTS(self):
+        DimensionDataMockHttp.type = 'EXISTS'
+        with self.assertRaises(DimensionDataAPIException) as context:
+            self.driver.create_target(
+                'name',
+                'e75ead52-692f-4314-8725-c8a4f4d13a87',
+                extra={'servicePlan': 'Enterprise'})
+        self.assertEqual(context.exception.code, 'ERROR')
+        self.assertEqual(context.exception.msg, 'Cloud backup for this server is already enabled or being enabled (state: NORMAL).')
+
+    def test_update_target(self):
+        target = self.driver.list_targets()[0]
+        extra = {'servicePlan': 'Essentials'}
+        new_target = self.driver.update_target(target, extra=extra)
+        self.assertEqual(new_target.extra['servicePlan'], 'Essentials')
+
+    def test_update_target_DEFAULT(self):
+        DimensionDataMockHttp.type = 'DEFAULT'
+        target = 'e75ead52-692f-4314-8725-c8a4f4d13a87'
+        self.driver.update_target(target)
+
+    def test_update_target_STR(self):
+        target = 'e75ead52-692f-4314-8725-c8a4f4d13a87'
+        extra = {'servicePlan': 'Essentials'}
+        new_target = self.driver.update_target(target, extra=extra)
+        self.assertEqual(new_target.extra['servicePlan'], 'Essentials')
+
+    def test_delete_target(self):
+        target = self.driver.list_targets()[0]
+        self.assertTrue(self.driver.delete_target(target))
+
+    def test_ex_add_client_to_target(self):
+        target = self.driver.list_targets()[0]
+        client = self.driver.ex_list_available_client_types(target)[0]
+        storage_policy = self.driver.ex_list_available_storage_policies(target)[0]
+        schedule_policy = self.driver.ex_list_available_schedule_policies(target)[0]
+        self.assertTrue(
+            self.driver.ex_add_client_to_target(target, client, storage_policy,
+                                                schedule_policy, 'ON_FAILURE', 'nobody@example.com')
+        )
+
+    def test_ex_add_client_to_target_STR(self):
+        self.assertTrue(
+            self.driver.ex_add_client_to_target('e75ead52-692f-4314-8725-c8a4f4d13a87', 'FA.Linux', '14 Day Storage Policy',
+                                                '12AM - 6AM', 'ON_FAILURE', 'nobody@example.com')
+        )
+
+    def test_ex_get_backup_details_for_target(self):
+        target = self.driver.list_targets()[0]
+        response = self.driver.ex_get_backup_details_for_target(target)
+        self.assertEqual(response.service_plan, 'Enterprise')
+        client = response.clients[0]
+        self.assertEqual(client.id, '30b1ff76-c76d-4d7c-b39d-3b72be0384c8')
+        self.assertEqual(client.type.type, 'FA.Linux')
+        self.assertEqual(client.running_job.progress, 5)
+        self.assertTrue(isinstance(client.running_job, BackupTargetJob))
+        self.assertEqual(len(client.alert.notify_list), 2)
+        self.assertTrue(isinstance(client.alert.notify_list, list))
+
+    def test_ex_get_backup_details_for_target_NOBACKUP(self):
+        target = self.driver.list_targets()[0].address
+        DimensionDataMockHttp.type = 'NOBACKUP'
+        response = self.driver.ex_get_backup_details_for_target(target)
+        self.assertTrue(response is None)
+
+    def test_ex_cancel_target_job(self):
+        target = self.driver.list_targets()[0]
+        response = self.driver.ex_get_backup_details_for_target(target)
+        client = response.clients[0]
+        self.assertTrue(isinstance(client.running_job, BackupTargetJob))
+        success = client.running_job.cancel()
+        self.assertTrue(success)
+
+    def test_ex_cancel_target_job_with_extras(self):
+        success = self.driver.cancel_target_job(
+            None,
+            ex_client='30b1ff76_c76d_4d7c_b39d_3b72be0384c8',
+            ex_target='e75ead52_692f_4314_8725_c8a4f4d13a87'
+        )
+        self.assertTrue(success)
+
+    def test_ex_cancel_target_job_FAIL(self):
+        DimensionDataMockHttp.type = 'FAIL'
+        with self.assertRaises(DimensionDataAPIException) as context:
+            self.driver.cancel_target_job(
+                None,
+                ex_client='30b1ff76_c76d_4d7c_b39d_3b72be0384c8',
+                ex_target='e75ead52_692f_4314_8725_c8a4f4d13a87'
+            )
+        self.assertEqual(context.exception.code, 'ERROR')
+
+    """Test a backup info for a target that does not have a client"""
+    def test_ex_get_backup_details_for_target_NO_CLIENT(self):
+        DimensionDataMockHttp.type = 'NOCLIENT'
+        response = self.driver.ex_get_backup_details_for_target('e75ead52-692f-4314-8725-c8a4f4d13a87')
+        self.assertEqual(response.service_plan, 'Essentials')
+        self.assertEqual(len(response.clients), 0)
+
+    """Test a backup details that has a client, but no alerting or running jobs"""
+    def test_ex_get_backup_details_for_target_NO_JOB_OR_ALERT(self):
+        DimensionDataMockHttp.type = 'NOJOB'
+        response = self.driver.ex_get_backup_details_for_target('e75ead52-692f-4314_8725-c8a4f4d13a87')
+        self.assertEqual(response.service_plan, 'Enterprise')
+        self.assertTrue(isinstance(response.clients, list))
+        self.assertEqual(len(response.clients), 1)
+        client = response.clients[0]
+        self.assertEqual(client.id, '30b1ff76-c76d-4d7c-b39d-3b72be0384c8')
+        self.assertEqual(client.type.type, 'FA.Linux')
+        self.assertIsNone(client.running_job)
+        self.assertIsNone(client.alert)
+
+    """Test getting backup info for a server that doesn't exist"""
+    def test_ex_get_backup_details_for_target_DISABLED(self):
+        DimensionDataMockHttp.type = 'DISABLED'
+        with self.assertRaises(DimensionDataAPIException) as context:
+            self.driver.ex_get_backup_details_for_target('e75ead52-692f-4314-8725-c8a4f4d13a87')
+        self.assertEqual(context.exception.code, 'ERROR')
+        self.assertEqual(context.exception.msg, 'Server e75ead52-692f-4314-8725-c8a4f4d13a87 has not been provisioned for backup')
+
+    def test_ex_list_available_client_types(self):
+        target = self.driver.list_targets()[0]
+        answer = self.driver.ex_list_available_client_types(target)
+        self.assertEqual(len(answer), 1)
+        self.assertEqual(answer[0].type, 'FA.Linux')
+        self.assertEqual(answer[0].is_file_system, True)
+        self.assertEqual(answer[0].description, 'Linux File system')
+
+    def test_ex_list_available_storage_policies(self):
+        target = self.driver.list_targets()[0]
+        answer = self.driver.ex_list_available_storage_policies(target)
+        self.assertEqual(len(answer), 1)
+        self.assertEqual(answer[0].name,
+                         '30 Day Storage Policy + Secondary Copy')
+        self.assertEqual(answer[0].retention_period, 30)
+        self.assertEqual(answer[0].secondary_location, 'Primary')
+
+    def test_ex_list_available_schedule_policies(self):
+        target = self.driver.list_targets()[0]
+        answer = self.driver.ex_list_available_schedule_policies(target)
+        self.assertEqual(len(answer), 1)
+        self.assertEqual(answer[0].name, '12AM - 6AM')
+        self.assertEqual(answer[0].description, 'Daily backup will start between 12AM - 6AM')
+
+    def test_ex_remove_client_from_target(self):
+        target = self.driver.list_targets()[0]
+        client = self.driver.ex_get_backup_details_for_target('e75ead52-692f-4314-8725-c8a4f4d13a87').clients[0]
+        self.assertTrue(self.driver.ex_remove_client_from_target(target, client))
+
+    def test_ex_remove_client_from_target_STR(self):
+        self.assertTrue(
+            self.driver.ex_remove_client_from_target(
+                'e75ead52-692f-4314-8725-c8a4f4d13a87',
+                '30b1ff76-c76d-4d7c-b39d-3b72be0384c8'
+            )
+        )
+
+    def test_ex_remove_client_from_target_FAIL(self):
+        DimensionDataMockHttp.type = 'FAIL'
+        with self.assertRaises(DimensionDataAPIException) as context:
+            self.driver.ex_remove_client_from_target(
+                'e75ead52-692f-4314-8725-c8a4f4d13a87',
+                '30b1ff76-c76d-4d7c-b39d-3b72be0384c8'
+            )
+        self.assertEqual(context.exception.code, 'ERROR')
+        self.assertTrue('Backup Client is currently performing another operation' in context.exception.msg)
+
+    def test_priv_target_to_target_address(self):
+        target = self.driver.list_targets()[0]
+        self.assertEqual(
+            self.driver._target_to_target_address(target),
+            'e75ead52-692f-4314-8725-c8a4f4d13a87'
+        )
+
+    def test_priv_target_to_target_address_STR(self):
+        self.assertEqual(
+            self.driver._target_to_target_address('e75ead52-692f-4314-8725-c8a4f4d13a87'),
+            'e75ead52-692f-4314-8725-c8a4f4d13a87'
+        )
+
+    def test_priv_target_to_target_address_TYPEERROR(self):
+        with self.assertRaises(TypeError):
+            self.driver._target_to_target_address([1, 2, 3])
+
+    def test_priv_client_to_client_id(self):
+        client = self.driver.ex_get_backup_details_for_target('e75ead52-692f-4314-8725-c8a4f4d13a87').clients[0]
+        self.assertEqual(
+            self.driver._client_to_client_id(client),
+            '30b1ff76-c76d-4d7c-b39d-3b72be0384c8'
+        )
+
+    def test_priv_client_to_client_id_STR(self):
+        self.assertEqual(
+            self.driver._client_to_client_id('30b1ff76-c76d-4d7c-b39d-3b72be0384c8'),
+            '30b1ff76-c76d-4d7c-b39d-3b72be0384c8'
+        )
+
+    def test_priv_client_to_client_id_TYPEERROR(self):
+        with self.assertRaises(TypeError):
+            self.driver._client_to_client_id([1, 2, 3])
+
+
+class InvalidRequestError(Exception):
+    def __init__(self, tag):
+        super(InvalidRequestError, self).__init__("Invalid Request - %s" % tag)
+
+
+class DimensionDataMockHttp(MockHttp):
+
+    fixtures = BackupFileFixtures('dimensiondata')
+
+    def _oec_0_9_myaccount_UNAUTHORIZED(self, method, url, body, headers):
+        return (httplib.UNAUTHORIZED, "", {}, httplib.responses[httplib.UNAUTHORIZED])
+
+    def _oec_0_9_myaccount(self, method, url, body, headers):
+        body = self.fixtures.load('oec_0_9_myaccount.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_myaccount_EXISTS(self, method, url, body, headers):
+        body = self.fixtures.load('oec_0_9_myaccount.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_myaccount_DEFAULT(self, method, url, body, headers):
+        body = self.fixtures.load('oec_0_9_myaccount.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_myaccount_INPROGRESS(self, method, url, body, headers):
+        body = self.fixtures.load('oec_0_9_myaccount.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_myaccount_FAIL(self, method, url, body, headers):
+        body = self.fixtures.load('oec_0_9_myaccount.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_myaccount_NOCLIENT(self, method, url, body, headers):
+        body = self.fixtures.load('oec_0_9_myaccount.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_myaccount_DISABLED(self, method, url, body, headers):
+        body = self.fixtures.load('oec_0_9_myaccount.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_myaccount_NOJOB(self, method, url, body, headers):
+        body = self.fixtures.load('oec_0_9_myaccount.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _caas_2_3_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_server_e75ead52_692f_4314_8725_c8a4f4d13a87(self, method, url, body, headers):
+        body = self.fixtures.load(
+            'server_server_e75ead52_692f_4314_8725_c8a4f4d13a87.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _caas_2_3_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_server_e75ead52_692f_4314_8725_c8a4f4d13a87_DEFAULT(self, method, url, body, headers):
+        body = self.fixtures.load(
+            'server_server_e75ead52_692f_4314_8725_c8a4f4d13a87_DEFAULT.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _caas_2_3_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_server_e75ead52_692f_4314_8725_c8a4f4d13a87_NOCLIENT(self, method, url, body, headers):
+        body = self.fixtures.load(
+            'server_server_e75ead52_692f_4314_8725_c8a4f4d13a87_DEFAULT.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _caas_2_3_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_server_e75ead52_692f_4314_8725_c8a4f4d13a87_NOJOB(self, method, url, body, headers):
+        body = self.fixtures.load(
+            'server_server_e75ead52_692f_4314_8725_c8a4f4d13a87_DEFAULT.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _caas_2_3_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_server_e75ead52_692f_4314_8725_c8a4f4d13a87_DISABLED(self, method, url, body, headers):
+        body = self.fixtures.load(
+            'server_server_e75ead52_692f_4314_8725_c8a4f4d13a87_DEFAULT.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _caas_2_3_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_server(self, method, url, body, headers):
+        body = self.fixtures.load(
+            'server_server.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_client_type(self, method, url, body, headers):
+        body = self.fixtures.load(
+            '_backup_client_type.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_client_storagePolicy(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            '_backup_client_storagePolicy.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_client_schedulePolicy(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            '_backup_client_schedulePolicy.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_client(
+            self, method, url, body, headers):
+        if method == 'POST':
+            body = self.fixtures.load(
+                '_backup_client_SUCCESS_PUT.xml')
+            return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+        else:
+            raise ValueError("Unknown Method {0}".format(method))
+
+    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_NOCLIENT(
+            self, method, url, body, headers):
+        # only gets here are implemented
+        # If we get any other method something has gone wrong
+        assert(method == 'GET')
+        body = self.fixtures.load(
+            '_backup_INFO_NOCLIENT.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_DISABLED(
+            self, method, url, body, headers):
+        # only gets here are implemented
+        # If we get any other method something has gone wrong
+        assert(method == 'GET')
+        body = self.fixtures.load(
+            '_backup_INFO_DISABLED.xml')
+        return (httplib.BAD_REQUEST, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_NOJOB(
+            self, method, url, body, headers):
+        # only gets here are implemented
+        # If we get any other method something has gone wrong
+        assert(method == 'GET')
+        body = self.fixtures.load(
+            '_backup_INFO_NOJOB.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_DEFAULT(
+            self, method, url, body, headers):
+        if method != 'POST':
+            raise InvalidRequestError('Only POST is accepted for this test')
+        request = ET.fromstring(body)
+        service_plan = request.get('servicePlan')
+        if service_plan != DEFAULT_BACKUP_PLAN:
+            raise InvalidRequestError('The default plan %s should have been passed in.  Not %s' % (DEFAULT_BACKUP_PLAN, service_plan))
+        body = self.fixtures.load(
+            '_backup_ENABLE.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup(
+            self, method, url, body, headers):
+        if method == 'POST':
+            body = self.fixtures.load(
+                '_backup_ENABLE.xml')
+            return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+        elif method == 'GET':
+            if url.endswith('disable'):
+                body = self.fixtures.load(
+                    '_backup_DISABLE.xml')
+                return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+            body = self.fixtures.load(
+                '_backup_INFO.xml')
+            return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+        else:
+            raise ValueError("Unknown Method {0}".format(method))
+
+    def _caas_2_3_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_server_e75ead52_692f_4314_8725_c8a4f4d13a87_NOBACKUP(
+            self, method, url, body, headers):
+        assert(method == 'GET')
+        body = self.fixtures.load('server_server_NOBACKUP.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_EXISTS(
+            self, method, url, body, headers):
+        # only POSTs are implemented
+        # If we get any other method something has gone wrong
+        assert(method == 'POST')
+        body = self.fixtures.load(
+            '_backup_EXISTS.xml')
+        return (httplib.BAD_REQUEST, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_modify(
+            self, method, url, body, headers):
+        request = ET.fromstring(body)
+        service_plan = request.get('servicePlan')
+        if service_plan != 'Essentials':
+            raise InvalidRequestError("Expected Essentials backup plan in request")
+        body = self.fixtures.load('_backup_modify.xml')
+
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_modify_DEFAULT(
+            self, method, url, body, headers):
+        request = ET.fromstring(body)
+        service_plan = request.get('servicePlan')
+        if service_plan != DEFAULT_BACKUP_PLAN:
+            raise InvalidRequestError("Expected % backup plan in test" % DEFAULT_BACKUP_PLAN)
+        body = self.fixtures.load('_backup_modify.xml')
+
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_client_30b1ff76_c76d_4d7c_b39d_3b72be0384c8(
+            self, method, url, body, headers):
+        if url.endswith('disable'):
+            body = self.fixtures.load(
+                ('_remove_backup_client.xml')
+            )
+        elif url.endswith('cancelJob'):
+            body = self.fixtures.load(
+                (''
+                 '_backup_client_30b1ff76_c76d_4d7c_b39d_3b72be0384c8_cancelJob.xml')
+            )
+        else:
+            raise ValueError("Unknown URL: %s" % url)
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_client_30b1ff76_c76d_4d7c_b39d_3b72be0384c8_FAIL(
+            self, method, url, body, headers):
+        if url.endswith('disable'):
+            body = self.fixtures.load(
+                ('_remove_backup_client_FAIL.xml')
+            )
+        elif url.endswith('cancelJob'):
+            body = self.fixtures.load(
+                (''
+                 '_backup_client_30b1ff76_c76d_4d7c_b39d_3b72be0384c8_cancelJob_FAIL.xml')
+            )
+        else:
+            raise ValueError("Unknown URL: %s" % url)
+        return (httplib.BAD_REQUEST, body, {}, httplib.responses[httplib.OK])
+
+
+if __name__ == '__main__':
+    sys.exit(unittest.main())

http://git-wip-us.apache.org/repos/asf/libcloud/blob/bb1b8104/libcloud/test/backup/test_dimensiondata_v2_4.py
----------------------------------------------------------------------
diff --git a/libcloud/test/backup/test_dimensiondata_v2_4.py b/libcloud/test/backup/test_dimensiondata_v2_4.py
new file mode 100644
index 0000000..d5da857
--- /dev/null
+++ b/libcloud/test/backup/test_dimensiondata_v2_4.py
@@ -0,0 +1,503 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+try:
+    from lxml import etree as ET
+except ImportError:
+    from xml.etree import ElementTree as ET
+
+import sys
+from libcloud.utils.py3 import httplib
+
+from libcloud.common.dimensiondata import DimensionDataAPIException
+from libcloud.common.types import InvalidCredsError
+from libcloud.backup.base import BackupTargetJob
+from libcloud.backup.drivers.dimensiondata import DimensionDataBackupDriver as DimensionData
+from libcloud.backup.drivers.dimensiondata import DEFAULT_BACKUP_PLAN
+
+from libcloud.test import MockHttp, unittest
+from libcloud.test.backup import TestCaseMixin
+from libcloud.test.file_fixtures import BackupFileFixtures
+
+from libcloud.test.secrets import DIMENSIONDATA_PARAMS
+
+
+class DimensionDataTests(unittest.TestCase, TestCaseMixin):
+
+    def setUp(self):
+        DimensionData.connectionCls.active_api_version = '2.4'
+        DimensionData.connectionCls.conn_classes = (None, DimensionDataMockHttp)
+        DimensionDataMockHttp.type = None
+        self.driver = DimensionData(*DIMENSIONDATA_PARAMS)
+
+    def test_invalid_region(self):
+        with self.assertRaises(ValueError):
+            self.driver = DimensionData(*DIMENSIONDATA_PARAMS, region='blah')
+
+    def test_invalid_creds(self):
+        DimensionDataMockHttp.type = 'UNAUTHORIZED'
+        with self.assertRaises(InvalidCredsError):
+            self.driver.list_targets()
+
+    def test_list_targets(self):
+        targets = self.driver.list_targets()
+        self.assertEqual(len(targets), 2)
+        self.assertEqual(targets[0].id, '5579f3a7-4c32-4cf5-8a7e-b45c36a35c10')
+        self.assertEqual(targets[0].address, 'e75ead52-692f-4314-8725-c8a4f4d13a87')
+        self.assertEqual(targets[0].extra['servicePlan'], 'Enterprise')
+
+    def test_create_target(self):
+        target = self.driver.create_target(
+            'name',
+            'e75ead52-692f-4314-8725-c8a4f4d13a87',
+            extra={'servicePlan': 'Enterprise'})
+        self.assertEqual(target.id, 'ee7c4b64-f7af-4a4f-8384-be362273530f')
+        self.assertEqual(target.address, 'e75ead52-692f-4314-8725-c8a4f4d13a87')
+        self.assertEqual(target.extra['servicePlan'], 'Enterprise')
+
+    def test_create_target_DEFAULT(self):
+        DimensionDataMockHttp.type = 'DEFAULT'
+        target = self.driver.create_target(
+            'name',
+            'e75ead52-692f-4314-8725-c8a4f4d13a87')
+        self.assertEqual(target.id, 'ee7c4b64-f7af-4a4f-8384-be362273530f')
+        self.assertEqual(target.address, 'e75ead52-692f-4314-8725-c8a4f4d13a87')
+
+    def test_create_target_EXISTS(self):
+        DimensionDataMockHttp.type = 'EXISTS'
+        with self.assertRaises(DimensionDataAPIException) as context:
+            self.driver.create_target(
+                'name',
+                'e75ead52-692f-4314-8725-c8a4f4d13a87',
+                extra={'servicePlan': 'Enterprise'})
+        self.assertEqual(context.exception.code, 'ERROR')
+        self.assertEqual(context.exception.msg, 'Cloud backup for this server is already enabled or being enabled (state: NORMAL).')
+
+    def test_update_target(self):
+        target = self.driver.list_targets()[0]
+        extra = {'servicePlan': 'Essentials'}
+        new_target = self.driver.update_target(target, extra=extra)
+        self.assertEqual(new_target.extra['servicePlan'], 'Essentials')
+
+    def test_update_target_DEFAULT(self):
+        DimensionDataMockHttp.type = 'DEFAULT'
+        target = 'e75ead52-692f-4314-8725-c8a4f4d13a87'
+        self.driver.update_target(target)
+
+    def test_update_target_STR(self):
+        target = 'e75ead52-692f-4314-8725-c8a4f4d13a87'
+        extra = {'servicePlan': 'Essentials'}
+        new_target = self.driver.update_target(target, extra=extra)
+        self.assertEqual(new_target.extra['servicePlan'], 'Essentials')
+
+    def test_delete_target(self):
+        target = self.driver.list_targets()[0]
+        self.assertTrue(self.driver.delete_target(target))
+
+    def test_ex_add_client_to_target(self):
+        target = self.driver.list_targets()[0]
+        client = self.driver.ex_list_available_client_types(target)[0]
+        storage_policy = self.driver.ex_list_available_storage_policies(target)[0]
+        schedule_policy = self.driver.ex_list_available_schedule_policies(target)[0]
+        self.assertTrue(
+            self.driver.ex_add_client_to_target(target, client, storage_policy,
+                                                schedule_policy, 'ON_FAILURE', 'nobody@example.com')
+        )
+
+    def test_ex_add_client_to_target_STR(self):
+        self.assertTrue(
+            self.driver.ex_add_client_to_target('e75ead52-692f-4314-8725-c8a4f4d13a87', 'FA.Linux', '14 Day Storage Policy',
+                                                '12AM - 6AM', 'ON_FAILURE', 'nobody@example.com')
+        )
+
+    def test_ex_get_backup_details_for_target(self):
+        target = self.driver.list_targets()[0]
+        response = self.driver.ex_get_backup_details_for_target(target)
+        self.assertEqual(response.service_plan, 'Enterprise')
+        client = response.clients[0]
+        self.assertEqual(client.id, '30b1ff76-c76d-4d7c-b39d-3b72be0384c8')
+        self.assertEqual(client.type.type, 'FA.Linux')
+        self.assertEqual(client.running_job.progress, 5)
+        self.assertTrue(isinstance(client.running_job, BackupTargetJob))
+        self.assertEqual(len(client.alert.notify_list), 2)
+        self.assertTrue(isinstance(client.alert.notify_list, list))
+
+    def test_ex_get_backup_details_for_target_NOBACKUP(self):
+        target = self.driver.list_targets()[0].address
+        DimensionDataMockHttp.type = 'NOBACKUP'
+        response = self.driver.ex_get_backup_details_for_target(target)
+        self.assertTrue(response is None)
+
+    def test_ex_cancel_target_job(self):
+        target = self.driver.list_targets()[0]
+        response = self.driver.ex_get_backup_details_for_target(target)
+        client = response.clients[0]
+        self.assertTrue(isinstance(client.running_job, BackupTargetJob))
+        success = client.running_job.cancel()
+        self.assertTrue(success)
+
+    def test_ex_cancel_target_job_with_extras(self):
+        success = self.driver.cancel_target_job(
+            None,
+            ex_client='30b1ff76_c76d_4d7c_b39d_3b72be0384c8',
+            ex_target='e75ead52_692f_4314_8725_c8a4f4d13a87'
+        )
+        self.assertTrue(success)
+
+    def test_ex_cancel_target_job_FAIL(self):
+        DimensionDataMockHttp.type = 'FAIL'
+        with self.assertRaises(DimensionDataAPIException) as context:
+            self.driver.cancel_target_job(
+                None,
+                ex_client='30b1ff76_c76d_4d7c_b39d_3b72be0384c8',
+                ex_target='e75ead52_692f_4314_8725_c8a4f4d13a87'
+            )
+        self.assertEqual(context.exception.code, 'ERROR')
+
+    """Test a backup info for a target that does not have a client"""
+    def test_ex_get_backup_details_for_target_NO_CLIENT(self):
+        DimensionDataMockHttp.type = 'NOCLIENT'
+        response = self.driver.ex_get_backup_details_for_target('e75ead52-692f-4314-8725-c8a4f4d13a87')
+        self.assertEqual(response.service_plan, 'Essentials')
+        self.assertEqual(len(response.clients), 0)
+
+    """Test a backup details that has a client, but no alerting or running jobs"""
+    def test_ex_get_backup_details_for_target_NO_JOB_OR_ALERT(self):
+        DimensionDataMockHttp.type = 'NOJOB'
+        response = self.driver.ex_get_backup_details_for_target('e75ead52-692f-4314_8725-c8a4f4d13a87')
+        self.assertEqual(response.service_plan, 'Enterprise')
+        self.assertTrue(isinstance(response.clients, list))
+        self.assertEqual(len(response.clients), 1)
+        client = response.clients[0]
+        self.assertEqual(client.id, '30b1ff76-c76d-4d7c-b39d-3b72be0384c8')
+        self.assertEqual(client.type.type, 'FA.Linux')
+        self.assertIsNone(client.running_job)
+        self.assertIsNone(client.alert)
+
+    """Test getting backup info for a server that doesn't exist"""
+    def test_ex_get_backup_details_for_target_DISABLED(self):
+        DimensionDataMockHttp.type = 'DISABLED'
+        with self.assertRaises(DimensionDataAPIException) as context:
+            self.driver.ex_get_backup_details_for_target('e75ead52-692f-4314-8725-c8a4f4d13a87')
+        self.assertEqual(context.exception.code, 'ERROR')
+        self.assertEqual(context.exception.msg, 'Server e75ead52-692f-4314-8725-c8a4f4d13a87 has not been provisioned for backup')
+
+    def test_ex_list_available_client_types(self):
+        target = self.driver.list_targets()[0]
+        answer = self.driver.ex_list_available_client_types(target)
+        self.assertEqual(len(answer), 1)
+        self.assertEqual(answer[0].type, 'FA.Linux')
+        self.assertEqual(answer[0].is_file_system, True)
+        self.assertEqual(answer[0].description, 'Linux File system')
+
+    def test_ex_list_available_storage_policies(self):
+        target = self.driver.list_targets()[0]
+        answer = self.driver.ex_list_available_storage_policies(target)
+        self.assertEqual(len(answer), 1)
+        self.assertEqual(answer[0].name,
+                         '30 Day Storage Policy + Secondary Copy')
+        self.assertEqual(answer[0].retention_period, 30)
+        self.assertEqual(answer[0].secondary_location, 'Primary')
+
+    def test_ex_list_available_schedule_policies(self):
+        target = self.driver.list_targets()[0]
+        answer = self.driver.ex_list_available_schedule_policies(target)
+        self.assertEqual(len(answer), 1)
+        self.assertEqual(answer[0].name, '12AM - 6AM')
+        self.assertEqual(answer[0].description, 'Daily backup will start between 12AM - 6AM')
+
+    def test_ex_remove_client_from_target(self):
+        target = self.driver.list_targets()[0]
+        client = self.driver.ex_get_backup_details_for_target('e75ead52-692f-4314-8725-c8a4f4d13a87').clients[0]
+        self.assertTrue(self.driver.ex_remove_client_from_target(target, client))
+
+    def test_ex_remove_client_from_target_STR(self):
+        self.assertTrue(
+            self.driver.ex_remove_client_from_target(
+                'e75ead52-692f-4314-8725-c8a4f4d13a87',
+                '30b1ff76-c76d-4d7c-b39d-3b72be0384c8'
+            )
+        )
+
+    def test_ex_remove_client_from_target_FAIL(self):
+        DimensionDataMockHttp.type = 'FAIL'
+        with self.assertRaises(DimensionDataAPIException) as context:
+            self.driver.ex_remove_client_from_target(
+                'e75ead52-692f-4314-8725-c8a4f4d13a87',
+                '30b1ff76-c76d-4d7c-b39d-3b72be0384c8'
+            )
+        self.assertEqual(context.exception.code, 'ERROR')
+        self.assertTrue('Backup Client is currently performing another operation' in context.exception.msg)
+
+    def test_priv_target_to_target_address(self):
+        target = self.driver.list_targets()[0]
+        self.assertEqual(
+            self.driver._target_to_target_address(target),
+            'e75ead52-692f-4314-8725-c8a4f4d13a87'
+        )
+
+    def test_priv_target_to_target_address_STR(self):
+        self.assertEqual(
+            self.driver._target_to_target_address('e75ead52-692f-4314-8725-c8a4f4d13a87'),
+            'e75ead52-692f-4314-8725-c8a4f4d13a87'
+        )
+
+    def test_priv_target_to_target_address_TYPEERROR(self):
+        with self.assertRaises(TypeError):
+            self.driver._target_to_target_address([1, 2, 3])
+
+    def test_priv_client_to_client_id(self):
+        client = self.driver.ex_get_backup_details_for_target('e75ead52-692f-4314-8725-c8a4f4d13a87').clients[0]
+        self.assertEqual(
+            self.driver._client_to_client_id(client),
+            '30b1ff76-c76d-4d7c-b39d-3b72be0384c8'
+        )
+
+    def test_priv_client_to_client_id_STR(self):
+        self.assertEqual(
+            self.driver._client_to_client_id('30b1ff76-c76d-4d7c-b39d-3b72be0384c8'),
+            '30b1ff76-c76d-4d7c-b39d-3b72be0384c8'
+        )
+
+    def test_priv_client_to_client_id_TYPEERROR(self):
+        with self.assertRaises(TypeError):
+            self.driver._client_to_client_id([1, 2, 3])
+
+
+class InvalidRequestError(Exception):
+    def __init__(self, tag):
+        super(InvalidRequestError, self).__init__("Invalid Request - %s" % tag)
+
+
+class DimensionDataMockHttp(MockHttp):
+
+    fixtures = BackupFileFixtures('dimensiondata')
+
+    def _oec_0_9_myaccount_UNAUTHORIZED(self, method, url, body, headers):
+        return (httplib.UNAUTHORIZED, "", {}, httplib.responses[httplib.UNAUTHORIZED])
+
+    def _oec_0_9_myaccount(self, method, url, body, headers):
+        body = self.fixtures.load('oec_0_9_myaccount.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_myaccount_EXISTS(self, method, url, body, headers):
+        body = self.fixtures.load('oec_0_9_myaccount.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_myaccount_DEFAULT(self, method, url, body, headers):
+        body = self.fixtures.load('oec_0_9_myaccount.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_myaccount_INPROGRESS(self, method, url, body, headers):
+        body = self.fixtures.load('oec_0_9_myaccount.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_myaccount_FAIL(self, method, url, body, headers):
+        body = self.fixtures.load('oec_0_9_myaccount.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_myaccount_NOCLIENT(self, method, url, body, headers):
+        body = self.fixtures.load('oec_0_9_myaccount.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_myaccount_DISABLED(self, method, url, body, headers):
+        body = self.fixtures.load('oec_0_9_myaccount.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_myaccount_NOJOB(self, method, url, body, headers):
+        body = self.fixtures.load('oec_0_9_myaccount.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _caas_2_4_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_server_e75ead52_692f_4314_8725_c8a4f4d13a87(self, method, url, body, headers):
+        body = self.fixtures.load(
+            'server_server_e75ead52_692f_4314_8725_c8a4f4d13a87.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _caas_2_4_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_server_e75ead52_692f_4314_8725_c8a4f4d13a87_DEFAULT(self, method, url, body, headers):
+        body = self.fixtures.load(
+            'server_server_e75ead52_692f_4314_8725_c8a4f4d13a87_DEFAULT.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _caas_2_4_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_server_e75ead52_692f_4314_8725_c8a4f4d13a87_NOCLIENT(self, method, url, body, headers):
+        body = self.fixtures.load(
+            'server_server_e75ead52_692f_4314_8725_c8a4f4d13a87_DEFAULT.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _caas_2_4_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_server_e75ead52_692f_4314_8725_c8a4f4d13a87_NOJOB(self, method, url, body, headers):
+        body = self.fixtures.load(
+            'server_server_e75ead52_692f_4314_8725_c8a4f4d13a87_DEFAULT.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _caas_2_4_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_server_e75ead52_692f_4314_8725_c8a4f4d13a87_DISABLED(self, method, url, body, headers):
+        body = self.fixtures.load(
+            'server_server_e75ead52_692f_4314_8725_c8a4f4d13a87_DEFAULT.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _caas_2_4_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_server(self, method, url, body, headers):
+        body = self.fixtures.load(
+            'server_server.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_client_type(self, method, url, body, headers):
+        body = self.fixtures.load(
+            '_backup_client_type.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_client_storagePolicy(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            '_backup_client_storagePolicy.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_client_schedulePolicy(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            '_backup_client_schedulePolicy.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_client(
+            self, method, url, body, headers):
+        if method == 'POST':
+            body = self.fixtures.load(
+                '_backup_client_SUCCESS_PUT.xml')
+            return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+        else:
+            raise ValueError("Unknown Method {0}".format(method))
+
+    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_NOCLIENT(
+            self, method, url, body, headers):
+        # only gets here are implemented
+        # If we get any other method something has gone wrong
+        assert(method == 'GET')
+        body = self.fixtures.load(
+            '_backup_INFO_NOCLIENT.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_DISABLED(
+            self, method, url, body, headers):
+        # only gets here are implemented
+        # If we get any other method something has gone wrong
+        assert(method == 'GET')
+        body = self.fixtures.load(
+            '_backup_INFO_DISABLED.xml')
+        return (httplib.BAD_REQUEST, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_NOJOB(
+            self, method, url, body, headers):
+        # only gets here are implemented
+        # If we get any other method something has gone wrong
+        assert(method == 'GET')
+        body = self.fixtures.load(
+            '_backup_INFO_NOJOB.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_DEFAULT(
+            self, method, url, body, headers):
+        if method != 'POST':
+            raise InvalidRequestError('Only POST is accepted for this test')
+        request = ET.fromstring(body)
+        service_plan = request.get('servicePlan')
+        if service_plan != DEFAULT_BACKUP_PLAN:
+            raise InvalidRequestError('The default plan %s should have been passed in.  Not %s' % (DEFAULT_BACKUP_PLAN, service_plan))
+        body = self.fixtures.load(
+            '_backup_ENABLE.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup(
+            self, method, url, body, headers):
+        if method == 'POST':
+            body = self.fixtures.load(
+                '_backup_ENABLE.xml')
+            return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+        elif method == 'GET':
+            if url.endswith('disable'):
+                body = self.fixtures.load(
+                    '_backup_DISABLE.xml')
+                return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+            body = self.fixtures.load(
+                '_backup_INFO.xml')
+            return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+        else:
+            raise ValueError("Unknown Method {0}".format(method))
+
+    def _caas_2_4_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_server_e75ead52_692f_4314_8725_c8a4f4d13a87_NOBACKUP(
+            self, method, url, body, headers):
+        assert(method == 'GET')
+        body = self.fixtures.load('server_server_NOBACKUP.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_EXISTS(
+            self, method, url, body, headers):
+        # only POSTs are implemented
+        # If we get any other method something has gone wrong
+        assert(method == 'POST')
+        body = self.fixtures.load(
+            '_backup_EXISTS.xml')
+        return (httplib.BAD_REQUEST, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_modify(
+            self, method, url, body, headers):
+        request = ET.fromstring(body)
+        service_plan = request.get('servicePlan')
+        if service_plan != 'Essentials':
+            raise InvalidRequestError("Expected Essentials backup plan in request")
+        body = self.fixtures.load('_backup_modify.xml')
+
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_modify_DEFAULT(
+            self, method, url, body, headers):
+        request = ET.fromstring(body)
+        service_plan = request.get('servicePlan')
+        if service_plan != DEFAULT_BACKUP_PLAN:
+            raise InvalidRequestError("Expected % backup plan in test" % DEFAULT_BACKUP_PLAN)
+        body = self.fixtures.load('_backup_modify.xml')
+
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_client_30b1ff76_c76d_4d7c_b39d_3b72be0384c8(
+            self, method, url, body, headers):
+        if url.endswith('disable'):
+            body = self.fixtures.load(
+                ('_remove_backup_client.xml')
+            )
+        elif url.endswith('cancelJob'):
+            body = self.fixtures.load(
+                (''
+                 '_backup_client_30b1ff76_c76d_4d7c_b39d_3b72be0384c8_cancelJob.xml')
+            )
+        else:
+            raise ValueError("Unknown URL: %s" % url)
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _oec_0_9_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_server_e75ead52_692f_4314_8725_c8a4f4d13a87_backup_client_30b1ff76_c76d_4d7c_b39d_3b72be0384c8_FAIL(
+            self, method, url, body, headers):
+        if url.endswith('disable'):
+            body = self.fixtures.load(
+                ('_remove_backup_client_FAIL.xml')
+            )
+        elif url.endswith('cancelJob'):
+            body = self.fixtures.load(
+                (''
+                 '_backup_client_30b1ff76_c76d_4d7c_b39d_3b72be0384c8_cancelJob_FAIL.xml')
+            )
+        else:
+            raise ValueError("Unknown URL: %s" % url)
+        return (httplib.BAD_REQUEST, body, {}, httplib.responses[httplib.OK])
+
+
+if __name__ == '__main__':
+    sys.exit(unittest.main())

http://git-wip-us.apache.org/repos/asf/libcloud/blob/bb1b8104/libcloud/test/compute/fixtures/dimensiondata/2.4/image_customerImage.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/dimensiondata/2.4/image_customerImage.xml b/libcloud/test/compute/fixtures/dimensiondata/2.4/image_customerImage.xml
new file mode 100644
index 0000000..4e59e18
--- /dev/null
+++ b/libcloud/test/compute/fixtures/dimensiondata/2.4/image_customerImage.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes"?>
+<customerImages xmlns="urn:didata.com:api:cloud:types" pageNumber="1" pageCount="35" totalCount="35" pageSize="250">
+    <customerImage id="5234e5c7-01de-4411-8b6e-baeb8d91cf5d" datacenterId="NA9">
+        <name>ImportedCustomerImage</name>
+        <description />
+        <operatingSystem id="REDHAT664" displayName="REDHAT6/64" family="UNIX" />
+        <cpu count="4" speed="STANDARD" coresPerSocket="1" />
+        <memoryGb>2</memoryGb>
+        <disk id="1a82316f-23ed-4fe9-b6d8-6b92ac467423" scsiId="0" sizeGb="12" speed="STANDARD" />
+        <createTime>2015-11-19T14:29:02.000Z</createTime>
+        <source type="IMPORT">
+        <artifact type="MF" value="ImportedCustomerImage.mf" date="2015-1119T14:28:54.000Z" />
+        <artifact type="OVF" value="ImportedCustomerImage.ovf" date="2015-1119T14:28:05.000Z" />
+        <artifact type="VMDK" value="ImportedCustomerImage-disk1.vmdk" date="2015-11-19T12:22:31.000Z" /></source>
+        <state>NORMAL</state>
+        <vmwareTools versionStatus="NEED_UPGRADE" runningStatus="NOT_RUNNING" apiVersion="8389" />
+        <virtualHardware version="vmx-10" upToDate="true" />
+    </customerImage>
+    <customerImage id="2ffa36c8-1848-49eb-b4fa-9d908775f68c" datacenterId="NA9">
+        <name>CustomerImageWithPricedSoftwareLabels</name>
+        <description />
+        <operatingSystem id="WIN2008S32" displayName="WIN2008S/32" family="WINDOWS" />
+        <cpu count="1" speed="STANDARD" coresPerSocket="1" />
+        <memoryGb>1</memoryGb>
+        <disk id="29455efc-51af-4b4d-91b3-d81ca0dff7d8" scsiId="0" sizeGb="50" speed="STANDARD" />
+        <softwareLabel>MSSQL2008R2S</softwareLabel>
+        <createTime>2015-11-03T15:25:34.000Z</createTime>
+        <source type="CLONE">
+            <artifact type="SERVER_ID" value="7c9c2551-269d-4274-a247126ba7c6215c" />
+        </source>
+        <state>NORMAL</state>
+        <vmwareTools versionStatus="CURRENT" runningStatus="NOT_RUNNING" />
+        <virtualHardware version="vmx-08" upToDate="false" />
+    </customerImage>
+    <customerImage id="1fc1844f-45d6-4364-b447-f7c7645b47de" datacenterId="NA9">
+        <name>CopiedCustomerImage</name>
+        <description />
+        <operatingSystem id="REDHAT664" displayName="REDHAT6/64" family="UNIX" />
+        <cpu count="1" speed="STANDARD" coresPerSocket="1" />
+        <memoryGb>2</memoryGb>
+        <disk id="42b20819-c161-4dec-aa94-73ec370a6e37" scsiId="0" sizeGb="10" speed="STANDARD" />
+        <createTime>2015-11-11T17:17:00.000Z</createTime>
+        <source type="COPY">
+            <artifact type="IMAGE_ID" value="0b8357b6-f156-4b27-b4fd-b81d09c15efc" />
+        </source>
+        <state>NORMAL</state>
+        <vmwareTools versionStatus="NEED_UPGRADE" runningStatus="NOT_RUNNING" apiVersion="9355" />
+        <virtualHardware version="vmx-10" upToDate="true" />
+    </customerImage>
+</customerImages>

http://git-wip-us.apache.org/repos/asf/libcloud/blob/bb1b8104/libcloud/test/compute/fixtures/dimensiondata/2.4/image_customerImage_2ffa36c8_1848_49eb_b4fa_9d908775f68c.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/dimensiondata/2.4/image_customerImage_2ffa36c8_1848_49eb_b4fa_9d908775f68c.xml b/libcloud/test/compute/fixtures/dimensiondata/2.4/image_customerImage_2ffa36c8_1848_49eb_b4fa_9d908775f68c.xml
new file mode 100644
index 0000000..bff6183
--- /dev/null
+++ b/libcloud/test/compute/fixtures/dimensiondata/2.4/image_customerImage_2ffa36c8_1848_49eb_b4fa_9d908775f68c.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+    <customerImage xmlns="urn:didata.com:api:cloud:types" id="2ffa36c8-1848-49eb-b4fa-9d908775f68c" datacenterId="NA9">
+        <name>CustomerImageWithPricedSoftwareLabels</name>
+        <description />
+        <operatingSystem id="WIN2008S32" displayName="WIN2008S/32" family="WINDOWS" />
+        <cpu count="1" speed="STANDARD" coresPerSocket="1" />
+        <memoryGb>1</memoryGb>
+        <disk id="29455efc-51af-4b4d-91b3-d81ca0dff7d8" scsiId="0" sizeGb="50" speed="STANDARD" />
+        <softwareLabel>MSSQL2008R2S</softwareLabel>
+        <createTime>2015-11-03T15:25:34.000Z</createTime>
+        <source type="CLONE">
+            <artifact type="SERVER_ID" value="7c9c2551-269d-4274-a247126ba7c6215c" />
+        </source>
+        <state>NORMAL</state>
+        <vmwareTools versionStatus="CURRENT" runningStatus="NOT_RUNNING" />
+        <virtualHardware version="vmx-08" upToDate="false" />
+    </customerImage>

http://git-wip-us.apache.org/repos/asf/libcloud/blob/bb1b8104/libcloud/test/compute/fixtures/dimensiondata/2.4/image_customerImage_5234e5c7_01de_4411_8b6e_baeb8d91cf5d.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/dimensiondata/2.4/image_customerImage_5234e5c7_01de_4411_8b6e_baeb8d91cf5d.xml b/libcloud/test/compute/fixtures/dimensiondata/2.4/image_customerImage_5234e5c7_01de_4411_8b6e_baeb8d91cf5d.xml
new file mode 100644
index 0000000..db5302d
--- /dev/null
+++ b/libcloud/test/compute/fixtures/dimensiondata/2.4/image_customerImage_5234e5c7_01de_4411_8b6e_baeb8d91cf5d.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+    <customerImage xmlns="urn:didata.com:api:cloud:types" id="5234e5c7-01de-4411-8b6e-baeb8d91cf5d" datacenterId="NA9">
+        <name>ImportedCustomerImage</name>
+        <description />
+        <operatingSystem id="REDHAT664" displayName="REDHAT6/64" family="UNIX" />
+        <cpu count="4" speed="STANDARD" coresPerSocket="1" />
+        <memoryGb>2</memoryGb>
+        <disk id="1a82316f-23ed-4fe9-b6d8-6b92ac467423" scsiId="0" sizeGb="12" speed="STANDARD" />
+        <createTime>2015-11-19T14:29:02.000Z</createTime>
+        <source type="IMPORT">
+        <artifact type="MF" value="ImportedCustomerImage.mf" date="2015-1119T14:28:54.000Z" />
+        <artifact type="OVF" value="ImportedCustomerImage.ovf" date="2015-1119T14:28:05.000Z" />
+        <artifact type="VMDK" value="ImportedCustomerImage-disk1.vmdk" date="2015-11-19T12:22:31.000Z" /></source>
+        <state>NORMAL</state>
+        <vmwareTools versionStatus="NEED_UPGRADE" runningStatus="NOT_RUNNING" apiVersion="8389" />
+        <virtualHardware version="vmx-10" upToDate="true" />
+    </customerImage>