Repository: eclipse-bluechi/bluechi (language: python)

Issue:
agent: move the IPV4 and IPV6 validation
We should validate first if the data provided
is IPV4 or IPV6, if not, translate.

Additionally, when there is a host to be translated to IP we must assume ip_address can match IPV4 and IPV6 sizes. For such scenario let's make INET6_ADDRSTRLEN the default size as it can match both sizes.

from netinet/in.h:
INET6_ADDRSTRLEN = 46bytes
INET_ADDRSTRLEN = 16bytes


Discussion / hints:
For reference only: https://github.com/containers/hirte/pull/348#discussion_r1226165611

Fix patch:
diff --git a/src/agent/agent.c b/src/agent/agent.c
index 16eb6ca048..86853560d3 100644
--- a/src/agent/agent.c
+++ b/src/agent/agent.c
@@ -99,13 +99,14 @@ bool agent_is_connected(Agent *agent) {
 
 static int agent_stop_local_proxy_service(Agent *agent, ProxyService *proxy);
 static bool agent_connect(Agent *agent);
+static bool agent_reconnect(Agent *agent);
 
 static int agent_disconnected(UNUSED sd_bus_message *message, void *userdata, UNUSED sd_bus_error *error) {
         Agent *agent = (Agent *) userdata;
 
         hirte_log_error("Disconnected from manager");
 
-        if (!agent_connect(agent)) {
+        if (!agent_reconnect(agent)) {
                 agent->connection_state = AGENT_CONNECTION_STATE_RETRY;
         }
 
@@ -131,7 +132,7 @@ static int agent_heartbeat_timer_callback(sd_event_source *event_source, UNUSED
         } else if (agent->connection_state == AGENT_CONNECTION_STATE_RETRY) {
                 agent->connection_retry_count++;
                 hirte_log_infof("Trying to connect to manager (try %d)", agent->connection_retry_count);
-                if (!agent_connect(agent)) {
+                if (!agent_reconnect(agent)) {
                         hirte_log_debugf("Connection retry %d failed", agent->connection_retry_count);
                 }
         }
@@ -1805,47 +1806,61 @@ static bool ensure_orch_address(Agent *agent) {
         if (agent->orch_addr != NULL) {
                 return true;
         }
-
-
         if (agent->host == NULL) {
                 hirte_log_errorf("No manager host specified for agent '%s'", agent->name);
                 return false;
         }
 
+        char *ip_address = agent->host;
+        bool host_is_ipv4 = is_ipv4(ip_address);
+        bool host_is_ipv6 = is_ipv6(ip_address);
 
-        if (!isIPv4Addr(agent->host) && !isIPv6Addr(agent->host)) {
-                char *ip_address = malloc0_array(0, sizeof(char), INET6_ADDRSTRLEN);
-                int r = get_address(agent->host, &ip_address);
-                if (r != 0) {
-                        hirte_log_errorf("Failed to get IP address from: '%s'", agent->host);
+        _cleanup_free_ char *resolved_ip_address = NULL;
+        if (!host_is_ipv4 && !host_is_ipv6) {
+                int r = get_address(ip_address, &resolved_ip_address);
+                if (r < 0) {
+                        hirte_log_errorf(
+                                        "Failed to get IP address from host '%s': %s",
+                                        agent->host,
+                                        strerror(-r));
                         return false;
                 }
-                hirte_log_infof("Translated '%s' to '%s'", agent->host, ip_address);
-                copy_str(&agent->host, ip_address);
+                hirte_log_infof("Translated '%s' to '%s'", ip_address, resolved_ip_address);
+                ip_address = resolved_ip_address;
         }
 
-        // IPv4
-        if (isIPv4Addr(agent->host)) {
+        if (host_is_ipv4 || is_ipv4(ip_address)) {
                 struct sockaddr_in host;
                 memset(&host, 0, sizeof(host));
                 host.sin_family = AF_INET;
                 host.sin_port = htons(agent->port);
-                r = inet_pton(AF_INET, agent->host, &host.sin_addr);
+                r = inet_pton(AF_INET, ip_address, &host.sin_addr);
                 if (r < 1) {
-                        hirte_log_errorf("INET4: Invalid host option '%s'", agent->host);
+                        hirte_log_errorf("INET4: Invalid host option '%s'", ip_address);
                         return false;
                 }
-                agent->orch_addr = assemble_tcp_address(&host);
-        } else if (isIPv6Addr(agent->host)) { // IPv6
+                _cleanup_free_ char *orch_addr = assemble_tcp_address(&host);
+                if (orch_addr == NULL) {
+                        return false;
+                }
+                agent_set_orch_address(agent, orch_addr);
+        } else if (host_is_ipv6 || is_ipv6(ip_address)) {
                 struct sockaddr_in6 host6;
                 memset(&host6, 0, sizeof(host6));
                 host6.sin6_family = AF_INET6;
                 host6.sin6_port = htons(agent->port);
-                r = inet_pton(AF_INET6, agent->host, &host6.sin6_addr);
+                r = inet_pton(AF_INET6, ip_address, &host6.sin6_addr);
                 if (r < 1) {
-                        hirte_log_errorf("INET6: Invalid host option '%s'", agent->host);
+                        hirte_log_errorf("INET6: Invalid host option '%s'", ip_address);
+                        return false;
+                }
+                _cleanup_free_ char *orch_addr = assemble_tcp_address_v6(&host6);
+                if (orch_addr == NULL) {
                         return false;
                 }
+                agent_set_orch_address(agent, orch_addr);
+        } else {
+                hirte_log_errorf("Unknown protocol for '%s'", ip_address);
         }
 
         return agent->orch_addr != NULL;
@@ -2149,6 +2164,20 @@ static bool agent_connect(Agent *agent) {
         return true;
 }
 
+static bool agent_reconnect(Agent *agent) {
+        // resolve FQDN again in case the system changed
+        // e.g. hirte controller has been migrated to a different host
+        if (agent->orch_addr != NULL) {
+                free(agent->orch_addr);
+                agent->orch_addr = NULL;
+        }
+        if (!ensure_orch_address(agent)) {
+                return false;
+        }
+
+        return agent_connect(agent);
+}
+
 static int stop_proxy_callback(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) {
         _cleanup_systemd_request_ SystemdRequest *req = userdata;
 
diff --git a/src/libhirte/common/network.c b/src/libhirte/common/network.c
index b04ed59760..f6eb34db92 100644
--- a/src/libhirte/common/network.c
+++ b/src/libhirte/common/network.c
@@ -1,7 +1,7 @@
 /* SPDX-License-Identifier: GPL-2.0-or-later */
 #include "network.h"
 
-bool isIPv4Addr(const char *domain) {
+bool is_ipv4(const char *domain) {
         if (domain == NULL) {
                 return false;
         }
@@ -9,7 +9,7 @@ bool isIPv4Addr(const char *domain) {
         return (inet_pton(AF_INET, domain, &(sa.sin_addr)) == 1);
 }
 
-bool isIPv6Addr(const char *domain) {
+bool is_ipv6(const char *domain) {
         if (domain == NULL) {
                 return false;
         }
@@ -19,12 +19,11 @@ bool isIPv6Addr(const char *domain) {
 
 int get_address(const char *domain, char **ip_address) {
         if (domain == NULL) {
-                return 1;
+                return -EINVAL;
         }
 
-        // Already an IP, no need to resolve
-        if (isIPv4Addr(domain) || isIPv6Addr(domain)) {
-                return 1;
+        if (is_ipv4(domain) || is_ipv6(domain)) {
+                return 0;
         }
 
         struct addrinfo hints, *res = NULL;
@@ -35,25 +34,29 @@ int get_address(const char *domain, char **ip_address) {
         int status = getaddrinfo(domain, NULL, &hints, &res);
         if (status != 0) {
                 hirte_log_errorf("getaddrinfo failed: %s\n", gai_strerror(status));
-                return 1;
+                return status;
         }
 
-        if (res->ai_family == AF_INET) { // IPv4 address
+        if (res->ai_family == AF_INET) {
                 struct sockaddr_in *ipv4 = (struct sockaddr_in *) res->ai_addr;
                 _cleanup_free_ char *reverse = typesafe_inet_ntop4(ipv4);
-                strncpy(*ip_address, reverse, INET_ADDRSTRLEN);
-                if (ip_address == NULL) {
-                        hirte_log_errorf("AF_INET: Failed to convert the IP address. errno: %d\n", errno);
-                        return 1;
+                if (reverse == NULL) {
+                        freeaddrinfo(res);
+                        return -ENOMEM;
                 }
-        } else if (res->ai_family == AF_INET6) { // IPv6 address
+                *ip_address = steal_pointer(&reverse);
+        } else if (res->ai_family == AF_INET6) {
                 struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *) res->ai_addr;
                 _cleanup_free_ char *reverse = typesafe_inet_ntop6(ipv6);
-                strncpy(*ip_address, reverse, INET6_ADDRSTRLEN);
-                if (ip_address == NULL) {
-                        hirte_log_errorf("AF_INET6: Failed to convert the IP address. errno: %d\n", errno);
-                        return 1;
+                if (reverse == NULL) {
+                        freeaddrinfo(res);
+                        return -ENOMEM;
                 }
+                *ip_address = steal_pointer(&reverse);
+        } else {
+                hirte_log_errorf("Unsupported address family '%d'", res->ai_family);
+                freeaddrinfo(res);
+                return -EFAULT;
         }
 
         freeaddrinfo(res);
@@ -63,12 +66,20 @@ int get_address(const char *domain, char **ip_address) {
 
 char *typesafe_inet_ntop4(const struct sockaddr_in *addr) {
         char *dst = malloc0_array(0, sizeof(char), INET_ADDRSTRLEN);
+        if (dst == NULL) {
+                hirte_log_error("AF_INET OOM: Failed allocate memory");
+                return NULL;
+        }
         inet_ntop(AF_INET, &addr->sin_addr, dst, INET_ADDRSTRLEN);
         return dst;
 }
 
 char *typesafe_inet_ntop6(const struct sockaddr_in6 *addr) {
         char *dst = malloc0_array(0, sizeof(char), INET6_ADDRSTRLEN);
+        if (dst == NULL) {
+                hirte_log_error("AF_INET6 OOM: Failed allocate memory");
+                return NULL;
+        }
         inet_ntop(AF_INET6, &addr->sin6_addr, dst, INET6_ADDRSTRLEN);
         return dst;
 }
diff --git a/src/libhirte/common/network.h b/src/libhirte/common/network.h
index fa202f96ad..fe7f06f8aa 100644
--- a/src/libhirte/common/network.h
+++ b/src/libhirte/common/network.h
@@ -12,9 +12,8 @@
 #include "libhirte/common/common.h"
 #include "libhirte/log/log.h"
 
-bool isIPv4Addr(const char *domain);
-
-bool isIPv6Addr(const char *domain);
+bool is_ipv4(const char *domain);
+bool is_ipv6(const char *domain);
 
 int get_address(const char *domain, char **ip_address);
 


Test patch:
diff --git a/src/libhirte/test/common/network/get_address_test.c b/src/libhirte/test/common/network/get_address_test.c
index 33f1fcdddf..091702867c 100644
--- a/src/libhirte/test/common/network/get_address_test.c
+++ b/src/libhirte/test/common/network/get_address_test.c
@@ -7,39 +7,48 @@
 #include "libhirte/common/network.h"
 #include "libhirte/common/string-util.h"
 
-bool test_get_address(const char *domain, char *ip, bool expected) {
-        bool result = get_address(domain, &ip);
-        // adjust the values for the below validation
-        if (result == 0) {
-                result = true;
-        } else {
-                result = false;
+bool test_get_address(const char *domain, const char *expected_address, int expected_ret) {
+        _cleanup_free_ char *ip = NULL;
+        int result = get_address(domain, &ip);
+        if (result != expected_ret) {
+                fprintf(stdout,
+                        "FAILED: get_address('%s') - Expected return code %d, but got %d (%s)\n",
+                        domain,
+                        expected_ret,
+                        result,
+                        strerror(-result));
+                return false;
         }
-
-        if (result == expected) {
-                return true;
+        if (expected_address == NULL && ip != NULL) {
+                fprintf(stdout, "FAILED: get_address('%s') - Expected null, but got non-null (%s)", domain, ip);
+                return false;
+        }
+        if (expected_address != NULL && ip == NULL) {
+                fprintf(stdout, "FAILED: get_address('%s') - Expected non-null, but got null", domain);
+                return false;
         }
-        fprintf(stdout,
-                "FAILED: get_address('%s') - Expected %s, but got %s\n",
-                domain,
-                bool_to_str(expected),
-                bool_to_str(result));
-        return false;
+        if (expected_address != NULL && ip != NULL && !streq(ip, expected_address)) {
+                fprintf(stdout,
+                        "FAILED: get_address('%s') - Expected %s, but got %s\n",
+                        domain,
+                        expected_address,
+                        ip);
+                return false;
+        }
+        return true;
 }
 
 
 int main() {
         bool result = true;
-        char ip[INET6_ADDRSTRLEN];
-
-        result = result && test_get_address(NULL, ip, false);
-        result = result && test_get_address(".", ip, false);
-        result = result && test_get_address("redhat", ip, false);
-        result = result && test_get_address("localhost", ip, true);
-        result = result && test_get_address("8.8.8.8", ip, false);
-        result = result && test_get_address("fe80::78b3:75ff:fe1b:6803", ip, false);
-        result = result && test_get_address("?10.10.10.3", ip, false);
-        result = result && test_get_address("192.168.1.", ip, false);
+        result = result && test_get_address(NULL, NULL, -EINVAL);
+        result = result && test_get_address(".", NULL, -ENOENT);
+        result = result && test_get_address("redhat", NULL, -ENOENT);
+        result = result && test_get_address("localhost", "127.0.0.1", 0);
+        result = result && test_get_address("8.8.8.8", NULL, 0);
+        result = result && test_get_address("fe80::78b3:75ff:fe1b:6803", NULL, 0);
+        result = result && test_get_address("?10.10.10.3", NULL, -ENOENT);
+        result = result && test_get_address("192.168.1.", NULL, -ENOENT);
         if (result) {
                 return EXIT_SUCCESS;
         }
diff --git a/src/libhirte/test/common/network/isIPv4Addr_test.c b/src/libhirte/test/common/network/isIPv4Addr_test.c
deleted file mode 100644
index d49cf8f9c5..0000000000
--- a/src/libhirte/test/common/network/isIPv4Addr_test.c
+++ /dev/null
@@ -1,40 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-#include <assert.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-#include "libhirte/common/network.h"
-#include "libhirte/common/string-util.h"
-
-bool test_isIPv4Addr(const char *in, bool expected) {
-        bool result = isIPv4Addr(in);
-        if (result == expected) {
-                return true;
-        }
-        fprintf(stdout,
-                "FAILED: isIPv4Addr('%s') - Expected %s, but got %s\n",
-                in,
-                bool_to_str(expected),
-                bool_to_str(result));
-        return false;
-}
-
-
-int main() {
-        bool result = true;
-
-        result = result && test_isIPv4Addr(NULL, false);
-        result = result && test_isIPv4Addr("", false);
-        result = result && test_isIPv4Addr(".", false);
-        result = result && test_isIPv4Addr("fe80::78b3:75ff:fe1b:6803", false);
-        result = result && test_isIPv4Addr("192.168.1.5", true);
-        result = result && test_isIPv4Addr("10.10.10.5", true);
-        result = result && test_isIPv4Addr("?10.10.10.3", false);
-        result = result && test_isIPv4Addr("mydomain.com", false);
-        result = result && test_isIPv4Addr("192.168.1.", false);
-        if (result) {
-                return EXIT_SUCCESS;
-        }
-        return EXIT_FAILURE;
-}
diff --git a/src/libhirte/test/common/network/isIPv6Addr_test.c b/src/libhirte/test/common/network/isIPv6Addr_test.c
deleted file mode 100644
index 4a45e6ee70..0000000000
--- a/src/libhirte/test/common/network/isIPv6Addr_test.c
+++ /dev/null
@@ -1,45 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-#include <assert.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-#include "libhirte/common/network.h"
-#include "libhirte/common/string-util.h"
-
-bool test_isIPv6Addr(const char *in, bool expected) {
-        bool result = isIPv6Addr(in);
-        if (result == expected) {
-                return true;
-        }
-        fprintf(stdout,
-                "FAILED: isIPv6Addr('%s') - Expected %s, but got %s\n",
-                in,
-                bool_to_str(expected),
-                bool_to_str(result));
-        return false;
-}
-
-
-int main() {
-        bool result = true;
-
-        result = result && test_isIPv6Addr(NULL, false);
-        result = result && test_isIPv6Addr("", false);
-        result = result && test_isIPv6Addr(".", false);
-        result = result && test_isIPv6Addr("fe80::78b3:75ff:fe1b:6803", true);
-        result = result && test_isIPv6Addr("1050:0000:0000:0000:0005:0600:300c:326b", true);
-        result = result && test_isIPv6Addr("1050:0:0:0:5:600:300c:326b", true);
-        result = result && test_isIPv6Addr("1050:0:0:0:5:600:300c:326b", true);
-        result = result && test_isIPv6Addr("ff06:0:0:0:0:0:0:c3", true);
-        result = result && test_isIPv6Addr("ff06::c3", true);
-        result = result && test_isIPv6Addr("192.168.1.5", false);
-        result = result && test_isIPv6Addr("10.10.10.5", false);
-        result = result && test_isIPv6Addr("?10.10.10.3", false);
-        result = result && test_isIPv6Addr("mydomain.com", false);
-        result = result && test_isIPv6Addr("192.168.1.", false);
-        if (result) {
-                return EXIT_SUCCESS;
-        }
-        return EXIT_FAILURE;
-}
diff --git a/src/libhirte/test/common/network/is_ipv4_test.c b/src/libhirte/test/common/network/is_ipv4_test.c
new file mode 100644
index 0000000000..c683393e4c
--- /dev/null
+++ b/src/libhirte/test/common/network/is_ipv4_test.c
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#include <assert.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "libhirte/common/network.h"
+#include "libhirte/common/string-util.h"
+
+bool test_is_ipv4(const char *in, bool expected) {
+        bool result = is_ipv4(in);
+        if (result == expected) {
+                return true;
+        }
+        fprintf(stdout,
+                "FAILED: is_ipv4('%s') - Expected %s, but got %s\n",
+                in,
+                bool_to_str(expected),
+                bool_to_str(result));
+        return false;
+}
+
+
+int main() {
+        bool result = true;
+
+        result = result && test_is_ipv4(NULL, false);
+        result = result && test_is_ipv4("", false);
+        result = result && test_is_ipv4(".", false);
+        result = result && test_is_ipv4("fe80::78b3:75ff:fe1b:6803", false);
+        result = result && test_is_ipv4("192.168.1.5", true);
+        result = result && test_is_ipv4("10.10.10.5", true);
+        result = result && test_is_ipv4("?10.10.10.3", false);
+        result = result && test_is_ipv4("mydomain.com", false);
+        result = result && test_is_ipv4("192.168.1.", false);
+        if (result) {
+                return EXIT_SUCCESS;
+        }
+        return EXIT_FAILURE;
+}
diff --git a/src/libhirte/test/common/network/is_ipv6_test.c b/src/libhirte/test/common/network/is_ipv6_test.c
new file mode 100644
index 0000000000..c18923c47c
--- /dev/null
+++ b/src/libhirte/test/common/network/is_ipv6_test.c
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#include <assert.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "libhirte/common/network.h"
+#include "libhirte/common/string-util.h"
+
+bool test_is_ipv6(const char *in, bool expected) {
+        bool result = is_ipv6(in);
+        if (result == expected) {
+                return true;
+        }
+        fprintf(stdout,
+                "FAILED: is_ipv6('%s') - Expected %s, but got %s\n",
+                in,
+                bool_to_str(expected),
+                bool_to_str(result));
+        return false;
+}
+
+
+int main() {
+        bool result = true;
+
+        result = result && test_is_ipv6(NULL, false);
+        result = result && test_is_ipv6("", false);
+        result = result && test_is_ipv6(".", false);
+        result = result && test_is_ipv6("fe80::78b3:75ff:fe1b:6803", true);
+        result = result && test_is_ipv6("1050:0000:0000:0000:0005:0600:300c:326b", true);
+        result = result && test_is_ipv6("1050:0:0:0:5:600:300c:326b", true);
+        result = result && test_is_ipv6("1050:0:0:0:5:600:300c:326b", true);
+        result = result && test_is_ipv6("ff06:0:0:0:0:0:0:c3", true);
+        result = result && test_is_ipv6("ff06::c3", true);
+        result = result && test_is_ipv6("192.168.1.5", false);
+        result = result && test_is_ipv6("10.10.10.5", false);
+        result = result && test_is_ipv6("?10.10.10.3", false);
+        result = result && test_is_ipv6("mydomain.com", false);
+        result = result && test_is_ipv6("192.168.1.", false);
+        if (result) {
+                return EXIT_SUCCESS;
+        }
+        return EXIT_FAILURE;
+}
diff --git a/src/libhirte/test/common/network/meson.build b/src/libhirte/test/common/network/meson.build
index 37e83612e2..35ffe91a4e 100644
--- a/src/libhirte/test/common/network/meson.build
+++ b/src/libhirte/test/common/network/meson.build
@@ -1,8 +1,8 @@
 # SPDX-License-Identifier: GPL-2.0-or-later
 
 network_src = [
-  'isIPv4Addr_test',
-  'isIPv6Addr_test',
+  'is_ipv4_test',
+  'is_ipv6_test',
   'get_address_test',
   'assemble_tcp_address',
 ]
diff --git a/tests/plans/tier0.fmf b/tests/plans/tier0.fmf
index bfc88e9251..cf6e26037a 100644
--- a/tests/plans/tier0.fmf
+++ b/tests/plans/tier0.fmf
@@ -5,7 +5,7 @@ discover:
 provision:
     how: local
 environment:
-    CONTAINER_USED: container-hirte-snapshot
+    CONTAINER_USED: integration-test-snapshot
     HIRTE_IMAGE_NAME: hirte-image
     TEST_NET_RANGE: 10.10.0.0/24
     MGR_PORT: 8420
diff --git a/tests/tests/tier0/hirte-agent-resolve-fqdn/main.fmf b/tests/tests/tier0/hirte-agent-resolve-fqdn/main.fmf
new file mode 100644
index 0000000000..fba53153e6
--- /dev/null
+++ b/tests/tests/tier0/hirte-agent-resolve-fqdn/main.fmf
@@ -0,0 +1,1 @@
+summary: Test if a local hirte-agent on the same node as the controllre can connect via the localhost name
diff --git a/tests/tests/tier0/hirte-agent-resolve-fqdn/test_hirte_agent_resolve_fqdn.py b/tests/tests/tier0/hirte-agent-resolve-fqdn/test_hirte_agent_resolve_fqdn.py
new file mode 100644
index 0000000000..555a70a18f
--- /dev/null
+++ b/tests/tests/tier0/hirte-agent-resolve-fqdn/test_hirte_agent_resolve_fqdn.py
@@ -0,0 +1,48 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+import pytest
+from typing import Dict
+
+from hirte_test.fixtures import get_primary_ip
+from hirte_test.test import HirteTest
+from hirte_test.container import HirteControllerContainer, HirteNodeContainer
+from hirte_test.config import HirteControllerConfig, HirteNodeConfig
+
+
+local_node_name = "local-foo"
+
+
+def create_local_node_config() -> HirteNodeConfig:
+    return HirteNodeConfig(
+        file_name="agent.conf",
+        manager_host=get_primary_ip(),
+        manager_port='8420')
+
+
+def verify_resolving_fqdn(ctrl: HirteControllerContainer, _: Dict[str, HirteNodeContainer]):
+    result, output = ctrl.exec_run('systemctl is-active hirte')
+    assert result == 0
+    assert output == 'active'
+
+    # create config for local hirte-agent and adding config to controller container
+    local_node_cfg = create_local_node_config()
+    local_node_cfg.node_name = local_node_name
+    local_node_cfg.manager_host = "localhost"
+    ctrl.create_file(local_node_cfg.get_confd_dir(), local_node_cfg.file_name, local_node_cfg.serialize())
+
+    ctrl.exec_run('systemctl start hirte-agent')
+    result, output = ctrl.exec_run('systemctl is-active hirte-agent')
+    assert result == 0
+    assert output == 'active'
+
+    result, output = ctrl.exec_run(f'hirtectl list-units {local_node_name}')
+    assert result == 0
+
+
+@pytest.mark.timeout(5)
+def test_agent_resolve_fqdn(hirte_test: HirteTest, hirte_ctrl_default_config: HirteControllerConfig):
+    hirte_ctrl_default_config.allowed_node_names = [local_node_name]
+
+    hirte_test.set_hirte_controller_config(hirte_ctrl_default_config)
+
+    hirte_test.run(verify_resolving_fqdn)