This is an automated email from the ASF dual-hosted git repository.
amc pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/master by this push:
new 692223c50 Add configuration to require minimum number of rules in
remap.config. (#8802)
692223c50 is described below
commit 692223c506aa8e08b9700e00741125bd5081c35c
Author: Alan M. Carroll <[email protected]>
AuthorDate: Thu May 5 21:14:15 2022 -0500
Add configuration to require minimum number of rules in remap.config.
(#8802)
---
doc/admin-guide/files/records.config.en.rst | 9 +++
doc/admin-guide/files/remap.config.en.rst | 6 +-
mgmt/RecordsConfig.cc | 2 +
proxy/ReverseProxy.cc | 2 +-
proxy/http/remap/RemapConfig.cc | 3 +
proxy/http/remap/UrlRewrite.cc | 22 +++---
proxy/http/remap/UrlRewrite.h | 8 +++
tests/gold_tests/remap/reload_1.replay.yaml | 39 ++++++++++
tests/gold_tests/remap/reload_2.replay.yaml | 39 ++++++++++
tests/gold_tests/remap/reload_3.replay.yaml | 39 ++++++++++
tests/gold_tests/remap/reload_4.replay.yaml | 39 ++++++++++
tests/gold_tests/remap/reload_server.replay.yaml | 51 +++++++++++++
.../remap/remap_load_empty_failure.test.py | 30 ++++++++
.../remap/remap_load_empty_success.test.py | 33 +++++++++
.../remap/remap_load_missing_failure.test.py | 29 ++++++++
.../remap/remap_load_missing_success.test.py | 35 +++++++++
tests/gold_tests/remap/remap_reload.test.py | 83 ++++++++++++++++++++++
17 files changed, 458 insertions(+), 11 deletions(-)
diff --git a/doc/admin-guide/files/records.config.en.rst
b/doc/admin-guide/files/records.config.en.rst
index e359a3cef..94330a9ba 100644
--- a/doc/admin-guide/files/records.config.en.rst
+++ b/doc/admin-guide/files/records.config.en.rst
@@ -3490,6 +3490,15 @@ URL Remap Rules
Set this variable to ``1`` if you want to retain the client host
header in a request during remapping.
+.. ts:cv:: CONFIG proxy.config.url_remap.min_rules_required INT 0
+ :reloadable:
+
+ The minimum number of rules :file:`remap.config` must have to be considered
valid. An otherwise
+ valid configuration with fewer than this many rules is considered to be
invalid as if it had a syntax
+ error. A value of zero allows :file:`remap.config` to be empty or absent.
+
+ This is dynamic to enable different requirements for startup and reloading.
+
.. _records-config-ssl-termination:
SSL Termination
diff --git a/doc/admin-guide/files/remap.config.en.rst
b/doc/admin-guide/files/remap.config.en.rst
index 6df6863ce..aba51f79f 100644
--- a/doc/admin-guide/files/remap.config.en.rst
+++ b/doc/admin-guide/files/remap.config.en.rst
@@ -44,7 +44,11 @@ Refer to :ref:`reverse-proxy-and-http-redirects`, for
information about
redirecting HTTP requests and using reverse proxy.
After you modify the :file:`remap.config` run the
-:option:`traffic_ctl config reload` to apply the changes.
+:option:`traffic_ctl config reload` to apply the changes. The current
configuration is replaced
+with the new configuration only if there are no errors in the file. Any syntax
error will prevent
+an update. Even if syntactically correct the file is considered valid only if
it has at least :ts:cv:`proxy.config.url_remap.min_rules_required`
+rules in it. This defaults to 0, but can be set higher if it is desirable to
prevent loading an
+empty or missing file.
Format
======
diff --git a/mgmt/RecordsConfig.cc b/mgmt/RecordsConfig.cc
index 9cf5bc01b..2be112110 100644
--- a/mgmt/RecordsConfig.cc
+++ b/mgmt/RecordsConfig.cc
@@ -1102,6 +1102,8 @@ static const RecordElement RecordsConfig[] =
,
{RECT_CONFIG, "proxy.config.plugin.dynamic_reload_mode", RECD_INT, "1",
RECU_RESTART_TS, RR_NULL, RECC_INT, "[0-1]", RECA_NULL}
,
+ {RECT_CONFIG, "proxy.config.url_remap.min_rules_required", RECD_INT, "0",
RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-9]+", RECA_NULL}
+ ,
//##############################################################################
//#
diff --git a/proxy/ReverseProxy.cc b/proxy/ReverseProxy.cc
index c04f6cdfb..8c0dddd7b 100644
--- a/proxy/ReverseProxy.cc
+++ b/proxy/ReverseProxy.cc
@@ -67,7 +67,7 @@ init_reverse_proxy()
Note("%s loading ...", ts::filename::REMAP);
if (!rewrite_table->load()) {
- Warning("%s failed to load", ts::filename::REMAP);
+ Emergency("%s failed to load", ts::filename::REMAP);
} else {
Note("%s finished loading", ts::filename::REMAP);
}
diff --git a/proxy/http/remap/RemapConfig.cc b/proxy/http/remap/RemapConfig.cc
index 61afbd702..03e50203c 100644
--- a/proxy/http/remap/RemapConfig.cc
+++ b/proxy/http/remap/RemapConfig.cc
@@ -944,6 +944,9 @@ remap_parse_config_bti(const char *path, BUILD_TABLE_INFO
*bti)
std::error_code ec;
std::string content{ts::file::load(ts::file::path{path}, ec)};
+ if (ec.value() == ENOENT) { // a missing file is ok - treat as empty, no
rules.
+ return true;
+ }
if (ec.value()) {
Warning("Failed to open remapping configuration file %s - %s", path,
strerror(ec.value()));
return false;
diff --git a/proxy/http/remap/UrlRewrite.cc b/proxy/http/remap/UrlRewrite.cc
index 96de22b3a..fcd4f39ae 100644
--- a/proxy/http/remap/UrlRewrite.cc
+++ b/proxy/http/remap/UrlRewrite.cc
@@ -89,10 +89,17 @@ UrlRewrite::load()
Debug("url_rewrite_regex", "strategyFactory file: %s", sf.c_str());
strategyFactory = new NextHopStrategyFactory(sf.c_str());
- if (0 == this->BuildTable(config_file_path)) {
- _valid = true;
- if (is_debug_tag_set("url_rewrite")) {
- Print();
+ if (TS_SUCCESS == this->BuildTable(config_file_path)) {
+ int n_rules = this->rule_count(); // Minimum # of rules to be considered a
valid configuration.
+ int required_rules;
+ REC_ReadConfigInteger(required_rules,
"proxy.config.url_remap.min_rules_required");
+ if (n_rules >= required_rules) {
+ _valid = true;
+ if (is_debug_tag_set("url_rewrite")) {
+ Print();
+ }
+ } else {
+ Warning("%s %d rules defined but %d rules required, configuration is
invalid.", modulePrefix, n_rules, required_rules);
}
} else {
Warning("something failed during BuildTable() -- check your remap
plugins!");
@@ -698,11 +705,8 @@ UrlRewrite::BuildTable(const char *path)
temporary_redirects.hash_lookup.reset(new URLTable);
forward_mappings_with_recv_port.hash_lookup.reset(new URLTable);
- int zret = 0;
-
if (!remap_parse_config(path, this)) {
- // XXX handle file reload error
- zret = 3;
+ return TS_ERROR;
}
// Destroy unused tables
@@ -730,7 +734,7 @@ UrlRewrite::BuildTable(const char *path)
forward_mappings_with_recv_port.hash_lookup.reset(nullptr);
}
- return zret;
+ return TS_SUCCESS;
}
/**
diff --git a/proxy/http/remap/UrlRewrite.h b/proxy/http/remap/UrlRewrite.h
index aa5d32d25..b413825ad 100644
--- a/proxy/http/remap/UrlRewrite.h
+++ b/proxy/http/remap/UrlRewrite.h
@@ -108,6 +108,14 @@ public:
return _valid;
};
+ /// @return Number of rules defined.
+ int
+ rule_count() const
+ {
+ return num_rules_forward + num_rules_reverse +
num_rules_redirect_permanent + num_rules_redirect_temporary +
+ num_rules_forward_with_recv_port;
+ }
+
static constexpr int MAX_REGEX_SUBS = 10;
struct RegexMapping {
diff --git a/tests/gold_tests/remap/reload_1.replay.yaml
b/tests/gold_tests/remap/reload_1.replay.yaml
new file mode 100644
index 000000000..a65574262
--- /dev/null
+++ b/tests/gold_tests/remap/reload_1.replay.yaml
@@ -0,0 +1,39 @@
+# 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.
+
+#
+# This replay file assumes that caching is enabled and
+# proxy.config.http.cache.ignore_client_cc_max_age is set to 0 so that we can
+# test max-age in the client requests.
+#
+
+meta:
+ version: "1.0"
+
+sessions:
+- transactions:
+ - all: { headers: { fields: [[ uuid, success ]]}}
+ client-request:
+ method: "GET"
+ version: "1.1"
+ scheme: "http"
+ url: /index.html
+ headers:
+ fields:
+ - [ Host, charlie.ex ]
+
+ proxy-response:
+ status: 200
diff --git a/tests/gold_tests/remap/reload_2.replay.yaml
b/tests/gold_tests/remap/reload_2.replay.yaml
new file mode 100644
index 000000000..a65574262
--- /dev/null
+++ b/tests/gold_tests/remap/reload_2.replay.yaml
@@ -0,0 +1,39 @@
+# 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.
+
+#
+# This replay file assumes that caching is enabled and
+# proxy.config.http.cache.ignore_client_cc_max_age is set to 0 so that we can
+# test max-age in the client requests.
+#
+
+meta:
+ version: "1.0"
+
+sessions:
+- transactions:
+ - all: { headers: { fields: [[ uuid, success ]]}}
+ client-request:
+ method: "GET"
+ version: "1.1"
+ scheme: "http"
+ url: /index.html
+ headers:
+ fields:
+ - [ Host, charlie.ex ]
+
+ proxy-response:
+ status: 200
diff --git a/tests/gold_tests/remap/reload_3.replay.yaml
b/tests/gold_tests/remap/reload_3.replay.yaml
new file mode 100644
index 000000000..ac49c7b65
--- /dev/null
+++ b/tests/gold_tests/remap/reload_3.replay.yaml
@@ -0,0 +1,39 @@
+# 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.
+
+#
+# This replay file assumes that caching is enabled and
+# proxy.config.http.cache.ignore_client_cc_max_age is set to 0 so that we can
+# test max-age in the client requests.
+#
+
+meta:
+ version: "1.0"
+
+sessions:
+- transactions:
+ - all: { headers: { fields: [[ uuid, success ]]}}
+ client-request:
+ method: "GET"
+ version: "1.1"
+ scheme: "http"
+ url: /index.html
+ headers:
+ fields:
+ - [ Host, charlie.ex ]
+
+ proxy-response:
+ status: 404
diff --git a/tests/gold_tests/remap/reload_4.replay.yaml
b/tests/gold_tests/remap/reload_4.replay.yaml
new file mode 100644
index 000000000..fc1e00261
--- /dev/null
+++ b/tests/gold_tests/remap/reload_4.replay.yaml
@@ -0,0 +1,39 @@
+# 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.
+
+#
+# This replay file assumes that caching is enabled and
+# proxy.config.http.cache.ignore_client_cc_max_age is set to 0 so that we can
+# test max-age in the client requests.
+#
+
+meta:
+ version: "1.0"
+
+sessions:
+- transactions:
+ - all: { headers: { fields: [[ uuid, success ]]}}
+ client-request:
+ method: "GET"
+ version: "1.1"
+ scheme: "http"
+ url: /index.html
+ headers:
+ fields:
+ - [ Host, golf.ex ]
+
+ proxy-response:
+ status: 200
diff --git a/tests/gold_tests/remap/reload_server.replay.yaml
b/tests/gold_tests/remap/reload_server.replay.yaml
new file mode 100644
index 000000000..c7045e63f
--- /dev/null
+++ b/tests/gold_tests/remap/reload_server.replay.yaml
@@ -0,0 +1,51 @@
+# 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.
+
+#
+# This replay file assumes that caching is enabled and
+# proxy.config.http.cache.ignore_client_cc_max_age is set to 0 so that we can
+# test max-age in the client requests.
+#
+
+meta:
+ version: "1.0"
+
+ blocks:
+ - 200_ok_response: &200_ok_response
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [ Content-Length, 16 ]
+ - [ Cache-Control, max-age=300 ]
+
+sessions:
+- transactions:
+ - all: { headers: { fields: [[ uuid, success ]]}}
+ client-request:
+ method: "GET"
+ version: "1.1"
+ scheme: "http"
+ url: /index.html
+ headers:
+ fields:
+ - [ Host, charlie.ex ]
+
+ <<: *200_ok_response
+
+ proxy-response:
+ status: 200
diff --git a/tests/gold_tests/remap/remap_load_empty_failure.test.py
b/tests/gold_tests/remap/remap_load_empty_failure.test.py
new file mode 100644
index 000000000..29dbfb996
--- /dev/null
+++ b/tests/gold_tests/remap/remap_load_empty_failure.test.py
@@ -0,0 +1,30 @@
+'''
+Verify correct behavior of regex_map in remap.config.
+'''
+# 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.
+
+Test.Summary = '''
+Test minimum rules on load - fail on missing file.
+'''
+ts = Test.MakeATSProcess("ts")
+ts.Disk.remap_config.AddLine(f"") # empty file
+ts.Disk.records_config.update({'proxy.config.url_remap.min_rules_required': 1})
+ts.ReturnCode = 33 # expect to Emergency fail due to empty "remap.config".
+
+tr = Test.AddTestRun("test")
+tr.Processes.Default.Command = "echo"
+tr.Processes.Default.StartAfter(ts, ready=When.FileExists(ts.Disk.diags_log))
diff --git a/tests/gold_tests/remap/remap_load_empty_success.test.py
b/tests/gold_tests/remap/remap_load_empty_success.test.py
new file mode 100644
index 000000000..17dce500c
--- /dev/null
+++ b/tests/gold_tests/remap/remap_load_empty_success.test.py
@@ -0,0 +1,33 @@
+'''
+Verify correct behavior of regex_map in remap.config.
+'''
+# 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.
+
+Test.Summary = '''
+Test minimum rules on load - succeed on empty file.
+'''
+
+ts = Test.MakeATSProcess("ts")
+ts.Disk.remap_config.AddLine(f"") # empty file
+ts.Disk.records_config.update({f'proxy.config.url_remap.min_rules_required':
0})
+
+tr = Test.AddTestRun("startup")
+p = tr.Processes.Default
+p.Command = "sh -c echo"
+p.ReturnCode = 0
+p.StartBefore(Test.Processes.ts)
+tr.StillRunningAfter = ts
diff --git a/tests/gold_tests/remap/remap_load_missing_failure.test.py
b/tests/gold_tests/remap/remap_load_missing_failure.test.py
new file mode 100644
index 000000000..880d953c7
--- /dev/null
+++ b/tests/gold_tests/remap/remap_load_missing_failure.test.py
@@ -0,0 +1,29 @@
+'''
+Verify correct behavior of regex_map in remap.config.
+'''
+# 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.
+
+Test.Summary = '''
+Test minimum rules on load - fail on missing file.
+'''
+ts = Test.MakeATSProcess("ts")
+ts.Disk.records_config.update({'proxy.config.url_remap.min_rules_required': 1})
+ts.ReturnCode = 33 # expect to Emergency fail due to empty "remap.config".
+
+tr = Test.AddTestRun("test")
+tr.Processes.Default.Command = "echo"
+tr.Processes.Default.StartAfter(ts, ready=When.FileExists(ts.Disk.diags_log))
diff --git a/tests/gold_tests/remap/remap_load_missing_success.test.py
b/tests/gold_tests/remap/remap_load_missing_success.test.py
new file mode 100644
index 000000000..fe63f67ee
--- /dev/null
+++ b/tests/gold_tests/remap/remap_load_missing_success.test.py
@@ -0,0 +1,35 @@
+'''
+Verify correct behavior of regex_map in remap.config.
+'''
+# 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.
+
+Test.Summary = '''
+Test minimum rules on load - succeed on missing file.
+'''
+
+ts = Test.MakeATSProcess("ts")
+
+ts.Disk.records_config.update({
+ f'proxy.config.url_remap.min_rules_required': 0
+})
+
+tr = Test.AddTestRun("startup")
+p = tr.Processes.Default
+p.Command = "sh -c echo"
+p.ReturnCode = 0
+p.StartBefore(Test.Processes.ts)
+tr.StillRunningAfter = ts
diff --git a/tests/gold_tests/remap/remap_reload.test.py
b/tests/gold_tests/remap/remap_reload.test.py
new file mode 100644
index 000000000..0c5f74873
--- /dev/null
+++ b/tests/gold_tests/remap/remap_reload.test.py
@@ -0,0 +1,83 @@
+# 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
+
+Test.Summary = '''
+Test remap reloading
+'''
+Test.testName = 'remap_reload'
+
+replay_file_1 = "reload_1.replay.yaml"
+replay_file_2 = "reload_2.replay.yaml"
+replay_file_3 = "reload_3.replay.yaml"
+replay_file_4 = "reload_4.replay.yaml"
+
+tm = Test.MakeATSProcess("tm", command="traffic_manager", select_ports=True)
+tm.Disk.diags_log.Content = Testers.ContainsExpression("remap.config failed to
load", "Remap should fail to load")
+remap_cfg_path = os.path.join(tm.Variables.CONFIGDIR, 'remap.config')
+
+pv = Test.MakeVerifierServerProcess("pv", "reload_server.replay.yaml")
+pv_port = pv.Variables.http_port
+tm.Disk.remap_config.AddLines([f"map http://alpha.ex
http://alpha.ex:{pv_port}",
+ f"map http://bravo.ex
http://bravo.ex:{pv_port}",
+ f"map http://charlie.ex
http://charlie.ex:{pv_port}",
+ f"map http://delta.ex
http://delta.ex:{pv_port}"])
+tm.Disk.records_config.update({'proxy.config.url_remap.min_rules_required': 3})
+
+nameserver = Test.MakeDNServer("dns", default='127.0.0.1')
+tm.Disk.records_config.update({
+ 'proxy.config.dns.nameservers': f"127.0.0.1:{nameserver.Variables.Port}",
'proxy.config.dns.resolv_conf': 'NULL'
+})
+
+tr_1 = Test.AddTestRun("verify load")
+tr_1.Processes.Default.StartBefore(pv)
+tr_1.Processes.Default.StartBefore(nameserver)
+tr_1.Processes.Default.StartBefore(tm)
+tr_1.AddVerifierClientProcess("client", replay_file_1,
http_ports=[tm.Variables.port])
+
+tr_2 = Test.AddTestRun("reload-fail")
+tr_2.Processes.Default.Command = 'traffic_ctl config reload'
+tr_2.Processes.Default.Env = tm.Env
+tr_2.Disk.File(remap_cfg_path).WriteOn("")
+tr_2.Disk.File(remap_cfg_path, typename="ats:config").AddLines([
+ f"map http://alpha.ex http://alpha.ex:{pv_port}", f"map http://bravo.ex
http://bravo.ex:{pv_port}"
+])
+
+tr_3 = Test.AddTestRun("pause")
+tr_3.Processes.Default.Command = 'sleep 5'
+
+tr_4 = Test.AddTestRun("after first reload")
+tr_4.AddVerifierClientProcess("client_2", replay_file_2,
http_ports=[tm.Variables.port])
+
+tr_5 = Test.AddTestRun("reload-succeed")
+tr_5.Processes.Default.Command = 'traffic_ctl config reload'
+tr_5.Processes.Default.Env = tm.Env
+tr_5.Disk.File(remap_cfg_path).WriteOn("")
+tr_5.Disk.File(remap_cfg_path,
+ typename="ats:config").AddLines([f"map http://echo.ex
http://echo.ex:{pv_port}",
+ f"map http://foxtrot.ex
http://foxtrot.ex:{pv_port}",
+ f"map http://golf.ex
http://golf.ex:{pv_port}",
+ f"map http://hotel.ex
http://hotel.ex:{pv_port}",
+ f"map http://india.ex
http://india.ex:{pv_port}"])
+
+tr_6 = Test.AddTestRun("pause")
+tr_6.Processes.Default.Command = 'sleep 5'
+
+tr_7 = Test.AddTestRun("post update charlie")
+tr_7.AddVerifierClientProcess("client_3", replay_file_3,
http_ports=[tm.Variables.port])
+
+tr_8 = Test.AddTestRun("post update golf")
+tr_8.AddVerifierClientProcess("client_4", replay_file_4,
http_ports=[tm.Variables.port])