diff --git a/doc/admin-guide/plugins/regex_revalidate.en.rst b/doc/admin-guide/plugins/regex_revalidate.en.rst index 57e8f990460..7fb68e9d142 100644 --- a/doc/admin-guide/plugins/regex_revalidate.en.rst +++ b/doc/admin-guide/plugins/regex_revalidate.en.rst @@ -70,6 +70,18 @@ config file changes are checked are only when ``traffic_ctl config reload`` is r regex_revalidate.so -d -c -l +The configuration parameter `--state-file` or `-f` may be used to configure +the plugin to maintain a state file with the last loaded configuration. +Normally when ATS restarts the epoch times of all rules are reset to +the first config file load time which will cause all matching assets to +issue new IMS requests to their parents for mathing rules. + +This option allows the revalidate rule "epoch" times to be retained between ATS +restarts. This state file by default is placed in var/trafficserver/ +but an absolute path may be specified as well. Syntax is as follows:: + + regex_revalidate.so -d -c -f + Revalidation Rules ================== @@ -127,6 +139,14 @@ currently lead to that rule being removed from the running plugin. In these cases, if the rule must be taken out of service, a service restart may be necessary. +State File +---------- + +The state file is not meant to be edited but is of the format:: + + + + Examples ======== diff --git a/plugins/regex_revalidate/regex_revalidate.c b/plugins/regex_revalidate/regex_revalidate.c index 01711f1a973..ecd900a372c 100644 --- a/plugins/regex_revalidate/regex_revalidate.c +++ b/plugins/regex_revalidate/regex_revalidate.c @@ -38,13 +38,15 @@ #include #endif -#define LOG_PREFIX "regex_revalidate" #define CONFIG_TMOUT 60000 #define FREE_TMOUT 300000 #define OVECTOR_SIZE 30 #define LOG_ROLL_INTERVAL 86400 #define LOG_ROLL_OFFSET 0 +static const char *const PLUGIN_NAME = "regex_revalidate"; +static const char *const DEFAULT_DIR = "var/trafficserver"; /* Not perfect, but no better API) */ + static char const *const RESULT_MISS = "MISS"; static char const *const RESULT_STALE = "STALE"; static char const *const RESULT_UNKNOWN = "UNKNOWN"; @@ -77,9 +79,10 @@ typedef struct invalidate_t { typedef struct { invalidate_t *invalidate_list; - char *config_file; + char *config_path; time_t last_load; TSTextLogObject log; + char *state_path; } plugin_state_t; static invalidate_t * @@ -115,21 +118,23 @@ free_invalidate_t(invalidate_t *i) } static void -free_invalidate_t_list(invalidate_t *i) +free_invalidate_t_list(invalidate_t *iptr) { - if (i->next) { - free_invalidate_t_list(i->next); + while (NULL != iptr) { + invalidate_t *const next = iptr->next; + free_invalidate_t(iptr); + iptr = next; } - free_invalidate_t(i); } static plugin_state_t * init_plugin_state_t(plugin_state_t *pstate) { pstate->invalidate_list = NULL; - pstate->config_file = NULL; + pstate->config_path = NULL; pstate->last_load = 0; pstate->log = NULL; + pstate->state_path = NULL; return pstate; } @@ -139,12 +144,15 @@ free_plugin_state_t(plugin_state_t *pstate) if (pstate->invalidate_list) { free_invalidate_t_list(pstate->invalidate_list); } - if (pstate->config_file) { - TSfree(pstate->config_file); + if (pstate->config_path) { + TSfree(pstate->config_path); } if (pstate->log) { TSTextLogObjectDestroy(pstate->log); } + if (pstate->state_path) { + TSfree(pstate->state_path); + } TSfree(pstate); } @@ -200,7 +208,7 @@ prune_config(invalidate_t **i) ilast = NULL; while (iptr) { if (difftime(iptr->expiry, now) < 0) { - TSDebug(LOG_PREFIX, "Removing %s expiry: %d type: %s now: %d", iptr->regex_text, (int)iptr->expiry, + TSDebug(PLUGIN_NAME, "Removing %s expiry: %d type: %s now: %d", iptr->regex_text, (int)iptr->expiry, strForResult(iptr->new_result), (int)now); if (ilast) { ilast->next = iptr->next; @@ -221,6 +229,96 @@ prune_config(invalidate_t **i) return pruned; } +static bool +load_state(plugin_state_t *pstate, invalidate_t **ilist) +{ + FILE *fs = NULL; + struct stat s; + char line[LINE_MAX]; + time_t now; + pcre *config_re = NULL; + const char *errptr; + int erroffset, ovector[OVECTOR_SIZE], rc; + int ln = 0; + + if (!*ilist) { + return true; + } + + if (stat(pstate->state_path, &s) < 0) { + TSDebug(PLUGIN_NAME, "Could not stat state %s", pstate->state_path); + return false; + } + + if (!(fs = fopen(pstate->state_path, "r"))) { + TSDebug(PLUGIN_NAME, "Could not open state %s for reading", pstate->state_path); + return false; + } + + now = time(NULL); + + config_re = pcre_compile("^([^#].+?)\\s+(\\d+)\\s+(\\d+)\\s+(\\w+)\\s*$", 0, &errptr, &erroffset, NULL); + TSAssert(NULL != config_re); + + while (fgets(line, LINE_MAX, fs) != NULL) { + TSDebug(PLUGIN_NAME, "state: processing: %d %s", ln, line); + ++ln; + rc = pcre_exec(config_re, NULL, line, strlen(line), 0, 0, ovector, OVECTOR_SIZE); + + if (5 == rc) { + invalidate_t *const inv = (invalidate_t *)TSmalloc(sizeof(invalidate_t)); + init_invalidate_t(inv); + + pcre_get_substring(line, ovector, rc, 1, &(inv->regex_text)); + inv->epoch = atoi(line + ovector[4]); + inv->expiry = atoi(line + ovector[6]); + + if (inv->expiry < now) { + TSDebug(PLUGIN_NAME, "state: skipping expired : '%s'", inv->regex_text); + free_invalidate_t(inv); + continue; + } + + int const len = ovector[9] - ovector[8]; + char const *const type = line + ovector[8]; + + if (0 == strncasecmp(type, RESULT_STALE, len)) { + TSDebug(PLUGIN_NAME, "state: regex line set to result type %s: '%s'", RESULT_STALE, inv->regex_text); + } else if (0 == strncasecmp(type, RESULT_MISS, len)) { + TSDebug(PLUGIN_NAME, "state: regex line set to result type %s: '%s'", RESULT_MISS, inv->regex_text); + inv->new_result = TS_CACHE_LOOKUP_MISS; + } else { + TSDebug(PLUGIN_NAME, "state: unknown regex line result type '%.*s', skipping '%s'", len, type, inv->regex_text); + } + + // iterate through the loaded config and try to merge + invalidate_t *iptr = *ilist; + do { + if (0 == strcmp(inv->regex_text, iptr->regex_text)) { + if (iptr->expiry == inv->expiry && iptr->new_result == inv->new_result) { + TSDebug(PLUGIN_NAME, "state: restoring epoch for %s", iptr->regex_text); + iptr->epoch = inv->epoch; + } + break; + } + + if (NULL == iptr->next) { + break; + } + iptr = iptr->next; + } while (NULL != iptr); + + free_invalidate_t(inv); + } else { + TSDebug(PLUGIN_NAME, "state: invalid line '%s'", line); + } + } + + pcre_free(config_re); + fclose(fs); + return true; +} + static bool load_config(plugin_state_t *pstate, invalidate_t **ilist) { @@ -236,67 +334,76 @@ load_config(plugin_state_t *pstate, invalidate_t **ilist) int ln = 0; invalidate_t *iptr, *i; - if (pstate->config_file[0] != '/') { - path_len = strlen(TSConfigDirGet()) + strlen(pstate->config_file) + 2; + if (pstate->config_path[0] != '/') { + path_len = strlen(TSConfigDirGet()) + strlen(pstate->config_path) + 2; path = alloca(path_len); - snprintf(path, path_len, "%s/%s", TSConfigDirGet(), pstate->config_file); + snprintf(path, path_len, "%s/%s", TSConfigDirGet(), pstate->config_path); } else { - path = pstate->config_file; + path = pstate->config_path; } if (stat(path, &s) < 0) { - TSDebug(LOG_PREFIX, "Could not stat %s", path); + TSDebug(PLUGIN_NAME, "Could not stat %s", path); return false; } - if (s.st_mtime > pstate->last_load) { + if (pstate->last_load < s.st_mtime) { now = time(NULL); if (!(fs = fopen(path, "r"))) { - TSDebug(LOG_PREFIX, "Could not open %s for reading", path); + TSDebug(PLUGIN_NAME, "Could not open %s for reading", path); return false; } config_re = pcre_compile("^([^#].+?)\\s+(\\d+)(\\s+(\\w+))?\\s*$", 0, &errptr, &erroffset, NULL); + TSAssert(NULL != config_re); + while (fgets(line, LINE_MAX, fs) != NULL) { - ln++; - TSDebug(LOG_PREFIX, "Processing: %d %s", ln, line); + TSDebug(PLUGIN_NAME, "Processing: %d %s", ln, line); + ++ln; rc = pcre_exec(config_re, NULL, line, strlen(line), 0, 0, ovector, OVECTOR_SIZE); + if (3 <= rc) { i = (invalidate_t *)TSmalloc(sizeof(invalidate_t)); init_invalidate_t(i); pcre_get_substring(line, ovector, rc, 1, &i->regex_text); + + i->regex = pcre_compile(i->regex_text, 0, &errptr, &erroffset, NULL); i->epoch = now; i->expiry = atoi(line + ovector[4]); - i->regex = pcre_compile(i->regex_text, 0, &errptr, &erroffset, NULL); + if (5 == rc) { + int const len = ovector[9] - ovector[8]; char const *const type = line + ovector[8]; - if (0 == strncasecmp(type, RESULT_MISS, strlen(RESULT_MISS))) { - TSDebug(LOG_PREFIX, "Regex line set to result type %s: '%s'", RESULT_MISS, i->regex_text); + if (0 == strncasecmp(type, RESULT_MISS, len)) { + TSDebug(PLUGIN_NAME, "Regex line set to result type %s: '%s'", RESULT_MISS, i->regex_text); i->new_result = TS_CACHE_LOOKUP_MISS; - } else if (0 != strncasecmp(type, RESULT_STALE, strlen(RESULT_STALE))) { - TSDebug(LOG_PREFIX, "Unknown regex line result type '%s', using default '%s' '%s'", type, RESULT_STALE, i->regex_text); + } else if (0 != strncasecmp(type, RESULT_STALE, len)) { + TSDebug(PLUGIN_NAME, "Unknown regex line result type '%s', using default '%s' '%s'", type, RESULT_STALE, i->regex_text); } } + if (i->expiry <= i->epoch) { - TSDebug(LOG_PREFIX, "Rule is already expired!"); + TSDebug(PLUGIN_NAME, "Rule is already expired!"); free_invalidate_t(i); + i = NULL; } else if (i->regex == NULL) { - TSDebug(LOG_PREFIX, "%s did not compile", i->regex_text); + TSDebug(PLUGIN_NAME, "%s did not compile", i->regex_text); free_invalidate_t(i); + i = NULL; } else { i->regex_extra = pcre_study(i->regex, 0, &errptr); if (!*ilist) { *ilist = i; - TSDebug(LOG_PREFIX, "Created new list and Loaded %s %d %d %s", i->regex_text, (int)i->epoch, (int)i->expiry, + TSDebug(PLUGIN_NAME, "Created new list and Loaded %s %d %d %s", i->regex_text, (int)i->epoch, (int)i->expiry, strForResult(i->new_result)); } else { iptr = *ilist; while (1) { if (strcmp(i->regex_text, iptr->regex_text) == 0) { if (iptr->expiry != i->expiry) { - TSDebug(LOG_PREFIX, "Updating duplicate %s", i->regex_text); + TSDebug(PLUGIN_NAME, "Updating duplicate %s", i->regex_text); iptr->epoch = i->epoch; iptr->expiry = i->expiry; } if (iptr->new_result != i->new_result) { - TSDebug(LOG_PREFIX, "Resetting duplicate due to type change %s", i->regex_text); + TSDebug(PLUGIN_NAME, "Resetting duplicate due to type change %s", i->regex_text); iptr->new_result = i->new_result; iptr->epoch = now; } @@ -311,12 +418,12 @@ load_config(plugin_state_t *pstate, invalidate_t **ilist) } if (i) { iptr->next = i; - TSDebug(LOG_PREFIX, "Loaded %s %d %d %s", i->regex_text, (int)i->epoch, (int)i->expiry, strForResult(i->new_result)); + TSDebug(PLUGIN_NAME, "Loaded %s %d %d %s", i->regex_text, (int)i->epoch, (int)i->expiry, strForResult(i->new_result)); } } } } else { - TSDebug(LOG_PREFIX, "Skipping line %d", ln); + TSDebug(PLUGIN_NAME, "Skipping line %d", ln); } } pcre_free(config_re); @@ -324,7 +431,7 @@ load_config(plugin_state_t *pstate, invalidate_t **ilist) pstate->last_load = s.st_mtime; return true; } else { - TSDebug(LOG_PREFIX, "File mod time is not newer: %d >= %d", (int)pstate->last_load, (int)s.st_mtime); + TSDebug(PLUGIN_NAME, "File mod time is not newer: %d >= %d", (int)pstate->last_load, (int)s.st_mtime); } return false; } @@ -334,27 +441,44 @@ list_config(plugin_state_t *pstate, invalidate_t *i) { invalidate_t *iptr; - TSDebug(LOG_PREFIX, "Current config:"); + TSDebug(PLUGIN_NAME, "Current config:"); if (pstate->log) { TSTextLogObjectWrite(pstate->log, "Current config:"); } + + FILE *state_file = NULL; + if (pstate->state_path) { + state_file = fopen(pstate->state_path, "w"); + if (NULL == state_file) { + TSDebug(PLUGIN_NAME, "Unable to open state file %s\n", pstate->state_path); + } + } + if (i) { iptr = i; while (iptr) { char const *const typestr = strForResult(iptr->new_result); - TSDebug(LOG_PREFIX, "%s epoch: %d expiry: %d result: %s", iptr->regex_text, (int)iptr->epoch, (int)iptr->expiry, typestr); + TSDebug(PLUGIN_NAME, "%s epoch: %d expiry: %d result: %s", iptr->regex_text, (int)iptr->epoch, (int)iptr->expiry, typestr); if (pstate->log) { TSTextLogObjectWrite(pstate->log, "%s epoch: %d expiry: %d result: %s", iptr->regex_text, (int)iptr->epoch, (int)iptr->expiry, typestr); } + if (state_file) { + fprintf(state_file, "%s %d %d %s\n", iptr->regex_text, (int)iptr->epoch, (int)iptr->expiry, typestr); + } iptr = iptr->next; } + } else { - TSDebug(LOG_PREFIX, "EMPTY"); + TSDebug(PLUGIN_NAME, "EMPTY"); if (pstate->log) { TSTextLogObjectWrite(pstate->log, "EMPTY"); } } + + if (NULL != state_file) { + fclose(state_file); + } } static int @@ -362,7 +486,7 @@ free_handler(TSCont cont, TSEvent event, void *edata) { invalidate_t *iptr; - TSDebug(LOG_PREFIX, "Freeing old config"); + TSDebug(PLUGIN_NAME, "Freeing old config"); iptr = (invalidate_t *)TSContDataGet(cont); free_invalidate_t_list(iptr); TSContDestroy(cont); @@ -381,7 +505,7 @@ config_handler(TSCont cont, TSEvent event, void *edata) mutex = TSContMutexGet(cont); TSMutexLock(mutex); - TSDebug(LOG_PREFIX, "In config Handler"); + TSDebug(PLUGIN_NAME, "In config Handler"); pstate = (plugin_state_t *)TSContDataGet(cont); i = copy_config(pstate->invalidate_list); @@ -398,7 +522,7 @@ config_handler(TSCont cont, TSEvent event, void *edata) TSContScheduleOnPool(free_cont, FREE_TMOUT, TS_THREAD_POOL_TASK); } } else { - TSDebug(LOG_PREFIX, "No Changes"); + TSDebug(PLUGIN_NAME, "No Changes"); if (i) { free_invalidate_t_list(i); } @@ -461,7 +585,7 @@ main_handler(TSCont cont, TSEvent event, void *edata) } if (pcre_exec(iptr->regex, iptr->regex_extra, url, url_len, 0, 0, NULL, 0) >= 0) { TSHttpTxnCacheLookupStatusSet(txn, iptr->new_result); - TSDebug(LOG_PREFIX, "Forced revalidate - %.*s %s", url_len, url, strForResult(iptr->new_result)); + TSDebug(PLUGIN_NAME, "Forced revalidate - %.*s %s", url_len, url, strForResult(iptr->new_result)); iptr = NULL; } } @@ -483,6 +607,21 @@ main_handler(TSCont cont, TSEvent event, void *edata) return 0; } +static char * +make_state_path(const char *filename) +{ + if ('/' == *filename) { + return TSstrdup(filename); + } else { + char buf[8192]; + const char *dir = TSInstallDirGet(); + snprintf(buf, sizeof(buf), "%s/%s/%s", dir, DEFAULT_DIR, filename); + return TSstrdup(buf); + } + + return NULL; +} + void TSPluginInit(int argc, const char *argv[]) { @@ -492,7 +631,7 @@ TSPluginInit(int argc, const char *argv[]) invalidate_t *iptr = NULL; bool disable_timed_reload = false; - TSDebug(LOG_PREFIX, "Starting plugin init"); + TSDebug(PLUGIN_NAME, "Starting plugin init"); pstate = (plugin_state_t *)TSmalloc(sizeof(plugin_state_t)); init_plugin_state_t(pstate); @@ -501,12 +640,13 @@ TSPluginInit(int argc, const char *argv[]) static const struct option longopts[] = {{"config", required_argument, NULL, 'c'}, {"log", required_argument, NULL, 'l'}, {"disable-timed-reload", no_argument, NULL, 'd'}, + {"state-file", required_argument, NULL, 'f'}, {NULL, 0, NULL, 0}}; - while ((c = getopt_long(argc, (char *const *)argv, "c:l:", longopts, NULL)) != -1) { + while ((c = getopt_long(argc, (char *const *)argv, "c:l:f:", longopts, NULL)) != -1) { switch (c) { case 'c': - pstate->config_file = TSstrdup(optarg); + pstate->config_path = TSstrdup(optarg); break; case 'l': if (TS_SUCCESS == TSTextLogObjectCreate(optarg, TS_LOG_MODE_ADD_TIMESTAMP, &pstate->log)) { @@ -517,25 +657,37 @@ TSPluginInit(int argc, const char *argv[]) case 'd': disable_timed_reload = true; break; + case 'f': + pstate->state_path = make_state_path(optarg); default: break; } } - if (!pstate->config_file) { + if (NULL == pstate->config_path) { TSError("[regex_revalidate] Plugin requires a --config option along with a config file name"); free_plugin_state_t(pstate); return; } if (!load_config(pstate, &iptr)) { - TSDebug(LOG_PREFIX, "Problem loading config from file %s", pstate->config_file); + TSDebug(PLUGIN_NAME, "Problem loading config from file %s", pstate->config_path); } else { pstate->invalidate_list = iptr; + + /* Load and merge previous state if provided */ + if (NULL != pstate->state_path) { + if (!load_state(pstate, &iptr)) { + TSDebug(PLUGIN_NAME, "Problem loading state from file %s", pstate->state_path); + } else { + TSDebug(PLUGIN_NAME, "Loaded state from file %s", pstate->state_path); + } + } + list_config(pstate, iptr); } - info.plugin_name = LOG_PREFIX; + info.plugin_name = PLUGIN_NAME; info.vendor_name = "Apache Software Foundation"; info.support_email = "dev@trafficserver.apache.org"; @@ -545,7 +697,7 @@ TSPluginInit(int argc, const char *argv[]) free_plugin_state_t(pstate); return; } else { - TSDebug(LOG_PREFIX, "Plugin registration succeeded"); + TSDebug(PLUGIN_NAME, "Plugin registration succeeded"); } main_cont = TSContCreate(main_handler, NULL); @@ -555,11 +707,11 @@ TSPluginInit(int argc, const char *argv[]) config_cont = TSContCreate(config_handler, TSMutexCreate()); TSContDataSet(config_cont, (void *)pstate); - TSMgmtUpdateRegister(config_cont, LOG_PREFIX); + TSMgmtUpdateRegister(config_cont, PLUGIN_NAME); if (!disable_timed_reload) { TSContScheduleOnPool(config_cont, CONFIG_TMOUT, TS_THREAD_POOL_TASK); } - TSDebug(LOG_PREFIX, "Plugin Init Complete"); + TSDebug(PLUGIN_NAME, "Plugin Init Complete"); } diff --git a/tests/gold_tests/pluginTest/regex_revalidate/regex_revalidate_state.test.py b/tests/gold_tests/pluginTest/regex_revalidate/regex_revalidate_state.test.py new file mode 100644 index 00000000000..d733cddda48 --- /dev/null +++ b/tests/gold_tests/pluginTest/regex_revalidate/regex_revalidate_state.test.py @@ -0,0 +1,131 @@ +''' +''' +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import time +Test.Summary = ''' +regex_revalidate plugin test, reload epoch state on ats start +''' + +# Test description: +# Ensures that that the regex revalidate config file is loaded, +# then epoch times from the state file are properly merged. + +Test.SkipUnless( + Condition.PluginExists('regex_revalidate.so') +) +Test.ContinueOnFail = False + +# configure origin server +server = Test.MakeOriginServer("server") + +# Define ATS and configure +ts = Test.MakeATSProcess("ts", command="traffic_manager") + +# **testname is required** +testName = "regex_revalidate_state" + +# default root +request_header_0 = {"headers": + "GET / HTTP/1.1\r\n" + + "Host: www.example.com\r\n" + + "\r\n", + "timestamp": "1469733493.993", + "body": "", + } + +response_header_0 = {"headers": + "HTTP/1.1 200 OK\r\n" + + "Connection: close\r\n" + + "Cache-Control: max-age=300\r\n" + + "\r\n", + "timestamp": "1469733493.993", + "body": "xxx", + } + +server.addResponse("sessionlog.json", request_header_0, response_header_0) + +reval_conf_path = os.path.join(ts.Variables.CONFIGDIR, 'reval.conf') +reval_state_path = os.path.join(ts.Variables.RUNTIMEDIR, 'reval.state') + +# Configure ATS server +ts.Disk.plugin_config.AddLine( + f"regex_revalidate.so -d -c reval.conf -l reval.log -f {reval_state_path}" +) + +sep = ' ' + +# rule with no initial state +path0_regex = "path0" +path0_expiry = str(time.time() + 90).split('.')[0] +path0_type = "STALE" +path0_rule = sep.join([path0_regex, path0_expiry, path0_type]) + +path1_regex = "path1" +path1_epoch = str(time.time() - 50).split('.')[0] +path1_expiry = str(time.time() + 600).split('.')[0] +path1_type = "MISS" +path1_rule = sep.join([path1_regex, path1_expiry, path1_type]) + +# Create gold files +gold_path_good = reval_state_path + ".good" +ts.Disk.File(gold_path_good, typename="ats:config").AddLines([ + sep.join([path0_regex, "``", path0_expiry, path0_type]), + sep.join([path1_regex, path1_epoch, path1_expiry, path1_type]), +]) + +# It seems there's no API for negative gold file matching +''' +gold_path_bad = reval_state_path + ".bad" +ts.Disk.File(gold_path_bad, typename="ats:config").AddLines([ + sep.join([path0_regex, path1_epoch, path0_expiry, path0_type]), + sep.join([path1_regex, path1_epoch, path1_expiry, path1_type]), +]) +''' + +# Create a state file, second line will be discarded and not merged +ts.Disk.File(reval_state_path, typename="ats:config").AddLines([ + sep.join([path1_regex, path1_epoch, path1_expiry, path1_type]), + sep.join(["dummy", path1_epoch, path1_expiry, path1_type]), +]) + +# Write out reval.conf file +ts.Disk.File(reval_conf_path, typename="ats:config").AddLines([ + path0_rule, path1_rule, +]) + +ts.Disk.remap_config.AddLine( + f"map http://ats/ http://127.0.0.1:{server.Variables.Port}" +) + +# minimal configuration +ts.Disk.records_config.update({ + 'proxy.config.diags.debug.enabled': 1, + 'proxy.config.diags.debug.tags': 'regex_revalidate', + 'proxy.config.http.wait_for_cache': 1, +}) + +# Start ATS and evaluate the new state file +tr = Test.AddTestRun("Initial load, state merged") +ps = tr.Processes.Default +ps.StartBefore(server, ready=When.PortOpen(server.Variables.Port)) +ps.StartBefore(Test.Processes.ts) +ps.Command = 'cat ' + reval_state_path +ps.ReturnCode = 0 +ps.Streams.stdout.Content = Testers.GoldFile(gold_path_good) +tr.StillRunningAfter = ts