diff --git a/api/envoy/admin/v2alpha/config_dump.proto b/api/envoy/admin/v2alpha/config_dump.proto index f81b7a3d6255c..8f78148ff45ac 100644 --- a/api/envoy/admin/v2alpha/config_dump.proto +++ b/api/envoy/admin/v2alpha/config_dump.proto @@ -17,17 +17,17 @@ import "gogoproto/gogo.proto"; // The :ref:`/config_dump ` admin endpoint uses this wrapper // message to maintain and serve arbitrary configuration information from any component in Envoy. message ConfigDump { - // This map is serialized and dumped in its entirety at the + // This list is serialized and dumped in its entirety at the // :ref:`/config_dump ` endpoint. // - // Keys are a short descriptor of the config object they map to. The following keys (and the - // messages they map to) are currently supported: + // The following configurations are currently supported and will be dumped in the order given + // below: // // * *bootstrap*: :ref:`BootstrapConfigDump ` - // * *listeners*: :ref:`ListenersConfigDump ` // * *clusters*: :ref:`ClustersConfigDump ` + // * *listeners*: :ref:`ListenersConfigDump ` // * *routes*: :ref:`RoutesConfigDump ` - map configs = 1 [(gogoproto.nullable) = false]; + repeated google.protobuf.Any configs = 1 [(gogoproto.nullable) = false]; } // This message describes the bootstrap configuration that Envoy was started with. This includes diff --git a/source/server/http/admin.cc b/source/server/http/admin.cc index 3e074e109d102..9a7c072880e39 100644 --- a/source/server/http/admin.cc +++ b/source/server/http/admin.cc @@ -389,13 +389,11 @@ Http::Code AdminImpl::handlerClusters(absl::string_view url, Http::HeaderMap& re Http::Code AdminImpl::handlerConfigDump(absl::string_view, Http::HeaderMap& response_headers, Buffer::Instance& response, AdminStream&) const { envoy::admin::v2alpha::ConfigDump dump; - auto& config_dump_map = *(dump.mutable_configs()); for (const auto& key_callback_pair : config_tracker_.getCallbacksMap()) { ProtobufTypes::MessagePtr message = key_callback_pair.second(); RELEASE_ASSERT(message, ""); - ProtobufWkt::Any any_message; + auto& any_message = *(dump.add_configs()); any_message.PackFrom(*message); - config_dump_map[key_callback_pair.first] = any_message; } response_headers.insertContentType().value().setReference( diff --git a/test/integration/integration_admin_test.cc b/test/integration/integration_admin_test.cc index 0376aa9190acf..2b0cf450b9fa9 100644 --- a/test/integration/integration_admin_test.cc +++ b/test/integration/integration_admin_test.cc @@ -283,20 +283,25 @@ TEST_P(IntegrationAdminTest, Admin) { EXPECT_STREQ("200", response->headers().Status()->value().c_str()); EXPECT_STREQ("application/json", ContentType(response)); json = Json::Factory::loadFromString(response->body()); - EXPECT_TRUE(json->getObject("configs")->hasObject("bootstrap")); - EXPECT_TRUE(json->getObject("configs")->hasObject("clusters")); - EXPECT_TRUE(json->getObject("configs")->hasObject("listeners")); - EXPECT_TRUE(json->getObject("configs")->hasObject("routes")); + size_t index = 0; + const std::string expected_types[] = { + "type.googleapis.com/envoy.admin.v2alpha.BootstrapConfigDump", + "type.googleapis.com/envoy.admin.v2alpha.ClustersConfigDump", + "type.googleapis.com/envoy.admin.v2alpha.ListenersConfigDump", + "type.googleapis.com/envoy.admin.v2alpha.RoutesConfigDump"}; + for (Json::ObjectSharedPtr obj_ptr : json->getObjectArray("configs")) { + EXPECT_TRUE(expected_types[index].compare(obj_ptr->getString("@type")) == 0); + index++; + } + // Validate we can parse as proto. envoy::admin::v2alpha::ConfigDump config_dump; MessageUtil::loadFromJson(response->body(), config_dump); - EXPECT_EQ(1, config_dump.configs().count("bootstrap")); - EXPECT_EQ(1, config_dump.configs().count("clusters")); - EXPECT_EQ(1, config_dump.configs().count("listeners")); - EXPECT_EQ(1, config_dump.configs().count("routes")); + EXPECT_EQ(4, config_dump.configs_size()); + // .. and that we can unpack one of the entries. envoy::admin::v2alpha::RoutesConfigDump route_config_dump; - config_dump.configs().at("routes").UnpackTo(&route_config_dump); + config_dump.configs(3).UnpackTo(&route_config_dump); EXPECT_EQ("route_config_0", route_config_dump.static_route_configs(0).route_config().name()); } diff --git a/test/server/http/admin_test.cc b/test/server/http/admin_test.cc index def04abb45d25..7b44491fce327 100644 --- a/test/server/http/admin_test.cc +++ b/test/server/http/admin_test.cc @@ -532,12 +532,12 @@ TEST_P(AdminInstanceTest, ConfigDump) { return msg; }); const std::string expected_json = R"EOF({ - "configs": { - "foo": { + "configs": [ + { "@type": "type.googleapis.com/google.protobuf.StringValue", "value": "bar" } - } + ] } )EOF"; EXPECT_EQ(Http::Code::OK, getCallback("/config_dump", header_map, response)); @@ -545,6 +545,59 @@ TEST_P(AdminInstanceTest, ConfigDump) { EXPECT_EQ(expected_json, output); } +TEST_P(AdminInstanceTest, ConfigDumpMaintainsOrder) { + // Add configs in random order and validate config_dump dumps in the order. + auto bootstrap_entry = admin_.getConfigTracker().add("bootstrap", [] { + auto msg = std::make_unique(); + msg->set_value("bootstrap_config"); + return msg; + }); + auto route_entry = admin_.getConfigTracker().add("routes", [] { + auto msg = std::make_unique(); + msg->set_value("routes_config"); + return msg; + }); + auto listener_entry = admin_.getConfigTracker().add("listeners", [] { + auto msg = std::make_unique(); + msg->set_value("listeners_config"); + return msg; + }); + auto cluster_entry = admin_.getConfigTracker().add("clusters", [] { + auto msg = std::make_unique(); + msg->set_value("clusters_config"); + return msg; + }); + const std::string expected_json = R"EOF({ + "configs": [ + { + "@type": "type.googleapis.com/google.protobuf.StringValue", + "value": "bootstrap_config" + }, + { + "@type": "type.googleapis.com/google.protobuf.StringValue", + "value": "clusters_config" + }, + { + "@type": "type.googleapis.com/google.protobuf.StringValue", + "value": "listeners_config" + }, + { + "@type": "type.googleapis.com/google.protobuf.StringValue", + "value": "routes_config" + } + ] +} +)EOF"; + // Run it multiple times and validate that order is preserved. + for (size_t i = 0; i < 5; i++) { + Buffer::OwnedImpl response; + Http::HeaderMapImpl header_map; + EXPECT_EQ(Http::Code::OK, getCallback("/config_dump", header_map, response)); + const std::string output = response.toString(); + EXPECT_EQ(expected_json, output); + } +} + TEST_P(AdminInstanceTest, Runtime) { Http::HeaderMapImpl header_map; Buffer::OwnedImpl response;