Add Health Monitor Max Retries Down

Add a "fall threshold" to health monitors such that sequential
checks will be required before reporting an unhealthy state.

Change-Id: Ifcdad59cfee7769f4fc3dce93c4eba45bca9f4c2
This commit is contained in:
Trevor Vardeman 2016-04-14 16:14:22 -05:00
parent b5b46ac4c3
commit e22d2009a0
12 changed files with 181 additions and 20 deletions

View File

@ -113,6 +113,7 @@ class HealthMonitorV2(model_base.BASEV2, models_v2.HasId, models_v2.HasTenant):
provisioning_status = sa.Column(sa.String(16), nullable=False)
admin_state_up = sa.Column(sa.Boolean(), nullable=False)
name = sa.Column(sa.String(attr.NAME_MAX_LEN), nullable=True)
max_retries_down = sa.Column(sa.Integer, nullable=True)
@property
def root_loadbalancer(self):

View File

@ -1 +1 @@
62deca5010cd
844352f9fe6f

View File

@ -0,0 +1,34 @@
# Copyright 2016 Rackspace
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
"""Add healthmonitor max retries down
Revision ID: 844352f9fe6f
Revises: 62deca5010cd
Create Date: 2016-04-21 15:32:05.647920
"""
# revision identifiers, used by Alembic.
revision = '844352f9fe6f'
down_revision = '62deca5010cd'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.add_column('lbaas_healthmonitors', sa.Column(
u'max_retries_down', sa.Integer(), nullable=True))

View File

@ -99,6 +99,11 @@ class BaseLoadBalancerManager(driver_mixins.BaseRefreshMixin,
"""
return False
@property
def allows_healthmonitor_thresholds(self):
"""Does this driver support thresholds for health monitors"""
return False
@property
def allocates_vip(self):
"""Does this driver need to allocate its own virtual IPs"""

View File

@ -63,6 +63,10 @@ class LoggingNoopLoadBalancerManager(LoggingNoopCommonManager,
def allows_create_graph(self):
return True
@property
def allows_healthmonitor_thresholds(self):
return True
@property
def allocates_vip(self):
LOG.debug('allocates_vip queried')

View File

@ -196,6 +196,10 @@ class LoadBalancerManager(driver_base.BaseLoadBalancerManager):
def allows_create_graph(self):
return True
@property
def allows_healthmonitor_thresholds(self):
return True
@property
def allocates_vip(self):
return cfg.CONF.octavia.allocates_vip
@ -447,7 +451,7 @@ class HealthMonitorManager(driver_base.BaseHealthMonitorManager):
'delay': hm.delay,
'timeout': hm.timeout,
'rise_threshold': hm.max_retries,
'fall_threshold': hm.max_retries,
'fall_threshold': hm.max_retries_down,
'http_method': hm.http_method,
'url_path': hm.url_path,
'expected_codes': hm.expected_codes,

View File

@ -0,0 +1,59 @@
# Copyright 2016 Rackspace
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# 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 neutron.api import extensions
from neutron.api.v2 import attributes as attr
EXTENDED_ATTRIBUTES_2_0 = {
'healthmonitors': {
'max_retries_down': {
'allow_post': True, 'allow_put': True,
'default': 3, 'validate': {'type:range': [1, 10]},
'convert_to': attr.convert_to_int, 'is_visible': True
}
}
}
class Healthmonitor_max_retries_down(extensions.ExtensionDescriptor):
@classmethod
def get_name(cls):
return "Add a fall threshold to health monitor"
@classmethod
def get_alias(cls):
return "hm_max_retries_down"
@classmethod
def get_description(cls):
return "Add a fall threshold to health monitor"
@classmethod
def get_namespace(cls):
return "http://wiki.openstack.org/neutron/LBaaS/API_2.0"
@classmethod
def get_updated(cls):
return "2016-04-19T16:00:00-00:00"
def get_required_extensions(self):
return ["lbaasv2"]
def get_extended_resources(self, version):
if version == "2.0":
return EXTENDED_ATTRIBUTES_2_0
else:
return {}

View File

@ -316,12 +316,14 @@ class HealthMonitor(BaseDataModel):
fields = ['id', 'tenant_id', 'type', 'delay', 'timeout', 'max_retries',
'http_method', 'url_path', 'expected_codes',
'provisioning_status', 'admin_state_up', 'pool', 'name']
'provisioning_status', 'admin_state_up', 'pool', 'name',
'max_retries_down']
def __init__(self, id=None, tenant_id=None, type=None, delay=None,
timeout=None, max_retries=None, http_method=None,
url_path=None, expected_codes=None, provisioning_status=None,
admin_state_up=None, pool=None, name=None):
admin_state_up=None, pool=None, name=None,
max_retries_down=None):
self.id = id
self.tenant_id = tenant_id
self.type = type
@ -335,6 +337,7 @@ class HealthMonitor(BaseDataModel):
self.admin_state_up = admin_state_up
self.pool = pool
self.name = name
self.max_retries_down = max_retries_down
def attached_to_loadbalancer(self):
return bool(self.pool and self.pool.loadbalancer)

View File

@ -391,7 +391,8 @@ class LoadBalancerPluginv2(loadbalancerv2.LoadBalancerPluginBaseV2):
"l7",
"lbaas_agent_schedulerv2",
"service-type",
"lb-graph"]
"lb-graph",
"hm_max_retries_down"]
path_prefix = loadbalancerv2.LOADBALANCERV2_PREFIX
agent_notifiers = (
@ -476,7 +477,8 @@ class LoadBalancerPluginv2(loadbalancerv2.LoadBalancerPluginBaseV2):
if not self.default_provider:
raise pconf.DefaultServiceProviderNotFound(
service_type=constants.LOADBALANCER)
del entity['provider']
if entity.get('provider'):
del entity['provider']
return self.default_provider
def _call_driver_operation(self, context, driver_method, db_entity,

View File

@ -39,6 +39,7 @@ from neutron_lbaas.db.loadbalancer import loadbalancer_dbv2
from neutron_lbaas.db.loadbalancer import models
from neutron_lbaas.drivers.logging_noop import driver as noop_driver
import neutron_lbaas.extensions
from neutron_lbaas.extensions import healthmonitor_max_retries_down
from neutron_lbaas.extensions import l7
from neutron_lbaas.extensions import lb_graph
from neutron_lbaas.extensions import loadbalancerv2
@ -66,6 +67,8 @@ class LbaasTestMixin(object):
resource_keys = list(loadbalancerv2.RESOURCE_ATTRIBUTE_MAP.keys())
resource_keys.extend(l7.RESOURCE_ATTRIBUTE_MAP.keys())
resource_keys.extend(lb_graph.RESOURCE_ATTRIBUTE_MAP.keys())
resource_keys.extend(healthmonitor_max_retries_down.
EXTENDED_ATTRIBUTES_2_0.keys())
resource_prefix_map = dict(
(k, loadbalancerv2.LOADBALANCERV2_PREFIX)
for k in resource_keys)
@ -186,7 +189,7 @@ class LbaasTestMixin(object):
def _get_healthmonitor_optional_args(self):
return ('weight', 'admin_state_up', 'expected_codes', 'url_path',
'http_method', 'name')
'http_method', 'name', 'max_retries_down')
def _create_healthmonitor(self, fmt, pool_id, type, delay, timeout,
max_retries, expected_res_status=None, **kwargs):
@ -428,7 +431,7 @@ class LbaasTestMixin(object):
@contextlib.contextmanager
def healthmonitor(self, fmt=None, pool_id='pool1id', type='TCP', delay=1,
timeout=1, max_retries=1, no_delete=False, **kwargs):
timeout=1, max_retries=2, no_delete=False, **kwargs):
if not fmt:
fmt = self.fmt
@ -532,6 +535,8 @@ class ExtendedPluginAwareExtensionManager(object):
extensions_list.append(l7)
if 'lb-graph' in self.extension_aliases:
extensions_list.append(lb_graph)
if 'hm_max_retries_down' in self.extension_aliases:
extensions_list.append(healthmonitor_max_retries_down)
for extension in extensions_list:
if 'RESOURCE_ATTRIBUTE_MAP' in extension.__dict__:
loadbalancerv2.RESOURCE_ATTRIBUTE_MAP.update(
@ -1190,7 +1195,8 @@ class TestLoadBalancerGraphCreation(LbaasPluginDbTestCase):
'delay': 1,
'timeout': 1,
'max_retries': 1,
'tenant_id': self._tenant_id
'tenant_id': self._tenant_id,
'max_retries_down': 1
}
expected_hm = {
'http_method': 'GET',
@ -3541,14 +3547,14 @@ class HealthMonitorTestBase(MemberTestBase):
return resp, body
class LbaasHealthMonitorTests(HealthMonitorTestBase):
class TestLbaasHealthMonitorTests(HealthMonitorTestBase):
def test_create_healthmonitor(self, **extras):
expected = {
'type': 'HTTP',
'delay': 1,
'timeout': 1,
'max_retries': 1,
'max_retries': 2,
'http_method': 'GET',
'url_path': '/',
'expected_codes': '200',
@ -3585,7 +3591,7 @@ class LbaasHealthMonitorTests(HealthMonitorTestBase):
'type': 'HTTP',
'delay': 1,
'timeout': 1,
'max_retries': 1,
'max_retries': 2,
'http_method': 'GET',
'url_path': '/',
'expected_codes': '200',
@ -3593,7 +3599,6 @@ class LbaasHealthMonitorTests(HealthMonitorTestBase):
'tenant_id': self._tenant_id,
'pools': [{'id': self.pool_id}],
'name': 'monitor1'
}
expected.update(extras)
@ -3660,7 +3665,7 @@ class LbaasHealthMonitorTests(HealthMonitorTestBase):
'type': 'TCP',
'delay': 1,
'timeout': 1,
'max_retries': 1,
'max_retries': 2,
'admin_state_up': True,
'tenant_id': self._tenant_id,
'pools': [{'id': self.pool_id}],
@ -3689,12 +3694,11 @@ class LbaasHealthMonitorTests(HealthMonitorTestBase):
'type': 'TCP',
'delay': 1,
'timeout': 1,
'max_retries': 1,
'max_retries': 2,
'admin_state_up': True,
'tenant_id': self._tenant_id,
'pools': [{'id': self.pool_id}],
'name': 'monitor1'
}
expected.update(extras)
@ -3922,6 +3926,45 @@ class LbaasHealthMonitorTests(HealthMonitorTestBase):
resp, body = self._create_healthmonitor_api(data)
self.assertEqual(webob.exc.HTTPBadRequest.code, resp.status_int)
def test_create_health_monitor_with_max_retries_down(self, **extras):
expected = {
'type': 'HTTP',
'delay': 1,
'timeout': 1,
'max_retries': 2,
'http_method': 'GET',
'url_path': '/',
'expected_codes': '200',
'admin_state_up': True,
'tenant_id': self._tenant_id,
'pools': [{'id': self.pool_id}],
'name': 'monitor1',
'max_retries_down': 1
}
expected.update(extras)
with self.healthmonitor(pool_id=self.pool_id, type='HTTP',
name='monitor1', max_retries_down=1,
**extras) as healthmonitor:
hm_id = healthmonitor['healthmonitor'].get('id')
self.assertTrue(hm_id)
actual = {}
for k, v in healthmonitor['healthmonitor'].items():
if k in expected:
actual[k] = v
self.assertEqual(expected, actual)
self._validate_statuses(self.lb_id, self.listener_id,
pool_id=self.pool_id,
hm_id=hm_id)
_, pool = self._get_pool_api(self.pool_id)
self.assertEqual(
{'type': lb_const.SESSION_PERSISTENCE_HTTP_COOKIE,
'cookie_name': None},
pool['pool'].get('session_persistence'))
return healthmonitor
def test_only_one_healthmonitor_per_pool(self):
with self.healthmonitor(pool_id=self.pool_id):
data = {'healthmonitor': {'type': lb_const.HEALTH_MONITOR_TCP,
@ -3938,14 +3981,15 @@ class LbaasHealthMonitorTests(HealthMonitorTestBase):
'type': 'HTTP',
'delay': 1,
'timeout': 1,
'max_retries': 1,
'max_retries': 2,
'http_method': 'GET',
'url_path': '/',
'expected_codes': '200',
'admin_state_up': True,
'tenant_id': self._tenant_id,
'pools': [{'id': self.pool_id}],
'name': 'monitor1'
'name': 'monitor1',
'max_retries_down': 3
}
with self.healthmonitor(pool_id=self.pool_id, type='HTTP',
@ -3960,7 +4004,7 @@ class LbaasHealthMonitorTests(HealthMonitorTestBase):
'type': 'HTTP',
'delay': 1,
'timeout': 1,
'max_retries': 1,
'max_retries': 2,
'http_method': 'GET',
'url_path': '/',
'expected_codes': '200',
@ -3968,6 +4012,7 @@ class LbaasHealthMonitorTests(HealthMonitorTestBase):
'tenant_id': self._tenant_id,
'pools': [{'id': self.pool_id}],
'name': '',
'max_retries_down': 3
}
with self.healthmonitor(pool_id=self.pool_id,

View File

@ -405,7 +405,7 @@ class TestOctaviaDriver(BaseOctaviaDriverTest):
'delay': hm.delay,
'timeout': hm.timeout,
'rise_threshold': hm.max_retries,
'fall_threshold': hm.max_retries,
'fall_threshold': hm.max_retries_down,
'http_method': hm.http_method,
'url_path': hm.url_path,
'expected_codes': hm.expected_codes,

View File

@ -22,6 +22,7 @@ from neutron_lib import constants as n_constants
from oslo_utils import uuidutils
from webob import exc
from neutron_lbaas.extensions import healthmonitor_max_retries_down as hm_down
from neutron_lbaas.extensions import loadbalancer
from neutron_lbaas.extensions import loadbalancerv2
from neutron_lbaas.extensions import sharedpools
@ -495,6 +496,8 @@ class TestLoadBalancerExtensionV2TestCase(base.ExtensionTestCase):
resource_map = loadbalancerv2.RESOURCE_ATTRIBUTE_MAP.copy()
for k in sharedpools.EXTENDED_ATTRIBUTES_2_0.keys():
resource_map[k].update(sharedpools.EXTENDED_ATTRIBUTES_2_0[k])
for k in hm_down.EXTENDED_ATTRIBUTES_2_0.keys():
resource_map[k].update(hm_down.EXTENDED_ATTRIBUTES_2_0[k])
self._setUpExtension(
'neutron_lbaas.extensions.loadbalancerv2.LoadBalancerPluginBaseV2',
constants.LOADBALANCERV2, resource_map,
@ -1002,6 +1005,7 @@ class TestLoadBalancerExtensionV2TestCase(base.ExtensionTestCase):
'delay': 2,
'timeout': 1,
'max_retries': 3,
'max_retries_down': 3,
'http_method': 'GET',
'url_path': '/path',
'expected_codes': '200-300',