forked from envoyproxy/envoy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhot_restart_impl_test.cc
251 lines (213 loc) · 8.77 KB
/
hot_restart_impl_test.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
#include <memory>
#include "common/api/os_sys_calls_impl.h"
#include "common/common/hex.h"
#include "server/hot_restart_impl.h"
#include "test/mocks/api/mocks.h"
#include "test/mocks/server/mocks.h"
#include "test/test_common/logging.h"
#include "test/test_common/threadsafe_singleton_injector.h"
#include "absl/strings/match.h"
#include "absl/strings/string_view.h"
#include "gtest/gtest.h"
using testing::_;
using testing::Invoke;
using testing::InvokeWithoutArgs;
using testing::Return;
using testing::ReturnRef;
using testing::WithArg;
namespace Envoy {
namespace Server {
class HotRestartImplTest : public testing::Test {
public:
void setup() {
EXPECT_CALL(os_sys_calls_, shmUnlink(_));
EXPECT_CALL(os_sys_calls_, shmOpen(_, _, _));
EXPECT_CALL(os_sys_calls_, ftruncate(_, _)).WillOnce(WithArg<1>(Invoke([this](off_t size) {
buffer_.resize(size);
return Api::SysCallIntResult{0, 0};
})));
EXPECT_CALL(os_sys_calls_, mmap(_, _, _, _, _, _)).WillOnce(InvokeWithoutArgs([this]() {
return Api::SysCallPtrResult{buffer_.data(), 0};
}));
EXPECT_CALL(os_sys_calls_, bind(_, _, _));
EXPECT_CALL(options_, statsOptions()).WillRepeatedly(ReturnRef(stats_options_));
// Test we match the correct stat with empty-slots before, after, or both.
hot_restart_ = std::make_unique<HotRestartImpl>(options_);
hot_restart_->drainParentListeners();
}
Api::MockOsSysCalls os_sys_calls_;
TestThreadsafeSingletonInjector<Api::OsSysCallsImpl> os_calls{&os_sys_calls_};
NiceMock<MockOptions> options_;
Stats::StatsOptionsImpl stats_options_;
std::vector<uint8_t> buffer_;
std::unique_ptr<HotRestartImpl> hot_restart_;
};
TEST_F(HotRestartImplTest, versionString) {
// Tests that the version-string will be consistent and SharedMemory::VERSION,
// between multiple instantiations.
std::string version;
uint64_t max_stats, max_obj_name_length;
// The mocking infrastructure requires a test setup and teardown every time we
// want to re-instantiate HotRestartImpl.
{
setup();
version = hot_restart_->version();
EXPECT_TRUE(absl::StartsWith(version, fmt::format("{}.", SharedMemory::VERSION))) << version;
max_stats = options_.maxStats(); // Save this so we can double it below.
max_obj_name_length = options_.statsOptions().maxObjNameLength();
TearDown();
}
{
setup();
EXPECT_EQ(version, hot_restart_->version()) << "Version string deterministic from options";
TearDown();
}
{
ON_CALL(options_, maxStats()).WillByDefault(Return(2 * max_stats));
setup();
EXPECT_NE(version, hot_restart_->version()) << "Version changes when max-stats change";
TearDown();
}
{
stats_options_.max_obj_name_length_ = 2 * max_obj_name_length;
setup();
EXPECT_NE(version, hot_restart_->version())
<< "Version changes when max-obj-name-length changes";
// TearDown is called automatically at end of test.
}
}
// Check consistency of internal raw stat representation by comparing hash of
// memory contents against a previously recorded value.
TEST_F(HotRestartImplTest, Consistency) {
setup();
// Generate a stat, encode it to hex, and take the hash of that hex string. We
// expect the hash to vary only when the internal representation of a stat has
// been intentionally changed, in which case SharedMemory::VERSION should be
// incremented as well.
const uint64_t expected_hash = 1874506077228772558;
const uint64_t max_name_length = stats_options_.maxNameLength();
const std::string name_1(max_name_length, 'A');
Stats::RawStatData* stat_1 = hot_restart_->statsAllocator().alloc(name_1);
const uint64_t stat_size = sizeof(Stats::RawStatData) + max_name_length;
const std::string stat_hex_dump_1 = Hex::encode(reinterpret_cast<uint8_t*>(stat_1), stat_size);
EXPECT_EQ(HashUtil::xxHash64(stat_hex_dump_1), expected_hash);
EXPECT_EQ(name_1, stat_1->key());
hot_restart_->statsAllocator().free(*stat_1);
}
TEST_F(HotRestartImplTest, crossAlloc) {
setup();
Stats::RawStatData* stat1 = hot_restart_->statsAllocator().alloc("stat1");
Stats::RawStatData* stat2 = hot_restart_->statsAllocator().alloc("stat2");
Stats::RawStatData* stat3 = hot_restart_->statsAllocator().alloc("stat3");
Stats::RawStatData* stat4 = hot_restart_->statsAllocator().alloc("stat4");
Stats::RawStatData* stat5 = hot_restart_->statsAllocator().alloc("stat5");
hot_restart_->statsAllocator().free(*stat2);
hot_restart_->statsAllocator().free(*stat4);
stat2 = nullptr;
stat4 = nullptr;
EXPECT_CALL(options_, restartEpoch()).WillRepeatedly(Return(1));
EXPECT_CALL(os_sys_calls_, shmOpen(_, _, _));
EXPECT_CALL(os_sys_calls_, mmap(_, _, _, _, _, _))
.WillOnce(Return(Api::SysCallPtrResult{buffer_.data(), 0}));
EXPECT_CALL(os_sys_calls_, bind(_, _, _));
HotRestartImpl hot_restart2(options_);
Stats::RawStatData* stat1_prime = hot_restart2.statsAllocator().alloc("stat1");
Stats::RawStatData* stat3_prime = hot_restart2.statsAllocator().alloc("stat3");
Stats::RawStatData* stat5_prime = hot_restart2.statsAllocator().alloc("stat5");
EXPECT_EQ(stat1, stat1_prime);
EXPECT_EQ(stat3, stat3_prime);
EXPECT_EQ(stat5, stat5_prime);
}
TEST_F(HotRestartImplTest, allocFail) {
EXPECT_CALL(options_, maxStats()).WillRepeatedly(Return(2));
setup();
Stats::RawStatData* s1 = hot_restart_->statsAllocator().alloc("1");
Stats::RawStatData* s2 = hot_restart_->statsAllocator().alloc("2");
Stats::RawStatData* s3 = hot_restart_->statsAllocator().alloc("3");
EXPECT_NE(s1, nullptr);
EXPECT_NE(s2, nullptr);
EXPECT_EQ(s3, nullptr);
}
// Because the shared memory is managed manually, make sure it meets
// basic requirements:
// - Objects are correctly aligned so that std::atomic works properly
// - Objects don't overlap
class HotRestartImplAlignmentTest : public HotRestartImplTest,
public testing::WithParamInterface<uint64_t> {
public:
HotRestartImplAlignmentTest() : name_len_(8 + GetParam()) {
stats_options_.max_obj_name_length_ = name_len_;
EXPECT_CALL(options_, statsOptions()).WillRepeatedly(ReturnRef(stats_options_));
EXPECT_CALL(options_, maxStats()).WillRepeatedly(Return(num_stats_));
setup();
EXPECT_EQ(name_len_ + stats_options_.maxStatSuffixLength(), stats_options_.maxNameLength());
}
Stats::StatsOptionsImpl stats_options_;
static const uint64_t num_stats_ = 8;
const uint64_t name_len_;
};
TEST_P(HotRestartImplAlignmentTest, objectAlignment) {
std::set<Stats::RawStatData*> used;
for (uint64_t i = 0; i < num_stats_; i++) {
Stats::RawStatData* stat = hot_restart_->statsAllocator().alloc(fmt::format("stat {}", i));
EXPECT_TRUE((reinterpret_cast<uintptr_t>(stat) % alignof(decltype(*stat))) == 0);
EXPECT_TRUE(used.find(stat) == used.end());
used.insert(stat);
}
}
TEST_P(HotRestartImplAlignmentTest, objectOverlap) {
// Iterate through all stats forwards and backwards, writing to all fields, then read them back to
// make sure that writing to an adjacent stat didn't overwrite
struct TestStat {
Stats::RawStatData* stat_;
std::string name_;
uint64_t index_;
};
std::vector<TestStat> stats;
for (uint64_t i = 0; i < num_stats_; i++) {
std::string name = fmt::format("{}zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"
"zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"
"zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz",
i)
.substr(0, stats_options_.maxNameLength());
TestStat ts;
ts.stat_ = hot_restart_->statsAllocator().alloc(name);
ts.name_ = ts.stat_->name_;
ts.index_ = i;
// If this isn't true then the hard coded part of the name isn't long enough to make the test
// valid.
EXPECT_EQ(ts.name_.size(), stats_options_.maxNameLength());
stats.push_back(ts);
}
auto write = [](TestStat& ts) {
ts.stat_->value_ = ts.index_;
ts.stat_->pending_increment_ = ts.index_;
ts.stat_->flags_ = ts.index_;
ts.stat_->ref_count_ = ts.index_;
ts.stat_->unused_ = ts.index_;
};
auto verify = [](TestStat& ts) {
EXPECT_EQ(ts.stat_->key(), ts.name_);
EXPECT_EQ(ts.stat_->value_, ts.index_);
EXPECT_EQ(ts.stat_->pending_increment_, ts.index_);
EXPECT_EQ(ts.stat_->flags_, ts.index_);
EXPECT_EQ(ts.stat_->ref_count_, ts.index_);
EXPECT_EQ(ts.stat_->unused_, ts.index_);
};
for (TestStat& ts : stats) {
write(ts);
}
for (TestStat& ts : stats) {
verify(ts);
}
for (auto it = stats.rbegin(); it != stats.rend(); ++it) {
write(*it);
}
for (auto it = stats.rbegin(); it != stats.rend(); ++it) {
verify(*it);
}
}
INSTANTIATE_TEST_CASE_P(HotRestartImplAlignmentTest, HotRestartImplAlignmentTest,
testing::Range(0UL, alignof(Stats::RawStatData) + 1));
} // namespace Server
} // namespace Envoy