http://git-wip-us.apache.org/repos/asf/libcloud/blob/972a4f03/libcloud/common/dimensiondata.py ---------------------------------------------------------------------- diff --git a/libcloud/common/dimensiondata.py b/libcloud/common/dimensiondata.py index 8410106..4ba1bca 100644 --- a/libcloud/common/dimensiondata.py +++ b/libcloud/common/dimensiondata.py @@ -1,1492 +1,1505 @@ -# 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 time import sleep -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.compute.base import Node -from libcloud.utils.py3 import basestring -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" -GENERAL_NS = NAMESPACE_BASE + "/general" -BACKUP_NS = NAMESPACE_BASE + "/backup" - -# 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-au-gov': { - 'name': 'Australia Canberra ACT (AU)', - 'host': 'api-canberra.dimensiondata.com', - 'vendor': 'DimensionData' - }, - 'dd-af': { - 'name': 'Africa (AF)', - 'host': 'api-mea.dimensiondata.com', - 'vendor': 'DimensionData' - }, - 'dd-ap': { - 'name': 'Asia Pacific (AP)', - 'host': 'api-ap.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' - }, - 'is-na': { - 'name': 'North America (NA)', - 'host': 'usapi.cloud.is.co.za', - 'vendor': 'InternetSolutions' - }, - 'is-eu': { - 'name': 'Europe (EU)', - 'host': 'euapi.cloud.is.co.za', - 'vendor': 'InternetSolutions' - }, - 'is-au': { - 'name': 'Australia (AU)', - 'host': 'auapi.cloud.is.co.za', - 'vendor': 'InternetSolutions' - }, - 'is-af': { - 'name': 'Africa (AF)', - 'host': 'meaapi.cloud.is.co.za', - 'vendor': 'InternetSolutions' - }, - 'is-ap': { - 'name': 'Asia Pacific (AP)', - 'host': 'apapi.cloud.is.co.za', - 'vendor': 'InternetSolutions' - }, - 'is-latam': { - 'name': 'South America (LATAM)', - 'host': 'latamapi.cloud.is.co.za', - 'vendor': 'InternetSolutions' - }, - 'is-canada': { - 'name': 'Canada (CA)', - 'host': 'canadaapi.cloud.is.co.za', - 'vendor': 'InternetSolutions' - }, - 'ntta-na': { - 'name': 'North America (NA)', - 'host': 'cloudapi.nttamerica.com', - 'vendor': 'NTTNorthAmerica' - }, - 'ntta-eu': { - 'name': 'Europe (EU)', - 'host': 'eucloudapi.nttamerica.com', - 'vendor': 'NTTNorthAmerica' - }, - 'ntta-au': { - 'name': 'Australia (AU)', - 'host': 'aucloudapi.nttamerica.com', - 'vendor': 'NTTNorthAmerica' - }, - 'ntta-af': { - 'name': 'Africa (AF)', - 'host': 'sacloudapi.nttamerica.com', - 'vendor': 'NTTNorthAmerica' - }, - 'ntta-ap': { - 'name': 'Asia Pacific (AP)', - 'host': 'hkcloudapi.nttamerica.com', - 'vendor': 'NTTNorthAmerica' - }, - 'cisco-na': { - 'name': 'North America (NA)', - 'host': 'iaas-api-na.cisco-ccs.com', - 'vendor': 'Cisco' - }, - 'cisco-eu': { - 'name': 'Europe (EU)', - 'host': 'iaas-api-eu.cisco-ccs.com', - 'vendor': 'Cisco' - }, - 'cisco-au': { - 'name': 'Australia (AU)', - 'host': 'iaas-api-au.cisco-ccs.com', - 'vendor': 'Cisco' - }, - 'cisco-af': { - 'name': 'Africa (AF)', - 'host': 'iaas-api-mea.cisco-ccs.com', - 'vendor': 'Cisco' - }, - 'cisco-ap': { - 'name': 'Asia Pacific (AP)', - 'host': 'iaas-api-ap.cisco-ccs.com', - 'vendor': 'Cisco' - }, - 'cisco-latam': { - 'name': 'South America (LATAM)', - 'host': 'iaas-api-sa.cisco-ccs.com', - 'vendor': 'Cisco' - }, - 'cisco-canada': { - 'name': 'Canada (CA)', - 'host': 'iaas-api-ca.cisco-ccs.com', - 'vendor': 'Cisco' - }, - 'med1-il': { - 'name': 'Israel (IL)', - 'host': 'api.cloud.med-1.com', - 'vendor': 'Med-1' - }, - 'med1-na': { - 'name': 'North America (NA)', - 'host': 'api-na.cloud.med-1.com', - 'vendor': 'Med-1' - }, - 'med1-eu': { - 'name': 'Europe (EU)', - 'host': 'api-eu.cloud.med-1.com', - 'vendor': 'Med-1' - }, - 'med1-au': { - 'name': 'Australia (AU)', - 'host': 'api-au.cloud.med-1.com', - 'vendor': 'Med-1' - }, - 'med1-af': { - 'name': 'Africa (AF)', - 'host': 'api-af.cloud.med-1.com', - 'vendor': 'Med-1' - }, - 'med1-ap': { - 'name': 'Asia Pacific (AP)', - 'host': 'api-ap.cloud.med-1.com', - 'vendor': 'Med-1' - }, - 'med1-latam': { - 'name': 'South America (LATAM)', - 'host': 'api-sa.cloud.med-1.com', - 'vendor': 'Med-1' - }, - 'med1-canada': { - 'name': 'Canada (CA)', - 'host': 'api-ca.cloud.med-1.com', - 'vendor': 'Med-1' - }, - 'indosat-id': { - 'name': 'Indonesia (ID)', - 'host': 'iaas-api.indosat.com', - 'vendor': 'Indosat' - }, - 'indosat-na': { - 'name': 'North America (NA)', - 'host': 'iaas-usapi.indosat.com', - 'vendor': 'Indosat' - }, - 'indosat-eu': { - 'name': 'Europe (EU)', - 'host': 'iaas-euapi.indosat.com', - 'vendor': 'Indosat' - }, - 'indosat-au': { - 'name': 'Australia (AU)', - 'host': 'iaas-auapi.indosat.com', - 'vendor': 'Indosat' - }, - 'indosat-af': { - 'name': 'Africa (AF)', - 'host': 'iaas-afapi.indosat.com', - 'vendor': 'Indosat' - }, - 'bsnl-in': { - 'name': 'India (IN)', - 'host': 'api.bsnlcloud.com', - 'vendor': 'BSNL' - }, - 'bsnl-na': { - 'name': 'North America (NA)', - 'host': 'usapi.bsnlcloud.com', - 'vendor': 'BSNL' - }, - 'bsnl-eu': { - 'name': 'Europe (EU)', - 'host': 'euapi.bsnlcloud.com', - 'vendor': 'BSNL' - }, - 'bsnl-au': { - 'name': 'Australia (AU)', - 'host': 'auapi.bsnlcloud.com', - 'vendor': 'BSNL' - }, - 'bsnl-af': { - 'name': 'Africa (AF)', - 'host': 'afapi.bsnlcloud.com', - 'vendor': 'BSNL' - }, -} - -# Default API end-point for the base connection class. -DEFAULT_REGION = 'dd-na' - -BAD_CODE_XML_ELEMENTS = ( - ('responseCode', SERVER_NS), - ('responseCode', TYPES_URN), - ('result', GENERAL_NS) -) - -BAD_MESSAGE_XML_ELEMENTS = ( - ('message', SERVER_NS), - ('message', TYPES_URN), - ('resultDetail', GENERAL_NS) -) - - -def dd_object_to_id(obj, obj_type, id_value='id'): - """ - Takes in a DD object or string and prints out it's id - This is a helper method, as many of our functions can take either an object - or a string, and we need an easy way of converting them - - :param obj: The object to get the id for - :type obj: ``object`` - - :param func: The function to call, e.g. ex_get_vlan. Note: This - function needs to return an object which has ``status`` - attribute. - :type func: ``function`` - - :rtype: ``str`` - """ - if isinstance(obj, obj_type): - return getattr(obj, id_value) - elif isinstance(obj, (basestring)): - return obj - else: - raise TypeError( - "Invalid type %s looking for basestring or %s" - % (type(obj).__name__, obj_type.__name__) - ) - - -class NetworkDomainServicePlan(object): - ESSENTIALS = "ESSENTIALS" - ADVANCED = "ADVANCED" - - -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() - - if self.status == httplib.BAD_REQUEST: - for response_code in BAD_CODE_XML_ELEMENTS: - code = findtext(body, response_code[0], response_code[1]) - if code is not None: - break - for message in BAD_MESSAGE_XML_ELEMENTS: - message = findtext(body, message[0], message[1]) - if message is not None: - break - raise DimensionDataAPIException(code=code, - msg=message, - driver=self.connection.driver) - if self.status is not httplib.OK: - raise DimensionDataAPIException(code=self.status, - msg=body, - 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.2' - - _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')) - headers['Content-Type'] = 'application/xml' - 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 paginated_request_with_orgId_api_2(self, action, params=None, data='', - headers=None, method='GET', - page_size=250): - """ - A paginated request to the MCP2.0 API - This essentially calls out to request_with_orgId_api_2 for each page - and yields the response to make a generator - This generator can be looped through to grab all the pages. - - :param action: The resource to access (i.e. 'network/vlan') - :type action: ``str`` - - :param params: Parameters to give to the action - :type params: ``dict`` or ``None`` - - :param data: The data payload to be added to the request - :type data: ``str`` - - :param headers: Additional header to be added to the request - :type headers: ``str`` or ``dict`` or ``None`` - - :param method: HTTP Method for the request (i.e. 'GET', 'POST') - :type method: ``str`` - - :param page_size: The size of each page to be returned - Note: Max page size in MCP2.0 is currently 250 - :type page_size: ``int`` - """ - if params is None: - params = {} - params['pageSize'] = page_size - - paged_resp = self.request_with_orgId_api_2(action, params, - data, headers, - method).object - yield paged_resp - paged_resp = paged_resp or {} - - while int(paged_resp.get('pageCount')) >= \ - int(paged_resp.get('pageSize')): - params['pageNumber'] = int(paged_resp.get('pageNumber')) + 1 - paged_resp = self.request_with_orgId_api_2(action, params, - data, headers, - method).object - yield paged_resp - - 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 wait_for_state(self, state, func, poll_interval=2, timeout=60, *args, - **kwargs): - """ - Wait for the function which returns a instance with field status/state - to match. - - Keep polling func until one of the desired states is matched - - :param state: Either the desired state (`str`) or a `list` of states - :type state: ``str`` or ``list`` - - :param func: The function to call, e.g. ex_get_vlan. Note: This - function needs to return an object which has ``status`` - attribute. - :type func: ``function`` - - :param poll_interval: The number of seconds to wait between checks - :type poll_interval: `int` - - :param timeout: The total number of seconds to wait to reach a state - :type timeout: `int` - - :param args: The arguments for func - :type args: Positional arguments - - :param kwargs: The arguments for func - :type kwargs: Keyword arguments - - :return: Result from the calling function. - """ - cnt = 0 - while cnt < timeout / poll_interval: - result = func(*args, **kwargs) - if isinstance(result, Node): - object_state = result.state - else: - object_state = result.status - - if object_state is state or object_state in state: - return result - sleep(poll_interval) - cnt += 1 - - msg = 'Status check for object %s timed out' % (result) - raise DimensionDataAPIException(code=object_state, - msg=msg, - driver=self.driver) - - 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 - - def get_account_details(self): - """ - Get the details of this account - - :rtype: :class:`DimensionDataAccountDetails` - """ - body = self.request_api_1('myaccount').object - return DimensionDataAccountDetails( - user_name=findtext(body, 'userName', DIRECTORY_NS), - full_name=findtext(body, 'fullName', DIRECTORY_NS), - first_name=findtext(body, 'firstName', DIRECTORY_NS), - last_name=findtext(body, 'lastName', DIRECTORY_NS), - email=findtext(body, 'emailAddress', DIRECTORY_NS)) - - -class DimensionDataAccountDetails(object): - """ - Dimension Data account class details - """ - def __init__(self, user_name, full_name, first_name, last_name, email): - self.user_name = user_name - self.full_name = full_name - self.first_name = first_name - self.last_name = last_name - self.email = email - - -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, plan): - self.id = str(id) - self.name = name - self.description = description - self.location = location - self.status = status - self.plan = plan - - 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 DimensionDataPublicIpBlock(object): - """ - DimensionData Public IP Block with location. - """ - - def __init__(self, id, base_ip, size, location, network_domain, - status): - self.id = str(id) - self.base_ip = base_ip - self.size = size - self.location = location - self.network_domain = network_domain - self.status = status - - def __repr__(self): - return (('<DimensionDataNetworkDomain: id=%s, base_ip=%s, ' - 'size=%s, location=%s, status=%s>') - % (self.id, self.base_ip, self.size, self.location, - self.status)) - - -class DimensionDataServerCpuSpecification(object): - """ - A class that represents the specification of the CPU(s) for a - node - """ - def __init__(self, cpu_count, cores_per_socket, performance): - """ - Instantiate a new :class:`DimensionDataServerCpuSpecification` - - :param cpu_count: The number of CPUs - :type cpu_count: ``int`` - - :param cores_per_socket: The number of cores per socket, the - recommendation is 1 - :type cores_per_socket: ``int`` - - :param performance: The performance type, e.g. HIGHPERFORMANCE - :type performance: ``str`` - """ - self.cpu_count = cpu_count - self.cores_per_socket = cores_per_socket - self.performance = performance - - def __repr__(self): - return (('<DimensionDataServerCpuSpecification: ' - 'cpu_count=%s, cores_per_socket=%s, ' - 'performance=%s>') - % (self.cpu_count, self.cores_per_socket, self.performance)) - - -class DimensionDataServerDisk(object): - """ - A class that represents the disk on a server - """ - def __init__(self, id, scsi_id, size_gb, speed, state): - """ - Instantiate a new :class:`DimensionDataServerDisk` - - :param id: The id of the disk - :type id: ``str`` - - :param scsi_id: Representation for scsi - :type scsi_id: ``int`` - - :param size_gb: Size of the disk - :type size_gb: ``int`` - - :param speed: Speed of the disk (i.e. STANDARD) - :type speed: ``str`` - - :param state: State of the disk (i.e. PENDING) - :type state: ``str`` - """ - self.id = id - self.scsi_id = scsi_id - self.size_gb = size_gb - self.speed = speed - self.state = state - - def __repr__(self): - return (('<DimensionDataServerDisk: ' - 'id=%s, size_gb=%s') - % (self.id, self.size_gb)) - - -class DimensionDataServerVMWareTools(object): - """ - A class that represents the VMWareTools for a node - """ - def __init__(self, status, version_status, api_version): - """ - Instantiate a new :class:`DimensionDataServerVMWareTools` object - - :param status: The status of VMWare Tools - :type status: ``str`` - - :param version_status: The status for the version of VMWare Tools - (i.e NEEDS_UPGRADE) - :type version_status: ``str`` - - :param api_version: The API version of VMWare Tools - :type api_version: ``str`` - """ - self.status = status - self.version_status = version_status - self.api_version = api_version - - def __repr__(self): - return (('<DimensionDataServerVMWareTools ' - 'status=%s, version_status=%s, ' - 'api_version=%s>') - % (self.status, self.version_status, self.api_version)) - - -class DimensionDataFirewallRule(object): - """ - DimensionData Firewall Rule for a network domain - """ - - def __init__(self, id, name, action, location, network_domain, - status, ip_version, protocol, source, destination, - enabled): - self.id = str(id) - self.name = name - self.action = action - self.location = location - self.network_domain = network_domain - self.status = status - self.ip_version = ip_version - self.protocol = protocol - self.source = source - self.destination = destination - self.enabled = enabled - - def __repr__(self): - return (('<DimensionDataFirewallRule: id=%s, name=%s, ' - 'action=%s, location=%s, network_domain=%s, ' - 'status=%s, ip_version=%s, protocol=%s, source=%s, ' - 'destination=%s, enabled=%s>') - % (self.id, self.name, self.action, self.location, - self.network_domain, self.status, self.ip_version, - self.protocol, self.source, self.destination, - self.enabled)) - - -class DimensionDataFirewallAddress(object): - """ - The source or destination model in a firewall rule - """ - def __init__(self, any_ip, ip_address, ip_prefix_size, - port_begin, port_end, address_list_id, - port_list_id): - self.any_ip = any_ip - self.ip_address = ip_address - self.ip_prefix_size = ip_prefix_size - self.port_begin = port_begin - self.port_end = port_end - self.address_list_id = address_list_id - self.port_list_id = port_list_id - - -class DimensionDataNatRule(object): - """ - An IP NAT rule in a network domain - """ - def __init__(self, id, network_domain, internal_ip, external_ip, status): - self.id = id - self.network_domain = network_domain - self.internal_ip = internal_ip - self.external_ip = external_ip - self.status = status - - def __repr__(self): - return (('<DimensionDataNatRule: id=%s, status=%s>') - % (self.id, self.status)) - - -class DimensionDataAntiAffinityRule(object): - """ - Anti-Affinity rule for DimensionData - - An Anti-Affinity rule ensures that servers in the rule will - not reside on the same VMware ESX host. - """ - def __init__(self, id, node_list): - """ - Instantiate a new :class:`DimensionDataAntiAffinityRule` - - :param id: The ID of the Anti-Affinity rule - :type id: ``str`` - - :param node_list: List of node ids that belong in this rule - :type node_list: ``list`` of ``str`` - """ - self.id = id - self.node_list = node_list - - def __repr__(self): - return (('<DimensionDataAntiAffinityRule: id=%s>') - % (self.id)) - - -class DimensionDataVlan(object): - """ - DimensionData VLAN. - """ - - def __init__(self, id, name, description, location, network_domain, - status, private_ipv4_range_address, private_ipv4_range_size, - ipv6_range_address, ipv6_range_size, ipv4_gateway, - ipv6_gateway): - """ - Initialize an instance of ``DimensionDataVlan`` - - :param id: The ID of the VLAN - :type id: ``str`` - - :param name: The name of the VLAN - :type name: ``str`` - - :param description: Plan text description of the VLAN - :type description: ``str`` - - :param location: The location (data center) of the VLAN - :type location: ``NodeLocation`` - - :param network_domain: The Network Domain that owns this VLAN - :type network_domain: :class:`DimensionDataNetworkDomain` - - :param status: The status of the VLAN - :type status: :class:`DimensionDataStatus` - - :param private_ipv4_range_address: The host address of the VLAN - IP space - :type private_ipv4_range_address: ``str`` - - :param private_ipv4_range_size: The size (e.g. '24') of the VLAN - as a CIDR range size - :type private_ipv4_range_size: ``int`` - - :param ipv6_range_address: The host address of the VLAN - IP space - :type ipv6_range_address: ``str`` - - :param ipv6_range_size: The size (e.g. '32') of the VLAN - as a CIDR range size - :type ipv6_range_size: ``int`` - - :param ipv4_gateway: The IPv4 default gateway address - :type ipv4_gateway: ``str`` - - :param ipv6_gateway: The IPv6 default gateway address - :type ipv6_gateway: ``str`` - """ - self.id = str(id) - self.name = name - self.location = location - self.description = description - self.network_domain = network_domain - self.status = status - self.private_ipv4_range_address = private_ipv4_range_address - self.private_ipv4_range_size = private_ipv4_range_size - self.ipv6_range_address = ipv6_range_address - self.ipv6_range_size = ipv6_range_size - self.ipv4_gateway = ipv4_gateway - self.ipv6_gateway = ipv6_gateway - - def __repr__(self): - return (('<DimensionDataVlan: 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, load_balance_method, - health_monitor_id, service_down_action, slow_ramp_time): - """ - Initialize an instance of ``DimensionDataPool`` - - :param id: The ID of the pool - :type id: ``str`` - - :param name: The name of the pool - :type name: ``str`` - - :param description: Plan text description of the pool - :type description: ``str`` - - :param status: The status of the pool - :type status: :class:`DimensionDataStatus` - - :param load_balance_method: The load balancer method - :type load_balance_method: ``str`` - - :param health_monitor_id: The ID of the health monitor - :type health_monitor_id: ``str`` - - :param service_down_action: Action to take when pool is down - :type service_down_action: ``str`` - - :param slow_ramp_time: The ramp-up time for service recovery - :type slow_ramp_time: ``int`` - """ - self.id = str(id) - self.name = name - self.description = description - self.status = status - self.load_balance_method = load_balance_method - self.health_monitor_id = health_monitor_id - self.service_down_action = service_down_action - self.slow_ramp_time = slow_ramp_time - - 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, port, node_id): - """ - Initialize an instance of ``DimensionDataPoolMember`` - - :param id: The ID of the pool member - :type id: ``str`` - - :param name: The name of the pool member - :type name: ``str`` - - :param status: The status of the pool - :type status: :class:`DimensionDataStatus` - - :param ip: The IP of the pool member - :type ip: ``str`` - - :param port: The port of the pool member - :type port: ``int`` - - :param node_id: The ID of the associated node - :type node_id: ``str`` - """ - self.id = str(id) - self.name = name - self.status = status - self.ip = ip - self.port = port - self.node_id = node_id - - def __repr__(self): - return (('<DimensionDataPoolMember: id=%s, name=%s, ' - 'ip=%s, status=%s, port=%s, node_id=%s>') - % (self.id, self.name, - self.ip, self.status, self.port, - self.node_id)) - - -class DimensionDataVIPNode(object): - def __init__(self, id, name, status, ip, connection_limit='10000', - connection_rate_limit='10000'): - """ - Initialize an instance of :class:`DimensionDataVIPNode` - - :param id: The ID of the node - :type id: ``str`` - - :param name: The name of the node - :type name: ``str`` - - :param status: The status of the node - :type status: :class:`DimensionDataStatus` - - :param ip: The IP of the node - :type ip: ``str`` - - :param connection_limit: The total connection limit for the node - :type connection_limit: ``int`` - - :param connection_rate_limit: The rate limit for the node - :type connection_rate_limit: ``int`` - """ - self.id = str(id) - self.name = name - self.status = status - self.ip = ip - self.connection_limit = connection_limit - self.connection_rate_limit = connection_rate_limit - - def __repr__(self): - return (('<DimensionDataVIPNode: id=%s, name=%s, ' - 'status=%s, ip=%s>') - % (self.id, self.name, - self.status, self.ip)) - - -class DimensionDataVirtualListener(object): - """ - DimensionData Virtual Listener. - """ - - def __init__(self, id, name, status, ip): - """ - Initialize an instance of :class:`DimensionDataVirtualListener` - - :param id: The ID of the listener - :type id: ``str`` - - :param name: The name of the listener - :type name: ``str`` - - :param status: The status of the listener - :type status: :class:`DimensionDataStatus` - - :param ip: The IP of the listener - :type ip: ``str`` - """ - self.id = str(id) - self.name = name - self.status = status - self.ip = ip - - def __repr__(self): - return (('<DimensionDataVirtualListener: id=%s, name=%s, ' - 'status=%s, ip=%s>') - % (self.id, self.name, - self.status, self.ip)) - - -class DimensionDataDefaultHealthMonitor(object): - """ - A default health monitor for a VIP (node, pool or listener) - """ - def __init__(self, id, name, node_compatible, pool_compatible): - """ - Initialize an instance of :class:`DimensionDataDefaultHealthMonitor` - - :param id: The ID of the monitor - :type id: ``str`` - - :param name: The name of the monitor - :type name: ``str`` - - :param node_compatible: Is a monitor capable of monitoring nodes - :type node_compatible: ``bool`` - - :param pool_compatible: Is a monitor capable of monitoring pools - :type pool_compatible: ``bool`` - """ - self.id = id - self.name = name - self.node_compatible = node_compatible - self.pool_compatible = pool_compatible - - def __repr__(self): - return (('<DimensionDataDefaultHealthMonitor: id=%s, name=%s>') - % (self.id, self.name)) - - -class DimensionDataPersistenceProfile(object): - """ - Each Persistence Profile declares the combination of Virtual Listener - type and protocol with which it is - compatible and whether or not it is compatible as a - Fallback Persistence Profile. - """ - def __init__(self, id, name, compatible_listeners, fallback_compatible): - """ - Initialize an instance of :class:`DimensionDataPersistenceProfile` - - :param id: The ID of the profile - :type id: ``str`` - - :param name: The name of the profile - :type name: ``str`` - - :param compatible_listeners: List of compatible Virtual Listener types - :type compatible_listeners: ``list`` of - :class:`DimensionDataVirtualListenerCompatibility` - - :param fallback_compatible: Is capable as a fallback profile - :type fallback_compatible: ``bool`` - """ - self.id = id - self.name = name - self.compatible_listeners = compatible_listeners - self.fallback_compatible = fallback_compatible - - def __repr__(self): - return (('<DimensionDataPersistenceProfile: id=%s, name=%s>') - % (self.id, self.name)) - - -class DimensionDataDefaultiRule(object): - """ - A default iRule for a network domain, can be applied to a listener - """ - def __init__(self, id, name, compatible_listeners): - """ - Initialize an instance of :class:`DimensionDataDefaultiRule` - - :param id: The ID of the iRule - :type id: ``str`` - - :param name: The name of the iRule - :type name: ``str`` - - :param compatible_listeners: List of compatible Virtual Listener types - :type compatible_listeners: ``list`` of - :class:`DimensionDataVirtualListenerCompatibility` - """ - self.id = id - self.name = name - self.compatible_listeners = compatible_listeners - - def __repr__(self): - return (('<DimensionDataDefaultiRule: id=%s, name=%s>') - % (self.id, self.name)) - - -class DimensionDataVirtualListenerCompatibility(object): - """ - A compatibility preference for a persistence profile or iRule - specifies which virtual listener types this profile or iRule can be - applied to. - """ - def __init__(self, type, protocol): - self.type = type - self.protocol = protocol - - def __repr__(self): - return (('<DimensionDataVirtualListenerCompatibility: ' - 'type=%s, protocol=%s>') - % (self.type, self.protocol)) - - -class DimensionDataBackupDetails(object): - """ - Dimension Data Backup Details represents information about - a targets backups configuration - """ - - def __init__(self, asset_id, service_plan, status, clients=None): - """ - Initialize an instance of :class:`DimensionDataBackupDetails` - - :param asset_id: Asset identification for backups - :type asset_id: ``str`` - - :param service_plan: The service plan for backups. i.e (Essentials) - :type service_plan: ``str`` - - :param status: The overall status this backup target. - i.e. (unregistered) - :type status: ``str`` - - :param clients: Backup clients attached to this target - :type clients: ``list`` of :class:`DimensionDataBackupClient` - """ - self.asset_id = asset_id - self.service_plan = service_plan - self.status = status - self.clients = clients - - def __repr__(self): - return (('<DimensionDataBackupDetails: id=%s>') - % (self.asset_id)) - - -class DimensionDataBackupClient(object): - """ - An object that represents a backup client - """ - def __init__(self, id, type, status, - schedule_policy, storage_policy, download_url, - alert=None, running_job=None): - """ - Initialize an instance of :class:`DimensionDataBackupClient` - - :param id: Unique ID for the client - :type id: ``str`` - - :param type: The type of client that this client is - :type type: :class:`DimensionDataBackupClientType` - - :param status: The states of this particular backup client. - i.e. (Unregistered) - :type status: ``str`` - - :param schedule_policy: The schedule policy for this client - NOTE: Dimension Data only sends back the name - of the schedule policy, no further details - :type schedule_policy: ``str`` - - :param storage_policy: The storage policy for this client - NOTE: Dimension Data only sends back the name - of the storage policy, no further details - :type storage_policy: ``str`` - - :param download_url: The download url for this client - :type download_url: ``str`` - - :param alert: The alert configured for this backup client (optional) - :type alert: :class:`DimensionDataBackupClientAlert` - - :param alert: The running job for the client (optional) - :type alert: :class:`DimensionDataBackupClientRunningJob` - """ - self.id = id - self.type = type - self.status = status - self.schedule_policy = schedule_policy - self.storage_policy = storage_policy - self.download_url = download_url - self.alert = alert - self.running_job = running_job - - def __repr__(self): - return (('<DimensionDataBackupClient: id=%s>') - % (self.id)) - - -class DimensionDataBackupClientAlert(object): - """ - An alert for a backup client - """ - def __init__(self, trigger, notify_list=[]): - """ - Initialize an instance of :class:`DimensionDataBackupClientAlert` - - :param trigger: Trigger type for the client i.e. ON_FAILURE - :type trigger: ``str`` - - :param notify_list: List of email addresses that are notified - when the alert is fired - :type notify_list: ``list`` of ``str`` - """ - self.trigger = trigger - self.notify_list = notify_list - - def __repr__(self): - return (('<DimensionDataBackupClientAlert: trigger=%s>') - % (self.trigger)) - - -class DimensionDataBackupClientRunningJob(object): - """ - A running job for a given backup client - """ - def __init__(self, id, status, percentage=0): - """ - Initialize an instance of :class:`DimensionDataBackupClientRunningJob` - - :param id: The unqiue ID of the job - :type id: ``str`` - - :param status: The status of the job i.e. Waiting - :type status: ``str`` - - :param percentage: The percentage completion of the job - :type percentage: ``int`` - """ - self.id = id - self.percentage = percentage - self.status = status - - def __repr__(self): - return (('<DimensionDataBackupClientRunningJob: id=%s>') - % (self.id)) - - -class DimensionDataBackupClientType(object): - """ - A client type object for backups - """ - def __init__(self, type, is_file_system, description): - """ - Initialize an instance of :class:`DimensionDataBackupClientType` - - :param type: The type of client i.e. (FA.Linux, MySQL, ect.) - :type type: ``str`` - - :param is_file_system: The name of the iRule - :type is_file_system: ``bool`` - - :param description: Description of the client - :type description: ``str`` - """ - self.type = type - self.is_file_system = is_file_system - self.description = description - - def __repr__(self): - return (('<DimensionDataBackupClientType: type=%s>') - % (self.type)) - - -class DimensionDataBackupStoragePolicy(object): - """ - A representation of a storage policy - """ - def __init__(self, name, retention_period, secondary_location): - """ - Initialize an instance of :class:`DimensionDataBackupStoragePolicy` - - :param name: The name of the storage policy i.e. 14 Day Storage Policy - :type name: ``str`` - - :param retention_period: How long to keep the backup in days - :type retention_period: ``int`` - - :param secondary_location: The secondary location i.e. Primary - :type secondary_location: ``str`` - """ - self.name = name - self.retention_period = retention_period - self.secondary_location = secondary_location - - def __repr__(self): - return (('<DimensionDataBackupStoragePolicy: name=%s>') - % (self.name)) - - -class DimensionDataBackupSchedulePolicy(object): - """ - A representation of a schedule policy - """ - def __init__(self, name, description): - """ - Initialize an instance of :class:`DimensionDataBackupSchedulePolicy` - - :param name: The name of the policy i.e 12AM - 6AM - :type name: ``str`` - - :param description: Short summary of the details of the policy - :type description: ``str`` - """ - self.name = name - self.description = description - - def __repr__(self): - return (('<DimensionDataBackupSchedulePolicy: name=%s>') - % (self.name)) - - -class DimensionDataTag(object): - """ - A representation of a Tag in Dimension Data - A Tag first must have a Tag Key, then an asset is tag with - a key and an option value. Tags can be queried later to filter assets - and also show up on usage report if so desired. - """ - def __init__(self, asset_type, asset_id, asset_name, - datacenter, key, value): - """ - Initialize an instance of :class:`DimensionDataTag` - - :param asset_type: The type of asset. Current asset types: - SERVER, VLAN, NETWORK_DOMAIN, CUSTOMER_IMAGE, - PUBLIC_IP_BLOCK, ACCOUNT - :type asset_type: ``str`` - - :param asset_id: The GUID of the asset that is tagged - :type asset_id: ``str`` - - :param asset_name: The name of the asset that is tagged - :type asset_name: ``str`` - - :param datacenter: The short datacenter name of the tagged asset - :type datacenter: ``str`` - - :param key: The tagged key - :type key: :class:`DimensionDataTagKey` - - :param value: The tagged value - :type value: ``None`` or ``str`` - """ - self.asset_type = asset_type - self.asset_id = asset_id - self.asset_name = asset_name - self.datacenter = datacenter - self.key = key - self.value = value - - def __repr__(self): - return (('<DimensionDataTag: asset_name=%s, tag_name=%s, value=%s>') - % (self.asset_name, self.key.name, self.value)) - - -class DimensionDataTagKey(object): - """ - A representation of a Tag Key in Dimension Data - A tag key is required to tag an asset - """ - def __init__(self, id, name, description, - value_required, display_on_report): - """ - Initialize an instance of :class:`DimensionDataTagKey` - - :param id: GUID of the tag key - :type id: ``str`` - - :param name: Name of the tag key - :type name: ``str`` - - :param description: Description of the tag key - :type description: ``str`` - - :param value_required: If a value is required for this tag key - :type value_required: ``bool`` - - :param display_on_report: If this tag key should be displayed on - usage reports - :type display_on_report: ``bool`` - """ - self.id = id - self.name = name - self.description = description - self.value_required = value_required - self.display_on_report = display_on_report - - def __repr__(self): - return (('<DimensionDataTagKey: name=%s>') - % (self.name)) +# 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 time import sleep +from libcloud.utils.py3 import httplib +from libcloud.utils.py3 import b +from libcloud.common.base import ConnectionUserAndKey, XmlResponse, RawResponse +from libcloud.common.types import LibcloudError, InvalidCredsError +from libcloud.compute.base import Node +from libcloud.utils.py3 import basestring +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" +GENERAL_NS = NAMESPACE_BASE + "/general" +BACKUP_NS = NAMESPACE_BASE + "/backup" + +# 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-au-gov': { + 'name': 'Australia Canberra ACT (AU)', + 'host': 'api-canberra.dimensiondata.com', + 'vendor': 'DimensionData' + }, + 'dd-af': { + 'name': 'Africa (AF)', + 'host': 'api-mea.dimensiondata.com', + 'vendor': 'DimensionData' + }, + 'dd-ap': { + 'name': 'Asia Pacific (AP)', + 'host': 'api-ap.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' + }, + 'is-na': { + 'name': 'North America (NA)', + 'host': 'usapi.cloud.is.co.za', + 'vendor': 'InternetSolutions' + }, + 'is-eu': { + 'name': 'Europe (EU)', + 'host': 'euapi.cloud.is.co.za', + 'vendor': 'InternetSolutions' + }, + 'is-au': { + 'name': 'Australia (AU)', + 'host': 'auapi.cloud.is.co.za', + 'vendor': 'InternetSolutions' + }, + 'is-af': { + 'name': 'Africa (AF)', + 'host': 'meaapi.cloud.is.co.za', + 'vendor': 'InternetSolutions' + }, + 'is-ap': { + 'name': 'Asia Pacific (AP)', + 'host': 'apapi.cloud.is.co.za', + 'vendor': 'InternetSolutions' + }, + 'is-latam': { + 'name': 'South America (LATAM)', + 'host': 'latamapi.cloud.is.co.za', + 'vendor': 'InternetSolutions' + }, + 'is-canada': { + 'name': 'Canada (CA)', + 'host': 'canadaapi.cloud.is.co.za', + 'vendor': 'InternetSolutions' + }, + 'ntta-na': { + 'name': 'North America (NA)', + 'host': 'cloudapi.nttamerica.com', + 'vendor': 'NTTNorthAmerica' + }, + 'ntta-eu': { + 'name': 'Europe (EU)', + 'host': 'eucloudapi.nttamerica.com', + 'vendor': 'NTTNorthAmerica' + }, + 'ntta-au': { + 'name': 'Australia (AU)', + 'host': 'aucloudapi.nttamerica.com', + 'vendor': 'NTTNorthAmerica' + }, + 'ntta-af': { + 'name': 'Africa (AF)', + 'host': 'sacloudapi.nttamerica.com', + 'vendor': 'NTTNorthAmerica' + }, + 'ntta-ap': { + 'name': 'Asia Pacific (AP)', + 'host': 'hkcloudapi.nttamerica.com', + 'vendor': 'NTTNorthAmerica' + }, + 'cisco-na': { + 'name': 'North America (NA)', + 'host': 'iaas-api-na.cisco-ccs.com', + 'vendor': 'Cisco' + }, + 'cisco-eu': { + 'name': 'Europe (EU)', + 'host': 'iaas-api-eu.cisco-ccs.com', + 'vendor': 'Cisco' + }, + 'cisco-au': { + 'name': 'Australia (AU)', + 'host': 'iaas-api-au.cisco-ccs.com', + 'vendor': 'Cisco' + }, + 'cisco-af': { + 'name': 'Africa (AF)', + 'host': 'iaas-api-mea.cisco-ccs.com', + 'vendor': 'Cisco' + }, + 'cisco-ap': { + 'name': 'Asia Pacific (AP)', + 'host': 'iaas-api-ap.cisco-ccs.com', + 'vendor': 'Cisco' + }, + 'cisco-latam': { + 'name': 'South America (LATAM)', + 'host': 'iaas-api-sa.cisco-ccs.com', + 'vendor': 'Cisco' + }, + 'cisco-canada': { + 'name': 'Canada (CA)', + 'host': 'iaas-api-ca.cisco-ccs.com', + 'vendor': 'Cisco' + }, + 'med1-il': { + 'name': 'Israel (IL)', + 'host': 'api.cloud.med-1.com', + 'vendor': 'Med-1' + }, + 'med1-na': { + 'name': 'North America (NA)', + 'host': 'api-na.cloud.med-1.com', + 'vendor': 'Med-1' + }, + 'med1-eu': { + 'name': 'Europe (EU)', + 'host': 'api-eu.cloud.med-1.com', + 'vendor': 'Med-1' + }, + 'med1-au': { + 'name': 'Australia (AU)', + 'host': 'api-au.cloud.med-1.com', + 'vendor': 'Med-1' + }, + 'med1-af': { + 'name': 'Africa (AF)', + 'host': 'api-af.cloud.med-1.com', + 'vendor': 'Med-1' + }, + 'med1-ap': { + 'name': 'Asia Pacific (AP)', + 'host': 'api-ap.cloud.med-1.com', + 'vendor': 'Med-1' + }, + 'med1-latam': { + 'name': 'South America (LATAM)', + 'host': 'api-sa.cloud.med-1.com', + 'vendor': 'Med-1' + }, + 'med1-canada': { + 'name': 'Canada (CA)', + 'host': 'api-ca.cloud.med-1.com', + 'vendor': 'Med-1' + }, + 'indosat-id': { + 'name': 'Indonesia (ID)', + 'host': 'iaas-api.indosat.com', + 'vendor': 'Indosat' + }, + 'indosat-na': { + 'name': 'North America (NA)', + 'host': 'iaas-usapi.indosat.com', + 'vendor': 'Indosat' + }, + 'indosat-eu': { + 'name': 'Europe (EU)', + 'host': 'iaas-euapi.indosat.com', + 'vendor': 'Indosat' + }, + 'indosat-au': { + 'name': 'Australia (AU)', + 'host': 'iaas-auapi.indosat.com', + 'vendor': 'Indosat' + }, + 'indosat-af': { + 'name': 'Africa (AF)', + 'host': 'iaas-afapi.indosat.com', + 'vendor': 'Indosat' + }, + 'bsnl-in': { + 'name': 'India (IN)', + 'host': 'api.bsnlcloud.com', + 'vendor': 'BSNL' + }, + 'bsnl-na': { + 'name': 'North America (NA)', + 'host': 'usapi.bsnlcloud.com', + 'vendor': 'BSNL' + }, + 'bsnl-eu': { + 'name': 'Europe (EU)', + 'host': 'euapi.bsnlcloud.com', + 'vendor': 'BSNL' + }, + 'bsnl-au': { + 'name': 'Australia (AU)', + 'host': 'auapi.bsnlcloud.com', + 'vendor': 'BSNL' + }, + 'bsnl-af': { + 'name': 'Africa (AF)', + 'host': 'afapi.bsnlcloud.com', + 'vendor': 'BSNL' + }, +} + +# Default API end-point for the base connection class. +DEFAULT_REGION = 'dd-na' + +BAD_CODE_XML_ELEMENTS = ( + ('responseCode', SERVER_NS), + ('responseCode', TYPES_URN), + ('result', GENERAL_NS) +) + +BAD_MESSAGE_XML_ELEMENTS = ( + ('message', SERVER_NS), + ('message', TYPES_URN), + ('resultDetail', GENERAL_NS) +) + + +def dd_object_to_id(obj, obj_type, id_value='id'): + """ + Takes in a DD object or string and prints out it's id + This is a helper method, as many of our functions can take either an object + or a string, and we need an easy way of converting them + + :param obj: The object to get the id for + :type obj: ``object`` + + :param func: The function to call, e.g. ex_get_vlan. Note: This + function needs to return an object which has ``status`` + attribute. + :type func: ``function`` + + :rtype: ``str`` + """ + if isinstance(obj, obj_type): + return getattr(obj, id_value) + elif isinstance(obj, (basestring)): + return obj + else: + raise TypeError( + "Invalid type %s looking for basestring or %s" + % (type(obj).__name__, obj_type.__name__) + ) + + +class NetworkDomainServicePlan(object): + ESSENTIALS = "ESSENTIALS" + ADVANCED = "ADVANCED" + + +class DimensionDataRawResponse(RawResponse): + pass + + +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() + + if self.status == httplib.BAD_REQUEST: + for response_code in BAD_CODE_XML_ELEMENTS: + code = findtext(body, response_code[0], response_code[1]) + if code is not None: + break + for message in BAD_MESSAGE_XML_ELEMENTS: + message = findtext(body, message[0], message[1]) + if message is not None: + break + raise DimensionDataAPIException(code=code, + msg=message, + driver=self.connection.driver) + if self.status is not httplib.OK: + raise DimensionDataAPIException(code=self.status, + msg=body, + 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.2' + + _orgId = None + responseCls = DimensionDataResponse + rawResponseCls = DimensionDataRawResponse + + 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')) + headers['Content-Type'] = 'application/xml' + 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 raw_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, raw=True) + + 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 paginated_request_with_orgId_api_2(self, action, params=None, data='', + headers=None, method='GET', + page_size=250): + """ + A paginated request to the MCP2.0 API + This essentially calls out to request_with_orgId_api_2 for each page + and yields the response to make a generator + This generator can be looped through to grab all the pages. + + :param action: The resource to access (i.e. 'network/vlan') + :type action: ``str`` + + :param params: Parameters to give to the action + :type params: ``dict`` or ``None`` + + :param data: The data payload to be added to the request + :type data: ``str`` + + :param headers: Additional header to be added to the request + :type headers: ``str`` or ``dict`` or ``None`` + + :param method: HTTP Method for the request (i.e. 'GET', 'POST') + :type method: ``str`` + + :param page_size: The size of each page to be returned + Note: Max page size in MCP2.0 is currently 250 + :type page_size: ``int`` + """ + if params is None: + params = {} + params['pageSize'] = page_size + + paged_resp = self.request_with_orgId_api_2(action, params, + data, headers, + method).object + yield paged_resp + paged_resp = paged_resp or {} + + while int(paged_resp.get('pageCount')) >= \ + int(paged_resp.get('pageSize')): + params['pageNumber'] = int(paged_resp.get('pageNumber')) + 1 + paged_resp = self.request_with_orgId_api_2(action, params, + data, headers, + method).object + yield paged_resp + + 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 wait_for_state(self, state, func, poll_interval=2, timeout=60, *args, + **kwargs): + """ + Wait for the function which returns a instance with field status/state + to match. + + Keep polling func until one of the desired states is matched + + :param state: Either the desired state (`str`) or a `list` of states + :type state: ``str`` or ``list`` + + :param func: The function to call, e.g. ex_get_vlan. Note: This + function needs to return an object which has ``status`` + attribute. + :type func: ``function`` + + :param poll_interval: The number of seconds to wait between checks + :type poll_interval: `int` + + :param timeout: The total number of seconds to wait to reach a state + :type timeout: `int` + + :param args: The arguments for func + :type args: Positional arguments + + :param kwargs: The arguments for func + :type kwargs: Keyword arguments + + :return: Result from the calling function. + """ + cnt = 0 + while cnt < timeout / poll_interval: + result = func(*args, **kwargs) + if isinstance(result, Node): + object_state = result.state + else: + object_state = result.status + + if object_state is state or object_state in state: + return result + sleep(poll_interval) + cnt += 1 + + msg = 'Status check for object %s timed out' % (result) + raise DimensionDataAPIException(code=object_state, + msg=msg, + driver=self.driver) + + 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 + + def get_account_details(self): + """ + Get the details of this account + + :rtype: :class:`DimensionDataAccountDetails` + """ + body = self.request_api_1('myaccount').object + return DimensionDataAccountDetails( + user_name=findtext(body, 'userName', DIRECTORY_NS), + full_name=findtext(body, 'fullName', DIRECTORY_NS), + first_name=findtext(body, 'firstName', DIRECTORY_NS), + last_name=findtext(body, 'lastName', DIRECTORY_NS), + email=findtext(body, 'emailAddress', DIRECTORY_NS)) + + +class DimensionDataAccountDetails(object): + """ + Dimension Data account class details + """ + def __init__(self, user_name, full_name, first_name, last_name, email): + self.user_name = user_name + self.full_name = full_name + self.first_name = first_name + self.last_name = last_name + self.email = email + + +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, plan): + self.id = str(id) + self.name = name + self.description = description + self.location = location + self.status = status + self.plan = plan + + 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 DimensionDataPublicIpBlock(object): + """ + DimensionData Public IP Block with location. + """ + + def __init__(self, id, base_ip, size, location, network_domain, + status): + self.id = str(id) + self.base_ip = base_ip + self.size = size + self.location = location + self.network_domain = network_domain + self.status = status + + def __repr__(self): + return (('<DimensionDataNetworkDomain: id=%s, base_ip=%s, ' + 'size=%s, location=%s, status=%s>') + % (self.id, self.base_ip, self.size, self.location, + self.status)) + + +class DimensionDataServerCpuSpecification(object): + """ + A class that represents the specification of the CPU(s) for a + node + """ + def __init__(self, cpu_count, cores_per_socket, performance): + """ + Instantiate a new :class:`DimensionDataServerCpuSpecification` + + :param cpu_count: The number of CPUs + :type cpu_count: ``int`` + + :param cores_per_socket: The number of cores per socket, the + recommendation is 1 + :type cores_per_socket: ``int`` + + :param performance: The performance type, e.g. HIGHPERFORMANCE + :type performance: ``str`` + """ + self.cpu_count = cpu_count + self.cores_per_socket = cores_per_socket + self.performance = performance + + def __repr__(self): + return (('<DimensionDataServerCpuSpecification: ' + 'cpu_count=%s, cores_per_socket=%s, ' + 'performance=%s>') + % (self.cpu_count, self.cores_per_socket, self.performance)) + + +class DimensionDataServerDisk(object): + """ + A class that represents the disk on a server + """ + def __init__(self, id, scsi_id, size_gb, speed, state): + """ + Instantiate a new :class:`DimensionDataServerDisk` + + :param id: The id of the disk + :type id: ``str`` + + :param scsi_id: Representation for scsi + :type scsi_id: ``int`` + + :param size_gb: Size of the disk + :type size_gb: ``int`` + + :param speed: Speed of the disk (i.e. STANDARD) + :type speed: ``str`` + + :param state: State of the disk (i.e. PENDING) + :type state: ``str`` + """ + self.id = id + self.scsi_id = scsi_id + self.size_gb = size_gb + self.speed = speed + self.state = state + + def __repr__(self): + return (('<DimensionDataServerDisk: ' + 'id=%s, size_gb=%s') + % (self.id, self.size_gb)) + + +class DimensionDataServerVMWareTools(object): + """ + A class that represents the VMWareTools for a node + """ + def __init__(self, status, version_status, api_version): + """ + Instantiate a new :class:`DimensionDataServerVMWareTools` object + + :param status: The status of VMWare Tools + :type status: ``str`` + + :param version_status: The status for the version of VMWare Tools + (i.e NEEDS_UPGRADE) + :type version_status: ``str`` + + :param api_version: The API version of VMWare Tools + :type api_version: ``str`` + """ + self.status = status + self.version_status = version_status + self.api_version = api_version + + def __repr__(self): + return (('<DimensionDataServerVMWareTools ' + 'status=%s, version_status=%s, ' + 'api_version=%s>') + % (self.status, self.version_status, self.api_version)) + + +class DimensionDataFirewallRule(object): + """ + DimensionData Firewall Rule for a network domain + """ + + def __init__(self, id, name, action, location, network_domain, + status, ip_version, protocol, source, destination, + enabled): + self.id = str(id) + self.name = name + self.action = action + self.location = location + self.network_domain = network_domain + self.status = status + self.ip_version = ip_version + self.protocol = protocol + self.source = source + self.destination = destination + self.enabled = enabled + + def __repr__(self): + return (('<DimensionDataFirewallRule: id=%s, name=%s, ' + 'action=%s, location=%s, network_domain=%s, ' + 'status=%s, ip_version=%s, protocol=%s, source=%s, ' + 'destination=%s, enabled=%s>') + % (self.id, self.name, self.action, self.location, + self.network_domain, self.status, self.ip_version, + self.protocol, self.source, self.destination, + self.enabled)) + + +class DimensionDataFirewallAddress(object): + """ + The source or destination model in a firewall rule + """ + def __init__(self, any_ip, ip_address, ip_prefix_size, + port_begin, port_end, address_list_id, + port_list_id): + self.any_ip = any_ip + self.ip_address = ip_address + self.ip_prefix_size = ip_prefix_size + self.port_begin = port_begin + self.port_end = port_end + self.address_list_id = address_list_id + self.port_list_id = port_list_id + + +class DimensionDataNatRule(object): + """ + An IP NAT rule in a network domain + """ + def __init__(self, id, network_domain, internal_ip, external_ip, status): + self.id = id + self.network_domain = network_domain + self.internal_ip = internal_ip + self.external_ip = external_ip + self.status = status + + def __repr__(self): + return (('<DimensionDataNatRule: id=%s, status=%s>') + % (self.id, self.status)) + + +class DimensionDataAntiAffinityRule(object): + """ + Anti-Affinity rule for DimensionData + + An Anti-Affinity rule ensures that servers in the rule will + not reside on the same VMware ESX host. + """ + def __init__(self, id, node_list): + """ + Instantiate a new :class:`DimensionDataAntiAffinityRule` + + :param id: The ID of the Anti-Affinity rule + :type id: ``str`` + + :param node_list: List of node ids that belong in this rule + :type node_list: ``list`` of ``str`` + """ + self.id = id + self.node_list = node_list + + def __repr__(self): + return (('<DimensionDataAntiAffinityRule: id=%s>') + % (self.id)) + + +class DimensionDataVlan(object): + """ + DimensionData VLAN. + """ + + def __init__(self, id, name, description, location, network_domain, + status, private_ipv4_range_address, private_ipv4_range_size, + ipv6_range_address, ipv6_range_size, ipv4_gateway, + ipv6_gateway): + """ + Initialize an instance of ``DimensionDataVlan`` + + :param id: The ID of the VLAN + :type id: ``str`` + + :param name: The name of the VLAN + :type name: ``str`` + + :param description: Plan text description of the VLAN + :type description: ``str`` + + :param location: The location (data center) of the VLAN + :type location: ``NodeLocation`` + + :param network_domain: The Network Domain that owns this VLAN + :type network_domain: :class:`DimensionDataNetworkDomain` + + :param status: The status of the VLAN + :type status: :class:`DimensionDataStatus` + + :param private_ipv4_range_address: The host address of the VLAN + IP space + :type private_ipv4_range_address: ``str`` + + :param private_ipv4_range_size: The size (e.g. '24') of the VLAN + as a CIDR range size + :type private_ipv4_range_size: ``int`` + + :param ipv6_range_address: The host address of the VLAN + IP space + :type ipv6_range_address: ``str`` + + :param ipv6_range_size: The size (e.g. '32') of the VLAN + as a CIDR range size + :type ipv6_range_size: ``int`` + + :param ipv4_gateway: The IPv4 default gateway address + :type ipv4_gateway: ``str`` + + :param ipv6_gateway: The IPv6 default gateway address + :type ipv6_gateway: ``str`` + """ + self.id = str(id) + self.name = name + self.location = location + self.description = description + self.network_domain = network_domain + self.status = status + self.private_ipv4_range_address = private_ipv4_range_address + self.private_ipv4_range_size = private_ipv4_range_size + self.ipv6_range_address = ipv6_range_address + self.ipv6_range_size = ipv6_range_size + self.ipv4_gateway = ipv4_gateway + self.ipv6_gateway = ipv6_gateway + + def __repr__(self): + return (('<DimensionDataVlan: 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, load_balance_method, + health_monitor_id, service_down_action, slow_ramp_time): + """ + Initialize an instance of ``DimensionDataPool`` + + :param id: The ID of the pool + :type id: ``str`` + + :param name: The name of the pool + :type name: ``str`` + + :param description: Plan text description of the pool + :type description: ``str`` + + :param status: The status of the pool + :type status: :class:`DimensionDataStatus` + + :param load_balance_method: The load balancer method + :type load_balance_method: ``str`` + + :param health_monitor_id: The ID of the health monitor + :type health_monitor_id: ``str`` + + :param service_down_action: Action to take when pool is down + :type service_down_action: ``str`` + + :param slow_ramp_time: The ramp-up time for service recovery + :type slow_ramp_time: ``int`` + """ + self.id = str(id) + self.name = name + self.description = description + self.status = status + self.load_balance_method = load_balance_method + self.health_monitor_id = health_monitor_id + self.service_down_action = service_down_action + self.slow_ramp_time = slow_ramp_time + + 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, port, node_id): + """ + Initialize an instance of ``DimensionDataPoolMember`` + + :param id: The ID of the pool member + :type id: ``str`` + + :param name: The name of the pool member + :type name: ``str`` + + :param status: The status of the pool + :type status: :class:`DimensionDataStatus` + + :param ip: The IP of the pool member + :type ip: ``str`` + + :param port: The port of the pool member + :type port: ``int`` + + :param node_id: The ID of the associated node + :type node_id: ``str`` + """ + self.id = str(id) + self.name = name + self.status = status + self.ip = ip + self.port = port + self.node_id = node_id + + def __repr__(self): + return (('<DimensionDataPoolMember: id=%s, name=%s, ' + 'ip=%s, status=%s, port=%s, node_id=%s>') + % (self.id, self.name, + self.ip, self.status, self.port, + self.node_id)) + + +class DimensionDataVIPNode(object): + def __init__(self, id, name, status, ip, connection_limit='10000', + connection_rate_limit='10000'): + """ + Initialize an instance of :class:`DimensionDataVIPNode` + + :param id: The ID of the node + :type id: ``str`` + + :param name: The name of the node + :type name: ``str`` + + :param status: The status of the node + :type status: :class:`DimensionDataStatus` + + :param ip: The IP of the node + :type ip: ``str`` + + :param connection_limit: The total connection limit for the node + :type connection_limit: ``int`` + + :param connection_rate_limit: The rate limit for the node + :type connection_rate_limit: ``int`` + """ + self.id = str(id) + self.name = name + self.status = status + self.ip = ip + self.connection_limit = connection_limit + self.connection_rate_limit = connection_rate_limit + + def __repr__(self): + return (('<DimensionDataVIPNode: id=%s, name=%s, ' + 'status=%s, ip=%s>') + % (self.id, self.name, + self.status, self.ip)) + + +class DimensionDataVirtualListener(object): + """ + DimensionData Virtual Listener. + """ + + def __init__(self, id, name, status, ip): + """ + Initialize an instance of :class:`DimensionDataVirtualListener` + + :param id: The ID of the listener + :type id: ``str`` + + :param name: The name of the listener + :type name: ``str`` + + :param status: The status of the listener + :type status: :class:`DimensionDataStatus` + + :param ip: The IP of the listener + :type ip: ``str`` + """ + self.id = str(id) + self.name = name + self.status = status + self.ip = ip + + def __repr__(self): + return (('<DimensionDataVirtualListener: id=%s, name=%s, ' + 'status=%s, ip=%s>') + % (self.id, self.name, + self.status, self.ip)) + + +class DimensionDataDefaultHealthMonitor(object): + """ + A default health monitor for a VIP (node, pool or listener) + """ + def __init__(self, id, name, node_compatible, pool_compatible): + """ + Initialize an instance of :class:`DimensionDataDefaultHealthMonitor` + + :param id: The ID of the monitor + :type id: ``str`` + + :param name: The name of the monitor + :type name: ``str`` + + :param node_compatible: Is a monitor capable of monitoring nodes + :type node_compatible: ``bool`` + + :param pool_compatible: Is a monitor capable of monitoring pools + :type pool_compatible: ``bool`` + """ + self.id = id + self.name = name + self.node_compatible = node_compatible + self.pool_compatible = pool_compatible + + def __repr__(self): +
<TRUNCATED>