From 8ad4f3474057bbf9db66035bc4c8cd6daf7b60a6 Mon Sep 17 00:00:00 2001 From: Reuven Lazarus Date: Fri, 26 May 2017 12:49:06 -0400 Subject: [PATCH] validation: Add config validation mode. (#499) (#863) This adds a --mode command-line flag. --mode=serve is the default and can be omitted; in that mode Envoy runs as it always has. With --mode=validate Envoy checks that the configuration file in --config-file is valid, then exits. "Valid" means that it performs the JSON schema check as usual, but also initializes as much of its internal state as possible, exiting with any errors as it would under --mode=serve. When initialization is complete, instead of listening for traffic, Envoy prints an "OK" message to stderr and exits, returning success. In validation mode, to the extent possible, Envoy avoids interfering with its environment. For example, the hot-restart process is skipped, so validation can safely be run on the same machine as a serving Envoy. Validation mode also doesn't send any upstream traffic or open any listeners. It does attempt to read any files referenced in the config, like certs and private keys, so validation will fail if those files aren't located at the expected paths. A future "lite validation" mode, that mocks out the filesystem as well as the network, is not yet implemented as of this patch. --- docs/operations/cli.rst | 11 ++ include/envoy/server/options.h | 28 +++++ include/envoy/upstream/BUILD | 3 + include/envoy/upstream/cluster_manager.h | 15 +++ source/common/common/assert.h | 4 +- source/common/http/async_client_impl.cc | 3 + source/common/http/async_client_impl.h | 1 + .../common/upstream/cluster_manager_impl.cc | 8 ++ source/common/upstream/cluster_manager_impl.h | 5 + source/exe/BUILD | 2 + source/exe/main.cc | 22 +++- source/server/BUILD | 2 +- source/server/config_validation/BUILD | 98 ++++++++++++++++ source/server/config_validation/api.cc | 16 +++ source/server/config_validation/api.h | 23 ++++ .../server/config_validation/async_client.cc | 17 +++ .../server/config_validation/async_client.h | 25 ++++ .../config_validation/cluster_manager.cc | 51 +++++++++ .../config_validation/cluster_manager.h | 60 ++++++++++ .../config_validation/connection_handler.cc | 27 +++++ .../config_validation/connection_handler.h | 40 +++++++ source/server/config_validation/dispatcher.cc | 37 ++++++ source/server/config_validation/dispatcher.h | 31 +++++ source/server/config_validation/dns.cc | 13 +++ source/server/config_validation/dns.h | 23 ++++ source/server/config_validation/server.cc | 77 +++++++++++++ source/server/config_validation/server.h | 107 ++++++++++++++++++ source/server/configuration_impl.cc | 16 ++- source/server/configuration_impl.h | 4 +- source/server/options_impl.cc | 13 +++ source/server/options_impl.h | 2 + source/server/server.cc | 9 +- source/server/server.h | 2 + .../upstream/cluster_manager_impl_test.cc | 15 +++ test/config_test/config_test.cc | 9 +- test/integration/server.h | 12 ++ test/mocks/server/BUILD | 2 + test/mocks/server/mocks.h | 3 + test/run_envoy_bazel_coverage.sh | 2 +- test/server/config_validation/BUILD | 68 +++++++++++ .../config_validation/async_client_test.cc | 25 ++++ .../config_validation/cluster_manager_test.cc | 58 ++++++++++ .../connection_handler_test.cc | 29 +++++ test/server/config_validation/server_test.cc | 45 ++++++++ test/server/configuration_impl_test.cc | 95 ++++++++-------- test/server/options_impl_test.cc | 10 +- test/server/server_test.cc | 11 -- 47 files changed, 1092 insertions(+), 87 deletions(-) create mode 100644 source/server/config_validation/BUILD create mode 100644 source/server/config_validation/api.cc create mode 100644 source/server/config_validation/api.h create mode 100644 source/server/config_validation/async_client.cc create mode 100644 source/server/config_validation/async_client.h create mode 100644 source/server/config_validation/cluster_manager.cc create mode 100644 source/server/config_validation/cluster_manager.h create mode 100644 source/server/config_validation/connection_handler.cc create mode 100644 source/server/config_validation/connection_handler.h create mode 100644 source/server/config_validation/dispatcher.cc create mode 100644 source/server/config_validation/dispatcher.h create mode 100644 source/server/config_validation/dns.cc create mode 100644 source/server/config_validation/dns.h create mode 100644 source/server/config_validation/server.cc create mode 100644 source/server/config_validation/server.h create mode 100644 test/server/config_validation/BUILD create mode 100644 test/server/config_validation/async_client_test.cc create mode 100644 test/server/config_validation/cluster_manager_test.cc create mode 100644 test/server/config_validation/connection_handler_test.cc create mode 100644 test/server/config_validation/server_test.cc diff --git a/docs/operations/cli.rst b/docs/operations/cli.rst index fd88f987826b..24139f828ac2 100644 --- a/docs/operations/cli.rst +++ b/docs/operations/cli.rst @@ -10,6 +10,17 @@ following are the command line options that Envoy supports. *(required)* The path to the :ref:`JSON configuration file `. +.. option:: --mode + + *(optional)* One of the operating modes for Envoy: + + * ``serve``: *(default)* Validate the JSON configuration and then serve traffic normally. + + * ``validate``: Validate the JSON configuration and then exit, printing either an "OK" message (in + which case the exit code is 0) or any errors generated by the configuration file (exit code 1). + No network traffic is generated, and the hot restart process is not performed, so no other Envoy + process on the machine will be disturbed. + .. option:: --admin-address-path *(optional)* The output file path where the admin address and port will be written. diff --git a/include/envoy/server/options.h b/include/envoy/server/options.h index 3ab17abd76e8..9d4eac89a28c 100644 --- a/include/envoy/server/options.h +++ b/include/envoy/server/options.h @@ -11,6 +11,28 @@ namespace Envoy { namespace Server { +/** + * Whether to run Envoy in serving mode, or in config validation mode at one of two levels (in which + * case we'll verify the configuration file is valid, print any errors, and exit without serving.) + */ +enum class Mode { + /** + * Default mode: Regular Envoy serving process. Configs are validated in the normal course of + * initialization, but if all is well we proceed to serve traffic. + */ + Serve, + + /** + * Validate as much as possible without opening network connections upstream or downstream. + */ + Validate, + + // TODO(rlazarus): Add a third option for "light validation": Mock out access to the filesystem. + // Perform no validation of files referenced in the config, such as runtime configs, SSL certs, + // etc. Validation will pass even if those files are malformed or don't exist, allowing the config + // to be validated in a non-prod environment. +}; + /** * General options for the server. */ @@ -62,6 +84,12 @@ class Options { */ virtual uint64_t restartEpoch() PURE; + /** + * @return whether to verify the configuration file is valid, print any errors, and exit + * without serving. + */ + virtual Mode mode() const PURE; + /** * @return std::chrono::milliseconds the duration in msec between log flushes. */ diff --git a/include/envoy/upstream/BUILD b/include/envoy/upstream/BUILD index 77dd3fe7fef3..c63deb669e4f 100644 --- a/include/envoy/upstream/BUILD +++ b/include/envoy/upstream/BUILD @@ -15,9 +15,12 @@ envoy_cc_library( ":load_balancer_interface", ":thread_local_cluster_interface", ":upstream_interface", + "//include/envoy/access_log:access_log_interface", "//include/envoy/http:async_client_interface", "//include/envoy/http:conn_pool_interface", "//include/envoy/json:json_object_interface", + "//include/envoy/local_info:local_info_interface", + "//include/envoy/runtime:runtime_interface", ], ) diff --git a/include/envoy/upstream/cluster_manager.h b/include/envoy/upstream/cluster_manager.h index 18f2d90fa49f..f0dabaac6518 100644 --- a/include/envoy/upstream/cluster_manager.h +++ b/include/envoy/upstream/cluster_manager.h @@ -6,9 +6,12 @@ #include #include +#include "envoy/access_log/access_log.h" #include "envoy/http/async_client.h" #include "envoy/http/conn_pool.h" #include "envoy/json/json_object.h" +#include "envoy/local_info/local_info.h" +#include "envoy/runtime/runtime.h" #include "envoy/upstream/load_balancer.h" #include "envoy/upstream/thread_local_cluster.h" #include "envoy/upstream/upstream.h" @@ -103,6 +106,8 @@ class ClusterManager { virtual void shutdown() PURE; }; +typedef std::unique_ptr ClusterManagerPtr; + /** * Global configuration for any SDS clusters. */ @@ -139,6 +144,16 @@ class ClusterManagerFactory { public: virtual ~ClusterManagerFactory() {} + /** + * Allocate a cluster manager from configuration JSON. + */ + virtual ClusterManagerPtr clusterManagerFromJson(const Json::Object& config, Stats::Store& stats, + ThreadLocal::Instance& tls, + Runtime::Loader& runtime, + Runtime::RandomGenerator& random, + const LocalInfo::LocalInfo& local_info, + AccessLog::AccessLogManager& log_manager) PURE; + /** * Allocate an HTTP connection pool. */ diff --git a/source/common/common/assert.h b/source/common/common/assert.h index 7569dd194ff0..cede5823bd7f 100644 --- a/source/common/common/assert.h +++ b/source/common/common/assert.h @@ -10,7 +10,7 @@ namespace Envoy { #define RELEASE_ASSERT(X) \ { \ if (!(X)) { \ - Logger::Registry::getLog(Logger::Id::assert) \ + Envoy::Logger::Registry::getLog(Envoy::Logger::Id::assert) \ .critical("assert failure: {}: {}:{}", #X, __FILE__, __LINE__); \ abort(); \ } \ @@ -26,7 +26,7 @@ namespace Envoy { * Indicate a panic situation and exit. */ #define PANIC(X) \ - Logger::Registry::getLog(Logger::Id::assert) \ + Envoy::Logger::Registry::getLog(Envoy::Logger::Id::assert) \ .critical("panic: {}: {}:{}", X, __FILE__, __LINE__); \ abort(); diff --git a/source/common/http/async_client_impl.cc b/source/common/http/async_client_impl.cc index 1764dfdecb26..c38c93d524bc 100644 --- a/source/common/http/async_client_impl.cc +++ b/source/common/http/async_client_impl.cc @@ -40,6 +40,7 @@ AsyncClient::Request* AsyncClientImpl::send(MessagePtr&& request, AsyncClient::C const Optional& timeout) { AsyncRequestImpl* async_request = new AsyncRequestImpl(std::move(request), *this, callbacks, timeout); + async_request->initialize(); std::unique_ptr new_request{async_request}; // The request may get immediately failed. If so, we will return nullptr. @@ -155,7 +156,9 @@ AsyncRequestImpl::AsyncRequestImpl(MessagePtr&& request, AsyncClientImpl& parent AsyncClient::Callbacks& callbacks, const Optional& timeout) : AsyncStreamImpl(parent, *this, timeout), request_(std::move(request)), callbacks_(callbacks) { +} +void AsyncRequestImpl::initialize() { sendHeaders(request_->headers(), !request_->body()); if (!remoteClosed() && request_->body()) { sendData(*request_->body(), true); diff --git a/source/common/http/async_client_impl.h b/source/common/http/async_client_impl.h index 4547e7f53016..a4a84418948f 100644 --- a/source/common/http/async_client_impl.h +++ b/source/common/http/async_client_impl.h @@ -217,6 +217,7 @@ class AsyncRequestImpl final : public AsyncClient::Request, virtual void cancel() override; private: + void initialize(); void onComplete(); // AsyncClient::StreamCallbacks diff --git a/source/common/upstream/cluster_manager_impl.cc b/source/common/upstream/cluster_manager_impl.cc index b1dcadb603cd..e5aa52a9a16b 100644 --- a/source/common/upstream/cluster_manager_impl.cc +++ b/source/common/upstream/cluster_manager_impl.cc @@ -552,6 +552,14 @@ ClusterManagerImpl::ThreadLocalClusterManagerImpl::ClusterEntry::connPool( return container.pools_[enumToInt(priority)].get(); } +ClusterManagerPtr ProdClusterManagerFactory::clusterManagerFromJson( + const Json::Object& config, Stats::Store& stats, ThreadLocal::Instance& tls, + Runtime::Loader& runtime, Runtime::RandomGenerator& random, + const LocalInfo::LocalInfo& local_info, AccessLog::AccessLogManager& log_manager) { + return ClusterManagerPtr{ + new ClusterManagerImpl(config, *this, stats, tls, runtime, random, local_info, log_manager)}; +} + Http::ConnectionPool::InstancePtr ProdClusterManagerFactory::allocateConnPool(Event::Dispatcher& dispatcher, HostConstSharedPtr host, ResourcePriority priority) { diff --git a/source/common/upstream/cluster_manager_impl.h b/source/common/upstream/cluster_manager_impl.h index 4f636e4ece06..f2f5fbd4d877 100644 --- a/source/common/upstream/cluster_manager_impl.h +++ b/source/common/upstream/cluster_manager_impl.h @@ -38,6 +38,11 @@ class ProdClusterManagerFactory : public ClusterManagerFactory { local_info_(local_info) {} // Upstream::ClusterManagerFactory + ClusterManagerPtr clusterManagerFromJson(const Json::Object& config, Stats::Store& stats, + ThreadLocal::Instance& tls, Runtime::Loader& runtime, + Runtime::RandomGenerator& random, + const LocalInfo::LocalInfo& local_info, + AccessLog::AccessLogManager& log_manager) override; Http::ConnectionPool::InstancePtr allocateConnPool(Event::Dispatcher& dispatcher, HostConstSharedPtr host, ResourcePriority priority) override; diff --git a/source/exe/BUILD b/source/exe/BUILD index c7184cd019fb..1a40fd609d6c 100644 --- a/source/exe/BUILD +++ b/source/exe/BUILD @@ -26,6 +26,7 @@ envoy_cc_library( "//source/common/event:libevent_lib", "//source/common/local_info:local_info_lib", "//source/common/network:utility_lib", + "//source/common/stats:stats_lib", "//source/common/stats:thread_local_store_lib", "//source/server:drain_manager_lib", "//source/server:options_lib", @@ -58,6 +59,7 @@ envoy_cc_library( "//source/common/common:compiler_requirements_lib", ":envoy_common_lib", ":hot_restart_lib", + "//source/server/config_validation:server_lib", ] + select({ "//bazel:disable_signal_trace": [], "//conditions:default": [":sigaction_lib"], diff --git a/source/exe/main.cc b/source/exe/main.cc index 4c37a55bfa49..92bb941472c5 100644 --- a/source/exe/main.cc +++ b/source/exe/main.cc @@ -5,6 +5,7 @@ #include "common/event/libevent.h" #include "common/local_info/local_info_impl.h" #include "common/network/utility.h" +#include "common/stats/stats_impl.h" #include "common/stats/thread_local_store.h" #include "exe/hot_restart.h" @@ -13,6 +14,7 @@ #include "exe/signal_action.h" #endif +#include "server/config_validation/server.h" #include "server/drain_manager_impl.h" #include "server/options_impl.h" #include "server/server.h" @@ -45,10 +47,24 @@ int main(int argc, char** argv) { // Enabled by default. Control with "bazel --define=signal_trace=disabled" Envoy::SignalAction handle_sigs; #endif - ares_library_init(ARES_LIB_INIT_ALL); Envoy::Event::Libevent::Global::initialize(); Envoy::OptionsImpl options(argc, argv, Envoy::Server::SharedMemory::version(), spdlog::level::warn); + Envoy::Server::ProdComponentFactory component_factory; + Envoy::LocalInfo::LocalInfoImpl local_info( + Envoy::Network::Utility::getLocalAddress(Envoy::Network::Address::IpVersion::v4), + options.serviceZone(), options.serviceClusterName(), options.serviceNodeName()); + + switch (options.mode()) { + case Envoy::Server::Mode::Serve: + break; + case Envoy::Server::Mode::Validate: + Envoy::Thread::MutexBasicLockable log_lock; + Envoy::Logger::Registry::initialize(options.logLevel(), log_lock); + return Envoy::Server::validateConfig(options, component_factory, local_info) ? 0 : 1; + } + + ares_library_init(ARES_LIB_INIT_ALL); std::unique_ptr restarter; try { @@ -61,11 +77,7 @@ int main(int argc, char** argv) { Envoy::Logger::Registry::initialize(options.logLevel(), restarter->logLock()); Envoy::DefaultTestHooks default_test_hooks; Envoy::Stats::ThreadLocalStoreImpl stats_store(*restarter); - Envoy::Server::ProdComponentFactory component_factory; // TODO(henna): Add CLI option for local address IP version. - Envoy::LocalInfo::LocalInfoImpl local_info( - Envoy::Network::Utility::getLocalAddress(Envoy::Network::Address::IpVersion::v4), - options.serviceZone(), options.serviceClusterName(), options.serviceNodeName()); Envoy::Server::InstanceImpl server(options, default_test_hooks, *restarter, stats_store, restarter->accessLogLock(), component_factory, local_info); server.run(); diff --git a/source/server/BUILD b/source/server/BUILD index ee2d3b634349..e4847912f39d 100644 --- a/source/server/BUILD +++ b/source/server/BUILD @@ -136,13 +136,13 @@ envoy_cc_library( "//source/common/api:api_lib", "//source/common/common:utility_lib", "//source/common/common:version_lib", - "//source/common/json:config_schemas_lib", "//source/common/memory:stats_lib", "//source/common/network:utility_lib", "//source/common/runtime:runtime_lib", "//source/common/ssl:context_lib", "//source/common/stats:statsd_lib", "//source/common/thread_local:thread_local_lib", + "//source/common/upstream:cluster_manager_lib", "//source/server/http:admin_lib", ], ) diff --git a/source/server/config_validation/BUILD b/source/server/config_validation/BUILD new file mode 100644 index 000000000000..9068fd16563a --- /dev/null +++ b/source/server/config_validation/BUILD @@ -0,0 +1,98 @@ +licenses(["notice"]) # Apache 2 + +load("//bazel:envoy_build_system.bzl", "envoy_cc_library", "envoy_package") + +envoy_package() + +envoy_cc_library( + name = "api_lib", + srcs = ["api.cc"], + hdrs = ["api.h"], + deps = [ + ":dispatcher_lib", + "//include/envoy/api:api_interface", + "//include/envoy/filesystem:filesystem_interface", + "//source/common/api:api_lib", + ], +) + +envoy_cc_library( + name = "async_client_lib", + srcs = ["async_client.cc"], + hdrs = ["async_client.h"], + deps = [ + "//include/envoy/http:async_client_interface", + "//include/envoy/http:message_interface", + ], +) + +envoy_cc_library( + name = "cluster_manager_lib", + srcs = ["cluster_manager.cc"], + hdrs = ["cluster_manager.h"], + deps = [ + ":async_client_lib", + "//include/envoy/upstream:cluster_manager_interface", + "//source/common/upstream:cluster_manager_lib", + ], +) + +envoy_cc_library( + name = "connection_handler_lib", + srcs = ["connection_handler.cc"], + hdrs = ["connection_handler.h"], + deps = [ + "//include/envoy/api:api_interface", + "//include/envoy/network:connection_handler_interface", + "//include/envoy/network:filter_interface", + "//include/envoy/network:listen_socket_interface", + "//source/common/common:assert_lib", + ], +) + +envoy_cc_library( + name = "dispatcher_lib", + srcs = ["dispatcher.cc"], + hdrs = ["dispatcher.h"], + deps = [ + "//include/envoy/event:dispatcher_interface", + "//source/common/common:assert_lib", + "//source/common/event:dispatcher_lib", + ], +) + +envoy_cc_library( + name = "dns_lib", + srcs = ["dns.cc"], + hdrs = ["dns.h"], + deps = [ + "//include/envoy/event:dispatcher_interface", + "//include/envoy/network:dns_interface", + ], +) + +envoy_cc_library( + name = "server_lib", + srcs = ["server.cc"], + hdrs = ["server.h"], + deps = [ + ":api_lib", + ":cluster_manager_lib", + ":connection_handler_lib", + ":dns_lib", + "//include/envoy/common:optional", + "//include/envoy/server:drain_manager_interface", + "//include/envoy/server:instance_interface", + "//include/envoy/ssl:context_manager_interface", + "//include/envoy/tracing:http_tracer_interface", + "//source/common/access_log:access_log_manager_lib", + "//source/common/common:assert_lib", + "//source/common/runtime:runtime_lib", + "//source/common/ssl:context_lib", + "//source/common/stats:stats_lib", + "//source/common/thread_local:thread_local_lib", + "//source/server:configuration_lib", + "//source/server:server_lib", + "//source/server/http:admin_lib", + ], +) diff --git a/source/server/config_validation/api.cc b/source/server/config_validation/api.cc new file mode 100644 index 000000000000..b85969408031 --- /dev/null +++ b/source/server/config_validation/api.cc @@ -0,0 +1,16 @@ +#include "server/config_validation/api.h" + +#include "server/config_validation/dispatcher.h" + +namespace Envoy { +namespace Api { + +ValidationImpl::ValidationImpl(std::chrono::milliseconds file_flush_interval_msec) + : Impl(file_flush_interval_msec) {} + +Event::DispatcherPtr ValidationImpl::allocateDispatcher() { + return Event::DispatcherPtr{new Event::ValidationDispatcher()}; +} + +} // Api +} // Envoy diff --git a/source/server/config_validation/api.h b/source/server/config_validation/api.h new file mode 100644 index 000000000000..969541a2a0f2 --- /dev/null +++ b/source/server/config_validation/api.h @@ -0,0 +1,23 @@ +#pragma once + +#include "envoy/api/api.h" +#include "envoy/filesystem/filesystem.h" + +#include "common/api/api_impl.h" + +namespace Envoy { +namespace Api { + +/** + * Config-validation-only implementation of Api::Api. Delegates to Api::Impl, + * except for allocateDispatcher() which sets up a ValidationDispatcher. + */ +class ValidationImpl : public Impl { +public: + ValidationImpl(std::chrono::milliseconds file_flush_interval_msec); + + Event::DispatcherPtr allocateDispatcher() override; +}; + +} // Api +} // Envoy diff --git a/source/server/config_validation/async_client.cc b/source/server/config_validation/async_client.cc new file mode 100644 index 000000000000..71f4f6433084 --- /dev/null +++ b/source/server/config_validation/async_client.cc @@ -0,0 +1,17 @@ +#include "server/config_validation/async_client.h" + +namespace Envoy { +namespace Http { + +AsyncClient::Request* ValidationAsyncClient::send(MessagePtr&&, Callbacks&, + const Optional&) { + return nullptr; +} + +AsyncClient::Stream* ValidationAsyncClient::start(StreamCallbacks&, + const Optional&) { + return nullptr; +} + +} // Http +} // Envoy diff --git a/source/server/config_validation/async_client.h b/source/server/config_validation/async_client.h new file mode 100644 index 000000000000..4f1267eb4ad9 --- /dev/null +++ b/source/server/config_validation/async_client.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include "envoy/http/async_client.h" +#include "envoy/http/message.h" + +namespace Envoy { +namespace Http { + +/** + * Config-validation-only implementation of AsyncClient. Both methods on AsyncClient are allowed to + * return nullptr if the request can't be created, so that's what the ValidationAsyncClient does in + * all cases. + */ +class ValidationAsyncClient : public AsyncClient { +public: + // Http::AsyncClient + AsyncClient::Request* send(MessagePtr&&, Callbacks&, + const Optional&) override; + AsyncClient::Stream* start(StreamCallbacks&, const Optional&) override; +}; + +} // Http +} // Envoy diff --git a/source/server/config_validation/cluster_manager.cc b/source/server/config_validation/cluster_manager.cc new file mode 100644 index 000000000000..9fd60b011cb8 --- /dev/null +++ b/source/server/config_validation/cluster_manager.cc @@ -0,0 +1,51 @@ +#include "server/config_validation/cluster_manager.h" + +namespace Envoy { +namespace Upstream { + +ValidationClusterManagerFactory::ValidationClusterManagerFactory( + Runtime::Loader& runtime, Stats::Store& stats, ThreadLocal::Instance& tls, + Runtime::RandomGenerator& random, Network::DnsResolver& dns_resolver, + Ssl::ContextManager& ssl_context_manager, Event::Dispatcher& primary_dispatcher, + const LocalInfo::LocalInfo& local_info) + : ProdClusterManagerFactory(runtime, stats, tls, random, dns_resolver, ssl_context_manager, + primary_dispatcher, local_info) {} + +ClusterManagerPtr ValidationClusterManagerFactory::clusterManagerFromJson( + const Json::Object& config, Stats::Store& stats, ThreadLocal::Instance& tls, + Runtime::Loader& runtime, Runtime::RandomGenerator& random, + const LocalInfo::LocalInfo& local_info, AccessLog::AccessLogManager& log_manager) { + return ClusterManagerPtr{new ValidationClusterManager(config, *this, stats, tls, runtime, random, + local_info, log_manager)}; +} + +CdsApiPtr ValidationClusterManagerFactory::createCds(const Json::Object& config, + ClusterManager& cm) { + // Create the CdsApiImpl... + ProdClusterManagerFactory::createCds(config, cm); + // ... and then throw it away, so that we don't actually connect to it. + return nullptr; +} + +ValidationClusterManager::ValidationClusterManager( + const Json::Object& config, ClusterManagerFactory& factory, Stats::Store& stats, + ThreadLocal::Instance& tls, Runtime::Loader& runtime, Runtime::RandomGenerator& random, + const LocalInfo::LocalInfo& local_info, AccessLog::AccessLogManager& log_manager) + : ClusterManagerImpl(config, factory, stats, tls, runtime, random, local_info, log_manager) {} + +Http::ConnectionPool::Instance* +ValidationClusterManager::httpConnPoolForCluster(const std::string&, ResourcePriority, + LoadBalancerContext*) { + return nullptr; +} + +Host::CreateConnectionData ValidationClusterManager::tcpConnForCluster(const std::string&) { + return Host::CreateConnectionData{nullptr, nullptr}; +} + +Http::AsyncClient& ValidationClusterManager::httpAsyncClientForCluster(const std::string&) { + return async_client_; +} + +} // Upstream +} // Envoy diff --git a/source/server/config_validation/cluster_manager.h b/source/server/config_validation/cluster_manager.h new file mode 100644 index 000000000000..dbf7a4dfd285 --- /dev/null +++ b/source/server/config_validation/cluster_manager.h @@ -0,0 +1,60 @@ +#pragma once + +#include "envoy/upstream/cluster_manager.h" + +#include "common/upstream/cluster_manager_impl.h" + +#include "server/config_validation/async_client.h" + +namespace Envoy { +namespace Upstream { + +/** + * Config-validation-only implementation of ClusterManagerFactory, which creates + * ValidationClusterManagers. It also creates, but never returns, CdsApiImpls. + */ +class ValidationClusterManagerFactory : public ProdClusterManagerFactory { +public: + ValidationClusterManagerFactory(Runtime::Loader& runtime, Stats::Store& stats, + ThreadLocal::Instance& tls, Runtime::RandomGenerator& random, + Network::DnsResolver& dns_resolver, + Ssl::ContextManager& ssl_context_manager, + Event::Dispatcher& primary_dispatcher, + const LocalInfo::LocalInfo& local_info); + + ClusterManagerPtr clusterManagerFromJson(const Json::Object& config, Stats::Store& stats, + ThreadLocal::Instance& tls, Runtime::Loader& runtime, + Runtime::RandomGenerator& random, + const LocalInfo::LocalInfo& local_info, + AccessLog::AccessLogManager& log_manager) override; + + // Delegates to ProdClusterManagerFactory::createCds, but discards the result and returns nullptr + // unconditionally. + CdsApiPtr createCds(const Json::Object& config, ClusterManager& cm) override; +}; + +/** + * Config-validation-only implementation of ClusterManager, which opens no upstream connections. + */ +class ValidationClusterManager : public ClusterManagerImpl { +public: + ValidationClusterManager(const Json::Object& config, ClusterManagerFactory& factory, + Stats::Store& stats, ThreadLocal::Instance& tls, + Runtime::Loader& runtime, Runtime::RandomGenerator& random, + const LocalInfo::LocalInfo& local_info, + AccessLog::AccessLogManager& log_manager); + + Http::ConnectionPool::Instance* httpConnPoolForCluster(const std::string&, ResourcePriority, + LoadBalancerContext*) override; + Host::CreateConnectionData tcpConnForCluster(const std::string&) override; + Http::AsyncClient& httpAsyncClientForCluster(const std::string&) override; + +private: + // ValidationAsyncClient always returns null on send() and start(), so it has + // no internal state -- we might as well just keep one and hand out references + // to it. + Http::ValidationAsyncClient async_client_; +}; + +} // Upstream +} // Envoy diff --git a/source/server/config_validation/connection_handler.cc b/source/server/config_validation/connection_handler.cc new file mode 100644 index 000000000000..97a124e20757 --- /dev/null +++ b/source/server/config_validation/connection_handler.cc @@ -0,0 +1,27 @@ +#include "server/config_validation/connection_handler.h" + +#include "common/common/assert.h" + +namespace Envoy { +namespace Server { + +ValidationConnectionHandler::ValidationConnectionHandler(Api::ApiPtr&& api) + : api_(std::move(api)), dispatcher_(api_->allocateDispatcher()) {} + +ValidationConnectionHandler::~ValidationConnectionHandler() { + dispatcher_->clearDeferredDeleteList(); +} + +void ValidationConnectionHandler::addListener(Network::FilterChainFactory&, Network::ListenSocket&, + Stats::Scope&, const Network::ListenerOptions&) { + NOT_IMPLEMENTED; +} + +void ValidationConnectionHandler::addSslListener(Network::FilterChainFactory&, Ssl::ServerContext&, + Network::ListenSocket&, Stats::Scope&, + const Network::ListenerOptions&) { + NOT_IMPLEMENTED; +} + +} // Server +} // Envoy diff --git a/source/server/config_validation/connection_handler.h b/source/server/config_validation/connection_handler.h new file mode 100644 index 000000000000..4e88c1305323 --- /dev/null +++ b/source/server/config_validation/connection_handler.h @@ -0,0 +1,40 @@ +#pragma once + +#include "envoy/api/api.h" +#include "envoy/network/connection_handler.h" +#include "envoy/network/filter.h" +#include "envoy/network/listen_socket.h" + +namespace Envoy { +namespace Server { + +/** + * Config-validation implementation of ConnectionHandler. Calls to add*Listener() will fail, since + * no listeners can be added at validation time. + */ +class ValidationConnectionHandler : public Network::ConnectionHandler { +public: + ValidationConnectionHandler(Api::ApiPtr&& api); + ~ValidationConnectionHandler(); + + Api::Api& api() { return *api_; } + Event::Dispatcher& dispatcher() { return *dispatcher_; } + + // Network::ConnectionHandler + uint64_t numConnections() override { return 0; } + void addListener(Network::FilterChainFactory&, Network::ListenSocket&, Stats::Scope&, + const Network::ListenerOptions&) override; + void addSslListener(Network::FilterChainFactory&, Ssl::ServerContext&, Network::ListenSocket&, + Stats::Scope&, const Network::ListenerOptions&) override; + Network::Listener* findListenerByAddress(const Network::Address::Instance&) override { + return nullptr; + } + void closeListeners() override{}; + +private: + Api::ApiPtr api_; + Event::DispatcherPtr dispatcher_; +}; + +} // Server +} // Envoy diff --git a/source/server/config_validation/dispatcher.cc b/source/server/config_validation/dispatcher.cc new file mode 100644 index 000000000000..dda8d9291366 --- /dev/null +++ b/source/server/config_validation/dispatcher.cc @@ -0,0 +1,37 @@ +#include "server/config_validation/dispatcher.h" + +#include "common/common/assert.h" + +namespace Envoy { +namespace Event { + +Network::ClientConnectionPtr + ValidationDispatcher::createClientConnection(Network::Address::InstanceConstSharedPtr) { + NOT_IMPLEMENTED; +} + +Network::ClientConnectionPtr +ValidationDispatcher::createSslClientConnection(Ssl::ClientContext&, + Network::Address::InstanceConstSharedPtr) { + NOT_IMPLEMENTED; +} + +Network::DnsResolverPtr ValidationDispatcher::createDnsResolver() { NOT_IMPLEMENTED; } + +Network::ListenerPtr ValidationDispatcher::createListener(Network::ConnectionHandler&, + Network::ListenSocket&, + Network::ListenerCallbacks&, + Stats::Scope&, + const Network::ListenerOptions&) { + NOT_IMPLEMENTED; +} + +Network::ListenerPtr +ValidationDispatcher::createSslListener(Network::ConnectionHandler&, Ssl::ServerContext&, + Network::ListenSocket&, Network::ListenerCallbacks&, + Stats::Scope&, const Network::ListenerOptions&) { + NOT_IMPLEMENTED; +} + +} // Event +} // Envoy diff --git a/source/server/config_validation/dispatcher.h b/source/server/config_validation/dispatcher.h new file mode 100644 index 000000000000..5649a9ffa722 --- /dev/null +++ b/source/server/config_validation/dispatcher.h @@ -0,0 +1,31 @@ +#pragma once + +#include "envoy/event/dispatcher.h" + +#include "common/event/dispatcher_impl.h" + +namespace Envoy { +namespace Event { + +/** + * Config-validation-only implementation of Event::Dispatcher. This class delegates all calls to + * Event::DispatcherImpl, except for the methods involved with network events. Those methods are + * disallowed at validation time. + */ +class ValidationDispatcher : public DispatcherImpl { +public: + Network::ClientConnectionPtr + createClientConnection(Network::Address::InstanceConstSharedPtr) override; + Network::ClientConnectionPtr + createSslClientConnection(Ssl::ClientContext&, Network::Address::InstanceConstSharedPtr) override; + Network::DnsResolverPtr createDnsResolver() override; + Network::ListenerPtr createListener(Network::ConnectionHandler&, Network::ListenSocket&, + Network::ListenerCallbacks&, Stats::Scope&, + const Network::ListenerOptions&) override; + Network::ListenerPtr createSslListener(Network::ConnectionHandler&, Ssl::ServerContext&, + Network::ListenSocket&, Network::ListenerCallbacks&, + Stats::Scope&, const Network::ListenerOptions&) override; +}; + +} // Event +} // Envoy diff --git a/source/server/config_validation/dns.cc b/source/server/config_validation/dns.cc new file mode 100644 index 000000000000..222f9c19d6bd --- /dev/null +++ b/source/server/config_validation/dns.cc @@ -0,0 +1,13 @@ +#include "server/config_validation/dns.h" + +namespace Envoy { +namespace Network { + +ActiveDnsQuery* ValidationDnsResolver::resolve(const std::string&, DnsLookupFamily, + ResolveCb callback) { + callback({}); + return nullptr; +} + +} // Network +} // Envoy diff --git a/source/server/config_validation/dns.h b/source/server/config_validation/dns.h new file mode 100644 index 000000000000..c15bb3a725d5 --- /dev/null +++ b/source/server/config_validation/dns.h @@ -0,0 +1,23 @@ +#pragma once + +#include "envoy/event/dispatcher.h" +#include "envoy/network/dns.h" + +namespace Envoy { +namespace Network { + +/** + * DnsResolver to be used in config validation runs. Every DNS query immediately fails to resolve, + * since we never need DNS information to validate a config. (If a config contains an unresolveable + * name, it still passes validation -- for example, we might be running validation in a test + * environment, while the name resolves fine in prod.) + */ +class ValidationDnsResolver : public DnsResolver { +public: + // Network::DnsResolver + ActiveDnsQuery* resolve(const std::string& dns_name, DnsLookupFamily dns_lookup_family, + ResolveCb callback) override; +}; + +} // Network +} // Envoy diff --git a/source/server/config_validation/server.cc b/source/server/config_validation/server.cc new file mode 100644 index 000000000000..1d6154cd66a5 --- /dev/null +++ b/source/server/config_validation/server.cc @@ -0,0 +1,77 @@ +#include "server/config_validation/server.h" + +#include "server/configuration_impl.h" + +namespace Envoy { +namespace Server { + +bool validateConfig(Options& options, ComponentFactory& component_factory, + const LocalInfo::LocalInfo& local_info) { + Thread::MutexBasicLockable access_log_lock; + Stats::IsolatedStoreImpl stats_store; + + try { + ValidationInstance server(options, stats_store, access_log_lock, component_factory, local_info); + std::cout << "configuration '" << options.configPath() << "' OK" << std::endl; + server.shutdown(); + return true; + } catch (const EnvoyException& e) { + return false; + } +} + +ValidationInstance::ValidationInstance(Options& options, Stats::IsolatedStoreImpl& store, + Thread::BasicLockable& access_log_lock, + ComponentFactory& component_factory, + const LocalInfo::LocalInfo& local_info) + : options_(options), stats_store_(store), + handler_(Api::ApiPtr{new Api::ValidationImpl(options.fileFlushIntervalMsec())}), + local_info_(local_info), + access_log_manager_(handler_.api(), handler_.dispatcher(), access_log_lock, store) { + try { + initialize(options, component_factory); + } catch (const EnvoyException& e) { + log().critical("error initializing configuration '{}': {}", options.configPath(), e.what()); + thread_local_.shutdownThread(); + throw; + } +} + +void ValidationInstance::initialize(Options& options, ComponentFactory& component_factory) { + // See comments on InstanceImpl::initialize() for the overall flow here. + // + // For validation, we only do a subset of normal server initialization: everything that could fail + // on a malformed config (e.g. JSON parsing and all the object construction that follows), but + // more importantly nothing with observable effects (e.g. binding to ports or shutting down any + // other Envoy process). + // + // If we get all the way through that stripped-down initialization flow, to the point where we'd + // be ready to serve, then the config has passed validation. + Json::ObjectSharedPtr config_json = Json::Factory::loadFromFile(options.configPath()); + Configuration::InitialImpl initial_config(*config_json); + thread_local_.registerThread(handler_.dispatcher(), true); + runtime_loader_ = component_factory.createRuntime(*this, initial_config); + ssl_context_manager_.reset(new Ssl::ContextManagerImpl(*runtime_loader_)); + cluster_manager_factory_.reset(new Upstream::ValidationClusterManagerFactory( + runtime(), stats(), threadLocal(), random(), dnsResolver(), sslContextManager(), dispatcher(), + localInfo())); + + Configuration::MainImpl* main_config = + new Configuration::MainImpl(*this, *cluster_manager_factory_); + config_.reset(main_config); + main_config->initialize(*config_json); + + clusterManager().setInitializedCb([this]() + -> void { init_manager_.initialize([]() -> void {}); }); +} + +void ValidationInstance::shutdown() { + // This normally happens at the bottom of InstanceImpl::run(), but we don't have a run(). We can + // do an abbreviated shutdown here since there's less to clean up -- for example, no workers to + // exit. + config_->clusterManager().shutdown(); + thread_local_.shutdownThread(); +} + +} // Server +} // Envoy diff --git a/source/server/config_validation/server.h b/source/server/config_validation/server.h new file mode 100644 index 000000000000..149943f89f72 --- /dev/null +++ b/source/server/config_validation/server.h @@ -0,0 +1,107 @@ +#pragma once + +#include + +#include "envoy/common/optional.h" +#include "envoy/server/drain_manager.h" +#include "envoy/server/instance.h" +#include "envoy/ssl/context_manager.h" +#include "envoy/tracing/http_tracer.h" + +#include "common/access_log/access_log_manager_impl.h" +#include "common/common/assert.h" +#include "common/runtime/runtime_impl.h" +#include "common/ssl/context_manager_impl.h" +#include "common/stats/stats_impl.h" +#include "common/thread_local/thread_local_impl.h" + +#include "server/config_validation/api.h" +#include "server/config_validation/cluster_manager.h" +#include "server/config_validation/connection_handler.h" +#include "server/config_validation/dns.h" +#include "server/http/admin.h" +#include "server/server.h" +#include "server/worker.h" + +namespace Envoy { +namespace Server { + +/** + * validateConfig() takes over from main() for a config-validation run of Envoy. It returns true if + * the config is valid, false if invalid. + */ +bool validateConfig(Options& options, ComponentFactory& component_factory, + const LocalInfo::LocalInfo& local_info); + +/** + * ValidationInstance does the bulk of the work for config-validation runs of Envoy. It implements + * Server::Instance, but some functionality not needed until serving time, such as updating + * health-check status, is not implemented. Everything else is written in terms of other + * validation-specific interface implementations, with the end result that we can load and + * initialize a configuration, skipping any steps that affect the outside world (such as + * hot-restarting or connecting to upstream clusters) but otherwise exercising the entire startup + * flow. + * + * If we finish initialization, and reach the point where an ordinary Envoy run would begin serving + * requests, the validation is considered successful. + */ +class ValidationInstance : Logger::Loggable, public Instance { +public: + ValidationInstance(Options& options, Stats::IsolatedStoreImpl& store, + Thread::BasicLockable& access_log_lock, ComponentFactory& component_factory, + const LocalInfo::LocalInfo& local_info); + + // Server::Instance + Admin& admin() override { NOT_IMPLEMENTED; } + Api::Api& api() override { return handler_.api(); } + Upstream::ClusterManager& clusterManager() override { return config_->clusterManager(); } + Ssl::ContextManager& sslContextManager() override { return *ssl_context_manager_; } + Event::Dispatcher& dispatcher() override { return handler_.dispatcher(); } + Network::DnsResolver& dnsResolver() override { return dns_resolver_; } + bool draining() override { NOT_IMPLEMENTED; } + void drainListeners() override { NOT_IMPLEMENTED; } + DrainManager& drainManager() override { NOT_IMPLEMENTED; } + AccessLog::AccessLogManager& accessLogManager() override { return access_log_manager_; } + void failHealthcheck(bool) override { NOT_IMPLEMENTED; } + int getListenSocketFd(const std::string&) override { NOT_IMPLEMENTED; } + Network::ListenSocket* getListenSocketByIndex(uint32_t) override { NOT_IMPLEMENTED; } + void getParentStats(HotRestart::GetParentStatsInfo&) override { NOT_IMPLEMENTED; } + HotRestart& hotRestart() override { NOT_IMPLEMENTED; } + Init::Manager& initManager() override { return init_manager_; } + Runtime::RandomGenerator& random() override { return random_generator_; } + RateLimit::ClientPtr + rateLimitClient(const Optional& timeout) override { + return config_->rateLimitClientFactory().create(timeout); + } + Runtime::Loader& runtime() override { return *runtime_loader_; } + void shutdown() override; + void shutdownAdmin() override { NOT_IMPLEMENTED; } + bool healthCheckFailed() override { NOT_IMPLEMENTED; } + Options& options() override { return options_; } + time_t startTimeCurrentEpoch() override { NOT_IMPLEMENTED; } + time_t startTimeFirstEpoch() override { NOT_IMPLEMENTED; } + Stats::Store& stats() override { return stats_store_; } + Tracing::HttpTracer& httpTracer() override { return config_->httpTracer(); } + ThreadLocal::Instance& threadLocal() override { return thread_local_; } + const LocalInfo::LocalInfo& localInfo() override { return local_info_; } + +private: + void initialize(Options& options, ComponentFactory& component_factory); + + Options& options_; + Stats::IsolatedStoreImpl& stats_store_; + ThreadLocal::InstanceImpl thread_local_; + ValidationConnectionHandler handler_; + Runtime::LoaderPtr runtime_loader_; + Runtime::RandomGeneratorImpl random_generator_; + std::unique_ptr ssl_context_manager_; + std::unique_ptr config_; + Network::ValidationDnsResolver dns_resolver_; + const LocalInfo::LocalInfo& local_info_; + AccessLog::AccessLogManagerImpl access_log_manager_; + std::unique_ptr cluster_manager_factory_; + InitManagerImpl init_manager_; +}; + +} // Server +} // Envoy diff --git a/source/server/configuration_impl.cc b/source/server/configuration_impl.cc index ecce1aa5424f..9999a4657533 100644 --- a/source/server/configuration_impl.cc +++ b/source/server/configuration_impl.cc @@ -36,17 +36,14 @@ bool FilterChainUtility::buildFilterChain(Network::FilterManager& filter_manager return filter_manager.initializeReadFilters(); } -MainImpl::MainImpl(Server::Instance& server) : server_(server) {} +MainImpl::MainImpl(Server::Instance& server, + Upstream::ClusterManagerFactory& cluster_manager_factory) + : server_(server), cluster_manager_factory_(cluster_manager_factory) {} void MainImpl::initialize(const Json::Object& json) { - cluster_manager_factory_.reset(new Upstream::ProdClusterManagerFactory( - server_.runtime(), server_.stats(), server_.threadLocal(), server_.random(), - server_.dnsResolver(), server_.sslContextManager(), server_.dispatcher(), - server_.localInfo())); - cluster_manager_.reset(new Upstream::ClusterManagerImpl( - *json.getObject("cluster_manager"), *cluster_manager_factory_, server_.stats(), - server_.threadLocal(), server_.runtime(), server_.random(), server_.localInfo(), - server_.accessLogManager())); + cluster_manager_ = cluster_manager_factory_.clusterManagerFromJson( + *json.getObject("cluster_manager"), server_.stats(), server_.threadLocal(), server_.runtime(), + server_.random(), server_.localInfo(), server_.accessLogManager()); std::vector listeners = json.getObjectArray("listeners"); log().info("loading {} listener(s)", listeners.size()); @@ -207,6 +204,7 @@ bool MainImpl::ListenerConfig::createFilterChain(Network::Connection& connection } InitialImpl::InitialImpl(const Json::Object& json) { + json.validateSchema(Json::Schema::TOP_LEVEL_CONFIG_SCHEMA); Json::ObjectSharedPtr admin = json.getObject("admin"); admin_.access_log_path_ = admin->getString("access_log_path"); admin_.profile_path_ = admin->getString("profile_path", "/var/log/envoy/envoy.prof"); diff --git a/source/server/configuration_impl.h b/source/server/configuration_impl.h index 9ae0bf38444c..b2b6bc61d35d 100644 --- a/source/server/configuration_impl.h +++ b/source/server/configuration_impl.h @@ -120,7 +120,7 @@ class FilterChainUtility { */ class MainImpl : Logger::Loggable, public Main { public: - MainImpl(Server::Instance& server); + MainImpl(Server::Instance& server, Upstream::ClusterManagerFactory& cluster_manager_factory_); /** * DEPRECATED - Register an NetworkFilterConfigFactory implementation as an option to create @@ -257,7 +257,7 @@ class MainImpl : Logger::Loggable, public Main { } Server::Instance& server_; - std::unique_ptr cluster_manager_factory_; + Upstream::ClusterManagerFactory& cluster_manager_factory_; std::unique_ptr cluster_manager_; Tracing::HttpTracerPtr http_tracer_; std::list listeners_; diff --git a/source/server/options_impl.cc b/source/server/options_impl.cc index daceefb2f0ba..49d0ffb92350 100644 --- a/source/server/options_impl.cc +++ b/source/server/options_impl.cc @@ -53,6 +53,10 @@ OptionsImpl::OptionsImpl(int argc, char** argv, const std::string& hot_restart_v TCLAP::ValueArg parent_shutdown_time_s("", "parent-shutdown-time-s", "Hot restart parent shutdown time in seconds", false, 900, "uint64_t", cmd); + TCLAP::ValueArg mode("", "mode", + "One of 'serve' (default; validate configs and then serve " + "traffic normally) or 'validate' (validate configs and exit).", + false, "serve", "string", cmd); try { cmd.parse(argc, argv); @@ -73,6 +77,15 @@ OptionsImpl::OptionsImpl(int argc, char** argv, const std::string& hot_restart_v } } + if (mode.getValue() == "serve") { + mode_ = Server::Mode::Serve; + } else if (mode.getValue() == "validate") { + mode_ = Server::Mode::Validate; + } else { + std::cerr << "error: unknown mode '" << mode.getValue() << "'" << std::endl; + exit(1); + } + // For base ID, scale what the user inputs by 10 so that we have spread for domain sockets. base_id_ = base_id.getValue() * 10; concurrency_ = concurrency.getValue(); diff --git a/source/server/options_impl.h b/source/server/options_impl.h index 7a0762dae627..a5b736f4144e 100644 --- a/source/server/options_impl.h +++ b/source/server/options_impl.h @@ -30,6 +30,7 @@ class OptionsImpl : public Server::Options { spdlog::level::level_enum logLevel() override { return log_level_; } std::chrono::seconds parentShutdownTime() override { return parent_shutdown_time_; } uint64_t restartEpoch() override { return restart_epoch_; } + Server::Mode mode() const override { return mode_; } std::chrono::milliseconds fileFlushIntervalMsec() override { return file_flush_interval_msec_; } private: @@ -45,5 +46,6 @@ class OptionsImpl : public Server::Options { std::chrono::milliseconds file_flush_interval_msec_; std::chrono::seconds drain_time_; std::chrono::seconds parent_shutdown_time_; + Server::Mode mode_; }; } // Envoy diff --git a/source/server/server.cc b/source/server/server.cc index c6ec2352b060..ad30bba74fc3 100644 --- a/source/server/server.cc +++ b/source/server/server.cc @@ -17,7 +17,6 @@ #include "common/api/api_impl.h" #include "common/common/utility.h" #include "common/common/version.h" -#include "common/json/config_schemas.h" #include "common/memory/stats.h" #include "common/network/utility.h" #include "common/runtime/runtime_impl.h" @@ -177,7 +176,6 @@ void InstanceImpl::initialize(Options& options, TestHooks& hooks, // Handle configuration that needs to take place prior to the main configuration load. Json::ObjectSharedPtr config_json = Json::Factory::loadFromFile(options.configPath()); - config_json->validateSchema(Json::Schema::TOP_LEVEL_CONFIG_SCHEMA); Configuration::InitialImpl initial_config(*config_json); log().info("admin address: {}", initial_config.admin().address()->asString()); @@ -214,9 +212,14 @@ void InstanceImpl::initialize(Options& options, TestHooks& hooks, // Once we have runtime we can initialize the SSL context manager. ssl_context_manager_.reset(new Ssl::ContextManagerImpl(*runtime_loader_)); + cluster_manager_factory_.reset(new Upstream::ProdClusterManagerFactory( + runtime(), stats(), threadLocal(), random(), dnsResolver(), sslContextManager(), dispatcher(), + localInfo())); + // Now the configuration gets parsed. The configuration may start setting thread local data // per above. See MainImpl::initialize() for why we do this pointer dance. - Configuration::MainImpl* main_config = new Configuration::MainImpl(*this); + Configuration::MainImpl* main_config = + new Configuration::MainImpl(*this, *cluster_manager_factory_); config_.reset(main_config); main_config->initialize(*config_json); diff --git a/source/server/server.h b/source/server/server.h index 7a9c7aff8303..df9862e65691 100644 --- a/source/server/server.h +++ b/source/server/server.h @@ -18,6 +18,7 @@ #include "common/runtime/runtime_impl.h" #include "common/ssl/context_manager_impl.h" #include "common/thread_local/thread_local_impl.h" +#include "common/upstream/cluster_manager_impl.h" #include "server/connection_handler_impl.h" #include "server/http/admin.h" @@ -174,6 +175,7 @@ class InstanceImpl : Logger::Loggable, public Instance { const LocalInfo::LocalInfo& local_info_; DrainManagerPtr drain_manager_; AccessLog::AccessLogManagerImpl access_log_manager_; + std::unique_ptr cluster_manager_factory_; InitManagerImpl init_manager_; std::unique_ptr guard_dog_; }; diff --git a/test/common/upstream/cluster_manager_impl_test.cc b/test/common/upstream/cluster_manager_impl_test.cc index 63dbd2f1d927..efae2dacd721 100644 --- a/test/common/upstream/cluster_manager_impl_test.cc +++ b/test/common/upstream/cluster_manager_impl_test.cc @@ -64,6 +64,21 @@ class TestClusterManagerFactory : public ClusterManagerFactory { return CdsApiPtr{createCds_()}; } + ClusterManagerPtr clusterManagerFromJson(const Json::Object& config, Stats::Store& stats, + ThreadLocal::Instance& tls, Runtime::Loader& runtime, + Runtime::RandomGenerator& random, + const LocalInfo::LocalInfo& local_info, + AccessLog::AccessLogManager& log_manager) override { + return ClusterManagerPtr{ + clusterManagerFromJson_(config, stats, tls, runtime, random, local_info, log_manager)}; + } + + MOCK_METHOD7(clusterManagerFromJson_, + ClusterManager*(const Json::Object& config, Stats::Store& stats, + ThreadLocal::Instance& tls, Runtime::Loader& runtime, + Runtime::RandomGenerator& random, + const LocalInfo::LocalInfo& local_info, + AccessLog::AccessLogManager& log_manager)); MOCK_METHOD1(allocateConnPool_, Http::ConnectionPool::Instance*(HostConstSharedPtr host)); MOCK_METHOD4(clusterFromJson_, Cluster*(const Json::Object& cluster, ClusterManager& cm, const Optional& sds_config, diff --git a/test/config_test/config_test.cc b/test/config_test/config_test.cc index 5cc75e1bb26b..bc99e6435441 100644 --- a/test/config_test/config_test.cc +++ b/test/config_test/config_test.cc @@ -23,7 +23,11 @@ namespace ConfigTest { class ConfigTest { public: - ConfigTest(const std::string& file_path) : options_(file_path) { + ConfigTest(const std::string& file_path) + : options_(file_path), + cluster_manager_factory_(server_.runtime(), server_.stats(), server_.threadLocal(), + server_.random(), server_.dnsResolver(), ssl_context_manager_, + server_.dispatcher(), server_.localInfo()) { ON_CALL(server_, options()).WillByDefault(ReturnRef(options_)); ON_CALL(server_, sslContextManager()).WillByDefault(ReturnRef(ssl_context_manager_)); ON_CALL(server_.api_, fileReadToEnd("lightstep_access_token")) @@ -31,7 +35,7 @@ class ConfigTest { Json::ObjectSharedPtr config_json = Json::Factory::loadFromFile(file_path); Server::Configuration::InitialImpl initial_config(*config_json); - Server::Configuration::MainImpl main_config(server_); + Server::Configuration::MainImpl main_config(server_, cluster_manager_factory_); ON_CALL(server_, clusterManager()) .WillByDefault( @@ -49,6 +53,7 @@ class ConfigTest { NiceMock server_; NiceMock ssl_context_manager_; Server::TestOptionsImpl options_; + Upstream::ProdClusterManagerFactory cluster_manager_factory_; }; uint32_t run(const std::string& directory) { diff --git a/test/integration/server.h b/test/integration/server.h index bd9ab98de84f..566e19709d67 100644 --- a/test/integration/server.h +++ b/test/integration/server.h @@ -43,6 +43,7 @@ class TestOptionsImpl : public Options { std::chrono::milliseconds fileFlushIntervalMsec() override { return std::chrono::milliseconds(10000); } + Mode mode() const override { return Mode::Serve; } private: const std::string config_path_; @@ -60,6 +61,17 @@ class TestDrainManager : public DrainManager { bool draining_{}; }; +class TestComponentFactory : public ComponentFactory { +public: + Server::DrainManagerPtr createDrainManager(Server::Instance&) override { + return Server::DrainManagerPtr{new Server::TestDrainManager()}; + } + Runtime::LoaderPtr createRuntime(Server::Instance& server, + Server::Configuration::Initial& config) override { + return Server::InstanceUtil::createRuntime(server, config); + } +}; + } // Server namespace Stats { diff --git a/test/mocks/server/BUILD b/test/mocks/server/BUILD index d5fe953a19ff..cf01bd18e491 100644 --- a/test/mocks/server/BUILD +++ b/test/mocks/server/BUILD @@ -21,6 +21,8 @@ envoy_cc_mock( "//include/envoy/ssl:context_manager_interface", "//source/common/ssl:context_lib", "//source/common/stats:stats_lib", + "//source/common/tracing:http_tracer_lib", + "//source/common/upstream:cluster_manager_lib", "//test/mocks/access_log:access_log_mocks", "//test/mocks/api:api_mocks", "//test/mocks/http:http_mocks", diff --git a/test/mocks/server/mocks.h b/test/mocks/server/mocks.h index 7995ae5e9522..44dee4e78e8e 100644 --- a/test/mocks/server/mocks.h +++ b/test/mocks/server/mocks.h @@ -14,6 +14,8 @@ #include "common/ssl/context_manager_impl.h" #include "common/stats/stats_impl.h" +#include "common/tracing/http_tracer_impl.h" +#include "common/upstream/cluster_manager_impl.h" #include "test/mocks/access_log/mocks.h" #include "test/mocks/api/mocks.h" @@ -51,6 +53,7 @@ class MockOptions : public Options { MOCK_METHOD0(parentShutdownTime, std::chrono::seconds()); MOCK_METHOD0(restartEpoch, uint64_t()); MOCK_METHOD0(fileFlushIntervalMsec, std::chrono::milliseconds()); + MOCK_CONST_METHOD0(mode, Mode()); std::string config_path_; std::string admin_address_path_; diff --git a/test/run_envoy_bazel_coverage.sh b/test/run_envoy_bazel_coverage.sh index 0726ddbca553..3e9fa1d71d5c 100755 --- a/test/run_envoy_bazel_coverage.sh +++ b/test/run_envoy_bazel_coverage.sh @@ -38,7 +38,7 @@ echo "Cleanup completed." # stats. The #foo# pattern is because gcov produces files such as # bazel-out#local-fastbuild#bin#external#spdlog_git#_virtual_includes#spdlog#spdlog#details#pattern_formatter_impl.h.gcov. # To find these while modifying this regex, perform a gcov run with -k set. -[[ -z "${GCOVR_EXCLUDE_REGEX}" ]] && GCOVR_EXCLUDE_REGEX=".*pb.h.gcov|.*#genfiles#.*|test#.*|external#.*|.*#external#.*|.*#prebuilt#.*" +[[ -z "${GCOVR_EXCLUDE_REGEX}" ]] && GCOVR_EXCLUDE_REGEX=".*pb.h.gcov|.*#genfiles#.*|test#.*|external#.*|.*#external#.*|.*#prebuilt#.*|.*#config_validation#.*" [[ -z "${GCOVR_EXCLUDE_DIR}" ]] && GCOVR_EXCLUDE_DIR=".*/external/.*" COVERAGE_DIR="${SRCDIR}"/generated/coverage diff --git a/test/server/config_validation/BUILD b/test/server/config_validation/BUILD new file mode 100644 index 000000000000..0147bb8fcb4d --- /dev/null +++ b/test/server/config_validation/BUILD @@ -0,0 +1,68 @@ +licenses(["notice"]) # Apache 2 + +load("//bazel:envoy_build_system.bzl", "envoy_cc_test", "envoy_package") + +envoy_package() + +envoy_cc_test( + name = "async_client_test", + srcs = ["async_client_test.cc"], + deps = [ + "//include/envoy/http:message_interface", + "//source/common/http:message_lib", + "//source/server/config_validation:async_client_lib", + "//test/mocks/http:http_mocks", + "//test/mocks/upstream:upstream_mocks", + ], +) + +envoy_cc_test( + name = "cluster_manager_test", + srcs = ["cluster_manager_test.cc"], + deps = [ + "//include/envoy/json:json_object_interface", + "//include/envoy/upstream:resource_manager_interface", + "//include/envoy/upstream:upstream_interface", + "//source/common/json:json_loader_lib", + "//source/common/ssl:context_lib", + "//source/common/stats:stats_lib", + "//source/server/config_validation:cluster_manager_lib", + "//test/mocks/access_log:access_log_mocks", + "//test/mocks/event:event_mocks", + "//test/mocks/http:http_mocks", + "//test/mocks/local_info:local_info_mocks", + "//test/mocks/network:network_mocks", + "//test/mocks/runtime:runtime_mocks", + "//test/mocks/thread_local:thread_local_mocks", + "//test/mocks/upstream:upstream_mocks", + ], +) + +envoy_cc_test( + name = "connection_handler_test", + srcs = ["connection_handler_test.cc"], + deps = [ + "//include/envoy/api:api_interface", + "//source/common/network:address_lib", + "//source/server/config_validation:connection_handler_lib", + "//test/mocks/api:api_mocks", + "//test/mocks/event:event_mocks", + ], +) + +envoy_cc_test( + name = "server_test", + srcs = ["server_test.cc"], + data = [ + "//configs:example_configs", + "//test/config_test:example_configs_test_setup.sh", + ], + deps = [ + "//source/server/config_validation:server_lib", + "//test/integration:integration_lib", + "//test/mocks/server:server_mocks", + "//test/mocks/stats:stats_mocks", + "//test/test_common:environment_lib", + "//test/test_common:utility_lib", + ], +) diff --git a/test/server/config_validation/async_client_test.cc b/test/server/config_validation/async_client_test.cc new file mode 100644 index 000000000000..fcaa91bd273a --- /dev/null +++ b/test/server/config_validation/async_client_test.cc @@ -0,0 +1,25 @@ +#include "envoy/http/message.h" + +#include "common/http/message_impl.h" + +#include "server/config_validation/async_client.h" + +#include "test/mocks/http/mocks.h" +#include "test/mocks/upstream/mocks.h" + +namespace Envoy { +namespace Http { + +TEST(ValidationAsyncClientTest, MockedMethods) { + MessagePtr message{new RequestMessageImpl()}; + MockAsyncClientCallbacks callbacks; + MockAsyncClientStreamCallbacks stream_callbacks; + + ValidationAsyncClient client; + EXPECT_EQ(nullptr, + client.send(std::move(message), callbacks, Optional())); + EXPECT_EQ(nullptr, client.start(stream_callbacks, Optional())); +} + +} // Http +} // Envoy diff --git a/test/server/config_validation/cluster_manager_test.cc b/test/server/config_validation/cluster_manager_test.cc new file mode 100644 index 000000000000..c7fbddee5ad4 --- /dev/null +++ b/test/server/config_validation/cluster_manager_test.cc @@ -0,0 +1,58 @@ +#include "envoy/json/json_object.h" +#include "envoy/upstream/resource_manager.h" +#include "envoy/upstream/upstream.h" + +#include "common/json/json_loader.h" +#include "common/ssl/context_manager_impl.h" +#include "common/stats/stats_impl.h" + +#include "server/config_validation/cluster_manager.h" + +#include "test/mocks/access_log/mocks.h" +#include "test/mocks/event/mocks.h" +#include "test/mocks/http/mocks.h" +#include "test/mocks/local_info/mocks.h" +#include "test/mocks/network/mocks.h" +#include "test/mocks/runtime/mocks.h" +#include "test/mocks/thread_local/mocks.h" +#include "test/mocks/upstream/mocks.h" + +namespace Envoy { +namespace Upstream { + +TEST(ValidationClusterManagerTest, MockedMethods) { + NiceMock runtime; + Stats::IsolatedStoreImpl stats; + NiceMock tls; + NiceMock random; + NiceMock dns_resolver; + Ssl::ContextManagerImpl ssl_context_manager{runtime}; + NiceMock dispatcher; + LocalInfo::MockLocalInfo local_info; + + ValidationClusterManagerFactory factory(runtime, stats, tls, random, dns_resolver, + ssl_context_manager, dispatcher, local_info); + + std::string json = R"EOF( + { + "clusters": [] + } + )EOF"; + Json::ObjectSharedPtr config = Json::Factory::loadFromString(json); + AccessLog::MockAccessLogManager log_manager; + + ClusterManagerPtr cluster_manager = + factory.clusterManagerFromJson(*config, stats, tls, runtime, random, local_info, log_manager); + EXPECT_EQ(nullptr, + cluster_manager->httpConnPoolForCluster("cluster", ResourcePriority::Default, nullptr)); + Host::CreateConnectionData data = cluster_manager->tcpConnForCluster("cluster"); + EXPECT_EQ(nullptr, data.connection_); + EXPECT_EQ(nullptr, data.host_description_); + + Http::AsyncClient& client = cluster_manager->httpAsyncClientForCluster("cluster"); + Http::MockAsyncClientStreamCallbacks stream_callbacks; + EXPECT_EQ(nullptr, client.start(stream_callbacks, Optional())); +} + +} // Upstream +} // Envoy diff --git a/test/server/config_validation/connection_handler_test.cc b/test/server/config_validation/connection_handler_test.cc new file mode 100644 index 000000000000..6772c40b103b --- /dev/null +++ b/test/server/config_validation/connection_handler_test.cc @@ -0,0 +1,29 @@ +#include "envoy/api/api.h" + +#include "common/network/address_impl.h" + +#include "server/config_validation/connection_handler.h" + +#include "test/mocks/api/mocks.h" +#include "test/mocks/event/mocks.h" + +namespace Envoy { +namespace Server { + +using testing::NiceMock; +using testing::Return; + +TEST(ValidationConnectionHandlerTest, MockedMethods) { + Api::MockApi* api = new Api::MockApi(); + Event::MockDispatcher* dispatcher = new NiceMock(); + EXPECT_CALL(*api, allocateDispatcher_()).WillOnce(Return(dispatcher)); + + ValidationConnectionHandler handler(Api::ApiPtr{api}); + EXPECT_EQ(0, handler.numConnections()); + Network::Address::Ipv4Instance address("0.0.0.0", 0); + EXPECT_EQ(nullptr, handler.findListenerByAddress(address)); + EXPECT_NO_THROW(handler.closeListeners()); +} + +} // Server +} // Envoy diff --git a/test/server/config_validation/server_test.cc b/test/server/config_validation/server_test.cc new file mode 100644 index 000000000000..4523fbf082cc --- /dev/null +++ b/test/server/config_validation/server_test.cc @@ -0,0 +1,45 @@ +#include "server/config_validation/server.h" + +#include "test/integration/server.h" +#include "test/mocks/server/mocks.h" +#include "test/mocks/stats/mocks.h" +#include "test/test_common/environment.h" + +namespace Envoy { +namespace Server { + +// Test param is the path to the config file to validate. +class ValidationServerTest : public testing::TestWithParam { +public: + static void SetUpTestCase() { + TestEnvironment::exec( + {TestEnvironment::runfilesPath("test/config_test/example_configs_test_setup.sh")}); + directory_ = TestEnvironment::temporaryDirectory() + "/test/config_test/"; + } + +protected: + ValidationServerTest() : options_(directory_ + GetParam()) {} + + static std::string directory_; + + testing::NiceMock options_; + TestComponentFactory component_factory_; + testing::NiceMock local_info_; +}; + +std::string ValidationServerTest::directory_ = ""; + +TEST_P(ValidationServerTest, Validate) { + EXPECT_TRUE(validateConfig(options_, component_factory_, local_info_)); +} + +// TODO(rlazarus): We'd like use this setup to replace //test/config_test (that is, run it against +// all the example configs) but can't until light validation is implemented, mocking out access to +// the filesystem for TLS certs, etc. In the meantime, these are the example configs that work +// as-is. +INSTANTIATE_TEST_CASE_P(ValidConfigs, ValidationServerTest, + ::testing::Values("front-envoy.json", "google_com_proxy.json", + "s2s-grpc-envoy.json", "service-envoy.json")); + +} // Server +} // Envoy diff --git a/test/server/configuration_impl_test.cc b/test/server/configuration_impl_test.cc index 62a168bb638b..be5f29cea459 100644 --- a/test/server/configuration_impl_test.cc +++ b/test/server/configuration_impl_test.cc @@ -43,7 +43,19 @@ TEST(FilterChainUtility, buildFilterChainFailWithBadFilters) { EXPECT_EQ(FilterChainUtility::buildFilterChain(connection, factories), false); } -TEST(ConfigurationImplTest, DefaultStatsFlushInterval) { +class ConfigurationImplTest : public testing::Test { +protected: + ConfigurationImplTest() + : cluster_manager_factory_(server_.runtime(), server_.stats(), server_.threadLocal(), + server_.random(), server_.dnsResolver(), + server_.sslContextManager(), server_.dispatcher(), + server_.localInfo()) {} + + NiceMock server_; + Upstream::ProdClusterManagerFactory cluster_manager_factory_; +}; + +TEST_F(ConfigurationImplTest, DefaultStatsFlushInterval) { std::string json = R"EOF( { "listeners": [], @@ -56,14 +68,13 @@ TEST(ConfigurationImplTest, DefaultStatsFlushInterval) { Json::ObjectSharedPtr loader = Json::Factory::loadFromString(json); - NiceMock server; - MainImpl config(server); + MainImpl config(server_, cluster_manager_factory_); config.initialize(*loader); EXPECT_EQ(std::chrono::milliseconds(5000), config.statsFlushInterval()); } -TEST(ConfigurationImplTest, CustomStatsFlushInterval) { +TEST_F(ConfigurationImplTest, CustomStatsFlushInterval) { std::string json = R"EOF( { "listeners": [], @@ -78,14 +89,13 @@ TEST(ConfigurationImplTest, CustomStatsFlushInterval) { Json::ObjectSharedPtr loader = Json::Factory::loadFromString(json); - NiceMock server; - MainImpl config(server); + MainImpl config(server_, cluster_manager_factory_); config.initialize(*loader); EXPECT_EQ(std::chrono::milliseconds(500), config.statsFlushInterval()); } -TEST(ConfigurationImplTest, EmptyFilter) { +TEST_F(ConfigurationImplTest, EmptyFilter) { std::string json = R"EOF( { "listeners" : [ @@ -102,14 +112,13 @@ TEST(ConfigurationImplTest, EmptyFilter) { Json::ObjectSharedPtr loader = Json::Factory::loadFromString(json); - NiceMock server; - MainImpl config(server); + MainImpl config(server_, cluster_manager_factory_); config.initialize(*loader); EXPECT_EQ(1U, config.listeners().size()); } -TEST(ConfigurationImplTest, DefaultListenerPerConnectionBufferLimit) { +TEST_F(ConfigurationImplTest, DefaultListenerPerConnectionBufferLimit) { std::string json = R"EOF( { "listeners" : [ @@ -126,14 +135,13 @@ TEST(ConfigurationImplTest, DefaultListenerPerConnectionBufferLimit) { Json::ObjectSharedPtr loader = Json::Factory::loadFromString(json); - NiceMock server; - MainImpl config(server); + MainImpl config(server_, cluster_manager_factory_); config.initialize(*loader); EXPECT_EQ(1024 * 1024U, config.listeners().back()->perConnectionBufferLimitBytes()); } -TEST(ConfigurationImplTest, SetListenerPerConnectionBufferLimit) { +TEST_F(ConfigurationImplTest, SetListenerPerConnectionBufferLimit) { std::string json = R"EOF( { "listeners" : [ @@ -151,14 +159,13 @@ TEST(ConfigurationImplTest, SetListenerPerConnectionBufferLimit) { Json::ObjectSharedPtr loader = Json::Factory::loadFromString(json); - NiceMock server; - MainImpl config(server); + MainImpl config(server_, cluster_manager_factory_); config.initialize(*loader); EXPECT_EQ(8192U, config.listeners().back()->perConnectionBufferLimitBytes()); } -TEST(ConfigurationImplTest, VerifySubjectAltNameConfig) { +TEST_F(ConfigurationImplTest, VerifySubjectAltNameConfig) { std::string json = R"EOF( { "listeners" : [ @@ -183,14 +190,13 @@ TEST(ConfigurationImplTest, VerifySubjectAltNameConfig) { Json::ObjectSharedPtr loader = TestEnvironment::jsonLoadFromString(json); - NiceMock server; - MainImpl config(server); + MainImpl config(server_, cluster_manager_factory_); config.initialize(*loader); EXPECT_TRUE(config.listeners().back()->sslContext() != nullptr); } -TEST(ConfigurationImplTest, SetUpstreamClusterPerConnectionBufferLimit) { +TEST_F(ConfigurationImplTest, SetUpstreamClusterPerConnectionBufferLimit) { std::string json = R"EOF( { "listeners" : [], @@ -213,8 +219,7 @@ TEST(ConfigurationImplTest, SetUpstreamClusterPerConnectionBufferLimit) { Json::ObjectSharedPtr loader = Json::Factory::loadFromString(json); - NiceMock server; - MainImpl config(server); + MainImpl config(server_, cluster_manager_factory_); config.initialize(*loader); ASSERT_EQ(1U, config.clusterManager().clusters().count("test_cluster")); @@ -224,10 +229,10 @@ TEST(ConfigurationImplTest, SetUpstreamClusterPerConnectionBufferLimit) { ->second.get() .info() ->perConnectionBufferLimitBytes()); - server.thread_local_.shutdownThread(); + server_.thread_local_.shutdownThread(); } -TEST(ConfigurationImplTest, BadListenerConfig) { +TEST_F(ConfigurationImplTest, BadListenerConfig) { std::string json = R"EOF( { "listeners" : [ @@ -245,12 +250,11 @@ TEST(ConfigurationImplTest, BadListenerConfig) { Json::ObjectSharedPtr loader = Json::Factory::loadFromString(json); - NiceMock server; - MainImpl config(server); + MainImpl config(server_, cluster_manager_factory_); EXPECT_THROW(config.initialize(*loader), Json::Exception); } -TEST(ConfigurationImplTest, BadFilterConfig) { +TEST_F(ConfigurationImplTest, BadFilterConfig) { std::string json = R"EOF( { "listeners" : [ @@ -273,12 +277,11 @@ TEST(ConfigurationImplTest, BadFilterConfig) { Json::ObjectSharedPtr loader = Json::Factory::loadFromString(json); - NiceMock server; - MainImpl config(server); + MainImpl config(server_, cluster_manager_factory_); EXPECT_THROW(config.initialize(*loader), Json::Exception); } -TEST(ConfigurationImplTest, BadFilterName) { +TEST_F(ConfigurationImplTest, BadFilterName) { std::string json = R"EOF( { "listeners" : [ @@ -301,13 +304,12 @@ TEST(ConfigurationImplTest, BadFilterName) { Json::ObjectSharedPtr loader = Json::Factory::loadFromString(json); - NiceMock server; - MainImpl config(server); + MainImpl config(server_, cluster_manager_factory_); EXPECT_THROW_WITH_MESSAGE(config.initialize(*loader), EnvoyException, "unable to create filter factory for 'invalid'/'read'"); } -TEST(ConfigurationImplTest, ServiceClusterNotSetWhenLSTracing) { +TEST_F(ConfigurationImplTest, ServiceClusterNotSetWhenLSTracing) { std::string json = R"EOF( { "listeners" : [ @@ -334,13 +336,12 @@ TEST(ConfigurationImplTest, ServiceClusterNotSetWhenLSTracing) { Json::ObjectSharedPtr loader = Json::Factory::loadFromString(json); - NiceMock server; - server.local_info_.cluster_name_ = ""; - MainImpl config(server); + server_.local_info_.cluster_name_ = ""; + MainImpl config(server_, cluster_manager_factory_); EXPECT_THROW(config.initialize(*loader), EnvoyException); } -TEST(ConfigurationImplTest, NullTracerSetWhenTracingConfigurationAbsent) { +TEST_F(ConfigurationImplTest, NullTracerSetWhenTracingConfigurationAbsent) { std::string json = R"EOF( { "listeners" : [ @@ -357,15 +358,14 @@ TEST(ConfigurationImplTest, NullTracerSetWhenTracingConfigurationAbsent) { Json::ObjectSharedPtr loader = Json::Factory::loadFromString(json); - NiceMock server; - server.local_info_.cluster_name_ = ""; - MainImpl config(server); + server_.local_info_.cluster_name_ = ""; + MainImpl config(server_, cluster_manager_factory_); config.initialize(*loader); EXPECT_NE(nullptr, dynamic_cast(&config.httpTracer())); } -TEST(ConfigurationImplTest, NullTracerSetWhenHttpKeyAbsentFromTracerConfiguration) { +TEST_F(ConfigurationImplTest, NullTracerSetWhenHttpKeyAbsentFromTracerConfiguration) { std::string json = R"EOF( { "listeners" : [ @@ -392,15 +392,14 @@ TEST(ConfigurationImplTest, NullTracerSetWhenHttpKeyAbsentFromTracerConfiguratio Json::ObjectSharedPtr loader = Json::Factory::loadFromString(json); - NiceMock server; - server.local_info_.cluster_name_ = ""; - MainImpl config(server); + server_.local_info_.cluster_name_ = ""; + MainImpl config(server_, cluster_manager_factory_); config.initialize(*loader); EXPECT_NE(nullptr, dynamic_cast(&config.httpTracer())); } -TEST(ConfigurationImplTest, ConfigurationFailsWhenInvalidTracerSpecified) { +TEST_F(ConfigurationImplTest, ConfigurationFailsWhenInvalidTracerSpecified) { std::string json = R"EOF( { "listeners" : [ @@ -427,8 +426,7 @@ TEST(ConfigurationImplTest, ConfigurationFailsWhenInvalidTracerSpecified) { Json::ObjectSharedPtr loader = Json::Factory::loadFromString(json); - NiceMock server; - MainImpl config(server); + MainImpl config(server_, cluster_manager_factory_); EXPECT_THROW_WITH_MESSAGE(config.initialize(*loader), EnvoyException, "No HttpTracerFactory found for type: invalid"); } @@ -450,7 +448,7 @@ class TestDeprecatedEchoConfigFactory : public NetworkFilterConfigFactory { } }; -TEST(NetworkFilterConfigTest, DeprecatedFilterConfigFactoryRegistrationTest) { +TEST_F(ConfigurationImplTest, DeprecatedFilterConfigFactoryRegistrationTest) { // Test ensures that the deprecated network filter registration still works without error. // Register the config factory @@ -478,8 +476,7 @@ TEST(NetworkFilterConfigTest, DeprecatedFilterConfigFactoryRegistrationTest) { Json::ObjectSharedPtr loader = Json::Factory::loadFromString(json); - NiceMock server; - MainImpl config(server); + MainImpl config(server_, cluster_manager_factory_); config.initialize(*loader); } } // Configuration diff --git a/test/server/options_impl_test.cc b/test/server/options_impl_test.cc index 6af55e0aa88a..28d11b02f4a7 100644 --- a/test/server/options_impl_test.cc +++ b/test/server/options_impl_test.cc @@ -27,11 +27,16 @@ TEST(OptionsImplDeathTest, HotRestartVersion) { EXPECT_EXIT(createOptionsImpl("envoy --hot-restart-version"), testing::ExitedWithCode(0), ""); } +TEST(OptionsImplDeathTest, InvalidMode) { + EXPECT_EXIT(createOptionsImpl("envoy --mode bogus"), testing::ExitedWithCode(1), "bogus"); +} + TEST(OptionsImplTest, All) { std::unique_ptr options = createOptionsImpl( - "envoy --concurrency 2 -c hello --admin-address-path path --restart-epoch 1 -l info " - "--service-cluster cluster --service-node node --service-zone zone " + "envoy --mode validate --concurrency 2 -c hello --admin-address-path path --restart-epoch 1 " + "-l info --service-cluster cluster --service-node node --service-zone zone " "--file-flush-interval-msec 9000 --drain-time-s 60 --parent-shutdown-time-s 90"); + EXPECT_EQ(Server::Mode::Validate, options->mode()); EXPECT_EQ(2U, options->concurrency()); EXPECT_EQ("hello", options->configPath()); EXPECT_EQ("path", options->adminAddressPath()); @@ -50,5 +55,6 @@ TEST(OptionsImplTest, DefaultParams) { EXPECT_EQ(std::chrono::seconds(600), options->drainTime()); EXPECT_EQ(std::chrono::seconds(900), options->parentShutdownTime()); EXPECT_EQ("", options->adminAddressPath()); + EXPECT_EQ(Server::Mode::Serve, options->mode()); } } // Envoy diff --git a/test/server/server_test.cc b/test/server/server_test.cc index efccfca86a0f..70db4b7a4140 100644 --- a/test/server/server_test.cc +++ b/test/server/server_test.cc @@ -37,17 +37,6 @@ TEST(InitManagerImplTest, Targets) { target.callback_(); } -class TestComponentFactory : public ComponentFactory { -public: - Server::DrainManagerPtr createDrainManager(Server::Instance&) override { - return Server::DrainManagerPtr{new Server::TestDrainManager()}; - } - Runtime::LoaderPtr createRuntime(Server::Instance& server, - Server::Configuration::Initial& config) override { - return Server::InstanceUtil::createRuntime(server, config); - } -}; - // Class creates minimally viable server instance for testing. class ServerInstanceImplTest : public testing::Test { protected: