Merge "Fix health monitor API handling of None updates"

This commit is contained in:
Zuul 2019-06-24 04:32:53 +00:00 committed by Gerrit Code Review
commit b2d40c1120
4 changed files with 102 additions and 10 deletions

View File

@ -285,14 +285,13 @@ class HealthMonitorController(base.BaseController):
raise exceptions.InvalidOption(
value=consts.EXPECTED_CODES, option='health monitors of '
'type {}'.format(db_hm.type))
else:
# For HTTP health monitor these cannot be null/None
if health_monitor.http_method is None:
health_monitor.http_method = wtypes.Unset
if health_monitor.url_path is None:
health_monitor.url_path = wtypes.Unset
if health_monitor.expected_codes is None:
health_monitor.expected_codes = wtypes.Unset
if health_monitor.delay is None:
raise exceptions.InvalidOption(value=None, option=consts.DELAY)
if health_monitor.max_retries is None:
raise exceptions.InvalidOption(value=None,
option=consts.MAX_RETRIES)
if health_monitor.timeout is None:
raise exceptions.InvalidOption(value=None, option=consts.TIMEOUT)
if health_monitor.domain_name and not (
db_hm.http_version or health_monitor.http_version):
@ -308,6 +307,29 @@ class HealthMonitorController(base.BaseController):
value='http_version %s' % http_version,
option='health monitors HTTP 1.1 domain name health check')
def _set_default_on_none(self, health_monitor):
"""Reset settings to their default values if None/null was passed in
A None/null value can be passed in to clear a value. PUT values
that were not provided by the user have a type of wtypes.UnsetType.
If the user is attempting to clear values, they should either
be set to None (for example in the name field) or they should be
reset to their default values.
This method is intended to handle those values that need to be set
back to a default value.
"""
if health_monitor.http_method is None:
health_monitor.http_method = (
consts.HEALTH_MONITOR_HTTP_DEFAULT_METHOD)
if health_monitor.url_path is None:
health_monitor.url_path = (
consts.HEALTH_MONITOR_DEFAULT_URL_PATH)
if health_monitor.expected_codes is None:
health_monitor.expected_codes = (
consts.HEALTH_MONITOR_DEFAULT_EXPECTED_CODES)
if health_monitor.max_retries_down is None:
health_monitor.max_retries_down = consts.DEFAULT_MAX_RETRIES_DOWN
@wsme_pecan.wsexpose(hm_types.HealthMonitorRootResponse, wtypes.text,
body=hm_types.HealthMonitorRootPUT, status_code=200)
def put(self, id, health_monitor_):
@ -327,6 +349,9 @@ class HealthMonitorController(base.BaseController):
if (pool.protocol == consts.PROTOCOL_UDP and
db_hm.type == consts.HEALTH_MONITOR_UDP_CONNECT):
self._validate_healthmonitor_request_for_udp(health_monitor)
self._set_default_on_none(health_monitor)
# Load the driver early as it also provides validation
driver = driver_factory.get_driver(provider)

View File

@ -86,7 +86,8 @@ class HealthMonitorPOST(BaseHealthMonitorType):
timeout = wtypes.wsattr(wtypes.IntegerType(minimum=0), mandatory=True)
max_retries_down = wtypes.wsattr(
wtypes.IntegerType(minimum=constants.MIN_HM_RETRIES,
maximum=constants.MAX_HM_RETRIES), default=3)
maximum=constants.MAX_HM_RETRIES),
default=constants.DEFAULT_MAX_RETRIES_DOWN)
max_retries = wtypes.wsattr(
wtypes.IntegerType(minimum=constants.MIN_HM_RETRIES,
maximum=constants.MAX_HM_RETRIES),
@ -152,7 +153,8 @@ class HealthMonitorSingleCreate(BaseHealthMonitorType):
timeout = wtypes.wsattr(wtypes.IntegerType(minimum=0), mandatory=True)
max_retries_down = wtypes.wsattr(
wtypes.IntegerType(minimum=constants.MIN_HM_RETRIES,
maximum=constants.MAX_HM_RETRIES), default=3)
maximum=constants.MAX_HM_RETRIES),
default=constants.DEFAULT_MAX_RETRIES_DOWN)
max_retries = wtypes.wsattr(
wtypes.IntegerType(minimum=constants.MIN_HM_RETRIES,
maximum=constants.MAX_HM_RETRIES),

View File

@ -193,11 +193,14 @@ HEALTH_MONITOR_DEFAULT_URL_PATH = '/'
TYPE = 'type'
URL_PATH = 'url_path'
HTTP_METHOD = 'http_method'
HTTP_VERSION = 'http_version'
EXPECTED_CODES = 'expected_codes'
DELAY = 'delay'
TIMEOUT = 'timeout'
MAX_RETRIES = 'max_retries'
MAX_RETRIES_DOWN = 'max_retries_down'
RISE_THRESHOLD = 'rise_threshold'
DOMAIN_NAME = 'domain_name'
UPDATE_STATS = 'UPDATE_STATS'
UPDATE_HEALTH = 'UPDATE_HEALTH'
@ -212,6 +215,7 @@ MIN_CONNECTION_LIMIT = -1
MIN_WEIGHT = 0
MAX_WEIGHT = 256
DEFAULT_MAX_RETRIES_DOWN = 3
MIN_HM_RETRIES = 1
MAX_HM_RETRIES = 10

View File

@ -1519,6 +1519,42 @@ class TestHealthMonitor(base.BaseAPITest):
self.put(self.HM_PATH.format(healthmonitor_id=api_hm.get('id')),
self._build_body(new_hm), status=400)
def test_update_delay_none(self):
api_hm = self.create_health_monitor(self.pool_with_listener_id,
constants.HEALTH_MONITOR_HTTP,
1, 1, 1, 1).get(self.root_tag)
new_hm = {constants.DELAY: None}
self.set_lb_status(self.lb_id)
expect_error_msg = ("None is not a valid option for %s" %
constants.DELAY)
res = self.put(self.HM_PATH.format(healthmonitor_id=api_hm.get('id')),
self._build_body(new_hm), status=400)
self.assertEqual(expect_error_msg, res.json['faultstring'])
def test_update_max_retries_none(self):
api_hm = self.create_health_monitor(self.pool_with_listener_id,
constants.HEALTH_MONITOR_HTTP,
1, 1, 1, 1).get(self.root_tag)
new_hm = {constants.MAX_RETRIES: None}
self.set_lb_status(self.lb_id)
expect_error_msg = ("None is not a valid option for %s" %
constants.MAX_RETRIES)
res = self.put(self.HM_PATH.format(healthmonitor_id=api_hm.get('id')),
self._build_body(new_hm), status=400)
self.assertEqual(expect_error_msg, res.json['faultstring'])
def test_update_timeout_none(self):
api_hm = self.create_health_monitor(self.pool_with_listener_id,
constants.HEALTH_MONITOR_HTTP,
1, 1, 1, 1).get(self.root_tag)
new_hm = {constants.TIMEOUT: None}
self.set_lb_status(self.lb_id)
expect_error_msg = ("None is not a valid option for %s" %
constants.TIMEOUT)
res = self.put(self.HM_PATH.format(healthmonitor_id=api_hm.get('id')),
self._build_body(new_hm), status=400)
self.assertEqual(expect_error_msg, res.json['faultstring'])
@mock.patch('octavia.api.drivers.utils.call_provider')
def test_update_with_bad_provider(self, mock_provider):
api_hm = self.create_health_monitor(
@ -1631,6 +1667,31 @@ class TestHealthMonitor(base.BaseAPITest):
'domain_name'], constants.DOMAIN_NAME_REGEX)
self.assertEqual(expect_error_msg, response.json['faultstring'])
def test_update_unset_defaults(self):
api_hm = self.create_health_monitor(
self.pool_with_listener_id, constants.HEALTH_MONITOR_HTTP,
1, 1, 1, 1, name='test', domain_name='test.example.com',
expected_codes='400', http_method='HEAD', http_version='1.1',
url_path='/test').get(self.root_tag)
new_hm = {constants.DOMAIN_NAME: None, constants.EXPECTED_CODES: None,
constants.HTTP_METHOD: None, constants.HTTP_VERSION: None,
constants.MAX_RETRIES_DOWN: None, 'name': None,
constants.URL_PATH: None}
self.set_lb_status(self.lb_id)
res = self.put(self.HM_PATH.format(healthmonitor_id=api_hm.get('id')),
self._build_body(new_hm)).json.get(self.root_tag)
self.assertIsNone(res[constants.DOMAIN_NAME])
self.assertEqual(constants.HEALTH_MONITOR_DEFAULT_EXPECTED_CODES,
res[constants.EXPECTED_CODES])
self.assertEqual(constants.HEALTH_MONITOR_HTTP_DEFAULT_METHOD,
res[constants.HTTP_METHOD])
self.assertIsNone(res[constants.HTTP_VERSION])
self.assertEqual(constants.DEFAULT_MAX_RETRIES_DOWN,
res[constants.MAX_RETRIES_DOWN])
self.assertEqual('', res['name'])
self.assertEqual(constants.HEALTH_MONITOR_DEFAULT_URL_PATH,
res[constants.URL_PATH])
def test_delete(self):
api_hm = self.create_health_monitor(
self.pool_with_listener_id,