diff --git a/octavia/api/v2/controllers/health_monitor.py b/octavia/api/v2/controllers/health_monitor.py index 808dd24312..90278f03b0 100644 --- a/octavia/api/v2/controllers/health_monitor.py +++ b/octavia/api/v2/controllers/health_monitor.py @@ -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) diff --git a/octavia/api/v2/types/health_monitor.py b/octavia/api/v2/types/health_monitor.py index 57237cebbd..92a3f7150d 100644 --- a/octavia/api/v2/types/health_monitor.py +++ b/octavia/api/v2/types/health_monitor.py @@ -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), diff --git a/octavia/common/constants.py b/octavia/common/constants.py index 0cfdde9f81..386d349166 100644 --- a/octavia/common/constants.py +++ b/octavia/common/constants.py @@ -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 diff --git a/octavia/tests/functional/api/v2/test_health_monitor.py b/octavia/tests/functional/api/v2/test_health_monitor.py index 249455aa3a..b6c54c4a0e 100644 --- a/octavia/tests/functional/api/v2/test_health_monitor.py +++ b/octavia/tests/functional/api/v2/test_health_monitor.py @@ -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,