IGNITE-14801 Add tests for commands to manage metadata (#9137)
diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerMetadataTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerMetadataTest.java
index e4a2579..f4aa759 100644
--- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerMetadataTest.java
+++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerMetadataTest.java
@@ -17,6 +17,7 @@
package org.apache.ignite.util;
+import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
@@ -26,12 +27,14 @@
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
+import java.time.LocalDate;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.UUID;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
import java.util.stream.Collectors;
-
import org.apache.ignite.IgniteBinary;
import org.apache.ignite.IgniteCache;
import org.apache.ignite.IgniteLogger;
@@ -48,12 +51,14 @@
import org.apache.ignite.testframework.GridTestUtils;
import org.jetbrains.annotations.Nullable;
import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
import static org.apache.ignite.internal.commandline.CommandHandler.EXIT_CODE_INVALID_ARGUMENTS;
import static org.apache.ignite.internal.commandline.CommandHandler.EXIT_CODE_OK;
import static org.apache.ignite.internal.commandline.CommandHandler.EXIT_CODE_UNEXPECTED_ERROR;
import static org.apache.ignite.testframework.GridTestUtils.assertContains;
+import static org.apache.ignite.testframework.GridTestUtils.assertNotContains;
/**
* Checks command line metadata commands.
@@ -66,6 +71,12 @@
private static final int TYPES_CNT = 10;
/** */
+ @Before
+ public void init() {
+ injectTestSystemOut();
+ }
+
+ /** */
@After
public void clear() {
crd.binary().types().stream().forEach(type -> crd.context().cacheObjects().removeType(type.typeId()));
@@ -80,8 +91,6 @@
*/
@Test
public void testMetadataList() {
- injectTestSystemOut();
-
for (int typeNum = 0; typeNum < TYPES_CNT; ++typeNum) {
BinaryObjectBuilder bob = crd.binary().builder("Type_" + typeNum);
@@ -121,8 +130,6 @@
*/
@Test
public void testMetadataRemoveWrongType() {
- injectTestSystemOut();
-
String wrongTypeName = "Type01";
assertEquals(EXIT_CODE_INVALID_ARGUMENTS, execute("--meta", "remove", "--typeName", wrongTypeName));
@@ -160,8 +167,6 @@
*/
@Test
public void testMetadataForInternalClassesIsNotRegistered() {
- injectTestSystemOut();
-
IgniteCache<Object, Object> dfltCache = grid(0).getOrCreateCache(DEFAULT_CACHE_NAME);
dfltCache.put(1, new TestValue());
@@ -204,8 +209,6 @@
*/
@Test
public void testMetadataDetails() {
- injectTestSystemOut();
-
BinaryObjectBuilder bob0 = crd.binary().builder("TypeName0");
bob0.setField("fld0", 0);
bob0.build();
@@ -257,8 +260,6 @@
*/
@Test
public void testInvalidArguments() {
- injectTestSystemOut();
-
String out;
assertEquals(EXIT_CODE_INVALID_ARGUMENTS, execute("--meta", "remove"));
@@ -290,8 +291,6 @@
*/
@Test
public void testRemoveUpdate() throws Exception {
- injectTestSystemOut();
-
Path typeFile = FS.getPath("type0.bin");
try {
@@ -357,8 +356,6 @@
*/
@Test
public void testDropThinConnectionsOnRemove() throws Exception {
- injectTestSystemOut();
-
Path typeFile = FS.getPath("type0.bin");
try (IgniteClient cli = Ignition.startClient(clientConfiguration())) {
@@ -393,11 +390,9 @@
*/
@Test
public void testDropJdbcThinConnectionsOnRemove() throws Exception {
- injectTestSystemOut();
-
Path typeFile = FS.getPath("type0.bin");
- try (Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1")) {
+ try (Connection conn = DriverManager.getConnection(jdbcThinUrl())) {
try (final Statement stmt = conn.createStatement()) {
stmt.execute("CREATE TABLE test(id INT PRIMARY KEY, objVal OTHER)");
@@ -441,6 +436,245 @@
}
/**
+ * Check the type successfully merged after remove-recreate-update operations.
+ * Steps:
+ * - creates several types with name 'TypeNameX' where X - some index.
+ * - removes the type by cmdline utility (store removed metadata to specified file).
+ * - checks that type removed.
+ * - creates new types with the same names but different field names
+ * - restores removed types from the file
+ * - checks all types successfully merged.
+ */
+ @Test
+ public void testTypeMergedAfterRemoveUpdate() {
+ String[] typeNames = new String[]{"TypeName0", "TypeName1"};
+
+ final int cnt = typeNames.length;
+
+ Object[] typeValues = new Object[]{0, LocalDate.now()};
+
+ int[] typeIds = new int[cnt];
+
+ repeat(cnt, i -> typeIds[i] = crd.binary().builder(typeNames[i])
+ .setField("fld0", typeValues[i])
+ .build().type().typeId());
+
+ Path[] typeBackups = new Path[typeNames.length];
+
+ try {
+ repeat(cnt, i -> {
+ Path path = FS.getPath(typeNames[i] + ".bin");
+
+ typeBackups[i] = path;
+
+ assertEquals(EXIT_CODE_OK, execute("--meta", "remove",
+ "--typeName", typeNames[i],
+ "--out", path.toString()));
+ });
+
+ assertEquals(EXIT_CODE_OK, execute("--meta", "list"));
+
+ String out = testOut.toString();
+
+ repeat(cnt, i -> assertNotContains(log, out, "typeName=" + typeNames[i]));
+
+ repeat(cnt, i -> crd.binary().builder(typeNames[i])
+ .setField("fld1", typeValues[i])
+ .build());
+
+ repeat(cnt, i -> assertEquals(EXIT_CODE_OK, execute("--meta", "update", "--in",
+ typeBackups[i].toString())));
+
+ repeat(cnt, i -> {
+ assertEquals(EXIT_CODE_OK, execute("--meta", "details", "--typeName", typeNames[i]));
+ checkTypeDetails(log, testOut.toString(), crd.context().cacheObjects().metadata(typeIds[i]));
+ });
+ }
+ finally {
+ repeat(cnt, i -> {
+ if (typeBackups[i] != null) {
+ try {
+ Files.deleteIfExists(typeBackups[i]);
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Check the type can't be merged after remove-recreate-update operations
+ * with incompatible changes.
+ *
+ * Steps:
+ * - creates several types with name 'TypeNameX' where X - some index.
+ * - removes the type by cmdline utility (store removed metadata to specified file).
+ * - checks that type removed.
+ * - creates new types with the same names and same fields but different field types.
+ * - try to restores and verifies that it fails.
+ */
+ @Test
+ public void testTypeCantBeMergedAfterRemoveUpdateWithIncompatibleChanges() {
+ String[] typeNames = new String[]{"TypeName0", "TypeName1"};
+
+ final int cnt = typeNames.length;
+
+ Object[] typeValues = new Object[]{0, LocalDate.now()};
+
+ repeat(cnt, i -> crd.binary().builder(typeNames[i])
+ .setField("fld0", typeValues[i])
+ .build().type().typeId());
+
+ Path[] typeBackups = new Path[typeNames.length];
+
+ try {
+ repeat(cnt, i -> {
+ Path path = FS.getPath(typeNames[i] + ".bin");
+
+ typeBackups[i] = path;
+
+ assertEquals(EXIT_CODE_OK, execute("--meta", "remove",
+ "--typeName", typeNames[i],
+ "--out", path.toString()));
+ });
+
+ assertEquals(EXIT_CODE_OK, execute("--meta", "list"));
+
+ String out = testOut.toString();
+
+ repeat(cnt, i -> assertNotContains(log, out, "typeName=" + typeNames[i]));
+
+ repeat(cnt, i -> crd.binary().builder(typeNames[i])
+ .setField("fld0", typeValues[cnt - i - 1]) // swap values
+ .build());
+
+ repeat(cnt, i -> {
+ assertEquals(EXIT_CODE_UNEXPECTED_ERROR, execute("--meta", "update", "--in",
+ typeBackups[i].toString()));
+
+ assertContains(log, testOut.toString(), "The type of an existing field can not be changed");
+ });
+ }
+ finally {
+ repeat(cnt, i -> {
+ if (typeBackups[i] != null) {
+ try {
+ Files.deleteIfExists(typeBackups[i]);
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Check that you could remove and update type when metadata store under load.
+ *
+ * Steps:
+ * - creates type with name 'Type0'.
+ * - start another thread which generates in an infinite loop new types.
+ * - removes the type by cmdline utility (store removed metadata to specified file).
+ * - checks that type removed.
+ * - restore the type.
+ * - checks that type restored.
+ */
+ @Test
+ public void testRemoveUpdateUnderLoad() throws Exception {
+ Path typeFile = FS.getPath("type0.bin");
+
+ try {
+ createType("Type0", 0);
+
+ assertEquals(EXIT_CODE_OK, execute("--meta", "list"));
+ assertContains(log, testOut.toString(), "typeName=Type0");
+
+ AtomicBoolean stop = new AtomicBoolean(false);
+
+ Thread t = new Thread(() -> {
+ long i = 1;
+
+ while (!stop.get())
+ createType("Type" + i++, i);
+ });
+ t.start();
+
+ assertEquals(EXIT_CODE_OK, execute("--meta", "remove",
+ "--typeName", "Type0",
+ "--out", typeFile.toString()));
+
+ assertEquals(EXIT_CODE_OK, execute("--meta", "list"));
+ assertNotContains(log, testOut.toString(), "typeName=Type0");
+
+ // Restore the metadata from file.
+ assertEquals(EXIT_CODE_OK, execute("--meta", "update", "--in", typeFile.toString()));
+
+ assertEquals(EXIT_CODE_OK, execute("--meta", "list"));
+ assertContains(log, testOut.toString(), "typeName=Type0");
+
+ stop.set(true);
+
+ t.join(getTestTimeout());
+ }
+ finally {
+ if (Files.exists(typeFile))
+ Files.delete(typeFile);
+ }
+ }
+
+ /**
+ * Checks metadata list/details behaviour after a type removing.
+ *
+ * Steps:
+ * - creates some type.
+ * - checks that metadata list|details command returns proper type information.
+ * - removes the type by cmdline utility.
+ * - checks list command output not contains removed type.
+ * - checks details command fails with "Type not found" error.
+ */
+ @Test
+ public void testMetadataListDetailsAfterTypeRemoving() throws IOException {
+ Path typeFile = FS.getPath("type0.bin");
+
+ try {
+ int typeId = createType("Type0", 0);
+
+ assertEquals(EXIT_CODE_OK, execute("--meta", "list"));
+ assertContains(log, testOut.toString(), "typeName=Type0");
+
+ assertEquals(EXIT_CODE_OK, execute("--meta", "details", "--typeName", "Type0"));
+ checkTypeDetails(log, testOut.toString(), crd.binary().type(typeId));
+
+ assertEquals(EXIT_CODE_OK, execute("--meta", "remove",
+ "--typeName", "Type0",
+ "--out", typeFile.toString()));
+
+ assertEquals(EXIT_CODE_OK, execute("--meta", "list"));
+ assertNotContains(log, testOut.toString(), "typeName=Type0");
+
+ assertEquals(EXIT_CODE_UNEXPECTED_ERROR, execute("--meta", "details", "--typeName", "Type0"));
+ assertContains(log, testOut.toString(), "type not found: " + typeId);
+ }
+ finally {
+ Files.deleteIfExists(typeFile);
+ }
+ }
+
+ /**
+ * Repeats {@code cons} {@code cnt} times.
+ *
+ * @param cnt Count.
+ * @param cons Cons.
+ */
+ private void repeat(int cnt, Consumer<Integer> cons) {
+ for (int i = 0; i < cnt; i++)
+ cons.accept(i);
+ }
+
+ /**
* @param t Binary type.
*/
private void checkTypeDetails(@Nullable IgniteLogger log, String cmdOut, BinaryType t) {
@@ -459,26 +693,33 @@
* @param typeName Type name
* @param val Field value.
*/
- void createType(String typeName, Object val) {
- createType(crd.binary(), typeName, val);
+ int createType(String typeName, Object val) {
+ return createType(crd.binary(), typeName, val);
}
/**
* @param typeName Type name.
* @param val Field value.
*/
- void createType(IgniteBinary bin, String typeName, Object val) {
- BinaryObjectBuilder bob = bin.builder(typeName);
- bob.setField("fld", val);
- bob.build();
+ int createType(IgniteBinary bin, String typeName, Object val) {
+ return bin.builder(typeName)
+ .setField("fld", val)
+ .build()
+ .type()
+ .typeId();
}
/** */
- private ClientConfiguration clientConfiguration() {
+ protected ClientConfiguration clientConfiguration() {
return new ClientConfiguration()
.setAddresses("127.0.0.1:10800");
}
+ /** */
+ protected String jdbcThinUrl() {
+ return "jdbc:ignite:thin://127.0.0.1";
+ }
+
/**
*
*/
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/tasks/MetadataInfoTask.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/tasks/MetadataInfoTask.java
index f543921..4c0dfce 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/tasks/MetadataInfoTask.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/tasks/MetadataInfoTask.java
@@ -21,6 +21,7 @@
import java.util.List;
import org.apache.ignite.IgniteException;
import org.apache.ignite.compute.ComputeJobResult;
+import org.apache.ignite.internal.binary.BinaryMetadata;
import org.apache.ignite.internal.processors.cache.binary.CacheObjectBinaryProcessorImpl;
import org.apache.ignite.internal.processors.task.GridInternal;
import org.apache.ignite.internal.visor.VisorJob;
@@ -28,7 +29,7 @@
import org.jetbrains.annotations.Nullable;
/**
- * Task for getting binary metadata.
+ * Task for MetadataListCommand and MetadataDetailsCommand commands.
*/
@GridInternal
public class MetadataInfoTask extends VisorMultiNodeTask<MetadataTypeArgs, MetadataListResult, MetadataListResult> {
@@ -55,7 +56,7 @@
}
/**
- * Job for getting binary metadata.
+ * Job for {@link CheckIndexInlineSizes} command.
*/
private static class MetadataListJob extends VisorJob<MetadataTypeArgs, MetadataListResult> {
/** */
@@ -80,8 +81,12 @@
// returns specified metadata
int typeId = arg.typeId(ignite.context());
- return new MetadataListResult(Collections.singleton(
- ((CacheObjectBinaryProcessorImpl)ignite.context().cacheObjects()).binaryMetadata(typeId)));
+ BinaryMetadata binMeta = ((CacheObjectBinaryProcessorImpl)ignite.context().cacheObjects()).binaryMetadata(typeId);
+
+ if (binMeta == null)
+ throw new IgniteException("Failed to get metadata, type not found: " + typeId);
+
+ return new MetadataListResult(Collections.singleton(binMeta));
}
}
}