diff --git a/CMakeLists.txt b/CMakeLists.txt
index 34ea7f9..5de6383 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -100,6 +100,7 @@ add_subdirectory( ut )
add_subdirectory( tools/matching-helper )
add_subdirectory( tools/arashpartow-helper )
add_subdirectory( tools/h2client )
+add_subdirectory( tools/udp-server )
###########
# Install #
diff --git a/Dockerfile b/Dockerfile
index 0a0a295..00df49e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -22,6 +22,7 @@ COPY --from=builder /code/build/${build_type}/bin/h2agent /opt/
COPY --from=builder /code/build/${build_type}/bin/h2client /opt/
COPY --from=builder /code/build/${build_type}/bin/matching-helper /opt/
COPY --from=builder /code/build/${build_type}/bin/arashpartow-helper /opt/
+COPY --from=builder /code/build/${build_type}/bin/udp-server /opt/
# We add curl & jq for helpers.src
# Ubuntu has bash already installed, but vim is missing
diff --git a/Dockerfile.training b/Dockerfile.training
index 4306484..55b231e 100644
--- a/Dockerfile.training
+++ b/Dockerfile.training
@@ -34,6 +34,7 @@ RUN ln -s /opt/h2agent
RUN ln -s /opt/h2client
RUN ln -s /opt/matching-helper
RUN ln -s /opt/arashpartow-helper
+RUN ln -s /opt/udp-server
ENTRYPOINT ["sleep", "infinity"]
CMD []
diff --git a/README.md b/README.md
index c80e897..123d54d 100644
--- a/README.md
+++ b/README.md
@@ -76,6 +76,8 @@ The option `--auto` builds the builder image (`--builder-image`) , then t
$> docker run --rm -it --network=host --entrypoint "/opt/matching-helper" ghcr.io/testillano/h2agent:latest --help
-or-
$> docker run --rm -it --network=host --entrypoint "/opt/arashpartow-helper" ghcr.io/testillano/h2agent:latest --help
+ -or-
+ $> docker run --rm -it --entrypoint "/opt/udp-server" ghcr.io/testillano/h2agent:latest --help
```
* Run within `kubernetes` deployment: corresponding `helm charts` are normally packaged into releases. This is described in ["how it is delivered"](#How-it-is-delivered) section, but in summary, you could do the following:
@@ -803,7 +805,7 @@ Expression: 404 == 404
Result: 1
```
-## Execution of h2client helper utility
+## Execution of h2client utility
This utility could be useful to test simple HTTP/2 requests.
@@ -873,6 +875,49 @@ Uri: http://localhost:8000/book/8472098362
Response headers: [date: Sun, 27 Nov 2022 18:58:32 GMT]
```
+## Execution of udp-server utility
+
+This utility could be useful to test UDP messages sent by `h2agent` (`udpSocket.*` target).
+You can also use netcat in bash, to generate messages easily:
+
+```bash
+echo -n "" | nc -u -q0 -w1 -U /tmp/my_unix_socket
+```
+
+### Command line
+
+You may take a look to `udp-server` command line by just typing the build path, for example for `Release` target using native executable:
+
+```bash
+$> ./build/Release/bin/udp-server --help
+Usage: udp-server [options]
+
+Options:
+
+--path
+ UDP unix socket path
+
+--print-each
+ Print messages each specific amount (must be positive). Defaults to 1.
+
+[-h|--help]
+ This help.
+
+Examples:
+ udp-server --path "/tmp/my_unix_socket"
+```
+
+Execution example:
+
+```bash
+$> ./build/Release/bin/udp-server --path "/tmp/my_unix_socket"
+
+Path: /tmp/my_unix_socket
+Print each: 1 message(s)
+
+Waiting for messages ([sequence] ) ...
+```
+
## Execution with TLS support
`H2agent` server mock supports `SSL/TLS`. You may use helpers located under `tools/ssl` to create server key and certificate files:
@@ -1706,7 +1751,7 @@ Defines the response behavior for an incoming request matching some basic condit
},
"target": {
"type": "string",
- "pattern": "^response\\.body\\.(string$|hexstring$)|^response\\.body\\.json\\.(object$|object\\..+|jsonstring$|jsonstring\\..+|string$|string\\..+|integer$|integer\\..+|unsigned$|unsigned\\..+|float$|float\\..+|boolean$|boolean\\..+)|^response\\.(header\\..+|statusCode|delayMs)$|^(var|globalVar|serverEvent)\\..+|^outState(\\.(POST|GET|PUT|DELETE|HEAD)(\\..+)?)?$|^txtFile\\..+|^binFile\\..+|^break$"
+ "pattern": "^response\\.body\\.(string$|hexstring$)|^response\\.body\\.json\\.(object$|object\\..+|jsonstring$|jsonstring\\..+|string$|string\\..+|integer$|integer\\..+|unsigned$|unsigned\\..+|float$|float\\..+|boolean$|boolean\\..+)|^response\\.(header\\..+|statusCode|delayMs)$|^(var|globalVar|serverEvent)\\..+|^outState(\\.(POST|GET|PUT|DELETE|HEAD)(\\..+)?)?$|^txtFile\\..+|^binFile\\..+|^udpSocket\\..+|^break$"
}
},
"additionalProperties" : {
@@ -2027,8 +2072,12 @@ The **target** of information is classified after parsing the following possible
- txtFile.`` *[string]*: dumps source (as string) over text file with the path provided. The path can be relative (to the execution directory) or absolute, and **admits variables substitution**. Note that paths to missing directories will fail to open (the process does not create tree hierarchy). It is considered long term file (file is closed 1 second after last write, by default) when a constant path is configured, because this is normally used for specific log files. On the other hand, when any substitution may took place in the path provided (it has variables in the form `@{varname}`) it is considered as a dynamic name, so understood as short term file (file is opened, written and closed without delay, by default). **Note:** you can force short term type inserting a variable, for example with empty value: `txtFile./path/to/short-term-file.txt@{empty}`. Delays in microseconds are configurable on process startup. Check [command line](#Command-line) for `--long-term-files-close-delay-usecs` and `--short-term-files-close-delay-usecs` options.
+ This target can also be used to write named pipes (previously created: `mkfifo /tmp/mypipe && chmod 0666 /tmp/mypipe`), with the following restriction: writes must close the file descriptor everytime, so long/short term delays for close operations must be zero depending on which of them applies: variable paths zeroes the delay by default, but constant ones shall be zeroed too by command-line (`--long-term-files-close-delay-usecs 0`). Just like with regular UNIX pipes (`|`), when the writer closes, the pipe is torn down, so fast operations writting named pipes could provoke data looses (some writes missed). In that case, it is more recommended to use UDP unix sockets target (`udpSocket./tmp/myunixsocket`).
+
- binFile.`` *[string]*: same as `txtFile` but writting binary data.
+- udpSocket.`[.]` *[string]*: sends source (as string) towards the UDP unix socket with the path provided, with an optional delay in milliseconds (if path contains dots, an unexpected delay can be configured and same could happen for the path, so in that case, you must force delay specification by mean adding `.0` or any valid value). The path can be relative (to the execution directory) or absolute, and **admits variables substitution**. UDP is a transport layer protocol in the TCP/IP suite, which provides a simple, connectionless, and unreliable communication service. It is a lightweight protocol that does not guarantee the delivery or order of data packets. Instead, it allows applications to send individual datagrams (data packets) to other hosts over the network without establishing a connection first. UDP is often used where low latency is crucial. In `h2agent` is useful to signal external applications to do associated tasks sharing specific data for the transactions processed. Use `./tools/udp-server` program to play with it.
+
- serverEvent.``: this target is always used in conjunction with `eraser` source acting as an alternative purge method to the purge `outState`. The main difference is that states-driven purge method acts over processed events key (`method` and `uri` for the provision in which the purge state is planned), so not all the test scenarios may be covered with that constraint if they need to remove events registered for different transactions. In this case, event addressing is defined by request *method* (`requestMethod`), *URI* (`requestUri`), and events *number* (`eventNumber`): events number *path* (`eventPath`) is not accepted, as this operation just remove specific events or whole history, like REST API for server-data deletion:
- *requestMethod*: any supported method (*POST*, *GET*, *PUT*, *DELETE*, *HEAD*). Mandatory.
@@ -3048,7 +3097,7 @@ Client provisions are a fundamental part of the client mode configuration. Unlik
},
"target": {
"type": "string",
- "pattern": "^request\\.body\\.(string$|hexstring$)|^request\\.body\\.json\\.(object$|object\\..+|jsonstring$|jsonstring\\..+|string$|string\\..+|integer$|integer\\..+|unsigned$|unsigned\\..+|float$|float\\..+|boolean$|boolean\\..+)|^request\\.(header\\..+|delayMs|timeoutMs)$|^(var|globalVar|clientEvent)\\..+|^outState$|^txtFile\\..+|^binFile\\..+"
+ "pattern": "^request\\.body\\.(string$|hexstring$)|^request\\.body\\.json\\.(object$|object\\..+|jsonstring$|jsonstring\\..+|string$|string\\..+|integer$|integer\\..+|unsigned$|unsigned\\..+|float$|float\\..+|boolean$|boolean\\..+)|^request\\.(header\\..+|delayMs|timeoutMs)$|^(var|globalVar|clientEvent)\\..+|^outState$|^txtFile\\..+|^binFile\\..+|^udpSocket\\..+"
}
},
"additionalProperties" : {
@@ -3071,7 +3120,7 @@ Client provisions are a fundamental part of the client mode configuration. Unlik
},
"target": {
"type": "string",
- "pattern": "^(var|globalVar|clientEvent)\\..+|^outState$|^txtFile\\..+|^binFile\\..+|^break$"
+ "pattern": "^(var|globalVar|clientEvent)\\..+|^outState$|^txtFile\\..+|^binFile\\..+|^udpSocket\\..+|^break$"
}
},
"additionalProperties" : {
@@ -3843,6 +3892,12 @@ FileSystem_observed_operations_total{operation="write"} 100000
FileSystem_observed_operations_total{operation="close"} 1
FileSystem_observed_operations_total{operation="empty"} 0
FileSystem_observed_operations_total{operation="open"} 1
+# TYPE UDPSocket_observed_operations_total counter
+UDPSocket_observed_operations_total{operation="open",success="false"} 0
+UDPSocket_observed_operations_total{operation="delayedWrite"} 0
+UDPSocket_observed_operations_total{operation="instantWrite"} 0
+UDPSocket_observed_operations_total{operation="open"} 0
+UDPSocket_observed_operations_total{operation="write"} 0
# HELP AdminHttp2Server_responses_delay_seconds_gauge Http2 message responses delay gauge (seconds) in AdminHttp2Server
# TYPE AdminHttp2Server_responses_delay_seconds_gauge gauge
AdminHttp2Server_responses_delay_seconds_gauge 7.2e-05
@@ -3907,6 +3962,7 @@ So, metrics implemented could be divided in two categories, **counters** and **g
- DELETE requests
- HEAD requests
- requests
+- File system and Unix sockets operations
- Error-condition requests (POST/GET/PUT/DELETE/HEAD/other)
#### Gauges and histograms
diff --git a/ct/src/conftest.py b/ct/src/conftest.py
index 53cd281..a7bad70 100644
--- a/ct/src/conftest.py
+++ b/ct/src/conftest.py
@@ -38,6 +38,7 @@
ADMIN_CONFIGURATION_URI = ADMIN_URI_PREFIX + 'configuration'
ADMIN_GLOBAL_VARIABLE_URI = ADMIN_URI_PREFIX + 'global-variable'
ADMIN_FILES_URI = ADMIN_URI_PREFIX + 'files'
+ADMIN_SOCKETS_URI = ADMIN_URI_PREFIX + 'udp-sockets'
ADMIN_LOGGING_URI = ADMIN_URI_PREFIX + 'logging'
ADMIN_SERVER_CONFIGURATION_URI = ADMIN_URI_PREFIX + 'server/configuration'
ADMIN_SERVER_MATCHING_URI = ADMIN_URI_PREFIX + 'server-matching'
@@ -638,6 +639,36 @@ def send(content, responseBodyRef = VALID_CLIENT_PROVISION__RESPONSE_BODY, respo
}
'''
+SOCKET_MANAGER_PROVISION='''
+{
+ "requestMethod": "GET",
+ "requestUri":"/app/v1/foo/bar",
+ "responseCode": 200,
+ "transform": [
+ {
+ "source": "value./tmp/my_unix_socket1",
+ "target": "var.file1"
+ },
+ {
+ "source": "value./tmp/my_unix_socket2",
+ "target": "var.file2"
+ },
+ {
+ "source": "value.0",
+ "target": "var.delayms"
+ },
+ {
+ "source": "value.hello",
+ "target": "udpSocket.@{file1}.@{delayms}"
+ },
+ {
+ "source": "value.world",
+ "target": "udpSocket.@{file2}.@{delayms}"
+ }
+ ]
+}
+'''
+
GLOBAL_VARIABLE_PROVISION_TEMPLATE_GVARCREATED_GVARREMOVED_GVARANSWERED='''
{{
"requestMethod":"POST",
diff --git a/ct/src/files_operation/fo_test.py b/ct/src/files_operation/fo_test.py
index 4871350..83918c0 100644
--- a/ct/src/files_operation/fo_test.py
+++ b/ct/src/files_operation/fo_test.py
@@ -1,6 +1,6 @@
import pytest
import json
-import time
+#import time
from conftest import ADMIN_FILES_URI, string2dict, FILE_MANAGER_PROVISION
@@ -26,7 +26,7 @@ def test_001_i_want_to_get_process_files(h2ac_admin, admin_server_provision, h2a
# # Wait 2 seconds (long-term file closes in 1 second by default)
# time.sleep(2)
#
- # Check file
+ # Check files json
response = h2ac_admin.get(ADMIN_FILES_URI)
responseBodyRef = [{ "bytes":5, "path": "/tmp/example.txt", "state": "closed" }]
h2ac_admin.assert_response__status_body_headers(response, 200, responseBodyRef)
diff --git a/ct/src/sockets_operation/so2_test.py b/ct/src/sockets_operation/so2_test.py
new file mode 100644
index 0000000..680718e
--- /dev/null
+++ b/ct/src/sockets_operation/so2_test.py
@@ -0,0 +1,26 @@
+import pytest
+import json
+from conftest import ADMIN_SOCKETS_URI, string2dict, SOCKET_MANAGER_PROVISION
+
+
+@pytest.mark.admin
+def test_001_i_want_to_get_process_sockets(h2ac_admin, admin_server_provision, h2ac_traffic):
+
+ # Provision
+ admin_server_provision(string2dict(SOCKET_MANAGER_PROVISION))
+
+ # Check file before traffic: skipped because the test could re-run
+ #response = h2ac_admin.get(ADMIN_SOCKETS_URI)
+ #assert response["status"] == 204
+
+ # Send GET
+ response = h2ac_traffic.get("/app/v1/foo/bar")
+ h2ac_admin.assert_response__status_body_headers(response, 200, "")
+
+ # Check sockets json
+ response = h2ac_admin.get(ADMIN_SOCKETS_URI)
+ responseBodyRef = [{ "socket":0, "path": "/tmp/my_unix_socket2" }, { "socket":0, "path": "/tmp/my_unix_socket1" }]
+ responseBodyRef[0]["socket"] = response["body"][0]["socket"]
+ responseBodyRef[1]["socket"] = response["body"][1]["socket"]
+ h2ac_admin.assert_response__status_body_headers(response, 200, responseBodyRef)
+
diff --git a/src/http2/MyAdminHttp2Server.cpp b/src/http2/MyAdminHttp2Server.cpp
index ee1eda1..9de86c5 100644
--- a/src/http2/MyAdminHttp2Server.cpp
+++ b/src/http2/MyAdminHttp2Server.cpp
@@ -53,6 +53,7 @@ SOFTWARE.
#include
#include
#include
+#include
#include
@@ -570,6 +571,10 @@ void MyAdminHttp2Server::receiveGET(const std::string &uri, const std::string &p
responseBody = getFileManager()->asJsonString();
statusCode = ((responseBody == "[]") ? ert::http2comm::ResponseCode::NO_CONTENT:ert::http2comm::ResponseCode::OK); // 204 or 200
}
+ else if (pathSuffix == "udp-sockets") {
+ responseBody = getSocketManager()->asJsonString();
+ statusCode = ((responseBody == "[]") ? ert::http2comm::ResponseCode::NO_CONTENT:ert::http2comm::ResponseCode::OK); // 204 or 200
+ }
else if (pathSuffix == "logging") {
responseBody = ert::tracing::Logger::levelAsString(ert::tracing::Logger::getLevel());
headers.emplace("content-type", nghttp2::asio_http2::header_value{"text/html"});
diff --git a/src/http2/MyAdminHttp2Server.hpp b/src/http2/MyAdminHttp2Server.hpp
index 8103416..619ad20 100644
--- a/src/http2/MyAdminHttp2Server.hpp
+++ b/src/http2/MyAdminHttp2Server.hpp
@@ -52,6 +52,7 @@ namespace model
class Configuration;
class GlobalVariable;
class FileManager;
+class SocketManager;
class AdminData;
class MockServerData;
class MockClientData;
@@ -131,6 +132,13 @@ class MyAdminHttp2Server: public ert::http2comm::Http2Server
return common_resources_.FileManagerPtr;
}
+ void setSocketManager(model::SocketManager *p) {
+ common_resources_.SocketManagerPtr = p;
+ }
+ model::SocketManager *getSocketManager() const {
+ return common_resources_.SocketManagerPtr;
+ }
+
void setMetricsData(ert::metrics::Metrics *metrics, const ert::metrics::bucket_boundaries_t &responseDelaySecondsHistogramBucketBoundaries, const ert::metrics::bucket_boundaries_t &messageSizeBytesHistogramBucketBoundaries) {
common_resources_.MetricsPtr = metrics;
common_resources_.ResponseDelaySecondsHistogramBucketBoundaries = responseDelaySecondsHistogramBucketBoundaries;
diff --git a/src/http2/MyTrafficHttp2Client.cpp b/src/http2/MyTrafficHttp2Client.cpp
index 59ab743..f7d9322 100644
--- a/src/http2/MyTrafficHttp2Client.cpp
+++ b/src/http2/MyTrafficHttp2Client.cpp
@@ -51,6 +51,7 @@ SOFTWARE.
#include
#include
#include
+#include
#include
namespace h2agent
diff --git a/src/http2/MyTrafficHttp2Client.hpp b/src/http2/MyTrafficHttp2Client.hpp
index 636ea82..be85055 100644
--- a/src/http2/MyTrafficHttp2Client.hpp
+++ b/src/http2/MyTrafficHttp2Client.hpp
@@ -57,6 +57,7 @@ namespace model
class Configuration;
class GlobalVariable;
class FileManager;
+class SocketManager;
class AdminData;
}
diff --git a/src/http2/MyTrafficHttp2Server.cpp b/src/http2/MyTrafficHttp2Server.cpp
index a564558..f9a12c9 100644
--- a/src/http2/MyTrafficHttp2Server.cpp
+++ b/src/http2/MyTrafficHttp2Server.cpp
@@ -50,6 +50,7 @@ SOFTWARE.
#include
#include
#include
+#include
#include
namespace h2agent
diff --git a/src/http2/MyTrafficHttp2Server.hpp b/src/http2/MyTrafficHttp2Server.hpp
index d291289..8cc46ba 100644
--- a/src/http2/MyTrafficHttp2Server.hpp
+++ b/src/http2/MyTrafficHttp2Server.hpp
@@ -59,6 +59,7 @@ class MockClientData;
class Configuration;
class GlobalVariable;
class FileManager;
+class SocketManager;
class AdminData;
}
diff --git a/src/jsonSchema/AdminSchemas.hpp b/src/jsonSchema/AdminSchemas.hpp
index a197bbd..5a4b369 100644
--- a/src/jsonSchema/AdminSchemas.hpp
+++ b/src/jsonSchema/AdminSchemas.hpp
@@ -235,7 +235,7 @@ const nlohmann::json server_provision = R"(
},
"target": {
"type": "string",
- "pattern": "^response\\.body\\.(string$|hexstring$)|^response\\.body\\.json\\.(object$|object\\..+|jsonstring$|jsonstring\\..+|string$|string\\..+|integer$|integer\\..+|unsigned$|unsigned\\..+|float$|float\\..+|boolean$|boolean\\..+)|^response\\.(header\\..+|statusCode|delayMs)$|^(var|globalVar|serverEvent)\\..+|^outState(\\.(POST|GET|PUT|DELETE|HEAD)(\\..+)?)?$|^txtFile\\..+|^binFile\\..+|^break$"
+ "pattern": "^response\\.body\\.(string$|hexstring$)|^response\\.body\\.json\\.(object$|object\\..+|jsonstring$|jsonstring\\..+|string$|string\\..+|integer$|integer\\..+|unsigned$|unsigned\\..+|float$|float\\..+|boolean$|boolean\\..+)|^response\\.(header\\..+|statusCode|delayMs)$|^(var|globalVar|serverEvent)\\..+|^outState(\\.(POST|GET|PUT|DELETE|HEAD)(\\..+)?)?$|^txtFile\\..+|^binFile\\..+|^udpSocket\\..+|^break$"
}
},
"additionalProperties" : {
@@ -426,7 +426,7 @@ const nlohmann::json client_provision = R"(
},
"target": {
"type": "string",
- "pattern": "^request\\.body\\.(string$|hexstring$)|^request\\.body\\.json\\.(object$|object\\..+|jsonstring$|jsonstring\\..+|string$|string\\..+|integer$|integer\\..+|unsigned$|unsigned\\..+|float$|float\\..+|boolean$|boolean\\..+)|^request\\.(header\\..+|delayMs|timeoutMs)$|^(var|globalVar|clientEvent)\\..+|^outState$|^txtFile\\..+|^binFile\\..+"
+ "pattern": "^request\\.body\\.(string$|hexstring$)|^request\\.body\\.json\\.(object$|object\\..+|jsonstring$|jsonstring\\..+|string$|string\\..+|integer$|integer\\..+|unsigned$|unsigned\\..+|float$|float\\..+|boolean$|boolean\\..+)|^request\\.(header\\..+|delayMs|timeoutMs)$|^(var|globalVar|clientEvent)\\..+|^outState$|^txtFile\\..+|^binFile\\..+|^udpSocket\\..+"
}
},
"additionalProperties" : {
@@ -449,7 +449,7 @@ const nlohmann::json client_provision = R"(
},
"target": {
"type": "string",
- "pattern": "^(var|globalVar|clientEvent)\\..+|^outState$|^txtFile\\..+|^binFile\\..+|^break$"
+ "pattern": "^(var|globalVar|clientEvent)\\..+|^outState$|^txtFile\\..+|^binFile\\..+|^udpSocket\\..+|^break$"
}
},
"additionalProperties" : {
diff --git a/src/main.cpp b/src/main.cpp
index 73de06c..a8f178a 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -52,6 +52,7 @@ SOFTWARE.
#include
#include
#include
+#include
#include
#include
#include
@@ -81,6 +82,7 @@ boost::asio::io_service *myTimersIoService = nullptr;
h2agent::model::Configuration* myConfiguration = nullptr;
h2agent::model::GlobalVariable* myGlobalVariable = nullptr;
h2agent::model::FileManager* myFileManager = nullptr;
+h2agent::model::SocketManager* mySocketManager = nullptr;
h2agent::model::MockServerData* myMockServerData = nullptr;
h2agent::model::MockClientData* myMockClientData = nullptr;
ert::metrics::Metrics *myMetrics = nullptr;
@@ -190,6 +192,9 @@ void stopAgent()
delete(myFileManager);
myFileManager = nullptr;
+ delete(mySocketManager);
+ mySocketManager = nullptr;
+
delete(myGlobalVariable);
myGlobalVariable = nullptr;
@@ -461,6 +466,7 @@ int main(int argc, char* argv[])
myConfiguration = new h2agent::model::Configuration();
myGlobalVariable = new h2agent::model::GlobalVariable();
myFileManager = new h2agent::model::FileManager(myTimersIoService);
+ mySocketManager = new h2agent::model::SocketManager(myTimersIoService);
// Parse command-line ///////////////////////////////////////////////////////////////////////////////////////
bool ipv6 = false; // ipv4 by default
@@ -810,6 +816,9 @@ int main(int argc, char* argv[])
// FileManager/SafeFile metrics
myFileManager->enableMetrics(myMetrics);
+ // SocketManager/SafeSocket metrics
+ mySocketManager->enableMetrics(myMetrics);
+
// Admin server
myAdminHttp2Server = new h2agent::http2::MyAdminHttp2Server(ADMIN_SERVER_WORKER_THREADS);
myAdminHttp2Server->enableMetrics(myMetrics);
@@ -818,6 +827,7 @@ int main(int argc, char* argv[])
myAdminHttp2Server->setConfiguration(myConfiguration);
myAdminHttp2Server->setGlobalVariable(myGlobalVariable);
myAdminHttp2Server->setFileManager(myFileManager);
+ myAdminHttp2Server->setSocketManager(mySocketManager);
myAdminHttp2Server->setMetricsData(myMetrics, responseDelaySecondsHistogramBucketBoundaries, messageSizeBytesHistogramBucketBoundaries); // for client connection class
// Timers thread:
diff --git a/src/model/AdminClientProvision.cpp b/src/model/AdminClientProvision.cpp
index 0e40082..88d0644 100644
--- a/src/model/AdminClientProvision.cpp
+++ b/src/model/AdminClientProvision.cpp
@@ -55,6 +55,7 @@ SOFTWARE.
#include
#include
#include
+#include
#include
@@ -161,7 +162,7 @@ void AdminClientProvision::transform( std::string &requestMethod,
}
}
- // TARGETS: ResponseBodyString, ResponseBodyHexString, ResponseBodyJson_String, ResponseBodyJson_Integer, ResponseBodyJson_Unsigned, ResponseBodyJson_Float, ResponseBodyJson_Boolean, ResponseBodyJson_Object, ResponseBodyJson_JsonString, ResponseHeader, ResponseStatusCode, ResponseDelayMs, TVar, TGVar, OutState, TTxtFile, TBinFile, ServerEventToPurge, Break
+ // TARGETS: ResponseBodyString, ResponseBodyHexString, ResponseBodyJson_String, ResponseBodyJson_Integer, ResponseBodyJson_Unsigned, ResponseBodyJson_Float, ResponseBodyJson_Boolean, ResponseBodyJson_Object, ResponseBodyJson_JsonString, ResponseHeader, ResponseStatusCode, ResponseDelayMs, TVar, TGVar, OutState, TTxtFile, TBinFile, UDPSocket, ServerEventToPurge, Break
if (!processTargets(transformation, sourceVault, variables, matches, eraser, hasFilter, responseStatusCode, responseBodyJson, responseBody, responseHeaders, responseDelayMs, outState, outStateMethod, outStateUri, breakCondition)) {
LOGDEBUG(ert::tracing::Logger::debug("Transformation item skipped on target", ERT_FILE_LOCATION));
continue;
diff --git a/src/model/AdminClientProvision.hpp b/src/model/AdminClientProvision.hpp
index 7fd3bb0..6bf84ea 100644
--- a/src/model/AdminClientProvision.hpp
+++ b/src/model/AdminClientProvision.hpp
@@ -61,6 +61,7 @@ class MockServerData;
class Configuration;
class GlobalVariable;
class FileManager;
+class SocketManager;
class AdminClientProvision
@@ -93,6 +94,7 @@ class AdminClientProvision
model::Configuration *configuration_{}; // just in case it is used
model::GlobalVariable *global_variable_{}; // just in case it is used
model::FileManager *file_manager_{}; // just in case it is used
+ model::SocketManager *socket_manager_{}; // just in case it is used
void loadTransformation(std::vector> &transformationsVector, const nlohmann::json &j);
@@ -238,6 +240,14 @@ class AdminClientProvision
file_manager_ = p;
}
+ /**
+ * Sets the socket manager reference,
+ * just in case it is used in event target
+ */
+ void setSocketManager(model::SocketManager *p) {
+ socket_manager_ = p;
+ }
+
// getters:
/**
diff --git a/src/model/AdminClientProvisionData.cpp b/src/model/AdminClientProvisionData.cpp
index 75e4b8e..541c12f 100644
--- a/src/model/AdminClientProvisionData.cpp
+++ b/src/model/AdminClientProvisionData.cpp
@@ -91,6 +91,7 @@ AdminClientProvisionData::LoadResult AdminClientProvisionData::loadSingle(const
provision->setConfiguration(cr.ConfigurationPtr);
provision->setGlobalVariable(cr.GlobalVariablePtr);
provision->setFileManager(cr.FileManagerPtr);
+ provision->setSocketManager(cr.SocketManagerPtr);
provision->setMockClientData(cr.MockClientDataPtr);
provision->setMockServerData(cr.MockServerDataPtr);
diff --git a/src/model/AdminServerProvision.cpp b/src/model/AdminServerProvision.cpp
index a8b9474..0e7150c 100644
--- a/src/model/AdminServerProvision.cpp
+++ b/src/model/AdminServerProvision.cpp
@@ -54,6 +54,7 @@ SOFTWARE.
#include
#include
#include
+#include
#include
@@ -824,6 +825,25 @@ bool AdminServerProvision::processTargets(std::shared_ptr transf
file_manager_->write(target/*path*/, targetS/*data*/, false/*binary*/, (longTerm ? configuration_->getLongTermFilesCloseDelayUsecs():configuration_->getShortTermFilesCloseDelayUsecs()));
}
}
+ else if (transformation->getTargetType() == Transformation::TargetType::UDPSocket) {
+ // extraction
+ targetS = sourceVault.getString(success);
+ if (!success) return false;
+
+ // assignments
+ // Possible delay provided in 'target': .
+ std::string path = target;
+ size_t lastDotPos = target.find_last_of(".");
+ unsigned int delayMs = atoi(target.substr(lastDotPos + 1).c_str());
+ path = target.substr(0, lastDotPos);
+
+ LOGDEBUG(
+ std::string msg = ert::tracing::Logger::asString("UDPSocket '%s' target, delayed %u milliseconds, in transformation item", path.c_str(), delayMs);
+ ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
+ );
+
+ socket_manager_->write(path, targetS/*data*/, delayMs * 1000 /* usecs */);
+ }
else if (transformation->getTargetType() == Transformation::TargetType::ServerEventToPurge) {
if (!eraser) {
LOGDEBUG(ert::tracing::Logger::debug("'ServerEventToPurge' target type only works with 'eraser' source type. This transformation will be ignored.", ERT_FILE_LOCATION));
@@ -1000,7 +1020,7 @@ void AdminServerProvision::transform( const std::string &requestUri,
}
}
- // TARGETS: ResponseBodyString, ResponseBodyHexString, ResponseBodyJson_String, ResponseBodyJson_Integer, ResponseBodyJson_Unsigned, ResponseBodyJson_Float, ResponseBodyJson_Boolean, ResponseBodyJson_Object, ResponseBodyJson_JsonString, ResponseHeader, ResponseStatusCode, ResponseDelayMs, TVar, TGVar, OutState, TTxtFile, TBinFile, ServerEventToPurge, Break
+ // TARGETS: ResponseBodyString, ResponseBodyHexString, ResponseBodyJson_String, ResponseBodyJson_Integer, ResponseBodyJson_Unsigned, ResponseBodyJson_Float, ResponseBodyJson_Boolean, ResponseBodyJson_Object, ResponseBodyJson_JsonString, ResponseHeader, ResponseStatusCode, ResponseDelayMs, TVar, TGVar, OutState, TTxtFile, TBinFile, UDPSocket, ServerEventToPurge, Break
if (!processTargets(transformation, sourceVault, variables, matches, eraser, hasFilter, responseStatusCode, responseBodyJson, responseBody, responseHeaders, responseDelayMs, outState, outStateMethod, outStateUri, breakCondition)) {
LOGDEBUG(ert::tracing::Logger::debug("Transformation item skipped on target", ERT_FILE_LOCATION));
continue;
diff --git a/src/model/AdminServerProvision.hpp b/src/model/AdminServerProvision.hpp
index 6aff3a3..e79c1eb 100644
--- a/src/model/AdminServerProvision.hpp
+++ b/src/model/AdminServerProvision.hpp
@@ -64,6 +64,7 @@ class MockClientData;
class Configuration;
class GlobalVariable;
class FileManager;
+class SocketManager;
class AdminServerProvision
@@ -102,6 +103,7 @@ class AdminServerProvision
model::Configuration *configuration_{}; // just in case it is used
model::GlobalVariable *global_variable_{}; // just in case it is used
model::FileManager *file_manager_{}; // just in case it is used
+ model::SocketManager *socket_manager_{}; // just in case it is used
void loadTransformation(const nlohmann::json &j);
@@ -239,6 +241,14 @@ class AdminServerProvision
file_manager_ = p;
}
+ /**
+ * Sets the socket manager reference,
+ * just in case it is used in event target
+ */
+ void setSocketManager(model::SocketManager *p) {
+ socket_manager_ = p;
+ }
+
// getters:
/**
diff --git a/src/model/AdminServerProvisionData.cpp b/src/model/AdminServerProvisionData.cpp
index be57028..76ca53c 100644
--- a/src/model/AdminServerProvisionData.cpp
+++ b/src/model/AdminServerProvisionData.cpp
@@ -105,6 +105,7 @@ AdminServerProvisionData::LoadResult AdminServerProvisionData::loadSingle(const
provision->setConfiguration(cr.ConfigurationPtr);
provision->setGlobalVariable(cr.GlobalVariablePtr);
provision->setFileManager(cr.FileManagerPtr);
+ provision->setSocketManager(cr.SocketManagerPtr);
provision->setMockServerData(cr.MockServerDataPtr);
provision->setMockClientData(cr.MockClientDataPtr);
diff --git a/src/model/CMakeLists.txt b/src/model/CMakeLists.txt
index fe45f0c..43c93e5 100644
--- a/src/model/CMakeLists.txt
+++ b/src/model/CMakeLists.txt
@@ -24,6 +24,8 @@ add_library (h2agent-model
${CMAKE_CURRENT_LIST_DIR}/Configuration.cpp
${CMAKE_CURRENT_LIST_DIR}/FileManager.cpp
${CMAKE_CURRENT_LIST_DIR}/SafeFile.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/SocketManager.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/SafeSocket.cpp
${CMAKE_CURRENT_LIST_DIR}/DataPart.cpp
)
diff --git a/src/model/FileManager.cpp b/src/model/FileManager.cpp
index 2175cfe..104de83 100644
--- a/src/model/FileManager.cpp
+++ b/src/model/FileManager.cpp
@@ -184,7 +184,7 @@ nlohmann::json FileManager::getJson() const {
std::string FileManager::asJsonString() const {
- return ((size() != 0) ? getJson().dump() : "[]"); // server data is shown as an array
+ return ((size() != 0) ? getJson().dump() : "[]");
}
diff --git a/src/model/SafeFile.hpp b/src/model/SafeFile.hpp
index 1de6e2d..5ea0a1a 100644
--- a/src/model/SafeFile.hpp
+++ b/src/model/SafeFile.hpp
@@ -89,7 +89,6 @@ class SafeFile {
* operations with the intention to reduce overhead in some scenarios. By default
* it is not used (if not provided in constructor), so delay is not performed
* regardless the close delay configured.
- * about I/O operations. It may be 'nullptr' if no metrics are enabled.
* @param mode open mode. By default, text files and append is selected. You
* could anyway add other flags, for example for binary dumps: std::ios::binary
*/
diff --git a/src/model/SafeSocket.cpp b/src/model/SafeSocket.cpp
new file mode 100644
index 0000000..051026a
--- /dev/null
+++ b/src/model/SafeSocket.cpp
@@ -0,0 +1,132 @@
+/*
+ ___________________________________________
+| _ ___ _ |
+| | | |__ \ | | |
+| | |__ ) |__ _ __ _ ___ _ __ | |_ |
+| | '_ \ / // _` |/ _` |/ _ \ '_ \| __| | HTTP/2 AGENT FOR MOCK TESTING
+| | | | |/ /| (_| | (_| | __/ | | | |_ | Version 0.0.z
+| |_| |_|____\__,_|\__, |\___|_| |_|\__| | https://github.com/testillano/h2agent
+| __/ | |
+| |___/ |
+|___________________________________________|
+
+Licensed under the MIT License .
+SPDX-License-Identifier: MIT
+Copyright (c) 2021 Eduardo Ramos
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation sockets (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+
+
+namespace h2agent
+{
+namespace model
+{
+
+
+SafeSocket::SafeSocket (SocketManager *socketManager, const std::string& path, boost::asio::io_service *timersIoService):
+ path_(path),
+ io_service_(timersIoService),
+ socket_manager_(socketManager)
+{
+ open();
+}
+
+void SafeSocket::delayedWrite(unsigned int writeDelayUs, const std::string &data) {
+ // metrics
+ socket_manager_->incrementObservedDelayedWriteOperationCounter();
+
+ //if (!io_service_) return; // protection
+ auto timer = std::make_shared(*io_service_, boost::posix_time::microseconds(writeDelayUs));
+ timer->expires_from_now(boost::posix_time::microseconds(writeDelayUs));
+ timer->async_wait([this, timer, data] (const boost::system::error_code& e) {
+ if( e ) return; // probably, we were cancelled (boost::asio::error::operation_aborted)
+ write(data);
+ });
+}
+
+bool SafeSocket::open() {
+
+ socket_ = socket(AF_UNIX, SOCK_DGRAM, 0);
+ if (socket_ >= 0) {
+ // metrics
+ socket_manager_->incrementObservedOpenOperationCounter();
+
+ memset(&server_addr_, 0, sizeof(struct sockaddr_un));
+ server_addr_.sun_family = AF_UNIX;
+ strcpy(server_addr_.sun_path, path_.c_str());
+ }
+ else {
+ LOGWARNING(ert::tracing::Logger::warning(ert::tracing::Logger::asString("Failed to open '%s'", path_.c_str()), ERT_FILE_LOCATION));
+ // metrics
+ socket_manager_->incrementObservedErrorOpenOperationCounter();
+ return false;
+ }
+
+ return true;
+}
+
+nlohmann::json SafeSocket::getJson() const {
+ nlohmann::json result;
+
+ result["path"] = path_;
+ result["socket"] = socket_;
+
+ return result;
+}
+
+void SafeSocket::write (const std::string& data, unsigned int writeDelayUs) {
+
+ // trace data & delay
+ LOGDEBUG(
+ ert::tracing::Logger::debug(ert::tracing::Logger::asString("Data for write operation is: %s", data.c_str()), ERT_FILE_LOCATION);
+ if (writeDelayUs != 0) ert::tracing::Logger::debug(ert::tracing::Logger::asString("Delay for write operation is: %lu", writeDelayUs), ERT_FILE_LOCATION);
+ );
+
+ // Write socket:
+ //std::lock_guard lock(mutex_);
+
+ // metrics
+ socket_manager_->incrementObservedWriteOperationCounter();
+
+ // Close socket:
+ if (io_service_ && writeDelayUs != 0) {
+ delayedWrite(writeDelayUs, data);
+ }
+ else {
+ sendto(socket_, data.c_str(), data.length(), 0, (struct sockaddr*)&server_addr_, sizeof(struct sockaddr_un));
+ LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Data written into '%s'", path_.c_str()), ERT_FILE_LOCATION));
+
+ // metrics
+ socket_manager_->incrementObservedInstantWriteOperationCounter();
+ }
+}
+
+}
+}
+
diff --git a/src/model/SafeSocket.hpp b/src/model/SafeSocket.hpp
new file mode 100644
index 0000000..11d92d2
--- /dev/null
+++ b/src/model/SafeSocket.hpp
@@ -0,0 +1,113 @@
+/*
+ ___________________________________________
+| _ ___ _ |
+| | | |__ \ | | |
+| | |__ ) |__ _ __ _ ___ _ __ | |_ |
+| | '_ \ / // _` |/ _` |/ _ \ '_ \| __| | HTTP/2 AGENT FOR MOCK TESTING
+| | | | |/ /| (_| | (_| | __/ | | | |_ | Version 0.0.z
+| |_| |_|____\__,_|\__, |\___|_| |_|\__| | https://github.com/testillano/h2agent
+| __/ | |
+| |___/ |
+|___________________________________________|
+
+Licensed under the MIT License .
+SPDX-License-Identifier: MIT
+Copyright (c) 2021 Eduardo Ramos
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation sockets (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#pragma once
+
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+#include
+
+
+namespace h2agent
+{
+namespace model
+{
+class SocketManager;
+
+/**
+ * This class allows safe writting of udp sockets.
+ * Write procedure could be planned with a certain delay
+ */
+class SafeSocket {
+
+ std::string path_;
+ int socket_;
+ struct sockaddr_un server_addr_;
+
+ boost::asio::io_service *io_service_{};
+
+ SocketManager *socket_manager_{};
+
+ void delayedWrite(unsigned int writeDelayUs, const std::string &data);
+
+public:
+
+ /**
+ * Constructor
+ *
+ * @param socketManager parent reference to socket manager.
+ * @param path socket path to write. It could be relative (to execution path) or absolute.
+ * @param timersIoService asio io service which will be used to delay write operations.
+ * By default it is not used (if not provided in constructor), so delay is not performed
+ * regardless the write delay configured.
+ */
+ SafeSocket (SocketManager *socketManager,
+ const std::string& path,
+ boost::asio::io_service *timersIoService = nullptr);
+
+ ~SafeSocket() {;}
+
+ /**
+ * Open the socket for writting
+ *
+ * @return Boolean about success operation.
+ */
+ bool open();
+
+ /**
+ * Json representation of the class instance
+ */
+ nlohmann::json getJson() const;
+
+ /**
+ * Write data to the socket.
+ * Write could be delayed.
+ *
+ * @param data data to write
+ * @param writeDelayUs delay for write operation. By default no delay is configured.
+ */
+ void write(const std::string& data, unsigned int writeDelayUs = 0);
+};
+
+}
+}
+
diff --git a/src/model/SocketManager.cpp b/src/model/SocketManager.cpp
new file mode 100644
index 0000000..81266d9
--- /dev/null
+++ b/src/model/SocketManager.cpp
@@ -0,0 +1,128 @@
+/*
+ ___________________________________________
+| _ ___ _ |
+| | | |__ \ | | |
+| | |__ ) |__ _ __ _ ___ _ __ | |_ |
+| | '_ \ / // _` |/ _` |/ _ \ '_ \| __| | HTTP/2 AGENT FOR MOCK TESTING
+| | | | |/ /| (_| | (_| | __/ | | | |_ | Version 0.0.z
+| |_| |_|____\__,_|\__, |\___|_| |_|\__| | https://github.com/testillano/h2agent
+| __/ | |
+| |___/ |
+|___________________________________________|
+
+Licensed under the MIT License .
+SPDX-License-Identifier: MIT
+Copyright (c) 2021 Eduardo Ramos
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#include
+
+#include
+
+#include
+
+namespace h2agent
+{
+namespace model
+{
+
+
+void SocketManager::enableMetrics(ert::metrics::Metrics *metrics) {
+
+ metrics_ = metrics;
+
+ if (metrics_) {
+ ert::metrics::counter_family_ref_t cf = metrics->addCounterFamily("UDPSocket_observed_operations_total", "H2agent udp socket operations");
+ observed_open_operation_counter_ = &(cf.Add({{"operation", "open"}}));
+ observed_write_operation_counter_ = &(cf.Add({{"operation", "write"}}));
+ observed_delayed_write_operation_counter_ = &(cf.Add({{"operation", "delayedWrite"}}));
+ observed_instant_write_operation_counter_ = &(cf.Add({{"operation", "instantWrite"}}));
+ observed_error_open_operation_counter_ = &(cf.Add({{"success", "false"}, {"operation", "open"}}));
+ }
+}
+
+void SocketManager::incrementObservedOpenOperationCounter() {
+ if (metrics_) observed_open_operation_counter_->Increment();
+}
+
+void SocketManager::incrementObservedWriteOperationCounter() {
+ if (metrics_) observed_write_operation_counter_->Increment();
+}
+
+void SocketManager::incrementObservedDelayedWriteOperationCounter() {
+ if (metrics_) observed_delayed_write_operation_counter_->Increment();
+}
+
+void SocketManager::incrementObservedInstantWriteOperationCounter() {
+ if (metrics_) observed_instant_write_operation_counter_->Increment();
+}
+
+void SocketManager::incrementObservedErrorOpenOperationCounter() {
+ if (metrics_) observed_error_open_operation_counter_->Increment();
+}
+
+void SocketManager::write(const std::string &path, const std::string &data, unsigned int writeDelayUs) {
+
+ std::shared_ptr safeSocket;
+
+ auto it = get(path);
+ if (it != end()) {
+ safeSocket = it->second;
+ }
+ else {
+ safeSocket = std::make_shared(this, path, io_service_);
+ add(path, safeSocket);
+ }
+
+ safeSocket->write(data, writeDelayUs);
+}
+
+bool SocketManager::clear()
+{
+ bool result = (map_.size() > 0); // something deleted
+
+ map_.clear(); // shared_ptr dereferenced too
+
+ return result;
+}
+
+nlohmann::json SocketManager::getJson() const {
+
+ nlohmann::json result;
+
+ read_guard_t guard(rw_mutex_);
+
+ for (auto it = begin(); it != end(); it++) {
+ result.push_back(it->second->getJson());
+ };
+
+ return result;
+}
+
+std::string SocketManager::asJsonString() const {
+
+ return ((size() != 0) ? getJson().dump() : "[]");
+}
+
+
+}
+}
+
diff --git a/src/model/SocketManager.hpp b/src/model/SocketManager.hpp
new file mode 100644
index 0000000..ca5c9de
--- /dev/null
+++ b/src/model/SocketManager.hpp
@@ -0,0 +1,152 @@
+/*
+ ___________________________________________
+| _ ___ _ |
+| | | |__ \ | | |
+| | |__ ) |__ _ __ _ ___ _ __ | |_ |
+| | '_ \ / // _` |/ _` |/ _ \ '_ \| __| | HTTP/2 AGENT FOR MOCK TESTING
+| | | | |/ /| (_| | (_| | __/ | | | |_ | Version 0.0.z
+| |_| |_|____\__,_|\__, |\___|_| |_|\__| | https://github.com/testillano/h2agent
+| __/ | |
+| |___/ |
+|___________________________________________|
+
+Licensed under the MIT License .
+SPDX-License-Identifier: MIT
+Copyright (c) 2021 Eduardo Ramos
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#pragma once
+
+#include
+
+#include
+
+#include
+#include
+
+
+namespace ert
+{
+namespace metrics
+{
+class Metrics;
+}
+}
+
+namespace h2agent
+{
+namespace model
+{
+
+/**
+ * This class stores a list of safe files.
+ */
+class SocketManager : public Map>
+{
+ mutable mutex_t rw_mutex_{};
+ boost::asio::io_service *io_service_{};
+
+ // metrics (will be passed to SafeSocket):
+ ert::metrics::Metrics *metrics_{};
+
+ ert::metrics::counter_t *observed_open_operation_counter_{};
+ ert::metrics::counter_t *observed_write_operation_counter_{};
+ ert::metrics::counter_t *observed_delayed_write_operation_counter_{};
+ ert::metrics::counter_t *observed_instant_write_operation_counter_{};
+ ert::metrics::counter_t *observed_error_open_operation_counter_{};
+
+public:
+ /**
+ * Socket manager class
+ *
+ * @param timersIoService timers IO service needed to schedule delayed write operations.
+ * If you never schedule write operations (@see write) it may be 'nullptr'.
+ * @param metrics underlaying reference for SafeSocket in order to compute prometheus metrics
+ * about I/O operations. It may be 'nullptr' if no metrics are enabled.
+ *
+ * @see SafeSocket
+ */
+ SocketManager(boost::asio::io_service *timersIoService = nullptr, ert::metrics::Metrics *metrics = nullptr) : io_service_(timersIoService), metrics_(metrics) {;}
+ ~SocketManager() = default;
+
+ /**
+ * Set metrics reference
+ *
+ * @param metrics Optional metrics object to compute counters
+ */
+ void enableMetrics(ert::metrics::Metrics *metrics);
+
+ /** incrementObservedOpenOperationCounter */
+ void incrementObservedOpenOperationCounter();
+
+ /** incrementObservedWriteOperationCounter */
+ void incrementObservedWriteOperationCounter();
+
+ /** incrementObservedDelayedWriteOperationCounter */
+ void incrementObservedDelayedWriteOperationCounter();
+
+ /** incrementObservedInstantWriteOperationCounter */
+ void incrementObservedInstantWriteOperationCounter();
+
+ /** incrementObservedErrorOpenOperationCounter */
+ void incrementObservedErrorOpenOperationCounter();
+
+ /**
+ * Write socket
+ *
+ * @param path path file to write. Can be relative (to execution directory) or absolute.
+ * @param data data string to write.
+ * @param writeDelayUs delay for write operation.
+ * Zero value means that no planned write is scheduled, so the socket is written instantly.
+ */
+ void write(const std::string &path, const std::string &data, unsigned int writeDelayUs);
+
+ /** Clears list
+ *
+ * @return Boolean about success of operation (something removed, nothing removed: already empty)
+ */
+ bool clear();
+
+ /**
+ * Builds json document for class configuration
+ *
+ * @return Json object
+ */
+ nlohmann::json getConfigurationJson() const;
+
+ /**
+ * Builds json document for class information
+ *
+ * @return Json object
+ */
+ nlohmann::json getJson() const;
+
+ /**
+ * Json string representation for class information (json object)
+ *
+ * @return Json string representation ('[]' for empty object).
+ */
+ std::string asJsonString() const;
+};
+
+}
+}
+
diff --git a/src/model/Transformation.cpp b/src/model/Transformation.cpp
index 7ffd2a9..a062f0c 100644
--- a/src/model/Transformation.cpp
+++ b/src/model/Transformation.cpp
@@ -333,7 +333,7 @@ bool Transformation::load(const nlohmann::json &j) {
// return false;
//}
- // TARGET (enum TargetType { ResponseBodyString = 0, ResponseBodyHexString, ResponseBodyJson_String, ResponseBodyJson_Integer, ResponseBodyJson_Unsigned, ResponseBodyJson_Float, ResponseBodyJson_Boolean, ResponseBodyJson_Object, ResponseBodyJson_JsonString, ResponseHeader, ResponseStatusCode, ResponseDelayMs, TVar, TGVar, OutState, TTxtFile, TBinFile, ServerEventToPurge, Break };)
+ // TARGET (enum TargetType { ResponseBodyString = 0, ResponseBodyHexString, ResponseBodyJson_String, ResponseBodyJson_Integer, ResponseBodyJson_Unsigned, ResponseBodyJson_Float, ResponseBodyJson_Boolean, ResponseBodyJson_Object, ResponseBodyJson_JsonString, ResponseHeader, ResponseStatusCode, ResponseDelayMs, TVar, TGVar, OutState, TTxtFile, TBinFile, UDPSocket, ServerEventToPurge, Break };)
target_ = ""; // empty by default (-), as many cases are only work modes and no parameters(+) are included in their transformation configuration
target2_ = ""; // same
@@ -364,6 +364,7 @@ bool Transformation::load(const nlohmann::json &j) {
// + outState.`[POST|GET|PUT|DELETE|HEAD][.]` *[string (or number as string)]*: next processing state for specific method (virtual server data will be created if needed: this way we could modify the flow for other methods different than the one which is managing the current provision). This target **admits variables substitution** in the `uri` part.
// + txtFile.`` *[string]*: dumps source (as string) over text file with the path provided.
// + binFile.`` *[string]*: dumps source (as string) over binary file with the path provided.
+ // + udpSocket.`` *[string]*: sends source (as string) towards the UDP unix socket with the path provided.
// + serverEvent.``: this target is always used in conjunction with `eraser`.
// - break *[string]*: when non-empty string is transferred, the transformations list is interrupted. Empty string (or undefined source) ignores the action.
//
@@ -406,6 +407,9 @@ bool Transformation::load(const nlohmann::json &j) {
static std::regex requestBodyJson_ObjectNode("^request.body.json.object.(.*)", std::regex::optimize);
static std::regex requestBodyJson_JsonStringNode("^request.body.json.jsonstring.(.*)", std::regex::optimize);
+ // Only target
+ static std::regex udpSocket("^udpSocket.(.*)", std::regex::optimize);
+
// no need to try (controlled regex)
//try {
// SERVER_MODE
@@ -558,6 +562,10 @@ bool Transformation::load(const nlohmann::json &j) {
target_ = matches.str(1);
target_type_ = TargetType::TBinFile;
}
+ else if (std::regex_match(targetSpec, matches, udpSocket)) { // path file
+ target_ = matches.str(1);
+ target_type_ = TargetType::UDPSocket;
+ }
else if (std::regex_match(targetSpec, matches, serverEvent)) { // value content
target_ = matches.str(1); // i.e. requestMethod=GET&requestUri=/app/v1/foo/bar%3Fid%3D5%26name%3Dtest&eventNumber=3
target_type_ = TargetType::ServerEventToPurge;
@@ -650,6 +658,9 @@ std::string Transformation::asString() const {
else if (target_type_ == TargetType::TTxtFile || target_type_ == TargetType::TBinFile) {
ss << " (path file)";
}
+ else if (target_type_ == TargetType::UDPSocket) {
+ ss << " ([.])";
+ }
if (!target_patterns_.empty()) {
ss << " | target variables:";
diff --git a/src/model/Transformation.hpp b/src/model/Transformation.hpp
index 7121794..be27f37 100644
--- a/src/model/Transformation.hpp
+++ b/src/model/Transformation.hpp
@@ -67,10 +67,10 @@ class Transformation
}
// Target type
- enum TargetType { ResponseBodyString = 0, ResponseBodyHexString, ResponseBodyJson_String, ResponseBodyJson_Integer, ResponseBodyJson_Unsigned, ResponseBodyJson_Float, ResponseBodyJson_Boolean, ResponseBodyJson_Object, ResponseBodyJson_JsonString, ResponseHeader, ResponseStatusCode, ResponseDelayMs, TVar, TGVar, OutState, TTxtFile, TBinFile, ServerEventToPurge, Break, RequestBodyString, RequestBodyHexString, RequestBodyJson_String, RequestBodyJson_Integer, RequestBodyJson_Unsigned, RequestBodyJson_Float, RequestBodyJson_Boolean, RequestBodyJson_Object, RequestBodyJson_JsonString };
+ enum TargetType { ResponseBodyString = 0, ResponseBodyHexString, ResponseBodyJson_String, ResponseBodyJson_Integer, ResponseBodyJson_Unsigned, ResponseBodyJson_Float, ResponseBodyJson_Boolean, ResponseBodyJson_Object, ResponseBodyJson_JsonString, ResponseHeader, ResponseStatusCode, ResponseDelayMs, TVar, TGVar, OutState, TTxtFile, TBinFile, UDPSocket, ServerEventToPurge, Break, RequestBodyString, RequestBodyHexString, RequestBodyJson_String, RequestBodyJson_Integer, RequestBodyJson_Unsigned, RequestBodyJson_Float, RequestBodyJson_Boolean, RequestBodyJson_Object, RequestBodyJson_JsonString };
const char* TargetTypeAsText(const TargetType & type) const
{
- static const char* text [] = { "ResponseBodyString", "ResponseBodyHexString", "ResponseBodyJson_String", "ResponseBodyJson_Integer", "ResponseBodyJson_Unsigned", "ResponseBodyJson_Float", "ResponseBodyJson_Boolean", "ResponseBodyJson_Object", "ResponseBodyJson_JsonString", "ResponseHeader", "ResponseStatusCode", "ResponseDelayMs", "TVar", "TGVar", "OutState", "TTxtFile", "TBinFile", "ServerEventToPurge", "Break", "RequestBodyString", "RequestBodyHexString", "RequestBodyJson_String", "RequestBodyJson_Integer", "RequestBodyJson_Unsigned", "RequestBodyJson_Float", "RequestBodyJson_Boolean", "RequestBodyJson_Object", "RequestBodyJson_JsonString" };
+ static const char* text [] = { "ResponseBodyString", "ResponseBodyHexString", "ResponseBodyJson_String", "ResponseBodyJson_Integer", "ResponseBodyJson_Unsigned", "ResponseBodyJson_Float", "ResponseBodyJson_Boolean", "ResponseBodyJson_Object", "ResponseBodyJson_JsonString", "ResponseHeader", "ResponseStatusCode", "ResponseDelayMs", "TVar", "TGVar", "OutState", "TTxtFile", "TBinFile", "UDPSocket", "ServerEventToPurge", "Break", "RequestBodyString", "RequestBodyHexString", "RequestBodyJson_String", "RequestBodyJson_Integer", "RequestBodyJson_Unsigned", "RequestBodyJson_Float", "RequestBodyJson_Boolean", "RequestBodyJson_Object", "RequestBodyJson_JsonString" };
return text [type];
}
@@ -112,7 +112,7 @@ class Transformation
TargetType target_type_{};
std::string target_{}; // ResponseBodyJson_String/Integer/Unsigned/Float/Boolean/Object/JsonString(empty: whole, path: node),
- // ResponseHeader, TVar, TGVar, OutState (foreign method part), TTxtFile(path), TBinFile (path)
+ // ResponseHeader, TVar, TGVar, OutState (foreign method part), TTxtFile(path), TBinFile (path), UDPSocket (path[.delayMs])
std::vector target_tokenized_{}; // ServerEventToPurge
std::string target2_{}; // OutState (foreign uri part)
diff --git a/src/model/common.hpp b/src/model/common.hpp
index be2a77b..959d44b 100644
--- a/src/model/common.hpp
+++ b/src/model/common.hpp
@@ -53,6 +53,7 @@ namespace model
class Configuration;
class GlobalVariable;
class FileManager;
+class SocketManager;
class MockServerData;
class MockClientData;
@@ -60,6 +61,7 @@ typedef struct {
Configuration *ConfigurationPtr;
GlobalVariable *GlobalVariablePtr;
FileManager *FileManagerPtr;
+ SocketManager *SocketManagerPtr;
MockServerData *MockServerDataPtr;
MockClientData *MockClientDataPtr;
ert::metrics::Metrics *MetricsPtr;
diff --git a/tools/README.md b/tools/README.md
index 93ed3f0..87d704e 100644
--- a/tools/README.md
+++ b/tools/README.md
@@ -31,6 +31,10 @@
│ ├── CMakeLists.txt
│ ├── Makefile
│ └── main.cpp
+├── udp-server
+│ ├── CMakeLists.txt
+│ ├── Makefile
+│ └── main.cpp
├── play-grafana.sh
├── schemas.sh
├── ssl
@@ -50,6 +54,7 @@
* play-h2agent: examples and a guide through them (`play.sh`).
* matching-helper: c++ utility to test regular expressions as a configuration helper.
* arashpartow-helper: c++ utility to test Arash-Partow math expressions.
+* udp-server: c++ utility to test UDP messages written by `h2agent` by mean `UDPSocket` target.
* play-grafana.sh: prometheus server and grafana deployment to provide an `h2agent` metrics front-end.
* schemas.sh: shows all the schemas available (also requests schema if configured).
* ssl: utilities to create certificates and test the server with tls/ssl enabled.
diff --git a/tools/helpers.src b/tools/helpers.src
index c81c8ab..04f5fc3 100644
--- a/tools/helpers.src
+++ b/tools/helpers.src
@@ -90,6 +90,16 @@ files_configuration() {
fi
}
+udp_sockets() {
+ if [ "$1" = "-h" -o "$1" = "--help" ]
+ then
+ echo "Usage: udp_sockets [-h|--help]; Gets the udp sockets processed."
+ return 0
+ else
+ do_curl ${ADMIN_URL}/udp-sockets
+ fi
+}
+
configuration() {
if [ "$1" = "-h" -o "$1" = "--help" ]
then
@@ -473,6 +483,7 @@ snapshot() {
global_variable_schema && json > ${dir}/global-variable_schema.json
files && json > ${dir}/files.json
files_configuration && json > ${dir}/files-configuration.json
+ udp_sockets && json > ${dir}/udp-sockets.json
configuration && json > ${dir}/configuration.json
server_configuration && json > ${dir}/server-configuration.json
@@ -608,6 +619,7 @@ help() {
global_variable -h
files -h
files_configuration -h
+ udp_sockets -h
configuration -h
echo
echo "=== Traffic server ==="
diff --git a/tools/matching-helper/main.cpp b/tools/matching-helper/main.cpp
index a600319..9550569 100644
--- a/tools/matching-helper/main.cpp
+++ b/tools/matching-helper/main.cpp
@@ -99,9 +99,9 @@ int main(int argc, char* argv[])
progname = basename(argv[0]);
// Parse command-line ///////////////////////////////////////////////////////////////////////////////////////
- std::string regex;
- std::string test;
- std::string fmt;
+ std::string regex{};
+ std::string test{};
+ std::string fmt{};
std::string value;
diff --git a/tools/udp-server/CMakeLists.txt b/tools/udp-server/CMakeLists.txt
new file mode 100644
index 0000000..3031ed3
--- /dev/null
+++ b/tools/udp-server/CMakeLists.txt
@@ -0,0 +1,2 @@
+add_executable( udp-server main.cpp )
+target_link_libraries( udp-server PRIVATE ${CMAKE_EXE_LINKER_FLAGS} )
diff --git a/tools/udp-server/main.cpp b/tools/udp-server/main.cpp
new file mode 100644
index 0000000..a488eb1
--- /dev/null
+++ b/tools/udp-server/main.cpp
@@ -0,0 +1,196 @@
+/*
+ __________________________________________________________
+| |
+| _ |
+| | | |
+| _ _ __| |_ __ __ ___ ___ _ ____ _____ _ __ |
+| | | | |/ _` | '_ \ |__| / __|/ _ \ '__\ \ / / _ \ '__| | SERVER UDP UTILITY TO TEST h2agent UDP messages
+| | |_| | (_| | |_) | \__ \ __/ | \ V / __/ | | Version 0.0.z
+| \__,_|\__,_| .__/ |___/\___|_| \_/ \___|_| | https://github.com/testillano/h2agent (tools/udp-server)
+| | | |
+| |_| |
+|__________________________________________________________|
+
+Licensed under the MIT License .
+SPDX-License-Identifier: MIT
+Copyright (c) 2021 Eduardo Ramos
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#include // basename
+#include
+#include
+#include
+#include
+#include
+
+// Standard
+#include
+#include
+#include
+
+#define BUFFER_SIZE 256
+
+const char* progname;
+
+////////////////////////////
+// Command line functions //
+////////////////////////////
+
+void usage(int rc)
+{
+ auto& ss = (rc == 0) ? std::cout : std::cerr;
+
+ ss << "Usage: " << progname << " [options]\n\nOptions:\n\n"
+
+ << "--path \n"
+ << " UDP unix socket path.\n\n"
+
+ << "--print-each \n"
+ << " Print messages each specific amount (must be positive). Defaults to 1.\n\n"
+
+ << "[-h|--help]\n"
+ << " This help.\n\n"
+
+ << "Examples: " << '\n'
+ << " " << progname << " --path \"/tmp/my_unix_socket\"" << '\n'
+
+ << '\n';
+
+ exit(rc);
+}
+
+bool cmdOptionExists(char** begin, char** end, const std::string& option,
+ std::string& value)
+{
+ char** itr = std::find(begin, end, option);
+ bool exists = (itr != end);
+
+ if (exists && ++itr != end)
+ {
+ value = *itr;
+ }
+
+ return exists;
+}
+
+void sighndl(int signal)
+{
+ std::cout << "Signal received: " << signal << std::endl;
+ //close(sockfd);
+ //unlink(path.c_str());
+ exit(EXIT_FAILURE);
+}
+
+
+///////////////////
+// MAIN FUNCTION //
+///////////////////
+
+int main(int argc, char* argv[])
+{
+ progname = basename(argv[0]);
+
+ // Parse command-line ///////////////////////////////////////////////////////////////////////////////////////
+ std::string path{};
+ std::string printEach{};
+
+ std::string value;
+
+ if (cmdOptionExists(argv, argv + argc, "-h", value)
+ || cmdOptionExists(argv, argv + argc, "--help", value))
+ {
+ usage(EXIT_SUCCESS);
+ }
+
+ if (cmdOptionExists(argv, argv + argc, "--path", value))
+ {
+ path = value;
+ }
+
+ if (cmdOptionExists(argv, argv + argc, "--print-each", value))
+ {
+ printEach = value;
+ }
+
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ std::cout << '\n';
+
+ if (path.empty()) usage(EXIT_FAILURE);
+ int i_printEach = (printEach.empty() ? 1:atoi(printEach.c_str()));
+ if (i_printEach <= 0) usage(EXIT_FAILURE);
+
+ std::cout << "Path: " << path << '\n';
+ std::cout << "Print each: " << i_printEach << " message(s)\n";
+
+ int sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);
+ if (sockfd < 0) {
+ perror("Error creating UDP socket !");
+ exit(EXIT_FAILURE);
+ }
+
+ struct sockaddr_un serverAddr;
+ memset(&serverAddr, 0, sizeof(struct sockaddr_un));
+ serverAddr.sun_family = AF_UNIX;
+ strcpy(serverAddr.sun_path, path.c_str());
+
+ unlink(path.c_str()); // just in case
+
+ // Capture TERM/INT signals for graceful exit:
+ signal(SIGTERM, sighndl);
+ signal(SIGINT, sighndl);
+
+ if (bind(sockfd, (struct sockaddr*)&serverAddr, sizeof(struct sockaddr_un)) < 0) {
+ perror("Error binding UDP socket !");
+ close(sockfd);
+ exit(EXIT_FAILURE);
+ }
+
+ char buffer[BUFFER_SIZE];
+ ssize_t bytesRead;
+ struct sockaddr_un clientAddr;
+ socklen_t clientAddrLen;
+
+ std::cout << std::endl << "Waiting for messages ([sequence] ) ..." << '\n';
+ int sequence = 1;
+ std::string message;
+ while ((bytesRead = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&clientAddr, &clientAddrLen)) > 0) {
+
+ buffer[bytesRead] = '\0'; // Agregar terminador nulo al final del texto leído
+ message.assign(buffer);
+
+ if (sequence % i_printEach == 0) {
+ std::cout << "[" << sequence << "] " << message << std::endl;
+ }
+ sequence++;
+
+ // exit condition:
+ if (message == "EOF") {
+ std::cout<< std::endl << "Existing (EOF received) !" << '\n';
+ break;
+ }
+ }
+
+ close(sockfd);
+ unlink(path.c_str());
+
+ exit(EXIT_SUCCESS);
+}
+
diff --git a/ut/http2Client/http2Client_test.cpp b/ut/http2Client/http2Client_test.cpp
index 0698cdd..5043878 100644
--- a/ut/http2Client/http2Client_test.cpp
+++ b/ut/http2Client/http2Client_test.cpp
@@ -8,6 +8,7 @@
#include
#include
#include
+#include
#include
#include
@@ -90,6 +91,7 @@ class XXXX_http2Server_test : public ::testing::Test
h2agent::model::Configuration* configuration_{};
h2agent::model::GlobalVariable* global_variable_{};
h2agent::model::FileManager* file_manager_{};
+ h2agent::model::SocketManager* socket_manager_{};
h2agent::model::MockServerData* mock_server_events_data_{};
h2agent::model::MockClientData* mock_client_events_data_{};
ert::metrics::Metrics* metrics_{};
@@ -101,8 +103,10 @@ class XXXX_http2Server_test : public ::testing::Test
configuration_ = new h2agent::model::Configuration();
global_variable_ = new h2agent::model::GlobalVariable();
file_manager_ = new h2agent::model::FileManager(timers_io_service_);
+ socket_manager_ = new h2agent::model::SocketManager(timers_io_service_);
metrics_ = new ert::metrics::Metrics();
file_manager_->enableMetrics(metrics_);
+ socket_manager_->enableMetrics(metrics_);
mock_server_events_data_ = new h2agent::model::MockServerData();
mock_client_events_data_ = new h2agent::model::MockClientData();
@@ -112,6 +116,7 @@ class XXXX_http2Server_test : public ::testing::Test
admin_http2_server_->setConfiguration(configuration_);
admin_http2_server_->setGlobalVariable(global_variable_);
admin_http2_server_->setFileManager(file_manager_);
+ admin_http2_server_->setSocketManager(socket_manager_);
admin_http2_server_->setMockServerData(mock_server_events_data_); // stored at administrative class to pass through created server provisions
admin_http2_server_->setMockClientData(mock_client_events_data_); // stored at administrative class to pass through created client provisions
admin_http2_server_->enableMetrics(metrics_);
@@ -147,6 +152,7 @@ class XXXX_http2Server_test : public ::testing::Test
delete(configuration_);
delete(global_variable_);
delete(file_manager_);
+ delete(socket_manager_);
delete(mock_server_events_data_);
delete(mock_client_events_data_);
admin_http2_server_->stop();
diff --git a/ut/http2Server/http2Server_test.cpp b/ut/http2Server/http2Server_test.cpp
index d118696..f723d8a 100644
--- a/ut/http2Server/http2Server_test.cpp
+++ b/ut/http2Server/http2Server_test.cpp
@@ -11,6 +11,7 @@
#include
#include
#include
+#include
#include
#include
@@ -175,6 +176,7 @@ class http2Server_test : public ::testing::Test
h2agent::model::Configuration* configuration_{};
h2agent::model::GlobalVariable* global_variable_{};
h2agent::model::FileManager* file_manager_{};
+ h2agent::model::SocketManager* socket_manager_{};
h2agent::model::MockServerData* mock_server_events_data_{};
h2agent::model::MockClientData* mock_client_events_data_{};
ert::metrics::Metrics* metrics_{};
@@ -186,8 +188,10 @@ class http2Server_test : public ::testing::Test
configuration_ = new h2agent::model::Configuration();
global_variable_ = new h2agent::model::GlobalVariable();
file_manager_ = new h2agent::model::FileManager(timers_io_service_);
+ socket_manager_ = new h2agent::model::SocketManager(timers_io_service_);
metrics_ = new ert::metrics::Metrics();
file_manager_->enableMetrics(metrics_);
+ socket_manager_->enableMetrics(metrics_);
mock_server_events_data_ = new h2agent::model::MockServerData();
mock_client_events_data_ = new h2agent::model::MockClientData();
@@ -197,6 +201,7 @@ class http2Server_test : public ::testing::Test
admin_http2_server_->setConfiguration(configuration_);
admin_http2_server_->setGlobalVariable(global_variable_);
admin_http2_server_->setFileManager(file_manager_);
+ admin_http2_server_->setSocketManager(socket_manager_);
admin_http2_server_->setMockServerData(mock_server_events_data_); // stored at administrative class to pass through created server provisions
admin_http2_server_->setMockClientData(mock_client_events_data_); // stored at administrative class to pass through created client provisions
admin_http2_server_->enableMetrics(metrics_);
@@ -232,6 +237,7 @@ class http2Server_test : public ::testing::Test
delete(configuration_);
delete(global_variable_);
delete(file_manager_);
+ delete(socket_manager_);
delete(mock_server_events_data_);
delete(mock_client_events_data_);
admin_http2_server_->stop();
diff --git a/ut/model/CMakeLists.txt b/ut/model/CMakeLists.txt
index 752512f..3c1e30e 100644
--- a/ut/model/CMakeLists.txt
+++ b/ut/model/CMakeLists.txt
@@ -6,3 +6,4 @@ add_subdirectory(Transformation)
add_subdirectory(Storage)
add_subdirectory(Configuration)
add_subdirectory(FileSystem)
+add_subdirectory(UnixSockets)
diff --git a/ut/model/Transformation/transform.cpp b/ut/model/Transformation/transform.cpp
index f9fb217..657db17 100644
--- a/ut/model/Transformation/transform.cpp
+++ b/ut/model/Transformation/transform.cpp
@@ -1,3 +1,7 @@
+#include
+#include
+#include
+
#include
#include
#include
@@ -16,6 +20,7 @@
#include
#include
#include
+#include
#include
#include
@@ -227,6 +232,7 @@ class Transform_test : public ::testing::Test
common_resources_.ConfigurationPtr = new h2agent::model::Configuration();
common_resources_.GlobalVariablePtr = new h2agent::model::GlobalVariable();
common_resources_.FileManagerPtr = new h2agent::model::FileManager(nullptr);
+ common_resources_.SocketManagerPtr = new h2agent::model::SocketManager(nullptr);
common_resources_.MockServerDataPtr = new h2agent::model::MockServerData();
// Global variables:
@@ -256,6 +262,7 @@ class Transform_test : public ::testing::Test
delete(common_resources_.ConfigurationPtr);
delete(common_resources_.GlobalVariablePtr);
delete(common_resources_.FileManagerPtr);
+ delete(common_resources_.SocketManagerPtr);
delete(common_resources_.MockServerDataPtr);
}
};
@@ -1574,6 +1581,53 @@ TEST_F(Transform_test, TargetBinFile)
EXPECT_EQ(value2, value);
}
+TEST_F(Transform_test, TargetUDPSocket)
+{
+ // Socket path:
+ std::string socketPath = "/tmp/h2agent.UT.Transform_test.TargetUDPSocket";
+ std::string targetValue = "udpSocket.";
+ targetValue += socketPath;
+ targetValue += ".0";
+
+ // Build test provision:
+ nlohmann::json item = R"({ "source": "value.hello", "target": "tobereplaced" })"_json;
+ item["target"] = targetValue;
+
+ server_provision_json_["transform"].push_back(item);
+
+ // Prepare UDP socket for reception:
+ int sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);
+ ASSERT_FALSE(sockfd < 0);
+
+ struct sockaddr_un serverAddr;
+ memset(&serverAddr, 0, sizeof(struct sockaddr_un));
+ serverAddr.sun_family = AF_UNIX;
+ strcpy(serverAddr.sun_path, socketPath.c_str());
+
+ unlink(socketPath.c_str()); // just in case, it exists
+
+ int bindrc = bind(sockfd, (struct sockaddr*)&serverAddr, sizeof(struct sockaddr_un));
+ if (bindrc < 0) close(sockfd);
+ ASSERT_FALSE(bindrc < 0);
+
+ // Run transformation:
+ provisionAndTransform(request_body_.dump());
+
+ // Validations:
+ EXPECT_EQ(status_code_, 200);
+
+ // Check socket content:
+ char buffer[32];
+ struct sockaddr_un clientAddr;
+ socklen_t clientAddrLen;
+
+ ssize_t bytesRead = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&clientAddr, &clientAddrLen);
+ ASSERT_TRUE(bytesRead > 0);
+ buffer[bytesRead] = '\0';
+ std::string msg;
+ msg.assign(buffer);
+ EXPECT_EQ(msg, "hello");
+}
TEST_F(Transform_test, TargetBreak)
{
diff --git a/ut/model/UnixSockets/CMakeLists.txt b/ut/model/UnixSockets/CMakeLists.txt
new file mode 100644
index 0000000..67fcc97
--- /dev/null
+++ b/ut/model/UnixSockets/CMakeLists.txt
@@ -0,0 +1,5 @@
+target_sources( unit-test
+PRIVATE
+ ${CMAKE_CURRENT_SOURCE_DIR}/safeSocket.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/socketManager.cpp
+)
diff --git a/ut/model/UnixSockets/safeSocket.cpp b/ut/model/UnixSockets/safeSocket.cpp
new file mode 100644
index 0000000..f3df2fe
--- /dev/null
+++ b/ut/model/UnixSockets/safeSocket.cpp
@@ -0,0 +1,131 @@
+#include
+#include
+#include
+
+#include
+
+#include
+
+#include
+#include
+
+// SafeSocket json example template:
+nlohmann::json SafeSocketJson = R"(
+{
+ "path": "/tmp/h2agent.ut.safesocket.txt",
+ "socket": 0
+}
+)"_json;
+
+// SafeSocket content example:
+std::string SafeSocketContent = "hi";
+
+class SafeSocket_test : public ::testing::Test
+{
+public:
+ h2agent::model::SocketManager *socket_manager_{};
+ boost::asio::io_service *timers_io_service_{};
+ std::thread *timers_thread_{};
+
+ SafeSocket_test() {
+ socket_manager_ = new h2agent::model::SocketManager(); // no metrics by default
+ timers_io_service_ = new boost::asio::io_service();
+ timers_thread_ = new std::thread([&] {
+ boost::asio::io_service::work work(*timers_io_service_);
+ timers_io_service_->run();
+ });
+ }
+
+ ~SafeSocket_test() {
+ timers_io_service_->stop();
+ delete(timers_io_service_);
+ delete(timers_thread_);
+ }
+};
+
+TEST_F(SafeSocket_test, SafeSocketWithWriteDelayed)
+{
+ // Socket path:
+ std::string socketPath = SafeSocketJson["path"];
+
+ // Create socket to receive:
+ int sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);
+ ASSERT_FALSE(sockfd < 0);
+
+ struct sockaddr_un serverAddr;
+ memset(&serverAddr, 0, sizeof(struct sockaddr_un));
+ serverAddr.sun_family = AF_UNIX;
+ strcpy(serverAddr.sun_path, socketPath.c_str());
+
+ unlink(socketPath.c_str()); // just in case, it exists
+
+ int bindrc = bind(sockfd, (struct sockaddr*)&serverAddr, sizeof(struct sockaddr_un));
+ if (bindrc < 0) close(sockfd);
+ ASSERT_FALSE(bindrc < 0);
+
+ // Open socket to write:
+ h2agent::model::SafeSocket socket(socket_manager_, socketPath, timers_io_service_);
+ socket.write(SafeSocketContent, 5000 /* write delay value */);
+
+ // Written after 5000 usecs (5 ms), we wait 50 ms (10x !) to ensure it is written:
+ boost::asio::deadline_timer exitTimer(*timers_io_service_, boost::posix_time::milliseconds(50));
+ exitTimer.async_wait([&] (const boost::system::error_code& e) { timers_io_service_->stop(); });
+ timers_thread_->join();
+
+ // Check socket content:
+ char buffer[32];
+ struct sockaddr_un clientAddr;
+ socklen_t clientAddrLen;
+
+ ssize_t bytesRead = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&clientAddr, &clientAddrLen);
+ ASSERT_TRUE(bytesRead > 0);
+ buffer[bytesRead] = '\0';
+ EXPECT_EQ(buffer, SafeSocketContent);
+
+ // Check json representation:
+ SafeSocketJson["socket"] = socket.getJson()["socket"]; // unpredictable
+ EXPECT_EQ(socket.getJson(), SafeSocketJson);
+}
+
+TEST_F(SafeSocket_test, SafeSocketWithInstantWrite)
+{
+ // Stop timers service:
+ timers_io_service_->stop();
+ timers_thread_->join();
+
+ // Socket path:
+ std::string socketPath = SafeSocketJson["path"];
+
+ // Create socket to receive:
+ int sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);
+ ASSERT_FALSE(sockfd < 0);
+
+ struct sockaddr_un serverAddr;
+ memset(&serverAddr, 0, sizeof(struct sockaddr_un));
+ serverAddr.sun_family = AF_UNIX;
+ strcpy(serverAddr.sun_path, socketPath.c_str());
+
+ unlink(socketPath.c_str()); // just in case, it exists
+
+ int bindrc = bind(sockfd, (struct sockaddr*)&serverAddr, sizeof(struct sockaddr_un));
+ if (bindrc < 0) close(sockfd);
+ ASSERT_FALSE(bindrc < 0);
+
+ // Open socket to write:
+ h2agent::model::SafeSocket socket(socket_manager_, socketPath, nullptr /* no timers io service will be used */);
+ socket.write(SafeSocketContent);
+
+ // Check socket content:
+ char buffer[32];
+ struct sockaddr_un clientAddr;
+ socklen_t clientAddrLen;
+
+ ssize_t bytesRead = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&clientAddr, &clientAddrLen);
+ ASSERT_TRUE(bytesRead > 0);
+ buffer[bytesRead] = '\0';
+ EXPECT_EQ(buffer, SafeSocketContent);
+
+ // Check json representation:
+ SafeSocketJson["socket"] = socket.getJson()["socket"]; // unpredictable
+ EXPECT_EQ(socket.getJson(), SafeSocketJson);
+}
diff --git a/ut/model/UnixSockets/socketManager.cpp b/ut/model/UnixSockets/socketManager.cpp
new file mode 100644
index 0000000..3a2fbff
--- /dev/null
+++ b/ut/model/UnixSockets/socketManager.cpp
@@ -0,0 +1,108 @@
+#include
+#include
+#include
+
+#include
+
+#include
+
+#include
+#include
+
+// SocketManager json example template:
+nlohmann::json SocketManagerJson = R"(
+[
+ {
+ "socket": 0,
+ "path": "/tmp/h2agent.ut.Beethoven.txt"
+ },
+ {
+ "socket": 0,
+ "path": "/tmp/h2agent.ut.Mozart.txt"
+ }
+]
+)"_json;
+
+// Target file content example:
+std::string SocketManagerSafeSocketContent1 = "hi";
+std::string SocketManagerSafeSocketContent2 = "bye";
+
+class SocketManager_test : public ::testing::Test
+{
+public:
+
+ SocketManager_test() {
+ ;
+ }
+
+ ~SocketManager_test() {
+ }
+};
+
+TEST_F(SocketManager_test, SocketManager)
+{
+ h2agent::model::SocketManager sm(nullptr);
+ sm.enableMetrics(nullptr);
+
+ // Socket paths:
+ std::string path1, path2;
+ path1 = SocketManagerJson[0]["path"];
+ path2 = SocketManagerJson[1]["path"];
+
+ // Create sockets to receive:
+ int sockfd1 = socket(AF_UNIX, SOCK_DGRAM, 0);
+ ASSERT_FALSE(sockfd1 < 0);
+ int sockfd2 = socket(AF_UNIX, SOCK_DGRAM, 0);
+ ASSERT_FALSE(sockfd2 < 0);
+
+ struct sockaddr_un serverAddr1, serverAddr2;
+ memset(&serverAddr1, 0, sizeof(struct sockaddr_un));
+ serverAddr1.sun_family = AF_UNIX;
+ strcpy(serverAddr1.sun_path, path1.c_str());
+ memset(&serverAddr2, 0, sizeof(struct sockaddr_un));
+ serverAddr2.sun_family = AF_UNIX;
+ strcpy(serverAddr2.sun_path, path2.c_str());
+
+ unlink(path1.c_str()); // just in case, it exists
+ unlink(path2.c_str()); // just in case, it exists
+
+ int bindrc1 = bind(sockfd1, (struct sockaddr*)&serverAddr1, sizeof(struct sockaddr_un));
+ if (bindrc1 < 0) close(sockfd1);
+ ASSERT_FALSE(bindrc1 < 0);
+
+ int bindrc2 = bind(sockfd2, (struct sockaddr*)&serverAddr2, sizeof(struct sockaddr_un));
+ if (bindrc2 < 0) close(sockfd2);
+ ASSERT_FALSE(bindrc2 < 0);
+
+ // write:
+ sm.write(path2, SocketManagerSafeSocketContent2, 0);
+ sm.write(path1, SocketManagerSafeSocketContent1, 0);
+
+ // read:
+ // Check socket content:
+ char buffer[32];
+ struct sockaddr_un clientAddr;
+ socklen_t clientAddrLen;
+
+ ssize_t bytesRead = recvfrom(sockfd1, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&clientAddr, &clientAddrLen);
+ ASSERT_TRUE(bytesRead > 0);
+ buffer[bytesRead] = '\0';
+ EXPECT_EQ(buffer, SocketManagerSafeSocketContent1);
+
+ bytesRead = recvfrom(sockfd2, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&clientAddr, &clientAddrLen);
+ ASSERT_TRUE(bytesRead > 0);
+ buffer[bytesRead] = '\0';
+ EXPECT_EQ(buffer, SocketManagerSafeSocketContent2);
+
+ // Check json representation:
+ SocketManagerJson[0]["socket"] = sm.getJson()[0]["socket"];
+ SocketManagerJson[1]["socket"] = sm.getJson()[1]["socket"];
+ EXPECT_EQ(sm.getJson(), SocketManagerJson);
+
+ // clear:
+ sm.clear();
+
+ // Check empty:
+ EXPECT_EQ(sm.asJsonString(), "[]");
+}
+