update_engine: call res_init and retry one extra time on unresolved host
libcurl error
Based on https://curl.haxx.se/docs/todo.html#updated_DNS_server_while_running:
"If /etc/resolv.conf gets updated while a program using libcurl is running, it
may cause name resolves to fail unless res_init() is called. We should
consider calling res_init() + retry once unconditionally on all name resolve
failures to mitigate against this."
This CL added following behavior:
On libcurl returns CURLE_COULDNT_RESOLVE_HOST error code:
1. we increase the max retry count by 1 for the first time it happens in the
lifetime of an LibcurlHttpFetcher object.
2. we call res_init unconditionally.
We also add UMA metrics to measure whether calling res_init helps
mitigate the unresolved host problem. WIP CL: https://chromium-review.googlesource.com/c/chromium/src/+/1698722
BUG=chromium:982813
TEST=FEATURES="test" emerge-kefka update_engine, tested on a device
Change-Id: Ia894eae93b3a0adbac1a831e657b75cba835dfa0
diff --git a/libcurl_http_fetcher_unittest.cc b/libcurl_http_fetcher_unittest.cc
index 88e48fa..7f00dae 100644
--- a/libcurl_http_fetcher_unittest.cc
+++ b/libcurl_http_fetcher_unittest.cc
@@ -43,6 +43,7 @@
brillo::FakeMessageLoop loop_{nullptr};
FakeHardware fake_hardware_;
LibcurlHttpFetcher libcurl_fetcher_{nullptr, &fake_hardware_};
+ UnresolvedHostStateMachine state_machine_;
};
TEST_F(LibcurlHttpFetcherTest, GetEmptyHeaderValueTest) {
@@ -78,4 +79,60 @@
EXPECT_EQ(header_value, actual_header_value);
}
+TEST_F(LibcurlHttpFetcherTest, InvalidURLTest) {
+ int no_network_max_retries = 1;
+ libcurl_fetcher_.set_no_network_max_retries(no_network_max_retries);
+
+ libcurl_fetcher_.BeginTransfer("not-an-URL");
+ while (loop_.PendingTasks()) {
+ loop_.RunOnce(true);
+ }
+
+ EXPECT_EQ(libcurl_fetcher_.get_no_network_max_retries(),
+ no_network_max_retries);
+}
+
+TEST_F(LibcurlHttpFetcherTest, CouldntResolveHostTest) {
+ int no_network_max_retries = 1;
+ libcurl_fetcher_.set_no_network_max_retries(no_network_max_retries);
+
+ // This test actually sends request to internet but according to
+ // https://tools.ietf.org/html/rfc2606#section-2, .invalid domain names are
+ // reserved and sure to be invalid. Ideally we should mock libcurl or
+ // reorganize LibcurlHttpFetcher so the part that sends request can be mocked
+ // easily.
+ // TODO(xiaochu) Refactor LibcurlHttpFetcher (and its relates) so it's
+ // easier to mock the part that depends on internet connectivity.
+ libcurl_fetcher_.BeginTransfer("https://An-uNres0lvable-uRl.invalid");
+ while (loop_.PendingTasks()) {
+ loop_.RunOnce(true);
+ }
+
+ // If libcurl fails to resolve the name, we call res_init() to reload
+ // resolv.conf and retry exactly once more. See crbug.com/982813 for details.
+ EXPECT_EQ(libcurl_fetcher_.get_no_network_max_retries(),
+ no_network_max_retries + 1);
+}
+
+TEST_F(LibcurlHttpFetcherTest, HttpFetcherStateMachineRetryFailedTest) {
+ state_machine_.UpdateState(true);
+ state_machine_.UpdateState(true);
+ EXPECT_EQ(state_machine_.getState(),
+ UnresolvedHostStateMachine::State::kNotRetry);
+}
+
+TEST_F(LibcurlHttpFetcherTest, HttpFetcherStateMachineRetrySucceedTest) {
+ state_machine_.UpdateState(true);
+ state_machine_.UpdateState(false);
+ EXPECT_EQ(state_machine_.getState(),
+ UnresolvedHostStateMachine::State::kRetriedSuccess);
+}
+
+TEST_F(LibcurlHttpFetcherTest, HttpFetcherStateMachineNoRetryTest) {
+ state_machine_.UpdateState(false);
+ state_machine_.UpdateState(false);
+ EXPECT_EQ(state_machine_.getState(),
+ UnresolvedHostStateMachine::State::kInit);
+}
+
} // namespace chromeos_update_engine