-
Notifications
You must be signed in to change notification settings - Fork 5.4k
request_info: initial implementation of dynamic metadata object #3918
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
mattklein123
merged 33 commits into
envoyproxy:master
from
curiouserrandy:DynamicMetadata
Aug 5, 2018
Merged
Changes from all commits
Commits
Show all changes
33 commits
Select commit
Hold shift + click to select a range
de5288a
Initial implementation of DynamicMetadata & StringAccessor
3b1250e
Integrated (I hope) with RequestInfo.
ee73e1f
Initial dynamic metadata test passes.
c8066df
All DM tests.
653fdd7
Trivial request_info test.
36a3594
Format fixed.
1bfe99c
Changes forced by asan testing.
603c588
Got test to build.
199a5c8
Fixed format
229414d
Incorporated most comments.
48f2246
Made type id thread safe.
ed497c8
Removed insertion into RequestInfo
dbbaec5
fit fix format
cedbac4
Cleanup
4207f27
Merge branch 'master' into DynamicMetadata
6864aa4
Added doxygen comments and finer grained probe function
18b3b57
Added missing const markers
0df9cfa
dock fix
510368d
Incoporated comments.
88fade4
dock fix
66d640e
Removed StringAccessor.
5dd51be
Merge remote-tracking branch 'origin/DynamicMetadata_xfer' into Dynam…
c5f3775
Shifted source associated with template MP to source/common
4a89207
Switched implementation over to dynamic_cast<> with all that represents.
d76a5fe
... and finished tests.
e59f4be
Incorporated rest of comments.
c6ba24c
Fix format
5205592
Merge remote-tracking branch 'origin/DynamicMetadata_xfer' into Dynam…
9a5ba4f
Remove unneeded header.
9cb16cc
Changed DynamicMetadataObject to Object
65d8e16
Incorporated comments
e62097f
fix format
d221181
Incorporated comments.
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| #pragma once | ||
|
|
||
| #include <memory> | ||
|
|
||
| #include "envoy/common/exception.h" | ||
| #include "envoy/common/pure.h" | ||
|
|
||
| #include "common/common/fmt.h" | ||
|
|
||
| #include "absl/strings/string_view.h" | ||
|
|
||
| namespace Envoy { | ||
| namespace RequestInfo { | ||
|
|
||
| class DynamicMetadata { | ||
| public: | ||
| class Object { | ||
| public: | ||
| virtual ~Object(){}; | ||
| }; | ||
|
|
||
| virtual ~DynamicMetadata(){}; | ||
|
|
||
| /** | ||
| * @param data_name the name of the data being set. | ||
| * @param data an owning pointer to the data to be stored. | ||
| * Note that it is an error to call setData() twice with the same data_name; this is to | ||
| * enforce a single authoritative source for each piece of data stored in DynamicMetadata. | ||
| */ | ||
| virtual void setData(absl::string_view data_name, std::unique_ptr<Object>&& data) PURE; | ||
|
|
||
| /** | ||
| * @param data_name the name of the data being set. | ||
| * @return a reference to the stored data. | ||
| * Note that it is an error to access data that has not previously been set. | ||
| * This function will fail if the data stored under |data_name| cannot be | ||
| * dynamically cast to the type specified. | ||
| */ | ||
| template <typename T> const T& getData(absl::string_view data_name) const { | ||
| const T* result = dynamic_cast<const T*>(getDataGeneric(data_name)); | ||
| if (!result) { | ||
| throw EnvoyException( | ||
| fmt::format("Data stored under {} cannot be coerced to specified type", data_name)); | ||
| } | ||
| return *result; | ||
| } | ||
|
|
||
| /** | ||
| * @param data_name the name of the data being probed. | ||
| * @return Whether data of the type and name specified exists in the | ||
| * data store. | ||
| */ | ||
| template <typename T> bool hasData(absl::string_view data_name) const { | ||
| return (hasDataWithName(data_name) && | ||
| (dynamic_cast<const T*>(getDataGeneric(data_name)) != nullptr)); | ||
| } | ||
|
|
||
| /** | ||
| * @param data_name the name of the data being probed. | ||
| * @return Whether data of any type and the name specified exists in the | ||
| * data store. | ||
| */ | ||
| virtual bool hasDataWithName(absl::string_view data_name) const PURE; | ||
|
|
||
| protected: | ||
| virtual const Object* getDataGeneric(absl::string_view data_name) const PURE; | ||
| }; | ||
|
|
||
| } // namespace RequestInfo | ||
| } // namespace Envoy |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| #include "common/request_info/dynamic_metadata_impl.h" | ||
|
|
||
| #include "envoy/common/exception.h" | ||
|
|
||
| namespace Envoy { | ||
| namespace RequestInfo { | ||
|
|
||
| void DynamicMetadataImpl::setData(absl::string_view data_name, std::unique_ptr<Object>&& data) { | ||
| if (data_storage_.find(data_name) != data_storage_.end()) { | ||
| throw EnvoyException("DynamicMetadata::setData<T> called twice with same name."); | ||
| } | ||
| // absl::string_view will not convert to std::string without an explicit case; see | ||
| // https://github.com/abseil/abseil-cpp/blob/master/absl/strings/string_view.h#L328 | ||
| data_storage_[static_cast<std::string>(data_name)] = std::move(data); | ||
| } | ||
|
|
||
| bool DynamicMetadataImpl::hasDataWithName(absl::string_view data_name) const { | ||
| return data_storage_.count(data_name) > 0; | ||
| } | ||
|
|
||
| const DynamicMetadata::Object* | ||
| DynamicMetadataImpl::getDataGeneric(absl::string_view data_name) const { | ||
| const auto& it = data_storage_.find(data_name); | ||
|
|
||
| if (it == data_storage_.end()) { | ||
| throw EnvoyException("DynamicMetadata::getData<T> called for unknown data name."); | ||
| } | ||
| return it->second.get(); | ||
| } | ||
|
|
||
| } // namespace RequestInfo | ||
| } // namespace Envoy | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| #pragma once | ||
|
|
||
| #include <map> | ||
|
|
||
| #include "envoy/request_info/dynamic_metadata.h" | ||
|
|
||
| #include "absl/strings/string_view.h" | ||
|
|
||
| namespace Envoy { | ||
| namespace RequestInfo { | ||
|
|
||
| class DynamicMetadataImpl : public DynamicMetadata { | ||
| public: | ||
| // DynamicMetadata | ||
| void setData(absl::string_view data_name, std::unique_ptr<Object>&& data) override; | ||
| bool hasDataWithName(absl::string_view) const override; | ||
| const Object* getDataGeneric(absl::string_view data_name) const override; | ||
|
|
||
| private: | ||
| // The explicit non-type-specific comparator is necessary to allow use of find() method | ||
| // with absl::string_view. See | ||
| // https://stackoverflow.com/questions/20317413/what-are-transparent-comparators. | ||
| std::map<std::string, std::unique_ptr<Object>, std::less<>> data_storage_; | ||
| }; | ||
|
|
||
| } // namespace RequestInfo | ||
| } // namespace Envoy |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,171 @@ | ||
| #include "envoy/common/exception.h" | ||
|
|
||
| #include "common/request_info/dynamic_metadata_impl.h" | ||
|
|
||
| #include "test/test_common/utility.h" | ||
|
|
||
| #include "gtest/gtest.h" | ||
|
|
||
| namespace Envoy { | ||
| namespace RequestInfo { | ||
| namespace { | ||
|
|
||
| class TestStoredTypeTracking : public DynamicMetadata::Object { | ||
| public: | ||
| TestStoredTypeTracking(int value, size_t* access_count, size_t* destruction_count) | ||
| : value_(value), access_count_(access_count), destruction_count_(destruction_count) {} | ||
| ~TestStoredTypeTracking() { | ||
| if (destruction_count_) { | ||
| ++*destruction_count_; | ||
| } | ||
| } | ||
|
|
||
| int access() const { | ||
| if (access_count_) { | ||
| ++*access_count_; | ||
| } | ||
| return value_; | ||
| } | ||
|
|
||
| private: | ||
| int value_; | ||
| size_t* access_count_; | ||
| size_t* destruction_count_; | ||
| }; | ||
|
|
||
| class SimpleType : public DynamicMetadata::Object { | ||
| public: | ||
| SimpleType(int value) : value_(value) {} | ||
|
|
||
| int access() const { return value_; } | ||
|
|
||
| private: | ||
| int value_; | ||
| }; | ||
|
|
||
| class DynamicMetadataImplTest : public testing::Test { | ||
| public: | ||
| DynamicMetadataImplTest() { resetDynamicMetadata(); } | ||
|
|
||
| void resetDynamicMetadata() { dynamic_metadata_ = std::make_unique<DynamicMetadataImpl>(); } | ||
| DynamicMetadata& dynamic_metadata() { return *dynamic_metadata_; } | ||
|
|
||
| private: | ||
| std::unique_ptr<DynamicMetadataImpl> dynamic_metadata_; | ||
| }; | ||
|
|
||
| } // namespace | ||
|
|
||
| TEST_F(DynamicMetadataImplTest, Simple) { | ||
| size_t access_count = 0u; | ||
| size_t destruction_count = 0u; | ||
| dynamic_metadata().setData( | ||
| "test_name", std::make_unique<TestStoredTypeTracking>(5, &access_count, &destruction_count)); | ||
| EXPECT_EQ(0u, access_count); | ||
| EXPECT_EQ(0u, destruction_count); | ||
|
|
||
| EXPECT_EQ(5, dynamic_metadata().getData<TestStoredTypeTracking>("test_name").access()); | ||
| EXPECT_EQ(1u, access_count); | ||
| EXPECT_EQ(0u, destruction_count); | ||
|
|
||
| resetDynamicMetadata(); | ||
| EXPECT_EQ(1u, access_count); | ||
| EXPECT_EQ(1u, destruction_count); | ||
| } | ||
|
|
||
| TEST_F(DynamicMetadataImplTest, SameTypes) { | ||
| size_t access_count_1 = 0u; | ||
| size_t access_count_2 = 0u; | ||
| size_t destruction_count = 0u; | ||
| static const int ValueOne = 5; | ||
| static const int ValueTwo = 6; | ||
|
|
||
| dynamic_metadata().setData("test_1", std::make_unique<TestStoredTypeTracking>( | ||
| ValueOne, &access_count_1, &destruction_count)); | ||
| dynamic_metadata().setData("test_2", std::make_unique<TestStoredTypeTracking>( | ||
| ValueTwo, &access_count_2, &destruction_count)); | ||
| EXPECT_EQ(0u, access_count_1); | ||
| EXPECT_EQ(0u, access_count_2); | ||
| EXPECT_EQ(0u, destruction_count); | ||
|
|
||
| EXPECT_EQ(ValueOne, dynamic_metadata().getData<TestStoredTypeTracking>("test_1").access()); | ||
| EXPECT_EQ(1u, access_count_1); | ||
| EXPECT_EQ(0u, access_count_2); | ||
| EXPECT_EQ(ValueTwo, dynamic_metadata().getData<TestStoredTypeTracking>("test_2").access()); | ||
| EXPECT_EQ(1u, access_count_1); | ||
| EXPECT_EQ(1u, access_count_2); | ||
| resetDynamicMetadata(); | ||
| EXPECT_EQ(2u, destruction_count); | ||
| } | ||
|
|
||
| TEST_F(DynamicMetadataImplTest, SimpleType) { | ||
| dynamic_metadata().setData("test_1", std::make_unique<SimpleType>(1)); | ||
| dynamic_metadata().setData("test_2", std::make_unique<SimpleType>(2)); | ||
|
|
||
| EXPECT_EQ(1, dynamic_metadata().getData<SimpleType>("test_1").access()); | ||
| EXPECT_EQ(2, dynamic_metadata().getData<SimpleType>("test_2").access()); | ||
| } | ||
|
|
||
| TEST_F(DynamicMetadataImplTest, NameConflict) { | ||
| dynamic_metadata().setData("test_1", std::make_unique<SimpleType>(1)); | ||
| EXPECT_THROW_WITH_MESSAGE(dynamic_metadata().setData("test_1", std::make_unique<SimpleType>(2)), | ||
| EnvoyException, | ||
| "DynamicMetadata::setData<T> called twice with same name."); | ||
| EXPECT_EQ(1, dynamic_metadata().getData<SimpleType>("test_1").access()); | ||
| } | ||
|
|
||
| TEST_F(DynamicMetadataImplTest, NameConflictDifferentTypes) { | ||
| dynamic_metadata().setData("test_1", std::make_unique<SimpleType>(1)); | ||
| EXPECT_THROW_WITH_MESSAGE( | ||
| dynamic_metadata().setData("test_1", | ||
| std::make_unique<TestStoredTypeTracking>(2, nullptr, nullptr)), | ||
| EnvoyException, "DynamicMetadata::setData<T> called twice with same name."); | ||
| } | ||
|
|
||
| TEST_F(DynamicMetadataImplTest, UnknownName) { | ||
| EXPECT_THROW_WITH_MESSAGE(dynamic_metadata().getData<SimpleType>("test_1"), EnvoyException, | ||
| "DynamicMetadata::getData<T> called for unknown data name."); | ||
| } | ||
|
|
||
| TEST_F(DynamicMetadataImplTest, WrongTypeGet) { | ||
| dynamic_metadata().setData("test_name", | ||
| std::make_unique<TestStoredTypeTracking>(5, nullptr, nullptr)); | ||
| EXPECT_EQ(5, dynamic_metadata().getData<TestStoredTypeTracking>("test_name").access()); | ||
| EXPECT_THROW_WITH_MESSAGE(dynamic_metadata().getData<SimpleType>("test_name"), EnvoyException, | ||
| "Data stored under test_name cannot be coerced to specified type"); | ||
| } | ||
|
|
||
| namespace { | ||
|
|
||
| class A : public DynamicMetadata::Object {}; | ||
|
|
||
| class B : public A {}; | ||
|
|
||
| class C : public B {}; | ||
|
|
||
| } // namespace | ||
|
|
||
| TEST_F(DynamicMetadataImplTest, FungibleInheritance) { | ||
| dynamic_metadata().setData("testB", std::make_unique<B>()); | ||
| EXPECT_TRUE(dynamic_metadata().hasData<B>("testB")); | ||
| EXPECT_TRUE(dynamic_metadata().hasData<A>("testB")); | ||
| EXPECT_FALSE(dynamic_metadata().hasData<C>("testB")); | ||
|
|
||
| dynamic_metadata().setData("testC", std::make_unique<C>()); | ||
| EXPECT_TRUE(dynamic_metadata().hasData<B>("testC")); | ||
| EXPECT_TRUE(dynamic_metadata().hasData<A>("testC")); | ||
| EXPECT_TRUE(dynamic_metadata().hasData<C>("testC")); | ||
| } | ||
|
|
||
| TEST_F(DynamicMetadataImplTest, HasData) { | ||
| dynamic_metadata().setData("test_1", std::make_unique<SimpleType>(1)); | ||
| EXPECT_TRUE(dynamic_metadata().hasData<SimpleType>("test_1")); | ||
| EXPECT_FALSE(dynamic_metadata().hasData<SimpleType>("test_2")); | ||
| EXPECT_FALSE(dynamic_metadata().hasData<TestStoredTypeTracking>("test_1")); | ||
| EXPECT_FALSE(dynamic_metadata().hasData<TestStoredTypeTracking>("test_2")); | ||
| EXPECT_TRUE(dynamic_metadata().hasDataWithName("test_1")); | ||
| EXPECT_FALSE(dynamic_metadata().hasDataWithName("test_2")); | ||
| } | ||
|
|
||
| } // namespace RequestInfo | ||
| } // namespace Envoy |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit:
getDataGenericThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I intentionally made it getData<> as the receiver of the throw will be above the public API of the DynamicMetadata class, and hence will be more interested in what they did wrong in targeting that public API. Given that, do you still want it spelled getDataGeneric?