diff --git a/README.md b/README.md index 115bb93..b502f65 100644 --- a/README.md +++ b/README.md @@ -248,13 +248,13 @@ Settings here can overwrite the settings in HAProxy, which are only applied to t |EXCLUDE_PORTS|if set, the application by the application services to the backend routes. You can exclude the ports that you don't want to be routed, like database port| |EXCLUDE_BASIC_AUTH|if set(any value) and `HTTP_BASIC_AUTH` global setting is set, no basic auth will be applied to this service.| |EXTRA_ROUTE_SETTINGS|a string which is append to the each backend route after the health check,possible value: "send-proxy"| +|FAILOVER|if set (any value), this service will run as a HAProxy `backup` for other configured service(s) in the same backend| |EXTRA_SETTINGS|comma-separated string of extra settings, and each part will be appended to either related backend section or listen session in the configuration file. To escape comma, use `\,`. Possible value: `balance source`| -|FAILOVER|if set(any value), it configures this service to be run as HAProxy `backup` for other configured service(s) in this backend| |FORCE_SSL|if set(any value) together with ssl termination enabled. HAProxy will redirect HTTP request to HTTPS request. |GZIP_COMPRESSION_TYPE|enable gzip compression. The value of this envvar is a list of MIME types that will be compressed. Some possible values: `text/html text/plain text/css application/javascript`. See:[HAProxy:compression](http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#4-compression)| |HEALTH_CHECK|set health check on each backend route, possible value: "check inter 2000 rise 2 fall 3". See:[HAProxy:check](https://cbonte.github.io/haproxy-dconv/configuration-1.5.html#5.2-check)| |HSTS_MAX_AGE|enable HSTS. It is an integer representing the max age of HSTS in seconds, possible value: `31536000`| -|HTTP_CHECK|enable HTTP protocol to check on the servers health, possible value: "OPTIONS * HTTP/1.1\r\nHost:\ www". See:[HAProxy:httpchk](https://cbonte.github.io/haproxy-dconv/configuration-1.5.html#4-option%20httpchk)| +|HTTP_CHECK|enable HTTP protocol to check on the servers health, possible value: "OPTIONS / HTTP/1.1\r\nHost:\ www". See:[HAProxy:httpchk](https://cbonte.github.io/haproxy-dconv/configuration-1.5.html#4-option%20httpchk)| |OPTION|comma-separated list of HAProxy `option` entries. `option` specified here will be added to related backend or listen part, and overwrite the OPTION settings in the HAProxy container| |SSL_CERT|ssl cert, a pem file with private key followed by public certificate, '\n'(two chars) as the line separator| |TCP_PORTS|comma separated ports(e.g. 9000, 9001, 2222/ssl). The port listed in `TCP_PORTS` will be load-balanced in TCP mode. Port ends with `/ssl` indicates that port needs SSL termination. diff --git a/haproxy/helper/backend_helper.py b/haproxy/helper/backend_helper.py index 221659d..dcb9e05 100644 --- a/haproxy/helper/backend_helper.py +++ b/haproxy/helper/backend_helper.py @@ -13,16 +13,13 @@ def get_backend_section(details, routes, vhosts, service_alias, routes_added): backend_settings, is_sticky = get_backend_settings(details, service_alias, HTTP_BASIC_AUTH) backend.extend(backend_settings) - route_health_check = get_route_health_check(details, service_alias, HEALTH_CHECK) - extra_route_settings = get_extra_route_settings(details, service_alias, EXTRA_ROUTE_SETTINGS) - route_setting = " ".join([route_health_check, extra_route_settings]).strip() - backend_routes = get_backend_routes(route_setting, is_sticky, routes, routes_added, service_alias, details) + backend_routes = get_backend_routes(is_sticky, routes, routes_added, service_alias) backend.extend(backend_routes) return backend -def get_backend_routes(route_setting, is_sticky, routes, routes_added, service_alias, details): +def get_backend_routes(is_sticky, routes, routes_added, service_alias): backend_routes = [] for _service_alias, routes in routes.iteritems(): if not service_alias or _service_alias == service_alias: @@ -38,29 +35,21 @@ def get_backend_routes(route_setting, is_sticky, routes, routes_added, service_a if is_sticky: backend_route.append("cookie %s" % route["container_name"]) - if route_setting: - backend_route.append(route_setting) + if "health_check" in route and route["health_check"]: + backend_route.append(route["health_check"]) + else: + backend_route.append(HEALTH_CHECK) - if details.get(service_alias, {}).get('failover', False): - backend_route.append("backup") + if "extra_route_settings" in route and route["extra_route_settings"]: + backend_route.append(route["extra_route_settings"]) + else: + backend_route.append(EXTRA_ROUTE_SETTINGS) - backend_routes.append(" ".join(backend_route)) + backend_routes.append(" ".join(backend_route).strip()) return sorted(backend_routes) -def get_route_health_check(details, service_alias, default_health_check): - health_check = get_service_attribute(details, "health_check", service_alias) - health_check = health_check if health_check else default_health_check - return health_check - - -def get_extra_route_settings(details, service_alias, default_extra_route_settings): - extra_route_settings = get_service_attribute(details, "extra_route_settings", service_alias) - extra_route_settings = extra_route_settings if extra_route_settings else default_extra_route_settings - return extra_route_settings - - def get_websocket_setting(vhosts, service_alias): websocket_setting = [] for v in vhosts: diff --git a/haproxy/parser/legacy_parser.py b/haproxy/parser/legacy_parser.py index 9080779..e9f4080 100644 --- a/haproxy/parser/legacy_parser.py +++ b/haproxy/parser/legacy_parser.py @@ -64,6 +64,21 @@ def _parse_routes(details, envvars): route = haproxy.config.BACKEND_MATCH.match(value).groupdict() route.update({"container_name": container_name}) + + route_health_check = details.get(service_alias, {}).get("health_check") + if route_health_check: + route.update({"health_check": route_health_check}) + + failover = None + if details.get(service_alias, {}).get("failover"): + failover = "backup" + + extra_route_settings = details.get(service_alias, {}).get("extra_route_settings") + extra_route_settings = " ".join([(extra_route_settings if extra_route_settings else ""), + (failover if failover else "")]).strip() + if extra_route_settings: + route.update({"extra_route_settings": extra_route_settings}) + exclude_ports = details.get(service_alias, {}).get("exclude_ports") if not exclude_ports or (exclude_ports and route["port"] not in exclude_ports): if service_alias in routes: diff --git a/haproxy/parser/new_parser.py b/haproxy/parser/new_parser.py index 9804645..e12c5c0 100644 --- a/haproxy/parser/new_parser.py +++ b/haproxy/parser/new_parser.py @@ -39,6 +39,21 @@ def _parse_routes(details, links): for endpoint in link["endpoints"].itervalues(): route = haproxy.config.BACKEND_MATCH.match(endpoint).groupdict() route.update({"container_name": container_name}) + + route_health_check = details.get(service_alias, {}).get("health_check") + if route_health_check: + route.update({"health_check": route_health_check}) + + failover = None + if details.get(service_alias, {}).get("failover"): + failover = "backup" + + extra_route_settings = details.get(service_alias, {}).get("extra_route_settings") + extra_route_settings = " ".join([(extra_route_settings if extra_route_settings else ""), + (failover if failover else "")]).strip() + if extra_route_settings: + route.update({"extra_route_settings": extra_route_settings}) + exclude_ports = details.get(service_alias, {}).get("exclude_ports", []) if not exclude_ports or (exclude_ports and route["port"] not in exclude_ports): if route not in routes[service_alias]: diff --git a/tests/unit/helper/test_backend_helper.py b/tests/unit/helper/test_backend_helper.py index 8dafaa8..9dec20b 100644 --- a/tests/unit/helper/test_backend_helper.py +++ b/tests/unit/helper/test_backend_helper.py @@ -1,95 +1,45 @@ import unittest from haproxy.helper.backend_helper import * - +from haproxy.config import HEALTH_CHECK class BackendHelperTestCase(unittest.TestCase): def test_get_backend_routes(self): - routes = {'HW': [{'container_name': 'HW_1', 'proto': 'tcp', 'port': '80', 'addr': '10.7.0.3'}, - {'container_name': 'HW_2', 'proto': 'tcp', 'port': '80', 'addr': '10.7.0.2'}], + routes = {'HW': [{'container_name': 'HW_1', 'proto': 'tcp', 'port': '80', 'addr': '10.7.0.3', 'health_check': 'check'}, + {'container_name': 'HW_2', 'proto': 'tcp', 'port': '80', 'addr': '10.7.0.2', 'health_check': 'check'}], 'WEB': [{'container_name': 'WEB_2', 'proto': 'tcp', 'port': '8080', 'addr': '10.7.0.4'}, {'container_name': 'WEB_1', 'proto': 'tcp', 'port': '8080', 'addr': '10.7.0.5'}]} self.assertEqual(["server HW_1 10.7.0.3:80 check", "server HW_2 10.7.0.2:80 check"], - get_backend_routes(route_setting="check", is_sticky=False, - routes=routes, routes_added=[], service_alias="HW", details={})) - self.assertEqual(["server WEB_1 10.7.0.5:8080", "server WEB_2 10.7.0.4:8080"], - get_backend_routes(route_setting="", is_sticky=False, - routes=routes, routes_added=[], service_alias="WEB", details={})) - self.assertEqual(["server WEB_1 10.7.0.5:8080 cookie WEB_1", "server WEB_2 10.7.0.4:8080 cookie WEB_2"], - get_backend_routes(route_setting="", is_sticky=True, - routes=routes, routes_added=[], service_alias="WEB", details={})) + get_backend_routes(is_sticky=False, routes=routes, routes_added=[], service_alias="HW")) + self.assertEqual(["server WEB_1 10.7.0.5:8080 %s" % HEALTH_CHECK, "server WEB_2 10.7.0.4:8080 %s" % HEALTH_CHECK], + get_backend_routes(is_sticky=False, routes=routes, routes_added=[], service_alias="WEB")) + self.assertEqual(["server WEB_1 10.7.0.5:8080 cookie WEB_1 %s" % HEALTH_CHECK, "server WEB_2 10.7.0.4:8080 cookie WEB_2 %s" % HEALTH_CHECK], + get_backend_routes(is_sticky=True, routes=routes, routes_added=[], service_alias="WEB")) self.assertEqual([], - get_backend_routes(route_setting="", is_sticky=False, - routes={}, routes_added=[], service_alias="WEB", details={})) - self.assertEqual(["server WEB_2 10.7.0.4:8080"], - get_backend_routes(route_setting="", is_sticky=False, - routes=routes, routes_added=[ + get_backend_routes(is_sticky=False, routes={}, routes_added=[], service_alias="WEB")) + self.assertEqual(["server WEB_2 10.7.0.4:8080 %s" % HEALTH_CHECK], + get_backend_routes(is_sticky=False, routes=routes, routes_added=[ {'container_name': 'WEB_1', 'proto': 'tcp', 'port': '8080', 'addr': '10.7.0.5'}], - service_alias="WEB", details={})) - self.assertEqual(["server WEB_2 10.7.0.4:8080 cookie WEB_2"], - get_backend_routes(route_setting="", is_sticky=True, - routes=routes, routes_added=[ + service_alias="WEB")) + self.assertEqual(["server WEB_2 10.7.0.4:8080 cookie WEB_2 %s" % HEALTH_CHECK], + get_backend_routes(is_sticky=True, routes=routes, routes_added=[ {'container_name': 'WEB_1', 'proto': 'tcp', 'port': '8080', 'addr': '10.7.0.5'}], - service_alias="WEB", details={})) - self.assertEqual(["server WEB_1 10.7.0.5:8080", "server WEB_2 10.7.0.4:8080"], - get_backend_routes(route_setting="", is_sticky=False, - routes=routes, routes_added=[ + service_alias="WEB")) + self.assertEqual(["server WEB_1 10.7.0.5:8080 %s" % HEALTH_CHECK, "server WEB_2 10.7.0.4:8080 %s" % HEALTH_CHECK], + get_backend_routes(is_sticky=False, routes=routes, routes_added=[ {'container_name': 'WEB_3', 'proto': 'tcp', 'port': '8080', 'addr': '10.7.0.5'}], - service_alias="WEB", details={})) + service_alias="WEB")) self.assertEqual([], - get_backend_routes(route_setting="", is_sticky=False, - routes=routes, routes_added=[ + get_backend_routes(is_sticky=False, routes=routes, routes_added=[ {'container_name': 'WEB_2', 'proto': 'tcp', 'port': '8080', 'addr': '10.7.0.4'}, {'container_name': 'WEB_1', 'proto': 'tcp', 'port': '8080', 'addr': '10.7.0.5'}], - service_alias="WEB", details={})) + service_alias="WEB")) self.assertEqual(["server HW_1 10.7.0.3:80 check", "server HW_2 10.7.0.2:80 check"], - get_backend_routes(route_setting="check", is_sticky=False, - routes=routes, routes_added=[ + get_backend_routes(is_sticky=False, routes=routes, routes_added=[ {'container_name': 'WEB_3', 'proto': 'tcp', 'port': '8080', 'addr': '10.7.0.5'}], - service_alias="HW", details={})) + service_alias="HW")) self.assertEqual([], - get_backend_routes(route_setting="", is_sticky=False, - routes=routes, routes_added=[], service_alias="HELLO", details={})) - - def test_get_backend_routes_with_failover(self): - routes = {'HW': [{'container_name': 'HW_1', 'proto': 'tcp', 'port': '80', 'addr': '10.7.0.3'}, - {'container_name': 'HW_2', 'proto': 'tcp', 'port': '80', 'addr': '10.7.0.2'}], - 'WEB': [{'container_name': 'WEB_2', 'proto': 'tcp', 'port': '8080', 'addr': '10.7.0.4'}, - {'container_name': 'WEB_1', 'proto': 'tcp', 'port': '8080', 'addr': '10.7.0.5'}]} - details = {'HW': {'failover': "true"}, - 'WEB': {'failover': ""}} - - self.assertEqual(["server HW_1 10.7.0.3:80 check backup", "server HW_2 10.7.0.2:80 check backup"], - get_backend_routes(route_setting="check", is_sticky=False, - routes=routes, routes_added=[ - {'container_name': 'WEB_3', 'proto': 'tcp', 'port': '8080', 'addr': '10.7.0.5'}], - service_alias="HW", details=details)) - self.assertEqual(["server WEB_1 10.7.0.5:8080", "server WEB_2 10.7.0.4:8080"], - get_backend_routes(route_setting="", is_sticky=False, - routes=routes, routes_added=[], service_alias="WEB", details=details)) - - def test_get_route_health(self): - details = {'web-a': {'health_check': 'health_check_web_a'}, - 'web-b': {'health_check': ''}, - 'web-c': {}} - default_health_check = 'default_health_check' - - self.assertEqual("health_check_web_a", get_route_health_check(details, 'web-a', default_health_check)) - self.assertEqual(default_health_check, get_route_health_check(details, 'web-b', default_health_check)) - self.assertEqual(default_health_check, get_route_health_check(details, 'web-c', default_health_check)) - self.assertEqual(default_health_check, get_route_health_check(details, 'web-d', default_health_check)) - - def test_get_extra_route_settings(self): - details = {'web-a': {'extra_route_settings': 'extra_route_settings_web_a'}, - 'web-b': {'extra_route_settings': ''}, - 'web-c': {}} - default_route_settings = 'default_routsettings' - - self.assertEqual("extra_route_settings_web_a", - get_extra_route_settings(details, 'web-a', default_route_settings)) - self.assertEqual(default_route_settings, get_extra_route_settings(details, 'web-b', default_route_settings)) - self.assertEqual(default_route_settings, get_extra_route_settings(details, 'web-c', default_route_settings)) - self.assertEqual(default_route_settings, get_extra_route_settings(details, 'web-d', default_route_settings)) + get_backend_routes(is_sticky=False, routes=routes, routes_added=[], service_alias="HELLO")) def test_get_websocket_setting(self): vhosts = [{'service_alias': 'web-a', 'path': '', 'host': 'a.com', 'scheme': 'http', 'port': '8080'}, diff --git a/tests/unit/parser/test_legacy_parser.py b/tests/unit/parser/test_legacy_parser.py index cf30508..8958e48 100644 --- a/tests/unit/parser/test_legacy_parser.py +++ b/tests/unit/parser/test_legacy_parser.py @@ -120,7 +120,7 @@ def test_parse_routes(self): } routes = {'WORLD': [{'container_name': 'WORLD_1', 'proto': 'tcp', 'port': '80', 'addr': '10.7.0.3'}], - 'HELLO': [{'container_name': 'HELLO_1', 'proto': 'tcp', 'port': '80', 'addr': '10.7.0.1', }], + 'HELLO': [{'container_name': 'HELLO_1', 'proto': 'tcp', 'port': '80', 'addr': '10.7.0.1'}], 'DUPLICATED': [{'container_name': 'DUPLICATED', 'proto': 'tcp', 'port': '80', 'addr': '10.7.0.1'}]} self.assertEqual(routes, specs._parse_routes(details, envvars)) diff --git a/tests/unit/test_haproxycfg.py b/tests/unit/test_haproxycfg.py index 16a95c8..48476e3 100644 --- a/tests/unit/test_haproxycfg.py +++ b/tests/unit/test_haproxycfg.py @@ -319,13 +319,11 @@ def test_config_backend_sections(self, mock_init, mock_details, mock_services, m haproxy = Haproxy() haproxy.specs = Specs() mock_details.return_value = {'HW': {'balance': "source", - 'virtual_host': "a.com", - 'health_check': "check", - 'extra_route_settings': 'extra settings'}} + 'virtual_host': "a.com"}} mock_services.return_value = ["HW"] mock_routes.return_value = { - 'HW': [{'container_name': 'HW_1', 'proto': 'http', 'port': '80', 'addr': '10.7.0.2'}, - {'container_name': 'HW_2', 'proto': 'http', 'port': '80', 'addr': '10.7.0.3'}]} + 'HW': [{'container_name': 'HW_1', 'proto': 'http', 'port': '80', 'addr': '10.7.0.2', 'health_check': 'check', 'extra_route_settings': 'extra settings'}, + {'container_name': 'HW_2', 'proto': 'http', 'port': '80', 'addr': '10.7.0.3', 'health_check': 'check', 'extra_route_settings': 'extra settings'}]} mock_vhosts.return_value = [ {'service_alias': 'HW', 'path': '', 'host': 'a.com', 'scheme': 'http', 'port': '80'}] self.assertEqual(OrderedDict([('backend SERVICE_HW', ['balance source', @@ -335,11 +333,10 @@ def test_config_backend_sections(self, mock_init, mock_details, mock_services, m haproxy = Haproxy() haproxy.specs = Specs() - mock_details.return_value = {'HW': {'balance': "source", - 'health_check': "check"}} + mock_details.return_value = {'HW': {'balance': "source"}} mock_services.return_value = ["HW"] mock_routes.return_value = { - 'HW': [{'container_name': 'HW_1', 'proto': 'http', 'port': '80', 'addr': '10.7.0.2'}, + 'HW': [{'container_name': 'HW_1', 'proto': 'http', 'port': '80', 'addr': '10.7.0.2', 'health_check': 'check'}, {'container_name': 'HW_2', 'proto': 'http', 'port': '80', 'addr': '10.7.0.3'}]} mock_vhosts.return_value = [] self.assertEqual({}, haproxy._config_backend_sections()) @@ -347,12 +344,11 @@ def test_config_backend_sections(self, mock_init, mock_details, mock_services, m haproxy = Haproxy() haproxy.specs = Specs() haproxy.require_default_route = True - mock_details.return_value = {'HW': {'balance': "source", - 'health_check': "check"}} + mock_details.return_value = {'HW': {'balance': "source"}} mock_services.return_value = ["HW"] mock_routes.return_value = { - 'HW': [{'container_name': 'HW_1', 'proto': 'http', 'port': '80', 'addr': '10.7.0.2'}, - {'container_name': 'HW_2', 'proto': 'http', 'port': '80', 'addr': '10.7.0.3'}]} + 'HW': [{'container_name': 'HW_1', 'proto': 'http', 'port': '80', 'addr': '10.7.0.2', 'health_check': 'check'}, + {'container_name': 'HW_2', 'proto': 'http', 'port': '80', 'addr': '10.7.0.3', 'health_check': 'check'}]} mock_vhosts.return_value = [] self.assertEqual(OrderedDict([('backend default_service', ['balance source', 'server HW_1 10.7.0.2:80 check',