From 393fca8d35f7746808e275c40dd15054e9908e66 Mon Sep 17 00:00:00 2001 From: hxy7yx <1595670487@qq.com> Date: Wed, 13 Nov 2024 15:07:24 +0800 Subject: [PATCH] feat(modbus-tcp):support backup ip --- include/neuron/connection/neu_connection.h | 2 + plugins/modbus/modbus-tcp.json | 44 ++++++++++++++++++++++ plugins/modbus/modbus_req.c | 16 ++++++++ plugins/modbus/modbus_req.h | 6 +++ plugins/modbus/modbus_tcp.c | 42 ++++++++++++++++++++- src/connection/connection.c | 4 ++ tests/ft/driver/test_modbus.py | 32 ++++++++++++++-- tests/ft/neuron/api.py | 4 ++ tests/ft/neuron/common.py | 2 +- 9 files changed, 145 insertions(+), 7 deletions(-) diff --git a/include/neuron/connection/neu_connection.h b/include/neuron/connection/neu_connection.h index 5518133a1..d12ac7305 100644 --- a/include/neuron/connection/neu_connection.h +++ b/include/neuron/connection/neu_connection.h @@ -301,4 +301,6 @@ int neu_conn_tcp_server_wait_msg(neu_conn_t *conn, int fd, void *context, int is_ipv4(const char *ip); int is_ipv6(const char *ip); +bool neu_conn_is_connected(neu_conn_t *conn); + #endif diff --git a/plugins/modbus/modbus-tcp.json b/plugins/modbus/modbus-tcp.json index 6df5d7385..1114729a5 100644 --- a/plugins/modbus/modbus-tcp.json +++ b/plugins/modbus/modbus-tcp.json @@ -257,5 +257,49 @@ "min": 1000, "max": 65535 } + }, + "backup": { + "name": "Enable Backup", + "name_zh": "启用主备切换", + "attribute": "required", + "type": "map", + "default": 0, + "valid": { + "map": [ + { + "key": "False", + "value": 0 + }, + { + "key": "True", + "value": 1 + } + ] + } + }, + "backup_host": { + "name": "Backup IP Address", + "name_zh": "备用 IP 地址", + "attribute": "required", + "type": "string", + "condition": { + "field": "backup", + "value": 1 + } + }, + "backup_port": { + "name": "Backup Port", + "name_zh": "备用端口号", + "attribute": "required", + "type": "int", + "default": 502, + "valid": { + "min": 1, + "max": 65535 + }, + "condition": { + "field": "backup", + "value": 1 + } } } \ No newline at end of file diff --git a/plugins/modbus/modbus_req.c b/plugins/modbus/modbus_req.c index 268e963bc..e5dcba4d6 100644 --- a/plugins/modbus/modbus_req.c +++ b/plugins/modbus/modbus_req.c @@ -119,6 +119,22 @@ int modbus_send_msg(void *ctx, uint16_t n_byte, uint8_t *bytes) ret = neu_conn_tcp_server_send(plugin->conn, plugin->client_fd, bytes, n_byte); } else { + if (plugin->backup && neu_conn_is_connected(plugin->conn) == false) { + if (plugin->current_backup == false && plugin->first_attempt_done) { + plog_notice(plugin, "switch to backup ip:port %s:%hu", + plugin->param_backup.params.tcp_client.ip, + plugin->param_backup.params.tcp_client.port); + plugin->current_backup = true; + plugin->conn = neu_conn_reconfig(plugin->conn, &plugin->param_backup); + } else { + plog_notice(plugin, "switch to original ip:port %s:%hu", + plugin->param.params.tcp_client.ip, + plugin->param.params.tcp_client.port); + plugin->current_backup = false; + plugin->conn = neu_conn_reconfig(plugin->conn, &plugin->param); + plugin->first_attempt_done = true; + } + } ret = neu_conn_send(plugin->conn, bytes, n_byte); } diff --git a/plugins/modbus/modbus_req.h b/plugins/modbus/modbus_req.h index 7f386aeb0..1d5ce405f 100644 --- a/plugins/modbus/modbus_req.h +++ b/plugins/modbus/modbus_req.h @@ -49,6 +49,12 @@ struct neu_plugin { bool degradation; uint16_t degrade_cycle; uint16_t degrade_time; + + bool backup; + bool current_backup; + bool first_attempt_done; + neu_conn_param_t param; + neu_conn_param_t param_backup; }; void modbus_conn_connected(void *data, int fd); diff --git a/plugins/modbus/modbus_tcp.c b/plugins/modbus/modbus_tcp.c index 649b3425f..865610c4f 100644 --- a/plugins/modbus/modbus_tcp.c +++ b/plugins/modbus/modbus_tcp.c @@ -125,6 +125,13 @@ static int driver_uninit(neu_plugin_t *plugin) modbus_stack_destroy(plugin->stack); } + if (plugin->param.params.tcp_client.ip != NULL) { + free(plugin->param.params.tcp_client.ip); + } + if (plugin->param_backup.params.tcp_client.ip != NULL) { + free(plugin->param_backup.params.tcp_client.ip); + } + neu_event_close(plugin->events); plog_notice(plugin, "%s uninit success", plugin->common.name); @@ -175,6 +182,15 @@ static int driver_config(neu_plugin_t *plugin, const char *config) neu_json_elem_t address_base = { .name = "address_base", .t = NEU_JSON_INT }; + neu_json_elem_t backup = { .name = "backup", + .t = NEU_JSON_INT }; + neu_json_elem_t backup_ip = { .name = "backup_host", + .t = NEU_JSON_STR, + .v.val_str = NULL }; + neu_json_elem_t backup_port = { .name = "backup_port", + .t = NEU_JSON_INT }; + neu_conn_param_t param_backup = { 0 }; + ret = neu_parse_param((char *) config, &err_param, 5, &port, &host, &mode, &timeout, &interval); @@ -229,7 +245,18 @@ static int driver_config(neu_plugin_t *plugin, const char *config) address_base.v.val_int = base_1; } + ret = neu_parse_param((char *) config, &err_param, 3, &backup, + &backup_ip, &backup_port); + if (ret != 0) { + free(err_param); + if (backup_ip.v.val_str != NULL) { + free(backup_ip.v.val_str); + } + backup.v.val_int = 0; + } + param.log = plugin->common.log; + param_backup.log = plugin->common.log; plugin->interval = interval.v.val_int; plugin->max_retries = max_retries.v.val_int; plugin->retry_interval = retry_interval.v.val_int; @@ -249,15 +276,27 @@ static int driver_config(neu_plugin_t *plugin, const char *config) param.params.tcp_server.timeout = timeout.v.val_int; param.params.tcp_server.max_link = 1; plugin->is_server = true; + backup.v.val_int = 0; } if (mode.v.val_int == 0) { + plugin->is_server = false; param.type = NEU_CONN_TCP_CLIENT; param.params.tcp_client.ip = host.v.val_str; param.params.tcp_client.port = port.v.val_int; param.params.tcp_client.timeout = timeout.v.val_int; - plugin->is_server = false; + + param_backup.type = NEU_CONN_TCP_CLIENT; + if (backup_ip.v.val_str != NULL) { + param_backup.params.tcp_client.ip = backup_ip.v.val_str; + } + param_backup.params.tcp_client.port = backup_port.v.val_int; + param_backup.params.tcp_client.timeout = timeout.v.val_int; } + plugin->backup = backup.v.val_int; + plugin->param = param; + plugin->param_backup = param_backup; + plog_notice(plugin, "config: host: %s, port: %" PRId64 ", mode: %" PRId64 "", host.v.val_str, port.v.val_int, mode.v.val_int); @@ -271,7 +310,6 @@ static int driver_config(neu_plugin_t *plugin, const char *config) modbus_conn_disconnected); } - free(host.v.val_str); return 0; } diff --git a/src/connection/connection.c b/src/connection/connection.c index 3ce15959b..714781310 100644 --- a/src/connection/connection.c +++ b/src/connection/connection.c @@ -1425,4 +1425,8 @@ int is_ipv6(const char *ip) { struct sockaddr_in6 sa; return inet_pton(AF_INET6, ip, &(sa.sin6_addr)) != 0; +} + +bool neu_conn_is_connected(neu_conn_t *conn) { + return conn->is_connected; } \ No newline at end of file diff --git a/tests/ft/driver/test_modbus.py b/tests/ft/driver/test_modbus.py index 8b61d28c2..12eee02a9 100644 --- a/tests/ft/driver/test_modbus.py +++ b/tests/ft/driver/test_modbus.py @@ -8,9 +8,11 @@ import neuron.config as config from neuron.common import * -tcp_port = random_port() -rtu_port = random_port() -otel_port = random_port() +tcp_port = random_port() +rtu_port = tcp_port + 1 +otel_port = tcp_port + 2 +tcp_port_2 = tcp_port + 3 +backup_tcp_port = tcp_port + 4 def start_socat(): @@ -1703,4 +1705,26 @@ def test_set_modbus_start_base(self, param): assert 1 == api.read_tag( node=param[0], group='group', tag=coil_bit_2[0]['name']) assert 0 == api.read_tag( - node=param[0], group='group', tag=coil_bit_3[0]['name']) \ No newline at end of file + node=param[0], group='group', tag=coil_bit_3[0]['name']) + + @description(given="configed modbus node with backup ip&port and start corresponding sinulators", + when="stop the original simulator", + then="success to read tags from backup simulator") + def test_modbus_backup(self, param): + if param[0] == 'modbus-tcp': + p_1 = process.start_simulator(['./modbus_simulator', 'tcp', f'{tcp_port_2}', 'ip_v4']) + p_2 = process.start_simulator(['./modbus_simulator', 'tcp', f'{backup_tcp_port}', 'ip_v4']) + + api.modbus_tcp_node_setting(node=param[0], port=tcp_port_2) + time.sleep(0.3) + api.write_tag(node=param[0], group='group', tag=hold_int16[0]['name'], value=17) + time.sleep(0.3) + assert 17 == api.read_tag(node=param[0], group='group', tag=hold_int16[0]['name']) + + api.modbus_tcp_node_setting_backup(node=param[0], port=tcp_port, backup_port=backup_tcp_port) + process.stop_simulator(p_1) + time.sleep(1) + assert 0 == api.read_tag(node=param[0], group='group', tag=hold_int16[0]['name']) + process.stop_simulator(p_2) + else: + pytest.skip() \ No newline at end of file diff --git a/tests/ft/neuron/api.py b/tests/ft/neuron/api.py index b27aa8d81..018f0d88c 100644 --- a/tests/ft/neuron/api.py +++ b/tests/ft/neuron/api.py @@ -378,6 +378,10 @@ def modbus_rtu_node_setting_base(node, port=502, connection_mode=0, transport_mo "max_retries": max_retries, "retry_interval": retry_interval, "link": link, "device": device, "stop": stop, "parity": parity, "baud": baud, "data": data, "address_base": base}) +def modbus_tcp_node_setting_backup(node, port, backup_port, connection_mode=0, transport_mode=0, interval=0, host='127.0.0.1', timeout=3000, max_retries=2, retry_interval=1): + return node_setting(node, json={"connection_mode": connection_mode, "transport_mode": transport_mode, "interval": interval, + "host": host, "port": port, "timeout": timeout, "max_retries": max_retries, "retry_interval": retry_interval, "backup": 1, "backup_host": "127.0.0.1", "backup_port": backup_port}) + def mqtt_node_setting(node, client_id="neuron_aBcDeF", host="broker.emqx.io", port=1883): return node_setting(node, json={"client-id": client_id, "qos": 0, "format": 0, diff --git a/tests/ft/neuron/common.py b/tests/ft/neuron/common.py index 5883564df..4388fc67f 100644 --- a/tests/ft/neuron/common.py +++ b/tests/ft/neuron/common.py @@ -29,7 +29,7 @@ def wrapper(*args, **kwargs): def random_port(): - return random.randint(30000, 65535) + return random.randint(30000, 65530) def compare_float(v1, v2, delta=0.001):