tests: Improve the reliability of TLSServerRunner (#6632)

This commit is contained in:
Teddy Reed 2020-09-07 08:38:55 -04:00 committed by GitHub
parent 26b53c5b48
commit 3759430a87
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 112 additions and 44 deletions

View File

@ -16,10 +16,12 @@
#include <osquery/core/core.h> #include <osquery/core/core.h>
#include <osquery/core/flags.h> #include <osquery/core/flags.h>
#include <osquery/database/database.h> #include <osquery/database/database.h>
#include <osquery/logger/logger.h>
#include <osquery/process/process.h> #include <osquery/process/process.h>
#include <osquery/remote/tests/test_utils.h> #include <osquery/remote/tests/test_utils.h>
#include <osquery/sql/sql.h> #include <osquery/sql/sql.h>
#include <osquery/tests/test_util.h> #include <osquery/tests/test_util.h>
#include <osquery/utils/conversions/join.h>
#include <osquery/utils/json/json.h> #include <osquery/utils/json/json.h>
#include <osquery/utils/system/time.h> #include <osquery/utils/system/time.h>
@ -33,75 +35,120 @@ DECLARE_string(tls_server_certs);
DECLARE_string(enroll_secret_path); DECLARE_string(enroll_secret_path);
DECLARE_bool(disable_caching); DECLARE_bool(disable_caching);
Status TLSServerRunner::startAndSetScript(const std::string& port,
const std::string& server_cert) {
auto script = (getTestHelperScriptsDirectory() / "test_http_server.py");
auto config_dir = getTestConfigDirectory();
std::vector<std::string> args = {
script.make_preferred().string(),
"--tls",
"--verbose",
"--test-configs-dir",
config_dir.make_preferred().string(),
};
if (!server_cert.empty()) {
args.push_back("--cert");
args.push_back(server_cert);
}
args.push_back(port);
const auto cmd = osquery::join(args, " ");
server_ = PlatformProcess::launchTestPythonScript(cmd);
if (server_ == nullptr) {
return Status::failure("Cannot create test python script: " + cmd);
}
return Status::success();
}
Status TLSServerRunner::getListeningPortPid(const std::string& port,
std::string& pid) {
// Reset the output.
pid.clear();
std::string q = "select pid from listening_ports where port = '" + port + "'";
auto caching = FLAGS_disable_caching;
FLAGS_disable_caching = true;
auto results = SQL(q);
FLAGS_disable_caching = caching;
if (results.rows().empty()) {
return Status::failure("No pid listening on port: " + port);
}
const auto& first_row = results.rows()[0];
pid = first_row.at("pid");
return Status::success();
}
bool TLSServerRunner::start(const std::string& server_cert) { bool TLSServerRunner::start(const std::string& server_cert) {
auto& self = instance(); auto& self = instance();
if (self.server_ != nullptr) { if (self.server_ != nullptr) {
return true; return true;
} }
int max_retry = 5; // We need to pick a 'random' port.
int retry = 0;
bool started = false;
std::srand((unsigned int)getUnixTime()); std::srand((unsigned int)getUnixTime());
bool started = false;
const size_t max_retry = 3;
size_t retry = 0;
while (retry < max_retry) { while (retry < max_retry) {
// Pick a port in an ephemeral range at random. // Pick a port in an ephemeral range at random.
self.port_ = std::to_string(std::rand() % 10000 + 20000); self.port_ = std::to_string(std::rand() % 10000 + 20000);
// Fork then exec a shell. {
auto python_server_path = // Check that the port is not used.
(getTestHelperScriptsDirectory() / "test_http_server.py"); std::string pid;
auto test_config_dir = getTestConfigDirectory(); if (self.getListeningPortPid(self.port_, pid).ok()) {
auto python_server_cmd = python_server_path.make_preferred().string() + // Another process is listening on this port.
" --tls --verbose " + " --test-configs-dir " + continue;
test_config_dir.make_preferred().string(); }
if (!server_cert.empty()) {
python_server_cmd += " --cert " + server_cert;
} }
python_server_cmd += " " + self.port_; auto status = self.startAndSetScript(self.port_, server_cert);
if (!status.ok()) {
self.server_ = PlatformProcess::launchTestPythonScript(python_server_cmd); // This is an unexpected problem, retry without waiting.
if (self.server_ == nullptr) { LOG(WARNING) << status.getMessage();
return started; continue;
} }
size_t delay = 0; size_t delay = 0;
std::string query = // Expect to wait for the server to listen on the port.
"select pid from listening_ports where port = '" + self.port_ + "'"; while (delay < max_retry * 2 * 1000) {
std::string pid;
bool port_occupied = false; status = self.getListeningPortPid(self.port_, pid);
// Wait for the server to listen on the port if (!status.ok()) {
while (delay < 2 * 1000) { // No pid listening, we should wait longer.
auto caching = FLAGS_disable_caching; LOG(WARNING) << status.getMessage();
FLAGS_disable_caching = true;
auto results = SQL(query);
FLAGS_disable_caching = caching;
if (!results.rows().empty()) {
const auto& first_row = results.rows()[0];
if (first_row.at("pid") == std::to_string(self.server_->pid())) {
started = true;
} else {
port_occupied = true;
}
break;
}
sleepFor(100); sleepFor(100);
delay += 100; delay += 100;
continue;
} }
// We only want to retry if it's an issue of port collision if (pid == std::to_string(self.server_->pid())) {
if (started || !port_occupied) { started = true;
} else {
// Another process is listening on this pid.
LOG(WARNING) << "Another process is listening on port: " << self.port_;
}
break; break;
} }
if (started) {
break;
}
self.stop();
sleepFor(1000); sleepFor(1000);
++retry; ++retry;
} }
return started; if (!started) {
return false;
}
return true;
} }
void TLSServerRunner::setClientConfig() { void TLSServerRunner::setClientConfig() {

View File

@ -38,12 +38,33 @@ class TLSServerRunner : private boost::noncopyable {
return instance().port_; return instance().port_;
} }
/// Start the server if it hasn't started already. /**
* Start the server if it hasn't started already.
*
* A failure status is returned on error.
*/
static bool start(const std::string& server_cert = {}); static bool start(const std::string& server_cert = {});
/// Stop the service when the process exits. /// Stop the service when the process exits.
static void stop(); static void stop();
private:
/**
* The output "pid" will be empty if no listening port is found.
*
* A failure status will also be returned if no pid is found.
*/
Status getListeningPortPid(const std::string& port, std::string& pid);
/**
* Try to start the TLS server and set ::server_ to the process.
*
* A failure status is returned if the process is not created.
* This does not check that the port was bound.
*/
Status startAndSetScript(const std::string& port,
const std::string& server_cert);
private: private:
/// Current server handle. /// Current server handle.
std::shared_ptr<PlatformProcess> server_{nullptr}; std::shared_ptr<PlatformProcess> server_{nullptr};