heat/contrib/rackspace/rackspace/resources/cloud_loadbalancer.py

926 lines
33 KiB
Python

#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import copy
import functools
import itertools
import six
from heat.common import exception
from heat.common.i18n import _
from heat.common.i18n import _LI
from heat.engine import attributes
from heat.engine import constraints
from heat.engine import function
from heat.engine import properties
from heat.engine import resource
from heat.engine import scheduler
from heat.openstack.common import log as logging
try:
from pyrax.exceptions import NotFound # noqa
PYRAX_INSTALLED = True
except ImportError:
# Setup fake exception for testing without pyrax
class NotFound(Exception):
pass
PYRAX_INSTALLED = False
LOG = logging.getLogger(__name__)
def lb_immutable(exc):
if 'immutable' in six.text_type(exc):
return True
return False
def retry_if_immutable(task):
@functools.wraps(task)
def wrapper(*args, **kwargs):
while True:
yield
try:
task(*args, **kwargs)
except Exception as exc:
# InvalidLoadBalancerParameters, or BadRequest for the
# same immutable load balancer error, so check the
# exception message instead of the exception type
if lb_immutable(exc):
continue
raise
else:
break
return wrapper
class LoadbalancerBuildError(exception.HeatException):
msg_fmt = _("There was an error building the loadbalancer:%(lb_name)s.")
class CloudLoadBalancer(resource.Resource):
"""Represents a Rackspace Cloud Loadbalancer."""
PROPERTIES = (
NAME, NODES, PROTOCOL, ACCESS_LIST, HALF_CLOSED, ALGORITHM,
CONNECTION_LOGGING, METADATA, PORT, TIMEOUT,
CONNECTION_THROTTLE, SESSION_PERSISTENCE, VIRTUAL_IPS,
CONTENT_CACHING, HEALTH_MONITOR, SSL_TERMINATION, ERROR_PAGE,
) = (
'name', 'nodes', 'protocol', 'accessList', 'halfClosed', 'algorithm',
'connectionLogging', 'metadata', 'port', 'timeout',
'connectionThrottle', 'sessionPersistence', 'virtualIps',
'contentCaching', 'healthMonitor', 'sslTermination', 'errorPage',
)
LB_UPDATE_PROPS = (NAME, ALGORITHM, PROTOCOL, HALF_CLOSED, PORT, TIMEOUT)
_NODE_KEYS = (
NODE_ADDRESSES, NODE_PORT, NODE_CONDITION, NODE_TYPE,
NODE_WEIGHT,
) = (
'addresses', 'port', 'condition', 'type',
'weight',
)
_ACCESS_LIST_KEYS = (
ACCESS_LIST_ADDRESS, ACCESS_LIST_TYPE,
) = (
'address', 'type',
)
_CONNECTION_THROTTLE_KEYS = (
CONNECTION_THROTTLE_MAX_CONNECTION_RATE,
CONNECTION_THROTTLE_MIN_CONNECTIONS,
CONNECTION_THROTTLE_MAX_CONNECTIONS,
CONNECTION_THROTTLE_RATE_INTERVAL,
) = (
'maxConnectionRate',
'minConnections',
'maxConnections',
'rateInterval',
)
_VIRTUAL_IP_KEYS = (
VIRTUAL_IP_TYPE, VIRTUAL_IP_IP_VERSION, VIRTUAL_IP_ID
) = (
'type', 'ipVersion', 'id'
)
_HEALTH_MONITOR_KEYS = (
HEALTH_MONITOR_ATTEMPTS_BEFORE_DEACTIVATION, HEALTH_MONITOR_DELAY,
HEALTH_MONITOR_TIMEOUT, HEALTH_MONITOR_TYPE, HEALTH_MONITOR_BODY_REGEX,
HEALTH_MONITOR_HOST_HEADER, HEALTH_MONITOR_PATH,
HEALTH_MONITOR_STATUS_REGEX,
) = (
'attemptsBeforeDeactivation', 'delay',
'timeout', 'type', 'bodyRegex',
'hostHeader', 'path',
'statusRegex',
)
_HEALTH_MONITOR_CONNECT_KEYS = (
HEALTH_MONITOR_ATTEMPTS_BEFORE_DEACTIVATION, HEALTH_MONITOR_DELAY,
HEALTH_MONITOR_TIMEOUT, HEALTH_MONITOR_TYPE,
)
_SSL_TERMINATION_KEYS = (
SSL_TERMINATION_SECURE_PORT, SSL_TERMINATION_PRIVATEKEY,
SSL_TERMINATION_CERTIFICATE, SSL_TERMINATION_INTERMEDIATE_CERTIFICATE,
SSL_TERMINATION_SECURE_TRAFFIC_ONLY,
) = (
'securePort', 'privatekey',
'certificate', 'intermediateCertificate',
'secureTrafficOnly',
)
ATTRIBUTES = (
PUBLIC_IP, VIPS
) = (
'PublicIp', 'virtualIps'
)
ALGORITHMS = ["LEAST_CONNECTIONS", "RANDOM", "ROUND_ROBIN",
"WEIGHTED_LEAST_CONNECTIONS", "WEIGHTED_ROUND_ROBIN"]
_health_monitor_schema = {
HEALTH_MONITOR_ATTEMPTS_BEFORE_DEACTIVATION: properties.Schema(
properties.Schema.NUMBER,
required=True,
constraints=[
constraints.Range(1, 10),
]
),
HEALTH_MONITOR_DELAY: properties.Schema(
properties.Schema.NUMBER,
required=True,
constraints=[
constraints.Range(1, 3600),
]
),
HEALTH_MONITOR_TIMEOUT: properties.Schema(
properties.Schema.NUMBER,
required=True,
constraints=[
constraints.Range(1, 300),
]
),
HEALTH_MONITOR_TYPE: properties.Schema(
properties.Schema.STRING,
required=True,
constraints=[
constraints.AllowedValues(['CONNECT', 'HTTP', 'HTTPS']),
]
),
HEALTH_MONITOR_BODY_REGEX: properties.Schema(
properties.Schema.STRING
),
HEALTH_MONITOR_HOST_HEADER: properties.Schema(
properties.Schema.STRING
),
HEALTH_MONITOR_PATH: properties.Schema(
properties.Schema.STRING
),
HEALTH_MONITOR_STATUS_REGEX: properties.Schema(
properties.Schema.STRING
),
}
properties_schema = {
NAME: properties.Schema(
properties.Schema.STRING,
update_allowed=True
),
NODES: properties.Schema(
properties.Schema.LIST,
schema=properties.Schema(
properties.Schema.MAP,
schema={
NODE_ADDRESSES: properties.Schema(
properties.Schema.LIST,
required=True,
description=(_("IP addresses for the load balancer "
"node. Must have at least one "
"address.")),
schema=properties.Schema(
properties.Schema.STRING
)
),
NODE_PORT: properties.Schema(
properties.Schema.NUMBER,
required=True
),
NODE_CONDITION: properties.Schema(
properties.Schema.STRING,
default='ENABLED',
constraints=[
constraints.AllowedValues(['ENABLED',
'DISABLED']),
]
),
NODE_TYPE: properties.Schema(
properties.Schema.STRING,
constraints=[
constraints.AllowedValues(['PRIMARY',
'SECONDARY']),
]
),
NODE_WEIGHT: properties.Schema(
properties.Schema.NUMBER,
constraints=[
constraints.Range(1, 100),
]
),
},
),
required=True,
update_allowed=True
),
PROTOCOL: properties.Schema(
properties.Schema.STRING,
required=True,
constraints=[
constraints.AllowedValues(['DNS_TCP', 'DNS_UDP', 'FTP',
'HTTP', 'HTTPS', 'IMAPS',
'IMAPv4', 'LDAP', 'LDAPS',
'MYSQL', 'POP3', 'POP3S', 'SMTP',
'TCP', 'TCP_CLIENT_FIRST', 'UDP',
'UDP_STREAM', 'SFTP']),
],
update_allowed=True
),
ACCESS_LIST: properties.Schema(
properties.Schema.LIST,
schema=properties.Schema(
properties.Schema.MAP,
schema={
ACCESS_LIST_ADDRESS: properties.Schema(
properties.Schema.STRING,
required=True
),
ACCESS_LIST_TYPE: properties.Schema(
properties.Schema.STRING,
required=True,
constraints=[
constraints.AllowedValues(['ALLOW', 'DENY']),
]
),
},
)
),
HALF_CLOSED: properties.Schema(
properties.Schema.BOOLEAN,
update_allowed=True
),
ALGORITHM: properties.Schema(
properties.Schema.STRING,
constraints=[
constraints.AllowedValues(ALGORITHMS)
],
update_allowed=True
),
CONNECTION_LOGGING: properties.Schema(
properties.Schema.BOOLEAN,
update_allowed=True
),
METADATA: properties.Schema(
properties.Schema.MAP,
update_allowed=True
),
PORT: properties.Schema(
properties.Schema.NUMBER,
required=True,
update_allowed=True
),
TIMEOUT: properties.Schema(
properties.Schema.NUMBER,
constraints=[
constraints.Range(1, 120),
],
update_allowed=True
),
CONNECTION_THROTTLE: properties.Schema(
properties.Schema.MAP,
schema={
CONNECTION_THROTTLE_MAX_CONNECTION_RATE: properties.Schema(
properties.Schema.NUMBER,
constraints=[
constraints.Range(0, 100000),
]
),
CONNECTION_THROTTLE_MIN_CONNECTIONS: properties.Schema(
properties.Schema.NUMBER,
constraints=[
constraints.Range(1, 1000),
]
),
CONNECTION_THROTTLE_MAX_CONNECTIONS: properties.Schema(
properties.Schema.NUMBER,
constraints=[
constraints.Range(1, 100000),
]
),
CONNECTION_THROTTLE_RATE_INTERVAL: properties.Schema(
properties.Schema.NUMBER,
constraints=[
constraints.Range(1, 3600),
]
),
},
update_allowed=True
),
SESSION_PERSISTENCE: properties.Schema(
properties.Schema.STRING,
constraints=[
constraints.AllowedValues(['HTTP_COOKIE', 'SOURCE_IP']),
],
update_allowed=True
),
VIRTUAL_IPS: properties.Schema(
properties.Schema.LIST,
schema=properties.Schema(
properties.Schema.MAP,
schema={
VIRTUAL_IP_TYPE: properties.Schema(
properties.Schema.STRING,
"The type of VIP (public or internal). This property"
" cannot be specified if 'id' is specified. This "
"property must be specified if id is not specified.",
constraints=[
constraints.AllowedValues(['SERVICENET',
'PUBLIC']),
]
),
VIRTUAL_IP_IP_VERSION: properties.Schema(
properties.Schema.STRING,
"IP version of the VIP. This property cannot be "
"specified if 'id' is specified. This property must "
"be specified if id is not specified.",
constraints=[
constraints.AllowedValues(['IPV6', 'IPV4']),
]
),
VIRTUAL_IP_ID: properties.Schema(
properties.Schema.NUMBER,
"ID of a shared VIP to use instead of creating a "
"new one. This property cannot be specified if type"
" or version is specified."
)
},
),
required=True,
constraints=[
constraints.Length(min=1)
]
),
CONTENT_CACHING: properties.Schema(
properties.Schema.STRING,
constraints=[
constraints.AllowedValues(['ENABLED', 'DISABLED']),
],
update_allowed=True
),
HEALTH_MONITOR: properties.Schema(
properties.Schema.MAP,
schema=_health_monitor_schema,
update_allowed=True
),
SSL_TERMINATION: properties.Schema(
properties.Schema.MAP,
schema={
SSL_TERMINATION_SECURE_PORT: properties.Schema(
properties.Schema.NUMBER,
default=443
),
SSL_TERMINATION_PRIVATEKEY: properties.Schema(
properties.Schema.STRING,
required=True
),
SSL_TERMINATION_CERTIFICATE: properties.Schema(
properties.Schema.STRING,
required=True
),
# only required if configuring intermediate ssl termination
# add to custom validation
SSL_TERMINATION_INTERMEDIATE_CERTIFICATE: properties.Schema(
properties.Schema.STRING
),
# pyrax will default to false
SSL_TERMINATION_SECURE_TRAFFIC_ONLY: properties.Schema(
properties.Schema.BOOLEAN,
default=False
),
},
update_allowed=True
),
ERROR_PAGE: properties.Schema(
properties.Schema.STRING,
update_allowed=True
),
}
attributes_schema = {
PUBLIC_IP: attributes.Schema(
_('Public IP address of the specified instance.')
),
VIPS: attributes.Schema(
_("A list of assigned virtual ip addresses")
)
}
def __init__(self, name, json_snippet, stack):
super(CloudLoadBalancer, self).__init__(name, json_snippet, stack)
self.clb = self.cloud_lb()
def cloud_lb(self):
return self.client('cloud_lb')
def _setup_properties(self, properties, function):
"""Use defined schema properties as kwargs for loadbalancer objects."""
if properties and function:
return [function(**self._remove_none(item_dict))
for item_dict in properties]
elif function:
return [function()]
def _alter_properties_for_api(self):
"""Set up required, but useless, key/value pairs.
The following properties have useless key/value pairs which must
be passed into the api. Set them up to make template definition easier.
"""
session_persistence = None
if self.SESSION_PERSISTENCE in self.properties.data:
session_persistence = {'persistenceType':
self.properties[self.SESSION_PERSISTENCE]}
connection_logging = None
if self.CONNECTION_LOGGING in self.properties.data:
connection_logging = {"enabled":
self.properties[self.CONNECTION_LOGGING]}
metadata = None
if self.METADATA in self.properties.data:
metadata = [{'key': k, 'value': v}
for k, v
in six.iteritems(self.properties[self.METADATA])]
return (session_persistence, connection_logging, metadata)
def _check_status(self, loadbalancer, status_list):
"""Update the loadbalancer state, check the status."""
loadbalancer.get()
if loadbalancer.status in status_list:
return True
else:
return False
def _configure_post_creation(self, loadbalancer):
"""Configure all load balancer properties post creation.
These properties can only be set after the load balancer is created.
"""
if self.properties[self.ACCESS_LIST]:
while not self._check_status(loadbalancer, ['ACTIVE']):
yield
loadbalancer.add_access_list(self.properties[self.ACCESS_LIST])
if self.properties[self.ERROR_PAGE]:
while not self._check_status(loadbalancer, ['ACTIVE']):
yield
loadbalancer.set_error_page(self.properties[self.ERROR_PAGE])
if self.properties[self.SSL_TERMINATION]:
while not self._check_status(loadbalancer, ['ACTIVE']):
yield
ssl_term = self.properties[self.SSL_TERMINATION]
loadbalancer.add_ssl_termination(
ssl_term[self.SSL_TERMINATION_SECURE_PORT],
ssl_term[self.SSL_TERMINATION_PRIVATEKEY],
ssl_term[self.SSL_TERMINATION_CERTIFICATE],
intermediateCertificate=ssl_term[
self.SSL_TERMINATION_INTERMEDIATE_CERTIFICATE],
enabled=True,
secureTrafficOnly=ssl_term[
self.SSL_TERMINATION_SECURE_TRAFFIC_ONLY])
if self.CONTENT_CACHING in self.properties:
enabled = self.properties[self.CONTENT_CACHING] == 'ENABLED'
while not self._check_status(loadbalancer, ['ACTIVE']):
yield
loadbalancer.content_caching = enabled
def _process_node(self, node):
if not node.get(self.NODE_ADDRESSES):
yield node
else:
for addr in node.get(self.NODE_ADDRESSES):
norm_node = copy.deepcopy(node)
norm_node['address'] = addr
del norm_node[self.NODE_ADDRESSES]
yield norm_node
def _process_nodes(self, node_list):
node_itr = itertools.imap(self._process_node, node_list)
return itertools.chain.from_iterable(node_itr)
def handle_create(self):
node_list = self._process_nodes(self.properties.get(self.NODES))
nodes = [self.clb.Node(**node) for node in node_list]
vips = self.properties.get(self.VIRTUAL_IPS)
virtual_ips = self._setup_properties(vips, self.clb.VirtualIP)
(session_persistence, connection_logging, metadata) = \
self._alter_properties_for_api()
lb_body = {
'port': self.properties[self.PORT],
'protocol': self.properties[self.PROTOCOL],
'nodes': nodes,
'virtual_ips': virtual_ips,
'algorithm': self.properties.get(self.ALGORITHM),
'halfClosed': self.properties.get(self.HALF_CLOSED),
'connectionThrottle': self.properties.get(
self.CONNECTION_THROTTLE),
'metadata': metadata,
'healthMonitor': self.properties.get(self.HEALTH_MONITOR),
'sessionPersistence': session_persistence,
'timeout': self.properties.get(self.TIMEOUT),
'connectionLogging': connection_logging,
}
lb_name = (self.properties.get(self.NAME) or
self.physical_resource_name())
LOG.debug("Creating loadbalancer: %s" % {lb_name: lb_body})
loadbalancer = self.clb.create(lb_name, **lb_body)
self.resource_id_set(str(loadbalancer.id))
post_create = scheduler.TaskRunner(self._configure_post_creation,
loadbalancer)
post_create(timeout=600)
return loadbalancer
def check_create_complete(self, loadbalancer):
return self._check_status(loadbalancer, ['ACTIVE'])
def handle_check(self):
loadbalancer = self.clb.get(self.resource_id)
if not self._check_status(loadbalancer, ['ACTIVE']):
raise exception.Error(_("Cloud LoadBalancer is not ACTIVE "
"(was: %s)") % loadbalancer.status)
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
"""Add and remove nodes specified in the prop_diff."""
lb = self.clb.get(self.resource_id)
checkers = []
if self.NODES in prop_diff:
updated_nodes = prop_diff[self.NODES]
checkers.extend(self._update_nodes(lb, updated_nodes))
updated_props = {}
for prop in prop_diff.keys():
if prop in self.LB_UPDATE_PROPS:
updated_props[prop] = prop_diff[prop]
if updated_props:
checkers.append(self._update_lb_properties(lb, updated_props))
if self.HEALTH_MONITOR in prop_diff:
updated_hm = prop_diff[self.HEALTH_MONITOR]
checkers.append(self._update_health_monitor(lb, updated_hm))
if self.SESSION_PERSISTENCE in prop_diff:
updated_sp = prop_diff[self.SESSION_PERSISTENCE]
checkers.append(self._update_session_persistence(lb, updated_sp))
if self.SSL_TERMINATION in prop_diff:
updated_ssl_term = prop_diff[self.SSL_TERMINATION]
checkers.append(self._update_ssl_termination(lb, updated_ssl_term))
if self.METADATA in prop_diff:
updated_metadata = prop_diff[self.METADATA]
checkers.append(self._update_metadata(lb, updated_metadata))
if self.ERROR_PAGE in prop_diff:
updated_errorpage = prop_diff[self.ERROR_PAGE]
checkers.append(self._update_errorpage(lb, updated_errorpage))
if self.CONNECTION_LOGGING in prop_diff:
updated_cl = prop_diff[self.CONNECTION_LOGGING]
checkers.append(self._update_connection_logging(lb, updated_cl))
if self.CONNECTION_THROTTLE in prop_diff:
updated_ct = prop_diff[self.CONNECTION_THROTTLE]
checkers.append(self._update_connection_throttle(lb, updated_ct))
if self.CONTENT_CACHING in prop_diff:
updated_cc = prop_diff[self.CONTENT_CACHING]
checkers.append(self._update_content_caching(lb, updated_cc))
return checkers
def _update_nodes(self, lb, updated_nodes):
@retry_if_immutable
def add_nodes(lb, new_nodes):
lb.add_nodes(new_nodes)
@retry_if_immutable
def remove_node(known, node):
known[node].delete()
@retry_if_immutable
def update_node(known, node):
known[node].update()
checkers = []
current_nodes = lb.nodes
diff_nodes = self._process_nodes(updated_nodes)
# Loadbalancers can be uniquely identified by address and
# port. Old is a dict of all nodes the loadbalancer
# currently knows about.
old = dict(("{0.address}{0.port}".format(node), node)
for node in current_nodes)
# New is a dict of the nodes the loadbalancer will know
# about after this update.
new = dict(("%s%s" % (node["address"],
node[self.NODE_PORT]), node)
for node in diff_nodes)
old_set = set(old.keys())
new_set = set(new.keys())
deleted = old_set.difference(new_set)
added = new_set.difference(old_set)
updated = new_set.intersection(old_set)
if len(current_nodes) + len(added) - len(deleted) < 1:
raise ValueError(_("The loadbalancer:%s requires at least one "
"node.") % self.name)
"""
Add loadbalancers in the new map that are not in the old map.
Add before delete to avoid deleting the last node and getting in
an invalid state.
"""
new_nodes = [self.clb.Node(**new[lb_node]) for lb_node in added]
if new_nodes:
checkers.append(scheduler.TaskRunner(add_nodes, lb, new_nodes))
# Delete loadbalancers in the old dict that are not in the
# new dict.
for node in deleted:
checkers.append(scheduler.TaskRunner(remove_node, old, node))
# Update nodes that have been changed
for node in updated:
node_changed = False
for attribute in new[node].keys():
new_value = new[node][attribute]
if new_value and new_value != getattr(old[node], attribute):
node_changed = True
setattr(old[node], attribute, new_value)
if node_changed:
checkers.append(scheduler.TaskRunner(update_node, old, node))
return checkers
def _update_lb_properties(self, lb, updated_props):
@retry_if_immutable
def update_lb():
lb.update(**updated_props)
return scheduler.TaskRunner(update_lb)
def _update_health_monitor(self, lb, updated_hm):
@retry_if_immutable
def add_health_monitor():
lb.add_health_monitor(**updated_hm)
@retry_if_immutable
def delete_health_monitor():
lb.delete_health_monitor()
if updated_hm is None:
return scheduler.TaskRunner(delete_health_monitor)
else:
# Adding a health monitor is a destructive, so there's
# no need to delete, then add
return scheduler.TaskRunner(add_health_monitor)
def _update_session_persistence(self, lb, updated_sp):
@retry_if_immutable
def add_session_persistence():
lb.session_persistence = updated_sp
@retry_if_immutable
def delete_session_persistence():
lb.session_persistence = ''
if updated_sp is None:
return scheduler.TaskRunner(delete_session_persistence)
else:
# Adding session persistence is destructive
return scheduler.TaskRunner(add_session_persistence)
def _update_ssl_termination(self, lb, updated_ssl_term):
@retry_if_immutable
def add_ssl_termination():
lb.add_ssl_termination(**updated_ssl_term)
@retry_if_immutable
def delete_ssl_termination():
lb.delete_ssl_termination()
if updated_ssl_term is None:
return scheduler.TaskRunner(delete_ssl_termination)
else:
# Adding SSL termination is destructive
return scheduler.TaskRunner(add_ssl_termination)
def _update_metadata(self, lb, updated_metadata):
@retry_if_immutable
def add_metadata():
lb.set_metadata(updated_metadata)
@retry_if_immutable
def delete_metadata():
lb.delete_metadata()
if updated_metadata is None:
return scheduler.TaskRunner(delete_metadata)
else:
return scheduler.TaskRunner(add_metadata)
def _update_errorpage(self, lb, updated_errorpage):
@retry_if_immutable
def add_errorpage():
lb.set_error_page(updated_errorpage)
@retry_if_immutable
def delete_errorpage():
lb.clear_error_page()
if updated_errorpage is None:
return scheduler.TaskRunner(delete_errorpage)
else:
return scheduler.TaskRunner(add_errorpage)
def _update_connection_logging(self, lb, updated_cl):
@retry_if_immutable
def enable_connection_logging():
lb.connection_logging = True
@retry_if_immutable
def disable_connection_logging():
lb.connection_logging = False
if updated_cl:
return scheduler.TaskRunner(enable_connection_logging)
else:
return scheduler.TaskRunner(disable_connection_logging)
def _update_connection_throttle(self, lb, updated_ct):
@retry_if_immutable
def add_connection_throttle():
lb.add_connection_throttle(**updated_ct)
@retry_if_immutable
def delete_connection_throttle():
lb.delete_connection_throttle()
if updated_ct is None:
return scheduler.TaskRunner(delete_connection_throttle)
else:
return scheduler.TaskRunner(add_connection_throttle)
def _update_content_caching(self, lb, updated_cc):
@retry_if_immutable
def enable_content_caching():
lb.content_caching = True
@retry_if_immutable
def disable_content_caching():
lb.content_caching = False
if updated_cc == 'ENABLED':
return scheduler.TaskRunner(enable_content_caching)
else:
return scheduler.TaskRunner(disable_content_caching)
def check_update_complete(self, checkers):
'''Push all checkers to completion in list order.'''
for checker in checkers:
if not checker.started():
checker.start()
if not checker.step():
return False
return True
def handle_delete(self):
if self.resource_id is None:
return
try:
loadbalancer = self.clb.get(self.resource_id)
except NotFound:
pass
else:
if loadbalancer.status != 'DELETED':
loadbalancer.delete()
def _remove_none(self, property_dict):
"""Remove None values that would cause schema validation problems.
These are values that may be initialized to None.
"""
return dict((key, value)
for (key, value) in six.iteritems(property_dict)
if value is not None)
def validate(self):
"""Validate any of the provided params."""
res = super(CloudLoadBalancer, self).validate()
if res:
return res
if self.properties.get(self.HALF_CLOSED):
if not (self.properties[self.PROTOCOL] == 'TCP' or
self.properties[self.PROTOCOL] == 'TCP_CLIENT_FIRST'):
message = (_('The %s property is only available for the TCP '
'or TCP_CLIENT_FIRST protocols')
% self.HALF_CLOSED)
raise exception.StackValidationFailed(message=message)
# health_monitor connect and http types require completely different
# schema
if self.properties.get(self.HEALTH_MONITOR):
prop_val = self.properties[self.HEALTH_MONITOR]
health_monitor = self._remove_none(prop_val)
schema = self._health_monitor_schema
if health_monitor[self.HEALTH_MONITOR_TYPE] == 'CONNECT':
schema = dict((k, v) for k, v in schema.items()
if k in self._HEALTH_MONITOR_CONNECT_KEYS)
properties.Properties(schema,
health_monitor,
function.resolve,
self.name).validate()
# if a vip specifies and id, it can't specify version or type;
# otherwise version and type are required
for vip in self.properties.get(self.VIRTUAL_IPS, []):
has_id = vip.get(self.VIRTUAL_IP_ID) is not None
has_version = vip.get(self.VIRTUAL_IP_IP_VERSION) is not None
has_type = vip.get(self.VIRTUAL_IP_TYPE) is not None
if has_id:
if (has_version or has_type):
message = _("Cannot specify type or version if VIP id is"
" specified.")
raise exception.StackValidationFailed(message=message)
elif not (has_version and has_type):
message = _("Must specify VIP type and version if no id "
"specified.")
raise exception.StackValidationFailed(message=message)
def _public_ip(self, lb):
for ip in lb.virtual_ips:
if ip.type == 'PUBLIC':
return unicode(ip.address)
def _resolve_attribute(self, key):
if self.resource_id:
lb = self.clb.get(self.resource_id)
attribute_function = {
self.PUBLIC_IP: self._public_ip(lb),
self.VIPS: [{"id": vip.id,
"type": vip.type,
"ip_version": vip.ip_version}
for vip in lb.virtual_ips]
}
if key not in attribute_function:
raise exception.InvalidTemplateAttribute(resource=self.name,
key=key)
function = attribute_function[key]
LOG.info(_LI('%(name)s.GetAtt(%(key)s) == %(function)s'),
{'name': self.name, 'key': key, 'function': function})
return function
def resource_mapping():
return {'Rackspace::Cloud::LoadBalancer': CloudLoadBalancer}
def available_resource_mapping():
if PYRAX_INSTALLED:
return resource_mapping()
return {}