Add load balancer PoolMember resource

This adds a new resource to manage the relationship between a load
balancing pool and an address, allowing more options like setting the
weight of the member.

Change-Id: Ic8a55ff8b8b00c7a4e3184b3028d5bf0cdd5c916
Implements: blueprint loadbalancer-pool-members
This commit is contained in:
Thomas Herve 2013-12-06 14:40:47 +01:00
parent f41780fe57
commit c7e94deadd
2 changed files with 212 additions and 0 deletions

View File

@ -16,6 +16,8 @@
from heat.common import exception from heat.common import exception
from heat.db import api as db_api from heat.db import api as db_api
from heat.engine import clients from heat.engine import clients
from heat.engine import constraints
from heat.engine import properties
from heat.engine import resource from heat.engine import resource
from heat.engine import scheduler from heat.engine import scheduler
from heat.engine.resources import nova_utils from heat.engine.resources import nova_utils
@ -304,6 +306,92 @@ class Pool(neutron.NeutronResource):
self._delete_pool() self._delete_pool()
class PoolMember(neutron.NeutronResource):
"""
A resource to handle load balancer members.
"""
properties_schema = {
'pool_id': properties.Schema(
properties.Schema.STRING,
_('The ID of the load balancing pool.'),
required=True,
update_allowed=True),
'address': properties.Schema(
properties.Schema.STRING,
_('IP address of the pool member on the pool network.'),
required=True),
'protocol_port': properties.Schema(
properties.Schema.INTEGER,
_('TCP port on which the pool member listens for requests or '
'connections.'),
required=True,
constraints=[constraints.Range(0, 65535)]),
'weight': properties.Schema(
properties.Schema.INTEGER,
_('Weight of pool member in the pool (default to 1).'),
update_allowed=True,
constraints=[constraints.Range(0, 256)]),
'admin_state_up': properties.Schema(
properties.Schema.BOOLEAN,
_('The administrative state of the pool member.'),
default=True)
}
attributes_schema = {
'admin_state_up': _('The administrative state of this pool '
'member.'),
'tenant_id': _('Tenant owning the pool member.'),
'weight': _('Weight of the pool member in the pool.'),
'address': _('IP address of the pool member.'),
'pool_id': _('The ID of the load balancing pool.'),
'protocol_port': _('TCP port on which the pool member listens for'
'requests or connections.'),
'show': _('All attributes.'),
}
update_allowed_keys = ('Properties',)
def handle_create(self):
pool = self.properties['pool_id']
client = self.neutron()
protocol_port = self.properties['protocol_port']
address = self.properties['address']
admin_state_up = self.properties['admin_state_up']
weight = self.properties.get('weight')
params = {
'pool_id': pool,
'address': address,
'protocol_port': protocol_port,
'admin_state_up': admin_state_up
}
if weight is not None:
params['weight'] = weight
member = client.create_member({'member': params})['member']
self.resource_id_set(member['id'])
def _show_resource(self):
return self.neutron().show_member(self.resource_id)['member']
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
if prop_diff:
self.neutron().update_member(
self.resource_id, {'member': prop_diff})
def handle_delete(self):
client = self.neutron()
try:
client.delete_member(self.resource_id)
except NeutronClientException as ex:
if ex.status_code != 404:
raise ex
else:
return scheduler.TaskRunner(self._confirm_delete)()
class LoadBalancer(resource.Resource): class LoadBalancer(resource.Resource):
""" """
A resource to link a neutron pool with servers. A resource to link a neutron pool with servers.
@ -387,5 +475,6 @@ def resource_mapping():
return { return {
'OS::Neutron::HealthMonitor': HealthMonitor, 'OS::Neutron::HealthMonitor': HealthMonitor,
'OS::Neutron::Pool': Pool, 'OS::Neutron::Pool': Pool,
'OS::Neutron::PoolMember': PoolMember,
'OS::Neutron::LoadBalancer': LoadBalancer, 'OS::Neutron::LoadBalancer': LoadBalancer,
} }

View File

@ -69,6 +69,23 @@ pool_template = '''
} }
''' '''
member_template = '''
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "Template to test load balancer member",
"Resources" : {
"member": {
"Type": "OS::Neutron::PoolMember",
"Properties": {
"protocol_port": 8080,
"pool_id": "pool123",
"address": "1.2.3.4"
}
}
}
}
'''
lb_template = ''' lb_template = '''
{ {
"AWSTemplateFormatVersion" : "2010-09-09", "AWSTemplateFormatVersion" : "2010-09-09",
@ -538,6 +555,112 @@ class PoolTest(HeatTestCase):
self.m.VerifyAll() self.m.VerifyAll()
@skipIf(neutronclient is None, 'neutronclient unavailable')
class PoolMemberTest(HeatTestCase):
def setUp(self):
super(PoolMemberTest, self).setUp()
self.fc = nova_fakes.FakeClient()
self.m.StubOutWithMock(neutronclient.Client, 'create_member')
self.m.StubOutWithMock(neutronclient.Client, 'delete_member')
self.m.StubOutWithMock(neutronclient.Client, 'update_member')
self.m.StubOutWithMock(neutronclient.Client, 'show_member')
self.m.StubOutWithMock(clients.OpenStackClients, 'keystone')
utils.setup_dummy_db()
def create_member(self):
clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient())
neutronclient.Client.create_member({
'member': {
'pool_id': 'pool123', 'protocol_port': 8080,
'address': '1.2.3.4', 'admin_state_up': True}}
).AndReturn({'member': {'id': 'member5678'}})
snippet = template_format.parse(member_template)
stack = utils.parse_stack(snippet)
return loadbalancer.PoolMember(
'member', snippet['Resources']['member'], stack)
def test_create(self):
rsrc = self.create_member()
self.m.ReplayAll()
scheduler.TaskRunner(rsrc.create)()
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
self.assertEqual('member5678', rsrc.resource_id)
self.m.VerifyAll()
def test_create_optional_parameters(self):
clients.OpenStackClients.keystone().AndReturn(
fakes.FakeKeystoneClient())
neutronclient.Client.create_member({
'member': {
'pool_id': 'pool123', 'protocol_port': 8080,
'weight': 100, 'admin_state_up': False,
'address': '1.2.3.4'}}
).AndReturn({'member': {'id': 'member5678'}})
snippet = template_format.parse(member_template)
snippet['Resources']['member']['Properties']['admin_state_up'] = False
snippet['Resources']['member']['Properties']['weight'] = 100
stack = utils.parse_stack(snippet)
rsrc = loadbalancer.PoolMember(
'member', snippet['Resources']['member'], stack)
self.m.ReplayAll()
scheduler.TaskRunner(rsrc.create)()
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
self.assertEqual('member5678', rsrc.resource_id)
self.m.VerifyAll()
def test_attribute(self):
rsrc = self.create_member()
neutronclient.Client.show_member('member5678').MultipleTimes(
).AndReturn(
{'member': {'admin_state_up': True, 'weight': 5}})
self.m.ReplayAll()
scheduler.TaskRunner(rsrc.create)()
self.assertEqual(True, rsrc.FnGetAtt('admin_state_up'))
self.assertEqual(5, rsrc.FnGetAtt('weight'))
self.m.VerifyAll()
def test_update(self):
rsrc = self.create_member()
neutronclient.Client.update_member(
'member5678', {'member': {'pool_id': 'pool456'}})
self.m.ReplayAll()
scheduler.TaskRunner(rsrc.create)()
update_template = copy.deepcopy(rsrc.t)
update_template['Properties']['pool_id'] = 'pool456'
scheduler.TaskRunner(rsrc.update, update_template)()
self.m.VerifyAll()
def test_delete(self):
rsrc = self.create_member()
neutronclient.Client.delete_member(u'member5678')
neutronclient.Client.show_member(u'member5678').AndRaise(
loadbalancer.NeutronClientException(status_code=404))
self.m.ReplayAll()
scheduler.TaskRunner(rsrc.create)()
scheduler.TaskRunner(rsrc.delete)()
self.assertEqual((rsrc.DELETE, rsrc.COMPLETE), rsrc.state)
self.m.VerifyAll()
def test_delete_missing_member(self):
rsrc = self.create_member()
neutronclient.Client.delete_member(u'member5678').AndRaise(
loadbalancer.NeutronClientException(status_code=404))
self.m.ReplayAll()
scheduler.TaskRunner(rsrc.create)()
scheduler.TaskRunner(rsrc.delete)()
self.assertEqual((rsrc.DELETE, rsrc.COMPLETE), rsrc.state)
self.m.VerifyAll()
@skipIf(neutronclient is None, 'neutronclient unavailable') @skipIf(neutronclient is None, 'neutronclient unavailable')
class LoadBalancerTest(HeatTestCase): class LoadBalancerTest(HeatTestCase):