diff --git a/kura/org.eclipse.kura.linux.net/src/main/java/org/eclipse/kura/linux/net/dhcp/DhcpServerManager.java b/kura/org.eclipse.kura.linux.net/src/main/java/org/eclipse/kura/linux/net/dhcp/DhcpServerManager.java index a6ee0ad1d69..53b9ad3c345 100644 --- a/kura/org.eclipse.kura.linux.net/src/main/java/org/eclipse/kura/linux/net/dhcp/DhcpServerManager.java +++ b/kura/org.eclipse.kura.linux.net/src/main/java/org/eclipse/kura/linux/net/dhcp/DhcpServerManager.java @@ -1,12 +1,12 @@ /******************************************************************************* - * Copyright (c) 2011, 2023 Eurotech and/or its affiliates and others - * + * Copyright (c) 2011, 2024 Eurotech and/or its affiliates and others + * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ - * + * * SPDX-License-Identifier: EPL-2.0 - * + * * Contributors: * Eurotech *******************************************************************************/ @@ -58,7 +58,8 @@ public DhcpServerManager(CommandExecutorService service) { public static DhcpServerTool getTool() { if (dhcpServerTool == DhcpServerTool.NONE) { - if (LinuxNetworkUtil.toolExists(DhcpServerTool.DNSMASQ.getValue())) { + if (LinuxNetworkUtil.toolExists(DhcpServerTool.DNSMASQ.getValue()) + && LinuxNetworkUtil.systemdSystemUnitExists(DhcpServerTool.DNSMASQ.getValue() + ".service")) { dhcpServerTool = DhcpServerTool.DNSMASQ; } else if (LinuxNetworkUtil.toolExists(DhcpServerTool.UDHCPD.getValue())) { dhcpServerTool = DhcpServerTool.UDHCPD; diff --git a/kura/org.eclipse.kura.linux.net/src/main/java/org/eclipse/kura/linux/net/dhcp/server/DnsmasqTool.java b/kura/org.eclipse.kura.linux.net/src/main/java/org/eclipse/kura/linux/net/dhcp/server/DnsmasqTool.java index 9d6fa752a4f..6f62abdecba 100644 --- a/kura/org.eclipse.kura.linux.net/src/main/java/org/eclipse/kura/linux/net/dhcp/server/DnsmasqTool.java +++ b/kura/org.eclipse.kura.linux.net/src/main/java/org/eclipse/kura/linux/net/dhcp/server/DnsmasqTool.java @@ -12,6 +12,7 @@ *******************************************************************************/ package org.eclipse.kura.linux.net.dhcp.server; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -42,10 +43,10 @@ public class DnsmasqTool implements DhcpLinuxTool { private String globalConfigFilename = "/etc/dnsmasq.d/dnsmasq-globals.conf"; private static final String GLOBAL_CONFIGURATION = "port=0\nbind-dynamic\n"; - static final Command IS_ACTIVE_COMMAND = new Command(new String[] { "systemctl", "is-active", "--quiet", - DhcpServerTool.DNSMASQ.getValue() }); - static final Command RESTART_COMMAND = new Command(new String[] { "systemctl", "restart", - DhcpServerTool.DNSMASQ.getValue() }); + static final String[] IS_ACTIVE_COMMANDLINE = new String[] { "systemctl", "is-active", "--quiet", + DhcpServerTool.DNSMASQ.getValue() }; + static final String[] RESTART_COMMANDLINE = new String[] { "systemctl", "restart", + DhcpServerTool.DNSMASQ.getValue() }; private CommandExecutorService executorService; private Map configsLastHash = Collections.synchronizedMap(new HashMap<>()); @@ -57,7 +58,7 @@ public DnsmasqTool(CommandExecutorService service) { @Override public boolean isRunning(String interfaceName) throws KuraProcessExecutionErrorException { - CommandStatus status = this.executorService.execute(IS_ACTIVE_COMMAND); + CommandStatus status = this.executorService.execute(new Command(IS_ACTIVE_COMMANDLINE)); boolean isRunning; try { @@ -85,11 +86,19 @@ public CommandStatus startInterface(String interfaceName) throws KuraProcessExec "Failed to start DHCP server for interface: " + interfaceName); } - CommandStatus restartStatus = this.executorService.execute(RESTART_COMMAND); + Command restartCommand = new Command(RESTART_COMMANDLINE); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayOutputStream err = new ByteArrayOutputStream(); + restartCommand.setErrorStream(err); + restartCommand.setOutputStream(out); + CommandStatus restartStatus = this.executorService.execute(restartCommand); if (!restartStatus.getExitStatus().isSuccessful()) { + logger.error("dnsmasq Systemd unit startup failed. Check if the tool is properly installed on the system."); + logger.error("dnsmasq stderr {}", new String(err.toByteArray(), StandardCharsets.UTF_8)); + logger.error("dnsmasq stdout {}", new String(out.toByteArray(), StandardCharsets.UTF_8)); removeInterfaceConfig(interfaceName); - restartStatus = this.executorService.execute(RESTART_COMMAND); + restartStatus = this.executorService.execute(restartCommand); } return restartStatus; @@ -100,7 +109,7 @@ public boolean disableInterface(String interfaceName) throws KuraProcessExecutio boolean isInterfaceDisabled = true; if (removeInterfaceConfig(interfaceName)) { - CommandStatus status = this.executorService.execute(RESTART_COMMAND); + CommandStatus status = this.executorService.execute(new Command(RESTART_COMMANDLINE)); isInterfaceDisabled = status.getExitStatus().isSuccessful(); } diff --git a/kura/org.eclipse.kura.linux.net/src/main/java/org/eclipse/kura/linux/net/util/LinuxNetworkUtil.java b/kura/org.eclipse.kura.linux.net/src/main/java/org/eclipse/kura/linux/net/util/LinuxNetworkUtil.java index 1d964c0a210..cafdcbd7125 100644 --- a/kura/org.eclipse.kura.linux.net/src/main/java/org/eclipse/kura/linux/net/util/LinuxNetworkUtil.java +++ b/kura/org.eclipse.kura.linux.net/src/main/java/org/eclipse/kura/linux/net/util/LinuxNetworkUtil.java @@ -1,12 +1,12 @@ /******************************************************************************* - * Copyright (c) 2011, 2022 Eurotech and/or its affiliates and others - * + * Copyright (c) 2011, 2024 Eurotech and/or its affiliates and others + * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ - * + * * SPDX-License-Identifier: EPL-2.0 - * + * * Contributors: * Eurotech *******************************************************************************/ @@ -44,12 +44,9 @@ public class LinuxNetworkUtil { - public static final String ACCESS_POINT_INTERFACE_SUFFIX = "_ap"; - - private static final String ETHTOOL_COMMAND = "ethtool"; - private static final Logger logger = LoggerFactory.getLogger(LinuxNetworkUtil.class); + public static final String ACCESS_POINT_INTERFACE_SUFFIX = "_ap"; private static Map ifconfigs = new HashMap<>(); private static final String[] IGNORE_IFACES = { "can", "sit", "mon.wlan" }; private static final ArrayList TOOLS = new ArrayList<>(); @@ -65,13 +62,12 @@ public class LinuxNetworkUtil { private static final String IFCONFIG = "ifconfig"; private static final String IWCONFIG = "iwconfig"; private static final String IP = "ip"; - private static final String LINE_MSG = "line: {}"; - private static final String ERR_EXECUTING_CMD_MSG = "error executing command --- {} --- exit value={}"; - private static final String FAKE_MAC_ADDRESS = "12:34:56:78:ab:cd"; - + private static final String ETHTOOL_COMMAND = "ethtool"; + private static final String[] DEFAULT_PATH = new String[] { "/sbin/", "/usr/sbin/", "/bin/", "/usr/bin/" }; + private static final ProcessBuilder PROCESS_BUILDER = new ProcessBuilder(); private final CommandExecutorService executorService; private final WifiOptions wifiOptions; @@ -264,22 +260,59 @@ private boolean isWifiLinkUpInternal(String ifaceName) throws KuraException { } public static boolean toolExists(String tool) { - boolean ret = false; - final String[] searchFolders = new String[] { "/sbin/", "/usr/sbin/", "/bin/", "/usr/bin/" }; + return getToolPath(tool).isPresent(); + } - if (TOOLS.contains(tool)) { - ret = true; + public static Optional getToolPath(String tool) { + Optional optionalToolPath = getCachedTool(tool); + if (optionalToolPath.isPresent()) { + return optionalToolPath; } else { - for (String folder : searchFolders) { - File fTool = new File(folder + tool); - if (fTool.exists()) { - TOOLS.add(tool); - ret = true; - break; + for (String folder : DEFAULT_PATH) { + String toolPath = folder + tool; + File toolFile = new File(toolPath); + if (toolFile.exists()) { + TOOLS.add(toolPath); + return Optional.of(toolPath); } } } - return ret; + return Optional.empty(); + } + + private static Optional getCachedTool(String tool) { + return TOOLS.stream().filter(item -> item.endsWith(tool)).findFirst(); + } + + /** + * Checks if the given Systemd system unit is installed. + * The result is based on the exit code of "systemctl status " + * as presented in https://www.man7.org/linux/man-pages/man1/systemctl.1.html#EXIT_STATUS + * + * @param unitName + * the name of the Systemd system unit + * @return true if the unit is installed + */ + public static boolean systemdSystemUnitExists(String unitName) { + Optional optionalSystemctlPath = getToolPath("systemctl"); + if (!optionalSystemctlPath.isPresent()) { + logger.debug("Systemctl command not found in default paths"); + return false; + } + PROCESS_BUILDER.command(optionalSystemctlPath.get(), "status", unitName); + Process process; + try { + process = PROCESS_BUILDER.start(); + int exitCode = process.waitFor(); + return exitCode < 4; + } catch (IOException e) { + logger.error("Cannot check {} unit existence", unitName, e); + return false; + } catch (InterruptedException e1) { + Thread.currentThread().interrupt(); + logger.error("Cannot check {} unit existence", unitName, e1); + return false; + } } /** @@ -1124,7 +1157,7 @@ public void createApNetworkInterface(String ifaceName, String dedicatedApInterfa return; } - CommandStatus status = this.executeCommand(formIwDevIfaceInterfaceAddAp(ifaceName, dedicatedApInterface)); + CommandStatus status = executeCommand(formIwDevIfaceInterfaceAddAp(ifaceName, dedicatedApInterface)); if (!status.getExitStatus().isSuccessful()) { throw new KuraException(KuraErrorCode.OS_COMMAND_ERROR, @@ -1137,7 +1170,7 @@ public void setNetworkInterfaceMacAddress(String ifaceName) throws KuraException return; } - CommandStatus status = this.executeCommand(formIpLinkSetAddress(ifaceName, FAKE_MAC_ADDRESS)); + CommandStatus status = executeCommand(formIpLinkSetAddress(ifaceName, FAKE_MAC_ADDRESS)); if (!status.getExitStatus().isSuccessful()) { throw new KuraException(KuraErrorCode.OS_COMMAND_ERROR, @@ -1150,7 +1183,7 @@ public void setNetworkInterfaceLinkUp(String ifaceName) throws KuraException { return; } - CommandStatus status = this.executeCommand(formIpLinkSetStatus(ifaceName, "up")); + CommandStatus status = executeCommand(formIpLinkSetStatus(ifaceName, "up")); if (!status.getExitStatus().isSuccessful()) { throw new KuraException(KuraErrorCode.OS_COMMAND_ERROR, "Failed to set link up for interface " + ifaceName); @@ -1162,7 +1195,7 @@ public void setNetworkInterfaceLinkDown(String ifaceName) throws KuraException { return; } - CommandStatus status = this.executeCommand(formIpLinkSetStatus(ifaceName, "down")); + CommandStatus status = executeCommand(formIpLinkSetStatus(ifaceName, "down")); if (!status.getExitStatus().isSuccessful()) { throw new KuraException(KuraErrorCode.OS_COMMAND_ERROR, "Failed to set link up for interface " + ifaceName); diff --git a/kura/test/org.eclipse.kura.linux.net.test/src/test/java/org/eclipse/kura/linux/net/dhcp/DhcpServerManagerTest.java b/kura/test/org.eclipse.kura.linux.net.test/src/test/java/org/eclipse/kura/linux/net/dhcp/DhcpServerManagerTest.java index 284219f6b19..2ab9d3e69a5 100644 --- a/kura/test/org.eclipse.kura.linux.net.test/src/test/java/org/eclipse/kura/linux/net/dhcp/DhcpServerManagerTest.java +++ b/kura/test/org.eclipse.kura.linux.net.test/src/test/java/org/eclipse/kura/linux/net/dhcp/DhcpServerManagerTest.java @@ -1,12 +1,12 @@ /******************************************************************************* - * Copyright (c) 2017, 2023 Eurotech and/or its affiliates and others - * + * Copyright (c) 2017, 2024 Eurotech and/or its affiliates and others + * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ - * + * * SPDX-License-Identifier: EPL-2.0 - * + * * Contributors: * Eurotech ******************************************************************************/ @@ -27,9 +27,12 @@ import org.eclipse.kura.linux.net.dhcp.server.DnsmasqLeaseReader; import org.eclipse.kura.linux.net.dhcp.server.UdhcpdConfigConverter; import org.eclipse.kura.linux.net.dhcp.server.UdhcpdLeaseReader; +import org.eclipse.kura.linux.net.util.LinuxNetworkUtil; +import org.junit.After; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; +import org.mockito.MockedStatic; import org.mockito.Mockito; public class DhcpServerManagerTest { @@ -50,6 +53,8 @@ public class DhcpServerManagerTest { private Optional returnedConfigConverter; private Optional returnedLeaseReader; private Exception occurredException; + private MockedStatic mockedLinuxNetworkUtil; + private DhcpServerTool tool; /* * Scenarios @@ -268,6 +273,63 @@ public void shouldReturnLeaseReaderForNone() throws NoSuchFieldException { thenReturnedLeaseReaderIsEmpty(); } + @Test + public void shouldGetDnsmasqToolIfBinaryAndUnitArePresent() throws NoSuchFieldException { + givenLinuxNetworkUtil("dnsmasq", Optional.of("dnsmasq.service")); + givenDhcpServerManager(DhcpServerTool.NONE); + + whenGetTool(); + + thenToolIs(DhcpServerTool.DNSMASQ); + } + + @Test + public void shouldNotGetDnsmasqToolIfOnlyBinaryIsPresent() throws NoSuchFieldException { + givenLinuxNetworkUtil("dnsmasq", Optional.empty()); + givenDhcpServerManager(DhcpServerTool.NONE); + + whenGetTool(); + + thenToolIs(DhcpServerTool.NONE); + } + + @Test + public void shouldGetDhcpdTool() throws NoSuchFieldException { + givenLinuxNetworkUtil("dhcpd", Optional.empty()); + givenDhcpServerManager(DhcpServerTool.NONE); + + whenGetTool(); + + thenToolIs(DhcpServerTool.DHCPD); + } + + @Test + public void shouldGetUdhcpdTool() throws NoSuchFieldException { + givenLinuxNetworkUtil("udhcpd", Optional.empty()); + givenDhcpServerManager(DhcpServerTool.NONE); + + whenGetTool(); + + thenToolIs(DhcpServerTool.UDHCPD); + } + + @Test + public void shouldNotGetAnyTool() throws NoSuchFieldException { + givenLinuxNetworkUtil("", Optional.empty()); + givenDhcpServerManager(DhcpServerTool.NONE); + + whenGetTool(); + + thenToolIs(DhcpServerTool.NONE); + } + + @After + public void deregisterStaticMocks() { + if (this.mockedLinuxNetworkUtil != null) { + this.mockedLinuxNetworkUtil.close(); + } + } + /* * Steps */ @@ -281,7 +343,16 @@ private void givenDhcpServerManager(DhcpServerTool dhcpServerTool) throws NoSuch TestUtil.setFieldValue(new DhcpServerManager(null), "dhcpServerTool", dhcpServerTool); - this.dhcpServerManager = new DhcpServerManager(executorMock); + this.dhcpServerManager = new DhcpServerManager(this.executorMock); + } + + private void givenLinuxNetworkUtil(String toolName, Optional unitName) { + this.mockedLinuxNetworkUtil = Mockito.mockStatic(LinuxNetworkUtil.class); + this.mockedLinuxNetworkUtil.when(() -> LinuxNetworkUtil.toolExists(toolName)).thenReturn(true); + if (unitName.isPresent()) { + this.mockedLinuxNetworkUtil.when(() -> LinuxNetworkUtil.systemdSystemUnitExists(unitName.get())) + .thenReturn(true); + } } /* @@ -328,6 +399,14 @@ private void whenGetLeaseReader() { } } + private void whenGetTool() { + try { + this.tool = DhcpServerManager.getTool(); + } catch (Exception e) { + this.occurredException = e; + } + } + /* * Then */ @@ -364,4 +443,7 @@ private void thenReturnedLeaseReaderIs(Class dhcpServerLeaseReader) { assertEquals(dhcpServerLeaseReader, this.returnedLeaseReader.get().getClass()); } + private void thenToolIs(DhcpServerTool expectedTool) { + assertEquals(expectedTool, this.tool); + } } diff --git a/kura/test/org.eclipse.kura.linux.net.test/src/test/java/org/eclipse/kura/linux/net/dhcp/server/DhcpdToolTest.java b/kura/test/org.eclipse.kura.linux.net.test/src/test/java/org/eclipse/kura/linux/net/dhcp/server/DhcpdToolTest.java index 601714f3fe3..8e1d3795ed4 100644 --- a/kura/test/org.eclipse.kura.linux.net.test/src/test/java/org/eclipse/kura/linux/net/dhcp/server/DhcpdToolTest.java +++ b/kura/test/org.eclipse.kura.linux.net.test/src/test/java/org/eclipse/kura/linux/net/dhcp/server/DhcpdToolTest.java @@ -29,6 +29,7 @@ import java.util.Map; import org.eclipse.kura.KuraProcessExecutionErrorException; +import org.eclipse.kura.executor.Command; import org.eclipse.kura.executor.CommandExecutorService; import org.eclipse.kura.executor.CommandStatus; import org.eclipse.kura.executor.ExitStatus; @@ -160,7 +161,7 @@ public void shouldReturnFalseWhenDisablingInterfaceandNoRunningTool() throws Exc thenDisableInterfaceReturned(false); } - + /* * Steps */ @@ -183,9 +184,10 @@ public int getExitCode() { public boolean isSuccessful() { return isSuccessful; } - + }; - CommandStatus returnedStatus = new CommandStatus(DnsmasqTool.IS_ACTIVE_COMMAND, returnedExitStatus); + CommandStatus returnedStatus = new CommandStatus(new Command(DnsmasqTool.IS_ACTIVE_COMMANDLINE), + returnedExitStatus); when(this.mockExecutor.execute(any())).thenReturn(returnedStatus); when(this.mockExecutor.isRunning(any(String[].class))).thenReturn(isSuccessful); @@ -197,7 +199,8 @@ private void givenDhcpdTool(DhcpServerTool tool) { private void givenDhcpServerManagerReturn(String interfaceName, String returnedConfigFilename) { mockServerManager = mockStatic(DhcpServerManager.class); - mockServerManager.when(() -> DhcpServerManager.getPidFilename(interfaceName)).thenReturn(returnedConfigFilename); + mockServerManager.when(() -> DhcpServerManager.getPidFilename(interfaceName)) + .thenReturn(returnedConfigFilename); } private void givenConfigFile(String filename) throws IOException { @@ -212,7 +215,7 @@ private void givenRunningPids() { public int getPid() { return 1234; } - + }); when(this.mockExecutor.getPids(any())).thenReturn(this.runningPids); diff --git a/kura/test/org.eclipse.kura.linux.net.test/src/test/java/org/eclipse/kura/linux/net/dhcp/server/DnsmasqToolTest.java b/kura/test/org.eclipse.kura.linux.net.test/src/test/java/org/eclipse/kura/linux/net/dhcp/server/DnsmasqToolTest.java index 2e8be8ead44..077fb8e257c 100644 --- a/kura/test/org.eclipse.kura.linux.net.test/src/test/java/org/eclipse/kura/linux/net/dhcp/server/DnsmasqToolTest.java +++ b/kura/test/org.eclipse.kura.linux.net.test/src/test/java/org/eclipse/kura/linux/net/dhcp/server/DnsmasqToolTest.java @@ -24,6 +24,7 @@ import java.io.IOException; import org.eclipse.kura.KuraProcessExecutionErrorException; +import org.eclipse.kura.executor.Command; import org.eclipse.kura.executor.CommandExecutorService; import org.eclipse.kura.executor.CommandStatus; import org.eclipse.kura.executor.ExitStatus; @@ -47,7 +48,7 @@ public class DnsmasqToolTest { private CommandStatus startInterfaceStatus; private boolean interfaceDisabled; private Exception occurredException; - + /* * Scenarios */ @@ -151,9 +152,10 @@ public int getExitCode() { public boolean isSuccessful() { return isSuccessful; } - + }; - CommandStatus returnedStatus = new CommandStatus(DnsmasqTool.IS_ACTIVE_COMMAND, returnedExitStatus); + CommandStatus returnedStatus = new CommandStatus(new Command(DnsmasqTool.IS_ACTIVE_COMMANDLINE), + returnedExitStatus); when(this.mockExecutor.execute(any())).thenReturn(returnedStatus); } @@ -166,13 +168,15 @@ private void givenDhcpServerManagerReturn(String interfaceName, String returnedC private void givenConfigFile(String filename) throws IOException { try { this.tmpFolder.newFolder("etc", "dnsmasq.d"); - } catch (Exception e) {} + } catch (Exception e) { + } this.tmpConfigFile = this.tmpFolder.newFile(filename); } private void givenDnsmasqTool() throws Exception { this.tool = new DnsmasqTool(this.mockExecutor); - this.tool.setDnsmasqGlobalConfigFile(this.tmpConfigFile.getAbsoluteFile().getParent() + "/dnsmasq-globals.conf"); + this.tool + .setDnsmasqGlobalConfigFile(this.tmpConfigFile.getAbsoluteFile().getParent() + "/dnsmasq-globals.conf"); } private void givenStartInterface(String interfaceName) throws KuraProcessExecutionErrorException { @@ -196,7 +200,7 @@ private void whenDisableInterface(String interfaceName) throws KuraProcessExecut this.interfaceDisabled = this.tool.disableInterface(interfaceName); } catch (Exception e) { this.occurredException = e; - } + } } /* diff --git a/kura/test/org.eclipse.kura.linux.net.test/src/test/java/org/eclipse/kura/linux/net/util/LinuxNetworkUtilTest.java b/kura/test/org.eclipse.kura.linux.net.test/src/test/java/org/eclipse/kura/linux/net/util/LinuxNetworkUtilTest.java index 98f2b86db94..cff0daa5f6a 100644 --- a/kura/test/org.eclipse.kura.linux.net.test/src/test/java/org/eclipse/kura/linux/net/util/LinuxNetworkUtilTest.java +++ b/kura/test/org.eclipse.kura.linux.net.test/src/test/java/org/eclipse/kura/linux/net/util/LinuxNetworkUtilTest.java @@ -1,19 +1,34 @@ /******************************************************************************* - * Copyright (c) 2022 Eurotech and/or its affiliates and others - * + * Copyright (c) 2022, 2024 Eurotech and/or its affiliates and others + * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ - * + * * SPDX-License-Identifier: EPL-2.0 - * + * * Contributors: * Eurotech *******************************************************************************/ package org.eclipse.kura.linux.net.util; import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Optional; import org.eclipse.kura.KuraException; import org.eclipse.kura.core.linux.executor.LinuxExitStatus; @@ -29,6 +44,9 @@ public class LinuxNetworkUtilTest { private CommandExecutorServiceStub commandExecutorServiceStub; private String macAddress; private String linkStatus; + private boolean toolExists; + private boolean systemdUnitExists; + private Optional toolPath; @Test public void createApNetworkInterface() { @@ -70,11 +88,82 @@ public void setNetworkInterfaceLinkDown() { thenNetworkInterfaceLinkIsDown(); } + @Test + public void shouldCheckToolExistence() throws NoSuchFieldException, IllegalAccessException, IOException { + givenLinuxNetworkUtil(); + givenToolPaths("/tmp/"); + givenTool("/tmp/dhcpd"); + + whenCheckTool("dhcpd"); + + thenToolExists(); + } + + @Test + public void shouldCheckSystemdUnitExistence() + throws NoSuchFieldException, IllegalAccessException, IOException, InterruptedException { + givenLinuxNetworkUtil(); + givenToolPaths("/tmp/"); + givenTool("/tmp/systemctl"); + givenProcessBuilder(0); + + whenCheckSystemdUnit("dnsmask.service"); + + thenSystemdUnitExists(); + } + + @Test + public void shouldNotCheckSystemdUnitExistence() + throws NoSuchFieldException, IllegalAccessException, IOException, InterruptedException { + givenLinuxNetworkUtil(); + givenToolPaths("/tmp/"); + givenTool("/tmp/systemctl"); + givenProcessBuilder(4); + + whenCheckSystemdUnit("dnsmask.service"); + + thenSystemdUnitNotExist(); + } + + @Test + public void shouldNotCheckSystemdUnitExistenceIfSystemctlNotExist() + throws NoSuchFieldException, IllegalAccessException, IOException, InterruptedException { + givenLinuxNetworkUtil(); + + whenCheckSystemdUnit("dnsmask.service"); + + thenSystemdUnitNotExist(); + } + + @Test + public void shouldGetToolPath() + throws NoSuchFieldException, IllegalAccessException, IOException, InterruptedException { + givenLinuxNetworkUtil(); + givenToolPaths("/tmp/"); + givenTool("/tmp/myAwesomeCommand"); + + whenGetTool("myAwesomeCommand"); + + thenToolIsRetrieved("/tmp/myAwesomeCommand"); + } + private void givenLinuxNetworkUtil() { CommandStatus status = new CommandStatus(new Command(new String[] {}), new LinuxExitStatus(0)); this.commandExecutorServiceStub = new CommandExecutorServiceStub(status); this.linuxNetworkUtil = new LinuxNetworkUtil(this.commandExecutorServiceStub); + } + private void givenToolPaths(String path) throws NoSuchFieldException, IllegalAccessException { + setFinalStaticField(LinuxNetworkUtil.class, "DEFAULT_PATH", new String[] { path }); + } + + private void givenTool(String tool) throws IOException { + Path newFilePath = Paths.get(tool); + try { + Files.createFile(newFilePath); + } catch (FileAlreadyExistsException e) { + // do nothing + } } private void givenInterfaceName(String interfaceName) { @@ -83,16 +172,36 @@ private void givenInterfaceName(String interfaceName) { private void givenMacAddress(String macAddress) { this.macAddress = macAddress; + } + private void givenLinkStatus(String linkStatus) { + this.linkStatus = linkStatus; + } + + private void givenProcessBuilder(int returnCode) + throws NoSuchFieldException, IOException, InterruptedException, IllegalAccessException { + Process mockedProcess = mock(Process.class); + when(mockedProcess.waitFor()).thenReturn(returnCode); + when(mockedProcess.getInputStream()).thenReturn(new ByteArrayInputStream(new byte[] {})); + ProcessBuilder mockedProcessBuilder = mock(ProcessBuilder.class); + when(mockedProcessBuilder.start()).thenReturn(mockedProcess); + setFinalStaticField(LinuxNetworkUtil.class, "PROCESS_BUILDER", mockedProcessBuilder); } private void whenDedicatedInterfaceName(String dedicatedInterfaceName) { this.dedicatedInterfaceName = dedicatedInterfaceName; + } + private void whenCheckTool(String toolName) { + this.toolExists = LinuxNetworkUtil.toolExists(toolName); } - private void givenLinkStatus(String linkStatus) { - this.linkStatus = linkStatus; + private void whenCheckSystemdUnit(String unitName) { + this.systemdUnitExists = LinuxNetworkUtil.systemdSystemUnitExists(unitName); + } + + private void whenGetTool(String tool) { + this.toolPath = LinuxNetworkUtil.getToolPath(tool); } private void thenApNetworkInterfaceIsCreated() { @@ -136,4 +245,31 @@ private void thenNetworkInterfaceLinkIsDown() { } } + private void thenToolExists() { + assertTrue(this.toolExists); + } + + private void thenSystemdUnitExists() { + assertTrue(this.systemdUnitExists); + } + + private void thenSystemdUnitNotExist() { + assertFalse(this.systemdUnitExists); + } + + private void thenToolIsRetrieved(String expectedToolPath) { + assertTrue(this.toolPath.isPresent()); + assertEquals(expectedToolPath, this.toolPath.get()); + } + + static void setFinalStaticField(Class clazz, String fieldName, Object value) + throws NoSuchFieldException, IllegalAccessException { + Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + Field modifiers = field.getClass().getDeclaredField("modifiers"); + modifiers.setAccessible(true); + modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL); + field.set(null, value); + } + }