|
@@ -0,0 +1,598 @@
|
|
|
+/**
|
|
|
+ * 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.
|
|
|
+ */
|
|
|
+
|
|
|
+#include <cppunit/extensions/HelperMacros.h>
|
|
|
+#include <sys/types.h>
|
|
|
+#include <netinet/in.h>
|
|
|
+#include <errno.h>
|
|
|
+#include <iostream>
|
|
|
+#include <sstream>
|
|
|
+#include <arpa/inet.h>
|
|
|
+#include <exception>
|
|
|
+#include <stdlib.h>
|
|
|
+
|
|
|
+#include "Util.h"
|
|
|
+#include "LibCMocks.h"
|
|
|
+#include "ZKMocks.h"
|
|
|
+
|
|
|
+using namespace std;
|
|
|
+
|
|
|
+static const int portOffset = 2000;
|
|
|
+
|
|
|
+class Client
|
|
|
+{
|
|
|
+
|
|
|
+private:
|
|
|
+ // Member variables
|
|
|
+ zhandle_t *zh;
|
|
|
+ unsigned int seed;
|
|
|
+
|
|
|
+public:
|
|
|
+ /**
|
|
|
+ * Create a client with given connection host string and add to our internal
|
|
|
+ * vector of clients. These are disconnected and cleaned up in tearDown().
|
|
|
+ */
|
|
|
+ Client(const string hosts, unsigned int seed) :
|
|
|
+ seed((seed * seed) + 0xAFAFAFAF)
|
|
|
+ {
|
|
|
+ reSeed();
|
|
|
+
|
|
|
+ zh = zookeeper_init(hosts.c_str(),0,1000,0,0,0);
|
|
|
+ CPPUNIT_ASSERT(zh);
|
|
|
+
|
|
|
+ reSeed();
|
|
|
+
|
|
|
+ cycleNextServer();
|
|
|
+ }
|
|
|
+
|
|
|
+ void close()
|
|
|
+ {
|
|
|
+ zookeeper_close(zh);
|
|
|
+ zh = NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ bool isReconfig()
|
|
|
+ {
|
|
|
+ return zh->reconfig != 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * re-seed this client with it's own previously generated seed so its
|
|
|
+ * random choices are unique and separate from the other clients
|
|
|
+ */
|
|
|
+ void reSeed()
|
|
|
+ {
|
|
|
+ srandom(seed);
|
|
|
+ srand48(seed);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get the server that this client is currently connected to.
|
|
|
+ */
|
|
|
+ string getServer()
|
|
|
+ {
|
|
|
+ const char* addrstring = zoo_get_current_server(zh);
|
|
|
+ return string(addrstring);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get the server this client is currently connected to with no port
|
|
|
+ * specification.
|
|
|
+ */
|
|
|
+ string getServerNoPort()
|
|
|
+ {
|
|
|
+ string addrstring = getServer();
|
|
|
+
|
|
|
+ size_t found = addrstring.find(":");
|
|
|
+ CPPUNIT_ASSERT(found != string::npos);
|
|
|
+
|
|
|
+ return addrstring.substr(0, found);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get the port of the server this client is currently connected to.
|
|
|
+ */
|
|
|
+ uint32_t getServerPort()
|
|
|
+ {
|
|
|
+ string addrstring = getServer();
|
|
|
+
|
|
|
+ size_t found = addrstring.find(":");
|
|
|
+ CPPUNIT_ASSERT(found != string::npos);
|
|
|
+
|
|
|
+ string portStr = addrstring.substr(found+1);
|
|
|
+
|
|
|
+ stringstream ss(portStr);
|
|
|
+ uint32_t port;
|
|
|
+ ss >> port;
|
|
|
+
|
|
|
+ CPPUNIT_ASSERT(port >= portOffset);
|
|
|
+
|
|
|
+ return port;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Cycle to the next available server on the next connect attempt. It also
|
|
|
+ * calls into getServer (above) to return the server connected to.
|
|
|
+ */
|
|
|
+ string cycleNextServer()
|
|
|
+ {
|
|
|
+ zoo_cycle_next_server(zh);
|
|
|
+ return getServer();
|
|
|
+ }
|
|
|
+
|
|
|
+ void cycleUntilServer(const string requested)
|
|
|
+ {
|
|
|
+ // Call cycleNextServer until the one it's connected to is the one
|
|
|
+ // specified (disregarding port).
|
|
|
+ string first;
|
|
|
+
|
|
|
+ while(true)
|
|
|
+ {
|
|
|
+ string next = cycleNextServer();
|
|
|
+ if (first.empty())
|
|
|
+ {
|
|
|
+ first = next;
|
|
|
+ }
|
|
|
+ // Else we've looped around!
|
|
|
+ else if (first == next)
|
|
|
+ {
|
|
|
+ CPPUNIT_ASSERT(false);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Strip port off
|
|
|
+ string server = getServerNoPort();
|
|
|
+
|
|
|
+ // If it matches the requested host we're now 'connected' to the right host
|
|
|
+ if (server == requested)
|
|
|
+ {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Set servers for this client.
|
|
|
+ */
|
|
|
+ void setServers(const string new_hosts)
|
|
|
+ {
|
|
|
+ int rc = zoo_set_servers(zh, new_hosts.c_str());
|
|
|
+ CPPUNIT_ASSERT_EQUAL((int)ZOK, rc);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Set servers for this client and validate reconfig value matches expected.
|
|
|
+ */
|
|
|
+ void setServersAndVerifyReconfig(const string new_hosts, bool is_reconfig)
|
|
|
+ {
|
|
|
+ setServers(new_hosts);
|
|
|
+ CPPUNIT_ASSERT_EQUAL(is_reconfig, isReconfig());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Sets the server list this client is connecting to AND if this requires
|
|
|
+ * the client to be reconfigured (as dictated by internal client policy)
|
|
|
+ * then it will trigger a call to cycleNextServer.
|
|
|
+ */
|
|
|
+ void setServersAndCycleIfNeeded(const string new_hosts)
|
|
|
+ {
|
|
|
+ setServers(new_hosts);
|
|
|
+ if (isReconfig())
|
|
|
+ {
|
|
|
+ cycleNextServer();
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+class Zookeeper_reconfig : public CPPUNIT_NS::TestFixture
|
|
|
+{
|
|
|
+ CPPUNIT_TEST_SUITE(Zookeeper_reconfig);
|
|
|
+
|
|
|
+ // Test cases
|
|
|
+ CPPUNIT_TEST(testcycleNextServer);
|
|
|
+ CPPUNIT_TEST(testMigrateOrNot);
|
|
|
+ CPPUNIT_TEST(testMigrationCycle);
|
|
|
+
|
|
|
+ // In threaded mode each 'create' is a thread -- it's not practical to create
|
|
|
+ // 10,000 threads to test load balancing. The load balancing code can easily
|
|
|
+ // be tested in single threaded mode as concurrency doesn't affect the algorithm.
|
|
|
+#ifndef THREADED
|
|
|
+ CPPUNIT_TEST(testMigrateProbability);
|
|
|
+ CPPUNIT_TEST(testLoadBalancing);
|
|
|
+#endif
|
|
|
+
|
|
|
+ CPPUNIT_TEST_SUITE_END();
|
|
|
+
|
|
|
+ FILE *logfile;
|
|
|
+
|
|
|
+ double slackPercent;
|
|
|
+ static const int numClients = 10000;
|
|
|
+ static const int portOffset = 2000;
|
|
|
+
|
|
|
+ vector<Client> clients;
|
|
|
+ vector<uint32_t> numClientsPerHost;
|
|
|
+
|
|
|
+public:
|
|
|
+ Zookeeper_reconfig() :
|
|
|
+ slackPercent(10.0)
|
|
|
+ {
|
|
|
+ logfile = openlogfile("Zookeeper_reconfig");
|
|
|
+ }
|
|
|
+
|
|
|
+ ~Zookeeper_reconfig()
|
|
|
+ {
|
|
|
+ if (logfile)
|
|
|
+ {
|
|
|
+ fflush(logfile);
|
|
|
+ fclose(logfile);
|
|
|
+ logfile = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ void setUp()
|
|
|
+ {
|
|
|
+ zoo_set_log_stream(logfile);
|
|
|
+ zoo_deterministic_conn_order(1);
|
|
|
+
|
|
|
+ numClientsPerHost.resize(numClients);
|
|
|
+ }
|
|
|
+
|
|
|
+ void tearDown()
|
|
|
+ {
|
|
|
+ for (int i = 0; i < clients.size(); i++)
|
|
|
+ {
|
|
|
+ clients.at(i).close();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Create a client with given connection host string and add to our internal
|
|
|
+ * vector of clients. These are disconnected and cleaned up in tearDown().
|
|
|
+ */
|
|
|
+ Client& createClient(const string hosts)
|
|
|
+ {
|
|
|
+ Client client(hosts, clients.size());
|
|
|
+ clients.push_back(client);
|
|
|
+
|
|
|
+ return clients.back();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Same as createClient(hosts) only it takes a specific host that this client
|
|
|
+ * should simulate being connected to.
|
|
|
+ */
|
|
|
+ Client& createClient(const string hosts, const string host)
|
|
|
+ {
|
|
|
+ // Ensure requested host is in the list
|
|
|
+ size_t found = hosts.find(host);
|
|
|
+ CPPUNIT_ASSERT(found != hosts.npos);
|
|
|
+
|
|
|
+ Client client(hosts, clients.size());
|
|
|
+ client.cycleUntilServer(host);
|
|
|
+ clients.push_back(client);
|
|
|
+
|
|
|
+ return clients.back();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Create a connection host list starting at 'start' and stopping at 'stop'
|
|
|
+ * where start >= stop. This creates a connection string with host:port pairs
|
|
|
+ * separated by commas. The given 'octet' is the starting octet that is used
|
|
|
+ * as the last octet in the host's IP. This is decremented on each iteration.
|
|
|
+ * Each port will be portOffset + octet.
|
|
|
+ */
|
|
|
+ string createHostList(uint32_t start, uint32_t stop = 1, uint32_t octet = 0)
|
|
|
+ {
|
|
|
+ if (octet == 0)
|
|
|
+ {
|
|
|
+ octet = start;
|
|
|
+ }
|
|
|
+
|
|
|
+ stringstream ss;
|
|
|
+
|
|
|
+ for (int i = start; i >= stop; i--, octet--)
|
|
|
+ {
|
|
|
+ ss << "10.10.10." << octet << ":" << portOffset + octet;
|
|
|
+
|
|
|
+ if (i > stop)
|
|
|
+ {
|
|
|
+ ss << ", ";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return ss.str();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Gets the lower bound of the number of clients per server that we expect
|
|
|
+ * based on the probabilistic load balancing algorithm implemented by the
|
|
|
+ * client code.
|
|
|
+ */
|
|
|
+ double lowerboundClientsPerServer(int numClients, int numServers)
|
|
|
+ {
|
|
|
+ return (1 - slackPercent/100.0) * numClients / numServers;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Gets the upper bound of the number of clients per server that we expect
|
|
|
+ * based on the probabilistic load balancing algorithm implemented by the
|
|
|
+ * client code.
|
|
|
+ */
|
|
|
+ double upperboundClientsPerServer(int numClients, int numServers)
|
|
|
+ {
|
|
|
+ return (1 + slackPercent/100.0) * numClients / numServers;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Update all the clients to use a new list of servers. This will also cause
|
|
|
+ * the client to cycle to the next server as needed (e.g. due to a reconfig).
|
|
|
+ * It then updates the number of clients connected to the server based on
|
|
|
+ * this change.
|
|
|
+ *
|
|
|
+ * Afterwards it validates that all of the servers have the correct amount of
|
|
|
+ * clients based on the probabilistic load balancing algorithm.
|
|
|
+ */
|
|
|
+ void updateAllClientsAndServers(int start, int stop = 1)
|
|
|
+ {
|
|
|
+ string newServers = createHostList(start, stop);
|
|
|
+ int numServers = start - stop + 1;
|
|
|
+
|
|
|
+ for (int i = 0; i < numClients; i++) {
|
|
|
+
|
|
|
+ Client &client = clients.at(i);
|
|
|
+ client.reSeed();
|
|
|
+
|
|
|
+ client.setServersAndCycleIfNeeded(newServers);
|
|
|
+ numClientsPerHost.at(client.getServerPort() - portOffset - 1)++;
|
|
|
+ }
|
|
|
+
|
|
|
+ int offset = stop - 1;
|
|
|
+ for (int index = offset; index < numServers; index++) {
|
|
|
+
|
|
|
+ if (numClientsPerHost.at(index) > upperboundClientsPerServer(numClients, numServers))
|
|
|
+ {
|
|
|
+ cout << "INDEX=" << index << " too many -- actual=" << numClientsPerHost.at(index)
|
|
|
+ << " expected=" << upperboundClientsPerServer(numClients, numServers) << endl;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ CPPUNIT_ASSERT(numClientsPerHost.at(index) <= upperboundClientsPerServer(numClients, numServers));
|
|
|
+
|
|
|
+ if (numClientsPerHost.at(index) < lowerboundClientsPerServer(numClients, numServers))
|
|
|
+ {
|
|
|
+ cout << "INDEX=" << index << " too few -- actual=" << numClientsPerHost.at(index)
|
|
|
+ << " expected=" << lowerboundClientsPerServer(numClients, numServers) << endl;
|
|
|
+ }
|
|
|
+
|
|
|
+ CPPUNIT_ASSERT(numClientsPerHost.at(index) >= lowerboundClientsPerServer(numClients, numServers));
|
|
|
+ numClientsPerHost.at(index) = 0; // prepare for next test
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /*-------------------------------------------------------------------------*
|
|
|
+ * TESTCASES
|
|
|
+ *------------------------------------------------------------------------*/
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Very basic sunny day test to ensure basic functionality of zoo_set_servers
|
|
|
+ * and zoo_cycle_next_server.
|
|
|
+ */
|
|
|
+ void testcycleNextServer()
|
|
|
+ {
|
|
|
+ const string initial_hosts = createHostList(10); // 2010..2001
|
|
|
+ const string new_hosts = createHostList(4); // 2004..2001
|
|
|
+
|
|
|
+ Client &client = createClient(initial_hosts);
|
|
|
+
|
|
|
+ client.setServersAndVerifyReconfig(new_hosts, true);
|
|
|
+
|
|
|
+ for (int i = 0; i < 10; i++)
|
|
|
+ {
|
|
|
+ string next = client.cycleNextServer();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Test the migration policy implicit within the probabilistic load balancing
|
|
|
+ * algorithm the Client implements. Tests all the corner cases whereby the
|
|
|
+ * list of servers is decreased, increased, and stays the same. Also combines
|
|
|
+ * various combinations of the currently connected server being in the new
|
|
|
+ * configuration and not.
|
|
|
+ */
|
|
|
+ void testMigrateOrNot()
|
|
|
+ {
|
|
|
+ const string initial_hosts = createHostList(4); // 2004..2001
|
|
|
+
|
|
|
+ Client &client = createClient(initial_hosts, "10.10.10.3");
|
|
|
+
|
|
|
+ // Ensemble size decreasing, my server is in the new list
|
|
|
+ client.setServersAndVerifyReconfig(createHostList(3), false);
|
|
|
+
|
|
|
+ // Ensemble size decreasing, my server is NOT in the new list
|
|
|
+ client.setServersAndVerifyReconfig(createHostList(2), true);
|
|
|
+
|
|
|
+ // Ensemble size stayed the same, my server is NOT in the new list
|
|
|
+ client.setServersAndVerifyReconfig(createHostList(2), true);
|
|
|
+
|
|
|
+ // Ensemble size increased, my server is not in the new ensemble
|
|
|
+ client.setServers(createHostList(4));
|
|
|
+ client.cycleUntilServer("10.10.10.1");
|
|
|
+ client.setServersAndVerifyReconfig(createHostList(7,2), true);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * This tests that as a client is in reconfig mode it will properly try to
|
|
|
+ * connect to all the new servers first. Then it will try to connect to all
|
|
|
+ * the 'old' servers that are staying in the new configuration. Finally it
|
|
|
+ * will fallback to the normal behavior of trying servers in round-robin.
|
|
|
+ */
|
|
|
+ void testMigrationCycle()
|
|
|
+ {
|
|
|
+ int num_initial = 4;
|
|
|
+ const string initial_hosts = createHostList(num_initial); // {2004..2001}
|
|
|
+
|
|
|
+ int num_new = 10;
|
|
|
+ string new_hosts = createHostList(12, 3); // {2012..2003}
|
|
|
+
|
|
|
+ // servers from the old list that appear in the new list {2004..2003}
|
|
|
+ int num_staying = 2;
|
|
|
+ string oldStaying = createHostList(4, 3);
|
|
|
+
|
|
|
+ // servers in the new list that are not in the old list {2012..2005}
|
|
|
+ int num_coming = 8;
|
|
|
+ string newComing = createHostList(12, 5);
|
|
|
+
|
|
|
+ // Ensemble in increasing in size, my server is not in the new ensemble
|
|
|
+ // load on the old servers must be decreased, so must connect to one of
|
|
|
+ // new servers (pNew = 1)
|
|
|
+ Client &client = createClient(initial_hosts, "10.10.10.1");
|
|
|
+ client.setServersAndVerifyReconfig(new_hosts, true);
|
|
|
+
|
|
|
+ // Since we're in reconfig mode, next connect should be from new list
|
|
|
+ // We should try all the new servers *BEFORE* trying any old servers
|
|
|
+ string seen;
|
|
|
+ for (int i = 0; i < num_coming; i++) {
|
|
|
+ string next = client.cycleNextServer();
|
|
|
+
|
|
|
+ // Assert next server is in the 'new' list
|
|
|
+ size_t found = newComing.find(next);
|
|
|
+ CPPUNIT_ASSERT(found != string::npos);
|
|
|
+
|
|
|
+ // Assert not in seen list then append
|
|
|
+ found = seen.find(next);
|
|
|
+ CPPUNIT_ASSERT(found == string::npos);
|
|
|
+ seen += found + ", ";
|
|
|
+ }
|
|
|
+
|
|
|
+ // Now it should start connecting to the old servers
|
|
|
+ seen.clear();
|
|
|
+ for (int i = 0; i < num_staying; i++) {
|
|
|
+ string next = client.cycleNextServer();
|
|
|
+
|
|
|
+ // Assert it's in the old list
|
|
|
+ size_t found = oldStaying.find(next);
|
|
|
+ CPPUNIT_ASSERT(found != string::npos);
|
|
|
+
|
|
|
+ // Assert not in seen list then append
|
|
|
+ found = seen.find(next);
|
|
|
+ CPPUNIT_ASSERT(found == string::npos);
|
|
|
+ seen += found + ", ";
|
|
|
+ }
|
|
|
+
|
|
|
+ // NOW it goes back to normal as we've tried all the new and old
|
|
|
+ string first = client.cycleNextServer();
|
|
|
+ for (int i = 0; i < num_new - 1; i++) {
|
|
|
+ client.cycleNextServer();
|
|
|
+ }
|
|
|
+
|
|
|
+ CPPUNIT_ASSERT_EQUAL(first, client.cycleNextServer());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Test the migration probability to ensure that it conforms to our expected
|
|
|
+ * lower and upper bounds of the number of clients per server as we are
|
|
|
+ * reconfigured.
|
|
|
+ *
|
|
|
+ * In this case, the list of servers is increased and the client's server is
|
|
|
+ * in the new list. Whether to move or not depends on the difference of
|
|
|
+ * server sizes with probability 1 - |old|/|new| the client disconnects.
|
|
|
+ *
|
|
|
+ * In the test below 1-9/10 = 1/10 chance of disconnecting
|
|
|
+ */
|
|
|
+ void testMigrateProbability()
|
|
|
+ {
|
|
|
+ const string initial_hosts = createHostList(9); // 10.10.10.9:2009...10.10.10.1:2001
|
|
|
+ string new_hosts = createHostList(10); // 10.10.10.10:2010...10.10.10.1:2001
|
|
|
+
|
|
|
+ uint32_t numDisconnects = 0;
|
|
|
+ for (int i = 0; i < numClients; i++) {
|
|
|
+ Client &client = createClient(initial_hosts, "10.10.10.3");
|
|
|
+ client.setServers(new_hosts);
|
|
|
+ if (client.isReconfig())
|
|
|
+ {
|
|
|
+ numDisconnects++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // should be numClients/10 in expectation, we test that it's numClients/10 +- slackPercent
|
|
|
+ CPPUNIT_ASSERT(numDisconnects < upperboundClientsPerServer(numClients, 10));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Tests the probabilistic load balancing algorithm implemented by the Client
|
|
|
+ * code.
|
|
|
+ *
|
|
|
+ * Test strategy:
|
|
|
+ *
|
|
|
+ * (1) Start with 9 servers and 10,000 clients. Remove a server, update
|
|
|
+ * everything, and ensure that the clients are redistributed properly.
|
|
|
+ *
|
|
|
+ * (2) Remove two more nodes and repeat the same validations of proper client
|
|
|
+ * redistribution. Ensure no clients are connected to the two removed
|
|
|
+ * nodes.
|
|
|
+ *
|
|
|
+ * (3) Remove the first server in the list and simultaneously add the three
|
|
|
+ * previously removed servers. Ensure everything is redistributed and
|
|
|
+ * no clients are connected to the one missing node.
|
|
|
+ *
|
|
|
+ * (4) Add the one missing server back into the mix and validate.
|
|
|
+ */
|
|
|
+ void testLoadBalancing()
|
|
|
+ {
|
|
|
+ zoo_deterministic_conn_order(0);
|
|
|
+
|
|
|
+ int rc = ZOK;
|
|
|
+
|
|
|
+ uint32_t numServers = 9;
|
|
|
+ const string initial_hosts = createHostList(numServers); // 10.10.10.9:2009...10.10.10.1:2001
|
|
|
+
|
|
|
+ // Create connections to servers
|
|
|
+ for (int i = 0; i < numClients; i++) {
|
|
|
+ Client &client = createClient(initial_hosts);
|
|
|
+ numClientsPerHost.at(client.getServerPort() - portOffset - 1)++;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (int i = 0; i < numServers; i++) {
|
|
|
+ CPPUNIT_ASSERT(numClientsPerHost.at(i) <= upperboundClientsPerServer(numClients, numServers));
|
|
|
+ CPPUNIT_ASSERT(numClientsPerHost.at(i) >= lowerboundClientsPerServer(numClients, numServers));
|
|
|
+ numClientsPerHost.at(i) = 0; // prepare for next test
|
|
|
+ }
|
|
|
+
|
|
|
+ // remove last server
|
|
|
+ numServers = 8;
|
|
|
+ updateAllClientsAndServers(numServers);
|
|
|
+ CPPUNIT_ASSERT_EQUAL((uint32_t)0, numClientsPerHost.at(numServers));
|
|
|
+
|
|
|
+ // Remove two more nodes
|
|
|
+ numServers = 6;
|
|
|
+ updateAllClientsAndServers(numServers);
|
|
|
+ CPPUNIT_ASSERT_EQUAL((uint32_t)0, numClientsPerHost.at(numServers));
|
|
|
+ CPPUNIT_ASSERT_EQUAL((uint32_t)0, numClientsPerHost.at(numServers+1));
|
|
|
+ CPPUNIT_ASSERT_EQUAL((uint32_t)0, numClientsPerHost.at(numServers+2));
|
|
|
+
|
|
|
+ // remove host 0 (first one in list) and add back 6, 7, and 8
|
|
|
+ numServers = 8;
|
|
|
+ updateAllClientsAndServers(numServers, 1);
|
|
|
+ CPPUNIT_ASSERT_EQUAL((uint32_t)0, numClientsPerHost.at(0));
|
|
|
+
|
|
|
+ // add back host number 0
|
|
|
+ numServers = 9;
|
|
|
+ updateAllClientsAndServers(numServers);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+CPPUNIT_TEST_SUITE_REGISTRATION(Zookeeper_reconfig);
|