From 75d9b08ea723674036f93076d1e94ba79ca7975c Mon Sep 17 00:00:00 2001 From: Jamie Hewland Date: Thu, 8 Sep 2016 17:32:47 +0200 Subject: [PATCH] Use a separate HAProxy map file for mapping Marathon app IDs to backends (#303) * Use a separate HAProxy map file to map app IDs to backends * Use os.path functions to generate map file paths * Implement /_haproxy_getappmap endpoint to get app ID -> backend map * Generalize getvhostmap.lua to fetch other map files * Rework validation to check new map files * Write map files before main config file * Edit temporary config file to include temporary map files so that new map files are validated * Small refactor - generalize some functions that work with temp files * Clean up and hopefully fix getmaps.lua * Hopefully fix map file reading * Hopefully actually fix map file reading * Run build-docs.sh --- Longhelp.md | 18 +++- README.md | 3 +- config.py | 6 +- getmaps.lua | 59 +++++++++++ getvhostmap.lua | 48 --------- marathon_lb.py | 202 ++++++++++++++++++++++++-------------- tests/test_marathon_lb.py | 158 ++++++++++++++++++----------- 7 files changed, 309 insertions(+), 185 deletions(-) create mode 100644 getmaps.lua delete mode 100644 getvhostmap.lua diff --git a/Longhelp.md b/Longhelp.md index 28565711..fcc2a532 100644 --- a/Longhelp.md +++ b/Longhelp.md @@ -24,13 +24,16 @@ usage: marathon_lb.py [-h] [--longhelp] [--marathon MARATHON [MARATHON ...]] [--command COMMAND] [--sse] [--health-check] [--lru-cache-capacity LRU_CACHE_CAPACITY] [--haproxy-map] [--dont-bind-http-https] - [--ssl-certs SSL_CERTS] [--skip-validation] [--dry] + [--ssl-certs SSL_CERTS] + [--marathon-ca-cert MARATHON_CA_CERT] + [--skip-validation] [--dry] [--min-serv-port-ip-per-task MIN_SERV_PORT_IP_PER_TASK] [--max-serv-port-ip-per-task MAX_SERV_PORT_IP_PER_TASK] [--syslog-socket SYSLOG_SOCKET] [--log-format LOG_FORMAT] [--log-level LOG_LEVEL] [--marathon-auth-credential-file MARATHON_AUTH_CREDENTIAL_FILE] [--auth-credentials AUTH_CREDENTIALS] + [--dcos-auth-credentials DCOS_AUTH_CREDENTIALS] Marathon HAProxy Load Balancer @@ -40,7 +43,7 @@ optional arguments: --marathon MARATHON [MARATHON ...], -m MARATHON [MARATHON ...] [required] Marathon endpoint, eg. -m http://marathon1:8080 http://marathon2:8080 (default: - None) + ['http://master.mesos:8080']) --listening LISTENING, -l LISTENING (deprecated) The address this script listens on for marathon events (e.g., http://0.0.0.0:8080) (default: @@ -76,6 +79,9 @@ optional arguments: frontend marathon_https_inEx: /etc/ssl/site1.co.pem,/etc/ssl/site2.co.pem (default: /etc/ssl/mesosphere.com.pem) + --marathon-ca-cert MARATHON_CA_CERT + CA certificate for Marathon HTTPS connections + (default: None) --skip-validation Skip haproxy config file validation (default: False) --dry, -d Only print configuration to console (default: False) --min-serv-port-ip-per-task MIN_SERV_PORT_IP_PER_TASK @@ -98,6 +104,8 @@ optional arguments: --auth-credentials AUTH_CREDENTIALS user/pass for the Marathon HTTP API in the format of 'user:pass'. (default: None) + --dcos-auth-credentials DCOS_AUTH_CREDENTIALS + DC/OS service account credentials (default: None) ``` ## Templates @@ -373,7 +381,7 @@ global server-state-base /var/state/haproxy/ lua-load /marathon-lb/getpids.lua lua-load /marathon-lb/getconfig.lua - lua-load /marathon-lb/getvhostmap.lua + lua-load /marathon-lb/getmaps.lua defaults load-server-state-from-file global log global @@ -401,6 +409,8 @@ listen stats http-request use-service lua.getpids if getpid acl getvhostmap path /_haproxy_getvhostmap http-request use-service lua.getvhostmap if getvhostmap + acl getappmap path /_haproxy_getappmap + http-request use-service lua.getappmap if getappmap acl getconfig path /_haproxy_getconfig http-request use-service lua.getconfig if getconfig ``` @@ -859,7 +869,7 @@ of the `HAPROXY_HTTP_FRONTEND_APPID_HEAD` using haproxy maps. **Default template for `HAPROXY_MAP_HTTP_FRONTEND_APPID_ACL`:** ``` - use_backend %[req.hdr(x-marathon-app-id),lower,map({haproxy_dir}/domain2backend.map)] + use_backend %[req.hdr(x-marathon-app-id),lower,map({haproxy_dir}/app2backend.map)] ``` ## `HAPROXY_TCP_BACKEND_ACL_ALLOW_DENY` *Global* diff --git a/README.md b/README.md index 0563f9d8..b4907559 100644 --- a/README.md +++ b/README.md @@ -170,7 +170,8 @@ Marathon-lb exposes a few endpoints on port 9090 (by default). They are: | `:9090/haproxy?stats;csv` | This is a CSV version of the stats above, which can be consumed by other tools. For example, it's used in the [`zdd.py`](zdd.py) script. | | `:9090/_haproxy_health_check` | HAProxy health check endpoint. Returns `200 OK` if HAProxy is healthy. | | `:9090/_haproxy_getconfig` | Returns the HAProxy config file as it was when HAProxy was started. Implemented in [`getconfig.lua`](getconfig.lua). | -| `:9090/_haproxy_getvhostmap` | Returns the HAProxy vhost to backend map. This endpoint returns HAProxy map file only when `--haproxy-map` flag is enabled, it returns a empty string otherwise. Implemented in [`getvhostmap.lua`](getvhostmap.lua). | +| `:9090/_haproxy_getvhostmap` | Returns the HAProxy vhost to backend map. This endpoint returns HAProxy map file only when the `--haproxy-map` flag is enabled, it returns an empty string otherwise. Implemented in [`getmaps.lua`](getmaps.lua). | +| `:9090/_haproxy_getappmap` | Returns the HAProxy app ID to backend map. Like `_haproxy_getvhostmap`, this requires the `--haproxy-map` flag to be enabled and returns an empty string otherwise. Also implemented in `getmaps.lua`. | | `:9090/_haproxy_getpids` | Returns the PIDs for all HAProxy instances within the current process namespace. This literally returns `$(pidof haproxy)`. Implemented in [`getpids.lua`](getpids.lua). This is also used by the [`zdd.py`](zdd.py) script to determine if connections have finished draining during a deploy. | diff --git a/config.py b/config.py index f862fed3..e8f20fc0 100644 --- a/config.py +++ b/config.py @@ -59,7 +59,7 @@ def load(self): server-state-base /var/state/haproxy/ lua-load /marathon-lb/getpids.lua lua-load /marathon-lb/getconfig.lua - lua-load /marathon-lb/getvhostmap.lua + lua-load /marathon-lb/getmaps.lua defaults load-server-state-from-file global log global @@ -87,6 +87,8 @@ def load(self): http-request use-service lua.getpids if getpid acl getvhostmap path /_haproxy_getvhostmap http-request use-service lua.getvhostmap if getvhostmap + acl getappmap path /_haproxy_getappmap + http-request use-service lua.getappmap if getappmap acl getconfig path /_haproxy_getconfig http-request use-service lua.getconfig if getconfig ''', @@ -422,7 +424,7 @@ def load(self): ConfigTemplate(name='MAP_HTTP_FRONTEND_APPID_ACL', value='''\ use_backend %[req.hdr(x-marathon-app-id),lower,\ -map({haproxy_dir}/domain2backend.map)] +map({haproxy_dir}/app2backend.map)] ''', overridable=True, description='''\ diff --git a/getmaps.lua b/getmaps.lua new file mode 100644 index 00000000..f2f0fb67 --- /dev/null +++ b/getmaps.lua @@ -0,0 +1,59 @@ +-- A simple Lua script which serves up the HAProxy +-- vhost to backend map file. +function check_file_exists(name) + local f = io.open(name, "r") + if f ~= nil then io.close(f) return true else return false end +end + +function read_file(filepath) + -- Read all of the given file, returning an empty string if the file doesn't + -- exist. + local content = "" + if check_file_exists(filepath) then + local f = io.open(filepath, "rb") + content = f:read("*all") + f:close() + end + return content +end + +function detect_config_dir() + -- Read the process's (HAProxy's) cmdline proc and parse the path to the + -- config file so that we can determine the config directory. + local f = io.open("/proc/self/cmdline", "rb") + local cmdline = f:read("*all") + f:close() + + local found = false + local sep = package.config:sub(1, 1) + for opt in string.gmatch(cmdline, "%g+") do + if opt == "-f" then + found = true + elseif found then + return opt:match("(.*"..sep..")") + end + end +end + +function load_map(filename) + local config_dir = detect_config_dir() + return read_file(config_dir..filename) +end + +function send_map(applet, map) + applet:set_status(200) + applet:add_header("content-length", string.len(map)) + applet:add_header("content-type", "text/plain") + applet:start_response() + applet:send(map) +end + +core.register_service("getvhostmap", "http", function(applet) + local haproxy_vhostmap = load_map("domain2backend.map") + send_map(applet, haproxy_vhostmap) +end) + +core.register_service("getappmap", "http", function(applet) + local haproxy_appmap = load_map("app2backend.map") + send_map(applet, haproxy_appmap) +end) diff --git a/getvhostmap.lua b/getvhostmap.lua deleted file mode 100644 index c3131f6c..00000000 --- a/getvhostmap.lua +++ /dev/null @@ -1,48 +0,0 @@ --- A simple Lua script which serves up the HAProxy --- vhost to backend map file. -function check_file_exists(name) - local f=io.open(name,"r") - if f~=nil then io.close(f) return true else return false end -end - -function read_vhostmap_file(cmdline) - local found = false - local filename = '' - for s in string.gmatch(cmdline, '%g+') do - if s == '-f' then - found = true - elseif found then - filename = s - sep = package.config:sub(1,1) - filename=filename:match("(.*"..sep..")").."domain2backend.map" - break - end - end - - local map = '' - if check_file_exists(filename) then - local f = io.open(filename, "rb") - map = f:read("*all") - f:close() - else - map = '' - end - return map -end - -function load_vhostmap() - local f = io.open('/proc/self/cmdline', "rb") - local cmdline = f:read("*all") - f:close() - return read_vhostmap_file(cmdline) -end - -core.register_service("getvhostmap", "http", function(applet) - local haproxy_vhostmap = load_vhostmap() - applet:set_status(200) - applet:add_header("content-length", string.len(haproxy_vhostmap)) - applet:add_header("content-type", "text/plain") - applet:start_response() - applet:send(haproxy_vhostmap) -end) - diff --git a/marathon_lb.py b/marathon_lb.py index 088ae50b..66520655 100755 --- a/marathon_lb.py +++ b/marathon_lb.py @@ -278,7 +278,7 @@ def has_group(groups, app_groups): def config(apps, groups, bind_http_https, ssl_certs, templater, - haproxy_map=False, map_array=[], + haproxy_map=False, domain_map_array=[], app_map_array=[], config_file="/etc/haproxy/haproxy.cfg"): logger.info("generating config") config = templater.haproxy_head @@ -362,7 +362,7 @@ def config(apps, groups, bind_http_https, ssl_certs, templater, app, backend, haproxy_map, - map_array, + domain_map_array, haproxy_dir, duplicate_map) http_frontend_list.append((backend_weight, p_fe)) @@ -387,8 +387,8 @@ def config(apps, groups, bind_http_https, ssl_certs, templater, duplicate_map['map_http_frontend_appid_acl'] = 1 map_element = {} map_element[app.appId] = backend - if map_element not in map_array: - map_array.append(map_element) + if map_element not in app_map_array: + app_map_array.append(map_element) else: http_appid_frontend_acl = templater \ .haproxy_http_frontend_appid_acl(app) @@ -986,47 +986,40 @@ def generateHttpVhostAcl( def writeConfigAndValidate( - config, config_file, map_string, map_file, haproxy_map): + config, config_file, domain_map_string, domain_map_file, + app_map_string, app_map_file, haproxy_map): # Test run, print to stdout and exit if args.dry: print(config) sys.exit() - # Write config to a temporary location - fd, haproxyTempConfigFile = mkstemp() - logger.debug("writing config to temp file %s", haproxyTempConfigFile) - with os.fdopen(fd, 'w') as haproxyTempConfig: - haproxyTempConfig.write(config) - - # Ensure new config is created with the same - # permissions the old file had or use defaults - # if config file doesn't exist yet - perms = 0o644 - if os.path.isfile(config_file): - perms = stat.S_IMODE(os.lstat(config_file).st_mode) - os.chmod(haproxyTempConfigFile, perms) - if haproxy_map: - fd, haproxyTempMapFile = mkstemp() - logger.debug("writing map to temp file %s", haproxyTempMapFile) - with os.fdopen(fd, 'w') as haproxyTempMap: - haproxyTempMap.write(map_string) + temp_config = config - perms = 0o644 - if os.path.isfile(map_file): - perms = stat.S_IMODE(os.lstat(map_file).st_mode) - os.chmod(haproxyTempMapFile, perms) + # First write the new maps to temporary files + if haproxy_map: + domain_temp_map_file = writeReplacementTempFile(domain_map_string, + domain_map_file) + app_temp_map_file = writeReplacementTempFile(app_map_string, + app_map_file) + + # Change the file paths in the config to (temporarily) point to the + # temporary map files so those can also be checked when the config is + # validated + if not args.skip_validation: + temp_config = config.replace( + domain_map_file, domain_temp_map_file + ).replace(app_map_file, app_temp_map_file) + + # Write the new config to a temporary file + haproxyTempConfigFile = writeReplacementTempFile(temp_config, config_file) # If skip validation flag is provided, don't check. if args.skip_validation: - logger.debug("skipping validation. moving temp file %s to %s", - haproxyTempConfigFile, - config_file) - move(haproxyTempConfigFile, config_file) + logger.debug("skipping validation.") if haproxy_map: - logger.debug("Moving temp file %s to %s", - haproxyTempMapFile, - map_file) - move(haproxyTempMapFile, map_file) + moveTempFile(domain_temp_map_file, domain_map_file) + moveTempFile(app_temp_map_file, app_map_file) + moveTempFile(haproxyTempConfigFile, config_file) return True # Check that config is valid @@ -1035,27 +1028,58 @@ def writeConfigAndValidate( returncode = subprocess.call(args=cmd) if returncode == 0: # Move into place - logger.debug("moving temp file %s to %s", - haproxyTempConfigFile, - config_file) - move(haproxyTempConfigFile, config_file) if haproxy_map: - logger.debug("moving temp file %s to %s", - haproxyTempMapFile, - map_file) - move(haproxyTempMapFile, map_file) + moveTempFile(domain_temp_map_file, domain_map_file) + moveTempFile(app_temp_map_file, app_map_file) + + # Edit the config file again to point to the actual map paths + with open(haproxyTempConfigFile, 'w') as tempConfig: + tempConfig.write(config) + + moveTempFile(haproxyTempConfigFile, config_file) return True else: logger.error("haproxy returned non-zero when checking config") return False -def compareWriteAndReloadConfig(config, config_file, map_array, haproxy_map): +def writeReplacementTempFile(content, file_to_replace): + # Create a temporary file containing the given content that will be used to + # replace the given file after validation. Returns the path to the + # temporary file. + fd, tempFile = mkstemp() + logger.debug( + "writing temp file %s that will replace %s", tempFile, file_to_replace) + with os.fdopen(fd, 'w') as tempConfig: + tempConfig.write(content) + + # Ensure the new file is created with the same permissions the old file had + # or use defaults if the file doesn't exist yet + perms = 0o644 + if os.path.isfile(file_to_replace): + perms = stat.S_IMODE(os.lstat(file_to_replace).st_mode) + os.chmod(tempFile, perms) + + return tempFile + + +def moveTempFile(temp_file, dest_file): + # Replace the old file with the new from its temporary location + logger.debug("moving temp file %s to %s", temp_file, dest_file) + move(temp_file, dest_file) + + +def compareWriteAndReloadConfig(config, config_file, domain_map_array, + app_map_array, haproxy_map): # See if the last config on disk matches this, and if so don't reload # haproxy - map_file = "/".join(config_file.split("/")[:-1]) + \ - "/" + "domain2backend.map" - map_string = str() + domain_map_file = os.path.join(os.path.dirname(config_file), + "domain2backend.map") + app_map_file = os.path.join(os.path.dirname(config_file), + "app2backend.map") + + domain_map_string = str() + app_map_string = str() runningConfig = str() try: logger.debug("reading running config from %s", config_file) @@ -1065,47 +1089,73 @@ def compareWriteAndReloadConfig(config, config_file, map_array, haproxy_map): logger.warning("couldn't open config file for reading") if haproxy_map: - if not os.path.isfile(map_file): - open(map_file, 'a').close() - runningmap = str() - try: - logger.debug("reading map config from %s", map_file) - with open(map_file, "r") as f: - runningmap = f.read() - except IOError: - logger.warning("couldn't open map file for reading") - for element in map_array: - for key, value in list(element.items()): - map_string = map_string + str(key) + " " + str(value) + "\n" - - if runningConfig != config or runningmap != map_string: + domain_map_string = generateMapString(domain_map_array) + app_map_string = generateMapString(app_map_array) + + if (runningConfig != config or + compareMapFile(domain_map_file, domain_map_string) or + compareMapFile(app_map_file, app_map_string)): logger.info( "running config/map is different from generated" " config - reloading") if writeConfigAndValidate( - config, config_file, map_string, map_file, haproxy_map): + config, config_file, domain_map_string, domain_map_file, + app_map_string, app_map_file, haproxy_map): reloadConfig() else: logger.warning("skipping reload: config not valid") else: - if os.path.isfile(map_file): - logger.debug("Truncating map file as haproxy-map flag " - "is disabled %s", map_file) - fd = os.open(map_file, os.O_RDWR) - os.ftruncate(fd, 0) - os.close(fd) + truncateMapFileIfExists(domain_map_file) + truncateMapFileIfExists(app_map_file) if runningConfig != config: logger.info( "running config is different from generated config" " - reloading") if writeConfigAndValidate( - config, config_file, map_string, map_file, haproxy_map): + config, config_file, domain_map_string, domain_map_file, + app_map_string, app_map_file, haproxy_map): reloadConfig() else: logger.warning("skipping reload: config not valid") +def generateMapString(map_array): + # Generate the string representation of the map file from a map array + map_string = str() + for element in map_array: + for key, value in list(element.items()): + map_string = map_string + str(key) + " " + str(value) + "\n" + return map_string + + +def compareMapFile(map_file, map_string): + # Read the map file (creating an empty file if it does not exist) and + # compare its contents to the given map string. Returns true if the map + # string is different to the contents of the file. + if not os.path.isfile(map_file): + open(map_file, 'a').close() + + runningmap = str() + try: + logger.debug("reading map config from %s", map_file) + with open(map_file, "r") as f: + runningmap = f.read() + except IOError: + logger.warning("couldn't open map file for reading") + + return runningmap != map_string + + +def truncateMapFileIfExists(map_file): + if os.path.isfile(map_file): + logger.debug("Truncating map file as haproxy-map flag " + "is disabled %s", map_file) + fd = os.open(map_file, os.O_RDWR) + os.ftruncate(fd, 0) + os.close(fd) + + def get_health_check(app, portIndex): for check in app['healthChecks']: if check.get('port'): @@ -1307,11 +1357,15 @@ def get_apps(marathon): def regenerate_config(apps, config_file, groups, bind_http_https, ssl_certs, templater, haproxy_map): - map_array = [] - compareWriteAndReloadConfig(config(apps, groups, bind_http_https, - ssl_certs, templater, haproxy_map, - map_array, config_file), - config_file, map_array, haproxy_map) + domain_map_array = [] + app_map_array = [] + + generated_config = config(apps, groups, bind_http_https, ssl_certs, + templater, haproxy_map, domain_map_array, + app_map_array, config_file) + + compareWriteAndReloadConfig(generated_config, config_file, + domain_map_array, app_map_array, haproxy_map) class MarathonEventProcessor(object): diff --git a/tests/test_marathon_lb.py b/tests/test_marathon_lb.py index 2de51665..c4d4c1c7 100644 --- a/tests/test_marathon_lb.py +++ b/tests/test_marathon_lb.py @@ -42,7 +42,7 @@ def setUp(self): server-state-base /var/state/haproxy/ lua-load /marathon-lb/getpids.lua lua-load /marathon-lb/getconfig.lua - lua-load /marathon-lb/getvhostmap.lua + lua-load /marathon-lb/getmaps.lua defaults load-server-state-from-file global log global @@ -70,6 +70,8 @@ def setUp(self): http-request use-service lua.getpids if getpid acl getvhostmap path /_haproxy_getvhostmap http-request use-service lua.getvhostmap if getvhostmap + acl getappmap path /_haproxy_getappmap + http-request use-service lua.getappmap if getappmap acl getconfig path /_haproxy_getconfig http-request use-service lua.getconfig if getconfig ''' @@ -1858,11 +1860,12 @@ def test_config_haproxy_map(self): app2.add_backend("agent2", "2.2.2.2", 1025, False) apps = [app1, app2] haproxy_map = True - map_array = [] + domain_map_array = [] + app_map_array = [] config_file = "/etc/haproxy/haproxy.cfg" config = marathon_lb.config(apps, groups, bind_http_https, ssl_certs, - templater, haproxy_map, map_array, - config_file) + templater, haproxy_map, domain_map_array, + app_map_array, config_file) expected = self.base_config + ''' frontend marathon_http_in bind *:80 @@ -1874,7 +1877,7 @@ def test_config_haproxy_map(self): bind *:9091 mode http use_backend %[req.hdr(x-marathon-app-id),lower,\ -map(/etc/haproxy/domain2backend.map)] +map(/etc/haproxy/app2backend.map)] frontend marathon_https_in bind *:443 ssl crt /etc/ssl/mesosphere.com.pem @@ -1912,17 +1915,27 @@ def test_config_haproxy_map(self): server agent1_1_1_1_1_1024 1.1.1.1:1024 check inter 3s fall 11 ''' self.assertMultiLineEqual(config, expected) - config_map = {} - for element in map_array: + + # Check the domain map + domain_config_map = {} + for element in domain_map_array: + for key, value in list(element.items()): + domain_config_map[key] = value + expected_domain_map = {} + expected_domain_map["server.nginx.net"] = "nginx_10000" + expected_domain_map["server.nginx1.net"] = "nginx_10000" + expected_domain_map["server.apache.net"] = "apache_10001" + self.assertEqual(domain_config_map, expected_domain_map) + + # Check the app map + app_config_map = {} + for element in app_map_array: for key, value in list(element.items()): - config_map[key] = value - expected_map = {} - expected_map["server.nginx.net"] = "nginx_10000" - expected_map["server.nginx1.net"] = "nginx_10000" - expected_map["server.apache.net"] = "apache_10001" - expected_map["/apache"] = "apache_10001" - expected_map["/nginx"] = "nginx_10000" - self.assertEqual(config_map, expected_map) + app_config_map[key] = value + expected_app_map = {} + expected_app_map["/apache"] = "apache_10001" + expected_app_map["/nginx"] = "nginx_10000" + self.assertEqual(app_config_map, expected_app_map) def test_config_haproxy_map_hybrid(self): apps = dict() @@ -1954,11 +1967,12 @@ def test_config_haproxy_map_hybrid(self): app2.add_backend("agent2", "2.2.2.2", 1025, False) apps = [app1, app2] haproxy_map = True - map_array = [] + domain_map_array = [] + app_map_array = [] config_file = "/etc/haproxy/haproxy.cfg" config = marathon_lb.config(apps, groups, bind_http_https, ssl_certs, - templater, haproxy_map, map_array, - config_file) + templater, haproxy_map, domain_map_array, + app_map_array, config_file) expected = self.base_config + ''' frontend marathon_http_in bind *:80 @@ -1973,7 +1987,7 @@ def test_config_haproxy_map_hybrid(self): bind *:9091 mode http use_backend %[req.hdr(x-marathon-app-id),lower,\ -map(/etc/haproxy/domain2backend.map)] +map(/etc/haproxy/app2backend.map)] frontend marathon_https_in bind *:443 ssl crt /etc/ssl/mesosphere.com.pem @@ -2014,16 +2028,26 @@ def test_config_haproxy_map_hybrid(self): server agent1_1_1_1_1_1024 1.1.1.1:1024 check inter 3s fall 11 ''' self.assertMultiLineEqual(config, expected) - config_map = {} - for element in map_array: + + # Check the domain map + domain_config_map = {} + for element in domain_map_array: + for key, value in list(element.items()): + domain_config_map[key] = value + expected_domain_map = {} + expected_domain_map["server.nginx.net"] = "nginx_10000" + expected_domain_map["server.nginx1.net"] = "nginx_10000" + self.assertEqual(domain_config_map, expected_domain_map) + + # Check the app map + app_config_map = {} + for element in app_map_array: for key, value in list(element.items()): - config_map[key] = value - expected_map = {} - expected_map["server.nginx.net"] = "nginx_10000" - expected_map["server.nginx1.net"] = "nginx_10000" - expected_map["/apache"] = "apache_10001" - expected_map["/nginx"] = "nginx_10000" - self.assertEqual(config_map, expected_map) + app_config_map[key] = value + expected_app_map = {} + expected_app_map["/apache"] = "apache_10001" + expected_app_map["/nginx"] = "nginx_10000" + self.assertEqual(app_config_map, expected_app_map) def test_config_haproxy_map_hybrid_with_vhost_path(self): apps = dict() @@ -2055,11 +2079,12 @@ def test_config_haproxy_map_hybrid_with_vhost_path(self): app2.add_backend("agent2", "2.2.2.2", 1025, False) apps = [app1, app2] haproxy_map = True - map_array = [] + domain_map_array = [] + app_map_array = [] config_file = "/etc/haproxy/haproxy.cfg" config = marathon_lb.config(apps, groups, bind_http_https, ssl_certs, - templater, haproxy_map, map_array, - config_file) + templater, haproxy_map, domain_map_array, + app_map_array, config_file) expected = self.base_config + ''' frontend marathon_http_in bind *:80 @@ -2075,7 +2100,7 @@ def test_config_haproxy_map_hybrid_with_vhost_path(self): bind *:9091 mode http use_backend %[req.hdr(x-marathon-app-id),lower,\ -map(/etc/haproxy/domain2backend.map)] +map(/etc/haproxy/app2backend.map)] frontend marathon_https_in bind *:443 ssl crt /etc/ssl/mesosphere.com.pem @@ -2118,16 +2143,26 @@ def test_config_haproxy_map_hybrid_with_vhost_path(self): server agent1_1_1_1_1_1024 1.1.1.1:1024 check inter 3s fall 11 ''' self.assertMultiLineEqual(config, expected) - config_map = {} - for element in map_array: + + # Check the domain map + domain_config_map = {} + for element in domain_map_array: + for key, value in list(element.items()): + domain_config_map[key] = value + expected_domain_map = {} + expected_domain_map["server.nginx.net"] = "nginx_10000" + expected_domain_map["server.nginx1.net"] = "nginx_10000" + self.assertEqual(domain_config_map, expected_domain_map) + + # Check the app map + app_config_map = {} + for element in app_map_array: for key, value in list(element.items()): - config_map[key] = value - expected_map = {} - expected_map["server.nginx.net"] = "nginx_10000" - expected_map["server.nginx1.net"] = "nginx_10000" - expected_map["/apache"] = "apache_10001" - expected_map["/nginx"] = "nginx_10000" - self.assertEqual(config_map, expected_map) + app_config_map[key] = value + expected_app_map = {} + expected_app_map["/apache"] = "apache_10001" + expected_app_map["/nginx"] = "nginx_10000" + self.assertEqual(app_config_map, expected_app_map) def test_config_haproxy_map_hybrid_httptohttps_redirect(self): apps = dict() @@ -2159,11 +2194,12 @@ def test_config_haproxy_map_hybrid_httptohttps_redirect(self): app2.redirectHttpToHttps = True apps = [app1, app2] haproxy_map = True - map_array = [] + domain_map_array = [] + app_map_array = [] config_file = "/etc/haproxy/haproxy.cfg" config = marathon_lb.config(apps, groups, bind_http_https, ssl_certs, - templater, haproxy_map, map_array, - config_file) + templater, haproxy_map, domain_map_array, + app_map_array, config_file) expected = self.base_config + ''' frontend marathon_http_in bind *:80 @@ -2178,7 +2214,7 @@ def test_config_haproxy_map_hybrid_httptohttps_redirect(self): bind *:9091 mode http use_backend %[req.hdr(x-marathon-app-id),lower,\ -map(/etc/haproxy/domain2backend.map)] +map(/etc/haproxy/app2backend.map)] frontend marathon_https_in bind *:443 ssl crt /etc/ssl/mesosphere.com.pem @@ -2216,18 +2252,28 @@ def test_config_haproxy_map_hybrid_httptohttps_redirect(self): server agent1_1_1_1_1_1024 1.1.1.1:1024 check inter 3s fall 11 ''' self.assertMultiLineEqual(config, expected) - config_map = {} - for element in map_array: + + # Check the domain map + domain_config_map = {} + for element in domain_map_array: + for key, value in list(element.items()): + domain_config_map[key] = value + expected_domain_map = {} + expected_domain_map["server.nginx.net"] = "nginx_10000" + expected_domain_map["server.nginx1.net"] = "nginx_10000" + expected_domain_map["server.apache.net"] = "apache_10001" + expected_domain_map["server.apache1.net"] = "apache_10001" + self.assertEqual(domain_config_map, expected_domain_map) + + # Check the app map + app_config_map = {} + for element in app_map_array: for key, value in list(element.items()): - config_map[key] = value - expected_map = {} - expected_map["server.nginx.net"] = "nginx_10000" - expected_map["server.nginx1.net"] = "nginx_10000" - expected_map["server.apache.net"] = "apache_10001" - expected_map["server.apache1.net"] = "apache_10001" - expected_map["/apache"] = "apache_10001" - expected_map["/nginx"] = "nginx_10000" - self.assertEqual(config_map, expected_map) + app_config_map[key] = value + expected_app_map = {} + expected_app_map["/apache"] = "apache_10001" + expected_app_map["/nginx"] = "nginx_10000" + self.assertEqual(app_config_map, expected_app_map) def test_config_simple_app_proxypass(self): apps = dict()