Refactor CLB to work with groups

The Rackspace CLB resource couldn't be used with resource groups
because nodes were individual IPs or references. This change
allows a node to have a list of ips for a given port, status,
etc so that references to resource groups can easily be added
to Rackspace CLB instances. Ref-based nodes were removed as they
aren't used.

Change-Id: Ia26a012758fb1cd0470c1c0edc27e61e77bd8f0a
This commit is contained in:
Randall Burt 2014-01-08 19:01:28 -06:00
parent bd2d1b3724
commit 9509ce0960
2 changed files with 58 additions and 74 deletions

View File

@ -10,16 +10,6 @@
# 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 heat.common import exception
from heat.engine import constraints
from heat.engine import properties
from heat.engine.properties import Properties
from heat.engine import resource
from heat.engine import scheduler
from heat.openstack.common.gettextutils import _
from heat.openstack.common import log as logging
try:
from pyrax.exceptions import NotFound
PYRAX_INSTALLED = True
@ -30,6 +20,18 @@ except ImportError:
PYRAX_INSTALLED = False
import copy
import itertools
from heat.openstack.common import log as logging
from heat.openstack.common.gettextutils import _
from heat.engine import scheduler
from heat.engine import constraints
from heat.engine import properties
from heat.engine import resource
from heat.engine.properties import Properties
from heat.common import exception
logger = logging.getLogger(__name__)
@ -52,10 +54,10 @@ class CloudLoadBalancer(resource.Resource):
)
_NODE_KEYS = (
NODE_ADDRESS, NODE_REF, NODE_PORT, NODE_CONDITION, NODE_TYPE,
NODE_ADDRESSES, NODE_PORT, NODE_CONDITION, NODE_TYPE,
NODE_WEIGHT,
) = (
'address', 'ref', 'port', 'condition', 'type',
'addresses', 'port', 'condition', 'type',
'weight',
)
@ -161,11 +163,15 @@ class CloudLoadBalancer(resource.Resource):
schema=properties.Schema(
properties.Schema.MAP,
schema={
NODE_ADDRESS: properties.Schema(
properties.Schema.STRING
),
NODE_REF: properties.Schema(
properties.Schema.STRING
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,
@ -174,7 +180,6 @@ class CloudLoadBalancer(resource.Resource):
NODE_CONDITION: properties.Schema(
properties.Schema.STRING,
default='ENABLED',
required=True,
constraints=[
constraints.AllowedValues(['ENABLED',
'DISABLED']),
@ -325,7 +330,6 @@ class CloudLoadBalancer(resource.Resource):
schema={
SSL_TERMINATION_SECURE_PORT: properties.Schema(
properties.Schema.NUMBER,
required=True,
default=443
),
SSL_TERMINATION_PRIVATEKEY: properties.Schema(
@ -434,16 +438,22 @@ class CloudLoadBalancer(resource.Resource):
yield
loadbalancer.content_caching = enabled
def handle_create(self):
node_list = []
for node in self.properties[self.NODES]:
# resolve references to stack resource IP's
if node.get(self.NODE_REF):
resource = self.stack.resource_by_refid(node[self.NODE_REF])
node[self.NODE_ADDRESS] = resource.FnGetAtt('PublicIp')
del node[self.NODE_REF]
node_list.append(node)
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)
@ -488,21 +498,16 @@ class CloudLoadBalancer(resource.Resource):
loadbalancer = self.clb.get(self.resource_id)
if self.NODES in prop_diff:
current_nodes = loadbalancer.nodes
diff_nodes = self._process_nodes(prop_diff[self.NODES])
#Loadbalancers can be uniquely identified by address and port.
#Old is a dict of all nodes the loadbalancer currently knows about.
for node in prop_diff[self.NODES]:
# resolve references to stack resource IP's
if node.get(self.NODE_REF):
res = self.stack.resource_by_refid(node[self.NODE_REF])
node[self.NODE_ADDRESS] = res.FnGetAtt('PublicIp')
del node[self.NODE_REF]
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[self.NODE_ADDRESS],
new = dict(("%s%s" % (node["address"],
node[self.NODE_PORT]), node)
for node in prop_diff[self.NODES])
for node in diff_nodes)
old_set = set(old.keys())
new_set = set(new.keys())

View File

@ -165,7 +165,8 @@ class LoadBalancerTest(HeatTestCase):
"Type": "Rackspace::Cloud::LoadBalancer",
"Properties": {
"name": "test-clb",
"nodes": [{"address": "166.78.103.141", "port": 80,
"nodes": [{"addresses": ["166.78.103.141"],
"port": 80,
"condition": "ENABLED"}],
"protocol": "HTTP",
"port": 80,
@ -255,6 +256,19 @@ class LoadBalancerTest(HeatTestCase):
expected[k] = v
return expected
def test_process_node(self):
nodes = [{'addresses': ['1234'], 'port': 80, 'enabled': True},
{'addresses': ['4567', '8901', '8903'], 'port': 80,
'enabled': True}]
rsrc, fake_loadbalancer = self._mock_loadbalancer(self.lb_template,
self.lb_name,
self.expected_body)
expected_nodes = [{'address': '1234', 'port': 80, 'enabled': True},
{'address': '4567', 'port': 80, 'enabled': True},
{'address': '8901', 'port': 80, 'enabled': True},
{'address': '8903', 'port': 80, 'enabled': True}]
self.assertEqual(expected_nodes, list(rsrc._process_nodes(nodes)))
def test_nodeless(self):
"""It's possible to create a LoadBalancer resource with no nodes."""
template = self._set_template(self.lb_template,
@ -472,47 +486,12 @@ class LoadBalancerTest(HeatTestCase):
def test_post_creation_content_caching(self):
template = self._set_template(self.lb_template,
contentCaching='ENABLED')
rsrc, fake_loadbalancer = self._mock_loadbalancer(template,
self.lb_name,
self.expected_body)
rsrc = self._mock_loadbalancer(template, self.lb_name,
self.expected_body)[0]
self.m.ReplayAll()
scheduler.TaskRunner(rsrc.create)()
self.m.VerifyAll()
def test_update_add_node_by_ref(self):
added_node = {'nodes': [
{"address": "166.78.103.141", "port": 80, "condition": "ENABLED"},
{"ref": "TEST_NODE_REF", "port": 80, "condition": "ENABLED"}]}
expected_ip = '172.168.1.4'
rsrc, fake_loadbalancer = self._mock_loadbalancer(self.lb_template,
self.lb_name,
self.expected_body)
fake_loadbalancer.nodes = self.expected_body['nodes']
self.m.ReplayAll()
scheduler.TaskRunner(rsrc.create)()
self.m.VerifyAll()
self.m.StubOutWithMock(rsrc.clb, 'get')
rsrc.clb.get(rsrc.resource_id).AndReturn(fake_loadbalancer)
self.m.StubOutWithMock(rsrc.stack, 'resource_by_refid')
class FakeFn(object):
def FnGetAtt(self, attr):
return expected_ip
rsrc.stack.resource_by_refid('TEST_NODE_REF').AndReturn(FakeFn())
self.m.StubOutWithMock(fake_loadbalancer, 'add_nodes')
fake_loadbalancer.add_nodes([
fake_loadbalancer.Node(address=expected_ip,
port=80,
condition='ENABLED')])
self.m.ReplayAll()
rsrc.handle_update({}, {}, added_node)
self.m.VerifyAll()
def test_update_add_node_by_address(self):
expected_ip = '172.168.1.4'
added_node = {'nodes': [