1554 lines
56 KiB
Python
1554 lines
56 KiB
Python
# 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
|
|
|
|
try:
|
|
import simplejson as json
|
|
except ImportError:
|
|
import json
|
|
|
|
from libcloud.utils.py3 import httplib
|
|
from libcloud.utils.misc import reverse_dict
|
|
from libcloud.loadbalancer.base import LoadBalancer, Member, Driver, Algorithm
|
|
from libcloud.loadbalancer.base import DEFAULT_ALGORITHM
|
|
from libcloud.compute.drivers.rackspace import RackspaceConnection
|
|
from libcloud.common.types import LibcloudError
|
|
from libcloud.common.base import JsonResponse, PollingConnection
|
|
from libcloud.loadbalancer.types import State, MemberCondition
|
|
from libcloud.common.openstack import OpenStackDriverMixin
|
|
from libcloud.common.rackspace import AUTH_URL
|
|
|
|
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(RackspaceResponse, self).parse_body()
|
|
|
|
def success(self):
|
|
return 200 <= int(self.status) <= 299
|
|
|
|
|
|
class RackspaceHealthMonitor(object):
|
|
"""
|
|
: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(RackspaceHTTPHealthMonitor, self).__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(RackspaceHTTPHealthMonitor, self)._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(object):
|
|
"""
|
|
: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(object):
|
|
ALLOW = 0
|
|
DENY = 1
|
|
|
|
_RULE_TYPE_STRING_MAP = {
|
|
ALLOW: 'ALLOW',
|
|
DENY: 'DENY'
|
|
}
|
|
|
|
|
|
class RackspaceAccessRule(object):
|
|
"""
|
|
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(RackspaceConnection, self).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(RackspaceLBDriver, self).__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/%s/nodes/%s' % (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/%s/nodes/%s' % (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/%s/accesslist/%s' % (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
|