Improve extensions integration testing

This commit is contained in:
Teddy Reed 2015-03-13 18:18:18 -07:00
parent aeaee645cd
commit 1170887d56
6 changed files with 102 additions and 77 deletions

View File

@ -45,10 +45,10 @@ const std::map<WatchdogLimitType, std::vector<size_t> > kWatchdogLimits = {
const std::string kExtensionExtension = ".ext";
FLAG(int32,
watchdog_level,
1,
"Performance limit level (0=loose, 1=normal, 2=restrictive, 3=debug)");
CLI_FLAG(int32,
watchdog_level,
1,
"Performance limit level (0=loose, 1=normal, 2=restrictive, 3=debug)");
CLI_FLAG(bool, disable_watchdog, false, "Disable userland watchdog process");
@ -160,7 +160,7 @@ void Watcher::addExtensionPath(const std::string& path) {
}
bool WatcherRunner::ok() {
::sleep(getWorkerLimit(INTERVAL));
interruptableSleep(getWorkerLimit(INTERVAL) * 1000);
// Watcher is OK to run if a worker or at least one extension exists.
return (Watcher::getWorker() >= 0 || Watcher::countExtensions() > 0);
}
@ -290,7 +290,7 @@ void WatcherRunner::createWorker() {
if (Watcher::getState(Watcher::getWorker()).last_respawn_time >
getUnixTime() - getWorkerLimit(RESPAWN_LIMIT)) {
LOG(WARNING) << "osqueryd worker respawning too quickly";
::sleep(getWorkerLimit(RESPAWN_DELAY));
interruptableSleep(getWorkerLimit(RESPAWN_DELAY) * 1000);
}
}
@ -359,6 +359,8 @@ bool WatcherRunner::createExtension(const std::string& extension) {
Flag::getValue("extensions_socket").c_str(),
"--timeout",
Flag::getValue("extensions_timeout").c_str(),
"--interval",
Flag::getValue("extensions_interval").c_str(),
(Flag::getValue("verbose") == "true") ? "--verbose" : (char*)nullptr,
(char*)nullptr,
environ);

View File

@ -26,8 +26,6 @@ namespace fs = boost::filesystem;
namespace osquery {
// Millisecond latency between watcher pings.
const int kWatcherMLatency = 3000;
// Millisecond latency between initalizing manager pings.
const int kExtensionInitializeMLatency = 200;
@ -46,14 +44,19 @@ CLI_FLAG(string,
CLI_FLAG(string,
extensions_autoload,
"",
"/usr/lib/osquery/extensions",
"An optional search path for autoloaded & managed extensions")
CLI_FLAG(string,
extensions_timeout,
"0",
"3",
"Seconds to wait for autoloaded extensions");
CLI_FLAG(string,
extensions_interval,
"3",
"Seconds delay between connectivity checks")
CLI_FLAG(string,
modules_autoload,
"/usr/lib/osquery/modules",
@ -65,6 +68,7 @@ EXTENSION_FLAG_ALIAS(std::string, socket, extensions_socket);
/// An extension manager may not be immediately available.
EXTENSION_FLAG_ALIAS(std::string, timeout, extensions_timeout);
EXTENSION_FLAG_ALIAS(std::string, interval, extensions_interval);
void ExtensionWatcher::enter() {
// Watch the manager, if the socket is removed then the extension will die.
@ -204,8 +208,8 @@ Status startExtension(const std::string& name, const std::string& version) {
Status startExtension(const std::string& name,
const std::string& version,
const std::string& min_sdk_version) {
auto status =
startExtensionWatcher(FLAGS_extensions_socket, kWatcherMLatency, true);
auto latency = atoi(FLAGS_extensions_interval.c_str()) * 1000;
auto status = startExtensionWatcher(FLAGS_extensions_socket, latency, true);
if (!status.ok()) {
// If the threaded watcher fails to start, fail the extension.
return status;
@ -493,10 +497,10 @@ Status startExtensionManager(const std::string& manager_path) {
}
}
auto latency = atoi(FLAGS_extensions_interval.c_str()) * 1000;
// Start a extension manager watcher, if the manager dies, so should we.
Dispatcher::getInstance().addService(
std::make_shared<ExtensionManagerWatcher>(manager_path,
kWatcherMLatency));
std::make_shared<ExtensionManagerWatcher>(manager_path, latency));
// Start the extension manager thread.
Dispatcher::getInstance().addService(

View File

@ -177,7 +177,9 @@ class ExtensionWatcher : public InternalRunnable {
public:
virtual ~ExtensionWatcher() {}
ExtensionWatcher(const std::string& path, size_t interval, bool fatal)
: path_(path), interval_(interval), fatal_(fatal) {}
: path_(path), interval_(interval), fatal_(fatal) {
interval_ = (interval_ < 200) ? 200 : interval_;
}
public:
/// The Dispatcher thread entry point.

View File

@ -13,6 +13,7 @@ from __future__ import print_function
# pyexpect.replwrap will not work with unicode_literals
#from __future__ import unicode_literals
import copy
import os
import psutil
import re
@ -43,10 +44,12 @@ except ImportError:
CONFIG_NAME = "/tmp/osquery-test"
DEFAULT_CONFIG = {
"options": {
"db_path": "%s.db" % CONFIG_NAME,
"database_path": "%s.db" % CONFIG_NAME,
"pidfile": "%s.pid" % CONFIG_NAME,
"config_path": "%s.conf" % CONFIG_NAME,
"extensions_socket": "%s.em" % CONFIG_NAME,
"extensions_interval": "1",
"extensions_timeout": "1",
"watchdog_level": "3",
"disable_logging": "true",
"force": "true",
@ -218,17 +221,20 @@ class ProcessGenerator(object):
'''Helper methods to patch into a unittest'''
generators = []
def _run_daemon(self, config, silent=False):
def _run_daemon(self, options={}, silent=False):
'''Spawn an osquery daemon process'''
global ARGS, CONFIG_NAME
global ARGS, CONFIG_NAME, CONFIG
config = copy.deepcopy(CONFIG)
for option in options.keys():
config["options"][option] = options[option]
utils.write_config(config)
binary = os.path.join(ARGS.build, "osquery", "osqueryd")
config = ["--%s=%s" % (k, v) for k, v in config["options"].items()]
flags = ["--%s=%s" % (k, v) for k, v in config["options"].items()]
daemon = ProcRunner("daemon", binary,
[
"--config_path=%s.conf" % CONFIG_NAME,
"--verbose" if ARGS.verbose else ""
] + config,
] + flags,
silent=silent)
self.generators.append(daemon)
return daemon
@ -243,6 +249,7 @@ class ProcessGenerator(object):
"--socket=%s" % CONFIG["options"]["extensions_socket"],
"--verbose" if ARGS.verbose else "",
"--timeout=%d" % timeout,
"--interval=%d" % 1,
],
silent=silent)
self.generators.append(extension)

View File

@ -37,7 +37,9 @@ import test_base
class EXClient:
transport = None
def __init__(self, path, uuid=None):
def __init__(self, path=None, uuid=None):
if path is None:
path = test_base.CONFIG["options"]["extensions_socket"]
self.path = path
if uuid:
self.path += ".%s" % str(uuid)
@ -103,26 +105,25 @@ class ExtensionTests(test_base.ProcessGenerator, unittest.TestCase):
def test_1_daemon_without_extensions(self):
# Start the daemon without thrift, prefer no watchdog because the tests
# kill the daemon very quickly.
config = test_base.CONFIG.copy()
config["options"]["disable_watchdog"] = "true"
config["options"]["disable_extensions"] = "true"
daemon = self._run_daemon(config)
daemon = self._run_daemon({
"disable_watchdog": True,
"disable_extensions": True,
})
self.assertTrue(daemon.isAlive())
# Now try to connect to the disabled API
client = EXClient(config["options"]["extensions_socket"])
client = EXClient()
self.assertFalse(client.open())
daemon.kill()
def test_2_daemon_api(self):
config = test_base.CONFIG.copy()
config["options"]["disable_watchdog"] = "true"
config["options"]["disable_extensions"] = "false"
daemon = self._run_daemon(config)
daemon = self._run_daemon({
"disable_watchdog": True,
})
self.assertTrue(daemon.isAlive())
# Get a python-based thrift client
client = EXClient(config["options"]["extensions_socket"])
client = EXClient()
expectTrue(client.open)
self.assertTrue(client.open())
em = client.getEM()
@ -150,14 +151,13 @@ class ExtensionTests(test_base.ProcessGenerator, unittest.TestCase):
daemon.kill()
def test_3_example_extension(self):
config = test_base.CONFIG.copy()
config["options"]["disable_watchdog"] = "true"
config["options"]["disable_extensions"] = "false"
daemon = self._run_daemon(config)
daemon = self._run_daemon({
"disable_watchdog": True,
})
self.assertTrue(daemon.isAlive())
# Get a python-based thrift client
client = EXClient(config["options"]["extensions_socket"])
client = EXClient()
expectTrue(client.open)
self.assertTrue(client.open())
em = client.getEM()
@ -180,7 +180,7 @@ class ExtensionTests(test_base.ProcessGenerator, unittest.TestCase):
self.assertEqual(ex_data.min_sdk_version, "0.0.0")
# Get a python-based thrift client to the extension's service
client2 = EXClient(config["options"]["extensions_socket"], ex_uuid)
client2 = EXClient(uuid=ex_uuid)
client2.open()
ex = client2.getEX()
self.assertEqual(ex.ping().code, 0)
@ -214,14 +214,13 @@ class ExtensionTests(test_base.ProcessGenerator, unittest.TestCase):
daemon.kill()
def test_4_extension_dies(self):
config = test_base.CONFIG.copy()
config["options"]["disable_watchdog"] = "true"
config["options"]["disable_extensions"] = "false"
daemon = self._run_daemon(config)
daemon = self._run_daemon({
"disable_watchdog": True,
})
self.assertTrue(daemon.isAlive())
# Get a python-based thrift client
client = EXClient(config["options"]["extensions_socket"])
client = EXClient()
expectTrue(client.open)
self.assertTrue(client.open())
em = client.getEM()
@ -267,14 +266,13 @@ class ExtensionTests(test_base.ProcessGenerator, unittest.TestCase):
self.assertTrue(extension.isAlive())
# Now start a daemon
config = test_base.CONFIG.copy()
config["options"]["disable_watchdog"] = "true"
config["options"]["disable_extensions"] = "false"
daemon = self._run_daemon(config)
daemon = self._run_daemon({
"disable_watchdog": True,
})
self.assertTrue(daemon.isAlive())
# Get a python-based thrift client
client = EXClient(config["options"]["extensions_socket"])
client = EXClient()
expectTrue(client.open)
self.assertTrue(client.open())
em = client.getEM()
@ -288,16 +286,14 @@ class ExtensionTests(test_base.ProcessGenerator, unittest.TestCase):
extension.kill()
def test_6_extensions_autoload(self):
config = test_base.CONFIG.copy()
config["options"]["disable_watchdog"] = "true"
config["options"]["disable_extensions"] = "false"
# Inlcude an extensions autoload path.
config["options"]["extensions_autoload"] = test_base.ARGS.build + "/osquery"
daemon = self._run_daemon(config)
daemon = self._run_daemon({
"disable_watchdog": True,
"extensions_autoload": test_base.ARGS.build + "/osquery",
})
self.assertTrue(daemon.isAlive())
# Get a python-based thrift client
client = EXClient(config["options"]["extensions_socket"])
client = EXClient()
expectTrue(client.open)
self.assertTrue(client.open())
em = client.getEM()
@ -309,19 +305,35 @@ class ExtensionTests(test_base.ProcessGenerator, unittest.TestCase):
client.close()
daemon.kill()
def test_7_external_config(self):
config = test_base.CONFIG.copy()
config["options"]["disable_watchdog"] = "true"
config["options"]["disable_extensions"] = "false"
# Inlcude an extensions autoload path.
config["options"]["extensions_autoload"] = test_base.ARGS.build + "/osquery"
# Now set a config plugin broadcasted by an autoloaded extension.
config["options"]["config_plugin"] = "example"
daemon = self._run_daemon(config)
def test_7_extensions_autoload_watchdog(self):
daemon = self._run_daemon({
"extensions_autoload": test_base.ARGS.build + "/osquery",
})
self.assertTrue(daemon.isAlive())
# Get a python-based thrift client
client = EXClient(config["options"]["extensions_socket"])
client = EXClient()
expectTrue(client.open)
self.assertTrue(client.open())
em = client.getEM()
# The waiting extension should have connected to the daemon.
result = expect(em.extensions, 1)
self.assertEqual(len(result), 1)
client.close()
daemon.kill()
def test_8_external_config(self):
daemon = self._run_daemon({
"disable_watchdog": True,
"extensions_autoload": test_base.ARGS.build + "/osquery",
"config_plugin": "example",
})
self.assertTrue(daemon.isAlive())
# Get a python-based thrift client
client = EXClient()
expectTrue(client.open)
self.assertTrue(client.open())
em = client.getEM()

View File

@ -19,17 +19,17 @@ import test_base
class WatchdogTests(test_base.ProcessGenerator, unittest.TestCase):
def test_1_daemon_without_watchdog(self):
config = test_base.CONFIG.copy()
config["options"]["disable_watchdog"] = "true"
config["options"]["disable_extensions"] = "true"
daemon = self._run_daemon(config)
daemon = self._run_daemon({
"disable_watchdog": True,
"disable_extensions": True,
})
self.assertTrue(daemon.isAlive())
daemon.kill()
def test_2_daemon_with_watchdog(self):
config = test_base.CONFIG.copy()
config["options"]["disable_watchdog"] = "false"
daemon = self._run_daemon(config)
daemon = self._run_daemon({
"disable_watchdog": False,
})
self.assertTrue(daemon.isAlive())
# Check that the daemon spawned a child process
@ -43,12 +43,10 @@ class WatchdogTests(test_base.ProcessGenerator, unittest.TestCase):
def test_3_catastrophic_worker_failure(self):
### Seems to fail often, disable test
return
config = test_base.CONFIG.copy()
# A bad DB path will cause the worker to fail.
config["options"]["db_path"] = "/tmp/this/does/not/exists.db"
config["options"]["disable_watchdog"] = "false"
daemon = self._run_daemon(config)
daemon = self._run_daemon({
"disable_watchdog": False,
"database_path": "/tmp/this/does/not/exists.db",
})
daemon.isAlive(5)
self.assertTrue(daemon.isDead(daemon.pid))
daemon.kill()