Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions test/common/upstream/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,54 @@ envoy_cc_test(
],
)

envoy_cc_test_library(
name = "load_balancer_fuzz_lib",
srcs = ["load_balancer_fuzz_base.cc"],
hdrs = ["load_balancer_fuzz_base.h"],
deps = [
":load_balancer_fuzz_proto_cc_proto",
":utility_lib",
"//source/common/upstream:load_balancer_lib",
"//test/mocks:common_lib",
"//test/mocks/runtime:runtime_mocks",
"//test/mocks/upstream:cluster_info_mocks",
"//test/mocks/upstream:host_set_mocks",
"//test/mocks/upstream:load_balancer_context_mock",
"//test/mocks/upstream:priority_set_mocks",
"@envoy_api//envoy/config/cluster/v3:pkg_cc_proto",
],
)

envoy_proto_library(
name = "load_balancer_fuzz_proto",
srcs = ["load_balancer_fuzz.proto"],
deps = [
"//test/fuzz:common_proto",
"@envoy_api//envoy/config/cluster/v3:pkg",
],
)

envoy_proto_library(
name = "round_robin_load_balancer_fuzz_proto",
srcs = ["round_robin_load_balancer_fuzz.proto"],
deps = [
"//test/common/upstream:load_balancer_fuzz_proto",
],
)

envoy_cc_fuzz_test(
name = "round_robin_load_balancer_fuzz_test",
srcs = ["round_robin_load_balancer_fuzz_test.cc"],
corpus = "//test/common/upstream:round_robin_load_balancer_corpus",
deps = [
":load_balancer_fuzz_lib",
":load_balancer_fuzz_proto_cc_proto",
":round_robin_load_balancer_fuzz_proto_cc_proto",
":utility_lib",
"//test/fuzz:utility_lib",
],
)

envoy_cc_test(
name = "load_balancer_simulation_test",
srcs = ["load_balancer_simulation_test.cc"],
Expand Down
37 changes: 37 additions & 0 deletions test/common/upstream/load_balancer_fuzz.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
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;
uint32 num_healthy_hosts = 2;
uint32 num_degraded_hosts = 3;
uint32 num_excluded_hosts = 4;
}

message LbAction {
oneof action_selector {
option (validate.required) = true;
UpdateHealthFlags update_health_flags = 1;
google.protobuf.Empty prefetch = 2;
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 modularly used 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;
//This is capped at the max port value on an ip address - 80
uint32 num_hosts_in_priority_set = 3 [(validate.rules).uint32.lt = 65455];
uint32 num_hosts_in_failover_set = 4 [(validate.rules).uint32.lt = 65455];
repeated uint64 bytestring_for_random_calls = 5
[(validate.rules).repeated = {min_items: 1, max_items: 50}];
}
115 changes: 115 additions & 0 deletions test/common/upstream/load_balancer_fuzz_base.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#include "test/common/upstream/load_balancer_fuzz_base.h"

#include "test/common/upstream/utility.h"

namespace Envoy {

namespace Random {
uint64_t FakeRandomGenerator::random() {
uint8_t index_of_data = counter % bytestring_.size();
++counter;
ENVOY_LOG_MISC(trace, "random() returned: {}", bytestring_.at(index_of_data));
return bytestring_.at(index_of_data);
}
} // namespace Random

namespace Upstream {

// Anonymous namespace for helper functions
namespace {
std::vector<uint64_t>
constructByteVectorForRandom(const Protobuf::RepeatedField<uint64_t>& byteString) {
std::vector<uint64_t> byteVector;
for (int i = 0; i < byteString.size(); ++i) {
byteVector.push_back(byteString.at(i));
}
return byteVector;
}
} // namespace

void LoadBalancerFuzzBase::initializeFixedHostSets(uint32_t num_hosts_in_priority_set,
uint32_t num_hosts_in_failover_set) {
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?
}

// Initializes random and fixed host sets
void LoadBalancerFuzzBase::initializeLbComponents(
test::common::upstream::LoadBalancerTestCase input) {
random_.bytestring_ = constructByteVectorForRandom(input.bytestring_for_random_calls());
initializeFixedHostSets(input.num_hosts_in_priority_set(), input.num_hosts_in_failover_set());
}

// 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(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::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().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;
}
}
}

} // namespace Upstream
} // namespace Envoy
74 changes: 74 additions & 0 deletions test/common/upstream/load_balancer_fuzz_base.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#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/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 Random {
class FakeRandomGenerator : public RandomGenerator {
public:
FakeRandomGenerator() = default;
~FakeRandomGenerator() override = default;

// RandomGenerator
uint64_t random() override; // returns a part of the generated bytestring
std::string uuid() override { return ""; }
uint32_t counter = 0; // Will get modded against byte string length for byte string index
std::vector<uint64_t> bytestring_; // String of bytes that returns on random() calls
};
} // namespace Random

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 initializeFixedHostSets(uint32_t num_hosts_in_priority_set,
uint32_t num_hosts_in_failover_set);

// Initializes load balancer components shared amongst every load balancer, random_, and
// priority_set_
void initializeLbComponents(test::common::upstream::LoadBalancerTestCase input);
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. 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::FakeRandomGenerator random_;
NiceMock<MockPrioritySet> priority_set_;
MockHostSet& host_set_ = *priority_set_.getMockHostSet(0);
MockHostSet& failover_host_set_ = *priority_set_.getMockHostSet(1);
std::shared_ptr<MockClusterInfo> info_{new NiceMock<MockClusterInfo>()};
std::unique_ptr<LoadBalancerBase> lb_;
};

} // namespace Upstream
} // namespace Envoy
18 changes: 18 additions & 0 deletions test/common/upstream/random_load_balancer_corpus/random_NoHosts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 35 additions & 0 deletions test/common/upstream/random_load_balancer_corpus/random_Normal

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.

Loading