Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add sample integ tests for latest systemd unit file #17410

Merged
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG-3.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- Add execution_hint to cardinality aggregator request (#[17312](https://github.com/opensearch-project/OpenSearch/pull/17312))
- Arrow Flight RPC plugin with Flight server bootstrap logic and client for internode communication ([#16962](https://github.com/opensearch-project/OpenSearch/pull/16962))
- Added offset management for the pull-based Ingestion ([#17354](https://github.com/opensearch-project/OpenSearch/pull/17354))
- Added integ tests for systemd configs ([#17410](https://github.com/opensearch-project/OpenSearch/pull/17410))
- Add filter function for AbstractQueryBuilder, BoolQueryBuilder, ConstantScoreQueryBuilder([#17409](https://github.com/opensearch-project/OpenSearch/pull/17409))
- [Star Tree] [Search] Resolving keyword & numeric bucket aggregation with metric aggregation using star-tree ([#17165](https://github.com/opensearch-project/OpenSearch/pull/17165))


### Dependencies
- Update Apache Lucene to 10.1.0 ([#16366](https://github.com/opensearch-project/OpenSearch/pull/16366))
- Bump Apache HttpCore5/HttpClient5 dependencies from 5.2.5/5.3.1 to 5.3.1/5.4.1 to support ExtendedSocketOption in HttpAsyncClient ([#16757](https://github.com/opensearch-project/OpenSearch/pull/16757))
Expand Down
1 change: 1 addition & 0 deletions qa/systemd-test/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
apply plugin: 'opensearch.standalone-test'
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/

package org.opensearch.systemdinteg;
import org.apache.lucene.tests.util.LuceneTestCase;

import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpStatus;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.io.BufferedReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.Locale;

import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;


public class SystemdIntegTests extends LuceneTestCase {

private static String opensearchPid;

@BeforeClass
public static void setup() throws IOException, InterruptedException {
opensearchPid = getOpenSearchPid();

if (opensearchPid.isEmpty()) {
throw new RuntimeException("Failed to find OpenSearch process ID");
}
}

private static String getOpenSearchPid() throws IOException, InterruptedException {
String command = "systemctl show --property=MainPID opensearch";
String output = executeCommand(command, "Failed to get OpenSearch PID");
return output.replace("MainPID=", "").trim();
}

private boolean checkPathExists(String path) throws IOException, InterruptedException {
String command = String.format(Locale.ROOT, "test -e %s && echo true || echo false", path);
return Boolean.parseBoolean(executeCommand(command, "Failed to check path existence"));
}

private boolean checkPathReadable(String path) throws IOException, InterruptedException {
String command = String.format(Locale.ROOT, "sudo su opensearch -s /bin/sh -c 'test -r %s && echo true || echo false'", path);
return Boolean.parseBoolean(executeCommand(command, "Failed to check read permission"));
}

private boolean checkPathWritable(String path) throws IOException, InterruptedException {
String command = String.format(Locale.ROOT, "sudo su opensearch -s /bin/sh -c 'test -w %s && echo true || echo false'", path);
return Boolean.parseBoolean(executeCommand(command, "Failed to check write permission"));
}

private String getPathOwnership(String path) throws IOException, InterruptedException {
String command = String.format(Locale.ROOT, "stat -c '%%U:%%G' %s", path);
return executeCommand(command, "Failed to get path ownership");
}

private static String executeCommand(String command, String errorMessage) throws IOException, InterruptedException {
Process process = Runtime.getRuntime().exec(new String[]{"bash", "-c", command});
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
StringBuilder output = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
}
if (process.waitFor() != 0) {
throw new RuntimeException(errorMessage);
}
return output.toString().trim();
}
}

public void testReadOnlyPaths() throws IOException, InterruptedException {
String[] readOnlyPaths = {
"/etc/os-release", "/usr/lib/os-release", "/etc/system-release",
"/proc/self/mountinfo", "/proc/diskstats",
"/proc/self/cgroup", "/sys/fs/cgroup/cpu", "/sys/fs/cgroup/cpu/-",
"/sys/fs/cgroup/cpuacct", "/sys/fs/cgroup/cpuacct/-",
"/sys/fs/cgroup/memory", "/sys/fs/cgroup/memory/-"
};

for (String path : readOnlyPaths) {
if (checkPathExists(path)) {
assertTrue("Path should be readable: " + path, checkPathReadable(path));
assertFalse("Path should not be writable: " + path, checkPathWritable(path));
}
}
}

public void testReadWritePaths() throws IOException, InterruptedException {
String[] readWritePaths = {"/var/log/opensearch", "/var/lib/opensearch"};
for (String path : readWritePaths) {
assertTrue("Path should exist: " + path, checkPathExists(path));
assertTrue("Path should be readable: " + path, checkPathReadable(path));
assertTrue("Path should be writable: " + path, checkPathWritable(path));
assertEquals("Path should be owned by opensearch:opensearch", "opensearch:opensearch", getPathOwnership(path));
}
}

public void testMaxProcesses() throws IOException, InterruptedException {
String limits = executeCommand("sudo su -c 'cat /proc/" + opensearchPid + "/limits'", "Failed to read process limits");
assertTrue("Max processes limit should be 4096 or unlimited",
limits.contains("Max processes 4096 4096") ||
limits.contains("Max processes unlimited unlimited"));
}

public void testFileDescriptorLimit() throws IOException, InterruptedException {
String limits = executeCommand("sudo su -c 'cat /proc/" + opensearchPid + "/limits'", "Failed to read process limits");
assertTrue("File descriptor limit should be at least 65535",
limits.contains("Max open files 65535 65535") ||
limits.contains("Max open files unlimited unlimited"));
}

public void testSystemCallFilter() throws IOException, InterruptedException {
// Check if Seccomp is enabled
String seccomp = executeCommand("sudo su -c 'grep Seccomp /proc/" + opensearchPid + "/status'", "Failed to read Seccomp status");
assertFalse("Seccomp should be enabled", seccomp.contains("0"));

// Test specific system calls that should be blocked
String rebootResult = executeCommand("sudo su opensearch -c 'kill -s SIGHUP 1' 2>&1 || echo 'Operation not permitted'", "Failed to test reboot system call");
assertTrue("Reboot system call should be blocked", rebootResult.contains("Operation not permitted"));

String swapResult = executeCommand("sudo su opensearch -c 'swapon -a' 2>&1 || echo 'Operation not permitted'", "Failed to test swap system call");
assertTrue("Swap system call should be blocked", swapResult.contains("Operation not permitted"));
}

public void testOpenSearchProcessCannotExit() throws IOException, InterruptedException {

String scriptPath;
try {
scriptPath = SystemdIntegTests.class.getResource("/scripts/terminate.sh").toURI().getPath();
} catch (URISyntaxException e) {
throw new RuntimeException("Failed to convert URL to URI", e);
}

if (scriptPath == null) {
throw new IllegalStateException("Could not find terminate.sh script in resources");
}
ProcessBuilder processBuilder = new ProcessBuilder(scriptPath, opensearchPid);
Process process = processBuilder.start();

// Wait a moment for any potential termination to take effect
Thread.sleep(2000);

// Verify the OpenSearch service status
String serviceStatus = executeCommand(
"systemctl is-active opensearch",
"Failed to check OpenSearch service status"
);

assertEquals("OpenSearch service should be active", "active", serviceStatus.trim());
}

}
12 changes: 12 additions & 0 deletions qa/systemd-test/src/test/resources/scripts/terminate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/sh

if [ $# -ne 1 ]; then
echo "Usage: $0 <PID>"
exit 1
fi

if kill -15 $1 2>/dev/null; then
echo "SIGTERM signal sent to process $1"
else
echo "Failed to send SIGTERM to process $1"
fi
Loading