From 9d119d264f8c1e2f76f0d15d6d13f004c9c5d4e7 Mon Sep 17 00:00:00 2001
From: Roman Dzhabarov <rdzhabarov@lyft.com>
Date: Thu, 6 Oct 2016 11:16:14 -0700
Subject: [PATCH 1/7] Make simulation test for load balancing.

---
 test/CMakeLists.txt                           |   1 +
 .../upstream/load_balancer_simulation_test.cc | 129 ++++++++++++++++++
 2 files changed, 130 insertions(+)
 create mode 100644 test/common/upstream/load_balancer_simulation_test.cc

diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 1cab2bfd5ad22..a6d9a00b9a86e 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -84,6 +84,7 @@ add_executable(envoy-test
   common/upstream/health_checker_impl_test.cc
   common/upstream/host_utility_test.cc
   common/upstream/load_balancer_impl_test.cc
+  common/upstream/load_balancer_simulation_test.cc
   common/upstream/logical_dns_cluster_test.cc
   common/upstream/resource_manager_impl_test.cc
   common/upstream/sds_test.cc
diff --git a/test/common/upstream/load_balancer_simulation_test.cc b/test/common/upstream/load_balancer_simulation_test.cc
new file mode 100644
index 0000000000000..3f50e4ca74255
--- /dev/null
+++ b/test/common/upstream/load_balancer_simulation_test.cc
@@ -0,0 +1,129 @@
+#include "common/runtime/runtime_impl.h"
+#include "common/upstream/load_balancer_impl.h"
+#include "common/upstream/upstream_impl.h"
+
+#include "test/mocks/runtime/mocks.h"
+#include "test/mocks/upstream/mocks.h"
+
+using testing::NiceMock;
+using testing::Return;
+
+namespace Upstream {
+
+static HostPtr newTestHost(const Upstream::Cluster& cluster, const std::string& url,
+                           uint32_t weight = 1, const std::string& zone = "") {
+  return HostPtr{new HostImpl(cluster, url, false, weight, zone)};
+}
+
+/**
+ * This test is for simulation only and should not be run as part of unit tests.
+ */
+class DISABLED_SimulationTest : public testing::Test {
+public:
+  DISABLED_SimulationTest() : stats_(ClusterImplBase::generateStats("", stats_store_)) {}
+
+  /**
+   * @param originating_cluster total number of hosts in each zone in originating cluster.
+   * @param all_destination_cluster total number of all hosts in each zone in upstream cluster.
+   * @param healthy_destination_cluster total number of healthy hosts in each zone in upstream
+   * cluster.
+   */
+  void run(std::vector<uint32_t> originating_cluster, std::vector<uint32_t> all_destination_cluster,
+           std::vector<uint32_t> healthy_destination_cluster) {
+    setupRuntime();
+    stats_.upstream_zone_count_.set(all_destination_cluster.size());
+
+    // zone to list of healthy hosts
+    std::unordered_map<std::string, std::vector<HostPtr>> healthy_map =
+        generateMap(healthy_destination_cluster);
+    std::unordered_map<std::string, std::vector<HostPtr>> all_map =
+        generateMap(all_destination_cluster);
+
+    std::vector<HostPtr> originatingHosts = generateList(originating_cluster);
+    cluster_.healthy_hosts_ = generateList(healthy_destination_cluster);
+    cluster_.hosts_ = generateList(all_destination_cluster);
+
+    std::map<std::string, uint32_t> hits;
+    for (uint32_t i = 0; i < total_number_of_requests; ++i) {
+      HostPtr from_host = selectOriginatingHost(originatingHosts);
+
+      cluster_.local_zone_hosts_ = all_map[from_host->zone()];
+      cluster_.local_zone_healthy_hosts_ = healthy_map[from_host->zone()];
+
+      ConstHostPtr selected = lb_.chooseHost();
+      hits[selected->url()]++;
+    }
+
+    for (auto& host_hit_num_pair : hits) {
+      std::cout << fmt::format("url:{}, hits:{}", host_hit_num_pair.first, host_hit_num_pair.second)
+                << std::endl;
+    }
+  }
+
+  HostPtr selectOriginatingHost(const std::vector<HostPtr>& hosts) {
+    // Originating cluster should have roughly the same per host request distribution.
+    return hosts[random_.random() % hosts.size()];
+  }
+
+  std::vector<HostPtr> generateList(const std::vector<uint32_t>& hosts) {
+    std::vector<HostPtr> ret;
+    for (size_t i = 0; i < hosts.size(); ++i) {
+      const std::string zone = std::to_string(i);
+      for (uint32_t j = 0; j < hosts[i]; ++j) {
+        // url:zone:host_number
+        const std::string url = fmt::format("tcp://host.{}.{}:80", i, j);
+        ret.push_back(newTestHost(cluster_, url, 1, zone));
+      }
+    }
+
+    return ret;
+  }
+
+  std::unordered_map<std::string, std::vector<HostPtr>>
+  generateMap(const std::vector<uint32_t>& hosts) {
+    std::unordered_map<std::string, std::vector<HostPtr>> ret;
+    for (size_t i = 0; i < hosts.size(); ++i) {
+      const std::string zone = std::to_string(i);
+      std::vector<HostPtr> zone_hosts;
+
+      for (uint32_t j = 0; j < hosts[i]; ++j) {
+        // url:zone:host_number
+        const std::string url = fmt::format("tcp://host.{}.{}:80", i, j);
+        zone_hosts.push_back(newTestHost(cluster_, url, 1, zone));
+      }
+
+      ret.insert({zone, std::move(zone_hosts)});
+    }
+
+    return ret;
+  };
+
+  void setupRuntime() {
+    ON_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50U))
+        .WillByDefault(Return(50U));
+    ON_CALL(runtime_.snapshot_, featureEnabled("upstream.zone_routing.enabled", 100))
+        .WillByDefault(Return(true));
+    ON_CALL(runtime_.snapshot_, getInteger("upstream.zone_routing.percent_diff", 3))
+        .WillByDefault(Return(3));
+  }
+
+  const uint32_t total_number_of_requests = 100000;
+
+  NiceMock<MockCluster> cluster_;
+  NiceMock<Runtime::MockLoader> runtime_;
+  Runtime::RandomGeneratorImpl random_;
+  Stats::IsolatedStoreImpl stats_store_;
+  ClusterStats stats_;
+  // TODO: make per originating host load balancer.
+  RandomLoadBalancer lb_{cluster_, stats_, runtime_, random_};
+};
+
+TEST_F(DISABLED_SimulationTest, strictlyEqualDistribution) {
+  run({1U, 1U, 1U}, {3U, 3U, 3U}, {3U, 3U, 3U});
+}
+
+TEST_F(DISABLED_SimulationTest, unequalZoneDistribution) {
+  run({1U, 1U, 1U}, {5U, 5U, 6U}, {5U, 5U, 6U});
+}
+
+} // Upstream
\ No newline at end of file

From a369c32fab5aee5420d387584a2b3e48a6465a4d Mon Sep 17 00:00:00 2001
From: Roman Dzhabarov <rdzhabarov@lyft.com>
Date: Thu, 6 Oct 2016 11:30:34 -0700
Subject: [PATCH 2/7] comment changes.

---
 test/common/upstream/load_balancer_simulation_test.cc | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/test/common/upstream/load_balancer_simulation_test.cc b/test/common/upstream/load_balancer_simulation_test.cc
index 3f50e4ca74255..90524151e767e 100644
--- a/test/common/upstream/load_balancer_simulation_test.cc
+++ b/test/common/upstream/load_balancer_simulation_test.cc
@@ -24,7 +24,7 @@ class DISABLED_SimulationTest : public testing::Test {
 
   /**
    * @param originating_cluster total number of hosts in each zone in originating cluster.
-   * @param all_destination_cluster total number of all hosts in each zone in upstream cluster.
+   * @param all_destination_cluster total number of hosts in each zone in upstream cluster.
    * @param healthy_destination_cluster total number of healthy hosts in each zone in upstream
    * cluster.
    */
@@ -33,7 +33,6 @@ class DISABLED_SimulationTest : public testing::Test {
     setupRuntime();
     stats_.upstream_zone_count_.set(all_destination_cluster.size());
 
-    // zone to list of healthy hosts
     std::unordered_map<std::string, std::vector<HostPtr>> healthy_map =
         generateMap(healthy_destination_cluster);
     std::unordered_map<std::string, std::vector<HostPtr>> all_map =
@@ -54,7 +53,7 @@ class DISABLED_SimulationTest : public testing::Test {
       hits[selected->url()]++;
     }
 
-    for (auto& host_hit_num_pair : hits) {
+    for (const auto& host_hit_num_pair : hits) {
       std::cout << fmt::format("url:{}, hits:{}", host_hit_num_pair.first, host_hit_num_pair.second)
                 << std::endl;
     }
@@ -70,7 +69,6 @@ class DISABLED_SimulationTest : public testing::Test {
     for (size_t i = 0; i < hosts.size(); ++i) {
       const std::string zone = std::to_string(i);
       for (uint32_t j = 0; j < hosts[i]; ++j) {
-        // url:zone:host_number
         const std::string url = fmt::format("tcp://host.{}.{}:80", i, j);
         ret.push_back(newTestHost(cluster_, url, 1, zone));
       }

From 58c34186ce01b9badbf7628d5d330eb62766d782 Mon Sep 17 00:00:00 2001
From: Roman Dzhabarov <rdzhabarov@lyft.com>
Date: Thu, 6 Oct 2016 11:31:56 -0700
Subject: [PATCH 3/7] comment removed.

---
 test/common/upstream/load_balancer_simulation_test.cc | 1 -
 1 file changed, 1 deletion(-)

diff --git a/test/common/upstream/load_balancer_simulation_test.cc b/test/common/upstream/load_balancer_simulation_test.cc
index 90524151e767e..d6bbfe106df75 100644
--- a/test/common/upstream/load_balancer_simulation_test.cc
+++ b/test/common/upstream/load_balancer_simulation_test.cc
@@ -85,7 +85,6 @@ class DISABLED_SimulationTest : public testing::Test {
       std::vector<HostPtr> zone_hosts;
 
       for (uint32_t j = 0; j < hosts[i]; ++j) {
-        // url:zone:host_number
         const std::string url = fmt::format("tcp://host.{}.{}:80", i, j);
         zone_hosts.push_back(newTestHost(cluster_, url, 1, zone));
       }

From eb1469af45f6c43e047f7e142ef9acd65f007d1b Mon Sep 17 00:00:00 2001
From: Roman Dzhabarov <rdzhabarov@lyft.com>
Date: Thu, 6 Oct 2016 16:06:41 -0700
Subject: [PATCH 4/7] PR comments.

---
 .../upstream/load_balancer_simulation_test.cc | 29 ++++++++++++-------
 1 file changed, 19 insertions(+), 10 deletions(-)

diff --git a/test/common/upstream/load_balancer_simulation_test.cc b/test/common/upstream/load_balancer_simulation_test.cc
index d6bbfe106df75..f0de5b22c19ef 100644
--- a/test/common/upstream/load_balancer_simulation_test.cc
+++ b/test/common/upstream/load_balancer_simulation_test.cc
@@ -20,7 +20,9 @@ static HostPtr newTestHost(const Upstream::Cluster& cluster, const std::string&
  */
 class DISABLED_SimulationTest : public testing::Test {
 public:
-  DISABLED_SimulationTest() : stats_(ClusterImplBase::generateStats("", stats_store_)) {}
+  DISABLED_SimulationTest() : stats_(ClusterImplBase::generateStats("", stats_store_)) {
+    setupRuntime();
+  }
 
   /**
    * @param originating_cluster total number of hosts in each zone in originating cluster.
@@ -30,21 +32,20 @@ class DISABLED_SimulationTest : public testing::Test {
    */
   void run(std::vector<uint32_t> originating_cluster, std::vector<uint32_t> all_destination_cluster,
            std::vector<uint32_t> healthy_destination_cluster) {
-    setupRuntime();
     stats_.upstream_zone_count_.set(all_destination_cluster.size());
 
     std::unordered_map<std::string, std::vector<HostPtr>> healthy_map =
-        generateMap(healthy_destination_cluster);
+      generateHostsPerZone(healthy_destination_cluster);
     std::unordered_map<std::string, std::vector<HostPtr>> all_map =
-        generateMap(all_destination_cluster);
+      generateHostsPerZone(all_destination_cluster);
 
-    std::vector<HostPtr> originatingHosts = generateList(originating_cluster);
-    cluster_.healthy_hosts_ = generateList(healthy_destination_cluster);
-    cluster_.hosts_ = generateList(all_destination_cluster);
+    std::vector<HostPtr> originating_hosts = generateHostList(originating_cluster);
+    cluster_.healthy_hosts_ = generateHostList(healthy_destination_cluster);
+    cluster_.hosts_ = generateHostList(all_destination_cluster);
 
     std::map<std::string, uint32_t> hits;
     for (uint32_t i = 0; i < total_number_of_requests; ++i) {
-      HostPtr from_host = selectOriginatingHost(originatingHosts);
+      HostPtr from_host = selectOriginatingHost(originating_hosts);
 
       cluster_.local_zone_hosts_ = all_map[from_host->zone()];
       cluster_.local_zone_healthy_hosts_ = healthy_map[from_host->zone()];
@@ -64,7 +65,11 @@ class DISABLED_SimulationTest : public testing::Test {
     return hosts[random_.random() % hosts.size()];
   }
 
-  std::vector<HostPtr> generateList(const std::vector<uint32_t>& hosts) {
+  /**
+   * Generate list of hosts based on number of hosts in the given zone.
+   * @param hosts number of hosts per zone.
+   */
+  std::vector<HostPtr> generateHostList(const std::vector <uint32_t> &hosts) {
     std::vector<HostPtr> ret;
     for (size_t i = 0; i < hosts.size(); ++i) {
       const std::string zone = std::to_string(i);
@@ -77,8 +82,12 @@ class DISABLED_SimulationTest : public testing::Test {
     return ret;
   }
 
+  /**
+   * Generate hosts by zone.
+   * @param hosts number of hosts per zone.
+   */
   std::unordered_map<std::string, std::vector<HostPtr>>
-  generateMap(const std::vector<uint32_t>& hosts) {
+  generateHostsPerZone(const std::vector <uint32_t> &hosts) {
     std::unordered_map<std::string, std::vector<HostPtr>> ret;
     for (size_t i = 0; i < hosts.size(); ++i) {
       const std::string zone = std::to_string(i);

From d11aa7a0632e1ead68c5b66e25c704f59143850e Mon Sep 17 00:00:00 2001
From: Roman Dzhabarov <rdzhabarov@lyft.com>
Date: Thu, 6 Oct 2016 16:07:58 -0700
Subject: [PATCH 5/7] comment on run method.

---
 test/common/upstream/load_balancer_simulation_test.cc | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/test/common/upstream/load_balancer_simulation_test.cc b/test/common/upstream/load_balancer_simulation_test.cc
index f0de5b22c19ef..6ff7283771a25 100644
--- a/test/common/upstream/load_balancer_simulation_test.cc
+++ b/test/common/upstream/load_balancer_simulation_test.cc
@@ -25,6 +25,8 @@ class DISABLED_SimulationTest : public testing::Test {
   }
 
   /**
+   * Run simulation with given parameters. Generate statistics on per host requests.
+   *
    * @param originating_cluster total number of hosts in each zone in originating cluster.
    * @param all_destination_cluster total number of hosts in each zone in upstream cluster.
    * @param healthy_destination_cluster total number of healthy hosts in each zone in upstream

From da6d9a2681d0a7fcd9045244e0ae9806813f23ae Mon Sep 17 00:00:00 2001
From: Roman Dzhabarov <rdzhabarov@lyft.com>
Date: Thu, 6 Oct 2016 16:13:07 -0700
Subject: [PATCH 6/7] Fix.

---
 test/common/upstream/load_balancer_simulation_test.cc | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/test/common/upstream/load_balancer_simulation_test.cc b/test/common/upstream/load_balancer_simulation_test.cc
index 6ff7283771a25..b778cc8b41c4d 100644
--- a/test/common/upstream/load_balancer_simulation_test.cc
+++ b/test/common/upstream/load_balancer_simulation_test.cc
@@ -37,9 +37,9 @@ class DISABLED_SimulationTest : public testing::Test {
     stats_.upstream_zone_count_.set(all_destination_cluster.size());
 
     std::unordered_map<std::string, std::vector<HostPtr>> healthy_map =
-      generateHostsPerZone(healthy_destination_cluster);
+        generateHostsPerZone(healthy_destination_cluster);
     std::unordered_map<std::string, std::vector<HostPtr>> all_map =
-      generateHostsPerZone(all_destination_cluster);
+        generateHostsPerZone(all_destination_cluster);
 
     std::vector<HostPtr> originating_hosts = generateHostList(originating_cluster);
     cluster_.healthy_hosts_ = generateHostList(healthy_destination_cluster);
@@ -71,7 +71,7 @@ class DISABLED_SimulationTest : public testing::Test {
    * Generate list of hosts based on number of hosts in the given zone.
    * @param hosts number of hosts per zone.
    */
-  std::vector<HostPtr> generateHostList(const std::vector <uint32_t> &hosts) {
+  std::vector<HostPtr> generateHostList(const std::vector<uint32_t>& hosts) {
     std::vector<HostPtr> ret;
     for (size_t i = 0; i < hosts.size(); ++i) {
       const std::string zone = std::to_string(i);
@@ -89,7 +89,7 @@ class DISABLED_SimulationTest : public testing::Test {
    * @param hosts number of hosts per zone.
    */
   std::unordered_map<std::string, std::vector<HostPtr>>
-  generateHostsPerZone(const std::vector <uint32_t> &hosts) {
+  generateHostsPerZone(const std::vector<uint32_t>& hosts) {
     std::unordered_map<std::string, std::vector<HostPtr>> ret;
     for (size_t i = 0; i < hosts.size(); ++i) {
       const std::string zone = std::to_string(i);

From f9897a19ff260420116a055fc52d0023ad6c1d97 Mon Sep 17 00:00:00 2001
From: Roman Dzhabarov <rdzhabarov@lyft.com>
Date: Fri, 7 Oct 2016 09:17:19 -0700
Subject: [PATCH 7/7] Remove setupRuntime.

---
 .../upstream/load_balancer_simulation_test.cc    | 16 ++++++----------
 1 file changed, 6 insertions(+), 10 deletions(-)

diff --git a/test/common/upstream/load_balancer_simulation_test.cc b/test/common/upstream/load_balancer_simulation_test.cc
index b778cc8b41c4d..0648312a2ffde 100644
--- a/test/common/upstream/load_balancer_simulation_test.cc
+++ b/test/common/upstream/load_balancer_simulation_test.cc
@@ -21,7 +21,12 @@ static HostPtr newTestHost(const Upstream::Cluster& cluster, const std::string&
 class DISABLED_SimulationTest : public testing::Test {
 public:
   DISABLED_SimulationTest() : stats_(ClusterImplBase::generateStats("", stats_store_)) {
-    setupRuntime();
+    ON_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50U))
+        .WillByDefault(Return(50U));
+    ON_CALL(runtime_.snapshot_, featureEnabled("upstream.zone_routing.enabled", 100))
+        .WillByDefault(Return(true));
+    ON_CALL(runtime_.snapshot_, getInteger("upstream.zone_routing.percent_diff", 3))
+        .WillByDefault(Return(3));
   }
 
   /**
@@ -106,15 +111,6 @@ class DISABLED_SimulationTest : public testing::Test {
     return ret;
   };
 
-  void setupRuntime() {
-    ON_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50U))
-        .WillByDefault(Return(50U));
-    ON_CALL(runtime_.snapshot_, featureEnabled("upstream.zone_routing.enabled", 100))
-        .WillByDefault(Return(true));
-    ON_CALL(runtime_.snapshot_, getInteger("upstream.zone_routing.percent_diff", 3))
-        .WillByDefault(Return(3));
-  }
-
   const uint32_t total_number_of_requests = 100000;
 
   NiceMock<MockCluster> cluster_;
