Skip to content

Commit

Permalink
Add support for dependencies in plugin descriptor properties with sem…
Browse files Browse the repository at this point in the history
…ver range (opensearch-project#1707)

Signed-off-by: Abhilasha Seth <[email protected]>
  • Loading branch information
abseth-amzn committed Dec 4, 2023
1 parent 69cc2a1 commit 7e41183
Show file tree
Hide file tree
Showing 23 changed files with 1,024 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,14 @@ private void printPlugin(Environment env, Terminal terminal, Path plugin, String
PluginInfo info = PluginInfo.readFromProperties(env.pluginsDir().resolve(plugin));
terminal.println(Terminal.Verbosity.SILENT, prefix + info.getName());
terminal.println(Terminal.Verbosity.VERBOSE, info.toString(prefix));
if (info.getOpenSearchVersion().equals(Version.CURRENT) == false) {
if (!PluginsService.isPluginVersionCompatible(info, Version.CURRENT)) {
terminal.errorPrintln(
"WARNING: plugin ["
+ info.getName()
+ "] was built for OpenSearch version "
+ info.getVersion()
+ " but version "
+ info.getOpenSearchVersionRange().toString()
+ " and is not compatible with "
+ Version.CURRENT
+ " is required"
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,12 @@
import org.opensearch.core.util.FileSystemUtils;
import org.opensearch.env.Environment;
import org.opensearch.env.TestEnvironment;
import org.opensearch.semver.SemverRange;
import org.opensearch.test.OpenSearchTestCase;
import org.opensearch.test.PosixPermissionsResetter;
import org.junit.After;
import org.junit.Before;
import org.opensearch.test.VersionUtils;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
Expand Down Expand Up @@ -284,6 +286,34 @@ static void writePlugin(String name, Path structure, String... additionalProps)
writeJar(structure.resolve("plugin.jar"), className);
}

static void writePlugin(String name, Path structure, SemverRange opensearchVersionRange, String... additionalProps) throws IOException {
String[] properties = Stream.concat(
Stream.of(
"description",
"fake desc",
"name",
name,
"version",
"1.0",
"dependencies",
"{opensearch:" + opensearchVersionRange + "}",
"java.version",
System.getProperty("java.specification.version"),
"classname",
"FakePlugin"
),
Arrays.stream(additionalProps)
).toArray(String[]::new);
PluginTestUtil.writePluginProperties(structure, properties);
String className = name.substring(0, 1).toUpperCase(Locale.ENGLISH) + name.substring(1) + "Plugin";
writeJar(structure.resolve("plugin.jar"), className);
}

static Path createPlugin(String name, Path structure, SemverRange opensearchVersionRange, String... additionalProps) throws IOException {
writePlugin(name, structure, opensearchVersionRange, additionalProps);
return writeZip(structure, null);
}

static void writePluginSecurityPolicy(Path pluginDir, String... permissions) throws IOException {
StringBuilder securityPolicyContent = new StringBuilder("grant {\n ");
for (String permission : permissions) {
Expand Down Expand Up @@ -867,6 +897,31 @@ public void testInstallMisspelledOfficialPlugins() throws Exception {
assertThat(e.getMessage(), containsString("Unknown plugin unknown_plugin"));
}

public void testInstallPluginWithCompatibleDependencies() throws Exception {
Tuple<Path, Environment> env = createEnv(fs, temp);
Path pluginDir = createPluginDir(temp);
String pluginZip = createPlugin("fake", pluginDir, SemverRange.fromString("~" + Version.CURRENT.toString()))
.toUri()
.toURL()
.toString();
skipJarHellCommand.execute(terminal, Collections.singletonList(pluginZip), false, env.v2());
assertThat(terminal.getOutput(), containsString("100%"));
}

public void testInstallPluginWithIncompatibleDependencies() throws Exception {
Tuple<Path, Environment> env = createEnv(fs, temp);
Path pluginDir = createPluginDir(temp);
// Core version is behind plugin version by one w.r.t patch, hence incompatible
Version coreVersion = Version.CURRENT;
Version pluginVersion = VersionUtils.getVersion(coreVersion.major, coreVersion.minor, (byte) (coreVersion.revision + 1));
String pluginZip = createPlugin("fake", pluginDir, SemverRange.fromString("~" + pluginVersion.toString())).toUri().toURL().toString();
IllegalArgumentException e = expectThrows(
IllegalArgumentException.class,
() -> skipJarHellCommand.execute(terminal, Collections.singletonList(pluginZip), false, env.v2())
);
assertThat(e.getMessage(), containsString("Plugin [fake] was built for OpenSearch version ~" + pluginVersion));
}

public void testBatchFlag() throws Exception {
MockTerminal terminal = new MockTerminal();
installPlugin(terminal, true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,12 +278,49 @@ public void testExistingIncompatiblePlugin() throws Exception {
buildFakePlugin(env, "fake desc 2", "fake_plugin2", "org.fake2");

MockTerminal terminal = listPlugins(home);
String message = "plugin [fake_plugin1] was built for OpenSearch version 1.0 but version " + Version.CURRENT + " is required";
String message = "plugin [fake_plugin1] was built for OpenSearch version 5.0.0 and is not compatible with " + Version.CURRENT;
assertEquals("fake_plugin1\nfake_plugin2\n", terminal.getOutput());
assertEquals("WARNING: " + message + "\n", terminal.getErrorOutput());

String[] params = { "-s" };
terminal = listPlugins(home, params);
assertEquals("fake_plugin1\nfake_plugin2\n", terminal.getOutput());
}

public void testPluginWithDependencies() throws Exception {
PluginTestUtil.writePluginProperties(
env.pluginsDir().resolve("fake_plugin1"),
"description",
"fake desc 1",
"name",
"fake_plugin1",
"version",
"1.0",
"dependencies",
"{opensearch:" + Version.CURRENT + "}",
"java.version",
System.getProperty("java.specification.version"),
"classname",
"org.fake1"
);
String[] params = { "-v" };
MockTerminal terminal = listPlugins(home, params);
assertEquals(
buildMultiline(
"Plugins directory: " + env.pluginsDir(),
"fake_plugin1",
"- Plugin information:",
"Name: fake_plugin1",
"Description: fake desc 1",
"Version: 1.0",
"OpenSearch Version: " + Version.CURRENT.toString(),
"Java Version: " + System.getProperty("java.specification.version"),
"Native Controller: false",
"Extended Plugins: []",
" * Classname: org.fake1",
"Folder name: null"
),
terminal.getOutput()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
import org.opensearch.core.concurrency.OpenSearchRejectedExecutionException;
import org.opensearch.core.xcontent.MediaType;
import org.opensearch.core.xcontent.MediaTypeRegistry;
import org.opensearch.semver.SemverRange;

import java.io.ByteArrayInputStream;
import java.io.EOFException;
Expand Down Expand Up @@ -1090,6 +1091,10 @@ public Version readVersion() throws IOException {
return Version.fromId(readVInt());
}

public SemverRange readSemverRange() throws IOException {
return SemverRange.fromString(readString());
}

/** Reads the {@link Version} from the input stream */
public Build readBuild() throws IOException {
// the following is new for opensearch: we write the distribution to support any "forks"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import org.opensearch.core.common.settings.SecureString;
import org.opensearch.core.common.text.Text;
import org.opensearch.core.concurrency.OpenSearchRejectedExecutionException;
import org.opensearch.semver.SemverRange;

import java.io.EOFException;
import java.io.FileNotFoundException;
Expand Down Expand Up @@ -1101,6 +1102,10 @@ public void writeVersion(final Version version) throws IOException {
writeVInt(version.id);
}

public void writeSemverRange(final SemverRange range) throws IOException {
writeString(range.toString());
}

/** Writes the OpenSearch {@link Build} informn to the output stream */
public void writeBuild(final Build build) throws IOException {
// the following is new for opensearch: we write the distribution name to support any "forks" of the code
Expand Down
149 changes: 149 additions & 0 deletions libs/core/src/main/java/org/opensearch/semver/SemverRange.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* 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.
*/

package org.opensearch.semver;

import org.opensearch.Version;
import org.opensearch.common.Nullable;
import org.opensearch.core.xcontent.ToXContentFragment;
import org.opensearch.core.xcontent.XContentBuilder;
import org.opensearch.semver.expr.Equal;
import org.opensearch.semver.expr.Expression;
import org.opensearch.semver.expr.Tilde;

import java.io.IOException;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;

import static java.lang.String.format;
import static java.util.Arrays.stream;

/**
* Represents a single semver range.
*/
public class SemverRange implements ToXContentFragment {

private final Version rangeVersion;
private final RangeOperator rangeOperator;

public SemverRange(final Version rangeVersion, final RangeOperator rangeOperator) {
this.rangeVersion = rangeVersion;
this.rangeOperator = rangeOperator;
}

/**
* Constructs a {@code SemverRange} from its string representation.
* @param range given range
* @return a {@code SemverRange}
*/
public static SemverRange fromString(final String range) {
Optional<RangeOperator> operator = stream(RangeOperator.values()).filter(
rangeOperator -> rangeOperator != RangeOperator.DEFAULT && range.startsWith(rangeOperator.asString())
).findFirst();
RangeOperator rangeOperator = operator.orElse(RangeOperator.DEFAULT);
String version = range.replaceFirst(rangeOperator.asString(), "");
if (!Version.stringHasLength(version)) {
throw new IllegalArgumentException("Version cannot be empty");
}
return new SemverRange(Version.fromString(version), rangeOperator);
}

/**
* Return the range operator for this range.
* @return range operator
*/
public RangeOperator getRangeOperator() {
return rangeOperator;
}

/**
* Check if range is satisfied by given version string.
*
* @param version version to check
* @return {@code true} if range is satisfied by version, {@code false} otherwise
*/
public boolean isSatisfiedBy(final String version) {
return isSatisfiedBy(Version.fromString(version));
}

/**
* Check if range is satisfied by given version.
*
* @param version version to check
* @return {@code true} if range is satisfied by version, {@code false} otherwise
* @see #isSatisfiedBy(String)
*/
public boolean isSatisfiedBy(final Version version) {
Expression expression = null;
switch (rangeOperator) {
case DEFAULT:
case EQ:
expression = new Equal(rangeVersion);
break;
case TILDE:
expression = new Tilde(rangeVersion);
break;
default:
throw new RuntimeException(format(Locale.ROOT, "Unsupported range operator: %s", rangeOperator));
}
return expression.evaluate(version);
}

@Override
public boolean equals(@Nullable final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
SemverRange range = (SemverRange) o;
return Objects.equals(rangeVersion, range.rangeVersion) && rangeOperator == range.rangeOperator;
}

@Override
public int hashCode() {
return Objects.hash(rangeVersion, rangeOperator);
}

@Override
public String toString() {
return rangeOperator.asString() + rangeVersion;
}

@Override
public XContentBuilder toXContent(final XContentBuilder builder, final Params params) throws IOException {
return builder.value(toString());
}

/**
* A range operator.
*/
public enum RangeOperator {

EQ("="),
TILDE("~"),
DEFAULT("");

private final String operator;

RangeOperator(final String operator) {
this.operator = operator;
}

/**
* String representation of the range operator.
*
* @return range operator as string
*/
public String asString() {
return operator;
}
}
}
39 changes: 39 additions & 0 deletions libs/core/src/main/java/org/opensearch/semver/expr/Equal.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 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.
*/

package org.opensearch.semver.expr;

import org.opensearch.Version;

/**
* Expression to evaluate equality of versions.
*/
public class Equal implements Expression {

private final Version version;

/**
* Constructs a {@code Equal} expression with the given version.
*
* @param version given version
*/
public Equal(final Version version) {
this.version = version;
}

/**
* Checks if the current version equals the member version.
*
* @param version the version to evaluate
* @return {@code true} if the versions are equal {@code false} otherwise
*/
@Override
public boolean evaluate(final Version version) {
return version.equals(this.version);
}
}
Loading

0 comments on commit 7e41183

Please sign in to comment.