-
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 21 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| syntax = "proto3"; | ||
|
|
||
| package test.common.upstream; | ||
|
|
||
| import "validate/validate.proto"; | ||
| import "envoy/config/cluster/v3/cluster.proto"; | ||
| import "google/protobuf/empty.proto"; | ||
|
|
||
| message UpdateHealthFlags { | ||
| // The host index determines what host set within the priority set which will get updated. | ||
| uint64 host_index = 1; | ||
| // These will determine how many hosts will get placed into health hosts, degraded hosts, and | ||
| // excluded hosts from the full host list. | ||
| uint32 num_healthy_hosts = 2; | ||
|
zasweq marked this conversation as resolved.
|
||
| uint32 num_degraded_hosts = 3; | ||
| uint32 num_excluded_hosts = 4; | ||
| // This is used to determine which hosts get marked as healthy, degraded, and excluded. | ||
| bytes random_bytestring = 5 [(validate.rules).bytes = {min_len: 1, max_len: 256}]; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FWIW, an idea of making this run-length encoded just occurred to me, it would be space efficient, but not sure how that would interact with the fuzzing cross-over. |
||
| } | ||
|
|
||
| message LbAction { | ||
| oneof action_selector { | ||
| option (validate.required) = true; | ||
| UpdateHealthFlags update_health_flags = 1; | ||
| google.protobuf.Empty prefetch = 2; | ||
|
zasweq marked this conversation as resolved.
|
||
| google.protobuf.Empty choose_host = 3; | ||
| } | ||
| } | ||
|
|
||
| // This message represents what LoadBalancerFuzzBase will interact with, performing setup of host sets and calling into load balancers. | ||
| // The logic that this message represents and the base class for load balancing fuzzing will be logic that maps to all types of load balancing | ||
| // and can be used in a modular way at the highest level for each load balancer. | ||
| message LoadBalancerTestCase { | ||
| envoy.config.cluster.v3.Cluster.CommonLbConfig common_lb_config = 1 | ||
| [(validate.rules).message.required = true]; | ||
| repeated LbAction actions = 2; | ||
|
|
||
| // TODO: Localities | ||
| repeated uint32 num_hosts_in_host_set = 3 | ||
| [(validate.rules).repeated = {min_items: 1, max_items: 20}]; | ||
|
zasweq marked this conversation as resolved.
|
||
|
|
||
| // This number is used to instantiate the prng. The prng takes the place of random() calls, allowing a representative random distribution which is also deterministic. | ||
| uint64 seed_for_prng = 4 [(validate.rules).uint64.gt = 0]; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| #include "test/common/upstream/load_balancer_fuzz_base.h" | ||
|
|
||
| #include "test/common/upstream/utility.h" | ||
|
|
||
| namespace Envoy { | ||
| namespace Upstream { | ||
|
|
||
| void LoadBalancerFuzzBase::initializeASingleHostSet(uint32_t num_hosts_in_host_set, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a TODO to initialize healthy/degraded hosts too and split into localities, right? feel free to resolve if so
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, but I don't think we should initialize explicit healthy/degraded hosts. That functionality is taken care of by updateHealthFlagsForHostSet. Moving all that logic up here would clutter things up. Yes, splitting into localities is what I'm working on today. |
||
| uint8_t index_of_host_set) { | ||
|
zasweq marked this conversation as resolved.
Outdated
|
||
| ENVOY_LOG_MISC(trace, "Will attempt to initialize host set {} with {} hosts.", index_of_host_set, | ||
| num_hosts_in_host_set); | ||
| MockHostSet& host_set = *priority_set_.getMockHostSet(index_of_host_set); | ||
| uint32_t hosts_made = 0; | ||
| // Cap each host set at 256 hosts for efficiency | ||
|
zasweq marked this conversation as resolved.
Outdated
|
||
| uint32_t max_num_hosts_in_host_set = 256; | ||
|
zasweq marked this conversation as resolved.
Outdated
|
||
| // Leave port clause in for future changes | ||
| while (hosts_made < std::min(num_hosts_in_host_set, max_num_hosts_in_host_set) && port_ < 65535) { | ||
| host_set.hosts_.push_back(makeTestHost(info_, "tcp://127.0.0.1:" + std::to_string(port_))); | ||
| ++port_; | ||
| ++hosts_made; | ||
|
asraa marked this conversation as resolved.
|
||
| } | ||
| // TODO: Random call and mod against host_set size for locality logic | ||
| } | ||
|
|
||
| // Initializes random and fixed host sets | ||
| void LoadBalancerFuzzBase::initializeLbComponents( | ||
| const test::common::upstream::LoadBalancerTestCase& input) { | ||
| random_.initializeSeed(input.seed_for_prng()); | ||
| uint8_t index_of_host_set = 0; | ||
| for (uint16_t num_hosts_in_host_set : input.num_hosts_in_host_set()) { | ||
| initializeASingleHostSet(num_hosts_in_host_set, index_of_host_set); | ||
| index_of_host_set++; | ||
| } | ||
| num_host_sets_ = index_of_host_set; | ||
|
zasweq marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| // Updating host sets is shared amongst all the load balancer tests. Since logically, we're just | ||
| // setting the mock priority set to have certain values, and all load balancers interface with host | ||
| // sets and their health statuses, this action maps to all load balancers. | ||
| void LoadBalancerFuzzBase::updateHealthFlagsForAHostSet(uint64_t host_index, | ||
|
zasweq marked this conversation as resolved.
Outdated
|
||
| uint32_t num_healthy_hosts, | ||
| uint32_t num_degraded_hosts, | ||
| uint32_t num_excluded_hosts, | ||
| std::string random_bytestring) { | ||
| uint8_t index_of_host_set = host_index % num_host_sets_; | ||
|
zasweq marked this conversation as resolved.
Outdated
|
||
| ENVOY_LOG_MISC(trace, "Updating health flags for host set: {}", index_of_host_set); | ||
| MockHostSet& host_set = *priority_set_.getMockHostSet(index_of_host_set); | ||
| // This downcast will not overflow because size is capped by port numbers | ||
|
zasweq marked this conversation as resolved.
|
||
| uint32_t host_set_size = host_set.hosts_.size(); | ||
| host_set.healthy_hosts_.clear(); | ||
| host_set.degraded_hosts_.clear(); | ||
| host_set.excluded_hosts_.clear(); | ||
|
|
||
| Fuzz::ProperSubsetSelector subset_selector(random_bytestring); | ||
|
|
||
| std::vector<std::vector<uint8_t>> subsets = subset_selector.constructSubsets( | ||
| 3, {num_healthy_hosts, num_degraded_hosts, num_excluded_hosts}, host_set_size); | ||
|
|
||
| // Healthy hosts are first subset | ||
| for (uint8_t index : subsets.at(0)) { | ||
|
zasweq marked this conversation as resolved.
Outdated
|
||
| host_set.healthy_hosts_.push_back(host_set.hosts_[index]); | ||
| ENVOY_LOG_MISC(trace, "Index of host made healthy: {}", index); | ||
|
asraa marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| // Degraded hosts are second subset | ||
| for (uint8_t index : subsets.at(1)) { | ||
| host_set.degraded_hosts_.push_back(host_set.hosts_[index]); | ||
|
zasweq marked this conversation as resolved.
|
||
| ENVOY_LOG_MISC(trace, "Index of host made degraded: {}", index); | ||
| } | ||
|
|
||
| // Excluded hosts are third subset | ||
| for (uint8_t index : subsets.at(1)) { | ||
|
zasweq marked this conversation as resolved.
Outdated
|
||
| host_set.excluded_hosts_.push_back(host_set.hosts_[index]); | ||
| ENVOY_LOG_MISC(trace, "Index of host made excluded: {}", index); | ||
| } | ||
|
|
||
| host_set.runCallbacks({}, {}); | ||
|
zasweq marked this conversation as resolved.
zasweq marked this conversation as resolved.
|
||
| } | ||
|
|
||
| void LoadBalancerFuzzBase::prefetch() { | ||
| // TODO: context, could generate it in proto action | ||
| lb_->peekAnotherHost(nullptr); | ||
| } | ||
|
|
||
| void LoadBalancerFuzzBase::chooseHost() { | ||
| // TODO: context, could generate it in proto action | ||
| lb_->chooseHost(nullptr); | ||
| } | ||
|
|
||
| void LoadBalancerFuzzBase::replay( | ||
| const Protobuf::RepeatedPtrField<test::common::upstream::LbAction>& actions) { | ||
| constexpr auto max_actions = 64; | ||
| for (int i = 0; i < std::min(max_actions, actions.size()); ++i) { | ||
| const auto& event = actions.at(i); | ||
| ENVOY_LOG_MISC(trace, "Action: {}", event.DebugString()); | ||
| switch (event.action_selector_case()) { | ||
| case test::common::upstream::LbAction::kUpdateHealthFlags: { | ||
| updateHealthFlagsForAHostSet(event.update_health_flags().host_index(), | ||
| event.update_health_flags().num_healthy_hosts(), | ||
| event.update_health_flags().num_degraded_hosts(), | ||
| event.update_health_flags().num_excluded_hosts(), | ||
| event.update_health_flags().random_bytestring()); | ||
| break; | ||
| } | ||
| case test::common::upstream::LbAction::kPrefetch: | ||
| prefetch(); | ||
| break; | ||
| case test::common::upstream::LbAction::kChooseHost: | ||
| chooseHost(); | ||
| break; | ||
| default: | ||
| break; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| } // namespace Upstream | ||
| } // namespace Envoy | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| #include "envoy/config/cluster/v3/cluster.pb.h" | ||
|
|
||
| #include "common/upstream/load_balancer_impl.h" | ||
|
|
||
| #include "test/common/upstream/load_balancer_fuzz.pb.validate.h" | ||
| #include "test/fuzz/random.h" | ||
| #include "test/mocks/common.h" | ||
| #include "test/mocks/runtime/mocks.h" | ||
| #include "test/mocks/upstream/cluster_info.h" | ||
| #include "test/mocks/upstream/host_set.h" | ||
| #include "test/mocks/upstream/load_balancer_context.h" | ||
| #include "test/mocks/upstream/priority_set.h" | ||
|
|
||
| namespace Envoy { | ||
| namespace Upstream { | ||
|
|
||
| // This class implements replay logic, and also handles the initial setup of static host sets and | ||
| // the subsequent updates to those sets. | ||
| class LoadBalancerFuzzBase { | ||
| public: | ||
| LoadBalancerFuzzBase() : stats_(ClusterInfoImpl::generateStats(stats_store_)){}; | ||
|
|
||
| // Untrusted upstreams don't have the ability to change the host set size, so keep it constant | ||
| // over the fuzz iteration. | ||
| void initializeASingleHostSet(uint32_t num_hosts_in_host_set, uint8_t index_of_host_set); | ||
|
|
||
| // Initializes load balancer components shared amongst every load balancer, random_, and | ||
| // priority_set_ | ||
| void initializeLbComponents(const test::common::upstream::LoadBalancerTestCase& input); | ||
| void updateHealthFlagsForAHostSet(uint64_t host_index, uint32_t num_healthy_hosts, | ||
| uint32_t num_degraded_hosts, uint32_t num_excluded_hosts, | ||
| std::string random_bytestring); | ||
| // These two actions have a lot of logic attached to them. However, all the logic that the load | ||
| // balancer needs to run its algorithm is already encapsulated within the load balancer. Thus, | ||
| // once the load balancer is constructed, all this class has to do is call lb_->peekAnotherHost() | ||
| // and lb_->chooseHost(). | ||
| void prefetch(); | ||
| void chooseHost(); | ||
| ~LoadBalancerFuzzBase() = default; | ||
| void replay(const Protobuf::RepeatedPtrField<test::common::upstream::LbAction>& actions); | ||
|
|
||
| // These public objects shared amongst all types of load balancers will be used to construct load | ||
| // balancers in specific load balancer fuzz classes | ||
| Stats::IsolatedStoreImpl stats_store_; | ||
| ClusterStats stats_; | ||
| NiceMock<Runtime::MockLoader> runtime_; | ||
| Random::PsuedoRandomGenerator64 random_; | ||
| NiceMock<MockPrioritySet> priority_set_; | ||
| std::shared_ptr<MockClusterInfo> info_{new NiceMock<MockClusterInfo>()}; | ||
| std::unique_ptr<LoadBalancerBase> lb_; | ||
|
htuch marked this conversation as resolved.
|
||
|
|
||
| // There are used to construct the priority set at the beginning of the fuzz iteration | ||
| uint16_t port_ = 80; | ||
|
zasweq marked this conversation as resolved.
Outdated
|
||
| uint8_t num_host_sets_ = 0; | ||
| }; | ||
|
|
||
| } // namespace Upstream | ||
| } // namespace Envoy | ||
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.
Uh oh!
There was an error while loading. Please reload this page.