You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@libcloud.apache.org by to...@apache.org on 2015/08/30 14:32:55 UTC
[07/21] libcloud git commit: [LIBCLOUD-737] Started a load balancer
driver for the Dimension Data VIP functionality.
[LIBCLOUD-737] Started a load balancer driver for the Dimension Data VIP functionality.
Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/b90c5610
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/b90c5610
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/b90c5610
Branch: refs/heads/trunk
Commit: b90c56106930dedbe526ffb18fb34661c0fcb0de
Parents: fbd02da
Author: Anthony Shaw <an...@gmail.com>
Authored: Wed Aug 26 17:24:10 2015 +1000
Committer: Anthony Shaw <an...@gmail.com>
Committed: Wed Aug 26 17:24:10 2015 +1000
----------------------------------------------------------------------
libcloud/common/dimensiondata.py | 342 +++++++++++++++++++
libcloud/compute/drivers/dimensiondata.py | 308 +----------------
libcloud/loadbalancer/drivers/dimensiondata.py | 248 ++++++++++++++
libcloud/loadbalancer/providers.py | 3 +-
libcloud/loadbalancer/types.py | 3 +-
..._9cbc_8dabe5a7d0e4_networkDomainVip_pool.xml | 37 ++
...8dabe5a7d0e4_networkDomainVip_poolMember.xml | 29 ++
...ber_3dd806a2_c2c8_4c0c_9a4f_5219ea9266c0.xml | 13 +
...ool_4d360b1f_bc2c_4ab7_9884_1f03ba2768f7.xml | 17 +
...5a7d0e4_networkDomainVip_virtualListener.xml | 51 +++
...ner_6115469d_a8bb_445b_bb23_d23b5283f2b9.xml | 44 +++
.../dimensiondata/oec_0_9_myaccount.xml | 26 ++
.../test/loadbalancer/test_dimensiondata.py | 160 +++++++++
13 files changed, 981 insertions(+), 300 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/libcloud/blob/b90c5610/libcloud/common/dimensiondata.py
----------------------------------------------------------------------
diff --git a/libcloud/common/dimensiondata.py b/libcloud/common/dimensiondata.py
new file mode 100644
index 0000000..f5cdf05
--- /dev/null
+++ b/libcloud/common/dimensiondata.py
@@ -0,0 +1,342 @@
+# 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.
+"""
+Dimension Data Common Components
+"""
+from base64 import b64encode
+from libcloud.utils.py3 import httplib
+from libcloud.utils.py3 import b
+
+from libcloud.common.base import ConnectionUserAndKey, XmlResponse
+from libcloud.common.types import LibcloudError, InvalidCredsError
+from libcloud.utils.xml import findtext
+
+# Roadmap / TODO:
+#
+# 1.0 - Copied from OpSource API, named provider details.
+
+# setup a few variables to represent all of the DimensionData cloud namespaces
+NAMESPACE_BASE = "http://oec.api.opsource.net/schemas"
+ORGANIZATION_NS = NAMESPACE_BASE + "/organization"
+SERVER_NS = NAMESPACE_BASE + "/server"
+NETWORK_NS = NAMESPACE_BASE + "/network"
+DIRECTORY_NS = NAMESPACE_BASE + "/directory"
+
+# API 2.0 Namespaces and URNs
+TYPES_URN = "urn:didata.com:api:cloud:types"
+
+# API end-points
+API_ENDPOINTS = {
+ 'dd-na': {
+ 'name': 'North America (NA)',
+ 'host': 'api-na.dimensiondata.com',
+ 'vendor': 'DimensionData'
+ },
+ 'dd-eu': {
+ 'name': 'Europe (EU)',
+ 'host': 'api-eu.dimensiondata.com',
+ 'vendor': 'DimensionData'
+ },
+ 'dd-au': {
+ 'name': 'Australia (AU)',
+ 'host': 'api-au.dimensiondata.com',
+ 'vendor': 'DimensionData'
+ },
+ 'dd-af': {
+ 'name': 'Africa (AF)',
+ 'host': 'api-af.dimensiondata.com',
+ 'vendor': 'DimensionData'
+ },
+ 'dd-ap': {
+ 'name': 'Asia Pacific (AP)',
+ 'host': 'api-na.dimensiondata.com',
+ 'vendor': 'DimensionData'
+ },
+ 'dd-latam': {
+ 'name': 'South America (LATAM)',
+ 'host': 'api-latam.dimensiondata.com',
+ 'vendor': 'DimensionData'
+ },
+ 'dd-canada': {
+ 'name': 'Canada (CA)',
+ 'host': 'api-canada.dimensiondata.com',
+ 'vendor': 'DimensionData'
+ }
+}
+
+# Default API end-point for the base connection class.
+DEFAULT_REGION = 'dd-na'
+
+
+class DimensionDataResponse(XmlResponse):
+ def parse_error(self):
+ if self.status == httplib.UNAUTHORIZED:
+ raise InvalidCredsError(self.body)
+ elif self.status == httplib.FORBIDDEN:
+ raise InvalidCredsError(self.body)
+
+ body = self.parse_body()
+
+ # TODO: The path is not fixed as server.
+ if self.status == httplib.BAD_REQUEST:
+ code = findtext(body, 'responseCode', SERVER_NS)
+ message = findtext(body, 'message', SERVER_NS)
+ raise DimensionDataAPIException(code,
+ message,
+ driver=self.connection.driver)
+
+ return self.body
+
+
+class DimensionDataAPIException(LibcloudError):
+ def __init__(self, code, msg, driver):
+ self.code = code
+ self.msg = msg
+ self.driver = driver
+
+ def __str__(self):
+ return "%s: %s" % (self.code, self.msg)
+
+ def __repr__(self):
+ return ("<DimensionDataAPIException: code='%s', msg='%s'>" %
+ (self.code, self.msg))
+
+
+class DimensionDataConnection(ConnectionUserAndKey):
+ """
+ Connection class for the DimensionData driver
+ """
+
+ api_path_version_1 = '/oec'
+ api_path_version_2 = '/caas'
+ api_version_1 = '0.9'
+ api_version_2 = '2.0'
+
+ _orgId = None
+ responseCls = DimensionDataResponse
+
+ allow_insecure = False
+
+ def __init__(self, user_id, key, secure=True, host=None, port=None,
+ url=None, timeout=None, proxy_url=None, **conn_kwargs):
+ super(DimensionDataConnection, self).__init__(
+ user_id=user_id,
+ key=key,
+ secure=secure,
+ host=host, port=port,
+ url=url, timeout=timeout,
+ proxy_url=proxy_url)
+
+ if conn_kwargs['region']:
+ self.host = conn_kwargs['region']['host']
+
+ def add_default_headers(self, headers):
+ headers['Authorization'] = \
+ ('Basic %s' % b64encode(b('%s:%s' % (self.user_id,
+ self.key))).decode('utf-8'))
+ return headers
+
+ def request_api_1(self, action, params=None, data='',
+ headers=None, method='GET'):
+ action = "%s/%s/%s" % (self.api_path_version_1,
+ self.api_version_1, action)
+
+ return super(DimensionDataConnection, self).request(
+ action=action,
+ params=params, data=data,
+ method=method, headers=headers)
+
+ def request_api_2(self, path, action, params=None, data='',
+ headers=None, method='GET'):
+ action = "%s/%s/%s/%s" % (self.api_path_version_2,
+ self.api_version_2, path, action)
+
+ return super(DimensionDataConnection, self).request(
+ action=action,
+ params=params, data=data,
+ method=method, headers=headers)
+
+ def request_with_orgId_api_1(self, action, params=None, data='',
+ headers=None, method='GET'):
+ action = "%s/%s" % (self.get_resource_path_api_1(), action)
+
+ return super(DimensionDataConnection, self).request(
+ action=action,
+ params=params, data=data,
+ method=method, headers=headers)
+
+ def request_with_orgId_api_2(self, action, params=None, data='',
+ headers=None, method='GET'):
+ action = "%s/%s" % (self.get_resource_path_api_2(), action)
+
+ return super(DimensionDataConnection, self).request(
+ action=action,
+ params=params, data=data,
+ method=method, headers=headers)
+
+ def get_resource_path_api_1(self):
+ """
+ This method returns a resource path which is necessary for referencing
+ resources that require a full path instead of just an ID, such as
+ networks, and customer snapshots.
+ """
+ return ("%s/%s/%s" % (self.api_path_version_1, self.api_version_1,
+ self._get_orgId()))
+
+ def get_resource_path_api_2(self):
+ """
+ This method returns a resource path which is necessary for referencing
+ resources that require a full path instead of just an ID, such as
+ networks, and customer snapshots.
+ """
+ return ("%s/%s/%s" % (self.api_path_version_2, self.api_version_2,
+ self._get_orgId()))
+
+ def _get_orgId(self):
+ """
+ Send the /myaccount API request to DimensionData cloud and parse the
+ 'orgId' from the XML response object. We need the orgId to use most
+ of the other API functions
+ """
+ if self._orgId is None:
+ body = self.request_api_1('myaccount').object
+ self._orgId = findtext(body, 'orgId', DIRECTORY_NS)
+ return self._orgId
+
+
+class DimensionDataStatus(object):
+ """
+ DimensionData API pending operation status class
+ action, request_time, user_name, number_of_steps, update_time,
+ step.name, step.number, step.percent_complete, failure_reason,
+ """
+ def __init__(self, action=None, request_time=None, user_name=None,
+ number_of_steps=None, update_time=None, step_name=None,
+ step_number=None, step_percent_complete=None,
+ failure_reason=None):
+ self.action = action
+ self.request_time = request_time
+ self.user_name = user_name
+ self.number_of_steps = number_of_steps
+ self.update_time = update_time
+ self.step_name = step_name
+ self.step_number = step_number
+ self.step_percent_complete = step_percent_complete
+ self.failure_reason = failure_reason
+
+ def __repr__(self):
+ return (('<DimensionDataStatus: action=%s, request_time=%s, '
+ 'user_name=%s, number_of_steps=%s, update_time=%s, '
+ 'step_name=%s, step_number=%s, '
+ 'step_percent_complete=%s, failure_reason=%s')
+ % (self.action, self.request_time, self.user_name,
+ self.number_of_steps, self.update_time, self.step_name,
+ self.step_number, self.step_percent_complete,
+ self.failure_reason))
+
+
+class DimensionDataNetwork(object):
+ """
+ DimensionData network with location.
+ """
+
+ def __init__(self, id, name, description, location, private_net,
+ multicast, status):
+ self.id = str(id)
+ self.name = name
+ self.description = description
+ self.location = location
+ self.private_net = private_net
+ self.multicast = multicast
+ self.status = status
+
+ def __repr__(self):
+ return (('<DimensionDataNetwork: id=%s, name=%s, description=%s, '
+ 'location=%s, private_net=%s, multicast=%s>')
+ % (self.id, self.name, self.description, self.location,
+ self.private_net, self.multicast))
+
+
+class DimensionDataNetworkDomain(object):
+ """
+ DimensionData network domain with location.
+ """
+
+ def __init__(self, id, name, description, location, status):
+ self.id = str(id)
+ self.name = name
+ self.description = description
+ self.location = location
+ self.status = status
+
+ def __repr__(self):
+ return (('<DimensionDataNetworkDomain: id=%s, name=%s,'
+ 'description=%s, location=%s, status=%s>')
+ % (self.id, self.name, self.description, self.location,
+ self.status))
+
+
+class DimensionDataVlan(object):
+ """
+ DimensionData VLAN.
+ """
+
+ def __init__(self, id, name, description, location, status):
+ self.id = str(id)
+ self.name = name
+ self.location = location
+ self.description = description
+ self.status = status
+
+ def __repr__(self):
+ return (('<DimensionDataNetworkDomain: id=%s, name=%s, '
+ 'description=%s, location=%s, status=%s>')
+ % (self.id, self.name, self.description,
+ self.location, self.status))
+
+class DimensionDataPool(object):
+ """
+ DimensionData VIP Pool.
+ """
+
+ def __init__(self, id, name, description, status):
+ self.id = str(id)
+ self.name = name
+ self.description = description
+ self.status = status
+
+ def __repr__(self):
+ return (('<DimensionDataPool: id=%s, name=%s, '
+ 'description=%s, status=%s>')
+ % (self.id, self.name, self.description,
+ self.status))
+
+class DimensionDataPoolMember(object):
+ """
+ DimensionData VIP Pool Member.
+ """
+
+ def __init__(self, id, name, status, ip_address, port):
+ self.id = str(id)
+ self.name = name
+ self.status = status
+ self.ip_address = ip_address
+ self.port = port
+
+ def __repr__(self):
+ return (('<DimensionDataPool: id=%s, name=%s, '
+ 'ip_address=%s, status=%s, port=%s>')
+ % (self.id, self.name,
+ self.ip_address, self.status, self.port))
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/libcloud/blob/b90c5610/libcloud/compute/drivers/dimensiondata.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/dimensiondata.py b/libcloud/compute/drivers/dimensiondata.py
index 8f0f71d..dcfe4b5 100644
--- a/libcloud/compute/drivers/dimensiondata.py
+++ b/libcloud/compute/drivers/dimensiondata.py
@@ -21,309 +21,21 @@ try:
except ImportError:
from xml.etree import ElementTree as ET
-from base64 import b64encode
-
-from libcloud.utils.py3 import httplib
-from libcloud.utils.py3 import b
-
from libcloud.compute.base import NodeDriver, Node
from libcloud.compute.base import NodeSize, NodeImage, NodeLocation
-from libcloud.common.types import LibcloudError, InvalidCredsError
-from libcloud.common.base import ConnectionUserAndKey, XmlResponse
+from libcloud.common.dimensiondata import (DimensionDataConnection,
+ DimensionDataStatus)
+from libcloud.common.dimensiondata import DimensionDataNetwork
+from libcloud.common.dimensiondata import DimensionDataNetworkDomain
+from libcloud.common.dimensiondata import DimensionDataVlan
+from libcloud.common.dimensiondata import API_ENDPOINTS
+from libcloud.common.dimensiondata import DEFAULT_REGION
+from libcloud.common.dimensiondata import TYPES_URN
+from libcloud.common.dimensiondata import SERVER_NS
+from libcloud.common.dimensiondata import NETWORK_NS
from libcloud.utils.xml import fixxpath, findtext, findall
from libcloud.compute.types import NodeState, Provider
-# Roadmap / TODO:
-#
-# 1.0 - Copied from OpSource API, named provider details.
-
-# setup a few variables to represent all of the DimensionData cloud namespaces
-NAMESPACE_BASE = "http://oec.api.opsource.net/schemas"
-ORGANIZATION_NS = NAMESPACE_BASE + "/organization"
-SERVER_NS = NAMESPACE_BASE + "/server"
-NETWORK_NS = NAMESPACE_BASE + "/network"
-DIRECTORY_NS = NAMESPACE_BASE + "/directory"
-RESET_NS = NAMESPACE_BASE + "/reset"
-VIP_NS = NAMESPACE_BASE + "/vip"
-IMAGEIMPORTEXPORT_NS = NAMESPACE_BASE + "/imageimportexport"
-DATACENTER_NS = NAMESPACE_BASE + "/datacenter"
-SUPPORT_NS = NAMESPACE_BASE + "/support"
-GENERAL_NS = NAMESPACE_BASE + "/general"
-IPPLAN_NS = NAMESPACE_BASE + "/ipplan"
-WHITELABEL_NS = NAMESPACE_BASE + "/whitelabel"
-
-# API 2.0 Namespaces and URNs
-TYPES_URN = "urn:didata.com:api:cloud:types"
-
-# API end-points
-API_ENDPOINTS = {
- 'dd-na': {
- 'name': 'North America (NA)',
- 'host': 'api-na.dimensiondata.com',
- 'vendor': 'DimensionData'
- },
- 'dd-eu': {
- 'name': 'Europe (EU)',
- 'host': 'api-eu.dimensiondata.com',
- 'vendor': 'DimensionData'
- },
- 'dd-au': {
- 'name': 'Australia (AU)',
- 'host': 'api-au.dimensiondata.com',
- 'vendor': 'DimensionData'
- },
- 'dd-af': {
- 'name': 'Africa (AF)',
- 'host': 'api-af.dimensiondata.com',
- 'vendor': 'DimensionData'
- },
- 'dd-ap': {
- 'name': 'Asia Pacific (AP)',
- 'host': 'api-na.dimensiondata.com',
- 'vendor': 'DimensionData'
- },
- 'dd-latam': {
- 'name': 'South America (LATAM)',
- 'host': 'api-latam.dimensiondata.com',
- 'vendor': 'DimensionData'
- },
- 'dd-canada': {
- 'name': 'Canada (CA)',
- 'host': 'api-canada.dimensiondata.com',
- 'vendor': 'DimensionData'
- }
-}
-
-# Default API end-point for the base connection class.
-DEFAULT_REGION = 'dd-na'
-
-
-class DimensionDataResponse(XmlResponse):
- def parse_error(self):
- if self.status == httplib.UNAUTHORIZED:
- raise InvalidCredsError(self.body)
- elif self.status == httplib.FORBIDDEN:
- raise InvalidCredsError(self.body)
-
- body = self.parse_body()
-
- # TODO: The path is not fixed as server.
- if self.status == httplib.BAD_REQUEST:
- code = findtext(body, 'responseCode', SERVER_NS)
- message = findtext(body, 'message', SERVER_NS)
- raise DimensionDataAPIException(code,
- message,
- driver=DimensionDataNodeDriver)
-
- return self.body
-
-
-class DimensionDataAPIException(LibcloudError):
- def __init__(self, code, msg, driver):
- self.code = code
- self.msg = msg
- self.driver = driver
-
- def __str__(self):
- return "%s: %s" % (self.code, self.msg)
-
- def __repr__(self):
- return ("<DimensionDataAPIException: code='%s', msg='%s'>" %
- (self.code, self.msg))
-
-
-class DimensionDataConnection(ConnectionUserAndKey):
- """
- Connection class for the DimensionData driver
- """
-
- api_path_version_1 = '/oec'
- api_path_version_2 = '/caas'
- api_version_1 = '0.9'
- api_version_2 = '2.0'
-
- _orgId = None
- responseCls = DimensionDataResponse
-
- allow_insecure = False
-
- def __init__(self, user_id, key, secure=True, host=None, port=None,
- url=None, timeout=None, proxy_url=None, **conn_kwargs):
- super(DimensionDataConnection, self).__init__(
- user_id=user_id,
- key=key,
- secure=secure,
- host=host, port=port,
- url=url, timeout=timeout,
- proxy_url=proxy_url)
-
- if conn_kwargs['region']:
- self.host = conn_kwargs['region']['host']
-
- def add_default_headers(self, headers):
- headers['Authorization'] = \
- ('Basic %s' % b64encode(b('%s:%s' % (self.user_id,
- self.key))).decode('utf-8'))
- return headers
-
- def request_api_1(self, action, params=None, data='',
- headers=None, method='GET'):
- action = "%s/%s/%s" % (self.api_path_version_1,
- self.api_version_1, action)
-
- return super(DimensionDataConnection, self).request(
- action=action,
- params=params, data=data,
- method=method, headers=headers)
-
- def request_api_2(self, path, action, params=None, data='',
- headers=None, method='GET'):
- action = "%s/%s/%s/%s" % (self.api_path_version_2,
- self.api_version_2, path, action)
-
- return super(DimensionDataConnection, self).request(
- action=action,
- params=params, data=data,
- method=method, headers=headers)
-
- def request_with_orgId_api_1(self, action, params=None, data='',
- headers=None, method='GET'):
- action = "%s/%s" % (self.get_resource_path_api_1(), action)
-
- return super(DimensionDataConnection, self).request(
- action=action,
- params=params, data=data,
- method=method, headers=headers)
-
- def request_with_orgId_api_2(self, action, params=None, data='',
- headers=None, method='GET'):
- action = "%s/%s" % (self.get_resource_path_api_2(), action)
-
- return super(DimensionDataConnection, self).request(
- action=action,
- params=params, data=data,
- method=method, headers=headers)
-
- def get_resource_path_api_1(self):
- """
- This method returns a resource path which is necessary for referencing
- resources that require a full path instead of just an ID, such as
- networks, and customer snapshots.
- """
- return ("%s/%s/%s" % (self.api_path_version_1, self.api_version_1,
- self._get_orgId()))
-
- def get_resource_path_api_2(self):
- """
- This method returns a resource path which is necessary for referencing
- resources that require a full path instead of just an ID, such as
- networks, and customer snapshots.
- """
- return ("%s/%s/%s" % (self.api_path_version_2, self.api_version_2,
- self._get_orgId()))
-
- def _get_orgId(self):
- """
- Send the /myaccount API request to DimensionData cloud and parse the
- 'orgId' from the XML response object. We need the orgId to use most
- of the other API functions
- """
- if self._orgId is None:
- body = self.request_api_1('myaccount').object
- self._orgId = findtext(body, 'orgId', DIRECTORY_NS)
- return self._orgId
-
-
-class DimensionDataStatus(object):
- """
- DimensionData API pending operation status class
- action, request_time, user_name, number_of_steps, update_time,
- step.name, step.number, step.percent_complete, failure_reason,
- """
- def __init__(self, action=None, request_time=None, user_name=None,
- number_of_steps=None, update_time=None, step_name=None,
- step_number=None, step_percent_complete=None,
- failure_reason=None):
- self.action = action
- self.request_time = request_time
- self.user_name = user_name
- self.number_of_steps = number_of_steps
- self.update_time = update_time
- self.step_name = step_name
- self.step_number = step_number
- self.step_percent_complete = step_percent_complete
- self.failure_reason = failure_reason
-
- def __repr__(self):
- return (('<DimensionDataStatus: action=%s, request_time=%s, '
- 'user_name=%s, number_of_steps=%s, update_time=%s, '
- 'step_name=%s, step_number=%s, '
- 'step_percent_complete=%s, failure_reason=%s')
- % (self.action, self.request_time, self.user_name,
- self.number_of_steps, self.update_time, self.step_name,
- self.step_number, self.step_percent_complete,
- self.failure_reason))
-
-
-class DimensionDataNetwork(object):
- """
- DimensionData network with location.
- """
-
- def __init__(self, id, name, description, location, private_net,
- multicast, status):
- self.id = str(id)
- self.name = name
- self.description = description
- self.location = location
- self.private_net = private_net
- self.multicast = multicast
- self.status = status
-
- def __repr__(self):
- return (('<DimensionDataNetwork: id=%s, name=%s, description=%s, '
- 'location=%s, private_net=%s, multicast=%s>')
- % (self.id, self.name, self.description, self.location,
- self.private_net, self.multicast))
-
-
-class DimensionDataNetworkDomain(object):
- """
- DimensionData network domain with location.
- """
-
- def __init__(self, id, name, description, location, status):
- self.id = str(id)
- self.name = name
- self.description = description
- self.location = location
- self.status = status
-
- def __repr__(self):
- return (('<DimensionDataNetworkDomain: id=%s, name=%s,'
- 'description=%s, location=%s, status=%s>')
- % (self.id, self.name, self.description, self.location,
- self.status))
-
-
-class DimensionDataVlan(object):
- """
- DimensionData VLAN.
- """
-
- def __init__(self, id, name, description, location, status):
- self.id = str(id)
- self.name = name
- self.location = location
- self.description = description
- self.status = status
-
- def __repr__(self):
- return (('<DimensionDataNetworkDomain: id=%s, name=%s, '
- 'description=%s, location=%s, status=%s>')
- % (self.id, self.name, self.description,
- self.location, self.status))
-
class DimensionDataNodeDriver(NodeDriver):
"""
http://git-wip-us.apache.org/repos/asf/libcloud/blob/b90c5610/libcloud/loadbalancer/drivers/dimensiondata.py
----------------------------------------------------------------------
diff --git a/libcloud/loadbalancer/drivers/dimensiondata.py b/libcloud/loadbalancer/drivers/dimensiondata.py
new file mode 100644
index 0000000..d294a9a
--- /dev/null
+++ b/libcloud/loadbalancer/drivers/dimensiondata.py
@@ -0,0 +1,248 @@
+# 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 withv
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from libcloud.common.dimensiondata import DimensionDataConnection
+from libcloud.common.dimensiondata import DimensionDataPool
+from libcloud.common.dimensiondata import DimensionDataPoolMember
+from libcloud.common.dimensiondata import API_ENDPOINTS
+from libcloud.common.dimensiondata import DEFAULT_REGION
+from libcloud.common.dimensiondata import TYPES_URN
+from libcloud.common.dimensiondata import SERVER_NS
+from libcloud.utils.misc import find, reverse_dict
+from libcloud.utils.xml import fixxpath, findtext, findall
+from libcloud.loadbalancer.types import State
+from libcloud.loadbalancer.base import Algorithm, Driver, LoadBalancer
+from libcloud.loadbalancer.base import DEFAULT_ALGORITHM, Member
+from libcloud.loadbalancer.types import Provider
+
+class DimensionDataLBDriver(Driver):
+ """
+ DimensionData node driver.
+ """
+
+ selected_region = None
+ connectionCls = DimensionDataConnection
+ name = 'Dimension Data Load Balancer'
+ website = 'https://cloud.dimensiondata.com/'
+ type = Provider.DIMENSIONDATA
+ api_version = 1.0
+
+ _VALUE_TO_ALGORITHM_MAP = {
+ 'ROUND_ROBIN': Algorithm.ROUND_ROBIN,
+ 'LEAST_CONNECTIONS': Algorithm.LEAST_CONNECTIONS,
+ 'SHORTEST_RESPONSE': Algorithm.SHORTEST_RESPONSE,
+ 'PERSISTENT_IP': Algorithm.PERSISTENT_IP
+ }
+ _ALGORITHM_TO_VALUE_MAP = reverse_dict(_VALUE_TO_ALGORITHM_MAP)
+
+ _VALUE_TO_STATE_MAP = {
+ 'NORMAL': State.RUNNING,
+ 'PENDING_ADD': State.PENDING,
+ 'PENDING_CHANGE': State.PENDING,
+ 'PENDING_DELETE': State.PENDING,
+ 'FAILED_ADD': State.ERROR,
+ 'FAILED_CHANGE': State.ERROR,
+ 'FAILED_DELETE': State.ERROR,
+ 'REQUIRES_SUPPORT': State.ERROR
+ }
+
+ def __init__(self, key, secret=None, secure=True, host=None, port=None,
+ api_version=None, region=DEFAULT_REGION, **kwargs):
+
+ if region not in API_ENDPOINTS:
+ raise ValueError('Invalid region: %s' % (region))
+
+ self.selected_region = API_ENDPOINTS[region]
+
+ super(DimensionDataLBDriver, self).__init__(key=key, secret=secret,
+ secure=secure, host=host,
+ port=port,
+ api_version=api_version,
+ region=region,
+ **kwargs)
+
+ def _ex_connection_class_kwargs(self):
+ """
+ Add the region to the kwargs before the connection is instantiated
+ """
+
+ kwargs = super(DimensionDataLBDriver,
+ self)._ex_connection_class_kwargs()
+ kwargs['region'] = self.selected_region
+ return kwargs
+
+ def list_balancers(self):
+ """
+ List all loadbalancers inside a gepgraphy.
+
+ In Dimension Data terminology these are known as virtual listeners
+
+ :rtype: ``list`` of :class:`LoadBalancer`
+ """
+
+ return self._to_balancers(
+ self.connection
+ .request_with_orgId_api_2('networkDomainVip/virtualListener').object)
+
+ def get_balancer(self, balancer_id):
+ """
+ Return a :class:`LoadBalancer` object.
+
+ :param balancer_id: id of a load balancer you want to fetch
+ :type balancer_id: ``str``
+
+ :rtype: :class:`LoadBalancer`
+ """
+
+ bal = self.connection \
+ .request_with_orgId_api_2('networkDomainVip/virtualListener/%s'
+ % balancer_id).object
+ return self._to_balancer(bal)
+
+ def list_protocols(self):
+ """
+ Return a list of supported protocols.
+
+ Since all protocols are support by Dimension Data, this is a list
+ of common protocols.
+
+ :rtype: ``list`` of ``str``
+ """
+ return ['dns', 'ftp', 'http', 'https', 'tcp', 'udp']
+
+ def balancer_list_members(self, balancer):
+ """
+ Return list of members attached to balancer.
+
+ In Dimension Data terminology these are the members of the pools
+ within a virtual listener.
+
+ :param balancer: LoadBalancer which should be used
+ :type balancer: :class:`LoadBalancer`
+
+ :rtype: ``list`` of :class:`Member`
+ """
+
+ pool_members = self.ex_get_pool_members(balancer.extra['pool_id'])
+ members = []
+ for pool_member in pool_members:
+ members.append(Member(
+ id=pool_member.id,
+ ip=pool_member.ip_address,
+ port=pool_member.port,
+ balancer=balancer,
+ extra=None
+ ))
+ return members
+
+ def balancer_attach_member(self, balancer, member):
+ return True
+
+ def balancer_detach_member(self, balancer, member):
+ return True
+
+ def destroy_balancer(self, balancer):
+ return True
+
+ def ex_get_pools(self):
+ pools = self.connection \
+ .request_with_orgId_api_2('networkDomainVip/pool').object
+ return self._to_pools(pools)
+
+ def ex_get_pool(self, pool_id):
+ pool = self.connection \
+ .request_with_orgId_api_2('networkDomainVip/pool/%s'
+ % pool_id).object
+ return self._to_pool(pool)
+
+ def ex_get_pool_members(self, pool_id):
+ members = self.connection \
+ .request_with_orgId_api_2('networkDomainVip/poolMember?poolId=%s'
+ % pool_id).object
+ return self._to_members(members)
+
+ def ex_get_pool_member(self, pool_member_id):
+ member = self.connection \
+ .request_with_orgId_api_2('networkDomainVip/poolMember/%s'
+ % pool_member_id).object
+ return self._to_member(member)
+
+ def _to_balancers(self, object ):
+ loadbalancers = []
+ for element in object.findall(fixxpath("virtualListener", TYPES_URN)):
+ loadbalancers.append(self._to_balancer(element))
+
+ return loadbalancers
+
+ def _to_balancer(self, element):
+ ipaddress = findtext(element, 'listenerIpAddress', TYPES_URN)
+ name = findtext(element, 'name', TYPES_URN)
+ port = findtext(element, 'port', TYPES_URN)
+ extra = {}
+
+ extra['pool_id'] = element.find(fixxpath(
+ 'pool',
+ TYPES_URN)).get('id')
+
+ balancer = LoadBalancer(
+ id=element.get('id'),
+ name=name,
+ state=self._VALUE_TO_STATE_MAP.get(
+ findtext(element, 'state', TYPES_URN),
+ State.UNKNOWN),
+ ip=ipaddress,
+ port=port,
+ driver=self.connection.driver,
+ extra=extra
+ )
+
+ return balancer
+
+ def _to_members(self, object ):
+ members = []
+ for element in object.findall(fixxpath("poolMember", TYPES_URN)):
+ members.append(self._to_member(element))
+
+ return members
+
+ def _to_member(self, element):
+ pool = DimensionDataPoolMember(
+ id=element.get('id'),
+ name=element.find(fixxpath(
+ 'node',
+ TYPES_URN)).get('name'),
+ status=findtext(element, 'state', TYPES_URN),
+ ip_address=element.find(fixxpath(
+ 'node',
+ TYPES_URN)).get('ipAddress'),
+ port=int(findtext(element, 'port', TYPES_URN))
+ )
+ return pool
+
+ def _to_pools(self, object ):
+ pools = []
+ for element in object.findall(fixxpath("pool", TYPES_URN)):
+ pools.append(self._to_pool(element))
+
+ return pools
+
+ def _to_pool(self, element):
+ pool = DimensionDataPool(
+ id=element.get('id'),
+ name=findtext(element, 'name', TYPES_URN),
+ status=findtext(element,'state', TYPES_URN),
+ description=findtext(element, 'description', TYPES_URN)
+ )
+ return pool
http://git-wip-us.apache.org/repos/asf/libcloud/blob/b90c5610/libcloud/loadbalancer/providers.py
----------------------------------------------------------------------
diff --git a/libcloud/loadbalancer/providers.py b/libcloud/loadbalancer/providers.py
index 5a65402..a4ff090 100644
--- a/libcloud/loadbalancer/providers.py
+++ b/libcloud/loadbalancer/providers.py
@@ -40,7 +40,8 @@ DRIVERS = {
('libcloud.loadbalancer.drivers.gce', 'GCELBDriver'),
Provider.SOFTLAYER:
('libcloud.loadbalancer.drivers.softlayer', 'SoftlayerLBDriver'),
-
+ Provider.DIMENSIONDATA:
+ ('libcloud.loadbalancer.drivers.dimensiondata', 'DimensionDataLBDriver'),
# Deprecated
Provider.RACKSPACE_US:
('libcloud.loadbalancer.drivers.rackspace', 'RackspaceLBDriver'),
http://git-wip-us.apache.org/repos/asf/libcloud/blob/b90c5610/libcloud/loadbalancer/types.py
----------------------------------------------------------------------
diff --git a/libcloud/loadbalancer/types.py b/libcloud/loadbalancer/types.py
index 8ae82e9..9a34293 100644
--- a/libcloud/loadbalancer/types.py
+++ b/libcloud/loadbalancer/types.py
@@ -40,7 +40,8 @@ class Provider(object):
CLOUDSTACK = 'cloudstack'
GCE = 'gce'
SOFTLAYER = 'softlayer'
-
+ DIMENSIONDATA = 'dimensiondata'
+
# Deprecated
RACKSPACE_US = 'rackspace_us'
RACKSPACE_UK = 'rackspace_uk'
http://git-wip-us.apache.org/repos/asf/libcloud/blob/b90c5610/libcloud/test/loadbalancer/fixtures/dimensiondata/caas_2_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_pool.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/loadbalancer/fixtures/dimensiondata/caas_2_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_pool.xml b/libcloud/test/loadbalancer/fixtures/dimensiondata/caas_2_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_pool.xml
new file mode 100644
index 0000000..239fe52
--- /dev/null
+++ b/libcloud/test/loadbalancer/fixtures/dimensiondata/caas_2_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_pool.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<pools
+xmlns="urn:didata.com:api:cloud:types" pageNumber="1" pageCount="2"
+totalCount="2" pageSize="250">
+<pool id="4d360b1f-bc2c-4ab7-9884-1f03ba2768f7" datacenterId="NA9">
+<networkDomainId>553f26b6-2a73-42c3-a78b-
+6116f11291d0</networkDomainId>
+<name>myDevelopmentPool.1</name>
+<description>Pool for load balancing development application
+servers.</description>
+<loadBalanceMethod>ROUND_ROBIN</loadBalanceMethod>
+<healthMonitor id="01683574-d487-11e4-811f-005056806999"
+name="CCDEFAULT.Http"/>
+<healthMonitor id="0168546c-d487-11e4-811f-005056806999"
+name="CCDEFAULT.Https"/>
+<serviceDownAction>RESELECT</serviceDownAction>
+<slowRampTime>10</slowRampTime>
+<state>NORMAL</state>
+<createTime>2015-06-04T09:15:07.000Z</createTime>
+</pool>
+<pool id="afb1fb1a-eab9-43f4-95c2-36a4cdda6cb8" datacenterId="NA9">
+<networkDomainId>553f26b6-2a73-42c3-a78b-
+6116f11291d0</networkDomainId>
+<name>myProductionPool.1</name>
+<description>Pool for load balancing production application
+servers.</description>
+<loadBalanceMethod>ROUND_ROBIN</loadBalanceMethod>
+<healthMonitor id="01683574-d487-11e4-811f-005056806999"
+name="CCDEFAULT.Http"/>
+<healthMonitor id="0168546c-d487-11e4-811f-005056806999"
+name="CCDEFAULT.Https"/>
+<serviceDownAction>NONE</serviceDownAction>
+<slowRampTime>10</slowRampTime>
+<state>NORMAL</state>
+<createTime>2015-06-03T14:11:17.000Z</createTime>
+</pool>
+</pools>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/libcloud/blob/b90c5610/libcloud/test/loadbalancer/fixtures/dimensiondata/caas_2_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_poolMember.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/loadbalancer/fixtures/dimensiondata/caas_2_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_poolMember.xml b/libcloud/test/loadbalancer/fixtures/dimensiondata/caas_2_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_poolMember.xml
new file mode 100644
index 0000000..b36f75e
--- /dev/null
+++ b/libcloud/test/loadbalancer/fixtures/dimensiondata/caas_2_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_poolMember.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<poolMembers
+xmlns="urn:didata.com:api:cloud:types" pageNumber="1" pageCount="2"
+totalCount="2" pageSize="250">
+<poolMember id="3dd806a2-c2c8-4c0c-9a4f-5219ea9266c0" datacenterId="NA9">
+<networkDomainId>553f26b6-2a73-42c3-a78b-
+6116f11291d0</networkDomainId>
+<pool id="4d360b1f-bc2c-4ab7-9884-1f03ba2768f7"
+name="myDevelopmentPool.1"/>
+<node id="3c207269-e75e-11e4-811f-005056806999" name="10.0.3.13"
+ipAddress="10.0.3.13" status="ENABLED"/>
+<port>9889</port>
+<status>ENABLED</status>
+<state>NORMAL</state>
+<createTime>2015-06-09T11:02:50.000Z</createTime>
+</poolMember>
+<poolMember id="b977578b-a827-4172-b285-030c3ba15daa" datacenterId="NA9">
+<networkDomainId>553f26b6-2a73-42c3-a78b-
+6116f11291d0</networkDomainId>
+<pool id="4d360b1f-bc2c-4ab7-9884-1f03ba2768f7"
+name="myDevelopmentPool.1"/>
+<node id="3c207269-e75e-11e4-811f-005056806999" name="10.0.3.13"
+ipAddress="10.0.3.13" status="ENABLED"/>
+<port>9888</port>
+<status>ENABLED</status>
+<state>NORMAL</state>
+<createTime>2015-06-09T10:43:29.000Z</createTime>
+</poolMember>
+</poolMembers>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/libcloud/blob/b90c5610/libcloud/test/loadbalancer/fixtures/dimensiondata/caas_2_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_poolMember_3dd806a2_c2c8_4c0c_9a4f_5219ea9266c0.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/loadbalancer/fixtures/dimensiondata/caas_2_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_poolMember_3dd806a2_c2c8_4c0c_9a4f_5219ea9266c0.xml b/libcloud/test/loadbalancer/fixtures/dimensiondata/caas_2_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_poolMember_3dd806a2_c2c8_4c0c_9a4f_5219ea9266c0.xml
new file mode 100644
index 0000000..7c86d4a
--- /dev/null
+++ b/libcloud/test/loadbalancer/fixtures/dimensiondata/caas_2_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_poolMember_3dd806a2_c2c8_4c0c_9a4f_5219ea9266c0.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<poolMember
+xmlns="urn:didata.com:api:cloud:types" id="3dd806a2-c2c8-4c0c-9a4f-5219ea9266c0" datacenterId="NA9">
+<networkDomainId>553f26b6-2a73-42c3-a78b-6116f11291d0</networkDomainId>
+<pool id="6f2f5d7b-cdd9-4d84-8ad7-999b64a87978"
+name="myDevelopmentPool.1"/>
+<node id="3c207269-e75e-11e4-811f-005056806999" name="10.0.3.13"
+ipAddress="10.0.3.13" status="ENABLED"/>
+<port>9889</port>
+<status>ENABLED</status>
+<state>NORMAL</state>
+<createTime>2015-06-09T11:02:50.000Z</createTime>
+</poolMember>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/libcloud/blob/b90c5610/libcloud/test/loadbalancer/fixtures/dimensiondata/caas_2_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_pool_4d360b1f_bc2c_4ab7_9884_1f03ba2768f7.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/loadbalancer/fixtures/dimensiondata/caas_2_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_pool_4d360b1f_bc2c_4ab7_9884_1f03ba2768f7.xml b/libcloud/test/loadbalancer/fixtures/dimensiondata/caas_2_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_pool_4d360b1f_bc2c_4ab7_9884_1f03ba2768f7.xml
new file mode 100644
index 0000000..89d3400
--- /dev/null
+++ b/libcloud/test/loadbalancer/fixtures/dimensiondata/caas_2_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_pool_4d360b1f_bc2c_4ab7_9884_1f03ba2768f7.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<pool
+xmlns="urn:didata.com:api:cloud:types" id="4d360b1f-bc2c-4ab7-9884-1f03ba2768f7" datacenterId="NA9">
+<networkDomainId>553f26b6-2a73-42c3-a78b-6116f11291d0</networkDomainId>
+<name>myDevelopmentPool.1</name>
+<description>Pool for load balancing development application
+servers.</description>
+<loadBalanceMethod>ROUND_ROBIN</loadBalanceMethod>
+<healthMonitor id="01683574-d487-11e4-811f-005056806999"
+name="CCDEFAULT.Http"/>
+<healthMonitor id="0168546c-d487-11e4-811f-005056806999"
+name="CCDEFAULT.Https"/>
+<serviceDownAction>RESELECT</serviceDownAction>
+<slowRampTime>10</slowRampTime>
+<state>NORMAL</state>
+<createTime>2015-06-04T09:15:07.000Z</createTime>
+</pool>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/libcloud/blob/b90c5610/libcloud/test/loadbalancer/fixtures/dimensiondata/caas_2_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_virtualListener.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/loadbalancer/fixtures/dimensiondata/caas_2_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_virtualListener.xml b/libcloud/test/loadbalancer/fixtures/dimensiondata/caas_2_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_virtualListener.xml
new file mode 100644
index 0000000..88a94fa
--- /dev/null
+++ b/libcloud/test/loadbalancer/fixtures/dimensiondata/caas_2_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_virtualListener.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<virtualListeners
+xmlns="urn:didata.com:api:cloud:types" pageNumber="1" pageCount="5"
+totalCount="5" pageSize="250">
+<virtualListener id="6115469d-a8bb-445b-bb23-d23b5283f2b9"
+datacenterId="NA9">
+<networkDomainId>553f26b6-2a73-42c3-a78b-
+6116f11291d0</networkDomainId>
+<name>myProduction.Virtual.Listener</name>
+<state>NORMAL</state>
+<description>Virtual Listener for load balancing our test
+systems.</description>
+<createTime>2015-05-28T15:59:49.000Z</createTime>
+<type>PERFORMANCE_LAYER_4</type>
+<protocol>HTTP</protocol>
+<listenerIpAddress>165.180.12.22</listenerIpAddress>
+<port>8899</port>
+<enabled>true</enabled>
+<connectionLimit>10000</connectionLimit>
+<connectionRateLimit>400</connectionRateLimit>
+<sourcePortPreservation>PRESERVE</sourcePortPreservation>
+<pool id="afb1fb1a-eab9-43f4-95c2-36a4cdda6cb8"
+name="myProductionPool.1">
+<loadBalanceMethod>ROUND_ROBIN</loadBalanceMethod>
+<serviceDownAction>NONE</serviceDownAction>
+<slowRampTime>10</slowRampTime>
+<healthMonitor id="01683574-d487-11e4-811f-005056806999"
+name="CCDEFAULT.Http"/>
+<healthMonitor id="0168546c-d487-11e4-811f-005056806999"
+name="CCDEFAULT.Https"/>
+</pool>
+<clientClonePool id="6f2f5d7b-cdd9-4d84-8ad7-999b64a87978"
+name="myDevelopmentPool.1">
+<loadBalanceMethod>ROUND_ROBIN</loadBalanceMethod>
+<serviceDownAction>RESELECT</serviceDownAction>
+<slowRampTime>10</slowRampTime>
+<healthMonitor id="01683574-d487-11e4-811f-005056806999"
+name="CCDEFAULT.Http"/>
+<healthMonitor id="0168546c-d487-11e4-811f-005056806999"
+name="CCDEFAULT.Https"/>
+</clientClonePool>
+<persistenceProfile id="a34ca25c-f3db-11e4-b010-005056806999"
+name="CCDEFAULT.DestinationAddress"/>
+<fallbackPersistenceProfile id="a34ca3f6-f3db-11e4-b010-005056806999"
+name="CCDEFAULT.SourceAddress"/>
+<irule id="2b20abd9-ffdc-11e4-b010-005056806999"
+name="CCDEFAULT.IpProtocolTimers"/>
+<irule id="2b20e790-ffdc-11e4-b010-005056806999"
+name="CCDEFAULT.Ips"/>
+</virtualListener>
+</virtualListeners>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/libcloud/blob/b90c5610/libcloud/test/loadbalancer/fixtures/dimensiondata/caas_2_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_virtualListener_6115469d_a8bb_445b_bb23_d23b5283f2b9.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/loadbalancer/fixtures/dimensiondata/caas_2_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_virtualListener_6115469d_a8bb_445b_bb23_d23b5283f2b9.xml b/libcloud/test/loadbalancer/fixtures/dimensiondata/caas_2_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_virtualListener_6115469d_a8bb_445b_bb23_d23b5283f2b9.xml
new file mode 100644
index 0000000..aea2f6f
--- /dev/null
+++ b/libcloud/test/loadbalancer/fixtures/dimensiondata/caas_2_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_virtualListener_6115469d_a8bb_445b_bb23_d23b5283f2b9.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<virtualListener
+xmlns="urn:didata.com:api:cloud:types" id="6115469d-a8bb-445b-bb23-d23b5283f2b9" datacenterId="NA9">
+<networkDomainId>553f26b6-2a73-42c3-a78b-6116f11291d0</networkDomainId>
+<name>myProduction.Virtual.Listener</name>
+<state>NORMAL</state>
+<description>Virtual Listener for load balancing our test
+systems.</description>
+<createTime>2015-05-28T15:59:49.000Z</createTime>
+<type>PERFORMANCE_LAYER_4</type>
+<protocol>HTTP</protocol>
+<listenerIpAddress>165.180.12.22</listenerIpAddress>
+<port>8899</port>
+<enabled>true</enabled>
+<connectionLimit>10000</connectionLimit>
+<connectionRateLimit>400</connectionRateLimit>
+<sourcePortPreservation>PRESERVE</sourcePortPreservation>
+<pool id="afb1fb1a-eab9-43f4-95c2-36a4cdda6cb8" name="myProductionPool.1">
+<loadBalanceMethod>ROUND_ROBIN</loadBalanceMethod>
+<serviceDownAction>NONE</serviceDownAction>
+<slowRampTime>10</slowRampTime>
+<healthMonitor id="01683574-d487-11e4-811f-005056806999"
+name="CCDEFAULT.Http"/>
+<healthMonitor id="0168546c-d487-11e4-811f-005056806999"
+name="CCDEFAULT.Https"/>
+</pool>
+<clientClonePool id="6f2f5d7b-cdd9-4d84-8ad7-999b64a87978"
+name="myDevelopmentPool.1">
+<loadBalanceMethod>ROUND_ROBIN</loadBalanceMethod>
+<serviceDownAction>RESELECT</serviceDownAction>
+<slowRampTime>10</slowRampTime>
+<healthMonitor id="01683574-d487-11e4-811f-005056806999"
+name="CCDEFAULT.Http"/>
+<healthMonitor id="0168546c-d487-11e4-811f-005056806999"
+name="CCDEFAULT.Https"/>
+</clientClonePool>
+<persistenceProfile id="a34ca25c-f3db-11e4-b010-005056806999"
+name="CCDEFAULT.DestinationAddress"/>
+<fallbackPersistenceProfile id="a34ca3f6-f3db-11e4-b010-005056806999"
+name="CCDEFAULT.SourceAddress"/>
+<irule id="2b20abd9-ffdc-11e4-b010-005056806999"
+name="CCDEFAULT.IpProtocolTimers"/>
+<irule id="2b20e790-ffdc-11e4-b010-005056806999" name="CCDEFAULT.Ips"/>
+</virtualListener>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/libcloud/blob/b90c5610/libcloud/test/loadbalancer/fixtures/dimensiondata/oec_0_9_myaccount.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/loadbalancer/fixtures/dimensiondata/oec_0_9_myaccount.xml b/libcloud/test/loadbalancer/fixtures/dimensiondata/oec_0_9_myaccount.xml
new file mode 100644
index 0000000..4f3b132
--- /dev/null
+++ b/libcloud/test/loadbalancer/fixtures/dimensiondata/oec_0_9_myaccount.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<ns3:Account xmlns="http://oec.api.opsource.net/schemas/server" xmlns:ns9="http://oec.api.opsource.net/schemas/reset" xmlns:ns5="http://oec.api.opsource.net/schemas/vip" xmlns:ns12="http://oec.api.opsource.net/schemas/general" xmlns:ns6="http://oec.api.opsource.net/schemas/imageimportexport" xmlns:ns13="http://oec.api.opsource.net/schemas/support" xmlns:ns7="http://oec.api.opsource.net/schemas/whitelabel" xmlns:ns10="http://oec.api.opsource.net/schemas/ipplan" xmlns:ns8="http://oec.api.opsource.net/schemas/datacenter" xmlns:ns11="http://oec.api.opsource.net/schemas/storage" xmlns:ns2="http://oec.api.opsource.net/schemas/organization" xmlns:ns4="http://oec.api.opsource.net/schemas/network" xmlns:ns3="http://oec.api.opsource.net/schemas/directory">
+ <ns3:userName>testuser</ns3:userName>
+ <ns3:fullName>Test User</ns3:fullName>
+ <ns3:firstName>Test</ns3:firstName>
+ <ns3:lastName>User</ns3:lastName>
+ <ns3:emailAddress>test@example.com</ns3:emailAddress>
+ <ns3:orgId>8a8f6abc-2745-4d8a-9cbc-8dabe5a7d0e4</ns3:orgId>
+ <ns3:roles>
+ <ns3:role>
+ <ns3:name>create image</ns3:name>
+ </ns3:role>
+ <ns3:role>
+ <ns3:name>reports</ns3:name>
+ </ns3:role>
+ <ns3:role>
+ <ns3:name>server</ns3:name>
+ </ns3:role>
+ <ns3:role>
+ <ns3:name>primary administrator</ns3:name>
+ </ns3:role>
+ <ns3:role>
+ <ns3:name>network</ns3:name>
+ </ns3:role>
+ </ns3:roles>
+</ns3:Account>
http://git-wip-us.apache.org/repos/asf/libcloud/blob/b90c5610/libcloud/test/loadbalancer/test_dimensiondata.py
----------------------------------------------------------------------
diff --git a/libcloud/test/loadbalancer/test_dimensiondata.py b/libcloud/test/loadbalancer/test_dimensiondata.py
new file mode 100644
index 0000000..0c62087
--- /dev/null
+++ b/libcloud/test/loadbalancer/test_dimensiondata.py
@@ -0,0 +1,160 @@
+# 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.
+import sys
+import unittest
+from libcloud.utils.py3 import httplib
+
+from libcloud.common.types import InvalidCredsError
+from libcloud.loadbalancer.base import LoadBalancer
+from libcloud.loadbalancer.drivers.dimensiondata import DimensionDataLBDriver as DimensionData
+from libcloud.loadbalancer.types import State
+from libcloud.common.dimensiondata import DimensionDataAPIException
+
+from libcloud.test import MockHttp
+from libcloud.test.file_fixtures import LoadBalancerFileFixtures
+
+from libcloud.test.secrets import DIMENSIONDATA_PARAMS
+
+
+class DimensionDataTests(unittest.TestCase):
+
+ def setUp(self):
+ DimensionData.connectionCls.conn_classes = (None, DimensionDataMockHttp)
+ DimensionDataMockHttp.type = None
+ self.driver = DimensionData(*DIMENSIONDATA_PARAMS)
+
+ def test_invalid_creds(self):
+ DimensionDataMockHttp.type = 'UNAUTHORIZED'
+ try:
+ self.driver.list_balancers()
+ self.assertTrue(
+ False) # Above command should have thrown an InvalidCredsException
+ except InvalidCredsError:
+ pass
+
+ def test_list_balancers(self):
+ bal = self.driver.list_balancers()
+ self.assertEqual(bal[0].name, 'myProduction.Virtual.Listener')
+ self.assertEqual(bal[0].id, '6115469d-a8bb-445b-bb23-d23b5283f2b9')
+ self.assertEqual(bal[0].port, '8899')
+ self.assertEqual(bal[0].ip, '165.180.12.22')
+ self.assertEqual(bal[0].state, State.RUNNING)
+
+ def test_balancer_list_members(self):
+ extra={}
+ extra['pool_id']='4d360b1f-bc2c-4ab7-9884-1f03ba2768f7'
+ balancer = LoadBalancer(
+ id='234',
+ name='test',
+ state=State.RUNNING,
+ ip='1.2.3.4',
+ port=1234,
+ driver=self.driver,
+ extra=extra
+ )
+ members = self.driver.balancer_list_members(balancer)
+ self.assertEqual(2, len(members))
+ self.assertEqual(members[0].ip, '10.0.3.13')
+ self.assertEqual(members[0].id, '3dd806a2-c2c8-4c0c-9a4f-5219ea9266c0')
+ self.assertEqual(members[0].port, 9889)
+
+ def test_get_balancer(self):
+ bal = self.driver.get_balancer('6115469d-a8bb-445b-bb23-d23b5283f2b9')
+ self.assertEqual(bal.name, 'myProduction.Virtual.Listener')
+ self.assertEqual(bal.id, '6115469d-a8bb-445b-bb23-d23b5283f2b9')
+ self.assertEqual(bal.port, '8899')
+ self.assertEqual(bal.ip, '165.180.12.22')
+ self.assertEqual(bal.state, State.RUNNING)
+
+ def test_list_protocols(self):
+ protocols = self.driver.list_protocols()
+ self.assertNotEqual(0, len(protocols))
+
+ def test_get_pools(self):
+ pools = self.driver.ex_get_pools()
+ self.assertNotEqual(0, len(pools))
+ self.assertEqual(pools[0].name, 'myDevelopmentPool.1')
+ self.assertEqual(pools[0].id, '4d360b1f-bc2c-4ab7-9884-1f03ba2768f7')
+
+ def test_get_pool(self):
+ pool = self.driver.ex_get_pool('4d360b1f-bc2c-4ab7-9884-1f03ba2768f7')
+ self.assertEqual(pool.name, 'myDevelopmentPool.1')
+ self.assertEqual(pool.id, '4d360b1f-bc2c-4ab7-9884-1f03ba2768f7')
+
+ def test_get_pool_members(self):
+ members = self.driver.ex_get_pool_members('4d360b1f-bc2c-4ab7-9884-1f03ba2768f7')
+ self.assertEqual(2, len(members))
+ self.assertEqual(members[0].id, '3dd806a2-c2c8-4c0c-9a4f-5219ea9266c0')
+ self.assertEqual(members[0].name, '10.0.3.13')
+ self.assertEqual(members[0].status, 'NORMAL')
+ self.assertEqual(members[0].ip_address, '10.0.3.13')
+ self.assertEqual(members[0].port, 9889)
+
+ def test_get_pool_member(self):
+ member = self.driver.ex_get_pool_member('3dd806a2-c2c8-4c0c-9a4f-5219ea9266c0')
+ self.assertEqual(member.id, '3dd806a2-c2c8-4c0c-9a4f-5219ea9266c0')
+ self.assertEqual(member.name, '10.0.3.13')
+ self.assertEqual(member.status, 'NORMAL')
+ self.assertEqual(member.ip_address, '10.0.3.13')
+ self.assertEqual(member.port, 9889)
+
+class DimensionDataMockHttp(MockHttp):
+
+ fixtures = LoadBalancerFileFixtures('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_INPROGRESS(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_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_virtualListener(self, method, url, body, headers):
+ body = self.fixtures.load(
+ 'caas_2_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_virtualListener.xml')
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+ def _caas_2_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_virtualListener_6115469d_a8bb_445b_bb23_d23b5283f2b9(self, method, url, body, headers):
+ body = self.fixtures.load(
+ 'caas_2_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_virtualListener_6115469d_a8bb_445b_bb23_d23b5283f2b9.xml')
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+ def _caas_2_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_pool(self, method, url, body, headers):
+ body = self.fixtures.load(
+ 'caas_2_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_pool.xml')
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+ def _caas_2_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_pool_4d360b1f_bc2c_4ab7_9884_1f03ba2768f7(self, method, url, body, headers):
+ body = self.fixtures.load(
+ 'caas_2_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_pool_4d360b1f_bc2c_4ab7_9884_1f03ba2768f7.xml')
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+ def _caas_2_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_poolMember(self, method, url, body, headers):
+ body = self.fixtures.load(
+ 'caas_2_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_poolMember.xml')
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+ def _caas_2_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_poolMember_3dd806a2_c2c8_4c0c_9a4f_5219ea9266c0(self, method, url, body, headers):
+ body = self.fixtures.load(
+ 'caas_2_0_8a8f6abc_2745_4d8a_9cbc_8dabe5a7d0e4_networkDomainVip_poolMember_3dd806a2_c2c8_4c0c_9a4f_5219ea9266c0.xml')
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+
+if __name__ == '__main__':
+ sys.exit(unittest.main())