libcloud-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From anthonys...@apache.org
Subject [22/51] [abbrv] libcloud git commit: merge update
Date Mon, 09 Jan 2017 04:52:42 GMT
http://git-wip-us.apache.org/repos/asf/libcloud/blob/68bf8534/libcloud/common/openstack_identity.py
----------------------------------------------------------------------
diff --cc libcloud/common/openstack_identity.py
index 11fdb78,da90f0b..9e841cf
--- a/libcloud/common/openstack_identity.py
+++ b/libcloud/common/openstack_identity.py
@@@ -1536,6 -1539,139 +1539,139 @@@ class OpenStackIdentity_3_0_Connection_
                                           driver=self.driver)
  
  
+ class OpenStackIdentity_2_0_Connection_VOMS(OpenStackIdentityConnection,
+                                             CertificateConnection):
+     """
+     Connection class for Keystone API v2.0. with VOMS proxy support
+     In this case the key parameter will be the path of the VOMS proxy file.
+     """
+ 
+     responseCls = OpenStackAuthResponse
+     name = 'OpenStack Identity API v2.0 VOMS support'
+     auth_version = '2.0'
+ 
+     def __init__(self, auth_url, user_id, key, tenant_name=None,
+                  domain_name='Default',
+                  token_scope=OpenStackIdentityTokenScope.PROJECT,
+                  timeout=None, parent_conn=None):
+         CertificateConnection.__init__(self, cert_file=key,
+                                        url=auth_url,
+                                        timeout=timeout)
+ 
+         self.parent_conn = parent_conn
+ 
+         # enable tests to use the same mock connection classes.
+         if parent_conn:
 -            self.conn_classes = parent_conn.conn_classes
++            self.conn_class = parent_conn.conn_class
+             self.driver = parent_conn.driver
+         else:
+             self.driver = None
+ 
+         self.auth_url = auth_url
+         self.tenant_name = tenant_name
+         self.domain_name = domain_name
+         self.token_scope = token_scope
+         self.timeout = timeout
+ 
+         self.urls = {}
+         self.auth_token = None
+         self.auth_token_expires = None
+         self.auth_user_info = None
+ 
+     def authenticate(self, force=False):
+         if not self._is_authentication_needed(force=force):
+             return self
+ 
+         tenant = self.tenant_name
+         if not tenant:
+             # if the tenant name is not specified look for it
+             token = self._get_unscoped_token()
+             tenant = self._get_tenant_name(token)
+ 
+         data = {'auth': {'voms': True, 'tenantName': tenant}}
+ 
+         reqbody = json.dumps(data)
+         return self._authenticate_2_0_with_body(reqbody)
+ 
+     def _get_unscoped_token(self):
+         """
+         Get unscoped token from VOMS proxy
+         """
+         data = {'auth': {'voms': True}}
+         reqbody = json.dumps(data)
+ 
+         response = self.request('/v2.0/tokens', data=reqbody,
+                                 headers={'Content-Type': 'application/json'},
+                                 method='POST')
+ 
+         if response.status == httplib.UNAUTHORIZED:
+             # Invalid credentials
+             raise InvalidCredsError()
+         elif response.status in [httplib.OK, httplib.CREATED]:
+             try:
+                 body = json.loads(response.body)
+                 return body['access']['token']['id']
+             except Exception:
+                 e = sys.exc_info()[1]
+                 raise MalformedResponseError('Failed to parse JSON', e)
+         else:
+             raise MalformedResponseError('Malformed response',
+                                          driver=self.driver)
+ 
+     def _get_tenant_name(self, token):
+         """
+         Get the first available tenant name (usually there are only one)
+         """
+         headers = {'Accept': 'application/json',
+                    'Content-Type': 'application/json',
+                    'X-Auth-Token': token}
+         response = self.request('/v2.0/tenants', headers=headers, method='GET')
+ 
+         if response.status == httplib.UNAUTHORIZED:
+             # Invalid credentials
+             raise InvalidCredsError()
+         elif response.status in [httplib.OK, httplib.CREATED]:
+             try:
+                 body = json.loads(response.body)
+                 return body["tenants"][0]["name"]
+             except Exception:
+                 e = sys.exc_info()[1]
+                 raise MalformedResponseError('Failed to parse JSON', e)
+         else:
+             raise MalformedResponseError('Malformed response',
+                                          driver=self.driver)
+ 
+     def _authenticate_2_0_with_body(self, reqbody):
+         resp = self.request('/v2.0/tokens', data=reqbody,
+                             headers={'Content-Type': 'application/json'},
+                             method='POST')
+ 
+         if resp.status == httplib.UNAUTHORIZED:
+             raise InvalidCredsError()
+         elif resp.status not in [httplib.OK,
+                                  httplib.NON_AUTHORITATIVE_INFORMATION]:
+             body = 'code: %s body: %s' % (resp.status, resp.body)
+             raise MalformedResponseError('Malformed response', body=body,
+                                          driver=self.driver)
+         else:
+             body = resp.object
+ 
+             try:
+                 access = body['access']
+                 expires = access['token']['expires']
+ 
+                 self.auth_token = access['token']['id']
+                 self.auth_token_expires = parse_date(expires)
+                 self.urls = access['serviceCatalog']
+                 self.auth_user_info = access.get('user', {})
+             except KeyError:
+                 e = sys.exc_info()[1]
+                 raise MalformedResponseError('Auth JSON response is \
+                                              missing required elements', e)
+ 
+         return self
+ 
+ 
  def get_class_for_auth_version(auth_version):
      """
      Retrieve class for the provided auth version.

http://git-wip-us.apache.org/repos/asf/libcloud/blob/68bf8534/libcloud/common/ovh.py
----------------------------------------------------------------------
diff --cc libcloud/common/ovh.py
index 0b342ab,3854e0b..0eae99b
--- a/libcloud/common/ovh.py
+++ b/libcloud/common/ovh.py
@@@ -102,7 -102,7 +102,7 @@@ class OvhConnection(ConnectionUserAndKe
              'Content-Type': 'application/json',
              'X-Ovh-Application': user_id,
          }
-         httpcon = LibcloudConnection(self.host)
 -        httpcon = LibcloudHTTPSConnection(self.host)
++        httpcon = LibcloudConnection(host=self.host, port=443)
          httpcon.request(method='POST', url=action, body=data, headers=headers)
          response = httpcon.getresponse()
  

http://git-wip-us.apache.org/repos/asf/libcloud/blob/68bf8534/libcloud/httplib_ssl.py
----------------------------------------------------------------------
diff --cc libcloud/httplib_ssl.py
index a4b603e,bbcf752..e1354e2
--- a/libcloud/httplib_ssl.py
+++ b/libcloud/httplib_ssl.py
@@@ -19,12 -19,19 +19,13 @@@ verification, depending on libcloud.sec
  """
  
  import os
 -import sys
  import socket
 -import ssl
 -import base64
 -import warnings
 +
 +import requests
  
  import libcloud.security
 -from libcloud.utils.py3 import b
+ from libcloud.utils.py3 import httplib
  from libcloud.utils.py3 import urlparse
 -from libcloud.utils.py3 import urlunquote
 -from libcloud.utils.py3 import match_hostname
 -from libcloud.utils.py3 import CertificateError
  
  
  __all__ = [
@@@ -144,31 -141,69 +145,31 @@@ class LibcloudBaseConnection(object)
          return (proxy_scheme, proxy_host, proxy_port, proxy_username,
                  proxy_password)
  
 -    def _setup_http_proxy(self):
 -        """
 -        Set up HTTP proxy.
 +    def _setup_verify(self):
 +        self.verify = libcloud.security.VERIFY_SSL_CERT
  
 -        :param proxy_url: Proxy URL (e.g. http://<host>:3128)
 -        :type proxy_url: ``str``
 -        """
 -        headers = {}
 -
 -        if self.proxy_username and self.proxy_password:
 -            # Include authentication header
 -            user_pass = '%s:%s' % (self.proxy_username, self.proxy_password)
 -            encoded = base64.encodestring(b(urlunquote(user_pass))).strip()
 -            auth_header = 'Basic %s' % (encoded.decode('utf-8'))
 -            headers['Proxy-Authorization'] = auth_header
 -
 -        if hasattr(self, 'set_tunnel'):
 -            # Python 2.7 and higher
 -            # pylint: disable=no-member
 -            self.set_tunnel(host=self.host, port=self.port, headers=headers)
 -        elif hasattr(self, '_set_tunnel'):
 -            # Python 2.6
 -            # pylint: disable=no-member
 -            self._set_tunnel(host=self.host, port=self.port, headers=headers)
 +    def _setup_ca_cert(self):
 +        if self.verify is False:
 +            pass
          else:
 -            raise ValueError('Unsupported Python version')
 +            self.ca_cert = libcloud.security.CA_CERTS_PATH
  
 -        self._set_hostport(host=self.proxy_host, port=self.proxy_port)
  
- class LibcloudConnection(LibcloudBaseConnection):
 -    def _activate_http_proxy(self, sock):
 -        self.sock = sock
 -        self._tunnel()  # pylint: disable=no-member
++class LibcloudConnection(httplib.HTTPSConnection, LibcloudBaseConnection):
 +    timeout = None
 +    host = None
 +    response = None
  
 -    def _set_hostport(self, host, port):
 -        """
 -        Backported from Python stdlib so Proxy support also works with
 -        Python 3.4.
 -        """
 -        if port is None:
 -            i = host.rfind(':')
 -            j = host.rfind(']')         # ipv6 addresses have [...]
 -            if i > j:
 -                try:
 -                    port = int(host[i + 1:])
 -                except ValueError:
 -                    msg = "nonnumeric port: '%s'" % (host[i + 1:])
 -                    raise httplib.InvalidURL(msg)
 -                host = host[:i]
 -            else:
 -                port = self.default_port  # pylint: disable=no-member
 -            if host and host[0] == '[' and host[-1] == ']':
 -                host = host[1:-1]
 -        self.host = host
 -        self.port = port
 -
 -
 -class LibcloudHTTPConnection(httplib.HTTPConnection, LibcloudBaseConnection):
 -    def __init__(self, *args, **kwargs):
 +    def __init__(self, host, port, **kwargs):
-         self.host = '{}://{}'.format(
++        self.host = '{0}://{1}'.format(
 +            'https' if port == 443 else 'http',
 +            host
 +        )
          # Support for HTTP proxy
          proxy_url_env = os.environ.get(HTTP_PROXY_ENV_VARIABLE_NAME, None)
          proxy_url = kwargs.pop('proxy_url', proxy_url_env)
  
-         super(LibcloudConnection, self).__init__()
 -        super(LibcloudHTTPConnection, self).__init__(*args, **kwargs)
++        LibcloudBaseConnection.__init__(self)
  
          if proxy_url:
              self.set_http_proxy(proxy_url=proxy_url)

http://git-wip-us.apache.org/repos/asf/libcloud/blob/68bf8534/libcloud/test/backup/test_dimensiondata_v2_3.py
----------------------------------------------------------------------
diff --cc libcloud/test/backup/test_dimensiondata_v2_3.py
index 0000000,b0e581a..80f35a3
mode 000000,100644..100644
--- a/libcloud/test/backup/test_dimensiondata_v2_3.py
+++ b/libcloud/test/backup/test_dimensiondata_v2_3.py
@@@ -1,0 -1,503 +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 DimensionData_v2_3_Tests(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())
++# 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_class = 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/68bf8534/libcloud/test/backup/test_dimensiondata_v2_4.py
----------------------------------------------------------------------
diff --cc libcloud/test/backup/test_dimensiondata_v2_4.py
index 0000000,285ac40..4fbed27
mode 000000,100644..100644
--- a/libcloud/test/backup/test_dimensiondata_v2_4.py
+++ b/libcloud/test/backup/test_dimensiondata_v2_4.py
@@@ -1,0 -1,503 +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 DimensionData_v2_4_Tests(unittest.TestCase, TestCaseMixin):
+ 
+     def setUp(self):
+         DimensionData.connectionCls.active_api_version = '2.4'
 -        DimensionData.connectionCls.conn_classes = (None, DimensionDataMockHttp)
++        DimensionData.connectionCls.conn_class = 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/68bf8534/libcloud/test/common/test_base_driver.py
----------------------------------------------------------------------
diff --cc libcloud/test/common/test_base_driver.py
index 6e04874,6e04874..f69c982
--- a/libcloud/test/common/test_base_driver.py
+++ b/libcloud/test/common/test_base_driver.py
@@@ -51,12 -51,12 +51,6 @@@ class BaseDriverTestCase(unittest.TestC
                  result['timeout'] = 13
                  return result
  
--        DummyDriver2.connectionCls = Mock()
--        DummyDriver2(key='foo')
--        call_kwargs = DummyDriver2.connectionCls.call_args[1]
--        self.assertEqual(call_kwargs['timeout'], 13)
--        self.assertEqual(call_kwargs['retry_delay'], None)
--
          # 4. Value provided via "_ex_connection_class_kwargs" and constructor,
          # constructor should win
          DummyDriver2.connectionCls = Mock()


Mime
View raw message