Skip to content

Commit 5733153

Browse files
author
=
committed
Tests
1 parent c29a11b commit 5733153

6 files changed

Lines changed: 119 additions & 94 deletions

File tree

README.md

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -187,43 +187,6 @@ engine.print()
187187
Serializer.dump(engine, "my_workflow", "my_workflow.json")
188188
```
189189

190-
## CapioCL Web API Documentation
191-
192-
This section describes the REST-style Web API exposed by the CapioCL Web Server.
193-
The server provides HTTP endpoints for configuring and querying the CapioCL engine at runtime.
194-
Within the `bruno_webapi_tests` you can find several tests and examples on how to perform
195-
requests to the API webserver using [bruno](https://www.usebruno.com).
196-
197-
All endpoints communicate using JSON over HTTP. To enable the webserver, users needs to explicitly start it with:
198-
199-
```cpp
200-
capiocl::engine::Engine engine();
201-
202-
// start engine with default parameters
203-
engine.startApiServer();
204-
205-
// or by specifying the address and port:
206-
engine.startApiServer("127.0.0.1", 5520);
207-
```
208-
209-
210-
or equivalently in python with:
211-
212-
```python
213-
engine = py_capio_cl.Engine()
214-
215-
#start engine with default parameters
216-
engine.startApiServer()
217-
218-
# or by specifying the address and port:
219-
engine.startApiServer("127.0.0.1", 5520)
220-
```
221-
222-
By default, the webserver listens only on local connection at the following address: ```127.0.0.1:5520```. No
223-
authentication
224-
services are currently available, and as such, users should put particular care when allowing connections from external
225-
endpoints.
226-
227190
## Notes
228191

229192
- All GET endpoints expect a JSON body containing the targeted file path.

capiocl/api.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#ifndef CAPIO_CL_WEBAPI_H
22
#define CAPIO_CL_WEBAPI_H
3+
#include <atomic>
34
#include <thread>
45

56
#include "capiocl.hpp"
@@ -14,6 +15,9 @@ class capiocl::api::CapioClApiServer {
1415
/// @brief port on which the current server runs
1516
const configuration::CapioClConfiguration &capiocl_configuration;
1617

18+
/// @brief variable to tell the thread to terminate
19+
std::atomic<bool> _terminate = false;
20+
1721
public:
1822
/// @brief default constructor.
1923
CapioClApiServer(engine::Engine *engine, configuration::CapioClConfiguration &config);

capiocl/engine.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
#ifndef CAPIO_CL_ENGINE_H
22
#define CAPIO_CL_ENGINE_H
33
#include <jsoncons/basic_json.hpp>
4-
#include <vector>
54
#include <shared_mutex>
5+
#include <vector>
66

77
#include "capiocl.hpp"
88
#include "capiocl/api.h"
@@ -26,8 +26,10 @@ struct CapioCLEntry final {
2626
std::string commit_rule = commitRules::ON_TERMINATION;
2727
///@brief Fire rule
2828
std::string fire_rule = fireRules::UPDATE;
29-
long directory_children_count = 0; ///@brief Expected number of files in directory
30-
long commit_on_close_count = 0; ///@brief Expected close count
29+
///@brief Expected number of files in directory
30+
long directory_children_count = 0;
31+
///@brief Expected close count
32+
long commit_on_close_count = 0;
3133
/// @brief whether to update or not directory item count
3234
bool enable_directory_count_update = true;
3335
/// @brief Store in memory or on the file system
@@ -342,7 +344,7 @@ class Engine final {
342344
std::vector<std::string> getConsumers(const std::filesystem::path &path) const;
343345

344346
/// @brief Get the commit-on-close counter for a file.
345-
long getCommitCloseCount(const std::filesystem::path::iterator::reference &path) const;
347+
long getCommitCloseCount(const std::filesystem::path &path) const;
346348

347349
/// @brief Get file dependencies.
348350
std::vector<std::filesystem::path>

src/Engine.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,19 @@
99
#include "capiocl/monitor.h"
1010
#include "capiocl/printer.h"
1111

12+
/// @brief Class to implement a shared mutex lock guard
1213
template <typename SharedMutex> class shared_lock_guard {
1314
public:
15+
/// @brief Constructor: acquire semaphore shared
1416
explicit shared_lock_guard(SharedMutex &m) : mutex_(m) { mutex_.lock_shared(); }
15-
17+
/// @brief Destructor: release resources
1618
~shared_lock_guard() { mutex_.unlock_shared(); }
1719

1820
shared_lock_guard(const shared_lock_guard &) = delete;
1921
shared_lock_guard &operator=(const shared_lock_guard &) = delete;
2022

2123
private:
24+
/// @brief Reference to mutex
2225
SharedMutex &mutex_;
2326
};
2427

@@ -734,8 +737,7 @@ void capiocl::engine::Engine::setFileDeps(const std::filesystem::path &path,
734737
setFileDeps(path, dependencies);
735738
}
736739

737-
long capiocl::engine::Engine::getCommitCloseCount(
738-
std::filesystem::path::iterator::reference &path) const {
740+
long capiocl::engine::Engine::getCommitCloseCount(const std::filesystem::path &path) const {
739741
if (path.empty()) {
740742
return 0;
741743
}

src/api.cpp

Lines changed: 68 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,99 @@
11
#include <arpa/inet.h>
2+
#include <condition_variable>
23
#include <iostream>
34
#include <jsoncons/json.hpp>
5+
#include <mutex>
46
#include <netinet/in.h>
7+
#include <signal.h>
58
#include <sys/socket.h>
69
#include <unistd.h>
710

811
#include "capiocl/api.h"
912
#include "capiocl/engine.h"
1013
#include "capiocl/printer.h"
1114

15+
std::mutex _setupMtx;
16+
std::condition_variable _setupCv;
17+
bool thread_ready = false;
18+
1219
/// @brief Main WebServer thread function
13-
void server(const std::string &address, const int port, capiocl::engine::Engine *engine) {
14-
pthread_setcancelstate(PTHREAD_CANCEL_ASYNCHRONOUS, nullptr);
20+
void server(const std::string &address, const int port, capiocl::engine::Engine *engine,
21+
std::atomic<bool> *terminate) {
22+
23+
constexpr int RECV_BUF_SIZE = 65535;
1524

16-
capiocl::printer::print(capiocl::printer::CLI_LEVEL_INFO,
17-
"Starting API server @ " + address + ":" + std::to_string(port));
25+
const auto &wf_name = engine->getWorkflowName();
1826

1927
int fd = socket(AF_INET, SOCK_DGRAM, 0);
20-
if (fd < 0) {
21-
perror("Socket creation failed");
22-
return;
23-
}
2428

2529
int reuse = 1;
26-
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) {
27-
perror("Setting SO_REUSEADDR failed");
28-
close(fd);
29-
return;
30-
}
30+
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
3131

3232
sockaddr_in localAddr{};
3333
localAddr.sin_family = AF_INET;
3434
localAddr.sin_port = htons(port);
3535
localAddr.sin_addr.s_addr = INADDR_ANY;
3636

37-
if (bind(fd, reinterpret_cast<sockaddr *>(&localAddr), sizeof(localAddr)) < 0) {
38-
perror("Bind failed");
39-
close(fd);
40-
return;
41-
}
37+
bind(fd, reinterpret_cast<sockaddr *>(&localAddr), sizeof(localAddr));
4238

4339
ip_mreq group{};
4440
group.imr_multiaddr.s_addr = inet_addr(address.c_str());
4541
group.imr_interface.s_addr = INADDR_ANY;
46-
if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group)) < 0) {
47-
perror("Adding multicast group failed");
48-
close(fd);
49-
return;
50-
}
42+
setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group));
5143

52-
char buffer[65535] = {0};
44+
char buffer[RECV_BUF_SIZE] = {0};
5345
sockaddr_in srcAddr{};
5446
socklen_t addrlen = sizeof(srcAddr);
5547

5648
// timeout of 1 second for termination
5749
timeval tv{};
58-
tv.tv_sec = 1;
59-
tv.tv_usec = 0;
50+
tv.tv_sec = 0;
51+
tv.tv_usec = 500;
6052
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
6153

62-
while (true) {
63-
ssize_t nbytes = recvfrom(fd, buffer, sizeof(buffer) - 1, 0,
64-
reinterpret_cast<struct sockaddr *>(&srcAddr), &addrlen);
65-
if (nbytes < 0) {
54+
thread_ready = true;
55+
_setupCv.notify_all();
56+
57+
while (!*terminate) {
58+
// GCOVR_EXCL_START
59+
ssize_t n = recvfrom(fd, buffer, RECV_BUF_SIZE - 1, 0,
60+
reinterpret_cast<sockaddr *>(&srcAddr), &addrlen);
61+
// GCOVR_EXCL_STOP
62+
63+
if (n <= 0) {
6664
continue;
6765
}
6866

69-
buffer[nbytes] = '\0';
67+
buffer[n] = '\0';
7068

7169
try {
72-
auto data = jsoncons::json::parse(buffer);
73-
auto path = data["path"].as_string();
74-
auto workflow_name = data["workflow_name"].as_string();
75-
auto rule = capiocl::engine::CapioCLEntry::fromJson(data["CapioClEntry"].as_string());
70+
auto data = jsoncons::json::parse(buffer); // GCOVR_EXCL_LINE
71+
const auto path =
72+
data.get_value_or<std::string, std::string>("path", ""); // GCOVR_EXCL_LINE
73+
if (path.empty()) {
74+
continue;
75+
}
76+
const auto workflow_name =
77+
data.get_value_or<std::string, std::string>("workflow_name", ""); // GCOVR_EXCL_LINE
78+
if (workflow_name.empty()) {
79+
continue;
80+
}
81+
const auto jsonEntry =
82+
data.get_value_or<std::string, std::string>("CapioClEntry", ""); // GCOVR_EXCL_LINE
83+
if (jsonEntry.empty()) {
84+
continue;
85+
}
86+
auto rule = capiocl::engine::CapioCLEntry::fromJson(jsonEntry);
7687

77-
if (workflow_name == engine->getWorkflowName()) {
88+
if (workflow_name == wf_name) {
7889
engine->add(path, rule);
90+
} else {
91+
continue;
7992
}
8093

8194
} catch (const jsoncons::json_exception &e) {
8295
capiocl::printer::print(capiocl::printer::CLI_LEVEL_ERROR,
83-
"JSON Parse Error: " + std::string(e.what()));
96+
"APIServer: Received invalid json: " + std::string(e.what()));
8497
}
8598
}
8699

@@ -92,15 +105,28 @@ capiocl::api::CapioClApiServer::CapioClApiServer(engine::Engine *engine,
92105
: capiocl_configuration(config) {
93106

94107
std::string address;
95-
config.getParameter("dynamic_api.ip", &address);
96-
97108
int port;
98-
config.getParameter("dynamic_api.port", &port);
109+
try {
110+
config.getParameter("dynamic_api.ip", &address); // GCOVR_EXCL_LINE
111+
} catch (...) {
112+
address = configuration::defaults::DEFAULT_API_MULTICAST_IP.v;
113+
}
114+
115+
try {
116+
config.getParameter("dynamic_api.port", &port); // GCOVR_EXCL_LINE
117+
} catch (...) {
118+
port = std::stoi(configuration::defaults::DEFAULT_API_MULTICAST_PORT.v);
119+
}
120+
121+
_webApiThread = std::thread(server, address, port, engine, &_terminate);
122+
123+
std::unique_lock lock(_setupMtx);
124+
_setupCv.wait(lock, [] { return thread_ready; });
99125

100-
_webApiThread = std::thread(server, address, port, engine);
126+
printer::print(printer::CLI_LEVEL_INFO, "API server @ " + address + ":" + std::to_string(port));
101127
}
102128

103129
capiocl::api::CapioClApiServer::~CapioClApiServer() {
104-
pthread_cancel(_webApiThread.native_handle());
130+
_terminate = true;
105131
_webApiThread.join();
106132
}

tests/cpp/test_apis.hpp

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,36 +15,34 @@
1515

1616
#include <string>
1717

18-
bool sendMulticast(const std::string &message, const std::string &multicast_ip, int port) {
19-
int fd = socket(AF_INET, SOCK_DGRAM, 0);
18+
inline bool sendMulticast(const std::string &message, const std::string &multicast_ip, int port) {
19+
const int fd = socket(AF_INET, SOCK_DGRAM, 0);
2020
if (fd < 0) {
2121
perror("socket");
2222
return false;
2323
}
2424

25-
u_char ttl = 1;
25+
const u_char ttl = 3;
2626
if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0) {
2727
perror("setsockopt (TTL)");
2828
close(fd);
2929
return false;
3030
}
3131

32-
sockaddr_in addr;
32+
sockaddr_in addr{};
3333
addr.sin_family = AF_INET;
3434
addr.sin_addr.s_addr = inet_addr(multicast_ip.c_str());
3535
addr.sin_port = htons(port);
3636

37-
ssize_t nbytes =
38-
sendto(fd, message.c_str(), message.size(), 0, (struct sockaddr *) &addr, sizeof(addr));
37+
ssize_t nbytes = sendto(fd, message.c_str(), message.size(), 0,
38+
reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr));
3939

4040
if (nbytes < 0) {
4141
perror("sendto");
4242
close(fd);
4343
return false;
4444
}
4545

46-
std::cout << "Sent " << nbytes << " bytes to " << multicast_ip << ":" << port << std::endl;
47-
4846
close(fd);
4947
return true;
5048
}
@@ -93,11 +91,41 @@ TEST(WEBSERVER_SUITE_NAME, TestWebServerAPIS) {
9391
engine.startApiServer();
9492
EXPECT_FALSE(engine.contains("file.txt"));
9593

94+
EXPECT_TRUE(
95+
sendMulticast(R"({"path" : "file.txt", "workflow_name" : "notMyWorkflow"})",
96+
capiocl::configuration::defaults::DEFAULT_API_MULTICAST_IP.v,
97+
stoi(capiocl::configuration::defaults::DEFAULT_API_MULTICAST_PORT.v)));
98+
99+
EXPECT_TRUE(
100+
sendMulticast("notAValidJson", capiocl::configuration::defaults::DEFAULT_API_MULTICAST_IP.v,
101+
stoi(capiocl::configuration::defaults::DEFAULT_API_MULTICAST_PORT.v)));
102+
103+
EXPECT_TRUE(
104+
sendMulticast(R"({"workflow_name" : "notMyWorkflow", "CapioClEntry":{}})",
105+
capiocl::configuration::defaults::DEFAULT_API_MULTICAST_IP.v,
106+
stoi(capiocl::configuration::defaults::DEFAULT_API_MULTICAST_PORT.v)));
107+
108+
EXPECT_TRUE(
109+
sendMulticast(R"({"path" : "file.txt", "CapioClEntry":{}})",
110+
capiocl::configuration::defaults::DEFAULT_API_MULTICAST_IP.v,
111+
stoi(capiocl::configuration::defaults::DEFAULT_API_MULTICAST_PORT.v)));
112+
113+
EXPECT_TRUE(
114+
sendMulticast(R"({"path" : "file.txt", "workflow_name" : "notMyWorkflow"})",
115+
capiocl::configuration::defaults::DEFAULT_API_MULTICAST_IP.v,
116+
stoi(capiocl::configuration::defaults::DEFAULT_API_MULTICAST_PORT.v)));
117+
96118
capiocl::engine::CapioCLEntry entry;
97119
entry.commit_rule = "on_file";
98120
entry.commit_on_close_count = 10;
99121
entry.fire_rule = "no_update";
100122

123+
EXPECT_TRUE(sendMulticast(
124+
R"({ "path" : "file.txt","workflow_name" : "notMyWorkflow", "CapioClEntry":)" +
125+
entry.toJson() + "}",
126+
capiocl::configuration::defaults::DEFAULT_API_MULTICAST_IP.v,
127+
stoi(capiocl::configuration::defaults::DEFAULT_API_MULTICAST_PORT.v)));
128+
101129
std::string request = R"({ "path" : "file.txt","workflow_name" : ")";
102130
request += capiocl::CAPIO_CL_DEFAULT_WF_NAME;
103131
request += R"(", "CapioClEntry":)" + entry.toJson() + "}";

0 commit comments

Comments
 (0)