| # 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.utils.py3 import ET |
| from libcloud.common.dimensiondata import DimensionDataConnection |
| from libcloud.common.dimensiondata import DimensionDataPool |
| from libcloud.common.dimensiondata import DimensionDataPoolMember |
| from libcloud.common.dimensiondata import DimensionDataVirtualListener |
| from libcloud.common.dimensiondata import DimensionDataVIPNode |
| from libcloud.common.dimensiondata import DimensionDataDefaultHealthMonitor |
| from libcloud.common.dimensiondata import DimensionDataPersistenceProfile |
| from libcloud.common.dimensiondata import DimensionDataVirtualListenerCompatibility |
| from libcloud.common.dimensiondata import DimensionDataDefaultiRule |
| from libcloud.common.dimensiondata import API_ENDPOINTS |
| from libcloud.common.dimensiondata import DEFAULT_REGION |
| from libcloud.common.dimensiondata import TYPES_URN |
| from libcloud.utils.misc import 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 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 |
| |
| network_domain_id = None |
| |
| _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 and host is None: |
| raise ValueError("Invalid region: %s, no host specified" % (region)) |
| if region is not None: |
| 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 create_balancer( |
| self, |
| name, |
| port=None, |
| protocol=None, |
| algorithm=None, |
| members=None, |
| ex_listener_ip_address=None, |
| ): |
| """ |
| Create a new load balancer instance |
| |
| :param name: Name of the new load balancer (required) |
| :type name: ``str`` |
| |
| :param port: An integer in the range of 1-65535. If not supplied, |
| it will be taken to mean 'Any Port' |
| :type port: ``int`` |
| |
| :param protocol: Loadbalancer protocol, defaults to http. |
| :type protocol: ``str`` |
| |
| :param members: list of Members to attach to balancer (optional) |
| :type members: ``list`` of :class:`Member` |
| |
| :param algorithm: Load balancing algorithm, defaults to ROUND_ROBIN. |
| :type algorithm: :class:`.Algorithm` |
| |
| :param ex_listener_ip_address: Must be a valid IPv4 in dot-decimal |
| notation (x.x.x.x). |
| :type ex_listener_ip_address: ``str`` |
| |
| :rtype: :class:`LoadBalancer` |
| """ |
| network_domain_id = self.network_domain_id |
| if protocol is None: |
| protocol = "http" |
| if algorithm is None: |
| algorithm = Algorithm.ROUND_ROBIN |
| |
| # Create a pool first |
| pool = self.ex_create_pool( |
| network_domain_id=network_domain_id, |
| name=name, |
| ex_description=None, |
| balancer_method=self._ALGORITHM_TO_VALUE_MAP[algorithm], |
| ) |
| |
| # Attach the members to the pool as nodes |
| if members is not None: |
| for member in members: |
| node = self.ex_create_node( |
| network_domain_id=network_domain_id, |
| name=member.ip, |
| ip=member.ip, |
| ex_description=None, |
| ) |
| self.ex_create_pool_member(pool=pool, node=node, port=port) |
| |
| # Create the virtual listener (balancer) |
| listener = self.ex_create_virtual_listener( |
| network_domain_id=network_domain_id, |
| name=name, |
| ex_description=name, |
| port=port, |
| pool=pool, |
| listener_ip_address=ex_listener_ip_address, |
| ) |
| |
| return LoadBalancer( |
| id=listener.id, |
| name=listener.name, |
| state=State.RUNNING, |
| ip=listener.ip, |
| port=port, |
| driver=self, |
| extra={ |
| "pool_id": pool.id, |
| "network_domain_id": network_domain_id, |
| "listener_ip_address": ex_listener_ip_address, |
| }, |
| ) |
| |
| def list_balancers(self, ex_network_domain_id=None): |
| """ |
| List all loadbalancers inside a geography or in given network. |
| |
| In Dimension Data terminology these are known as virtual listeners |
| |
| :param ex_network_domain_id: UUID of Network Domain |
| if not None returns only balancers in the given network |
| if None then returns all pools for the organization |
| :type ex_network_domain_id: ``str`` |
| |
| :rtype: ``list`` of :class:`LoadBalancer` |
| """ |
| params = None |
| if ex_network_domain_id is not None: |
| params = {"networkDomainId": ex_network_domain_id} |
| |
| return self._to_balancers( |
| self.connection.request_with_orgId_api_2( |
| "networkDomainVip/virtualListener", params=params |
| ).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 ["http", "https", "tcp", "udp", "ftp", "smtp"] |
| |
| 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, |
| port=pool_member.port, |
| balancer=balancer, |
| extra=None, |
| ) |
| ) |
| return members |
| |
| def balancer_attach_member(self, balancer, member): |
| """ |
| Attach a member to balancer |
| |
| :param balancer: LoadBalancer which should be used |
| :type balancer: :class:`LoadBalancer` |
| |
| :param member: Member to join to the balancer |
| :type member: :class:`Member` |
| |
| :return: Member after joining the balancer. |
| :rtype: :class:`Member` |
| """ |
| node = self.ex_create_node( |
| network_domain_id=balancer.extra["network_domain_id"], |
| name="Member." + member.ip, |
| ip=member.ip, |
| ex_description="", |
| ) |
| if node is False: |
| return False |
| pool = self.ex_get_pool(balancer.extra["pool_id"]) |
| pool_member = self.ex_create_pool_member(pool=pool, node=node, port=member.port) |
| member.id = pool_member.id |
| return member |
| |
| def balancer_detach_member(self, balancer, member): |
| """ |
| Detach member from balancer |
| |
| :param balancer: LoadBalancer which should be used |
| :type balancer: :class:`LoadBalancer` |
| |
| :param member: Member which should be used |
| :type member: :class:`Member` |
| |
| :return: ``True`` if member detach was successful, otherwise ``False``. |
| :rtype: ``bool`` |
| """ |
| create_pool_m = ET.Element( |
| "removePoolMember", {"xmlns": TYPES_URN, "id": member.id} |
| ) |
| |
| result = self.connection.request_with_orgId_api_2( |
| "networkDomainVip/removePoolMember", |
| method="POST", |
| data=ET.tostring(create_pool_m), |
| ).object |
| response_code = findtext(result, "responseCode", TYPES_URN) |
| return response_code in ["IN_PROGRESS", "OK"] |
| |
| def destroy_balancer(self, balancer): |
| """ |
| Destroy a load balancer (virtual listener) |
| |
| :param balancer: LoadBalancer which should be used |
| :type balancer: :class:`LoadBalancer` |
| |
| :return: ``True`` if the destroy was successful, otherwise ``False``. |
| :rtype: ``bool`` |
| """ |
| delete_listener = ET.Element( |
| "deleteVirtualListener", {"xmlns": TYPES_URN, "id": balancer.id} |
| ) |
| |
| result = self.connection.request_with_orgId_api_2( |
| "networkDomainVip/deleteVirtualListener", |
| method="POST", |
| data=ET.tostring(delete_listener), |
| ).object |
| response_code = findtext(result, "responseCode", TYPES_URN) |
| return response_code in ["IN_PROGRESS", "OK"] |
| |
| def ex_set_current_network_domain(self, network_domain_id): |
| """ |
| Set the network domain (part of the network) of the driver |
| |
| :param network_domain_id: ID of the pool (required) |
| :type network_domain_id: ``str`` |
| """ |
| self.network_domain_id = network_domain_id |
| |
| def ex_get_current_network_domain(self): |
| """ |
| Get the current network domain ID of the driver. |
| |
| :return: ID of the network domain |
| :rtype: ``str`` |
| """ |
| return self.network_domain_id |
| |
| def ex_create_pool_member(self, pool, node, port=None): |
| """ |
| Create a new member in an existing pool from an existing node |
| |
| :param pool: Instance of ``DimensionDataPool`` (required) |
| :type pool: ``DimensionDataPool`` |
| |
| :param node: Instance of ``DimensionDataVIPNode`` (required) |
| :type node: ``DimensionDataVIPNode`` |
| |
| :param port: Port the the service will listen on |
| :type port: ``str`` |
| |
| :return: The node member, instance of ``DimensionDataPoolMember`` |
| :rtype: ``DimensionDataPoolMember`` |
| """ |
| create_pool_m = ET.Element("addPoolMember", {"xmlns": TYPES_URN}) |
| ET.SubElement(create_pool_m, "poolId").text = pool.id |
| ET.SubElement(create_pool_m, "nodeId").text = node.id |
| if port is not None: |
| ET.SubElement(create_pool_m, "port").text = str(port) |
| ET.SubElement(create_pool_m, "status").text = "ENABLED" |
| |
| response = self.connection.request_with_orgId_api_2( |
| "networkDomainVip/addPoolMember", |
| method="POST", |
| data=ET.tostring(create_pool_m), |
| ).object |
| |
| member_id = None |
| node_name = None |
| for info in findall(response, "info", TYPES_URN): |
| if info.get("name") == "poolMemberId": |
| member_id = info.get("value") |
| if info.get("name") == "nodeName": |
| node_name = info.get("value") |
| |
| return DimensionDataPoolMember( |
| id=member_id, |
| name=node_name, |
| status=State.RUNNING, |
| ip=node.ip, |
| port=port, |
| node_id=node.id, |
| ) |
| |
| def ex_create_node( |
| self, |
| network_domain_id, |
| name, |
| ip, |
| ex_description, |
| connection_limit=25000, |
| connection_rate_limit=2000, |
| ): |
| """ |
| Create a new node |
| |
| :param network_domain_id: Network Domain ID (required) |
| :type name: ``str`` |
| |
| :param name: name of the node (required) |
| :type name: ``str`` |
| |
| :param ip: IPv4 address of the node (required) |
| :type ip: ``str`` |
| |
| :param ex_description: Description of the node (required) |
| :type ex_description: ``str`` |
| |
| :param connection_limit: Maximum number |
| of concurrent connections per sec |
| :type connection_limit: ``int`` |
| |
| :param connection_rate_limit: Maximum number of concurrent sessions |
| :type connection_rate_limit: ``int`` |
| |
| :return: Instance of ``DimensionDataVIPNode`` |
| :rtype: ``DimensionDataVIPNode`` |
| """ |
| create_node_elm = ET.Element("createNode", {"xmlns": TYPES_URN}) |
| ET.SubElement(create_node_elm, "networkDomainId").text = network_domain_id |
| ET.SubElement(create_node_elm, "name").text = name |
| ET.SubElement(create_node_elm, "description").text = str(ex_description) |
| ET.SubElement(create_node_elm, "ipv4Address").text = ip |
| ET.SubElement(create_node_elm, "status").text = "ENABLED" |
| ET.SubElement(create_node_elm, "connectionLimit").text = str(connection_limit) |
| ET.SubElement(create_node_elm, "connectionRateLimit").text = str( |
| connection_rate_limit |
| ) |
| |
| response = self.connection.request_with_orgId_api_2( |
| action="networkDomainVip/createNode", |
| method="POST", |
| data=ET.tostring(create_node_elm), |
| ).object |
| |
| node_id = None |
| node_name = None |
| for info in findall(response, "info", TYPES_URN): |
| if info.get("name") == "nodeId": |
| node_id = info.get("value") |
| if info.get("name") == "name": |
| node_name = info.get("value") |
| return DimensionDataVIPNode( |
| id=node_id, name=node_name, status=State.RUNNING, ip=ip |
| ) |
| |
| def ex_update_node(self, node): |
| """ |
| Update the properties of a node |
| |
| :param pool: The instance of ``DimensionDataNode`` to update |
| :type pool: ``DimensionDataNode`` |
| |
| :return: The instance of ``DimensionDataNode`` |
| :rtype: ``DimensionDataNode`` |
| """ |
| create_node_elm = ET.Element("editNode", {"xmlns": TYPES_URN}) |
| ET.SubElement(create_node_elm, "connectionLimit").text = str( |
| node.connection_limit |
| ) |
| ET.SubElement(create_node_elm, "connectionRateLimit").text = str( |
| node.connection_rate_limit |
| ) |
| |
| self.connection.request_with_orgId_api_2( |
| action="networkDomainVip/createNode", |
| method="POST", |
| data=ET.tostring(create_node_elm), |
| ).object |
| return node |
| |
| def ex_set_node_state(self, node, enabled): |
| """ |
| Change the state of a node (enable/disable) |
| |
| :param pool: The instance of ``DimensionDataNode`` to update |
| :type pool: ``DimensionDataNode`` |
| |
| :param enabled: The target state of the node |
| :type enabled: ``bool`` |
| |
| :return: The instance of ``DimensionDataNode`` |
| :rtype: ``DimensionDataNode`` |
| """ |
| create_node_elm = ET.Element("editNode", {"xmlns": TYPES_URN}) |
| ET.SubElement(create_node_elm, "status").text = ( |
| "ENABLED" if enabled is True else "DISABLED" |
| ) |
| |
| self.connection.request_with_orgId_api_2( |
| action="networkDomainVip/editNode", |
| method="POST", |
| data=ET.tostring(create_node_elm), |
| ).object |
| return node |
| |
| def ex_create_pool( |
| self, |
| network_domain_id, |
| name, |
| balancer_method, |
| ex_description, |
| health_monitors=None, |
| service_down_action="NONE", |
| slow_ramp_time=30, |
| ): |
| """ |
| Create a new pool |
| |
| :param network_domain_id: Network Domain ID (required) |
| :type name: ``str`` |
| |
| :param name: name of the node (required) |
| :type name: ``str`` |
| |
| :param balancer_method: The load balancer algorithm (required) |
| :type balancer_method: ``str`` |
| |
| :param ex_description: Description of the node (required) |
| :type ex_description: ``str`` |
| |
| :param health_monitors: A list of health monitors to use for the pool. |
| :type health_monitors: ``list`` of |
| :class:`DimensionDataDefaultHealthMonitor` |
| |
| :param service_down_action: What to do when node |
| is unavailable NONE, DROP or RESELECT |
| :type service_down_action: ``str`` |
| |
| :param slow_ramp_time: Number of seconds to stagger ramp up of nodes |
| :type slow_ramp_time: ``int`` |
| |
| :return: Instance of ``DimensionDataPool`` |
| :rtype: ``DimensionDataPool`` |
| """ |
| # Names cannot contain spaces. |
| name.replace(" ", "_") |
| create_node_elm = ET.Element("createPool", {"xmlns": TYPES_URN}) |
| ET.SubElement(create_node_elm, "networkDomainId").text = network_domain_id |
| ET.SubElement(create_node_elm, "name").text = name |
| ET.SubElement(create_node_elm, "description").text = str(ex_description) |
| ET.SubElement(create_node_elm, "loadBalanceMethod").text = str(balancer_method) |
| |
| if health_monitors is not None: |
| for monitor in health_monitors: |
| ET.SubElement(create_node_elm, "healthMonitorId").text = str(monitor.id) |
| |
| ET.SubElement(create_node_elm, "serviceDownAction").text = service_down_action |
| ET.SubElement(create_node_elm, "slowRampTime").text = str(slow_ramp_time) |
| |
| response = self.connection.request_with_orgId_api_2( |
| action="networkDomainVip/createPool", |
| method="POST", |
| data=ET.tostring(create_node_elm), |
| ).object |
| |
| pool_id = None |
| for info in findall(response, "info", TYPES_URN): |
| if info.get("name") == "poolId": |
| pool_id = info.get("value") |
| |
| return DimensionDataPool( |
| id=pool_id, |
| name=name, |
| description=ex_description, |
| status=State.RUNNING, |
| load_balance_method=str(balancer_method), |
| health_monitor_id=None, |
| service_down_action=service_down_action, |
| slow_ramp_time=str(slow_ramp_time), |
| ) |
| |
| def ex_create_virtual_listener( |
| self, |
| network_domain_id, |
| name, |
| ex_description, |
| port=None, |
| pool=None, |
| listener_ip_address=None, |
| persistence_profile=None, |
| fallback_persistence_profile=None, |
| irule=None, |
| protocol="TCP", |
| connection_limit=25000, |
| connection_rate_limit=2000, |
| source_port_preservation="PRESERVE", |
| ): |
| """ |
| Create a new virtual listener (load balancer) |
| |
| :param network_domain_id: Network Domain ID (required) |
| :type name: ``str`` |
| |
| :param name: name of the listener (required) |
| :type name: ``str`` |
| |
| :param ex_description: Description of the node (required) |
| :type ex_description: ``str`` |
| |
| :param port: An integer in the range of 1-65535. If not supplied, |
| it will be taken to mean 'Any Port' |
| :type port: ``int`` |
| |
| :param pool: The pool to use for the listener |
| :type pool: :class:`DimensionDataPool` |
| |
| :param listener_ip_address: The IPv4 Address of the virtual listener |
| :type listener_ip_address: ``str`` |
| |
| :param persistence_profile: Persistence profile |
| :type persistence_profile: :class:`DimensionDataPersistenceProfile` |
| |
| :param fallback_persistence_profile: Fallback persistence profile |
| :type fallback_persistence_profile: |
| :class:`DimensionDataPersistenceProfile` |
| |
| :param irule: The iRule to apply |
| :type irule: :class:`DimensionDataDefaultiRule` |
| |
| :param protocol: For STANDARD type, ANY, TCP or UDP |
| for PERFORMANCE_LAYER_4 choice of ANY, TCP, UDP, HTTP |
| :type protcol: ``str`` |
| |
| :param connection_limit: Maximum number |
| of concurrent connections per sec |
| :type connection_limit: ``int`` |
| |
| :param connection_rate_limit: Maximum number of concurrent sessions |
| :type connection_rate_limit: ``int`` |
| |
| :param source_port_preservation: Choice of PRESERVE, |
| PRESERVE_STRICT or CHANGE |
| :type source_port_preservation: ``str`` |
| |
| :return: Instance of the listener |
| :rtype: ``DimensionDataVirtualListener`` |
| """ |
| if (port == 80) or (port == 443): |
| listener_type = "PERFORMANCE_LAYER_4" |
| else: |
| listener_type = "STANDARD" |
| |
| create_node_elm = ET.Element("createVirtualListener", {"xmlns": TYPES_URN}) |
| ET.SubElement(create_node_elm, "networkDomainId").text = network_domain_id |
| ET.SubElement(create_node_elm, "name").text = name |
| ET.SubElement(create_node_elm, "description").text = str(ex_description) |
| ET.SubElement(create_node_elm, "type").text = listener_type |
| ET.SubElement(create_node_elm, "protocol").text = protocol |
| if listener_ip_address is not None: |
| ET.SubElement(create_node_elm, "listenerIpAddress").text = str( |
| listener_ip_address |
| ) |
| if port is not None: |
| ET.SubElement(create_node_elm, "port").text = str(port) |
| ET.SubElement(create_node_elm, "enabled").text = "true" |
| ET.SubElement(create_node_elm, "connectionLimit").text = str(connection_limit) |
| ET.SubElement(create_node_elm, "connectionRateLimit").text = str( |
| connection_rate_limit |
| ) |
| ET.SubElement( |
| create_node_elm, "sourcePortPreservation" |
| ).text = source_port_preservation |
| if pool is not None: |
| ET.SubElement(create_node_elm, "poolId").text = pool.id |
| if persistence_profile is not None: |
| ET.SubElement( |
| create_node_elm, "persistenceProfileId" |
| ).text = persistence_profile.id |
| if fallback_persistence_profile is not None: |
| ET.SubElement( |
| create_node_elm, "fallbackPersistenceProfileId" |
| ).text = fallback_persistence_profile.id |
| if irule is not None: |
| ET.SubElement(create_node_elm, "iruleId").text = irule.id |
| |
| response = self.connection.request_with_orgId_api_2( |
| action="networkDomainVip/createVirtualListener", |
| method="POST", |
| data=ET.tostring(create_node_elm), |
| ).object |
| |
| virtual_listener_id = None |
| virtual_listener_ip = None |
| for info in findall(response, "info", TYPES_URN): |
| if info.get("name") == "virtualListenerId": |
| virtual_listener_id = info.get("value") |
| if info.get("name") == "listenerIpAddress": |
| virtual_listener_ip = info.get("value") |
| |
| return DimensionDataVirtualListener( |
| id=virtual_listener_id, |
| name=name, |
| ip=virtual_listener_ip, |
| status=State.RUNNING, |
| ) |
| |
| def ex_get_pools(self, ex_network_domain_id=None): |
| """ |
| Get all of the pools inside the current geography or |
| in given network. |
| |
| :param ex_network_domain_id: UUID of Network Domain |
| if not None returns only balancers in the given network |
| if None then returns all pools for the organization |
| :type ex_network_domain_id: ``str`` |
| |
| :return: Returns a ``list`` of type ``DimensionDataPool`` |
| :rtype: ``list`` of ``DimensionDataPool`` |
| """ |
| params = None |
| |
| if ex_network_domain_id is not None: |
| params = {"networkDomainId": ex_network_domain_id} |
| |
| pools = self.connection.request_with_orgId_api_2( |
| "networkDomainVip/pool", params=params |
| ).object |
| |
| return self._to_pools(pools) |
| |
| def ex_get_pool(self, pool_id): |
| """ |
| Get a specific pool inside the current geography |
| |
| :param pool_id: The identifier of the pool |
| :type pool_id: ``str`` |
| |
| :return: Returns an instance of ``DimensionDataPool`` |
| :rtype: ``DimensionDataPool`` |
| """ |
| pool = self.connection.request_with_orgId_api_2( |
| "networkDomainVip/pool/%s" % pool_id |
| ).object |
| return self._to_pool(pool) |
| |
| def ex_update_pool(self, pool): |
| """ |
| Update the properties of an existing pool |
| only method, serviceDownAction and slowRampTime are updated |
| |
| :param pool: The instance of ``DimensionDataPool`` to update |
| :type pool: ``DimensionDataPool`` |
| |
| :return: ``True`` for success, ``False`` for failure |
| :rtype: ``bool`` |
| """ |
| create_node_elm = ET.Element("editPool", {"xmlns": TYPES_URN}) |
| |
| ET.SubElement(create_node_elm, "loadBalanceMethod").text = str( |
| pool.load_balance_method |
| ) |
| ET.SubElement( |
| create_node_elm, "serviceDownAction" |
| ).text = pool.service_down_action |
| ET.SubElement(create_node_elm, "slowRampTime").text = str(pool.slow_ramp_time) |
| |
| response = self.connection.request_with_orgId_api_2( |
| action="networkDomainVip/editPool", |
| method="POST", |
| data=ET.tostring(create_node_elm), |
| ).object |
| response_code = findtext(response, "responseCode", TYPES_URN) |
| return response_code in ["IN_PROGRESS", "OK"] |
| |
| def ex_destroy_pool(self, pool): |
| """ |
| Destroy an existing pool |
| |
| :param pool: The instance of ``DimensionDataPool`` to destroy |
| :type pool: ``DimensionDataPool`` |
| |
| :return: ``True`` for success, ``False`` for failure |
| :rtype: ``bool`` |
| """ |
| destroy_request = ET.Element("deletePool", {"xmlns": TYPES_URN, "id": pool.id}) |
| |
| result = self.connection.request_with_orgId_api_2( |
| action="networkDomainVip/deletePool", |
| method="POST", |
| data=ET.tostring(destroy_request), |
| ).object |
| response_code = findtext(result, "responseCode", TYPES_URN) |
| return response_code in ["IN_PROGRESS", "OK"] |
| |
| def ex_get_pool_members(self, pool_id): |
| """ |
| Get the members of a pool |
| |
| :param pool: The instance of a pool |
| :type pool: ``DimensionDataPool`` |
| |
| :return: Returns an ``list`` of ``DimensionDataPoolMember`` |
| :rtype: ``list`` of ``DimensionDataPoolMember`` |
| """ |
| 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): |
| """ |
| Get a specific member of a pool |
| |
| :param pool: The id of a pool member |
| :type pool: ``str`` |
| |
| :return: Returns an instance of ``DimensionDataPoolMember`` |
| :rtype: ``DimensionDataPoolMember`` |
| """ |
| member = self.connection.request_with_orgId_api_2( |
| "networkDomainVip/poolMember/%s" % pool_member_id |
| ).object |
| return self._to_member(member) |
| |
| def ex_set_pool_member_state(self, member, enabled=True): |
| request = ET.Element("editPoolMember", {"xmlns": TYPES_URN, "id": member.id}) |
| state = "ENABLED" if enabled is True else "DISABLED" |
| ET.SubElement(request, "status").text = state |
| |
| result = self.connection.request_with_orgId_api_2( |
| action="networkDomainVip/editPoolMember", |
| method="POST", |
| data=ET.tostring(request), |
| ).object |
| |
| response_code = findtext(result, "responseCode", TYPES_URN) |
| return response_code in ["IN_PROGRESS", "OK"] |
| |
| def ex_destroy_pool_member(self, member, destroy_node=False): |
| """ |
| Destroy a specific member of a pool |
| |
| :param pool: The instance of a pool member |
| :type pool: ``DimensionDataPoolMember`` |
| |
| :param destroy_node: Also destroy the associated node |
| :type destroy_node: ``bool`` |
| |
| :return: ``True`` for success, ``False`` for failure |
| :rtype: ``bool`` |
| """ |
| # remove the pool member |
| destroy_request = ET.Element( |
| "removePoolMember", {"xmlns": TYPES_URN, "id": member.id} |
| ) |
| |
| result = self.connection.request_with_orgId_api_2( |
| action="networkDomainVip/removePoolMember", |
| method="POST", |
| data=ET.tostring(destroy_request), |
| ).object |
| |
| if member.node_id is not None and destroy_node is True: |
| return self.ex_destroy_node(member.node_id) |
| else: |
| response_code = findtext(result, "responseCode", TYPES_URN) |
| return response_code in ["IN_PROGRESS", "OK"] |
| |
| def ex_get_nodes(self, ex_network_domain_id=None): |
| """ |
| Get the nodes within this geography or in given network. |
| |
| :param ex_network_domain_id: UUID of Network Domain |
| if not None returns only balancers in the given network |
| if None then returns all pools for the organization |
| :type ex_network_domain_id: ``str`` |
| |
| :return: Returns an ``list`` of ``DimensionDataVIPNode`` |
| :rtype: ``list`` of ``DimensionDataVIPNode`` |
| """ |
| params = None |
| if ex_network_domain_id is not None: |
| params = {"networkDomainId": ex_network_domain_id} |
| |
| nodes = self.connection.request_with_orgId_api_2( |
| "networkDomainVip/node", params=params |
| ).object |
| return self._to_nodes(nodes) |
| |
| def ex_get_node(self, node_id): |
| """ |
| Get the node specified by node_id |
| |
| :return: Returns an instance of ``DimensionDataVIPNode`` |
| :rtype: Instance of ``DimensionDataVIPNode`` |
| """ |
| nodes = self.connection.request_with_orgId_api_2( |
| "networkDomainVip/node/%s" % node_id |
| ).object |
| return self._to_node(nodes) |
| |
| def ex_destroy_node(self, node_id): |
| """ |
| Destroy a specific node |
| |
| :param node_id: The ID of of a ``DimensionDataVIPNode`` |
| :type node_id: ``str`` |
| |
| :return: ``True`` for success, ``False`` for failure |
| :rtype: ``bool`` |
| """ |
| # Destroy the node |
| destroy_request = ET.Element("deleteNode", {"xmlns": TYPES_URN, "id": node_id}) |
| |
| result = self.connection.request_with_orgId_api_2( |
| action="networkDomainVip/deleteNode", |
| method="POST", |
| data=ET.tostring(destroy_request), |
| ).object |
| response_code = findtext(result, "responseCode", TYPES_URN) |
| return response_code in ["IN_PROGRESS", "OK"] |
| |
| def ex_wait_for_state( |
| self, state, func, poll_interval=2, timeout=60, *args, **kwargs |
| ): |
| """ |
| Wait for the function which returns a instance |
| with field status 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 |
| :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 self.connection.wait_for_state( |
| state, func, poll_interval, timeout, *args, **kwargs |
| ) |
| |
| def ex_get_default_health_monitors(self, network_domain_id): |
| """ |
| Get the default health monitors available for a network domain |
| |
| :param network_domain_id: The ID of of a ``DimensionDataNetworkDomain`` |
| :type network_domain_id: ``str`` |
| |
| :rtype: `list` of :class:`DimensionDataDefaultHealthMonitor` |
| """ |
| result = self.connection.request_with_orgId_api_2( |
| action="networkDomainVip/defaultHealthMonitor", |
| params={"networkDomainId": network_domain_id}, |
| method="GET", |
| ).object |
| return self._to_health_monitors(result) |
| |
| def ex_get_default_persistence_profiles(self, network_domain_id): |
| """ |
| Get the default persistence profiles available for a network domain |
| |
| :param network_domain_id: The ID of of a ``DimensionDataNetworkDomain`` |
| :type network_domain_id: ``str`` |
| |
| :rtype: `list` of :class:`DimensionDataPersistenceProfile` |
| """ |
| result = self.connection.request_with_orgId_api_2( |
| action="networkDomainVip/defaultPersistenceProfile", |
| params={"networkDomainId": network_domain_id}, |
| method="GET", |
| ).object |
| return self._to_persistence_profiles(result) |
| |
| def ex_get_default_irules(self, network_domain_id): |
| """ |
| Get the default iRules available for a network domain |
| |
| :param network_domain_id: The ID of of a ``DimensionDataNetworkDomain`` |
| :type network_domain_id: ``str`` |
| |
| :rtype: `list` of :class:`DimensionDataDefaultiRule` |
| """ |
| result = self.connection.request_with_orgId_api_2( |
| action="networkDomainVip/defaultIrule", |
| params={"networkDomainId": network_domain_id}, |
| method="GET", |
| ).object |
| return self._to_irules(result) |
| |
| def _to_irules(self, object): |
| irules = [] |
| matches = object.findall(fixxpath("defaultIrule", TYPES_URN)) |
| for element in matches: |
| irules.append(self._to_irule(element)) |
| return irules |
| |
| def _to_irule(self, element): |
| compatible = [] |
| matches = element.findall(fixxpath("virtualListenerCompatibility", TYPES_URN)) |
| for match_element in matches: |
| compatible.append( |
| DimensionDataVirtualListenerCompatibility( |
| type=match_element.get("type"), |
| protocol=match_element.get("protocol", None), |
| ) |
| ) |
| irule_element = element.find(fixxpath("irule", TYPES_URN)) |
| return DimensionDataDefaultiRule( |
| id=irule_element.get("id"), |
| name=irule_element.get("name"), |
| compatible_listeners=compatible, |
| ) |
| |
| def _to_persistence_profiles(self, object): |
| profiles = [] |
| matches = object.findall(fixxpath("defaultPersistenceProfile", TYPES_URN)) |
| for element in matches: |
| profiles.append(self._to_persistence_profile(element)) |
| return profiles |
| |
| def _to_persistence_profile(self, element): |
| compatible = [] |
| matches = element.findall(fixxpath("virtualListenerCompatibility", TYPES_URN)) |
| for match_element in matches: |
| compatible.append( |
| DimensionDataVirtualListenerCompatibility( |
| type=match_element.get("type"), |
| protocol=match_element.get("protocol", None), |
| ) |
| ) |
| |
| return DimensionDataPersistenceProfile( |
| id=element.get("id"), |
| fallback_compatible=bool(element.get("fallbackCompatible") == "true"), |
| name=findtext(element, "name", TYPES_URN), |
| compatible_listeners=compatible, |
| ) |
| |
| def _to_health_monitors(self, object): |
| monitors = [] |
| matches = object.findall(fixxpath("defaultHealthMonitor", TYPES_URN)) |
| for element in matches: |
| monitors.append(self._to_health_monitor(element)) |
| return monitors |
| |
| def _to_health_monitor(self, element): |
| return DimensionDataDefaultHealthMonitor( |
| id=element.get("id"), |
| name=findtext(element, "name", TYPES_URN), |
| node_compatible=bool( |
| findtext(element, "nodeCompatible", TYPES_URN) == "true" |
| ), |
| pool_compatible=bool( |
| findtext(element, "poolCompatible", TYPES_URN) == "true" |
| ), |
| ) |
| |
| def _to_nodes(self, object): |
| nodes = [] |
| for element in object.findall(fixxpath("node", TYPES_URN)): |
| nodes.append(self._to_node(element)) |
| |
| return nodes |
| |
| def _to_node(self, element): |
| ipaddress = findtext(element, "ipv4Address", TYPES_URN) |
| if ipaddress is None: |
| ipaddress = findtext(element, "ipv6Address", TYPES_URN) |
| |
| name = findtext(element, "name", TYPES_URN) |
| |
| node = DimensionDataVIPNode( |
| id=element.get("id"), |
| name=name, |
| status=self._VALUE_TO_STATE_MAP.get( |
| findtext(element, "state", TYPES_URN), State.UNKNOWN |
| ), |
| connection_rate_limit=findtext(element, "connectionRateLimit", TYPES_URN), |
| connection_limit=findtext(element, "connectionLimit", TYPES_URN), |
| ip=ipaddress, |
| ) |
| |
| return node |
| |
| 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 = {} |
| |
| pool_element = element.find(fixxpath("pool", TYPES_URN)) |
| if pool_element is None: |
| extra["pool_id"] = None |
| |
| else: |
| extra["pool_id"] = pool_element.get("id") |
| |
| extra["network_domain_id"] = findtext(element, "networkDomainId", TYPES_URN) |
| |
| 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): |
| port = findtext(element, "port", TYPES_URN) |
| if port is not None: |
| port = int(port) |
| pool_member = DimensionDataPoolMember( |
| id=element.get("id"), |
| name=element.find(fixxpath("node", TYPES_URN)).get("name"), |
| status=findtext(element, "state", TYPES_URN), |
| node_id=element.find(fixxpath("node", TYPES_URN)).get("id"), |
| ip=element.find(fixxpath("node", TYPES_URN)).get("ipAddress"), |
| port=port, |
| ) |
| return pool_member |
| |
| 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), |
| load_balance_method=findtext(element, "loadBalanceMethod", TYPES_URN), |
| health_monitor_id=findtext(element, "healthMonitorId", TYPES_URN), |
| service_down_action=findtext(element, "serviceDownAction", TYPES_URN), |
| slow_ramp_time=findtext(element, "slowRampTime", TYPES_URN), |
| ) |
| return pool |