-
Notifications
You must be signed in to change notification settings - Fork 5.5k
[fuzz] Added random load balancer fuzz #13400
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
Changes from 11 commits
b4bb682
8df52a8
7ee7b14
9e8d678
b977914
1cfaa5d
73c24ed
54d5b67
d5f072f
8c00f14
810eb49
413214c
b5619fe
c4a53c0
6bcb394
ea0264a
e43bb2c
6e3c420
a7d7e88
5c6bdaf
92666c1
6ee35db
a81eabe
31a5668
91e5738
794918b
c1c8e29
741b3a0
52cbbd5
4bec47d
11a7f74
4675237
d07db49
ff8e22f
d71c5d1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| #include "test/common/upstream/load_balancer_fuzz.h" | ||
|
|
||
| #include "test/common/upstream/utility.h" | ||
|
|
||
| namespace Envoy { | ||
| namespace Upstream { | ||
|
|
||
| // Anonymous namespace for helper functions | ||
| namespace { | ||
| std::vector<uint64_t> | ||
| constructByteVectorForRandom(test::common::upstream::LoadBalancerTestCase input) { | ||
| std::vector<uint64_t> byteVector; | ||
| for (int i = 0; i < input.bytestring_for_random_calls().size(); ++i) { | ||
| byteVector.push_back(input.bytestring_for_random_calls(i)); | ||
| } | ||
| return byteVector; | ||
| } | ||
| } // namespace | ||
|
|
||
| // Required because super class constructor has logic | ||
| LoadBalancerFuzzBase::LoadBalancerFuzzBase() : LoadBalancerFuzzTestBase() {} | ||
|
|
||
| void LoadBalancerFuzzBase::initializeFixedHostSets(uint32_t num_hosts_in_priority_set, | ||
| uint32_t num_hosts_in_failover_set) { | ||
| // TODO: Cap on ports? | ||
| int port = 80; | ||
| for (uint32_t i = 0; i < num_hosts_in_priority_set; ++i) { | ||
| host_set_.hosts_.push_back(makeTestHost(info_, "tcp://127.0.0.1:" + std::to_string(port))); | ||
| ++port; | ||
| } | ||
| for (uint32_t i = 0; i < num_hosts_in_failover_set; ++i) { | ||
| failover_host_set_.hosts_.push_back( | ||
| makeTestHost(info_, "tcp://127.0.0.1:" + std::to_string(port))); | ||
| ++port; | ||
| } | ||
| // TODO: More than two hosts? | ||
| } | ||
|
|
||
| void LoadBalancerFuzzBase::initializeAndReplay(test::common::upstream::LoadBalancerTestCase input) { | ||
| // TODO: Keep this random instantiation even in load balancers that don't call into it, or do they | ||
| // all? | ||
| random_.bytestring_ = constructByteVectorForRandom(input); | ||
|
zasweq marked this conversation as resolved.
Outdated
|
||
| try { | ||
| initialize(input); // Initializes specific load balancers | ||
| } catch (EnvoyException& e) { | ||
| ENVOY_LOG_MISC(debug, "EnvoyException: {}", e.what()); | ||
| return; | ||
| } | ||
| initializeFixedHostSets(input.num_hosts_in_priority_set(), input.num_hosts_in_failover_set()); | ||
| replay(input); | ||
| } | ||
|
|
||
| // So, these should be shared amongst all of the types. Since logically, we're just setting the mock | ||
| // priority set to have certain values, we're doing the same thing across all of them here | ||
| void LoadBalancerFuzzBase::updateHealthFlagsForAHostSet(bool failover_host_set, | ||
| uint32_t num_healthy_hosts, | ||
| uint32_t num_degraded_hosts, | ||
| uint32_t num_excluded_hosts) { | ||
| MockHostSet& host_set = *priority_set_.getMockHostSet(int(failover_host_set)); | ||
| uint32_t i = 0; | ||
| for (; i < num_healthy_hosts && i < host_set.hosts_.size(); ++i) { | ||
| host_set.healthy_hosts_.push_back(host_set.hosts_[i]); | ||
| } | ||
| for (; i < (num_healthy_hosts + num_degraded_hosts) && i < host_set.hosts_.size(); ++i) { | ||
| host_set.degraded_hosts_.push_back(host_set.hosts_[i]); | ||
| } | ||
|
|
||
| for (; i < (num_healthy_hosts + num_degraded_hosts + num_excluded_hosts) && | ||
| i < host_set.hosts_.size(); | ||
| ++i) { | ||
| host_set.excluded_hosts_.push_back(host_set.hosts_[i]); | ||
| } | ||
|
|
||
| host_set.runCallbacks({}, {}); | ||
| } | ||
|
|
||
| void LoadBalancerFuzzBase::replay(test::common::upstream::LoadBalancerTestCase input) { | ||
| constexpr auto max_actions = 64; | ||
| for (int i = 0; i < std::min(max_actions, input.actions().size()); ++i) { | ||
| const auto& event = input.actions(i); | ||
| ENVOY_LOG_MISC(trace, "Action: {}", event.DebugString()); | ||
| switch (event.action_selector_case()) { | ||
| case test::common::upstream::LbAction::kUpdateHealthFlags: { | ||
| updateHealthFlagsForAHostSet(event.update_health_flags().failover_host_set(), | ||
| event.update_health_flags().num_degraded_hosts(), | ||
| event.update_health_flags().num_excluded_hosts()); | ||
| break; | ||
| } | ||
| case test::common::upstream::LbAction::kPrefetch: { | ||
| prefetch(); | ||
| break; | ||
| } | ||
| case test::common::upstream::LbAction::kChooseHost: { | ||
| chooseHost(); | ||
| break; | ||
| } | ||
| default: | ||
| break; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| RandomLoadBalancerFuzzTest::RandomLoadBalancerFuzzTest() : LoadBalancerFuzzBase() {} | ||
|
|
||
| void RandomLoadBalancerFuzzTest::initialize(test::common::upstream::LoadBalancerTestCase input) { | ||
| load_balancer_ = std::make_unique<RandomLoadBalancer>(priority_set_, nullptr, stats_, runtime_, | ||
| random_, input.common_lb_config()); | ||
| } | ||
|
|
||
| // For random load balancing, a randomly generated uint64 gets modded against the hosts to choose | ||
| // from. This is not something an untrusted upstream can affect, and fuzzing must be deterministic, | ||
| // so the fuzzer generates a bytestring which represents the random calls. | ||
|
|
||
| // Logic specific for random load balancers | ||
| void RandomLoadBalancerFuzzTest::prefetch() { | ||
| // random() calls are handled by fake random | ||
| load_balancer_->peekAnotherHost(nullptr); | ||
| } | ||
|
|
||
| void RandomLoadBalancerFuzzTest::chooseHost() { load_balancer_->chooseHost(nullptr); } | ||
|
|
||
| } // namespace Upstream | ||
| } // namespace Envoy | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| #include "common/upstream/load_balancer_impl.h" | ||
|
|
||
| #include "test/common/upstream/load_balancer_fuzz.pb.validate.h" | ||
| #include "test/common/upstream/load_balancer_fuzz_test_utils.h" | ||
|
|
||
| namespace Envoy { | ||
| namespace Upstream { | ||
|
|
||
| class LoadBalancerFuzzBase : public LoadBalancerFuzzTestBase { | ||
|
zasweq marked this conversation as resolved.
Outdated
|
||
| public: | ||
| LoadBalancerFuzzBase(); | ||
|
|
||
| // Untrusted upstreams don't have the ability to change the host set size, so keep it constant | ||
| // over the fuzz iteration. | ||
| void initializeFixedHostSets(uint32_t num_hosts_in_priority_set, | ||
| uint32_t num_hosts_in_failover_set); | ||
|
|
||
| virtual void initialize(test::common::upstream::LoadBalancerTestCase input) PURE; | ||
| void initializeAndReplay(test::common::upstream::LoadBalancerTestCase input); | ||
|
asraa marked this conversation as resolved.
Outdated
|
||
| void updateHealthFlagsForAHostSet(bool failover_host_set, uint32_t num_healthy_hosts, | ||
| uint32_t num_degraded_hosts = 0, | ||
| uint32_t num_excluded_hosts = 0); | ||
| // These two actions have a lot of logic attached to them such as mocks, so you need to delegate | ||
| // these to specific logic per each specific load balancer. This makes sense, as the load | ||
| // balancing algorithms sometimes use other components which are tightly coupled into the | ||
| // algorithm logically. | ||
| virtual void prefetch() PURE; | ||
| virtual void chooseHost() PURE; | ||
| virtual ~LoadBalancerFuzzBase() = default; | ||
|
|
||
| private: | ||
| void replay(test::common::upstream::LoadBalancerTestCase input); | ||
| // TODO: Move load balancer here? | ||
| }; | ||
|
|
||
| class RandomLoadBalancerFuzzTest : public LoadBalancerFuzzBase { | ||
| public: | ||
| RandomLoadBalancerFuzzTest(); | ||
| void initialize(test::common::upstream::LoadBalancerTestCase input) override; | ||
| // Has mock logic for random | ||
| void prefetch() override; | ||
| void chooseHost() override; | ||
| ~RandomLoadBalancerFuzzTest() = default; | ||
|
|
||
| std::unique_ptr<RandomLoadBalancer> load_balancer_; | ||
| }; | ||
|
|
||
| // TODO: Since each load balancer has a specific config, this will need to be addressed in their | ||
|
asraa marked this conversation as resolved.
Outdated
|
||
| // specific base classes. We can do this with an extended proto, which also will contain specific | ||
| // logic for mocks | ||
|
|
||
| } // namespace Upstream | ||
| } // namespace Envoy | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| syntax = "proto3"; | ||
|
|
||
| package test.common.upstream; | ||
|
|
||
| import "validate/validate.proto"; | ||
| import "envoy/config/cluster/v3/cluster.proto"; | ||
| import "google/protobuf/empty.proto"; | ||
|
|
||
| message UpdateHealthFlags { | ||
| bool failover_host_set = 1; | ||
|
zasweq marked this conversation as resolved.
Outdated
|
||
| uint32 num_healthy_hosts = 2; | ||
|
zasweq marked this conversation as resolved.
|
||
| uint32 num_degraded_hosts = 3; | ||
| uint32 num_excluded_hosts = 4; | ||
| } | ||
|
|
||
| //Left as message in case specific load balancers need something generated for these actions | ||
| message ChooseHost { | ||
| } | ||
|
|
||
| message Prefetch { | ||
| } | ||
|
|
||
| message LbAction { | ||
| oneof action_selector { | ||
| option (validate.required) = true; | ||
| UpdateHealthFlags update_health_flags = 1; | ||
| Prefetch prefetch = 2; | ||
| ChooseHost choose_host = 3; | ||
| } | ||
| } | ||
|
|
||
| message LoadBalancerTestCase { | ||
| envoy.config.cluster.v3.Cluster.CommonLbConfig common_lb_config = 1 | ||
| [(validate.rules).message.required = true]; | ||
| repeated LbAction actions = 2; | ||
| //This is capped at the max port value on an ip address - 80 | ||
|
zasweq marked this conversation as resolved.
Outdated
|
||
| int64 num_hosts_in_priority_set = 3 [(validate.rules).int64.lt = 65455]; | ||
|
zasweq marked this conversation as resolved.
Outdated
|
||
| int64 num_hosts_in_failover_set = 4 [(validate.rules).int64.lt = 65455]; | ||
| repeated int64 bytestring_for_random_calls = 5 | ||
|
zasweq marked this conversation as resolved.
Outdated
|
||
| [(validate.rules).repeated = {min_items: 1, max_items: 50}]; | ||
|
zasweq marked this conversation as resolved.
Outdated
|
||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.