LBaaS: check for associations before deleting health monitor

Need to prohibit health monitor deletion if it has associations with
pools. Given that pools may belong to different lbaas drivers the process
of monitor deletion becomes complex and unreliable since association
deletion may fail on any single driver.

DocImpact

Closes-Bug: #1243129
Change-Id: I27c20e7a5be8433f90569534ecf838e33027cb00
This commit is contained in:
Oleg Bondarev 2013-10-24 16:53:55 +04:00
parent ee0788b9b3
commit 58e83412f5
5 changed files with 80 additions and 64 deletions

View File

@ -770,6 +770,15 @@ class LoadBalancerPluginDb(LoadBalancerPluginBase,
return self._make_health_monitor_dict(monitor_db) return self._make_health_monitor_dict(monitor_db)
def delete_health_monitor(self, context, id): def delete_health_monitor(self, context, id):
"""Delete health monitor object from DB
Raises an error if the monitor has associations with pools
"""
query = self._model_query(context, PoolMonitorAssociation)
has_associations = query.filter_by(monitor_id=id).first()
if has_associations:
raise loadbalancer.HealthMonitorInUse(monitor_id=id)
with context.session.begin(subtransactions=True): with context.session.begin(subtransactions=True):
monitor_db = self._get_resource(context, HealthMonitor, id) monitor_db = self._get_resource(context, HealthMonitor, id)
context.session.delete(monitor_db) context.session.delete(monitor_db)

View File

@ -69,6 +69,11 @@ class PoolInUse(qexception.InUse):
message = _("Pool %(pool_id)s is still in use") message = _("Pool %(pool_id)s is still in use")
class HealthMonitorInUse(qexception.InUse):
message = _("Health monitor %(monitor_id)s still has associations with "
"pools")
class PoolStatsNotFound(qexception.NotFound): class PoolStatsNotFound(qexception.NotFound):
message = _("Statistics of Pool %(pool_id)s could not be found") message = _("Statistics of Pool %(pool_id)s could not be found")

View File

@ -254,19 +254,6 @@ class LoadBalancerPlugin(ldb.LoadBalancerPluginDb,
def _delete_db_health_monitor(self, context, id): def _delete_db_health_monitor(self, context, id):
super(LoadBalancerPlugin, self).delete_health_monitor(context, id) super(LoadBalancerPlugin, self).delete_health_monitor(context, id)
def delete_health_monitor(self, context, id):
with context.session.begin(subtransactions=True):
hm = self.get_health_monitor(context, id)
qry = context.session.query(
ldb.PoolMonitorAssociation
).filter_by(monitor_id=id).join(ldb.Pool)
for assoc in qry:
driver = self._get_driver_for_pool(context, assoc['pool_id'])
driver.delete_pool_health_monitor(context,
hm,
assoc['pool_id'])
super(LoadBalancerPlugin, self).delete_health_monitor(context, id)
def create_pool_health_monitor(self, context, health_monitor, pool_id): def create_pool_health_monitor(self, context, health_monitor, pool_id):
retval = super(LoadBalancerPlugin, self).create_pool_health_monitor( retval = super(LoadBalancerPlugin, self).create_pool_health_monitor(
context, context,

View File

@ -1022,8 +1022,8 @@ class TestLoadBalancer(LoadBalancerPluginDbTestCase):
qry = qry.filter_by(id=monitor['health_monitor']['id']) qry = qry.filter_by(id=monitor['health_monitor']['id'])
self.assertIsNone(qry.first()) self.assertIsNone(qry.first())
def test_delete_healthmonitor_cascade_deletion_of_associations(self): def test_delete_healthmonitor_with_associations_raises(self):
with self.health_monitor(type='HTTP', no_delete=True) as monitor: with self.health_monitor(type='HTTP') as monitor:
with self.pool() as pool: with self.pool() as pool:
data = { data = {
'health_monitor': { 'health_monitor': {
@ -1046,17 +1046,21 @@ class TestLoadBalancer(LoadBalancerPluginDbTestCase):
qry = ctx.session.query(ldb.PoolMonitorAssociation) qry = ctx.session.query(ldb.PoolMonitorAssociation)
qry = qry.filter_by(monitor_id=monitor['health_monitor']['id']) qry = qry.filter_by(monitor_id=monitor['health_monitor']['id'])
self.assertTrue(qry.all()) self.assertTrue(qry.all())
# delete the HealthMonitor instance # try to delete the HealthMonitor instance
req = self.new_delete_request( req = self.new_delete_request(
'health_monitors', 'health_monitors',
monitor['health_monitor']['id'] monitor['health_monitor']['id']
) )
res = req.get_response(self.ext_api) res = req.get_response(self.ext_api)
self.assertEqual(res.status_int, webob.exc.HTTPNoContent.code) self.assertEqual(res.status_int, webob.exc.HTTPConflict.code)
# check if all corresponding Pool associations are deleted
qry = ctx.session.query(ldb.HealthMonitor)
qry = qry.filter_by(id=monitor['health_monitor']['id'])
self.assertIsNotNone(qry.first())
# check if all corresponding Pool associations are not deleted
qry = ctx.session.query(ldb.PoolMonitorAssociation) qry = ctx.session.query(ldb.PoolMonitorAssociation)
qry = qry.filter_by(monitor_id=monitor['health_monitor']['id']) qry = qry.filter_by(monitor_id=monitor['health_monitor']['id'])
self.assertFalse(qry.all()) self.assertTrue(qry.all())
def test_show_healthmonitor(self): def test_show_healthmonitor(self):
with self.health_monitor() as monitor: with self.health_monitor() as monitor:
@ -1363,6 +1367,15 @@ class TestLoadBalancer(LoadBalancerPluginDbTestCase):
pool_updated['pool']['id']) pool_updated['pool']['id'])
# clean up # clean up
# disassociate the health_monitor from the pool first
req = self.new_delete_request(
"pools",
fmt=self.fmt,
id=pool['pool']['id'],
subresource="health_monitors",
sub_id=health_monitor['health_monitor']['id'])
res = req.get_response(self.ext_api)
self.assertEqual(res.status_int, webob.exc.HTTPNoContent.code)
self._delete('health_monitors', self._delete('health_monitors',
health_monitor['health_monitor']['id']) health_monitor['health_monitor']['id'])
self._delete('members', member1['member']['id']) self._delete('members', member1['member']['id'])
@ -1370,10 +1383,10 @@ class TestLoadBalancer(LoadBalancerPluginDbTestCase):
def test_create_pool_health_monitor(self): def test_create_pool_health_monitor(self):
with contextlib.nested( with contextlib.nested(
self.pool(name="pool"),
self.health_monitor(), self.health_monitor(),
self.health_monitor() self.health_monitor(),
) as (pool, health_mon1, health_mon2): self.pool(name="pool")
) as (health_mon1, health_mon2, pool):
res = self.plugin.create_pool_health_monitor( res = self.plugin.create_pool_health_monitor(
context.get_admin_context(), context.get_admin_context(),
health_mon1, pool['pool']['id'] health_mon1, pool['pool']['id']
@ -1401,9 +1414,9 @@ class TestLoadBalancer(LoadBalancerPluginDbTestCase):
with mock.patch.object(self.plugin.drivers['lbaas'], with mock.patch.object(self.plugin.drivers['lbaas'],
'create_pool_health_monitor') as driver_call: 'create_pool_health_monitor') as driver_call:
with contextlib.nested( with contextlib.nested(
self.pool(), self.health_monitor(),
self.health_monitor() self.pool()
) as (pool, hm): ) as (hm, pool):
data = {'health_monitor': { data = {'health_monitor': {
'id': hm['health_monitor']['id'], 'id': hm['health_monitor']['id'],
'tenant_id': self._tenant_id}} 'tenant_id': self._tenant_id}}
@ -1420,10 +1433,10 @@ class TestLoadBalancer(LoadBalancerPluginDbTestCase):
def test_pool_monitor_list_of_pools(self): def test_pool_monitor_list_of_pools(self):
with contextlib.nested( with contextlib.nested(
self.pool(), self.health_monitor(),
self.pool(), self.pool(),
self.health_monitor() self.pool()
) as (p1, p2, hm): ) as (hm, p1, p2):
ctx = context.get_admin_context() ctx = context.get_admin_context()
data = {'health_monitor': { data = {'health_monitor': {
'id': hm['health_monitor']['id'], 'id': hm['health_monitor']['id'],
@ -1455,9 +1468,9 @@ class TestLoadBalancer(LoadBalancerPluginDbTestCase):
def test_create_pool_health_monitor_already_associated(self): def test_create_pool_health_monitor_already_associated(self):
with contextlib.nested( with contextlib.nested(
self.pool(name="pool"),
self.health_monitor(), self.health_monitor(),
) as (pool, hm): self.pool(name="pool")
) as (hm, pool):
res = self.plugin.create_pool_health_monitor( res = self.plugin.create_pool_health_monitor(
context.get_admin_context(), context.get_admin_context(),
hm, pool['pool']['id'] hm, pool['pool']['id']
@ -1501,33 +1514,35 @@ class TestLoadBalancer(LoadBalancerPluginDbTestCase):
self.assertFalse(updated_pool['status_description']) self.assertFalse(updated_pool['status_description'])
def test_update_pool_health_monitor(self): def test_update_pool_health_monitor(self):
with self.pool() as pool: with contextlib.nested(
with self.health_monitor() as hm: self.health_monitor(),
res = self.plugin.create_pool_health_monitor( self.pool(name="pool")
context.get_admin_context(), ) as (hm, pool):
hm, pool['pool']['id']) res = self.plugin.create_pool_health_monitor(
self.assertEqual({'health_monitor': context.get_admin_context(),
[hm['health_monitor']['id']]}, hm, pool['pool']['id'])
res) self.assertEqual({'health_monitor':
[hm['health_monitor']['id']]},
res)
assoc = self.plugin.get_pool_health_monitor( assoc = self.plugin.get_pool_health_monitor(
context.get_admin_context(), context.get_admin_context(),
hm['health_monitor']['id'], hm['health_monitor']['id'],
pool['pool']['id']) pool['pool']['id'])
self.assertEqual(assoc['status'], 'PENDING_CREATE') self.assertEqual(assoc['status'], 'PENDING_CREATE')
self.assertIsNone(assoc['status_description']) self.assertIsNone(assoc['status_description'])
self.plugin.update_pool_health_monitor( self.plugin.update_pool_health_monitor(
context.get_admin_context(), context.get_admin_context(),
hm['health_monitor']['id'], hm['health_monitor']['id'],
pool['pool']['id'], pool['pool']['id'],
'ACTIVE', 'ok') 'ACTIVE', 'ok')
assoc = self.plugin.get_pool_health_monitor( assoc = self.plugin.get_pool_health_monitor(
context.get_admin_context(), context.get_admin_context(),
hm['health_monitor']['id'], hm['health_monitor']['id'],
pool['pool']['id']) pool['pool']['id'])
self.assertEqual(assoc['status'], 'ACTIVE') self.assertEqual(assoc['status'], 'ACTIVE')
self.assertEqual(assoc['status_description'], 'ok') self.assertEqual(assoc['status_description'], 'ok')
def test_check_orphan_pool_associations(self): def test_check_orphan_pool_associations(self):
with contextlib.nested( with contextlib.nested(

View File

@ -282,9 +282,9 @@ class TestLoadBalancerCallbacks(TestLoadBalancerPluginBase):
self.assertEqual([member], logical_config['members']) self.assertEqual([member], logical_config['members'])
def test_get_logical_device_pending_create_health_monitor(self): def test_get_logical_device_pending_create_health_monitor(self):
with self.pool() as pool: with self.health_monitor() as monitor:
with self.vip(pool=pool) as vip: with self.pool() as pool:
with self.health_monitor() as monitor: with self.vip(pool=pool) as vip:
ctx = context.get_admin_context() ctx = context.get_admin_context()
self.plugin_instance.update_status(ctx, ldb.Pool, self.plugin_instance.update_status(ctx, ldb.Pool,
pool['pool']['id'], pool['pool']['id'],
@ -408,9 +408,9 @@ class TestLoadBalancerCallbacks(TestLoadBalancerPluginBase):
def test_update_status_health_monitor(self): def test_update_status_health_monitor(self):
with contextlib.nested( with contextlib.nested(
self.pool(), self.health_monitor(),
self.health_monitor() self.pool()
) as (pool, hm): ) as (hm, pool):
pool_id = pool['pool']['id'] pool_id = pool['pool']['id']
ctx = context.get_admin_context() ctx = context.get_admin_context()
self.plugin_instance.create_pool_health_monitor(ctx, hm, pool_id) self.plugin_instance.create_pool_health_monitor(ctx, hm, pool_id)
@ -671,9 +671,9 @@ class TestLoadBalancerPluginNotificationWrapper(TestLoadBalancerPluginBase):
def test_create_pool_health_monitor(self): def test_create_pool_health_monitor(self):
with contextlib.nested( with contextlib.nested(
self.health_monitor(),
self.pool(), self.pool(),
self.health_monitor() ) as (hm, pool):
) as (pool, hm):
pool_id = pool['pool']['id'] pool_id = pool['pool']['id']
ctx = context.get_admin_context() ctx = context.get_admin_context()
self.plugin_instance.create_pool_health_monitor(ctx, hm, pool_id) self.plugin_instance.create_pool_health_monitor(ctx, hm, pool_id)