Merge "Allow for setting ViP ID"
This commit is contained in:
commit
299d65c032
@ -87,9 +87,9 @@ class CloudLoadBalancer(resource.Resource):
|
||||
)
|
||||
|
||||
_VIRTUAL_IP_KEYS = (
|
||||
VIRTUAL_IP_TYPE, VIRTUAL_IP_IP_VERSION,
|
||||
VIRTUAL_IP_TYPE, VIRTUAL_IP_IP_VERSION, VIRTUAL_IP_ID
|
||||
) = (
|
||||
'type', 'ipVersion',
|
||||
'type', 'ipVersion', 'id'
|
||||
)
|
||||
|
||||
_HEALTH_MONITOR_KEYS = (
|
||||
@ -119,9 +119,9 @@ class CloudLoadBalancer(resource.Resource):
|
||||
)
|
||||
|
||||
ATTRIBUTES = (
|
||||
PUBLIC_IP,
|
||||
PUBLIC_IP, VIPS
|
||||
) = (
|
||||
'PublicIp',
|
||||
'PublicIp', 'virtualIps'
|
||||
)
|
||||
|
||||
ALGORITHMS = ["LEAST_CONNECTIONS", "RANDOM", "ROUND_ROBIN",
|
||||
@ -317,7 +317,9 @@ class CloudLoadBalancer(resource.Resource):
|
||||
schema={
|
||||
VIRTUAL_IP_TYPE: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
required=True,
|
||||
"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']),
|
||||
@ -325,14 +327,25 @@ class CloudLoadBalancer(resource.Resource):
|
||||
),
|
||||
VIRTUAL_IP_IP_VERSION: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
default='IPV6',
|
||||
"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
|
||||
required=True,
|
||||
constraints=[
|
||||
constraints.Length(min=1)
|
||||
]
|
||||
),
|
||||
CONTENT_CACHING: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
@ -380,6 +393,9 @@ class CloudLoadBalancer(resource.Resource):
|
||||
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):
|
||||
@ -392,7 +408,8 @@ class CloudLoadBalancer(resource.Resource):
|
||||
def _setup_properties(self, properties, function):
|
||||
"""Use defined schema properties as kwargs for loadbalancer objects."""
|
||||
if properties and function:
|
||||
return [function(**item_dict) for item_dict in properties]
|
||||
return [function(**self._remove_none(item_dict))
|
||||
for item_dict in properties]
|
||||
elif function:
|
||||
return [function()]
|
||||
|
||||
@ -479,6 +496,7 @@ class CloudLoadBalancer(resource.Resource):
|
||||
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) = \
|
||||
@ -582,7 +600,7 @@ class CloudLoadBalancer(resource.Resource):
|
||||
"""
|
||||
return dict((key, value)
|
||||
for (key, value) in six.iteritems(property_dict)
|
||||
if value)
|
||||
if value is not None)
|
||||
|
||||
def validate(self):
|
||||
"""Validate any of the provided params."""
|
||||
@ -593,38 +611,56 @@ class CloudLoadBalancer(resource.Resource):
|
||||
if self.properties.get(self.HALF_CLOSED):
|
||||
if not (self.properties[self.PROTOCOL] == 'TCP' or
|
||||
self.properties[self.PROTOCOL] == 'TCP_CLIENT_FIRST'):
|
||||
return {'Error':
|
||||
'The %s property is only available for the TCP or '
|
||||
'TCP_CLIENT_FIRST protocols' % self.HALF_CLOSED}
|
||||
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):
|
||||
health_monitor = \
|
||||
self._remove_none(self.properties[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)
|
||||
try:
|
||||
Properties(schema,
|
||||
health_monitor,
|
||||
function.resolve,
|
||||
self.name).validate()
|
||||
except exception.StackValidationFailed as svf:
|
||||
return {'Error': str(svf)}
|
||||
|
||||
def _public_ip(self):
|
||||
#TODO(andrew-plunk) return list here and let caller choose ip
|
||||
for ip in self.clb.get(self.resource_id).virtual_ips:
|
||||
# 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 ip.address
|
||||
return unicode(ip.address)
|
||||
|
||||
def _resolve_attribute(self, key):
|
||||
if self.resource_id:
|
||||
lb = self.clb.get(self.resource_id)
|
||||
attribute_function = {
|
||||
'PublicIp': self._public_ip()
|
||||
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,
|
||||
@ -632,7 +668,7 @@ class CloudLoadBalancer(resource.Resource):
|
||||
function = attribute_function[key]
|
||||
LOG.info(_('%(name)s.GetAtt(%(key)s) == %(function)s'),
|
||||
{'name': self.name, 'key': key, 'function': function})
|
||||
return unicode(function)
|
||||
return function
|
||||
|
||||
|
||||
def resource_mapping():
|
||||
|
@ -18,7 +18,7 @@ import mock
|
||||
import six
|
||||
import uuid
|
||||
|
||||
from heat.common.exception import StackValidationFailed
|
||||
from heat.common import exception
|
||||
from heat.common import template_format
|
||||
from heat.engine import resource
|
||||
from heat.engine import rsrc_defn
|
||||
@ -307,6 +307,34 @@ class LoadBalancerTest(common.HeatTestCase):
|
||||
scheduler.TaskRunner(rsrc.create)()
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_validate_vip(self):
|
||||
snippet = {
|
||||
"nodes": [],
|
||||
"protocol": 'HTTP',
|
||||
"port": 80,
|
||||
"halfClosed": None,
|
||||
"algorithm": u'LEAST_CONNECTIONS',
|
||||
"virtualIps": [{"id": "1234"}]
|
||||
}
|
||||
stack = mock.Mock()
|
||||
stack.db_resource_get.return_value = None
|
||||
# happy path
|
||||
resdef = rsrc_defn.ResourceDefinition("testvip",
|
||||
lb.CloudLoadBalancer,
|
||||
properties=snippet)
|
||||
rsrc = lb.CloudLoadBalancer("testvip", resdef, stack)
|
||||
self.assertIsNone(rsrc.validate())
|
||||
# make sure the vip id prop is exclusive
|
||||
snippet["virtualIps"][0]["type"] = "PUBLIC"
|
||||
exc = self.assertRaises(exception.StackValidationFailed,
|
||||
rsrc.validate)
|
||||
self.assertIn("Cannot specify type or version", str(exc))
|
||||
# make sure you have to specify type and version if no id
|
||||
snippet["virtualIps"] = [{}]
|
||||
exc = self.assertRaises(exception.StackValidationFailed,
|
||||
rsrc.validate)
|
||||
self.assertIn("Must specify VIP type and version", str(exc))
|
||||
|
||||
def test_validate_half_closed(self):
|
||||
#test failure (invalid protocol)
|
||||
template = self._set_template(self.lb_template, halfClosed=True)
|
||||
@ -314,11 +342,10 @@ class LoadBalancerTest(common.HeatTestCase):
|
||||
rsrc, fake_loadbalancer = self._mock_loadbalancer(template,
|
||||
self.lb_name,
|
||||
expected)
|
||||
self.assertEqual(
|
||||
{'Error':
|
||||
'The halfClosed property is only available for the '
|
||||
'TCP or TCP_CLIENT_FIRST protocols'},
|
||||
rsrc.validate())
|
||||
exc = self.assertRaises(exception.StackValidationFailed,
|
||||
rsrc.validate)
|
||||
self.assertIn('The halfClosed property is only available for the TCP'
|
||||
' or TCP_CLIENT_FIRST protocols', str(exc))
|
||||
|
||||
#test TCP protocol
|
||||
template = self._set_template(template, protocol='TCP')
|
||||
@ -366,8 +393,9 @@ class LoadBalancerTest(common.HeatTestCase):
|
||||
rsrc, fake_loadbalancer = self._mock_loadbalancer(template,
|
||||
self.lb_name,
|
||||
expected)
|
||||
self.assertEqual({'Error': 'Unknown Property bodyRegex'},
|
||||
rsrc.validate())
|
||||
exc = self.assertRaises(exception.StackValidationFailed,
|
||||
rsrc.validate)
|
||||
self.assertIn('Unknown Property bodyRegex', str(exc))
|
||||
|
||||
#test http fields
|
||||
health_monitor['type'] = 'HTTP'
|
||||
@ -401,7 +429,8 @@ class LoadBalancerTest(common.HeatTestCase):
|
||||
self.lb_name,
|
||||
expected)
|
||||
|
||||
exc = self.assertRaises(StackValidationFailed, rsrc.validate)
|
||||
exc = self.assertRaises(exception.StackValidationFailed,
|
||||
rsrc.validate)
|
||||
self.assertIn("Property certificate not assigned", six.text_type(exc))
|
||||
|
||||
ssl_termination['certificate'] = 'dfaewfwef'
|
||||
|
Loading…
Reference in New Issue
Block a user