Aggie Student Toolkit — Load Balancer CSCE 412 Project 4
Arnav Fotedar · Fall 2026 · C++ load balancer simulation
Loading...
Searching...
No Matches
test_main.cpp
Go to the documentation of this file.
1
6
7#include <iostream>
8#include <sstream>
9#include <string>
10#include <vector>
11
12#include "Config.h"
13#include "Firewall.h"
14#include "LoadBalancer.h"
15#include "Logger.h"
16#include "RequestGenerator.h"
17#include "Switch.h"
18#include "WebServer.h"
19
20namespace {
21
22int failures = 0;
23int checks = 0;
24
25void check(bool condition, const std::string& description) {
26 ++checks;
27 if (!condition) {
28 ++failures;
29 std::cout << " FAIL: " << description << '\n';
30 }
31}
32
33Request makeRequest(unsigned long id, unsigned int task, const std::string& source = "10.0.0.1",
34 JobType type = JobType::Processing, unsigned long arrival = 0) {
35 Request request;
36 request.id = id;
37 request.sourceIp = source;
38 request.destinationIp = "10.0.0.2";
39 request.taskCycles = task;
40 request.arrivalCycle = arrival;
41 request.jobType = type;
42 return request;
43}
44
45void testWebServer() {
46 WebServer server(1);
47 check(server.isIdle(), "new server is idle");
48 server.assign(makeRequest(1, 2), 0);
49 check(!server.isIdle(), "server is busy after assign");
50 check(!server.tick(), "two-cycle task is not done after one tick");
51 check(server.tick(), "two-cycle task is done after two ticks");
52 check(server.isIdle() && server.completedCount() == 1 && server.busyCycles() == 2,
53 "server returns to idle and counts the completion");
54 bool threw = false;
55 server.assign(makeRequest(2, 5), 0);
56 try {
57 server.assign(makeRequest(3, 5), 0);
58 } catch (const std::logic_error&) {
59 threw = true;
60 }
61 check(threw, "assigning to a busy server throws");
62}
63
64void testFirewall() {
65 Firewall firewall;
66 check(firewall.addRange("192.168.0.0/16"), "valid CIDR is accepted");
67 check(firewall.addRange("8.8.8.8"), "single host is accepted");
68 check(!firewall.addRange("300.1.1.1/8"), "octet above 255 is rejected");
69 check(!firewall.addRange("10.0.0.0/33"), "prefix above 32 is rejected");
70 check(firewall.isBlocked("192.168.44.9"), "address inside a range is blocked");
71 check(firewall.isBlocked("8.8.8.8"), "blocked single host");
72 check(!firewall.isBlocked("8.8.8.9"), "neighbor of a blocked host is allowed");
73 check(!firewall.isBlocked("192.169.0.1"), "address outside the range is allowed");
74 check(firewall.isBlocked("not-an-ip"), "malformed address is blocked");
75}
76
77void testGenerator() {
78 GeneratorSettings settings;
79 settings.minTaskCycles = 3;
80 settings.maxTaskCycles = 7;
81 settings.arrivalRate = 2.0;
82 settings.waveAmplitude = 0.0;
83 RequestGenerator first(settings, 99);
84 RequestGenerator second(settings, 99);
85 bool inRange = true;
86 bool same = true;
87 for (unsigned long cycle = 1; cycle <= 500; ++cycle) {
88 const std::vector<Request> a = first.arrivalsFor(cycle);
89 const std::vector<Request> b = second.arrivalsFor(cycle);
90 same = same && a.size() == b.size();
91 for (std::size_t i = 0; i < a.size() && i < b.size(); ++i) {
92 inRange = inRange && a[i].taskCycles >= 3 && a[i].taskCycles <= 7;
93 same = same && a[i].sourceIp == b[i].sourceIp && a[i].taskCycles == b[i].taskCycles;
94 }
95 }
96 check(inRange, "task times stay inside the configured range");
97 check(same, "the same seed reproduces the same requests");
98 const double mean = static_cast<double>(first.stats().total) / 500.0;
99 check(mean > 1.7 && mean < 2.3, "observed arrival rate is close to the configured mean");
100
101 settings.waveAmplitude = 0.5;
102 settings.wavePeriod = 100;
103 RequestGenerator wave(settings, 1);
104 check(wave.rateAt(25) > 2.9 && wave.rateAt(75) < 1.1, "traffic wave raises and lowers the rate");
105}
106
107void testScaling() {
108 Logger logger(false, false);
109 ScalingPolicy policy;
110 policy.upperFactor = 5;
111 policy.lowerFactor = 2;
112 policy.cooldownCycles = 1;
113 policy.maxServers = 4;
114 LoadBalancer balancer("T", 2, policy, Firewall(), 0, logger);
115 for (unsigned long id = 1; id <= 40; ++id) {
116 balancer.admit(makeRequest(id, 50), 0);
117 }
118 balancer.start();
119 check(balancer.busyServers() == 2 && balancer.queueSize() == 38, "start dispatches to every server");
120 balancer.processCycle(1, {});
121 check(balancer.liveServers() == 3, "queue above upper x servers adds a server");
122 balancer.processCycle(2, {});
123 balancer.processCycle(3, {});
124 check(balancer.liveServers() == 4, "pool never exceeds maxServers");
125
126 // Let all work drain so the queue falls below lower x servers.
127 for (unsigned long cycle = 4; cycle <= 3000; ++cycle) {
128 balancer.processCycle(cycle, {});
129 }
130 check(balancer.liveServers() == 1, "an empty queue shrinks the pool to minServers");
131 check(balancer.stats().completed == 40, "every request is completed");
132 check(balancer.stats().admitted ==
133 balancer.stats().completed + balancer.queueSize() + balancer.inService(),
134 "admitted requests are all accounted for");
135}
136
137void testRetiringServerFinishesWork() {
138 Logger logger(false, false);
139 ScalingPolicy policy;
140 policy.upperFactor = 100;
141 policy.lowerFactor = 1;
142 policy.cooldownCycles = 1;
143 LoadBalancer balancer("T", 2, policy, Firewall(), 0, logger);
144 balancer.admit(makeRequest(1, 10), 0);
145 balancer.admit(makeRequest(2, 3), 0);
146 balancer.start();
147 balancer.processCycle(1, {});
148 check(balancer.retiringServers() == 1, "busy server is marked retiring instead of being dropped");
149 for (unsigned long cycle = 2; cycle <= 3; ++cycle) {
150 balancer.processCycle(cycle, {});
151 }
152 check(balancer.liveServers() == 1 && balancer.stats().completed == 1,
153 "retiring server is removed only after finishing its request");
154}
155
156void testRejectAndDiscard() {
157 Logger logger(false, false);
158 Firewall firewall;
159 firewall.addRange("66.0.0.0/8");
160 LoadBalancer balancer("T", 1, ScalingPolicy(), firewall, 2, logger);
161 check(!balancer.admit(makeRequest(1, 5, "66.1.2.3"), 0), "blocked source is rejected");
162 check(balancer.admit(makeRequest(2, 5), 0) && balancer.admit(makeRequest(3, 5), 0), "allowed requests queue");
163 check(!balancer.admit(makeRequest(4, 5), 0), "request beyond queue capacity is discarded");
164 check(balancer.stats().rejected == 1 && balancer.stats().discarded == 1, "reject and discard are counted");
165}
166
167void testSwitch() {
168 Logger logger(false, false);
169 Switch router(std::make_unique<LoadBalancer>("P", 1, ScalingPolicy(), Firewall(), 0, logger),
170 std::make_unique<LoadBalancer>("S", 1, ScalingPolicy(), Firewall(), 0, logger));
171 router.route(makeRequest(1, 5, "10.0.0.1", JobType::Processing), 0);
172 router.route(makeRequest(2, 5, "10.0.0.1", JobType::Streaming), 0);
173 router.route(makeRequest(3, 5, "10.0.0.1", JobType::Streaming), 0);
174 check(router.processing().queueSize() == 1 && router.streaming().queueSize() == 2,
175 "switch routes requests by job type");
176}
177
178void testConfig() {
179 Config config;
180 std::string error;
181 const char* args[] = {"lb", "--servers", "7", "--cycles", "50", "--block", "1.2.3.0/24", "--no-default-blocks"};
182 check(parseArguments(8, const_cast<char**>(args), config, error), "options parse");
183 check(config.servers == 7 && config.cycles == 50, "numeric options are read");
184 check(config.blockedRanges.size() == 1 && config.blockedRanges[0] == "1.2.3.0/24",
185 "--no-default-blocks keeps only user ranges");
186 const char* bad[] = {"lb", "--servers", "-3"};
187 Config other;
188 check(!parseArguments(3, const_cast<char**>(bad), other, error), "negative server count is rejected");
189 Config invalid;
190 invalid.minTask = 50;
191 invalid.maxTask = 10;
192 check(!validateConfig(invalid, error), "min task above max task is invalid");
193
194 Config prompted;
195 std::istringstream answers("12\n500\n\n\n\n\ny\nn\n");
196 std::ostringstream questions;
197 promptForSettings(prompted, answers, questions);
198 check(prompted.servers == 12 && prompted.cycles == 500 && prompted.verbose && !prompted.useSwitch,
199 "interactive prompts read answers and keep defaults on blank lines");
200}
201
202} // namespace
203
208int main() {
209 testWebServer();
210 testFirewall();
211 testGenerator();
212 testScaling();
213 testRetiringServerFinishesWork();
214 testRejectAndDiscard();
215 testSwitch();
216 testConfig();
217 std::cout << (checks - failures) << "/" << checks << " checks passed\n";
218 return failures == 0 ? 0 : 1;
219}
void promptForSettings(Config &config, std::istream &in, std::ostream &out)
Asks the user for the main settings, keeping the current value when the answer is blank.
Definition Config.cpp:201
bool validateConfig(const Config &config, std::string &error)
Checks that the settings are consistent.
Definition Config.cpp:214
bool parseArguments(int argc, char *argv[], Config &config, std::string &error)
Reads command-line options into a Config.
Definition Config.cpp:95
Declares the Config structure and the command-line and interactive input functions.
Declares the Firewall class, an IPv4 range blocker used before requests are queued.
Declares the LoadBalancer class, which queues requests, dispatches them and scales its server pool.
Declares the Logger class that prints colored, mode-aware simulation events.
JobType
Category of work carried by a Request.
Definition Request.h:19
@ Processing
Short computational job, such as rendering a page or running a query.
Definition Request.h:20
@ Streaming
Long-lived transfer, such as streaming media or a large download.
Definition Request.h:21
Declares the RequestGenerator class, the seeded source of random traffic.
Declares the Switch class (bonus), which routes requests to separate load balancers by job type.
Declares the WebServer class, a worker that processes one Request at a time.
Rejects requests whose source address falls in a blocked IPv4 range.
Definition Firewall.h:22
bool addRange(const std::string &cidr)
Adds a blocked range.
Definition Firewall.cpp:44
bool isBlocked(const std::string &ip) const
Checks whether an address is blocked.
Definition Firewall.cpp:74
Distributes queued requests across a dynamically sized pool of WebServer objects.
Writes simulation events to the console and, optionally, to a file.
Definition Logger.h:40
Produces random requests from a single seeded Mersenne Twister engine.
Routes each request to a processing or a streaming LoadBalancer.
Definition Switch.h:24
A simulated web server that processes at most one request at a time.
Definition WebServer.h:26
int main(int argc, char *argv[])
Parses input, runs the simulation and writes the summary to the console and log file.
Definition main.cpp:90
Every user-adjustable setting for one simulation run.
Definition Config.h:21
unsigned int servers
Initial number of web servers.
Definition Config.h:22
bool useSwitch
Bonus: route P and S jobs to two load balancers.
Definition Config.h:42
unsigned long cycles
Clock cycles to simulate.
Definition Config.h:23
unsigned int maxTask
Longest task time, in cycles.
Definition Config.h:25
bool verbose
Print every event instead of only status lines.
Definition Config.h:40
unsigned int minTask
Shortest task time, in cycles.
Definition Config.h:24
std::vector< std::string > blockedRanges
Firewall CIDR ranges.
Definition Config.h:39
Tunable parameters for random request generation.
unsigned int minTaskCycles
Shortest possible task, in clock cycles.
double waveAmplitude
Traffic wave size: the rate swings between (1 - a) and (1 + a) x arrivalRate.
unsigned long wavePeriod
Cycles in one busy-to-quiet traffic wave; 0 disables the wave.
double arrivalRate
Average new requests per clock cycle (Poisson mean).
unsigned int maxTaskCycles
Longest possible task, in clock cycles.
A single web request waiting for, or receiving, service.
Definition Request.h:41
unsigned int taskCycles
Clock cycles of processing required (always at least 1).
Definition Request.h:45
unsigned long arrivalCycle
Clock cycle at which the request was generated.
Definition Request.h:46
JobType jobType
Processing ('P') or streaming ('S') work.
Definition Request.h:47
std::string destinationIp
IPv4 address the response is returned to.
Definition Request.h:44
unsigned long id
Sequential identifier assigned by the generator.
Definition Request.h:42
std::string sourceIp
IPv4 address of the client sending the request.
Definition Request.h:43
Rules for growing and shrinking the server pool.
unsigned int maxServers
Never more live servers (active + retiring) than this.
double lowerFactor
Scale down when queue < lowerFactor x active servers.
unsigned int cooldownCycles
Cycles between scaling evaluations.
double upperFactor
Scale up when queue > upperFactor x active servers.