blob: fd5094a90facb991fd4cff54f35421bac0086448 [file] [log] [blame]
# 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.
from datetime import datetime
from libcloud.utils.py3 import httplib
from libcloud.utils.misc import reverse_dict
from libcloud.common.base import JsonResponse, PollingConnection
from libcloud.common.types import LibcloudError
from libcloud.common.openstack import OpenStackDriverMixin
from libcloud.common.rackspace import AUTH_URL
from libcloud.loadbalancer.base import DEFAULT_ALGORITHM, Driver, Member, Algorithm, LoadBalancer
from libcloud.loadbalancer.types import State, MemberCondition
from libcloud.compute.drivers.rackspace import RackspaceConnection
try:
import simplejson as json
except ImportError:
import json
ENDPOINT_ARGS_MAP = {
"dfw": {
"service_type": "rax:load-balancer",
"name": "cloudLoadBalancers",
"region": "DFW",
},
"ord": {
"service_type": "rax:load-balancer",
"name": "cloudLoadBalancers",
"region": "ORD",
},
"iad": {
"service_type": "rax:load-balancer",
"name": "cloudLoadBalancers",
"region": "IAD",
},
"lon": {
"service_type": "rax:load-balancer",
"name": "cloudLoadBalancers",
"region": "LON",
},
"syd": {
"service_type": "rax:load-balancer",
"name": "cloudLoadBalancers",
"region": "SYD",
},
"hkg": {
"service_type": "rax:load-balancer",
"name": "cloudLoadBalancers",
"region": "HKG",
},
}
class RackspaceResponse(JsonResponse):
def parse_body(self):
if not self.body:
return None
return super().parse_body()
def success(self):
return 200 <= int(self.status) <= 299
class RackspaceHealthMonitor:
"""
:param type: type of load balancer. currently CONNECT (connection
monitoring), HTTP, HTTPS (connection and HTTP
monitoring) are supported.
:type type: ``str``
:param delay: minimum seconds to wait before executing the health
monitor. (Must be between 1 and 3600)
:type delay: ``int``
:param timeout: maximum seconds to wait when establishing a
connection before timing out. (Must be between 1
and 3600)
:type timeout: ``int``
:param attempts_before_deactivation: Number of monitor failures
before removing a node from
rotation. (Must be between 1
and 10)
:type attempts_before_deactivation: ``int``
"""
def __init__(self, type, delay, timeout, attempts_before_deactivation):
self.type = type
self.delay = delay
self.timeout = timeout
self.attempts_before_deactivation = attempts_before_deactivation
def __repr__(self):
return (
"<RackspaceHealthMonitor: type=%s, delay=%d, timeout=%d, "
"attempts_before_deactivation=%d>"
% (self.type, self.delay, self.timeout, self.attempts_before_deactivation)
)
def _to_dict(self):
return {
"type": self.type,
"delay": self.delay,
"timeout": self.timeout,
"attemptsBeforeDeactivation": self.attempts_before_deactivation,
}
class RackspaceHTTPHealthMonitor(RackspaceHealthMonitor):
"""
A HTTP health monitor adds extra features to a Rackspace health monitor.
:param path: the HTTP path to monitor.
:type path: ``str``
:param body_regex: Regular expression used to evaluate the body of
the HTTP response.
:type body_regex: ``str``
:param status_regex: Regular expression used to evaluate the HTTP
status code of the response.
:type status_regex: ``str``
"""
def __init__(
self,
type,
delay,
timeout,
attempts_before_deactivation,
path,
body_regex,
status_regex,
):
super().__init__(type, delay, timeout, attempts_before_deactivation)
self.path = path
self.body_regex = body_regex
self.status_regex = status_regex
def __repr__(self):
return (
"<RackspaceHTTPHealthMonitor: type=%s, delay=%d, timeout=%d, "
"attempts_before_deactivation=%d, path=%s, body_regex=%s, "
"status_regex=%s>"
% (
self.type,
self.delay,
self.timeout,
self.attempts_before_deactivation,
self.path,
self.body_regex,
self.status_regex,
)
)
def _to_dict(self):
super_dict = super()._to_dict()
super_dict["path"] = self.path
super_dict["statusRegex"] = self.status_regex
if self.body_regex:
super_dict["bodyRegex"] = self.body_regex
return super_dict
class RackspaceConnectionThrottle:
"""
:param min_connections: Minimum number of connections per IP address
before applying throttling.
:type min_connections: ``int``
:param max_connections: Maximum number of connections per IP address.
(Must be between 0 and 100000, 0 allows an
unlimited number of connections.)
:type max_connections: ``int``
:param max_connection_rate: Maximum number of connections allowed
from a single IP address within the
given rate_interval_seconds. (Must be
between 0 and 100000, 0 allows an
unlimited number of connections.)
:type max_connection_rate: ``int``
:param rate_interval_seconds: Interval at which the
max_connection_rate is enforced.
(Must be between 1 and 3600.)
:type rate_interval_seconds: ``int``
"""
def __init__(
self,
min_connections,
max_connections,
max_connection_rate,
rate_interval_seconds,
):
self.min_connections = min_connections
self.max_connections = max_connections
self.max_connection_rate = max_connection_rate
self.rate_interval_seconds = rate_interval_seconds
def __repr__(self):
return (
"<RackspaceConnectionThrottle: min_connections=%d, "
"max_connections=%d, max_connection_rate=%d, "
"rate_interval_seconds=%d>"
% (
self.min_connections,
self.max_connections,
self.max_connection_rate,
self.rate_interval_seconds,
)
)
def _to_dict(self):
return {
"maxConnections": self.max_connections,
"minConnections": self.min_connections,
"maxConnectionRate": self.max_connection_rate,
"rateInterval": self.rate_interval_seconds,
}
class RackspaceAccessRuleType:
ALLOW = 0
DENY = 1
_RULE_TYPE_STRING_MAP = {ALLOW: "ALLOW", DENY: "DENY"}
class RackspaceAccessRule:
"""
An access rule allows or denies traffic to a Load Balancer based on the
incoming IPs.
:param id: Unique identifier to refer to this rule by.
:type id: ``str``
:param rule_type: RackspaceAccessRuleType.ALLOW or
RackspaceAccessRuleType.DENY.
:type id: ``int``
:param address: IP address or cidr (can be IPv4 or IPv6).
:type address: ``str``
"""
def __init__(self, id=None, rule_type=None, address=None):
self.id = id
self.rule_type = rule_type
self.address = address
def _to_dict(self):
type_string = RackspaceAccessRuleType._RULE_TYPE_STRING_MAP[self.rule_type]
as_dict = {"type": type_string, "address": self.address}
if self.id is not None:
as_dict["id"] = self.id
return as_dict
class RackspaceConnection(RackspaceConnection, PollingConnection):
responseCls = RackspaceResponse
auth_url = AUTH_URL
poll_interval = 2
timeout = 80
cache_busting = True
def request(self, action, params=None, data="", headers=None, method="GET"):
if not headers:
headers = {}
if not params:
params = {}
if method in ("POST", "PUT"):
headers["Content-Type"] = "application/json"
return super().request(
action=action, params=params, data=data, method=method, headers=headers
)
def get_poll_request_kwargs(self, response, context, request_kwargs):
return {"action": request_kwargs["action"], "method": "GET"}
def has_completed(self, response):
state = response.object["loadBalancer"]["status"]
if state == "ERROR":
raise LibcloudError("Load balancer entered an ERROR state.", driver=self.driver)
return state == "ACTIVE"
def encode_data(self, data):
return data
class RackspaceLBDriver(Driver, OpenStackDriverMixin):
connectionCls = RackspaceConnection
api_name = "rackspace_lb"
name = "Rackspace LB"
website = "http://www.rackspace.com/"
LB_STATE_MAP = {
"ACTIVE": State.RUNNING,
"BUILD": State.PENDING,
"ERROR": State.ERROR,
"DELETED": State.DELETED,
"PENDING_UPDATE": State.PENDING,
"PENDING_DELETE": State.PENDING,
}
LB_MEMBER_CONDITION_MAP = {
"ENABLED": MemberCondition.ENABLED,
"DISABLED": MemberCondition.DISABLED,
"DRAINING": MemberCondition.DRAINING,
}
CONDITION_LB_MEMBER_MAP = reverse_dict(LB_MEMBER_CONDITION_MAP)
_VALUE_TO_ALGORITHM_MAP = {
"RANDOM": Algorithm.RANDOM,
"ROUND_ROBIN": Algorithm.ROUND_ROBIN,
"LEAST_CONNECTIONS": Algorithm.LEAST_CONNECTIONS,
"WEIGHTED_ROUND_ROBIN": Algorithm.WEIGHTED_ROUND_ROBIN,
"WEIGHTED_LEAST_CONNECTIONS": Algorithm.WEIGHTED_LEAST_CONNECTIONS,
}
_ALGORITHM_TO_VALUE_MAP = reverse_dict(_VALUE_TO_ALGORITHM_MAP)
def __init__(
self,
key,
secret=None,
secure=True,
host=None,
port=None,
region="ord",
**kwargs,
):
ex_force_region = kwargs.pop("ex_force_region", None)
if ex_force_region:
# For backward compatibility
region = ex_force_region
OpenStackDriverMixin.__init__(self, **kwargs)
super().__init__(key=key, secret=secret, secure=secure, host=host, port=port, region=region)
@classmethod
def list_regions(cls):
return ENDPOINT_ARGS_MAP.keys()
def _ex_connection_class_kwargs(self):
endpoint_args = ENDPOINT_ARGS_MAP[self.region]
kwargs = self.openstack_connection_kwargs()
kwargs["get_endpoint_args"] = endpoint_args
return kwargs
def list_protocols(self):
return self._to_protocols(self.connection.request("/loadbalancers/protocols").object)
def ex_list_protocols_with_default_ports(self):
"""
List protocols with default ports.
:rtype: ``list`` of ``tuple``
:return: A list of protocols with default ports included.
"""
return self._to_protocols_with_default_ports(
self.connection.request("/loadbalancers/protocols").object
)
def list_balancers(
self,
ex_member_address=None,
ex_status=None,
ex_changes_since=None,
ex_params={},
):
"""
@inherits: :class:`Driver.list_balancers`
:param ex_member_address: Optional IP address of the attachment member.
If provided, only the load balancers which
have this member attached will be returned.
:type ex_member_address: ``str``
:param ex_status: Optional. Filter balancers by status
:type ex_status: ``str``
:param ex_changes_since: Optional. List all load balancers that have
changed since the specified date/time
:type ex_changes_since: ``str``
:param ex_params: Optional. Set parameters to be submitted to the API
in the query string
:type ex_params: ``dict``
"""
params = {}
if ex_member_address:
params["nodeaddress"] = ex_member_address
if ex_status:
params["status"] = ex_status
if ex_changes_since:
params["changes-since"] = ex_changes_since
for key, value in ex_params.items():
params[key] = value
return self._to_balancers(self.connection.request("/loadbalancers", params=params).object)
def create_balancer(self, name, members, protocol="http", port=80, algorithm=DEFAULT_ALGORITHM):
return self.ex_create_balancer(name, members, protocol, port, algorithm)
def ex_create_balancer(
self,
name,
members,
protocol="http",
port=80,
algorithm=DEFAULT_ALGORITHM,
vip="PUBLIC",
):
"""
Creates a new load balancer instance
:param name: Name of the new load balancer (required)
:type name: ``str``
:param members: ``list`` of:class:`Member`s to attach to balancer
:type members: ``list`` of :class:`Member`
:param protocol: Loadbalancer protocol, defaults to http.
:type protocol: ``str``
:param port: Port the load balancer should listen on, defaults to 80
:type port: ``str``
:param algorithm: Load balancing algorithm, defaults to
LBAlgorithm.ROUND_ROBIN
:type algorithm: :class:`Algorithm`
:param vip: Virtual ip type of PUBLIC, SERVICENET, or ID of a virtual
ip
:type vip: ``str``
:rtype: :class:`LoadBalancer`
"""
balancer_attrs = self._kwargs_to_mutable_attrs(
name=name, protocol=protocol, port=port, algorithm=algorithm, vip=vip
)
balancer_attrs.update({"nodes": [self._member_attributes(member) for member in members]})
# balancer_attrs['nodes'] = ['fu']
balancer_object = {"loadBalancer": balancer_attrs}
resp = self.connection.request(
"/loadbalancers", method="POST", data=json.dumps(balancer_object)
)
return self._to_balancer(resp.object["loadBalancer"])
def _member_attributes(self, member):
member_attributes = {"address": member.ip, "port": member.port}
member_attributes.update(self._kwargs_to_mutable_member_attrs(**member.extra))
# If the condition is not specified on the member, then it should be
# set to ENABLED by default
if "condition" not in member_attributes:
member_attributes["condition"] = self.CONDITION_LB_MEMBER_MAP[MemberCondition.ENABLED]
return member_attributes
def destroy_balancer(self, balancer):
uri = "/loadbalancers/%s" % (balancer.id)
resp = self.connection.request(uri, method="DELETE")
return resp.status == httplib.ACCEPTED
def ex_destroy_balancers(self, balancers):
"""
Destroys a list of Balancers (the API supports up to 10).
:param balancers: A list of Balancers to destroy.
:type balancers: ``list`` of :class:`LoadBalancer`
:return: Returns whether the destroy request was accepted.
:rtype: ``bool``
"""
ids = [("id", balancer.id) for balancer in balancers]
resp = self.connection.request("/loadbalancers", method="DELETE", params=ids)
return resp.status == httplib.ACCEPTED
def get_balancer(self, balancer_id):
uri = "/loadbalancers/%s" % (balancer_id)
resp = self.connection.request(uri)
return self._to_balancer(resp.object["loadBalancer"])
def balancer_attach_member(self, balancer, member):
member_object = {"nodes": [self._member_attributes(member)]}
uri = "/loadbalancers/%s/nodes" % (balancer.id)
resp = self.connection.request(uri, method="POST", data=json.dumps(member_object))
return self._to_members(resp.object, balancer)[0]
def ex_balancer_attach_members(self, balancer, members):
"""
Attaches a list of members to a load balancer.
:param balancer: The Balancer to which members will be attached.
:type balancer: :class:`LoadBalancer`
:param members: A list of Members to attach.
:type members: ``list`` of :class:`Member`
:rtype: ``list`` of :class:`Member`
"""
member_objects = {"nodes": [self._member_attributes(member) for member in members]}
uri = "/loadbalancers/%s/nodes" % (balancer.id)
resp = self.connection.request(uri, method="POST", data=json.dumps(member_objects))
return self._to_members(resp.object, balancer)
def balancer_detach_member(self, balancer, member):
# Loadbalancer always needs to have at least 1 member.
# Last member cannot be detached. You can only disable it or destroy
# the balancer.
uri = "/loadbalancers/{}/nodes/{}".format(balancer.id, member.id)
resp = self.connection.request(uri, method="DELETE")
return resp.status == httplib.ACCEPTED
def ex_balancer_detach_members(self, balancer, members):
"""
Detaches a list of members from a balancer (the API supports up to 10).
This method blocks until the detach request has been processed and the
balancer is in a RUNNING state again.
:param balancer: The Balancer to detach members from.
:type balancer: :class:`LoadBalancer`
:param members: A list of Members to detach.
:type members: ``list`` of :class:`Member`
:return: Updated Balancer.
:rtype: :class:`LoadBalancer`
"""
accepted = self.ex_balancer_detach_members_no_poll(balancer, members)
if not accepted:
msg = "Detach members request was not accepted"
raise LibcloudError(msg, driver=self)
return self._get_updated_balancer(balancer)
def ex_balancer_detach_members_no_poll(self, balancer, members):
"""
Detaches a list of members from a balancer (the API supports up to 10).
This method returns immediately.
:param balancer: The Balancer to detach members from.
:type balancer: :class:`LoadBalancer`
:param members: A list of Members to detach.
:type members: ``list`` of :class:`Member`
:return: Returns whether the detach request was accepted.
:rtype: ``bool``
"""
uri = "/loadbalancers/%s/nodes" % (balancer.id)
ids = [("id", member.id) for member in members]
resp = self.connection.request(uri, method="DELETE", params=ids)
return resp.status == httplib.ACCEPTED
def balancer_list_members(self, balancer):
uri = "/loadbalancers/%s/nodes" % (balancer.id)
data = self.connection.request(uri).object
return self._to_members(data, balancer)
def update_balancer(self, balancer, **kwargs):
attrs = self._kwargs_to_mutable_attrs(**kwargs)
resp = self.connection.async_request(
action="/loadbalancers/%s" % balancer.id,
method="PUT",
data=json.dumps(attrs),
)
return self._to_balancer(resp.object["loadBalancer"])
def ex_update_balancer_no_poll(self, balancer, **kwargs):
"""
Update balancer no poll.
@inherits: :class:`Driver.update_balancer`
"""
attrs = self._kwargs_to_mutable_attrs(**kwargs)
resp = self.connection.request(
action="/loadbalancers/%s" % balancer.id,
method="PUT",
data=json.dumps(attrs),
)
return resp.status == httplib.ACCEPTED
def ex_balancer_update_member(self, balancer, member, **kwargs):
"""
Updates a Member's extra attributes for a Balancer. The attributes can
include 'weight' or 'condition'. This method blocks until the update
request has been processed and the balancer is in a RUNNING state
again.
:param balancer: Balancer to update the member on.
:type balancer: :class:`LoadBalancer`
:param member: Member which should be used
:type member: :class:`Member`
:keyword **kwargs: New attributes. Should contain either 'weight'
or 'condition'. 'condition' can be set to 'ENABLED', 'DISABLED'.
or 'DRAINING'. 'weight' can be set to a positive integer between
1 and 100, with a higher weight indicating that the node will receive
more traffic (assuming the Balancer is using a weighted algorithm).
:type **kwargs: ``dict``
:return: Updated Member.
:rtype: :class:`Member`
"""
accepted = self.ex_balancer_update_member_no_poll(balancer, member, **kwargs)
if not accepted:
msg = "Update member attributes was not accepted"
raise LibcloudError(msg, driver=self)
balancer = self._get_updated_balancer(balancer)
members = balancer.extra["members"]
updated_members = [m for m in members if m.id == member.id]
if not updated_members:
raise LibcloudError("Could not find updated member")
return updated_members[0]
def ex_balancer_update_member_no_poll(self, balancer, member, **kwargs):
"""
Updates a Member's extra attributes for a Balancer. The attribute can
include 'weight' or 'condition'. This method returns immediately.
:param balancer: Balancer to update the member on.
:type balancer: :class:`LoadBalancer`
:param member: Member which should be used
:type member: :class:`Member`
:keyword **kwargs: New attributes. Should contain either 'weight'
or 'condition'. 'condition' can be set to 'ENABLED', 'DISABLED'.
or 'DRAINING'. 'weight' can be set to a positive integer between
1 and 100, with a higher weight indicating that the node will receive
more traffic (assuming the Balancer is using a weighted algorithm).
:type **kwargs: ``dict``
:return: Returns whether the update request was accepted.
:rtype: ``bool``
"""
resp = self.connection.request(
action="/loadbalancers/{}/nodes/{}".format(balancer.id, member.id),
method="PUT",
data=json.dumps(self._kwargs_to_mutable_member_attrs(**kwargs)),
)
return resp.status == httplib.ACCEPTED
def ex_list_algorithm_names(self):
"""
Lists algorithms supported by the API. Returned as strings because
this list may change in the future.
:rtype: ``list`` of ``str``
"""
response = self.connection.request("/loadbalancers/algorithms")
return [a["name"].upper() for a in response.object["algorithms"]]
def ex_get_balancer_error_page(self, balancer):
"""
List error page configured for the specified load balancer.
:param balancer: Balancer which should be used
:type balancer: :class:`LoadBalancer`
:rtype: ``str``
"""
uri = "/loadbalancers/%s/errorpage" % (balancer.id)
resp = self.connection.request(uri)
return resp.object["errorpage"]["content"]
def ex_balancer_access_list(self, balancer):
"""
List the access list.
:param balancer: Balancer which should be used
:type balancer: :class:`LoadBalancer`
:rtype: ``list`` of :class:`RackspaceAccessRule`
"""
uri = "/loadbalancers/%s/accesslist" % (balancer.id)
resp = self.connection.request(uri)
return [self._to_access_rule(el) for el in resp.object["accessList"]]
def _get_updated_balancer(self, balancer):
"""
Updating a balancer's attributes puts a balancer into
'PENDING_UPDATE' status. Wait until the balancer is
back in 'ACTIVE' status and then return the individual
balancer details call.
"""
resp = self.connection.async_request(action="/loadbalancers/%s" % balancer.id, method="GET")
return self._to_balancer(resp.object["loadBalancer"])
def ex_update_balancer_health_monitor(self, balancer, health_monitor):
"""
Sets a Balancer's health monitor. This method blocks until the update
request has been processed and the balancer is in a RUNNING state
again.
:param balancer: Balancer to update.
:type balancer: :class:`LoadBalancer`
:param health_monitor: Health Monitor for the balancer.
:type health_monitor: :class:`RackspaceHealthMonitor`
:return: Updated Balancer.
:rtype: :class:`LoadBalancer`
"""
accepted = self.ex_update_balancer_health_monitor_no_poll(balancer, health_monitor)
if not accepted:
msg = "Update health monitor request not accepted"
raise LibcloudError(msg, driver=self)
return self._get_updated_balancer(balancer)
def ex_update_balancer_health_monitor_no_poll(self, balancer, health_monitor):
"""
Sets a Balancer's health monitor. This method returns immediately.
:param balancer: Balancer to update health monitor on.
:type balancer: :class:`LoadBalancer`
:param health_monitor: Health Monitor for the balancer.
:type health_monitor: :class:`RackspaceHealthMonitor`
:return: Returns whether the update request was accepted.
:rtype: ``bool``
"""
uri = "/loadbalancers/%s/healthmonitor" % (balancer.id)
resp = self.connection.request(
uri, method="PUT", data=json.dumps(health_monitor._to_dict())
)
return resp.status == httplib.ACCEPTED
def ex_disable_balancer_health_monitor(self, balancer):
"""
Disables a Balancer's health monitor. This method blocks until the
disable request has been processed and the balancer is in a RUNNING
state again.
:param balancer: Balancer to disable health monitor on.
:type balancer: :class:`LoadBalancer`
:return: Updated Balancer.
:rtype: :class:`LoadBalancer`
"""
if not self.ex_disable_balancer_health_monitor_no_poll(balancer):
msg = "Disable health monitor request not accepted"
raise LibcloudError(msg, driver=self)
return self._get_updated_balancer(balancer)
def ex_disable_balancer_health_monitor_no_poll(self, balancer):
"""
Disables a Balancer's health monitor. This method returns
immediately.
:param balancer: Balancer to disable health monitor on.
:type balancer: :class:`LoadBalancer`
:return: Returns whether the disable request was accepted.
:rtype: ``bool``
"""
uri = "/loadbalancers/%s/healthmonitor" % (balancer.id)
resp = self.connection.request(uri, method="DELETE")
return resp.status == httplib.ACCEPTED
def ex_update_balancer_connection_throttle(self, balancer, connection_throttle):
"""
Updates a Balancer's connection throttle. This method blocks until
the update request has been processed and the balancer is in a
RUNNING state again.
:param balancer: Balancer to update connection throttle on.
:type balancer: :class:`LoadBalancer`
:param connection_throttle: Connection Throttle for the balancer.
:type connection_throttle: :class:`RackspaceConnectionThrottle`
:return: Updated Balancer.
:rtype: :class:`LoadBalancer`
"""
accepted = self.ex_update_balancer_connection_throttle_no_poll(
balancer, connection_throttle
)
if not accepted:
msg = "Update connection throttle request not accepted"
raise LibcloudError(msg, driver=self)
return self._get_updated_balancer(balancer)
def ex_update_balancer_connection_throttle_no_poll(self, balancer, connection_throttle):
"""
Sets a Balancer's connection throttle. This method returns
immediately.
:param balancer: Balancer to update connection throttle on.
:type balancer: :class:`LoadBalancer`
:param connection_throttle: Connection Throttle for the balancer.
:type connection_throttle: :class:`RackspaceConnectionThrottle`
:return: Returns whether the update request was accepted.
:rtype: ``bool``
"""
uri = "/loadbalancers/%s/connectionthrottle" % (balancer.id)
resp = self.connection.request(
uri, method="PUT", data=json.dumps(connection_throttle._to_dict())
)
return resp.status == httplib.ACCEPTED
def ex_disable_balancer_connection_throttle(self, balancer):
"""
Disables a Balancer's connection throttle. This method blocks until
the disable request has been processed and the balancer is in a RUNNING
state again.
:param balancer: Balancer to disable connection throttle on.
:type balancer: :class:`LoadBalancer`
:return: Updated Balancer.
:rtype: :class:`LoadBalancer`
"""
if not self.ex_disable_balancer_connection_throttle_no_poll(balancer):
msg = "Disable connection throttle request not accepted"
raise LibcloudError(msg, driver=self)
return self._get_updated_balancer(balancer)
def ex_disable_balancer_connection_throttle_no_poll(self, balancer):
"""
Disables a Balancer's connection throttle. This method returns
immediately.
:param balancer: Balancer to disable connection throttle on.
:type balancer: :class:`LoadBalancer`
:return: Returns whether the disable request was accepted.
:rtype: ``bool``
"""
uri = "/loadbalancers/%s/connectionthrottle" % (balancer.id)
resp = self.connection.request(uri, method="DELETE")
return resp.status == httplib.ACCEPTED
def ex_enable_balancer_connection_logging(self, balancer):
"""
Enables connection logging for a Balancer. This method blocks until
the enable request has been processed and the balancer is in a RUNNING
state again.
:param balancer: Balancer to enable connection logging on.
:type balancer: :class:`LoadBalancer`
:return: Updated Balancer.
:rtype: :class:`LoadBalancer`
"""
if not self.ex_enable_balancer_connection_logging_no_poll(balancer):
msg = "Enable connection logging request not accepted"
raise LibcloudError(msg, driver=self)
return self._get_updated_balancer(balancer)
def ex_enable_balancer_connection_logging_no_poll(self, balancer):
"""
Enables connection logging for a Balancer. This method returns
immediately.
:param balancer: Balancer to enable connection logging on.
:type balancer: :class:`LoadBalancer`
:return: Returns whether the enable request was accepted.
:rtype: ``bool``
"""
uri = "/loadbalancers/%s/connectionlogging" % (balancer.id)
resp = self.connection.request(
uri, method="PUT", data=json.dumps({"connectionLogging": {"enabled": True}})
)
return resp.status == httplib.ACCEPTED
def ex_disable_balancer_connection_logging(self, balancer):
"""
Disables connection logging for a Balancer. This method blocks until
the enable request has been processed and the balancer is in a RUNNING
state again.
:param balancer: Balancer to disable connection logging on.
:type balancer: :class:`LoadBalancer`
:return: Updated Balancer.
:rtype: :class:`LoadBalancer`
"""
if not self.ex_disable_balancer_connection_logging_no_poll(balancer):
msg = "Disable connection logging request not accepted"
raise LibcloudError(msg, driver=self)
return self._get_updated_balancer(balancer)
def ex_disable_balancer_connection_logging_no_poll(self, balancer):
"""
Disables connection logging for a Balancer. This method returns
immediately.
:param balancer: Balancer to disable connection logging on.
:type balancer: :class:`LoadBalancer`
:return: Returns whether the disable request was accepted.
:rtype: ``bool``
"""
uri = "/loadbalancers/%s/connectionlogging" % (balancer.id)
resp = self.connection.request(
uri,
method="PUT",
data=json.dumps({"connectionLogging": {"enabled": False}}),
)
return resp.status == httplib.ACCEPTED
def ex_enable_balancer_session_persistence(self, balancer):
"""
Enables session persistence for a Balancer by setting the persistence
type to 'HTTP_COOKIE'. This method blocks until the enable request
has been processed and the balancer is in a RUNNING state again.
:param balancer: Balancer to enable session persistence on.
:type balancer: :class:`LoadBalancer`
:return: Updated Balancer.
:rtype: :class:`LoadBalancer`
"""
if not self.ex_enable_balancer_session_persistence_no_poll(balancer):
msg = "Enable session persistence request not accepted"
raise LibcloudError(msg, driver=self)
return self._get_updated_balancer(balancer)
def ex_enable_balancer_session_persistence_no_poll(self, balancer):
"""
Enables session persistence for a Balancer by setting the persistence
type to 'HTTP_COOKIE'. This method returns immediately.
:param balancer: Balancer to enable session persistence on.
:type balancer: :class:`LoadBalancer`
:return: Returns whether the enable request was accepted.
:rtype: ``bool``
"""
uri = "/loadbalancers/%s/sessionpersistence" % (balancer.id)
resp = self.connection.request(
uri,
method="PUT",
data=json.dumps({"sessionPersistence": {"persistenceType": "HTTP_COOKIE"}}),
)
return resp.status == httplib.ACCEPTED
def ex_disable_balancer_session_persistence(self, balancer):
"""
Disables session persistence for a Balancer. This method blocks until
the disable request has been processed and the balancer is in a RUNNING
state again.
:param balancer: Balancer to disable session persistence on.
:type balancer: :class:`LoadBalancer`
:return: Updated Balancer.
:rtype: :class:`LoadBalancer`
"""
if not self.ex_disable_balancer_session_persistence_no_poll(balancer):
msg = "Disable session persistence request not accepted"
raise LibcloudError(msg, driver=self)
return self._get_updated_balancer(balancer)
def ex_disable_balancer_session_persistence_no_poll(self, balancer):
"""
Disables session persistence for a Balancer. This method returns
immediately.
:param balancer: Balancer to disable session persistence for.
:type balancer: :class:`LoadBalancer`
:return: Returns whether the disable request was accepted.
:rtype: ``bool``
"""
uri = "/loadbalancers/%s/sessionpersistence" % (balancer.id)
resp = self.connection.request(uri, method="DELETE")
return resp.status == httplib.ACCEPTED
def ex_update_balancer_error_page(self, balancer, page_content):
"""
Updates a Balancer's custom error page. This method blocks until
the update request has been processed and the balancer is in a
RUNNING state again.
:param balancer: Balancer to update the custom error page for.
:type balancer: :class:`LoadBalancer`
:param page_content: HTML content for the custom error page.
:type page_content: ``str``
:return: Updated Balancer.
:rtype: :class:`LoadBalancer`
"""
accepted = self.ex_update_balancer_error_page_no_poll(balancer, page_content)
if not accepted:
msg = "Update error page request not accepted"
raise LibcloudError(msg, driver=self)
return self._get_updated_balancer(balancer)
def ex_update_balancer_error_page_no_poll(self, balancer, page_content):
"""
Updates a Balancer's custom error page. This method returns
immediately.
:param balancer: Balancer to update the custom error page for.
:type balancer: :class:`LoadBalancer`
:param page_content: HTML content for the custom error page.
:type page_content: ``str``
:return: Returns whether the update request was accepted.
:rtype: ``bool``
"""
uri = "/loadbalancers/%s/errorpage" % (balancer.id)
resp = self.connection.request(
uri, method="PUT", data=json.dumps({"errorpage": {"content": page_content}})
)
return resp.status == httplib.ACCEPTED
def ex_disable_balancer_custom_error_page(self, balancer):
"""
Disables a Balancer's custom error page, returning its error page to
the Rackspace-provided default. This method blocks until the disable
request has been processed and the balancer is in a RUNNING state
again.
:param balancer: Balancer to disable the custom error page for.
:type balancer: :class:`LoadBalancer`
:return: Updated Balancer.
:rtype: :class:`LoadBalancer`
"""
if not self.ex_disable_balancer_custom_error_page_no_poll(balancer):
msg = "Disable custom error page request not accepted"
raise LibcloudError(msg, driver=self)
return self._get_updated_balancer(balancer)
def ex_disable_balancer_custom_error_page_no_poll(self, balancer):
"""
Disables a Balancer's custom error page, returning its error page to
the Rackspace-provided default. This method returns immediately.
:param balancer: Balancer to disable the custom error page for.
:type balancer: :class:`LoadBalancer`
:return: Returns whether the disable request was accepted.
:rtype: ``bool``
"""
uri = "/loadbalancers/%s/errorpage" % (balancer.id)
resp = self.connection.request(uri, method="DELETE")
# Load Balancer API currently returns 200 OK on custom error page
# delete.
return resp.status == httplib.OK or resp.status == httplib.ACCEPTED
def ex_create_balancer_access_rule(self, balancer, rule):
"""
Adds an access rule to a Balancer's access list. This method blocks
until the update request has been processed and the balancer is in a
RUNNING state again.
:param balancer: Balancer to create the access rule for.
:type balancer: :class:`LoadBalancer`
:param rule: Access Rule to add to the balancer.
:type rule: :class:`RackspaceAccessRule`
:return: The created access rule.
:rtype: :class:`RackspaceAccessRule`
"""
accepted = self.ex_create_balancer_access_rule_no_poll(balancer, rule)
if not accepted:
msg = "Create access rule not accepted"
raise LibcloudError(msg, driver=self)
balancer = self._get_updated_balancer(balancer)
access_list = balancer.extra["accessList"]
created_rule = self._find_matching_rule(rule, access_list)
if not created_rule:
raise LibcloudError("Could not find created rule")
return created_rule
def ex_create_balancer_access_rule_no_poll(self, balancer, rule):
"""
Adds an access rule to a Balancer's access list. This method returns
immediately.
:param balancer: Balancer to create the access rule for.
:type balancer: :class:`LoadBalancer`
:param rule: Access Rule to add to the balancer.
:type rule: :class:`RackspaceAccessRule`
:return: Returns whether the create request was accepted.
:rtype: ``bool``
"""
uri = "/loadbalancers/%s/accesslist" % (balancer.id)
resp = self.connection.request(
uri, method="POST", data=json.dumps({"networkItem": rule._to_dict()})
)
return resp.status == httplib.ACCEPTED
def ex_create_balancer_access_rules(self, balancer, rules):
"""
Adds a list of access rules to a Balancer's access list. This method
blocks until the update request has been processed and the balancer is
in a RUNNING state again.
:param balancer: Balancer to create the access rule for.
:type balancer: :class:`LoadBalancer`
:param rules: List of :class:`RackspaceAccessRule` to add to the
balancer.
:type rules: ``list`` of :class:`RackspaceAccessRule`
:return: The created access rules.
:rtype: :class:`RackspaceAccessRule`
"""
accepted = self.ex_create_balancer_access_rules_no_poll(balancer, rules)
if not accepted:
msg = "Create access rules not accepted"
raise LibcloudError(msg, driver=self)
balancer = self._get_updated_balancer(balancer)
access_list = balancer.extra["accessList"]
created_rules = []
for r in rules:
matched_rule = self._find_matching_rule(r, access_list)
if matched_rule:
created_rules.append(matched_rule)
if len(created_rules) != len(rules):
raise LibcloudError("Could not find all created rules")
return created_rules
def _find_matching_rule(self, rule_to_find, access_list):
"""
LB API does not return the ID for the newly created rules, so we have
to search the list to find the rule with a matching rule type and
address to return an object with the right identifier.it. The API
enforces rule type and address uniqueness.
"""
for r in access_list:
if rule_to_find.rule_type == r.rule_type and rule_to_find.address == r.address:
return r
return None
def ex_create_balancer_access_rules_no_poll(self, balancer, rules):
"""
Adds a list of access rules to a Balancer's access list. This method
returns immediately.
:param balancer: Balancer to create the access rule for.
:type balancer: :class:`LoadBalancer`
:param rules: List of :class:`RackspaceAccessRule` to add to
the balancer.
:type rules: ``list`` of :class:`RackspaceAccessRule`
:return: Returns whether the create request was accepted.
:rtype: ``bool``
"""
uri = "/loadbalancers/%s/accesslist" % (balancer.id)
resp = self.connection.request(
uri,
method="POST",
data=json.dumps({"accessList": [rule._to_dict() for rule in rules]}),
)
return resp.status == httplib.ACCEPTED
def ex_destroy_balancer_access_rule(self, balancer, rule):
"""
Removes an access rule from a Balancer's access list. This method
blocks until the update request has been processed and the balancer
is in a RUNNING state again.
:param balancer: Balancer to remove the access rule from.
:type balancer: :class:`LoadBalancer`
:param rule: Access Rule to remove from the balancer.
:type rule: :class:`RackspaceAccessRule`
:return: Updated Balancer.
:rtype: :class:`LoadBalancer`
"""
accepted = self.ex_destroy_balancer_access_rule_no_poll(balancer, rule)
if not accepted:
msg = "Delete access rule not accepted"
raise LibcloudError(msg, driver=self)
return self._get_updated_balancer(balancer)
def ex_destroy_balancer_access_rule_no_poll(self, balancer, rule):
"""
Removes an access rule from a Balancer's access list. This method
returns immediately.
:param balancer: Balancer to remove the access rule from.
:type balancer: :class:`LoadBalancer`
:param rule: Access Rule to remove from the balancer.
:type rule: :class:`RackspaceAccessRule`
:return: Returns whether the destroy request was accepted.
:rtype: ``bool``
"""
uri = "/loadbalancers/{}/accesslist/{}".format(balancer.id, rule.id)
resp = self.connection.request(uri, method="DELETE")
return resp.status == httplib.ACCEPTED
def ex_destroy_balancer_access_rules(self, balancer, rules):
"""
Removes a list of access rules from a Balancer's access list. This
method blocks until the update request has been processed and the
balancer is in a RUNNING state again.
:param balancer: Balancer to remove the access rules from.
:type balancer: :class:`LoadBalancer`
:param rules: List of :class:`RackspaceAccessRule` objects to remove
from the balancer.
:type rules: ``list`` of :class:`RackspaceAccessRule`
:return: Updated Balancer.
:rtype: :class:`LoadBalancer`
"""
accepted = self.ex_destroy_balancer_access_rules_no_poll(balancer, rules)
if not accepted:
msg = "Destroy access rules request not accepted"
raise LibcloudError(msg, driver=self)
return self._get_updated_balancer(balancer)
def ex_destroy_balancer_access_rules_no_poll(self, balancer, rules):
"""
Removes a list of access rules from a Balancer's access list. This
method returns immediately.
:param balancer: Balancer to remove the access rules from.
:type balancer: :class:`LoadBalancer`
:param rules: List of :class:`RackspaceAccessRule` objects to remove
from the balancer.
:type rules: ``list`` of :class:`RackspaceAccessRule`
:return: Returns whether the destroy request was accepted.
:rtype: ``bool``
"""
ids = [("id", rule.id) for rule in rules]
uri = "/loadbalancers/%s/accesslist" % balancer.id
resp = self.connection.request(uri, method="DELETE", params=ids)
return resp.status == httplib.ACCEPTED
def ex_list_current_usage(self, balancer):
"""
Return current load balancer usage report.
:param balancer: Balancer to remove the access rules from.
:type balancer: :class:`LoadBalancer`
:return: Raw load balancer usage object.
:rtype: ``dict``
"""
uri = "/loadbalancers/%s/usage/current" % (balancer.id)
resp = self.connection.request(uri, method="GET")
return resp.object
def _to_protocols(self, object):
protocols = []
for item in object["protocols"]:
protocols.append(item["name"].lower())
return protocols
def _to_protocols_with_default_ports(self, object):
protocols = []
for item in object["protocols"]:
name = item["name"].lower()
port = int(item["port"])
protocols.append((name, port))
return protocols
def _to_balancers(self, object):
return [self._to_balancer(el) for el in object["loadBalancers"]]
def _to_balancer(self, el):
ip = None
port = None
sourceAddresses = {}
if "port" in el:
port = el["port"]
if "sourceAddresses" in el:
sourceAddresses = el["sourceAddresses"]
extra = {
"ipv6PublicSource": sourceAddresses.get("ipv6Public"),
"ipv4PublicSource": sourceAddresses.get("ipv4Public"),
"ipv4PrivateSource": sourceAddresses.get("ipv4Servicenet"),
"service_name": self.connection.get_service_name(),
"uri": "https://%s%s/loadbalancers/%s"
% (self.connection.host, self.connection.request_path, el["id"]),
}
if "virtualIps" in el:
ip = el["virtualIps"][0]["address"]
extra["virtualIps"] = el["virtualIps"]
if "protocol" in el:
extra["protocol"] = el["protocol"]
if "algorithm" in el and el["algorithm"] in self._VALUE_TO_ALGORITHM_MAP:
extra["algorithm"] = self._value_to_algorithm(el["algorithm"])
if "healthMonitor" in el:
health_monitor = self._to_health_monitor(el)
if health_monitor:
extra["healthMonitor"] = health_monitor
if "connectionThrottle" in el:
extra["connectionThrottle"] = self._to_connection_throttle(el)
if "sessionPersistence" in el:
persistence = el["sessionPersistence"]
extra["sessionPersistenceType"] = persistence.get("persistenceType")
if "connectionLogging" in el:
logging = el["connectionLogging"]
extra["connectionLoggingEnabled"] = logging.get("enabled")
if "nodes" in el:
extra["members"] = self._to_members(el)
if "created" in el:
extra["created"] = self._iso_to_datetime(el["created"]["time"])
if "updated" in el:
extra["updated"] = self._iso_to_datetime(el["updated"]["time"])
if "accessList" in el:
extra["accessList"] = [self._to_access_rule(rule) for rule in el["accessList"]]
return LoadBalancer(
id=el["id"],
name=el["name"],
state=self.LB_STATE_MAP.get(el["status"], State.UNKNOWN),
ip=ip,
port=port,
driver=self.connection.driver,
extra=extra,
)
def _to_members(self, object, balancer=None):
return [self._to_member(el, balancer) for el in object["nodes"]]
def _to_member(self, el, balancer=None):
extra = {}
if "weight" in el:
extra["weight"] = el["weight"]
if "condition" in el and el["condition"] in self.LB_MEMBER_CONDITION_MAP:
extra["condition"] = self.LB_MEMBER_CONDITION_MAP.get(el["condition"])
if "status" in el:
extra["status"] = el["status"]
lbmember = Member(
id=el["id"],
ip=el["address"],
port=el["port"],
balancer=balancer,
extra=extra,
)
return lbmember
def _protocol_to_value(self, protocol):
non_standard_protocols = {
"imapv2": "IMAPv2",
"imapv3": "IMAPv3",
"imapv4": "IMAPv4",
}
protocol_name = protocol.lower()
if protocol_name in non_standard_protocols:
protocol_value = non_standard_protocols[protocol_name]
else:
protocol_value = protocol.upper()
return protocol_value
def _kwargs_to_mutable_attrs(self, **attrs):
update_attrs = {}
if "name" in attrs:
update_attrs["name"] = attrs["name"]
if "algorithm" in attrs:
algorithm_value = self._algorithm_to_value(attrs["algorithm"])
update_attrs["algorithm"] = algorithm_value
if "protocol" in attrs:
update_attrs["protocol"] = self._protocol_to_value(attrs["protocol"])
if "port" in attrs:
update_attrs["port"] = int(attrs["port"])
if "vip" in attrs:
if attrs["vip"] == "PUBLIC" or attrs["vip"] == "SERVICENET":
update_attrs["virtualIps"] = [{"type": attrs["vip"]}]
else:
update_attrs["virtualIps"] = [{"id": attrs["vip"]}]
return update_attrs
def _kwargs_to_mutable_member_attrs(self, **attrs):
update_attrs = {}
if "condition" in attrs:
update_attrs["condition"] = self.CONDITION_LB_MEMBER_MAP.get(attrs["condition"])
if "weight" in attrs:
update_attrs["weight"] = attrs["weight"]
return update_attrs
def _to_health_monitor(self, el):
health_monitor_data = el["healthMonitor"]
type = health_monitor_data.get("type")
delay = health_monitor_data.get("delay")
timeout = health_monitor_data.get("timeout")
attempts_before_deactivation = health_monitor_data.get("attemptsBeforeDeactivation")
if type == "CONNECT":
return RackspaceHealthMonitor(
type=type,
delay=delay,
timeout=timeout,
attempts_before_deactivation=attempts_before_deactivation,
)
if type == "HTTP" or type == "HTTPS":
return RackspaceHTTPHealthMonitor(
type=type,
delay=delay,
timeout=timeout,
attempts_before_deactivation=attempts_before_deactivation,
path=health_monitor_data.get("path"),
status_regex=health_monitor_data.get("statusRegex"),
body_regex=health_monitor_data.get("bodyRegex", ""),
)
return None
def _to_connection_throttle(self, el):
connection_throttle_data = el["connectionThrottle"]
min_connections = connection_throttle_data.get("minConnections")
max_connections = connection_throttle_data.get("maxConnections")
max_connection_rate = connection_throttle_data.get("maxConnectionRate")
rate_interval = connection_throttle_data.get("rateInterval")
return RackspaceConnectionThrottle(
min_connections=min_connections,
max_connections=max_connections,
max_connection_rate=max_connection_rate,
rate_interval_seconds=rate_interval,
)
def _to_access_rule(self, el):
return RackspaceAccessRule(
id=el.get("id"),
rule_type=self._to_access_rule_type(el.get("type")),
address=el.get("address"),
)
def _to_access_rule_type(self, type):
if type == "ALLOW":
return RackspaceAccessRuleType.ALLOW
elif type == "DENY":
return RackspaceAccessRuleType.DENY
def _iso_to_datetime(self, isodate):
date_formats = ("%Y-%m-%dT%H:%M:%SZ", "%Y-%m-%dT%H:%M:%S%z")
date = None
for date_format in date_formats:
try:
date = datetime.strptime(isodate, date_format)
except ValueError:
pass
if date:
break
return date