From d9fc605765c843316558347a85d728a2f178c8c7 Mon Sep 17 00:00:00 2001 From: Matt Klein Date: Mon, 13 Feb 2017 12:21:33 -0800 Subject: [PATCH] orderly cm/cds/rds initialization We have a fairly complicated init situation at this point in which we need to do the following: 1) Initialize static cm clusters 2) Initialize cds cm clusters 3) Initialize rds 4) Start listening This change sets up a generic init manager framework that allows extensions to initialize after cm init but before we start listening. This commit also adds "integration tests" for CDS/RDS in that we specify them in one of the integration test configs. Nothing is currently implemented but it at least checks that the server initialized correctly. fixes https://github.com/lyft/envoy/issues/465 --- include/envoy/init/init.h | 36 ++++++++++ include/envoy/server/instance.h | 11 +++ source/common/router/rds_impl.cc | 30 +++++--- source/common/router/rds_impl.h | 29 +++++--- .../config/network/http_connection_manager.cc | 3 +- source/server/server.cc | 69 ++++++++++++++----- source/server/server.h | 21 ++++++ test/CMakeLists.txt | 2 + test/common/router/rds_impl_test.cc | 32 +++++++-- test/config/integration/server.json | 35 ++++++++++ test/mocks/init/mocks.cc | 25 +++++++ test/mocks/init/mocks.h | 37 ++++++++++ test/mocks/server/mocks.cc | 1 + test/mocks/server/mocks.h | 3 + test/server/server_test.cc | 32 +++++++++ 15 files changed, 324 insertions(+), 42 deletions(-) create mode 100644 include/envoy/init/init.h create mode 100644 test/mocks/init/mocks.cc create mode 100644 test/mocks/init/mocks.h create mode 100644 test/server/server_test.cc diff --git a/include/envoy/init/init.h b/include/envoy/init/init.h new file mode 100644 index 000000000000..1db3957bafd2 --- /dev/null +++ b/include/envoy/init/init.h @@ -0,0 +1,36 @@ +#pragma once + +#include "envoy/common/pure.h" + +namespace Init { + +/** + * A single initialization target. + */ +class Target { +public: + virtual ~Target() {} + + /** + * Called when the target should begin its own initialization. + * @param callback supplies the callback to invoke when the target has completed its + * initialization. + */ + virtual void initialize(std::function callback) PURE; +}; + +/** + * A manager that initializes multiple targets. + */ +class Manager { +public: + virtual ~Manager() {} + + /** + * Register a target to be initialized in the future. The manager will call initialize() on + * each target at some point in the future. + */ + virtual void registerTarget(Target& target) PURE; +}; + +} // Init diff --git a/include/envoy/server/instance.h b/include/envoy/server/instance.h index 63bb625415e6..a639c4d4bb47 100644 --- a/include/envoy/server/instance.h +++ b/include/envoy/server/instance.h @@ -2,6 +2,7 @@ #include "envoy/access_log/access_log.h" #include "envoy/api/api.h" +#include "envoy/init/init.h" #include "envoy/local_info/local_info.h" #include "envoy/ratelimit/ratelimit.h" #include "envoy/runtime/runtime.h" @@ -103,6 +104,16 @@ class Instance { */ virtual HotRestart& hotRestart() PURE; + /** + * @return the server's init manager. This can be used for extensions that need to initialize + * after cluster manager init but before the server starts listening. All extensions + * should register themselves during configuration load. initialize() will be called on + * each registered target after cluster manager init but before the server starts + * listening. Once all targets have initialized and invoked their callbacks, the server + * will start listening. + */ + virtual Init::Manager& initManager() PURE; + /** * @return the server's CLI options. */ diff --git a/source/common/router/rds_impl.cc b/source/common/router/rds_impl.cc index 1b0f38aa0f49..7b7ba26e3f9d 100644 --- a/source/common/router/rds_impl.cc +++ b/source/common/router/rds_impl.cc @@ -6,12 +6,11 @@ namespace Router { -RouteConfigProviderPtr -RouteConfigProviderUtil::create(const Json::Object& config, Runtime::Loader& runtime, - Upstream::ClusterManager& cm, Event::Dispatcher& dispatcher, - Runtime::RandomGenerator& random, - const LocalInfo::LocalInfo& local_info, Stats::Scope& scope, - const std::string& stat_prefix, ThreadLocal::Instance& tls) { +RouteConfigProviderPtr RouteConfigProviderUtil::create( + const Json::Object& config, Runtime::Loader& runtime, Upstream::ClusterManager& cm, + Event::Dispatcher& dispatcher, Runtime::RandomGenerator& random, + const LocalInfo::LocalInfo& local_info, Stats::Scope& scope, const std::string& stat_prefix, + ThreadLocal::Instance& tls, Init::Manager& init_manager) { bool has_rds = config.hasObject("rds"); bool has_route_config = config.hasObject("route_config"); if (!(has_rds ^ has_route_config)) { @@ -23,13 +22,11 @@ RouteConfigProviderUtil::create(const Json::Object& config, Runtime::Loader& run return RouteConfigProviderPtr{ new StaticRouteConfigProviderImpl(*config.getObject("route_config"), runtime, cm)}; } else { - // TODO: Ordered initialization of RDS: 1) CDS/clusters, 2) RDS, 3) start listening. This - // will be done in a follow up where we will add a formal init handler in the server. Json::ObjectPtr rds_config = config.getObject("rds"); rds_config->validateSchema(Json::Schema::RDS_CONFIGURATION_SCHEMA); std::unique_ptr provider{new RdsRouteConfigProviderImpl( *rds_config, runtime, cm, dispatcher, random, local_info, scope, stat_prefix, tls)}; - provider->initialize(); + provider->registerInitTarget(init_manager); return std::move(provider); } } @@ -55,6 +52,10 @@ RdsRouteConfigProviderImpl::RdsRouteConfigProviderImpl( throw EnvoyException("rds: setting --service-cluster and --service-node are required"); } + if (!cm.get(remote_cluster_name_)) { + throw EnvoyException(fmt::format("rds: unknown remote cluster '{}'", remote_cluster_name_)); + } + ConfigPtr initial_config(new NullConfigImpl()); tls_.set(tls_slot_, [initial_config](Event::Dispatcher&) -> ThreadLocal::ThreadLocalObjectPtr { return ThreadLocal::ThreadLocalObjectPtr{new ThreadLocalConfig(initial_config)}; @@ -94,6 +95,13 @@ void RdsRouteConfigProviderImpl::parseResponse(const Http::Message& response) { stats_.update_success_.inc(); } +void RdsRouteConfigProviderImpl::onFetchComplete() { + if (initialize_callback_) { + initialize_callback_(); + initialize_callback_ = nullptr; + } +} + void RdsRouteConfigProviderImpl::onFetchFailure(EnvoyException* e) { stats_.update_failure_.inc(); if (e) { @@ -103,4 +111,8 @@ void RdsRouteConfigProviderImpl::onFetchFailure(EnvoyException* e) { } } +void RdsRouteConfigProviderImpl::registerInitTarget(Init::Manager& init_manager) { + init_manager.registerTarget(*this); +} + } // Router diff --git a/source/common/router/rds_impl.h b/source/common/router/rds_impl.h index 75bdf7555312..f7be67dfb721 100644 --- a/source/common/router/rds_impl.h +++ b/source/common/router/rds_impl.h @@ -1,5 +1,6 @@ #pragma once +#include "envoy/init/init.h" #include "envoy/json/json_object.h" #include "envoy/local_info/local_info.h" #include "envoy/router/rds.h" @@ -23,7 +24,8 @@ class RouteConfigProviderUtil { Upstream::ClusterManager& cm, Event::Dispatcher& dispatcher, Runtime::RandomGenerator& random, const LocalInfo::LocalInfo& local_info, Stats::Scope& scope, - const std::string& stat_prefix, ThreadLocal::Instance& tls); + const std::string& stat_prefix, ThreadLocal::Instance& tls, + Init::Manager& init_manager); }; /** @@ -64,16 +66,15 @@ struct RdsStats { * the RDS API. */ class RdsRouteConfigProviderImpl : public RouteConfigProvider, + public Init::Target, Http::RestApiFetcher, Logger::Loggable { public: - RdsRouteConfigProviderImpl(const Json::Object& config, Runtime::Loader& runtime, - Upstream::ClusterManager& cm, Event::Dispatcher& dispatcher, - Runtime::RandomGenerator& random, - const LocalInfo::LocalInfo& local_info, Stats::Scope& scope, - const std::string& stat_prefix, ThreadLocal::Instance& tls); - - void initialize() { RestApiFetcher::initialize(); } + // Init::Target + void initialize(std::function callback) override { + initialize_callback_ = callback; + RestApiFetcher::initialize(); + } // Router::RouteConfigProvider Router::ConfigPtr config() override; @@ -81,7 +82,7 @@ class RdsRouteConfigProviderImpl : public RouteConfigProvider, // Http::RestApiFetcher void createRequest(Http::Message& request) override; void parseResponse(const Http::Message& response) override; - void onFetchComplete() override {} + void onFetchComplete() override; void onFetchFailure(EnvoyException* e) override; private: @@ -94,6 +95,13 @@ class RdsRouteConfigProviderImpl : public RouteConfigProvider, ConfigPtr config_; }; + RdsRouteConfigProviderImpl(const Json::Object& config, Runtime::Loader& runtime, + Upstream::ClusterManager& cm, Event::Dispatcher& dispatcher, + Runtime::RandomGenerator& random, + const LocalInfo::LocalInfo& local_info, Stats::Scope& scope, + const std::string& stat_prefix, ThreadLocal::Instance& tls); + void registerInitTarget(Init::Manager& init_manager); + Runtime::Loader& runtime_; const LocalInfo::LocalInfo& local_info_; ThreadLocal::Instance& tls_; @@ -102,6 +110,9 @@ class RdsRouteConfigProviderImpl : public RouteConfigProvider, bool initialized_{}; uint64_t last_config_hash_{}; RdsStats stats_; + std::function initialize_callback_; + + friend class RouteConfigProviderUtil; }; } // Router diff --git a/source/server/config/network/http_connection_manager.cc b/source/server/config/network/http_connection_manager.cc index bf6754659da5..81428bcd1574 100644 --- a/source/server/config/network/http_connection_manager.cc +++ b/source/server/config/network/http_connection_manager.cc @@ -74,7 +74,8 @@ HttpConnectionManagerConfig::HttpConnectionManagerConfig(const Json::Object& con route_config_provider_ = Router::RouteConfigProviderUtil::create( config, server.runtime(), server.clusterManager(), server.dispatcher(), server.random(), - server.localInfo(), server.stats(), stats_prefix_, server.threadLocal()); + server.localInfo(), server.stats(), stats_prefix_, server.threadLocal(), + server.initManager()); if (config.hasObject("use_remote_address")) { use_remote_address_ = config.getBoolean("use_remote_address"); diff --git a/source/server/server.cc b/source/server/server.cc index 8bd35330fcb7..4441320e20b1 100644 --- a/source/server/server.cc +++ b/source/server/server.cc @@ -20,6 +20,32 @@ namespace Server { +void InitManagerImpl::initialize(std::function callback) { + ASSERT(state_ == State::NotInitialized); + if (targets_.empty()) { + callback(); + state_ = State::Initialized; + } else { + callback_ = callback; + state_ = State::Initializing; + for (auto target : targets_) { + target->initialize([this, target]() -> void { + ASSERT(std::find(targets_.begin(), targets_.end(), target) != targets_.end()); + targets_.remove(target); + if (targets_.empty()) { + state_ = State::Initialized; + callback_(); + } + }); + } + } +} + +void InitManagerImpl::registerTarget(Init::Target& target) { + ASSERT(state_ == State::NotInitialized); + targets_.push_back(&target); +} + InstanceImpl::InstanceImpl(Options& options, TestHooks& hooks, HotRestart& restarter, Stats::StoreRoot& store, Thread::BasicLockable& access_log_lock, ComponentFactory& component_factory, @@ -50,6 +76,7 @@ InstanceImpl::InstanceImpl(Options& options, TestHooks& hooks, HotRestart& resta initialize(options, hooks, component_factory); } catch (const EnvoyException& e) { log().critical("error initializing configuration '{}': {}", options.configPath(), e.what()); + thread_local_.shutdownThread(); exit(1); } } @@ -212,28 +239,32 @@ void InstanceImpl::initialize(Options& options, TestHooks& hooks, // Register for cluster manager init notification. We don't start serving worker traffic until // upstream clusters are initialized which may involve running the event loop. Note however that - // if there are only static clusters this will fire immediately. + // this can fire immediately if all clusters have already initialized. clusterManager().setInitializedCb([this, &hooks]() -> void { - log().warn("all clusters initialized. starting workers"); - for (const WorkerPtr& worker : workers_) { - try { - worker->initializeConfiguration(*config_, socket_map_); - } catch (const Network::CreateListenerException& e) { - // It is possible that we fail to start listening on a port, even though we were able to - // bind to it above. This happens when there is a race between two applications to listen - // on the same port. In general if we can't initialize the worker configuration just print - // the error and exit cleanly without crashing. - log().critical("shutting down due to error initializing worker configuration: {}", - e.what()); - shutdown(); - } + log().warn("all clusters initialized. initializing init manager"); + init_manager_.initialize([this, &hooks]() -> void { startWorkers(hooks); }); + }); +} + +void InstanceImpl::startWorkers(TestHooks& hooks) { + log().warn("all dependencies initialized. starting workers"); + for (const WorkerPtr& worker : workers_) { + try { + worker->initializeConfiguration(*config_, socket_map_); + } catch (const Network::CreateListenerException& e) { + // It is possible that we fail to start listening on a port, even though we were able to + // bind to it above. This happens when there is a race between two applications to listen + // on the same port. In general if we can't initialize the worker configuration just print + // the error and exit cleanly without crashing. + log().critical("shutting down due to error initializing worker configuration: {}", e.what()); + shutdown(); } + } - // At this point we are ready to take traffic and all listening ports are up. Notify our parent - // if applicable that they can stop listening and drain. - restarter_.drainParentListeners(); - hooks.onServerInitialized(); - }); + // At this point we are ready to take traffic and all listening ports are up. Notify our parent + // if applicable that they can stop listening and drain. + restarter_.drainParentListeners(); + hooks.onServerInitialized(); } Runtime::LoaderPtr InstanceUtil::createRuntime(Instance& server, diff --git a/source/server/server.h b/source/server/server.h index aeacea8ab18e..4015f327c6b5 100644 --- a/source/server/server.h +++ b/source/server/server.h @@ -68,6 +68,24 @@ class InstanceUtil : Logger::Loggable { static Runtime::LoaderPtr createRuntime(Instance& server, Server::Configuration::Initial& config); }; +/** + * Implementation of Init::Manager for use during post cluster manager init / pre listening. + */ +class InitManagerImpl : public Init::Manager { +public: + void initialize(std::function callback); + + // Init::Manager + void registerTarget(Init::Target& target) override; + +private: + enum class State { NotInitialized, Initializing, Initialized }; + + std::list targets_; + State state_{State::NotInitialized}; + std::function callback_; +}; + /** * This is the actual full standalone server which stiches together various common components. */ @@ -95,6 +113,7 @@ class InstanceImpl : Logger::Loggable, public Instance { int getListenSocketFd(uint32_t port) override; void getParentStats(HotRestart::GetParentStatsInfo& info) override; HotRestart& hotRestart() override { return restarter_; } + Init::Manager& initManager() override { return init_manager_; } Runtime::RandomGenerator& random() override { return random_generator_; } RateLimit::ClientPtr rateLimitClient(const Optional& timeout) override { @@ -118,6 +137,7 @@ class InstanceImpl : Logger::Loggable, public Instance { void initializeStatSinks(); void loadServerFlags(const Optional& flags_path); uint64_t numConnections(); + void startWorkers(TestHooks& hooks); Options& options_; HotRestart& restarter_; @@ -143,6 +163,7 @@ class InstanceImpl : Logger::Loggable, public Instance { const LocalInfo::LocalInfo& local_info_; DrainManagerPtr drain_manager_; AccessLog::AccessLogManagerImpl access_log_manager_; + InitManagerImpl init_manager_; }; } // Server diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 5a4b70eb3c89..b6202771adc1 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -129,6 +129,7 @@ add_executable(envoy-test mocks/filesystem/mocks.cc mocks/grpc/mocks.cc mocks/http/mocks.cc + mocks/init/mocks.cc mocks/local_info/mocks.cc mocks/network/mocks.cc mocks/ratelimit/mocks.cc @@ -150,6 +151,7 @@ add_executable(envoy-test server/http/admin_test.cc server/http/health_check_test.cc server/options_impl_test.cc + server/server_test.cc test_common/printers.cc test_common/utility.cc) diff --git a/test/common/router/rds_impl_test.cc b/test/common/router/rds_impl_test.cc index 5cb90469edb6..bec7c4a95d82 100644 --- a/test/common/router/rds_impl_test.cc +++ b/test/common/router/rds_impl_test.cc @@ -2,6 +2,7 @@ #include "common/json/json_loader.h" #include "common/router/rds_impl.h" +#include "test/mocks/init/mocks.h" #include "test/mocks/local_info/mocks.h" #include "test/mocks/thread_local/mocks.h" #include "test/mocks/upstream/mocks.h" @@ -34,9 +35,11 @@ class RdsImplTest : public testing::Test { Json::ObjectPtr config = Json::Factory::LoadFromString(config_json); interval_timer_ = new Event::MockTimer(&dispatcher_); - expectRequest(); + EXPECT_CALL(init_manager_, registerTarget(_)); rds_ = RouteConfigProviderUtil::create(*config, runtime_, cm_, dispatcher_, random_, - local_info_, store_, "foo.", tls_); + local_info_, store_, "foo.", tls_, init_manager_); + expectRequest(); + init_manager_.initialize(); } void expectRequest() { @@ -62,6 +65,7 @@ class RdsImplTest : public testing::Test { NiceMock local_info_; Stats::IsolatedStoreImpl store_; NiceMock tls_; + NiceMock init_manager_; Http::MockAsyncClientRequest request_; RouteConfigProviderPtr rds_; Event::MockTimer* interval_timer_{}; @@ -78,7 +82,7 @@ TEST_F(RdsImplTest, RdsAndStatic) { Json::ObjectPtr config = Json::Factory::LoadFromString(config_json); EXPECT_THROW(RouteConfigProviderUtil::create(*config, runtime_, cm_, dispatcher_, random_, - local_info_, store_, "foo.", tls_), + local_info_, store_, "foo.", tls_, init_manager_), EnvoyException); } @@ -97,7 +101,25 @@ TEST_F(RdsImplTest, LocalInfoNotDefined) { local_info_.node_name_ = ""; interval_timer_ = new Event::MockTimer(&dispatcher_); EXPECT_THROW(RouteConfigProviderUtil::create(*config, runtime_, cm_, dispatcher_, random_, - local_info_, store_, "foo.", tls_), + local_info_, store_, "foo.", tls_, init_manager_), + EnvoyException); +} + +TEST_F(RdsImplTest, UnknownCluster) { + std::string config_json = R"EOF( + { + "rds": { + "cluster": "foo_cluster", + "route_config_name": "foo_route_config" + } + } + )EOF"; + + Json::ObjectPtr config = Json::Factory::LoadFromString(config_json); + ON_CALL(cm_, get("foo_cluster")).WillByDefault(Return(nullptr)); + interval_timer_ = new Event::MockTimer(&dispatcher_); + EXPECT_THROW(RouteConfigProviderUtil::create(*config, runtime_, cm_, dispatcher_, random_, + local_info_, store_, "foo.", tls_, init_manager_), EnvoyException); } @@ -120,6 +142,7 @@ TEST_F(RdsImplTest, Basic) { Http::HeaderMapPtr{new Http::TestHeaderMapImpl{{":status", "200"}}})); message->body(Buffer::InstancePtr{new Buffer::OwnedImpl(response1_json)}); + EXPECT_CALL(init_manager_.initialized_, ready()); EXPECT_CALL(*interval_timer_, enableTimer(_)); callbacks_->onSuccess(std::move(message)); EXPECT_EQ(nullptr, rds_->config()->route(Http::TestHeaderMapImpl{{":authority", "foo"}}, 0)); @@ -201,6 +224,7 @@ TEST_F(RdsImplTest, Failure) { Http::HeaderMapPtr{new Http::TestHeaderMapImpl{{":status", "200"}}})); message->body(Buffer::InstancePtr{new Buffer::OwnedImpl(response1_json)}); + EXPECT_CALL(init_manager_.initialized_, ready()); EXPECT_CALL(*interval_timer_, enableTimer(_)); callbacks_->onSuccess(std::move(message)); diff --git a/test/config/integration/server.json b/test/config/integration/server.json index 309abb805e88..4f367434d2f7 100644 --- a/test/config/integration/server.json +++ b/test/config/integration/server.json @@ -223,6 +223,25 @@ } } ] + }, + { + "port": 10005, + "filters": [ + { + "type": "read", + "name": "http_connection_manager", + "config": { + "codec_type": "http1", + "stat_prefix": "rds_dummy", + "rds": { + "cluster": "rds", + "route_config_name": "foo" + }, + "filters": [ + { "type": "decoder", "name": "router", "config": {} } + ] + } + }] }], "admin": { "access_log_path": "/dev/null", "port": 10003 }, @@ -248,7 +267,23 @@ }, "cluster_manager": { + "cds": { + "cluster": { + "name": "cds", + "connect_timeout_ms": 250, + "type": "static", + "lb_type": "round_robin", + "hosts": [{"url": "tcp://127.0.0.1:12000"}] + } + }, "clusters": [ + { + "name": "rds", + "connect_timeout_ms": 250, + "type": "static", + "lb_type": "round_robin", + "hosts": [{"url": "tcp://127.0.0.1:12000"}] + }, { "name": "cluster_1", "connect_timeout_ms": 250, diff --git a/test/mocks/init/mocks.cc b/test/mocks/init/mocks.cc new file mode 100644 index 000000000000..07f4e0b9564f --- /dev/null +++ b/test/mocks/init/mocks.cc @@ -0,0 +1,25 @@ +#include "mocks.h" + +using testing::_; +using testing::Invoke; + +namespace Init { + +MockTarget::MockTarget() { + ON_CALL(*this, initialize(_)) + .WillByDefault(Invoke([this](std::function callback) -> void { + EXPECT_EQ(nullptr, callback_); + callback_ = callback; + })); +} + +MockTarget::~MockTarget() {} + +MockManager::MockManager() { + ON_CALL(*this, registerTarget(_)) + .WillByDefault(Invoke([this](Target& target) -> void { targets_.push_back(&target); })); +} + +MockManager::~MockManager() {} + +} // Init diff --git a/test/mocks/init/mocks.h b/test/mocks/init/mocks.h new file mode 100644 index 000000000000..400786ca9412 --- /dev/null +++ b/test/mocks/init/mocks.h @@ -0,0 +1,37 @@ +#pragma once + +#include "envoy/init/init.h" + +#include "test/mocks/common.h" + +namespace Init { + +class MockTarget : public Target { +public: + MockTarget(); + ~MockTarget(); + + MOCK_METHOD1(initialize, void(std::function callback)); + + std::function callback_; +}; + +class MockManager : public Manager { +public: + MockManager(); + ~MockManager(); + + void initialize() { + for (auto target : targets_) { + target->initialize([this]() -> void { initialized_.ready(); }); + } + } + + // Init::Manager + MOCK_METHOD1(registerTarget, void(Target& target)); + + std::list targets_; + ReadyWatcher initialized_; +}; + +} // Init diff --git a/test/mocks/server/mocks.cc b/test/mocks/server/mocks.cc index 965360f4dfdc..748bfa92cea9 100644 --- a/test/mocks/server/mocks.cc +++ b/test/mocks/server/mocks.cc @@ -37,6 +37,7 @@ MockInstance::MockInstance() : ssl_context_manager_(runtime_loader_) { ON_CALL(*this, localInfo()).WillByDefault(ReturnRef(local_info_)); ON_CALL(*this, options()).WillByDefault(ReturnRef(options_)); ON_CALL(*this, drainManager()).WillByDefault(ReturnRef(drain_manager_)); + ON_CALL(*this, initManager()).WillByDefault(ReturnRef(init_manager_)); } MockInstance::~MockInstance() {} diff --git a/test/mocks/server/mocks.h b/test/mocks/server/mocks.h index ebaf26c39229..a2275d1eacc7 100644 --- a/test/mocks/server/mocks.h +++ b/test/mocks/server/mocks.h @@ -13,6 +13,7 @@ #include "test/mocks/access_log/mocks.h" #include "test/mocks/api/mocks.h" #include "test/mocks/http/mocks.h" +#include "test/mocks/init/mocks.h" #include "test/mocks/local_info/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/runtime/mocks.h" @@ -101,6 +102,7 @@ class MockInstance : public Instance { MOCK_METHOD1(getParentStats, void(HotRestart::GetParentStatsInfo&)); MOCK_METHOD0(healthCheckFailed, bool()); MOCK_METHOD0(hotRestart, HotRestart&()); + MOCK_METHOD0(initManager, Init::Manager&()); MOCK_METHOD0(options, Options&()); MOCK_METHOD0(random, Runtime::RandomGenerator&()); MOCK_METHOD0(rateLimitClient_, RateLimit::Client*()); @@ -131,6 +133,7 @@ class MockInstance : public Instance { testing::NiceMock options_; testing::NiceMock random_; testing::NiceMock local_info_; + testing::NiceMock init_manager_; }; } // Server diff --git a/test/server/server_test.cc b/test/server/server_test.cc new file mode 100644 index 000000000000..4534d14dfab5 --- /dev/null +++ b/test/server/server_test.cc @@ -0,0 +1,32 @@ +#include "server/server.h" + +#include "test/mocks/common.h" +#include "test/mocks/init/mocks.h" + +using testing::_; +using testing::InSequence; + +namespace Server { + +TEST(InitManagerImplTest, NoTargets) { + InitManagerImpl manager; + ReadyWatcher initialized; + + EXPECT_CALL(initialized, ready()); + manager.initialize([&]() -> void { initialized.ready(); }); +} + +TEST(InitManagerImplTest, Targets) { + InSequence s; + InitManagerImpl manager; + Init::MockTarget target; + ReadyWatcher initialized; + + manager.registerTarget(target); + EXPECT_CALL(target, initialize(_)); + manager.initialize([&]() -> void { initialized.ready(); }); + EXPECT_CALL(initialized, ready()); + target.callback_(); +} + +} // Server