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:
parent
f41780fe57
commit
c7e94deadd
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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):
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue