diff --git a/examples/policies/lb_policy.yaml b/examples/policies/lb_policy.yaml index 9c7879254..1bbb5d822 100644 --- a/examples/policies/lb_policy.yaml +++ b/examples/policies/lb_policy.yaml @@ -39,3 +39,29 @@ properties: # TCP port to listen on protocol_port: 80 + + health_monitor: + # The type of probe sent by the load balancer to verify the member state, + # can be PING, TCP, HTTP, or HTTPS. + type: 'PING' + + # The amount of time, in seconds, between sending probes to members. + delay: 10 + + # The maximum time in seconds that a monitor waits to connect before it + # times out. This value must be less than the delay value. + timeout: 5 + + # The number of allowed connection failures before changing the status + # of the member to INACTIVE. A valid value is from 1 to 10. + max_retries: 4 + + # The HTTP method that the monitor uses for requests. + http_method: 'GET' + + # The HTTP path of the request sent by the monitor to test the health of + # a member. A string value that must begin with the forward slash '/'. + url_path: '/index.html' + + # Expected HTTP codes for a passing HTTP(S) monitor. + expected_codes: '200, 202' diff --git a/senlin/drivers/openstack/lbaas.py b/senlin/drivers/openstack/lbaas.py index 107fc658c..f946f8bfa 100644 --- a/senlin/drivers/openstack/lbaas.py +++ b/senlin/drivers/openstack/lbaas.py @@ -154,6 +154,9 @@ class LoadBalancerDriver(base.DriverBase): if not hm: return True, result + if not hm: + return True, result + # Create health monitor try: health_monitor = self.nc().healthmonitor_create( diff --git a/senlin/policies/lb_policy.py b/senlin/policies/lb_policy.py index 629186964..4f28beec2 100644 --- a/senlin/policies/lb_policy.py +++ b/senlin/policies/lb_policy.py @@ -53,9 +53,9 @@ class LoadBalancingPolicy(base.Policy): ] KEYS = ( - POOL, VIP, + POOL, VIP, HEALTH_MONITOR, ) = ( - 'pool', 'vip', + 'pool', 'vip', 'health_monitor', ) _POOL_KEYS = ( @@ -78,6 +78,18 @@ class LoadBalancingPolicy(base.Policy): 'ROUND_ROBIN', 'LEAST_CONNECTIONS', 'SOURCE_IP', ) + HEALTH_MONITOR_TYPES = ( + PING, TCP, HTTP, HTTPS, + ) = ( + 'PING', 'TCP', 'HTTP', 'HTTPS', + ) + + HTTP_METHODS = ( + GET, POST, PUT, DELETE, + ) = ( + 'GET', 'POST', 'PUT', 'DELETE', + ) + _VIP_KEYS = ( VIP_SUBNET, VIP_ADDRESS, VIP_CONNECTION_LIMIT, VIP_PROTOCOL, VIP_PROTOCOL_PORT, VIP_ADMIN_STATE_UP, @@ -86,6 +98,14 @@ class LoadBalancingPolicy(base.Policy): 'protocol_port', 'admin_state_up', ) + HEALTH_MONITOR_KEYS = ( + HM_TYPE, HM_DELAY, HM_TIMEOUT, HM_MAX_RETRIES, HM_ADMIN_STATE_UP, + HM_HTTP_METHOD, HM_URL_PATH, HM_EXPECTED_CODES, + ) = ( + 'type', 'delay', 'timeout', 'max_retries', 'admin_state_up', + 'http_method', 'url_path', 'expected_codes', + ) + _SESSION_PERSISTENCE_KEYS = ( PERSISTENCE_TYPE, COOKIE_NAME, ) = ( @@ -180,6 +200,51 @@ class LoadBalancingPolicy(base.Policy): ), }, ), + HEALTH_MONITOR: schema.Map( + _('Health monitor for loadbalancer.'), + schema={ + HM_TYPE: schema.String( + _('The type of probe sent by the load balancer to verify ' + 'the member state.'), + constraints=[ + constraints.AllowedValues(HEALTH_MONITOR_TYPES), + ], + default=PING, + ), + HM_DELAY: schema.Integer( + _('The amount of time in seconds between sending ' + 'probes to members.'), + default=10, + ), + HM_TIMEOUT: schema.Integer( + _('The maximum time in seconds that a monitor waits to ' + 'connect before it times out.'), + default=5, + ), + HM_MAX_RETRIES: schema.Integer( + _('The number of allowed connection failures before ' + 'changing the status of the member to INACTIVE.'), + default=3, + ), + HM_ADMIN_STATE_UP: schema.Boolean( + _('Administrative state of the health monitor.'), + default=True, + ), + HM_HTTP_METHOD: schema.String( + _('The HTTP method that the monitor uses for requests.'), + constraints=[ + constraints.AllowedValues(HTTP_METHODS), + ], + ), + HM_URL_PATH: schema.String( + _('The HTTP path of the request sent by the monitor to ' + 'test the health of a member.'), + ), + HM_EXPECTED_CODES: schema.String( + _('Expected HTTP codes for a passing HTTP(S) monitor.'), + ), + }, + ), } def __init__(self, name, spec, **kwargs): @@ -187,6 +252,7 @@ class LoadBalancingPolicy(base.Policy): self.pool_spec = self.properties.get(self.POOL, {}) self.vip_spec = self.properties.get(self.VIP, {}) + self.hm_spec = self.properties.get(self.HEALTH_MONITOR, None) self.validate() self.lb = None @@ -213,7 +279,8 @@ class LoadBalancingPolicy(base.Policy): params = self._build_conn_params(cluster) lb_driver = driver_base.SenlinDriver().loadbalancing(params) - res, data = lb_driver.lb_create(self.vip_spec, self.pool_spec) + res, data = lb_driver.lb_create(self.vip_spec, self.pool_spec, + self.hm_spec) if res is False: return False, data diff --git a/senlin/tests/functional/utils/test_utils.py b/senlin/tests/functional/utils/test_utils.py index b37d7ca98..0be2f8217 100644 --- a/senlin/tests/functional/utils/test_utils.py +++ b/senlin/tests/functional/utils/test_utils.py @@ -63,6 +63,16 @@ spec_lb_policy = { "connection_limit": 100, "protocol": "HTTP", "protocol_port": 80 + }, + "health_monitor": { + "type": "HTTP", + "delay": "1", + "timeout": 1, + "max_retries": 5, + "admin_state_up": True, + "http_method": "GET", + "url_path": "/index.html", + "expected_codes": "200,201,202" } } } diff --git a/senlin/tests/unit/policies/test_lb_policy.py b/senlin/tests/unit/policies/test_lb_policy.py index e4ae198da..82138a80e 100644 --- a/senlin/tests/unit/policies/test_lb_policy.py +++ b/senlin/tests/unit/policies/test_lb_policy.py @@ -52,6 +52,16 @@ class TestLoadBalancingPolicy(base.SenlinTestCase): 'protocol': 'HTTP', 'protocol_port': 80, 'admin_state_up': True, + }, + 'health_monitor': { + 'type': 'HTTP', + 'delay': 10, + 'timeout': 5, + 'max_retries': 3, + 'admin_state_up': True, + 'http_method': 'GET', + 'url_path': '/index.html', + 'expected_codes': '200,201,202' } } } @@ -145,7 +155,8 @@ class TestLoadBalancingPolicy(base.SenlinTestCase): self.assertTrue(res) self.assertEqual('policy_data', data) self.lb_driver.lb_create.assert_called_once_with(policy.vip_spec, - policy.pool_spec) + policy.pool_spec, + policy.hm_spec) m_load.assert_called_once_with(mock.ANY, cluster_id=cluster.id) member_add_calls = [ mock.call(node1, 'LB_ID', 'POOL_ID', 80, 'test-subnet'), @@ -343,6 +354,16 @@ class TestLoadBalancingPolicyOperations(base.SenlinTestCase): 'protocol': 'HTTP', 'protocol_port': 80, 'admin_state_up': True, + }, + 'health_monitor': { + 'type': 'HTTP', + 'delay': '1', + 'timeout': 1, + 'max_retries': 5, + 'admin_state_up': True, + 'http_method': 'GET', + 'url_path': '/index.html', + 'expected_codes': '200,201,202' } } }