Skip to content
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

[fuzz] Scaled Load balancer fuzz to 60k hosts #13771

Merged
merged 6 commits into from
Oct 30, 2020
Merged
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
18 changes: 11 additions & 7 deletions test/common/upstream/load_balancer_fuzz.proto
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@ import "google/protobuf/empty.proto";

message UpdateHealthFlags {
// The host priority determines what host set within the priority set which will get updated.
uint64 host_priority = 1;
uint32 host_priority = 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;
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}];
// TODO: What should this be capped at? There might be some efficiency/code coverage trade off
// dependent on the amount of digits this random_bytestring is allowed to scale too.
repeated uint32 random_bytestring = 5
[(validate.rules).repeated = {min_items: 1, max_items: 60000}];
}

message LbAction {
Expand All @@ -32,13 +35,14 @@ message LbAction {
}

message SetupPriorityLevel {
uint32 num_hosts_in_priority_level = 1 [(validate.rules).uint32.lte = 500];
uint32 num_hosts_locality_a = 2 [(validate.rules).uint32.lte = 500];
uint32 num_hosts_locality_b = 3 [(validate.rules).uint32.lte = 500];
uint32 num_hosts_in_priority_level = 1 [(validate.rules).uint32.lte = 60000];
uint32 num_hosts_locality_a = 2 [(validate.rules).uint32.lte = 60000];
uint32 num_hosts_locality_b = 3 [(validate.rules).uint32.lte = 60000];
// Hard cap at 3 localities for simplicity
uint32 num_hosts_locality_c = 4 [(validate.rules).uint32.lte = 500];
uint32 num_hosts_locality_c = 4 [(validate.rules).uint32.lte = 60000];
// For choosing which hosts go in which locality
bytes random_bytestring = 5 [(validate.rules).bytes = {min_len: 1, max_len: 256}];
repeated uint32 random_bytestring = 5
[(validate.rules).repeated = {min_items: 1, max_items: 60000}];
}

// This message represents what LoadBalancerFuzzBase will interact with, performing setup of host sets and calling into load balancers.
Expand Down
86 changes: 62 additions & 24 deletions test/common/upstream/load_balancer_fuzz_base.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,28 @@ namespace Envoy {
namespace Upstream {

namespace {
// TODO(zasweq): This will be relaxed in the future in order to fully represent the state space
// possible within Load Balancing. In it's current state, it is too slow (particularly due to calls
// to makeTestHost()) to scale up hosts. Once this is made more efficient, this number will be
// increased.
constexpr uint32_t MaxNumHostsPerPriorityLevel = 256;

constexpr uint32_t MaxNumHostsPerPriorityLevel = 60000;

// Helper function for converting repeated proto fields to byte vectors to pass into random subset
std::vector<uint32_t>
constructByteVectorForRandom(const Protobuf::RepeatedField<Protobuf::uint32>& random_bytestring) {
std::vector<uint32_t> random_bytestring_vector(random_bytestring.begin(),
random_bytestring.end());
return random_bytestring_vector;
}

} // namespace

HostVector
LoadBalancerFuzzBase::initializeHostsForUseInFuzzing(std::shared_ptr<MockClusterInfo> info) {
HostVector hosts;
for (uint32_t i = 1; i <= 60000; ++i) {
hosts.push_back(makeTestHost(info, "tcp://127.0.0.1:" + std::to_string(i)));
}
return hosts;
}

void LoadBalancerFuzzBase::initializeASingleHostSet(
const test::common::upstream::SetupPriorityLevel& setup_priority_level,
const uint8_t priority_level, uint16_t& port) {
Expand All @@ -22,17 +36,19 @@ void LoadBalancerFuzzBase::initializeASingleHostSet(
priority_level, num_hosts_in_priority_level);
MockHostSet& host_set = *priority_set_.getMockHostSet(priority_level);
uint32_t hosts_made = 0;
// Cap each host set at 256 hosts for efficiency - Leave port clause in for future changes
// Cap each host set at 60000 hosts - however, let port number enforce a max of 60k hosts across
// all priority levels.
while (hosts_made < std::min(num_hosts_in_priority_level, MaxNumHostsPerPriorityLevel) &&
port < 65535) {
host_set.hosts_.push_back(makeTestHost(info_, "tcp://127.0.0.1:" + std::to_string(port)));
port < 60000) {
zasweq marked this conversation as resolved.
Show resolved Hide resolved
host_set.hosts_.push_back(initialized_hosts_[port]);
++port;
++hosts_made;
}

Fuzz::ProperSubsetSelector subset_selector(setup_priority_level.random_bytestring());
Fuzz::ProperSubsetSelector subset_selector(
constructByteVectorForRandom(setup_priority_level.random_bytestring()));

const std::vector<std::vector<uint8_t>> localities = subset_selector.constructSubsets(
const std::vector<std::vector<uint32_t>> localities = subset_selector.constructSubsets(
{setup_priority_level.num_hosts_locality_a(), setup_priority_level.num_hosts_locality_b(),
setup_priority_level.num_hosts_locality_c()},
host_set.hosts_.size());
Expand All @@ -44,7 +60,7 @@ void LoadBalancerFuzzBase::initializeASingleHostSet(
std::array<HostVector, 3> locality_indexes = {locality_a, locality_b, locality_c};

for (uint8_t locality = 0; locality < locality_indexes.size(); locality++) {
for (uint8_t index : localities[locality]) {
for (uint32_t index : localities[locality]) {
locality_indexes[locality].push_back(host_set.hosts_[index]);
locality_indexes_[index] = locality;
}
Expand All @@ -58,8 +74,16 @@ void LoadBalancerFuzzBase::initializeASingleHostSet(
// Initializes random and fixed host sets
void LoadBalancerFuzzBase::initializeLbComponents(
const test::common::upstream::LoadBalancerTestCase& input) {
static NiceMock<MockClusterInfo> info;
static std::shared_ptr<MockClusterInfo> info_pointer{std::shared_ptr<MockClusterInfo>{}, &info};

// Will statically initialize 60000 hosts in this vector, so each fuzz run doesn't construct new
// hosts to use. This will require clearing of state after each run.
static HostVector initialized_hosts = initializeHostsForUseInFuzzing(info_pointer);
initialized_hosts_ = initialized_hosts;

random_.initializeSeed(input.seed_for_prng());
uint16_t port = 80;
uint16_t port = 1;
for (uint8_t priority_of_host_set = 0;
priority_of_host_set < input.setup_priority_levels().size(); ++priority_of_host_set) {
initializeASingleHostSet(input.setup_priority_levels().at(priority_of_host_set),
Expand All @@ -71,11 +95,10 @@ void LoadBalancerFuzzBase::initializeLbComponents(
// 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(const uint64_t host_priority,
const uint32_t num_healthy_hosts,
const uint32_t num_degraded_hosts,
const uint32_t num_excluded_hosts,
const std::string random_bytestring) {
void LoadBalancerFuzzBase::updateHealthFlagsForAHostSet(
const uint64_t host_priority, const uint32_t num_healthy_hosts,
const uint32_t num_degraded_hosts, const uint32_t num_excluded_hosts,
const Protobuf::RepeatedField<Protobuf::uint32>& random_bytestring) {
const uint8_t priority_of_host_set = host_priority % num_priority_levels_;
ENVOY_LOG_MISC(trace, "Updating health flags for host set at priority: {}", priority_of_host_set);
MockHostSet& host_set = *priority_set_.getMockHostSet(priority_of_host_set);
Expand All @@ -99,21 +122,21 @@ void LoadBalancerFuzzBase::updateHealthFlagsForAHostSet(const uint64_t host_prio
EXCLUDED = 2,
};

Fuzz::ProperSubsetSelector subset_selector(random_bytestring);
Fuzz::ProperSubsetSelector subset_selector(constructByteVectorForRandom(random_bytestring));

const std::vector<std::vector<uint8_t>> subsets = subset_selector.constructSubsets(
const std::vector<std::vector<uint32_t>> subsets = subset_selector.constructSubsets(
{num_healthy_hosts, num_degraded_hosts, num_excluded_hosts}, host_set_size);

// Healthy hosts are first subset
for (uint8_t index : subsets.at(HealthStatus::HEALTHY)) {
for (uint32_t index : subsets.at(HealthStatus::HEALTHY)) {
host_set.healthy_hosts_.push_back(host_set.hosts_[index]);
// No health flags for healthy
}
ENVOY_LOG_MISC(trace, "Hosts made healthy at priority level {}: {}", priority_of_host_set,
absl::StrJoin(subsets.at(HealthStatus::HEALTHY), " "));

// Degraded hosts are second subset
for (uint8_t index : subsets.at(HealthStatus::DEGRADED)) {
for (uint32_t index : subsets.at(HealthStatus::DEGRADED)) {
host_set.degraded_hosts_.push_back(host_set.hosts_[index]);
// Health flags are not currently directly used by most load balancers, but
// they may be added and also are used by other components.
Expand All @@ -125,7 +148,7 @@ void LoadBalancerFuzzBase::updateHealthFlagsForAHostSet(const uint64_t host_prio
absl::StrJoin(subsets.at(HealthStatus::DEGRADED), " "));

// Excluded hosts are third subset
for (uint8_t index : subsets.at(HealthStatus::EXCLUDED)) {
for (uint32_t index : subsets.at(HealthStatus::EXCLUDED)) {
host_set.excluded_hosts_.push_back(host_set.hosts_[index]);
// Health flags are not currently directly used by most load balancers, but
// they may be added and also are used by other components.
Expand Down Expand Up @@ -156,8 +179,8 @@ void LoadBalancerFuzzBase::updateHealthFlagsForAHostSet(const uint64_t host_prio
// Iterate through subsets
for (uint8_t health_status = 0; health_status < locality_health_statuses.size();
health_status++) {
for (uint8_t index : subsets.at(health_status)) { // Each subset logically represents a health
// status
for (uint32_t index : subsets.at(health_status)) { // Each subset logically represents a health
zasweq marked this conversation as resolved.
Show resolved Hide resolved
// status
// If the host is in a locality, we have to update the corresponding health status host vector
if (!(locality_indexes_.find(index) == locality_indexes_.end())) {
// After computing the host index subsets, we want to propagate these changes to a host set
Expand Down Expand Up @@ -225,5 +248,20 @@ void LoadBalancerFuzzBase::replay(
}
}

void LoadBalancerFuzzBase::clearStaticHostsHealthFlags() {
// The only outstanding health flags set are those that are set from hosts being placed in
// degraded and excluded. Thus, use the priority set pointer to know which flags to clear.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you also clear unconditionally?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there is no unconditional flag clear, maybe a clearFlagForTest method or an ASSERT that the state matches expected?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that my comment of "how do we know what hosts to clear?" is outdated. Since the only health flags that get set are those that are degraded and excluded, which will naturally be in that HostVector regardless? Thus making this work?

for (uint32_t priority_level = 0; priority_level < priority_set_.hostSetsPerPriority().size();
++priority_level) {
MockHostSet& host_set = *priority_set_.getMockHostSet(priority_level);
for (auto& host : host_set.degraded_hosts_) {
host->healthFlagClear(Host::HealthFlag::DEGRADED_ACTIVE_HC);
}
for (auto& host : host_set.excluded_hosts_) {
host->healthFlagClear(Host::HealthFlag::FAILED_ACTIVE_HC);
}
}
}

} // namespace Upstream
} // namespace Envoy
17 changes: 12 additions & 5 deletions test/common/upstream/load_balancer_fuzz_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ class LoadBalancerFuzzBase {
// Initializes load balancer components shared amongst every load balancer, random_, and
// priority_set_
void initializeLbComponents(const test::common::upstream::LoadBalancerTestCase& input);
void updateHealthFlagsForAHostSet(const uint64_t host_priority, const uint32_t num_healthy_hosts,
const uint32_t num_degraded_hosts,
const uint32_t num_excluded_hosts,
const std::string random_bytestring);
void
updateHealthFlagsForAHostSet(const uint64_t host_priority, const uint32_t num_healthy_hosts,
const uint32_t num_degraded_hosts, const uint32_t num_excluded_hosts,
const Protobuf::RepeatedField<Protobuf::uint32>& 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()
Expand All @@ -36,14 +36,15 @@ class LoadBalancerFuzzBase {
~LoadBalancerFuzzBase() = default;
void replay(const Protobuf::RepeatedPtrField<test::common::upstream::LbAction>& actions);

void clearStaticHostsHealthFlags();

// 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_;

private:
Expand All @@ -61,6 +62,12 @@ class LoadBalancerFuzzBase {
// localities Key - index of host within full host list, value - locality level host at index is
// in
absl::node_hash_map<uint8_t, uint8_t> locality_indexes_;

static HostVector initializeHostsForUseInFuzzing(std::shared_ptr<MockClusterInfo> info);

// Will statically initialize 60000 hosts in this vector. Will have to clear these static
// hosts flags at the end of each fuzz iteration
HostVector initialized_hosts_;
};

} // namespace Upstream
Expand Down

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

10 changes: 8 additions & 2 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.

15 changes: 12 additions & 3 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.

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

Loading