| /** @file |
| |
| Unit tests for the NextHopRoundRobin. |
| |
| @section license License |
| |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
| @section details Details |
| |
| Unit testing the NextHopRoundRobin class. |
| |
| */ |
| |
| #define CATCH_CONFIG_MAIN /* include main function */ |
| |
| #include <catch.hpp> /* catch unit-test framework */ |
| #include <yaml-cpp/yaml.h> |
| |
| #include "HttpSM.h" |
| #include "nexthop_test_stubs.h" |
| #include "NextHopSelectionStrategy.h" |
| #include "NextHopStrategyFactory.h" |
| #include "NextHopRoundRobin.h" |
| |
| SCENARIO("Testing NextHopRoundRobin class, using policy 'rr-strict'", "[NextHopRoundRobin]") |
| { |
| // We need this to build a HdrHeap object in build_request(); |
| // No thread setup, forbid use of thread local allocators. |
| cmd_disable_pfreelist = true; |
| // Get all of the HTTP WKS items populated. |
| http_init(); |
| |
| GIVEN("Loading the round-robin-tests.yaml config for round robin 'rr-strict' tests.") |
| { |
| std::shared_ptr<NextHopSelectionStrategy> strategy; |
| NextHopStrategyFactory nhf(TS_SRC_DIR "unit-tests/round-robin-tests.yaml"); |
| strategy = nhf.strategyInstance("rr-strict-exhaust-ring"); |
| |
| WHEN("the config is loaded.") |
| { |
| THEN("the rr-strict strategy is ready for use.") |
| { |
| REQUIRE(nhf.strategies_loaded == true); |
| REQUIRE(strategy != nullptr); |
| REQUIRE(strategy->policy_type == NH_RR_STRICT); |
| } |
| } |
| |
| WHEN("making requests using a 'rr-strict' policy.") |
| { |
| HttpSM sm; |
| ParentResult *result = &sm.t_state.parent_result; |
| TSHttpTxn txnp = reinterpret_cast<TSHttpTxn>(&sm); |
| |
| THEN("then testing rr-strict.") |
| { |
| REQUIRE(nhf.strategies_loaded == true); |
| REQUIRE(strategy != nullptr); |
| |
| // first request. |
| build_request(10001, &sm, nullptr, "rabbit.net", nullptr); |
| strategy->findNextHop(txnp); |
| CHECK(strcmp(result->hostname, "p1.foo.com") == 0); |
| |
| // second request. |
| build_request(10002, &sm, nullptr, "rabbit.net", nullptr); |
| result->reset(); |
| strategy->findNextHop(txnp); |
| CHECK(strcmp(result->hostname, "p2.foo.com") == 0); |
| |
| // third request. |
| build_request(10003, &sm, nullptr, "rabbit.net", nullptr); |
| result->reset(); |
| strategy->findNextHop(txnp); |
| CHECK(strcmp(result->hostname, "p1.foo.com") == 0); |
| |
| // did not reset result, kept it as last parent selected was p1.fo.com, mark it down and we should only select p2.foo.com |
| strategy->markNextHop(txnp, result->hostname, result->port, NH_MARK_DOWN); |
| |
| // fourth request, p1 is down should select p2. |
| build_request(10004, &sm, nullptr, "rabbit.net", nullptr); |
| result->reset(); |
| strategy->findNextHop(txnp); |
| CHECK(strcmp(result->hostname, "p2.foo.com") == 0); |
| |
| // fifth request, p1 is down should still select p2. |
| build_request(10005, &sm, nullptr, "rabbit.net", nullptr); |
| result->reset(); |
| strategy->findNextHop(txnp); |
| CHECK(strcmp(result->hostname, "p2.foo.com") == 0); |
| |
| // mark down p2. |
| strategy->markNextHop(txnp, result->hostname, result->port, NH_MARK_DOWN); |
| |
| // fifth request, p1 and p2 are both down, should get s1.bar.com from failover ring. |
| build_request(10006, &sm, nullptr, "rabbit.net", nullptr); |
| result->reset(); |
| strategy->findNextHop(txnp); |
| CHECK(strcmp(result->hostname, "s1.bar.com") == 0); |
| |
| // sixth request, p1 and p2 are still down, should get s1.bar.com from failover ring. |
| build_request(10007, &sm, nullptr, "rabbit.net", nullptr); |
| result->reset(); |
| strategy->findNextHop(txnp); |
| CHECK(strcmp(result->hostname, "s1.bar.com") == 0); |
| |
| // mark down s1. |
| strategy->markNextHop(txnp, result->hostname, result->port, NH_MARK_DOWN); |
| |
| // seventh request, p1, p2, s1 are down, should get s2.bar.com from failover ring. |
| build_request(10008, &sm, nullptr, "rabbit.net", nullptr); |
| result->reset(); |
| strategy->findNextHop(txnp); |
| CHECK(strcmp(result->hostname, "s2.bar.com") == 0); |
| |
| // mark down s2. |
| strategy->markNextHop(txnp, result->hostname, result->port, NH_MARK_DOWN); |
| |
| // eighth request, p1, p2, s1, s2 are down, should get PARENT_DIRECT as go_direct is true |
| build_request(10009, &sm, nullptr, "rabbit.net", nullptr); |
| result->reset(); |
| strategy->findNextHop(txnp); |
| CHECK(result->result == ParentResultType::PARENT_DIRECT); |
| |
| // check that nextHopExists() returns false when all parents are down. |
| CHECK(strategy->nextHopExists(txnp) == false); |
| |
| // change the request time to trigger a retry. |
| time_t now = (time(nullptr) + 5); |
| |
| // ninth request, p1 and p2 are still down, should get p2.foo.com as it will be retried |
| build_request(10010, &sm, nullptr, "rabbit.net", nullptr); |
| result->reset(); |
| strategy->findNextHop(txnp, nullptr, now); |
| REQUIRE(result->result == ParentResultType::PARENT_SPECIFIED); |
| CHECK(strcmp(result->hostname, "p2.foo.com") == 0); |
| |
| // tenth request, p1 should now be retried. |
| build_request(10011, &sm, nullptr, "rabbit.net", nullptr); |
| result->reset(); |
| strategy->findNextHop(txnp, nullptr, now); |
| REQUIRE(result->result == ParentResultType::PARENT_SPECIFIED); |
| CHECK(strcmp(result->hostname, "p1.foo.com") == 0); |
| } |
| br_destroy(sm); |
| } |
| } |
| } |
| |
| SCENARIO("Testing NextHopRoundRobin class, using policy 'first-live'", "[NextHopRoundRobin]") |
| { |
| // We need this to build a HdrHeap object in build_request(); |
| // No thread setup, forbid use of thread local allocators. |
| cmd_disable_pfreelist = true; |
| // Get all of the HTTP WKS items populated. |
| http_init(); |
| |
| GIVEN("Loading the round-robin-tests.yaml config for round robin 'first-live' tests.") |
| { |
| std::shared_ptr<NextHopSelectionStrategy> strategy; |
| NextHopStrategyFactory nhf(TS_SRC_DIR "unit-tests/round-robin-tests.yaml"); |
| strategy = nhf.strategyInstance("first-live"); |
| |
| WHEN("the config is loaded.") |
| { |
| THEN("the 'first-live' strategy is available.") |
| { |
| REQUIRE(nhf.strategies_loaded == true); |
| REQUIRE(strategy != nullptr); |
| REQUIRE(strategy->policy_type == NH_FIRST_LIVE); |
| } |
| } |
| |
| WHEN("when using a strategy with a 'first-live' policy.") |
| { |
| HttpSM sm; |
| ParentResult *result = &sm.t_state.parent_result; |
| TSHttpTxn txnp = reinterpret_cast<TSHttpTxn>(&sm); |
| |
| THEN("when making requests and marking down hosts.") |
| { |
| REQUIRE(nhf.strategies_loaded == true); |
| REQUIRE(strategy != nullptr); |
| |
| // first request. |
| build_request(10012, &sm, nullptr, "rabbit.net", nullptr); |
| strategy->findNextHop(txnp); |
| CHECK(strcmp(result->hostname, "p1.foo.com") == 0); |
| |
| // second request. |
| build_request(10013, &sm, nullptr, "rabbit.net", nullptr); |
| result->reset(); |
| strategy->findNextHop(txnp); |
| CHECK(strcmp(result->hostname, "p1.foo.com") == 0); |
| |
| // mark down p1. |
| strategy->markNextHop(txnp, result->hostname, result->port, NH_MARK_DOWN); |
| |
| // third request. |
| build_request(10014, &sm, nullptr, "rabbit.net", nullptr); |
| result->reset(); |
| strategy->findNextHop(txnp); |
| CHECK(strcmp(result->hostname, "p2.foo.com") == 0); |
| |
| // change the request time to trigger a retry. |
| time_t now = (time(nullptr) + 5); |
| |
| // fourth request, p1 should be marked for retry |
| build_request(10015, &sm, nullptr, "rabbit.net", nullptr); |
| result->reset(); |
| strategy->findNextHop(txnp, nullptr, now); |
| CHECK(strcmp(result->hostname, "p1.foo.com") == 0); |
| } |
| br_destroy(sm); |
| } |
| } |
| } |
| |
| SCENARIO("Testing NextHopRoundRobin class, using policy 'rr-ip'", "[NextHopRoundRobin]") |
| { |
| // We need this to build a HdrHeap object in build_request(); |
| // No thread setup, forbid use of thread local allocators. |
| cmd_disable_pfreelist = true; |
| // Get all of the HTTP WKS items populated. |
| http_init(); |
| |
| GIVEN("Loading the round-robin-tests.yaml config for round robin 'rr-ip' tests.") |
| { |
| std::shared_ptr<NextHopSelectionStrategy> strategy; |
| NextHopStrategyFactory nhf(TS_SRC_DIR "unit-tests/round-robin-tests.yaml"); |
| strategy = nhf.strategyInstance("rr-ip"); |
| sockaddr_in sa1, sa2; |
| sa1.sin_port = 10000; |
| sa1.sin_family = AF_INET; |
| inet_pton(AF_INET, "192.168.1.1", &(sa1.sin_addr)); |
| sa2.sin_port = 10001; |
| sa2.sin_family = AF_INET; |
| inet_pton(AF_INET, "192.168.1.2", &(sa2.sin_addr)); |
| HttpSM sm; |
| ParentResult *result = &sm.t_state.parent_result; |
| TSHttpTxn txnp = reinterpret_cast<TSHttpTxn>(&sm); |
| |
| WHEN("the config is loaded.") |
| { |
| THEN("then the 'rr-strict' strategy is ready.") |
| { |
| REQUIRE(nhf.strategies_loaded == true); |
| REQUIRE(strategy != nullptr); |
| REQUIRE(strategy->policy_type == NH_RR_IP); |
| } |
| } |
| |
| WHEN("using the 'rr-strict' strategy.") |
| { |
| THEN("when making requests and marking down hosts.") |
| { |
| REQUIRE(nhf.strategies_loaded == true); |
| REQUIRE(strategy != nullptr); |
| |
| // call and test parentExists(), this call should not affect |
| // findNextHop() round robin strict results |
| CHECK(strategy->nextHopExists(txnp) == true); |
| |
| // first request. |
| // memcpy(&rdata.client_ip, &sa1, sizeof(sa1)); |
| build_request(10016, &sm, &sa1, "rabbit.net", nullptr); |
| strategy->findNextHop(txnp); |
| CHECK(strcmp(result->hostname, "p4.foo.com") == 0); |
| |
| // call and test parentExists(), this call should not affect |
| // findNextHop round robin strict results. |
| CHECK(strategy->nextHopExists(txnp) == true); |
| |
| // second request. |
| // memcpy(&rdata.client_ip, &sa2, sizeof(sa2)); |
| build_request(10017, &sm, &sa2, "rabbit.net", nullptr); |
| result->reset(); |
| strategy->findNextHop(txnp); |
| CHECK(strcmp(result->hostname, "p3.foo.com") == 0); |
| |
| // call and test parentExists(), this call should not affect |
| // findNextHop() round robin strict results |
| CHECK(strategy->nextHopExists(txnp) == true); |
| |
| // third request with same client ip, result should still be p3 |
| build_request(10018, &sm, &sa2, "rabbit.net", nullptr); |
| result->reset(); |
| strategy->findNextHop(txnp); |
| CHECK(strcmp(result->hostname, "p3.foo.com") == 0); |
| |
| // call and test parentExists(), this call should not affect |
| // findNextHop() round robin strict results. |
| CHECK(strategy->nextHopExists(txnp) == true); |
| |
| // fourth request with same client ip and same result indicating a failure should result in p4 |
| // being selected. |
| build_request(10019, &sm, &sa2, "rabbit.net", nullptr); |
| strategy->findNextHop(txnp); |
| CHECK(strcmp(result->hostname, "p4.foo.com") == 0); |
| } |
| } |
| br_destroy(sm); |
| } |
| } |
| |
| SCENARIO("Testing NextHopRoundRobin class, using policy 'latched'", "[NextHopRoundRobin]") |
| { |
| // We need this to build a HdrHeap object in build_request(); |
| // No thread setup, forbid use of thread local allocators. |
| cmd_disable_pfreelist = true; |
| // Get all of the HTTP WKS items populated. |
| http_init(); |
| |
| GIVEN("Loading the round-robin-tests.yaml config for round robin 'latched' tests.") |
| { |
| std::shared_ptr<NextHopSelectionStrategy> strategy; |
| NextHopStrategyFactory nhf(TS_SRC_DIR "unit-tests/round-robin-tests.yaml"); |
| strategy = nhf.strategyInstance("latched"); |
| |
| WHEN("the config is loaded.") |
| { |
| THEN("then the 'latched' strategy is available.") |
| { |
| REQUIRE(nhf.strategies_loaded == true); |
| REQUIRE(strategy != nullptr); |
| REQUIRE(strategy->policy_type == NH_RR_LATCHED); |
| } |
| } |
| |
| WHEN("using a strategy having a 'latched' policy.") |
| { |
| HttpSM sm; |
| ParentResult *result = &sm.t_state.parent_result; |
| TSHttpTxn txnp = reinterpret_cast<TSHttpTxn>(&sm); |
| |
| THEN("when making requests and marking down hosts.") |
| { |
| REQUIRE(nhf.strategies_loaded == true); |
| REQUIRE(strategy != nullptr); |
| |
| // first request should select p3 |
| build_request(10020, &sm, nullptr, "rabbit.net", nullptr); |
| strategy->findNextHop(txnp); |
| CHECK(strcmp(result->hostname, "p3.foo.com") == 0); |
| |
| // second request should select p3 |
| build_request(10021, &sm, nullptr, "rabbit.net", nullptr); |
| result->reset(); |
| strategy->findNextHop(txnp); |
| CHECK(strcmp(result->hostname, "p3.foo.com") == 0); |
| |
| // third request, use previous result to simulate a failure, we should now select p4. |
| build_request(10022, &sm, nullptr, "rabbit.net", nullptr); |
| strategy->findNextHop(txnp); |
| CHECK(strcmp(result->hostname, "p4.foo.com") == 0); |
| |
| // fourth request we should be latched on p4 |
| build_request(10023, &sm, nullptr, "rabbit.net", nullptr); |
| result->reset(); |
| strategy->findNextHop(txnp); |
| CHECK(strcmp(result->hostname, "p4.foo.com") == 0); |
| |
| // fifth request, use previous result to simulate a failure, we should now select p3. |
| build_request(10024, &sm, nullptr, "rabbit.net", nullptr); |
| strategy->findNextHop(txnp); |
| CHECK(strcmp(result->hostname, "p3.foo.com") == 0); |
| } |
| br_destroy(sm); |
| } |
| } |
| } |