Add zstd to native access (#105715)

This commit makes zstd compression available to Elasticsearch. The
library is pulled in through maven in jar files for each platform, then
bundled in a new platform directory under lib. Access to the zstd
compression/decompression is through NativeAccess.
This commit is contained in:
Ryan Ernst 2024-03-13 09:45:12 -07:00 committed by GitHub
parent 8ff083be1b
commit 405b88b882
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 870 additions and 34 deletions

View file

@ -119,6 +119,10 @@ gradlePlugin {
id = 'elasticsearch.java-doc' id = 'elasticsearch.java-doc'
implementationClass = 'org.elasticsearch.gradle.internal.ElasticsearchJavadocPlugin' implementationClass = 'org.elasticsearch.gradle.internal.ElasticsearchJavadocPlugin'
} }
javaBase {
id = 'elasticsearch.java-base'
implementationClass = 'org.elasticsearch.gradle.internal.ElasticsearchJavaBasePlugin'
}
java { java {
id = 'elasticsearch.java' id = 'elasticsearch.java'
implementationClass = 'org.elasticsearch.gradle.internal.ElasticsearchJavaPlugin' implementationClass = 'org.elasticsearch.gradle.internal.ElasticsearchJavaPlugin'

View file

@ -8,7 +8,7 @@
package org.elasticsearch.gradle.internal package org.elasticsearch.gradle.internal
import spock.lang.TempDir
import spock.lang.Unroll import spock.lang.Unroll
import com.github.tomakehurst.wiremock.WireMockServer import com.github.tomakehurst.wiremock.WireMockServer
@ -103,10 +103,6 @@ class JdkDownloadPluginFuncTest extends AbstractGradleFuncTest {
plugins { plugins {
id 'elasticsearch.jdk-download' apply false id 'elasticsearch.jdk-download' apply false
} }
subprojects {
}
""" """
3.times { 3.times {
subProject(':sub-' + it) << """ subProject(':sub-' + it) << """

View file

@ -9,6 +9,7 @@
import org.elasticsearch.gradle.util.Pair import org.elasticsearch.gradle.util.Pair
import org.elasticsearch.gradle.util.GradleUtils import org.elasticsearch.gradle.util.GradleUtils
import org.elasticsearch.gradle.internal.info.BuildParams import org.elasticsearch.gradle.internal.info.BuildParams
import org.elasticsearch.gradle.internal.test.TestUtil
import org.jetbrains.gradle.ext.JUnit import org.jetbrains.gradle.ext.JUnit
import java.nio.file.Files import java.nio.file.Files
@ -128,9 +129,13 @@ if (providers.systemProperty('idea.active').getOrNull() == 'true') {
':x-pack:plugin:esql:compute:gen:jar', ':x-pack:plugin:esql:compute:gen:jar',
':server:generateModulesList', ':server:generateModulesList',
':server:generatePluginsList', ':server:generatePluginsList',
':generateProviderImpls'].collect { elasticsearchProject.right()?.task(it) ?: it }) ':generateProviderImpls',
':libs:elasticsearch-native:elasticsearch-native-libraries:extractLibs'].collect { elasticsearchProject.right()?.task(it) ?: it })
} }
// this path is produced by the extractLibs task above
String testLibraryPath = TestUtil.getTestLibraryPath("${elasticsearchProject.left()}/libs/native/libraries/build/platform")
idea { idea {
project { project {
vcs = 'Git' vcs = 'Git'
@ -162,6 +167,8 @@ if (providers.systemProperty('idea.active').getOrNull() == 'true') {
'-ea', '-ea',
'-Djava.security.manager=allow', '-Djava.security.manager=allow',
'-Djava.locale.providers=SPI,COMPAT', '-Djava.locale.providers=SPI,COMPAT',
'-Djava.library.path=' + testLibraryPath,
'-Djna.library.path=' + testLibraryPath,
// TODO: only open these for mockito when it is modularized // TODO: only open these for mockito when it is modularized
'--add-opens=java.base/java.security.cert=ALL-UNNAMED', '--add-opens=java.base/java.security.cert=ALL-UNNAMED',
'--add-opens=java.base/java.nio.channels=ALL-UNNAMED', '--add-opens=java.base/java.nio.channels=ALL-UNNAMED',

View file

@ -12,11 +12,15 @@ import org.elasticsearch.gradle.VersionProperties;
import org.elasticsearch.gradle.internal.conventions.precommit.PrecommitTaskPlugin; import org.elasticsearch.gradle.internal.conventions.precommit.PrecommitTaskPlugin;
import org.elasticsearch.gradle.internal.info.BuildParams; import org.elasticsearch.gradle.internal.info.BuildParams;
import org.elasticsearch.gradle.internal.info.GlobalBuildInfoPlugin; import org.elasticsearch.gradle.internal.info.GlobalBuildInfoPlugin;
import org.elasticsearch.gradle.internal.test.TestUtil;
import org.elasticsearch.gradle.test.SystemPropertyCommandLineArgumentProvider;
import org.elasticsearch.gradle.util.GradleUtils; import org.elasticsearch.gradle.util.GradleUtils;
import org.gradle.api.JavaVersion; import org.gradle.api.JavaVersion;
import org.gradle.api.Plugin; import org.gradle.api.Plugin;
import org.gradle.api.Project; import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ResolutionStrategy; import org.gradle.api.artifacts.ResolutionStrategy;
import org.gradle.api.file.FileCollection;
import org.gradle.api.plugins.JavaBasePlugin; import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.provider.Provider; import org.gradle.api.provider.Provider;
@ -26,10 +30,13 @@ import org.gradle.api.tasks.compile.AbstractCompile;
import org.gradle.api.tasks.compile.CompileOptions; import org.gradle.api.tasks.compile.CompileOptions;
import org.gradle.api.tasks.compile.GroovyCompile; import org.gradle.api.tasks.compile.GroovyCompile;
import org.gradle.api.tasks.compile.JavaCompile; import org.gradle.api.tasks.compile.JavaCompile;
import org.gradle.api.tasks.testing.Test;
import org.gradle.jvm.toolchain.JavaLanguageVersion; import org.gradle.jvm.toolchain.JavaLanguageVersion;
import org.gradle.jvm.toolchain.JavaToolchainService; import org.gradle.jvm.toolchain.JavaToolchainService;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import javax.inject.Inject; import javax.inject.Inject;
@ -59,6 +66,7 @@ public class ElasticsearchJavaBasePlugin implements Plugin<Project> {
configureConfigurations(project); configureConfigurations(project);
configureCompile(project); configureCompile(project);
configureInputNormalization(project); configureInputNormalization(project);
configureNativeLibraryPath(project);
// convenience access to common versions used in dependencies // convenience access to common versions used in dependencies
project.getExtensions().getExtraProperties().set("versions", VersionProperties.getVersions()); project.getExtensions().getExtraProperties().set("versions", VersionProperties.getVersions());
@ -165,6 +173,26 @@ public class ElasticsearchJavaBasePlugin implements Plugin<Project> {
project.getNormalization().getRuntimeClasspath().ignore("IMPL-JARS/**/META-INF/MANIFEST.MF"); project.getNormalization().getRuntimeClasspath().ignore("IMPL-JARS/**/META-INF/MANIFEST.MF");
} }
private static void configureNativeLibraryPath(Project project) {
String nativeProject = ":libs:elasticsearch-native:elasticsearch-native-libraries";
Configuration nativeConfig = project.getConfigurations().create("nativeLibs");
nativeConfig.defaultDependencies(deps -> {
deps.add(project.getDependencies().project(Map.of("path", nativeProject, "configuration", "default")));
});
// This input to the following lambda needs to be serializable. Configuration is not serializable, but FileCollection is.
FileCollection nativeConfigFiles = nativeConfig;
project.getTasks().withType(Test.class).configureEach(test -> {
var systemProperties = test.getExtensions().getByType(SystemPropertyCommandLineArgumentProvider.class);
var libraryPath = (Supplier<String>) () -> TestUtil.getTestLibraryPath(nativeConfigFiles.getAsPath());
test.dependsOn(nativeConfigFiles);
// we may use JNA or the JDK's foreign function api to load libraries, so we set both sysprops
systemProperties.systemProperty("java.library.path", libraryPath);
systemProperties.systemProperty("jna.library.path", libraryPath);
});
}
private static Provider<Integer> releaseVersionProviderFromCompileTask(Project project, AbstractCompile compileTask) { private static Provider<Integer> releaseVersionProviderFromCompileTask(Project project, AbstractCompile compileTask) {
return project.provider(() -> { return project.provider(() -> {
JavaVersion javaVersion = JavaVersion.toVersion(compileTask.getTargetCompatibility()); JavaVersion javaVersion = JavaVersion.toVersion(compileTask.getTargetCompatibility());

View file

@ -105,7 +105,7 @@ public class MrjarPlugin implements Plugin<Project> {
testTask.dependsOn(jarTask); testTask.dependsOn(jarTask);
SourceSetContainer sourceSets = GradleUtils.getJavaSourceSets(project); SourceSetContainer sourceSets = GradleUtils.getJavaSourceSets(project);
FileCollection mainRuntime = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME).getRuntimeClasspath(); FileCollection mainRuntime = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME).getOutput();
FileCollection testRuntime = sourceSets.getByName(SourceSet.TEST_SOURCE_SET_NAME).getRuntimeClasspath(); FileCollection testRuntime = sourceSets.getByName(SourceSet.TEST_SOURCE_SET_NAME).getRuntimeClasspath();
testTask.setClasspath(testRuntime.minus(mainRuntime).plus(project.files(jarTask))); testTask.setClasspath(testRuntime.minus(mainRuntime).plus(project.files(jarTask)));
}); });

View file

@ -0,0 +1,25 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.gradle.internal.test;
import org.elasticsearch.gradle.Architecture;
import org.elasticsearch.gradle.ElasticsearchDistribution;
import java.util.Locale;
public class TestUtil {
public static String getTestLibraryPath(String nativeLibsDir) {
String arch = Architecture.current().toString().toLowerCase(Locale.ROOT);
String platform = String.format(Locale.ROOT, "%s-%s", ElasticsearchDistribution.CURRENT_PLATFORM, arch);
String existingLibraryPath = System.getProperty("java.library.path");
return String.format(Locale.ROOT, "%s/%s:%s", nativeLibsDir, platform, existingLibraryPath);
}
}

View file

@ -50,6 +50,13 @@ abstract class AbstractGradleFuncTest extends Specification {
propertiesFile = testProjectDir.newFile('gradle.properties') propertiesFile = testProjectDir.newFile('gradle.properties')
propertiesFile << propertiesFile <<
"org.gradle.java.installations.fromEnv=JAVA_HOME,RUNTIME_JAVA_HOME,JAVA15_HOME,JAVA14_HOME,JAVA13_HOME,JAVA12_HOME,JAVA11_HOME,JAVA8_HOME" "org.gradle.java.installations.fromEnv=JAVA_HOME,RUNTIME_JAVA_HOME,JAVA15_HOME,JAVA14_HOME,JAVA13_HOME,JAVA12_HOME,JAVA11_HOME,JAVA8_HOME"
def nativeLibsProject = subProject(":libs:elasticsearch-native:elasticsearch-native-libraries")
nativeLibsProject << """
plugins {
id 'base'
}
"""
} }
def cleanup() { def cleanup() {

View file

@ -15,7 +15,7 @@ CopySpec archiveFiles(String distributionType, String os, String architecture, b
return copySpec { return copySpec {
into("elasticsearch-${version}") { into("elasticsearch-${version}") {
into('lib') { into('lib') {
with libFiles with libFiles(os, architecture)
} }
into('config') { into('config') {
dirMode 0750 dirMode 0750

View file

@ -261,7 +261,7 @@ configure(subprojects.findAll { ['archives', 'packages'].contains(it.name) }) {
* Properties to expand when copying packaging files * * Properties to expand when copying packaging files *
*****************************************************************************/ *****************************************************************************/
configurations { configurations {
['libs', 'libsVersionChecker', 'libsCliLauncher', 'libsServerCli', 'libsWindowsServiceCli', 'libsPluginCli', 'libsKeystoreCli', 'libsSecurityCli', 'libsGeoIpCli', 'libsAnsiConsole'].each { ['libs', 'libsVersionChecker', 'libsCliLauncher', 'libsServerCli', 'libsWindowsServiceCli', 'libsPluginCli', 'libsKeystoreCli', 'libsSecurityCli', 'libsGeoIpCli', 'libsAnsiConsole', 'libsNative'].each {
create(it) { create(it) {
canBeConsumed = false canBeConsumed = false
canBeResolved = true canBeResolved = true
@ -292,6 +292,7 @@ configure(subprojects.findAll { ['archives', 'packages'].contains(it.name) }) {
libsKeystoreCli project(path: ':distribution:tools:keystore-cli') libsKeystoreCli project(path: ':distribution:tools:keystore-cli')
libsSecurityCli project(':x-pack:plugin:security:cli') libsSecurityCli project(':x-pack:plugin:security:cli')
libsGeoIpCli project(':distribution:tools:geoip-cli') libsGeoIpCli project(':distribution:tools:geoip-cli')
libsNative project(':libs:elasticsearch-native:elasticsearch-native-libraries')
} }
project.ext { project.ext {
@ -299,7 +300,7 @@ configure(subprojects.findAll { ['archives', 'packages'].contains(it.name) }) {
/***************************************************************************** /*****************************************************************************
* Common files in all distributions * * Common files in all distributions *
*****************************************************************************/ *****************************************************************************/
libFiles = libFiles = { os, architecture ->
copySpec { copySpec {
// Delay by using closures, since they have not yet been configured, so no jar task exists yet. // Delay by using closures, since they have not yet been configured, so no jar task exists yet.
from(configurations.libs) from(configurations.libs)
@ -330,7 +331,14 @@ configure(subprojects.findAll { ['archives', 'packages'].contains(it.name) }) {
into('tools/ansi-console') { into('tools/ansi-console') {
from(configurations.libsAnsiConsole) from(configurations.libsAnsiConsole)
} }
into('platform') {
from(configurations.libsNative)
if (os != null) {
include (os + '-' + architecture + '/*')
}
}
} }
}
modulesFiles = { os, architecture -> modulesFiles = { os, architecture ->
copySpec { copySpec {

View file

@ -131,6 +131,7 @@ def commonPackageConfig(String type, String architecture) {
// top level "into" directive is not inherited from ospackage for some reason, so we must // top level "into" directive is not inherited from ospackage for some reason, so we must
// specify it again explicitly for copying common files // specify it again explicitly for copying common files
String platform = 'linux-' + ((architecture == 'x64') ? 'x86_64' : architecture)
into('/usr/share/elasticsearch') { into('/usr/share/elasticsearch') {
into('bin') { into('bin') {
with binFiles(type, false) with binFiles(type, false)
@ -140,7 +141,7 @@ def commonPackageConfig(String type, String architecture) {
fileMode 0644 fileMode 0644
} }
into('lib') { into('lib') {
with libFiles with libFiles('linux', architecture)
} }
into('modules') { into('modules') {
with modulesFiles('linux', architecture) with modulesFiles('linux', architecture)

View file

@ -10,7 +10,11 @@ package org.elasticsearch.server.cli;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.EsExecutors; import org.elasticsearch.common.util.concurrent.EsExecutors;
import org.elasticsearch.core.SuppressForbidden;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -21,6 +25,8 @@ final class SystemJvmOptions {
static List<String> systemJvmOptions(Settings nodeSettings, final Map<String, String> sysprops) { static List<String> systemJvmOptions(Settings nodeSettings, final Map<String, String> sysprops) {
String distroType = sysprops.get("es.distribution.type"); String distroType = sysprops.get("es.distribution.type");
boolean isHotspot = sysprops.getOrDefault("sun.management.compiler", "").contains("HotSpot"); boolean isHotspot = sysprops.getOrDefault("sun.management.compiler", "").contains("HotSpot");
String libraryPath = findLibraryPath(sysprops);
return Stream.of( return Stream.of(
/* /*
* Cache ttl in seconds for positive DNS lookups noting that this overrides the JDK security property networkaddress.cache.ttl; * Cache ttl in seconds for positive DNS lookups noting that this overrides the JDK security property networkaddress.cache.ttl;
@ -71,6 +77,8 @@ final class SystemJvmOptions {
maybeOverrideDockerCgroup(distroType), maybeOverrideDockerCgroup(distroType),
maybeSetActiveProcessorCount(nodeSettings), maybeSetActiveProcessorCount(nodeSettings),
setReplayFile(distroType, isHotspot), setReplayFile(distroType, isHotspot),
"-Djava.library.path=" + libraryPath,
"-Djna.library.path=" + libraryPath,
// Pass through distribution type // Pass through distribution type
"-Des.distribution.type=" + distroType "-Des.distribution.type=" + distroType
).filter(e -> e.isEmpty() == false).collect(Collectors.toList()); ).filter(e -> e.isEmpty() == false).collect(Collectors.toList());
@ -127,4 +135,38 @@ final class SystemJvmOptions {
} }
return ""; return "";
} }
private static String findLibraryPath(Map<String, String> sysprops) {
// working dir is ES installation, so we use relative path here
Path platformDir = Paths.get("lib", "platform");
String existingPath = sysprops.get("java.library.path");
assert existingPath != null;
String osname = sysprops.get("os.name");
String os;
if (osname.startsWith("Windows")) {
os = "windows";
} else if (osname.startsWith("Linux")) {
os = "linux";
} else if (osname.startsWith("Mac OS")) {
os = "darwin";
} else {
os = "unsupported_os[" + osname + "]";
}
String archname = sysprops.get("os.arch");
String arch;
if (archname.equals("amd64")) {
arch = "x64";
} else if (archname.equals("aarch64")) {
arch = archname;
} else {
arch = "unsupported_arch[" + archname + "]";
}
return platformDir.resolve(os + "-" + arch).toAbsolutePath() + getPathSeparator() + existingPath;
}
@SuppressForbidden(reason = "no way to get path separator with nio")
private static String getPathSeparator() {
return File.pathSeparator;
}
} }

View file

@ -17,6 +17,7 @@ import org.elasticsearch.test.ESTestCase.WithoutSecurityManager;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.StringReader; import java.io.StringReader;
import java.nio.file.FileSystems;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.NoSuchFileException; import java.nio.file.NoSuchFileException;
import java.nio.file.Path; import java.nio.file.Path;
@ -29,10 +30,12 @@ import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasEntry;
import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasKey; import static org.hamcrest.Matchers.hasKey;
import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.hasSize;
@ -41,6 +44,15 @@ import static org.hamcrest.Matchers.not;
@WithoutSecurityManager @WithoutSecurityManager
public class JvmOptionsParserTests extends ESTestCase { public class JvmOptionsParserTests extends ESTestCase {
private static final Map<String, String> TEST_SYSPROPS = Map.of(
"os.name",
"Linux",
"os.arch",
"aarch64",
"java.library.path",
"/usr/lib"
);
public void testSubstitution() { public void testSubstitution() {
final List<String> jvmOptions = JvmOptionsParser.substitutePlaceholders( final List<String> jvmOptions = JvmOptionsParser.substitutePlaceholders(
List.of("-Djava.io.tmpdir=${ES_TMPDIR}"), List.of("-Djava.io.tmpdir=${ES_TMPDIR}"),
@ -350,30 +362,65 @@ public class JvmOptionsParserTests extends ESTestCase {
public void testNodeProcessorsActiveCount() { public void testNodeProcessorsActiveCount() {
{ {
final List<String> jvmOptions = SystemJvmOptions.systemJvmOptions(Settings.EMPTY, Map.of()); final List<String> jvmOptions = SystemJvmOptions.systemJvmOptions(Settings.EMPTY, TEST_SYSPROPS);
assertThat(jvmOptions, not(hasItem(containsString("-XX:ActiveProcessorCount=")))); assertThat(jvmOptions, not(hasItem(containsString("-XX:ActiveProcessorCount="))));
} }
{ {
Settings nodeSettings = Settings.builder().put(EsExecutors.NODE_PROCESSORS_SETTING.getKey(), 1).build(); Settings nodeSettings = Settings.builder().put(EsExecutors.NODE_PROCESSORS_SETTING.getKey(), 1).build();
final List<String> jvmOptions = SystemJvmOptions.systemJvmOptions(nodeSettings, Map.of()); final List<String> jvmOptions = SystemJvmOptions.systemJvmOptions(nodeSettings, TEST_SYSPROPS);
assertThat(jvmOptions, hasItem("-XX:ActiveProcessorCount=1")); assertThat(jvmOptions, hasItem("-XX:ActiveProcessorCount=1"));
} }
{ {
// check rounding // check rounding
Settings nodeSettings = Settings.builder().put(EsExecutors.NODE_PROCESSORS_SETTING.getKey(), 0.2).build(); Settings nodeSettings = Settings.builder().put(EsExecutors.NODE_PROCESSORS_SETTING.getKey(), 0.2).build();
final List<String> jvmOptions = SystemJvmOptions.systemJvmOptions(nodeSettings, Map.of()); final List<String> jvmOptions = SystemJvmOptions.systemJvmOptions(nodeSettings, TEST_SYSPROPS);
assertThat(jvmOptions, hasItem("-XX:ActiveProcessorCount=1")); assertThat(jvmOptions, hasItem("-XX:ActiveProcessorCount=1"));
} }
{ {
// check validation // check validation
Settings nodeSettings = Settings.builder().put(EsExecutors.NODE_PROCESSORS_SETTING.getKey(), 10000).build(); Settings nodeSettings = Settings.builder().put(EsExecutors.NODE_PROCESSORS_SETTING.getKey(), 10000).build();
var e = expectThrows(IllegalArgumentException.class, () -> SystemJvmOptions.systemJvmOptions(nodeSettings, Map.of())); var e = expectThrows(IllegalArgumentException.class, () -> SystemJvmOptions.systemJvmOptions(nodeSettings, TEST_SYSPROPS));
assertThat(e.getMessage(), containsString("setting [node.processors] must be <=")); assertThat(e.getMessage(), containsString("setting [node.processors] must be <="));
} }
} }
public void testCommandLineDistributionType() { public void testCommandLineDistributionType() {
final List<String> jvmOptions = SystemJvmOptions.systemJvmOptions(Settings.EMPTY, Map.of("es.distribution.type", "testdistro")); var sysprops = new HashMap<>(TEST_SYSPROPS);
sysprops.put("es.distribution.type", "testdistro");
final List<String> jvmOptions = SystemJvmOptions.systemJvmOptions(Settings.EMPTY, sysprops);
assertThat(jvmOptions, hasItem("-Des.distribution.type=testdistro")); assertThat(jvmOptions, hasItem("-Des.distribution.type=testdistro"));
} }
public void testLibraryPath() {
assertLibraryPath("Mac OS", "aarch64", "darwin-aarch64");
assertLibraryPath("Mac OS", "amd64", "darwin-x64");
assertLibraryPath("Linux", "aarch64", "linux-aarch64");
assertLibraryPath("Linux", "amd64", "linux-x64");
assertLibraryPath("Windows", "amd64", "windows-x64");
assertLibraryPath("Unknown", "aarch64", "unsupported_os[Unknown]-aarch64");
assertLibraryPath("Mac OS", "Unknown", "darwin-unsupported_arch[Unknown]");
}
private void assertLibraryPath(String os, String arch, String expected) {
String existingPath = "/usr/lib";
var sysprops = Map.of("os.name", os, "os.arch", arch, "java.library.path", existingPath);
final List<String> jvmOptions = SystemJvmOptions.systemJvmOptions(Settings.EMPTY, sysprops);
Map<String, String> options = new HashMap<>();
for (var jvmOption : jvmOptions) {
if (jvmOption.startsWith("-D")) {
String[] parts = jvmOption.substring(2).split("=");
assert parts.length == 2;
options.put(parts[0], parts[1]);
}
}
String separator = FileSystems.getDefault().getSeparator();
assertThat(
options,
hasEntry(equalTo("java.library.path"), allOf(containsString("platform" + separator + expected), containsString(existingPath)))
);
assertThat(
options,
hasEntry(equalTo("jna.library.path"), allOf(containsString("platform" + separator + expected), containsString(existingPath)))
);
}
} }

View file

@ -6,12 +6,7 @@
* Side Public License, v 1. * Side Public License, v 1.
*/ */
import org.elasticsearch.gradle.transform.UnzipTransform
import org.elasticsearch.gradle.internal.GenerateProviderManifest
import org.elasticsearch.gradle.internal.precommit.CheckForbiddenApisTask import org.elasticsearch.gradle.internal.precommit.CheckForbiddenApisTask
import org.gradle.api.internal.artifacts.ArtifactAttributes
import java.util.stream.Collectors
apply plugin: 'elasticsearch.publish' apply plugin: 'elasticsearch.publish'
apply plugin: 'elasticsearch.build' apply plugin: 'elasticsearch.build'

View file

@ -0,0 +1,35 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.nativeaccess.jna;
import com.sun.jna.Memory;
import org.elasticsearch.nativeaccess.CloseableByteBuffer;
import java.nio.ByteBuffer;
class JnaCloseableByteBuffer implements CloseableByteBuffer {
private final Memory memory;
private final ByteBuffer bufferView;
JnaCloseableByteBuffer(int len) {
this.memory = new Memory(len);
this.bufferView = memory.getByteBuffer(0, len);
}
@Override
public ByteBuffer buffer() {
return bufferView;
}
@Override
public void close() {
memory.close();
}
}

View file

@ -0,0 +1,19 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.nativeaccess.jna;
import org.elasticsearch.nativeaccess.CloseableByteBuffer;
import org.elasticsearch.nativeaccess.lib.JavaLibrary;
class JnaJavaLibrary implements JavaLibrary {
@Override
public CloseableByteBuffer newBuffer(int len) {
return new JnaCloseableByteBuffer(len);
}
}

View file

@ -8,14 +8,29 @@
package org.elasticsearch.nativeaccess.jna; package org.elasticsearch.nativeaccess.jna;
import org.elasticsearch.nativeaccess.lib.JavaLibrary;
import org.elasticsearch.nativeaccess.lib.NativeLibraryProvider; import org.elasticsearch.nativeaccess.lib.NativeLibraryProvider;
import org.elasticsearch.nativeaccess.lib.PosixCLibrary; import org.elasticsearch.nativeaccess.lib.PosixCLibrary;
import org.elasticsearch.nativeaccess.lib.SystemdLibrary; import org.elasticsearch.nativeaccess.lib.SystemdLibrary;
import org.elasticsearch.nativeaccess.lib.ZstdLibrary;
import java.util.Map; import java.util.Map;
public class JnaNativeLibraryProvider extends NativeLibraryProvider { public class JnaNativeLibraryProvider extends NativeLibraryProvider {
public JnaNativeLibraryProvider() { public JnaNativeLibraryProvider() {
super("jna", Map.of(PosixCLibrary.class, JnaPosixCLibrary::new, SystemdLibrary.class, JnaSystemdLibrary::new)); super(
"jna",
Map.of(
JavaLibrary.class,
JnaJavaLibrary::new,
PosixCLibrary.class,
JnaPosixCLibrary::new,
SystemdLibrary.class,
JnaSystemdLibrary::new,
ZstdLibrary.class,
JnaZstdLibrary::new
)
);
} }
} }

View file

@ -0,0 +1,62 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.nativeaccess.jna;
import com.sun.jna.Library;
import com.sun.jna.Native;
import org.elasticsearch.nativeaccess.lib.ZstdLibrary;
import java.nio.ByteBuffer;
class JnaZstdLibrary implements ZstdLibrary {
private interface NativeFunctions extends Library {
long ZSTD_compressBound(int scrLen);
long ZSTD_compress(ByteBuffer dst, int dstLen, ByteBuffer src, int srcLen, int compressionLevel);
boolean ZSTD_isError(long code);
String ZSTD_getErrorName(long code);
long ZSTD_decompress(ByteBuffer dst, int dstLen, ByteBuffer src, int srcLen);
}
private final NativeFunctions functions;
JnaZstdLibrary() {
this.functions = Native.load("zstd", NativeFunctions.class);
}
@Override
public long compressBound(int scrLen) {
return functions.ZSTD_compressBound(scrLen);
}
@Override
public long compress(ByteBuffer dst, ByteBuffer src, int compressionLevel) {
return functions.ZSTD_compress(dst, dst.remaining(), src, src.remaining(), compressionLevel);
}
@Override
public boolean isError(long code) {
return functions.ZSTD_isError(code);
}
@Override
public String getErrorName(long code) {
return functions.ZSTD_getErrorName(code);
}
@Override
public long decompress(ByteBuffer dst, ByteBuffer src) {
return functions.ZSTD_decompress(dst, dst.remaining(), src, src.remaining());
}
}

View file

@ -0,0 +1,64 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import org.elasticsearch.gradle.transform.UnzipTransform
apply plugin: 'base'
configurations {
libs {
attributes.attribute(ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE, ArtifactTypeDefinition.DIRECTORY_TYPE)
canBeConsumed = false
}
}
var zstdVersion = "1.5.5"
repositories {
exclusiveContent {
forRepository {
maven {
url "https://artifactory.elastic.dev/artifactory/elasticsearch-zstd"
metadataSources {
artifact()
}
}
}
filter {
includeModule("org.elasticsearch", "zstd")
}
}
}
dependencies {
registerTransform(UnzipTransform, transformSpec -> {
transformSpec.getFrom().attribute(ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE, ArtifactTypeDefinition.JAR_TYPE);
transformSpec.getTo().attribute(ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE, ArtifactTypeDefinition.DIRECTORY_TYPE);
});
libs "org.elasticsearch:zstd:${zstdVersion}:darwin-aarch64"
libs "org.elasticsearch:zstd:${zstdVersion}:darwin-x86-64"
libs "org.elasticsearch:zstd:${zstdVersion}:linux-aarch64"
libs "org.elasticsearch:zstd:${zstdVersion}:linux-x86-64"
libs "org.elasticsearch:zstd:${zstdVersion}:windows-x86-64"
}
def extractLibs = tasks.register('extractLibs', Copy) {
from configurations.libs
into layout.buildDirectory.dir('platform')
// TODO: fix architecture in uploaded libs
filesMatching("*-x86-64/*") {
it.path = it.path.replace("x86-64", "x64")
}
filesMatching("win32*/*") {
it.path = it.path.replace("win32", "windows")
}
}
artifacts {
'default' extractLibs
}

View file

@ -14,7 +14,7 @@ module org.elasticsearch.nativeaccess {
requires org.elasticsearch.base; requires org.elasticsearch.base;
requires org.elasticsearch.logging; requires org.elasticsearch.logging;
exports org.elasticsearch.nativeaccess to org.elasticsearch.server, org.elasticsearch.systemd; exports org.elasticsearch.nativeaccess to org.elasticsearch.nativeaccess.jna, org.elasticsearch.server, org.elasticsearch.systemd;
// allows jna to implement a library provider, and ProviderLocator to load it // allows jna to implement a library provider, and ProviderLocator to load it
exports org.elasticsearch.nativeaccess.lib to org.elasticsearch.nativeaccess.jna, org.elasticsearch.base; exports org.elasticsearch.nativeaccess.lib to org.elasticsearch.nativeaccess.jna, org.elasticsearch.base;

View file

@ -10,15 +10,22 @@ package org.elasticsearch.nativeaccess;
import org.elasticsearch.logging.LogManager; import org.elasticsearch.logging.LogManager;
import org.elasticsearch.logging.Logger; import org.elasticsearch.logging.Logger;
import org.elasticsearch.nativeaccess.lib.JavaLibrary;
import org.elasticsearch.nativeaccess.lib.NativeLibraryProvider;
import org.elasticsearch.nativeaccess.lib.ZstdLibrary;
abstract class AbstractNativeAccess implements NativeAccess { abstract class AbstractNativeAccess implements NativeAccess {
protected static final Logger logger = LogManager.getLogger(NativeAccess.class); protected static final Logger logger = LogManager.getLogger(NativeAccess.class);
private final String name; private final String name;
private final JavaLibrary javaLib;
private final Zstd zstd;
protected AbstractNativeAccess(String name) { protected AbstractNativeAccess(String name, NativeLibraryProvider libraryProvider) {
this.name = name; this.name = name;
this.javaLib = libraryProvider.getLibrary(JavaLibrary.class);
this.zstd = new Zstd(libraryProvider.getLibrary(ZstdLibrary.class));
} }
String getName() { String getName() {
@ -29,4 +36,15 @@ abstract class AbstractNativeAccess implements NativeAccess {
public Systemd systemd() { public Systemd systemd() {
return null; return null;
} }
@Override
public Zstd getZstd() {
return zstd;
}
@Override
public CloseableByteBuffer newBuffer(int len) {
assert len > 0;
return javaLib.newBuffer(len);
}
} }

View file

@ -0,0 +1,18 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.nativeaccess;
import java.nio.ByteBuffer;
public interface CloseableByteBuffer extends AutoCloseable {
ByteBuffer buffer();
@Override
void close();
}

View file

@ -28,4 +28,12 @@ public interface NativeAccess {
boolean definitelyRunningAsRoot(); boolean definitelyRunningAsRoot();
Systemd systemd(); Systemd systemd();
/**
* Returns an accessor to zstd compression functions.
* @return an object used to compress and decompress bytes using zstd
*/
Zstd getZstd();
CloseableByteBuffer newBuffer(int len);
} }

View file

@ -37,10 +37,10 @@ class NativeAccessHolder {
logger.warn("Unable to load native provider. Native methods will be disabled.", e); logger.warn("Unable to load native provider. Native methods will be disabled.", e);
} }
if (inst == null) { if (inst == null) {
inst = new NoopNativeAccess(); INSTANCE = new NoopNativeAccess();
} else { } else {
logger.info("Using [" + libProvider.getName() + "] native provider and native methods for [" + inst.getName() + "]"); logger.info("Using [" + libProvider.getName() + "] native provider and native methods for [" + inst.getName() + "]");
INSTANCE = inst;
} }
INSTANCE = inst;
} }
} }

View file

@ -8,11 +8,14 @@
package org.elasticsearch.nativeaccess; package org.elasticsearch.nativeaccess;
class NoopNativeAccess extends AbstractNativeAccess { import org.elasticsearch.logging.LogManager;
import org.elasticsearch.logging.Logger;
NoopNativeAccess() { class NoopNativeAccess implements NativeAccess {
super("noop");
} private static final Logger logger = LogManager.getLogger(NativeAccess.class);
NoopNativeAccess() {}
@Override @Override
public boolean definitelyRunningAsRoot() { public boolean definitelyRunningAsRoot() {
@ -25,4 +28,16 @@ class NoopNativeAccess extends AbstractNativeAccess {
logger.warn("Cannot get systemd access because native access is not available"); logger.warn("Cannot get systemd access because native access is not available");
return null; return null;
} }
@Override
public Zstd getZstd() {
logger.warn("cannot compress with zstd because native access is not available");
return null;
}
@Override
public CloseableByteBuffer newBuffer(int len) {
logger.warn("cannot allocate buffer because native access is not available");
return null;
}
} }

View file

@ -16,7 +16,7 @@ abstract class PosixNativeAccess extends AbstractNativeAccess {
protected final PosixCLibrary libc; protected final PosixCLibrary libc;
PosixNativeAccess(String name, NativeLibraryProvider libraryProvider) { PosixNativeAccess(String name, NativeLibraryProvider libraryProvider) {
super(name); super(name, libraryProvider);
this.libc = libraryProvider.getLibrary(PosixCLibrary.class); this.libc = libraryProvider.getLibrary(PosixCLibrary.class);
} }

View file

@ -13,7 +13,7 @@ import org.elasticsearch.nativeaccess.lib.NativeLibraryProvider;
class WindowsNativeAccess extends AbstractNativeAccess { class WindowsNativeAccess extends AbstractNativeAccess {
WindowsNativeAccess(NativeLibraryProvider libraryProvider) { WindowsNativeAccess(NativeLibraryProvider libraryProvider) {
super("Windows"); super("Windows", libraryProvider);
} }
@Override @Override

View file

@ -0,0 +1,81 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.nativeaccess;
import org.elasticsearch.nativeaccess.lib.ZstdLibrary;
import java.nio.ByteBuffer;
import java.util.Objects;
public final class Zstd {
private final ZstdLibrary zstdLib;
Zstd(ZstdLibrary zstdLib) {
this.zstdLib = zstdLib;
}
/**
* Compress the content of {@code src} into {@code dst} at compression level {@code level}, and return the number of compressed bytes.
* {@link ByteBuffer#position()} and {@link ByteBuffer#limit()} of both {@link ByteBuffer}s are left unmodified.
*/
public int compress(ByteBuffer dst, ByteBuffer src, int level) {
Objects.requireNonNull(dst, "Null destination buffer");
Objects.requireNonNull(src, "Null source buffer");
assert dst.isDirect();
assert dst.isReadOnly() == false;
assert src.isDirect();
assert src.isReadOnly() == false;
long ret = zstdLib.compress(dst, src, level);
if (zstdLib.isError(ret)) {
throw new IllegalArgumentException(zstdLib.getErrorName(ret));
} else if (ret < 0 || ret > Integer.MAX_VALUE) {
throw new IllegalStateException("Integer overflow? ret=" + ret);
}
return (int) ret;
}
/**
* Compress the content of {@code src} into {@code dst}, and return the number of decompressed bytes. {@link ByteBuffer#position()} and
* {@link ByteBuffer#limit()} of both {@link ByteBuffer}s are left unmodified.
*/
public int decompress(ByteBuffer dst, ByteBuffer src) {
Objects.requireNonNull(dst, "Null destination buffer");
Objects.requireNonNull(src, "Null source buffer");
assert dst.isDirect();
assert dst.isReadOnly() == false;
assert src.isDirect();
assert src.isReadOnly() == false;
long ret = zstdLib.decompress(dst, src);
if (zstdLib.isError(ret)) {
throw new IllegalArgumentException(zstdLib.getErrorName(ret));
} else if (ret < 0 || ret > Integer.MAX_VALUE) {
throw new IllegalStateException("Integer overflow? ret=" + ret);
}
return (int) ret;
}
/**
* Return the maximum number of compressed bytes given an input length.
*/
public int compressBound(int srcLen) {
long ret = zstdLib.compressBound(srcLen);
if (zstdLib.isError(ret)) {
throw new IllegalArgumentException(zstdLib.getErrorName(ret));
} else if (ret < 0 || ret > Integer.MAX_VALUE) {
throw new IllegalArgumentException(
srcLen
+ " bytes may require up to "
+ Long.toUnsignedString(ret)
+ " bytes, which overflows the maximum capacity of a ByteBuffer"
);
}
return (int) ret;
}
}

View file

@ -0,0 +1,15 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.nativeaccess.lib;
import org.elasticsearch.nativeaccess.CloseableByteBuffer;
public non-sealed interface JavaLibrary extends NativeLibrary {
CloseableByteBuffer newBuffer(int len);
}

View file

@ -9,4 +9,4 @@
package org.elasticsearch.nativeaccess.lib; package org.elasticsearch.nativeaccess.lib;
/** A marker interface for libraries that can be loaded by {@link org.elasticsearch.nativeaccess.lib.NativeLibraryProvider} */ /** A marker interface for libraries that can be loaded by {@link org.elasticsearch.nativeaccess.lib.NativeLibraryProvider} */
public sealed interface NativeLibrary permits PosixCLibrary, SystemdLibrary {} public sealed interface NativeLibrary permits JavaLibrary, PosixCLibrary, SystemdLibrary, ZstdLibrary {}

View file

@ -0,0 +1,24 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.nativeaccess.lib;
import java.nio.ByteBuffer;
public non-sealed interface ZstdLibrary extends NativeLibrary {
long compressBound(int scrLen);
long compress(ByteBuffer dst, ByteBuffer src, int compressionLevel);
boolean isError(long code);
String getErrorName(long code);
long decompress(ByteBuffer dst, ByteBuffer src);
}

View file

@ -0,0 +1,34 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.nativeaccess.jdk;
import org.elasticsearch.nativeaccess.CloseableByteBuffer;
import java.lang.foreign.Arena;
import java.nio.ByteBuffer;
class JdkCloseableByteBuffer implements CloseableByteBuffer {
private final Arena arena;
private final ByteBuffer bufferView;
JdkCloseableByteBuffer(int len) {
this.arena = Arena.ofShared();
this.bufferView = this.arena.allocate(len).asByteBuffer();
}
@Override
public ByteBuffer buffer() {
return bufferView;
}
@Override
public void close() {
arena.close();
}
}

View file

@ -0,0 +1,19 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.nativeaccess.jdk;
import org.elasticsearch.nativeaccess.CloseableByteBuffer;
import org.elasticsearch.nativeaccess.lib.JavaLibrary;
class JdkJavaLibrary implements JavaLibrary {
@Override
public CloseableByteBuffer newBuffer(int len) {
return new JdkCloseableByteBuffer(len);
}
}

View file

@ -8,15 +8,29 @@
package org.elasticsearch.nativeaccess.jdk; package org.elasticsearch.nativeaccess.jdk;
import org.elasticsearch.nativeaccess.lib.JavaLibrary;
import org.elasticsearch.nativeaccess.lib.NativeLibraryProvider; import org.elasticsearch.nativeaccess.lib.NativeLibraryProvider;
import org.elasticsearch.nativeaccess.lib.PosixCLibrary; import org.elasticsearch.nativeaccess.lib.PosixCLibrary;
import org.elasticsearch.nativeaccess.lib.SystemdLibrary; import org.elasticsearch.nativeaccess.lib.SystemdLibrary;
import org.elasticsearch.nativeaccess.lib.ZstdLibrary;
import java.util.Map; import java.util.Map;
public class JdkNativeLibraryProvider extends NativeLibraryProvider { public class JdkNativeLibraryProvider extends NativeLibraryProvider {
public JdkNativeLibraryProvider() { public JdkNativeLibraryProvider() {
super("jdk", Map.of(PosixCLibrary.class, JdkPosixCLibrary::new, SystemdLibrary.class, JdkSystemdLibrary::new)); super(
"jdk",
Map.of(
JavaLibrary.class,
JdkJavaLibrary::new,
PosixCLibrary.class,
JdkPosixCLibrary::new,
SystemdLibrary.class,
JdkSystemdLibrary::new,
ZstdLibrary.class,
JdkZstdLibrary::new
)
);
} }
} }

View file

@ -24,6 +24,7 @@ import static java.lang.foreign.ValueLayout.JAVA_INT;
import static org.elasticsearch.nativeaccess.jdk.LinkerHelper.downcallHandle; import static org.elasticsearch.nativeaccess.jdk.LinkerHelper.downcallHandle;
class JdkSystemdLibrary implements SystemdLibrary { class JdkSystemdLibrary implements SystemdLibrary {
static { static {
System.load(findLibSystemd()); System.load(findLibSystemd());
} }
@ -39,6 +40,7 @@ class JdkSystemdLibrary implements SystemdLibrary {
continue; continue;
} }
try (var stream = Files.walk(basepath)) { try (var stream = Files.walk(basepath)) {
var foundpath = stream.filter(Files::isDirectory).map(p -> p.resolve(libsystemd)).filter(Files::exists).findAny(); var foundpath = stream.filter(Files::isDirectory).map(p -> p.resolve(libsystemd)).filter(Files::exists).findAny();
if (foundpath.isPresent()) { if (foundpath.isPresent()) {
return foundpath.get().toAbsolutePath().toString(); return foundpath.get().toAbsolutePath().toString();

View file

@ -0,0 +1,91 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.nativeaccess.jdk;
import org.elasticsearch.nativeaccess.lib.ZstdLibrary;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.MemorySegment;
import java.lang.invoke.MethodHandle;
import java.nio.ByteBuffer;
import static java.lang.foreign.ValueLayout.ADDRESS;
import static java.lang.foreign.ValueLayout.JAVA_BOOLEAN;
import static java.lang.foreign.ValueLayout.JAVA_INT;
import static java.lang.foreign.ValueLayout.JAVA_LONG;
import static org.elasticsearch.nativeaccess.jdk.LinkerHelper.downcallHandle;
class JdkZstdLibrary implements ZstdLibrary {
static {
System.loadLibrary("zstd");
}
private static final MethodHandle compressBound$mh = downcallHandle("ZSTD_compressBound", FunctionDescriptor.of(JAVA_LONG, JAVA_INT));
private static final MethodHandle compress$mh = downcallHandle(
"ZSTD_compress",
FunctionDescriptor.of(JAVA_LONG, ADDRESS, JAVA_INT, ADDRESS, JAVA_INT, JAVA_INT)
);
private static final MethodHandle isError$mh = downcallHandle("ZSTD_isError", FunctionDescriptor.of(JAVA_BOOLEAN, JAVA_LONG));
private static final MethodHandle getErrorName$mh = downcallHandle("ZSTD_getErrorName", FunctionDescriptor.of(ADDRESS, JAVA_LONG));
private static final MethodHandle decompress$mh = downcallHandle(
"ZSTD_decompress",
FunctionDescriptor.of(JAVA_LONG, ADDRESS, JAVA_INT, ADDRESS, JAVA_INT)
);
@Override
public long compressBound(int srcLen) {
try {
return (long) compressBound$mh.invokeExact(srcLen);
} catch (Throwable t) {
throw new AssertionError(t);
}
}
@Override
public long compress(ByteBuffer dst, ByteBuffer src, int compressionLevel) {
var nativeDst = MemorySegment.ofBuffer(dst);
var nativeSrc = MemorySegment.ofBuffer(src);
try {
return (long) compress$mh.invokeExact(nativeDst, dst.remaining(), nativeSrc, src.remaining(), compressionLevel);
} catch (Throwable t) {
throw new AssertionError(t);
}
}
@Override
public boolean isError(long code) {
try {
return (boolean) isError$mh.invokeExact(code);
} catch (Throwable t) {
throw new AssertionError(t);
}
}
@Override
public String getErrorName(long code) {
try {
MemorySegment str = (MemorySegment) getErrorName$mh.invokeExact(code);
return str.reinterpret(Long.MAX_VALUE).getUtf8String(0);
} catch (Throwable t) {
throw new AssertionError(t);
}
}
@Override
public long decompress(ByteBuffer dst, ByteBuffer src) {
var nativeDst = MemorySegment.ofBuffer(dst);
var nativeSrc = MemorySegment.ofBuffer(src);
try {
return (long) decompress$mh.invokeExact(nativeDst, dst.remaining(), nativeSrc, src.remaining());
} catch (Throwable t) {
throw new AssertionError(t);
}
}
}

View file

@ -0,0 +1,141 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.nativeaccess;
import org.elasticsearch.test.ESTestCase;
import org.hamcrest.Matchers;
import org.junit.BeforeClass;
import java.util.Arrays;
import static org.hamcrest.Matchers.equalTo;
public class ZstdTests extends ESTestCase {
static NativeAccess nativeAccess;
static Zstd zstd;
@BeforeClass
public static void getZstd() {
nativeAccess = NativeAccess.instance();
zstd = nativeAccess.getZstd();
}
public void testCompressBound() {
assertThat(zstd.compressBound(0), Matchers.greaterThanOrEqualTo(1));
assertThat(zstd.compressBound(100), Matchers.greaterThanOrEqualTo(100));
expectThrows(IllegalArgumentException.class, () -> zstd.compressBound(Integer.MAX_VALUE));
expectThrows(IllegalArgumentException.class, () -> zstd.compressBound(-1));
expectThrows(IllegalArgumentException.class, () -> zstd.compressBound(-100));
expectThrows(IllegalArgumentException.class, () -> zstd.compressBound(Integer.MIN_VALUE));
}
public void testCompressValidation() {
try (var src = nativeAccess.newBuffer(1000); var dst = nativeAccess.newBuffer(500)) {
var srcBuf = src.buffer();
var dstBuf = dst.buffer();
var npe1 = expectThrows(NullPointerException.class, () -> zstd.compress(null, srcBuf, 0));
assertThat(npe1.getMessage(), equalTo("Null destination buffer"));
var npe2 = expectThrows(NullPointerException.class, () -> zstd.compress(dstBuf, null, 0));
assertThat(npe2.getMessage(), equalTo("Null source buffer"));
// dst capacity too low
for (int i = 0; i < srcBuf.remaining(); ++i) {
srcBuf.put(i, randomByte());
}
var e = expectThrows(IllegalArgumentException.class, () -> zstd.compress(dstBuf, srcBuf, 0));
assertThat(e.getMessage(), equalTo("Destination buffer is too small"));
}
}
public void testDecompressValidation() {
try (
var original = nativeAccess.newBuffer(1000);
var compressed = nativeAccess.newBuffer(500);
var restored = nativeAccess.newBuffer(500)
) {
var originalBuf = original.buffer();
var compressedBuf = compressed.buffer();
var npe1 = expectThrows(NullPointerException.class, () -> zstd.decompress(null, originalBuf));
assertThat(npe1.getMessage(), equalTo("Null destination buffer"));
var npe2 = expectThrows(NullPointerException.class, () -> zstd.decompress(compressedBuf, null));
assertThat(npe2.getMessage(), equalTo("Null source buffer"));
// Invalid compressed format
for (int i = 0; i < originalBuf.remaining(); ++i) {
originalBuf.put(i, (byte) i);
}
var e = expectThrows(IllegalArgumentException.class, () -> zstd.decompress(compressedBuf, originalBuf));
assertThat(e.getMessage(), equalTo("Unknown frame descriptor"));
int compressedLength = zstd.compress(compressedBuf, originalBuf, 0);
compressedBuf.limit(compressedLength);
e = expectThrows(IllegalArgumentException.class, () -> zstd.decompress(restored.buffer(), compressedBuf));
assertThat(e.getMessage(), equalTo("Destination buffer is too small"));
}
}
public void testOneByte() {
doTestRoundtrip(new byte[] { 'z' });
}
public void testConstant() {
byte[] b = new byte[randomIntBetween(100, 1000)];
Arrays.fill(b, randomByte());
doTestRoundtrip(b);
}
public void testCycle() {
byte[] b = new byte[randomIntBetween(100, 1000)];
for (int i = 0; i < b.length; ++i) {
b[i] = (byte) (i & 0x0F);
}
doTestRoundtrip(b);
}
private void doTestRoundtrip(byte[] data) {
try (
var original = nativeAccess.newBuffer(data.length);
var compressed = nativeAccess.newBuffer(zstd.compressBound(data.length));
var restored = nativeAccess.newBuffer(data.length)
) {
original.buffer().put(0, data);
int compressedLength = zstd.compress(compressed.buffer(), original.buffer(), randomIntBetween(-3, 9));
compressed.buffer().limit(compressedLength);
int decompressedLength = zstd.decompress(restored.buffer(), compressed.buffer());
assertThat(restored.buffer(), equalTo(original.buffer()));
assertThat(decompressedLength, equalTo(data.length));
}
// Now with non-zero offsets
final int compressedOffset = randomIntBetween(1, 1000);
final int decompressedOffset = randomIntBetween(1, 1000);
try (
var original = nativeAccess.newBuffer(decompressedOffset + data.length);
var compressed = nativeAccess.newBuffer(compressedOffset + zstd.compressBound(data.length));
var restored = nativeAccess.newBuffer(decompressedOffset + data.length)
) {
original.buffer().put(decompressedOffset, data);
original.buffer().position(decompressedOffset);
compressed.buffer().position(compressedOffset);
int compressedLength = zstd.compress(compressed.buffer(), original.buffer(), randomIntBetween(-3, 9));
compressed.buffer().limit(compressedOffset + compressedLength);
restored.buffer().position(decompressedOffset);
int decompressedLength = zstd.decompress(restored.buffer(), compressed.buffer());
assertThat(
restored.buffer().slice(decompressedOffset, data.length),
equalTo(original.buffer().slice(decompressedOffset, data.length))
);
assertThat(decompressedLength, equalTo(data.length));
}
}
}

View file

@ -154,6 +154,7 @@ project(":libs").children.each { libsProject ->
lp.name = lp.name // for :libs:elasticsearch-x-content:impl lp.name = lp.name // for :libs:elasticsearch-x-content:impl
} }
} }
project(":libs:elasticsearch-native:libraries").name = "elasticsearch-native-libraries"
project(":qa:stable-api").children.each { libsProject -> project(":qa:stable-api").children.each { libsProject ->
libsProject.name = "elasticsearch-${libsProject.name}" libsProject.name = "elasticsearch-${libsProject.name}"