You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by tl...@apache.org on 2020/06/26 10:28:46 UTC
[ignite] branch master updated: IGNITE-13154 Add command line
commands (control.sh) to manage binary types (#7936)
This is an automated email from the ASF dual-hosted git repository.
tledkov pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git
The following commit(s) were added to refs/heads/master by this push:
new b6bd5a3 IGNITE-13154 Add command line commands (control.sh) to manage binary types (#7936)
b6bd5a3 is described below
commit b6bd5a3855bb2ad4eb3d8791bd707134cc7d2b28
Author: tledkov <tl...@gridgain.com>
AuthorDate: Fri Jun 26 13:28:30 2020 +0300
IGNITE-13154 Add command line commands (control.sh) to manage binary types (#7936)
---
.../org/apache/ignite/internal/IgniteFeatures.java | 4 +-
.../ignite/internal/binary/BinaryContext.java | 7 +
.../apache/ignite/internal/binary/BinaryUtils.java | 7 +-
.../internal/client/thin/ReliableChannel.java | 12 +
.../internal/client/thin/TcpIgniteClient.java | 18 +-
.../internal/commandline/BaselineCommand.java | 4 +-
.../internal/commandline/CommandArgIterator.java | 41 +-
.../ignite/internal/commandline/CommandList.java | 6 +-
.../internal/commandline/CommonArgParser.java | 4 +-
.../ignite/internal/commandline/TxCommands.java | 6 +-
.../internal/commandline/meta/MetadataCommand.java | 132 ++++++
.../commandline/meta/MetadataSubCommandsList.java | 78 ++++
.../subcommands/MetadataAbstractSubCommand.java | 145 +++++++
.../meta/subcommands/MetadataDetailsCommand.java | 90 +++++
.../meta/subcommands/MetadataHelpCommand.java | 54 +++
.../meta/subcommands/MetadataListCommand.java | 56 +++
.../meta/subcommands/MetadataRemoveCommand.java | 116 ++++++
.../meta/subcommands/MetadataUpdateCommand.java | 88 ++++
.../commandline/meta/tasks/MetadataInfoTask.java | 91 +++++
.../commandline/meta/tasks/MetadataListResult.java | 74 ++++
.../commandline/meta/tasks/MetadataMarshalled.java | 86 ++++
.../commandline/meta/tasks/MetadataRemoveTask.java | 149 +++++++
.../commandline/meta/tasks/MetadataTypeArgs.java | 129 ++++++
.../commandline/meta/tasks/MetadataUpdateTask.java | 99 +++++
.../internal/jdbc/thin/JdbcThinConnection.java | 8 +-
.../cache/binary/BinaryMetadataFileStore.java | 204 ++++++++--
.../cache/binary/BinaryMetadataHolder.java | 32 +-
.../cache/binary/BinaryMetadataTransport.java | 446 +++++++++++++++------
.../binary/CacheObjectBinaryProcessorImpl.java | 82 +++-
.../binary/MetadataRemoveAcceptedMessage.java | 96 +++++
.../binary/MetadataRemoveProposedMessage.java | 143 +++++++
.../cacheobject/IgniteCacheObjectProcessor.java | 10 +
.../processors/odbc/ClientListenerProcessor.java | 138 ++++---
.../ignite/plugin/security/SecurityPermission.java | 5 +-
.../main/resources/META-INF/classnames.properties | 11 +
.../commandline/CommandHandlerParsingTest.java | 3 +-
.../cache/binary/BinaryMetadataRemoveTest.java | 308 ++++++++++++++
.../BinaryMetadataRemoveWithPersistenceTest.java | 86 ++++
.../IgnitePdsBinaryMetadataAsyncWritingTest.java | 2 +-
.../testsuites/IgniteBinaryObjectsTestSuite.java | 5 +
.../util/GridCommandHandlerClusterByClassTest.java | 8 +-
...ridCommandHandlerClusterByClassTest_help.output | 16 +
...andHandlerClusterByClassWithSSLTest_help.output | 16 +
...teCacheWithIndexingAndPersistenceTestSuite.java | 4 +-
.../util/GridCommandHandlerMetadataTest.java | 384 ++++++++++++++++++
45 files changed, 3260 insertions(+), 243 deletions(-)
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/IgniteFeatures.java b/modules/core/src/main/java/org/apache/ignite/internal/IgniteFeatures.java
index 904d93e..d245459 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/IgniteFeatures.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/IgniteFeatures.java
@@ -105,8 +105,10 @@ public enum IgniteFeatures {
CHECK_INDEX_INLINE_SIZES(36),
/** Distributed propagation of tx collisions dump interval. */
- DISTRIBUTED_TX_COLLISIONS_DUMP(37);
+ DISTRIBUTED_TX_COLLISIONS_DUMP(37),
+ /** Remove metadata from cluster for specified type. */
+ REMOVE_METADATA(39);
/**
* Unique feature identifier.
*/
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java
index 6379174..52dcf1c 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java
@@ -1450,6 +1450,13 @@ public class BinaryContext {
}
/**
+ * @param typeId Type ID.
+ */
+ public synchronized void removeType(int typeId) {
+ schemas.remove(typeId);
+ }
+
+ /**
* Type descriptors.
*/
private static class TypeDescriptors {
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryUtils.java
index ed2b3ac..8491943 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryUtils.java
@@ -1044,8 +1044,11 @@ public class BinaryUtils {
"Type '" + oldMeta.typeName() + "' with typeId " + oldMeta.typeId()
+ " has a different/incorrect type for field '" + newField.getKey()
+ "'. Expected '" + oldFieldTypeName + "' but '" + newFieldTypeName
- + "' was provided. Field type's modification is unsupported, clean {root_path}/marshaller " +
- "and {root_path}/db/binary_meta directories if the type change is required."
+ + "' was provided. The type of an existing field can not be changed. " +
+ "Use a different field name or follow this procedure to reuse the current name:\n" +
+ "- Delete data records that use the old field type;\n" +
+ "- Remove metadata by the command: " +
+ "'control.sh --meta remove --typeId " + oldMeta.typeId() + "'."
);
}
}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ReliableChannel.java b/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ReliableChannel.java
index 89cc783..a2c81d1 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ReliableChannel.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ReliableChannel.java
@@ -104,6 +104,9 @@ final class ReliableChannel implements AutoCloseable, NotificationListener {
/** Channel is closed. */
private volatile boolean closed;
+ /** Fail (disconnect) listeners. */
+ private ArrayList<Runnable> chFailLsnrs = new ArrayList<>();
+
/**
* Constructor.
*/
@@ -410,6 +413,8 @@ final class ReliableChannel implements AutoCloseable, NotificationListener {
// when current index was changed and no other wrong channel will be closed by current thread because
// onChannelFailure checks channel binded to the holder before closing it.
onChannelFailure(channels[curChIdx], ch);
+
+ chFailLsnrs.forEach(Runnable::run);
}
/**
@@ -462,6 +467,13 @@ final class ReliableChannel implements AutoCloseable, NotificationListener {
}
/**
+ * @param chFailLsnr Listener for the channel fail (disconnect).
+ */
+ public void addChannelFailListener(Runnable chFailLsnr) {
+ chFailLsnrs.add(chFailLsnr);
+ }
+
+ /**
* Channels holder.
*/
private class ClientChannelHolder {
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/client/thin/TcpIgniteClient.java b/modules/core/src/main/java/org/apache/ignite/internal/client/thin/TcpIgniteClient.java
index 3db66f5..b5ab5e4 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/client/thin/TcpIgniteClient.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/client/thin/TcpIgniteClient.java
@@ -103,7 +103,9 @@ public class TcpIgniteClient implements IgniteClient {
Function<ClientChannelConfiguration, ClientChannel> chFactory,
ClientConfiguration cfg
) throws ClientException {
- marsh = new ClientBinaryMarshaller(new ClientBinaryMetadataHandler(), new ClientMarshallerContext());
+ final ClientBinaryMetadataHandler metadataHandler = new ClientBinaryMetadataHandler();
+
+ marsh = new ClientBinaryMarshaller(metadataHandler, new ClientMarshallerContext());
marsh.setBinaryConfiguration(cfg.getBinaryConfiguration());
@@ -113,6 +115,8 @@ public class TcpIgniteClient implements IgniteClient {
ch = new ReliableChannel(chFactory, cfg, binary);
+ ch.addChannelFailListener(() -> metadataHandler.onReconnect());
+
transactions = new TcpClientTransactions(ch, marsh,
new ClientTransactionConfiguration(cfg.getTransactionConfiguration()));
@@ -292,10 +296,11 @@ public class TcpIgniteClient implements IgniteClient {
*/
private class ClientBinaryMetadataHandler implements BinaryMetadataHandler {
/** In-memory metadata cache. */
- private final BinaryMetadataHandler cache = BinaryCachingMetadataHandler.create();
+ private volatile BinaryMetadataHandler cache = BinaryCachingMetadataHandler.create();
/** {@inheritDoc} */
- @Override public void addMeta(int typeId, BinaryType meta, boolean failIfUnregistered) throws BinaryObjectException {
+ @Override public void addMeta(int typeId, BinaryType meta, boolean failIfUnregistered)
+ throws BinaryObjectException {
if (cache.metadata(typeId) == null) {
try {
ch.request(
@@ -372,6 +377,13 @@ public class TcpIgniteClient implements IgniteClient {
@Override public Collection<BinaryType> metadata() throws BinaryObjectException {
return cache.metadata();
}
+
+ /**
+ * Clear local cache on reconnect.
+ */
+ void onReconnect() {
+ cache = BinaryCachingMetadataHandler.create();
+ }
}
/**
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/BaselineCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/BaselineCommand.java
index 46d4f6e..6991dcb 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/commandline/BaselineCommand.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/BaselineCommand.java
@@ -247,7 +247,7 @@ public class BaselineCommand implements Command<BaselineArguments> {
break;
case VERSION:
- baselineArgs.withTopVer(argIter.nextLongArg("topology version"));
+ baselineArgs.withTopVer(argIter.nextNonNegativeLongArg("topology version"));
break;
@@ -264,7 +264,7 @@ public class BaselineCommand implements Command<BaselineArguments> {
baselineArgs.withEnable(autoAdjustArg == AutoAdjustCommandArg.ENABLE);
if (autoAdjustArg == AutoAdjustCommandArg.TIMEOUT)
- baselineArgs.withSoftBaselineTimeout(argIter.nextLongArg("soft timeout"));
+ baselineArgs.withSoftBaselineTimeout(argIter.nextNonNegativeLongArg("soft timeout"));
}
while (argIter.hasNextSubArg());
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/CommandArgIterator.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/CommandArgIterator.java
index d9ff51c..0e1e89b 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/commandline/CommandArgIterator.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/CommandArgIterator.java
@@ -100,16 +100,49 @@ public class CommandArgIterator {
/**
* @return Numeric value.
*/
+ public long nextNonNegativeLongArg(String argName) {
+ long val = nextLongArg(argName);
+
+ if (val < 0)
+ throw new IllegalArgumentException("Invalid value for " + argName + ": " + val);
+
+ return val;
+ }
+
+ /**
+ * @return Numeric value.
+ */
+ public int nextNonNegativeIntArg(String argName) {
+ int val = nextIntArg(argName);
+
+ if (val < 0)
+ throw new IllegalArgumentException("Invalid value for " + argName + ": " + val);
+
+ return val;
+ }
+
+ /**
+ * @return Numeric value.
+ */
public long nextLongArg(String argName) {
String str = nextArg("Expecting " + argName);
try {
- long val = Long.parseLong(str);
+ return str.startsWith("0x") ? Long.parseLong(str.substring(2), 16) : Long.parseLong(str);
+ }
+ catch (NumberFormatException ignored) {
+ throw new IllegalArgumentException("Invalid value for " + argName + ": " + str);
+ }
+ }
- if (val < 0)
- throw new IllegalArgumentException("Invalid value for " + argName + ": " + val);
+ /**
+ * @return Numeric value.
+ */
+ public int nextIntArg(String argName) {
+ String str = nextArg("Expecting " + argName);
- return val;
+ try {
+ return str.startsWith("0x") ? Integer.parseInt(str.substring(2), 16) : Integer.parseInt(str);
}
catch (NumberFormatException ignored) {
throw new IllegalArgumentException("Invalid value for " + argName + ": " + str);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/CommandList.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/CommandList.java
index 196ad4d..a86c06d 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/commandline/CommandList.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/CommandList.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.commandline;
import org.apache.ignite.internal.commandline.cache.CacheCommands;
import org.apache.ignite.internal.commandline.diagnostic.DiagnosticCommand;
import org.apache.ignite.internal.commandline.encryption.EncryptionCommand;
+import org.apache.ignite.internal.commandline.meta.MetadataCommand;
import org.apache.ignite.internal.commandline.query.KillCommand;
import org.apache.ignite.internal.commandline.snapshot.SnapshotCommand;
@@ -61,7 +62,10 @@ public enum CommandList {
KILL("--kill", new KillCommand()),
/** Snapshot commands. */
- SNAPSHOT("--snapshot", new SnapshotCommand());
+ SNAPSHOT("--snapshot", new SnapshotCommand()),
+
+ /** Metadata commands. */
+ METADATA("--meta", new MetadataCommand());
/** Private values copy so there's no need in cloning it every time. */
private static final CommandList[] VALUES = CommandList.values();
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/CommonArgParser.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/CommonArgParser.java
index 88b9d73..5d3568f 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/commandline/CommonArgParser.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/CommonArgParser.java
@@ -242,12 +242,12 @@ public class CommonArgParser {
break;
case CMD_PING_INTERVAL:
- pingInterval = argIter.nextLongArg("ping interval");
+ pingInterval = argIter.nextNonNegativeLongArg("ping interval");
break;
case CMD_PING_TIMEOUT:
- pingTimeout = argIter.nextLongArg("ping timeout");
+ pingTimeout = argIter.nextNonNegativeLongArg("ping timeout");
break;
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/TxCommands.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/TxCommands.java
index 6b0d10f..fbe89f2 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/commandline/TxCommands.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/TxCommands.java
@@ -233,7 +233,7 @@ public class TxCommands implements Command<VisorTxTaskArg> {
case TX_LIMIT:
argIter.nextArg("");
- limit = (int)argIter.nextLongArg(TxCommandArg.TX_LIMIT.toString());
+ limit = (int)argIter.nextNonNegativeLongArg(TxCommandArg.TX_LIMIT.toString());
break;
@@ -271,13 +271,13 @@ public class TxCommands implements Command<VisorTxTaskArg> {
case TX_DURATION:
argIter.nextArg("");
- duration = argIter.nextLongArg(TxCommandArg.TX_DURATION.toString()) * 1000L;
+ duration = argIter.nextNonNegativeLongArg(TxCommandArg.TX_DURATION.toString()) * 1000L;
break;
case TX_SIZE:
argIter.nextArg("");
- size = (int)argIter.nextLongArg(TxCommandArg.TX_SIZE.toString());
+ size = (int)argIter.nextNonNegativeLongArg(TxCommandArg.TX_SIZE.toString());
break;
case TX_LABEL:
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/MetadataCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/MetadataCommand.java
new file mode 100644
index 0000000..bdf53b5
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/MetadataCommand.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.commandline.meta;
+
+import java.util.logging.Logger;
+import org.apache.ignite.internal.client.GridClientConfiguration;
+import org.apache.ignite.internal.commandline.Command;
+import org.apache.ignite.internal.commandline.CommandArgIterator;
+import org.apache.ignite.internal.commandline.meta.subcommands.MetadataRemoveCommand;
+import org.apache.ignite.internal.commandline.meta.subcommands.MetadataUpdateCommand;
+import org.apache.ignite.internal.commandline.meta.tasks.MetadataTypeArgs;
+
+import static org.apache.ignite.IgniteSystemProperties.IGNITE_ENABLE_EXPERIMENTAL_COMMAND;
+import static org.apache.ignite.internal.commandline.Command.usage;
+import static org.apache.ignite.internal.commandline.CommandHandler.UTILITY_NAME;
+import static org.apache.ignite.internal.commandline.CommandList.METADATA;
+import static org.apache.ignite.internal.commandline.CommandLogger.optional;
+import static org.apache.ignite.internal.commandline.meta.MetadataSubCommandsList.DETAILS;
+import static org.apache.ignite.internal.commandline.meta.MetadataSubCommandsList.HELP;
+import static org.apache.ignite.internal.commandline.meta.MetadataSubCommandsList.LIST;
+import static org.apache.ignite.internal.commandline.meta.MetadataSubCommandsList.REMOVE;
+import static org.apache.ignite.internal.commandline.meta.MetadataSubCommandsList.UPDATE;
+
+/**
+ *
+ */
+public class MetadataCommand implements Command<Object> {
+ /**
+ *
+ */
+ private Command<?> delegate;
+
+ /** {@inheritDoc} */
+ @Override public void printUsage(Logger log) {
+ if (!experimentalEnabled())
+ return;
+
+ usage(log, "Print metadata command help:",
+ METADATA,
+ HELP.toString()
+ );
+
+ usage(log, "Print list of binary metadata types:",
+ METADATA,
+ LIST.toString()
+ );
+
+ usage(log, "Print detailed info about specified binary type " +
+ "(the type must be specified by type name or by type identifier):",
+ METADATA,
+ DETAILS.toString(),
+ optional(MetadataTypeArgs.TYPE_ID, "<typeId>"),
+ optional(MetadataTypeArgs.TYPE_NAME, "<typeName>")
+ );
+
+ usage(log, "Remove the metadata of the specified type " +
+ "(the type must be specified by type name or by type identifier) from cluster and saves the removed " +
+ "metadata to the specified file. \n" +
+ "If the file name isn't specified the output file name is: '<typeId>.bin'",
+ METADATA,
+ REMOVE.toString(),
+ optional(MetadataTypeArgs.TYPE_ID, "<typeId>"),
+ optional(MetadataTypeArgs.TYPE_NAME, "<typeName>"),
+ optional(MetadataRemoveCommand.OUT_FILE_NAME, "<fileName>")
+ );
+
+ usage(log, "Update cluster metadata from specified file (file name is required)",
+ METADATA,
+ UPDATE.toString(),
+ MetadataUpdateCommand.IN_FILE_NAME, "<fileName>"
+ );
+ }
+
+ /** {@inheritDoc} */
+ @Override public String name() {
+ return METADATA.toCommandName();
+ }
+
+ /** {@inheritDoc} */
+ @Override public void parseArguments(CommandArgIterator argIter) {
+ MetadataSubCommandsList subcommand = MetadataSubCommandsList.parse(argIter.nextArg("Expected metadata action."));
+
+ if (subcommand == null)
+ throw new IllegalArgumentException("Expected correct metadata action.");
+
+ delegate = subcommand.command();
+
+ delegate.parseArguments(argIter);
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean experimental() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String confirmationPrompt() {
+ return delegate != null ? delegate.confirmationPrompt() : null;
+ }
+
+ /** {@inheritDoc} */
+ @Override public Object execute(GridClientConfiguration clientCfg, Logger log) throws Exception {
+ if (experimentalEnabled())
+ return delegate.execute(clientCfg, log);
+ else {
+ log.warning(String.format("For use experimental command add %s=true to JVM_OPTS in %s",
+ IGNITE_ENABLE_EXPERIMENTAL_COMMAND, UTILITY_NAME));
+
+ return null;
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override public Object arg() {
+ return delegate.arg();
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/MetadataSubCommandsList.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/MetadataSubCommandsList.java
new file mode 100644
index 0000000..b111984
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/MetadataSubCommandsList.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.commandline.meta;
+
+import org.apache.ignite.internal.commandline.Command;
+import org.apache.ignite.internal.commandline.meta.subcommands.MetadataDetailsCommand;
+import org.apache.ignite.internal.commandline.meta.subcommands.MetadataHelpCommand;
+import org.apache.ignite.internal.commandline.meta.subcommands.MetadataListCommand;
+import org.apache.ignite.internal.commandline.meta.subcommands.MetadataRemoveCommand;
+import org.apache.ignite.internal.commandline.meta.subcommands.MetadataUpdateCommand;
+import org.jetbrains.annotations.NotNull;
+
+/** */
+public enum MetadataSubCommandsList {
+ /** */
+ HELP("help", new MetadataHelpCommand()),
+ /** */
+ LIST("list", new MetadataListCommand()),
+ /** */
+ DETAILS("details", new MetadataDetailsCommand()),
+ /** */
+ REMOVE("remove", new MetadataRemoveCommand()),
+ /** */
+ UPDATE("update", new MetadataUpdateCommand());
+
+ /** */
+ private final String name;
+
+ /** */
+ private final Command<?> cmd;
+
+ /** */
+ MetadataSubCommandsList(String name, Command<?> cmd) {
+ this.name = name;
+ this.cmd = cmd;
+ }
+
+ /** */
+ public String text() {
+ return name;
+ }
+
+ /** */
+ @NotNull
+ public Command<?> command() {
+ return cmd;
+ }
+
+ /** */
+ public static MetadataSubCommandsList parse(String name) {
+ for (MetadataSubCommandsList cmd : values()) {
+ if (cmd.name.equalsIgnoreCase(name))
+ return cmd;
+ }
+
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return name;
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/subcommands/MetadataAbstractSubCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/subcommands/MetadataAbstractSubCommand.java
new file mode 100644
index 0000000..0e586e0
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/subcommands/MetadataAbstractSubCommand.java
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.commandline.meta.subcommands;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.util.Collection;
+import java.util.logging.Logger;
+import org.apache.ignite.internal.client.GridClient;
+import org.apache.ignite.internal.client.GridClientCompute;
+import org.apache.ignite.internal.client.GridClientConfiguration;
+import org.apache.ignite.internal.client.GridClientDisconnectedException;
+import org.apache.ignite.internal.client.GridClientNode;
+import org.apache.ignite.internal.commandline.Command;
+import org.apache.ignite.internal.commandline.CommandArgIterator;
+import org.apache.ignite.internal.commandline.CommandLogger;
+import org.apache.ignite.internal.dto.IgniteDataTransferObject;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.visor.VisorTaskArgument;
+
+/** */
+public abstract class MetadataAbstractSubCommand<
+ MetadataArgsDto extends IgniteDataTransferObject,
+ MetadataResultDto extends IgniteDataTransferObject>
+ implements Command<MetadataArgsDto> {
+ /** Filesystem. */
+ protected static final FileSystem FS = FileSystems.getDefault();
+
+ /** */
+ private MetadataArgsDto args;
+
+ /** {@inheritDoc} */
+ @Override public boolean experimental() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public final void printUsage(Logger log) {
+ throw new UnsupportedOperationException("printUsage");
+ }
+
+ /** {@inheritDoc} */
+ @Override public final void parseArguments(CommandArgIterator argIter) {
+ args = parseArguments0(argIter);
+ }
+
+ /** {@inheritDoc} */
+ @Override public final Object execute(GridClientConfiguration clientCfg, Logger log) throws Exception {
+ try (GridClient client = Command.startClient(clientCfg)) {
+ GridClientCompute compute = client.compute();
+
+ // Try to find connectable server nodes.
+ Collection<GridClientNode> nodes = compute.nodes((n) -> n.connectable() && !n.isClient());
+
+ if (F.isEmpty(nodes)) {
+ nodes = compute.nodes(GridClientNode::connectable);
+
+ if (F.isEmpty(nodes))
+ throw new GridClientDisconnectedException("Connectable nodes not found", null);
+ }
+
+ GridClientNode node = nodes.stream()
+ .findAny().orElse(null);
+
+ if (node == null)
+ node = compute.balancer().balancedNode(nodes);
+
+ MetadataResultDto res = compute.projection(node).execute(
+ taskName(),
+ new VisorTaskArgument<>(node.nodeId(), arg(), false)
+ );
+
+ printResult(res, log);
+ }
+ catch (Throwable e) {
+ log.severe("Failed to execute metadata command='" + name() + "'");
+ log.severe(CommandLogger.errorMessage(e));
+
+ throw e;
+ }
+
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override public final MetadataArgsDto arg() {
+ return args;
+ }
+
+ /** */
+ protected abstract String taskName();
+
+ /** */
+ protected MetadataArgsDto parseArguments0(CommandArgIterator argIter) {
+ return null;
+ }
+
+ /** */
+ protected abstract void printResult(MetadataResultDto res, Logger log);
+
+ /**
+ * @param val Integer value.
+ * @return String.
+ */
+ protected String printInt(int val) {
+ return "0x" + Integer.toHexString(val).toUpperCase() + " (" + val + ')';
+ }
+
+ /**
+ *
+ */
+ public static class VoidDto extends IgniteDataTransferObject {
+ /** */
+ private static final long serialVersionUID = 0L;
+
+ /** {@inheritDoc} */
+ @Override protected void writeExternalData(ObjectOutput out) throws IOException {
+ // No-op.
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void readExternalData(byte protoVer, ObjectInput in)
+ throws IOException, ClassNotFoundException {
+ // No-op.
+ }
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/subcommands/MetadataDetailsCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/subcommands/MetadataDetailsCommand.java
new file mode 100644
index 0000000..4c919c0
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/subcommands/MetadataDetailsCommand.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.commandline.meta.subcommands;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import org.apache.ignite.internal.binary.BinaryMetadata;
+import org.apache.ignite.internal.binary.BinaryUtils;
+import org.apache.ignite.internal.commandline.CommandArgIterator;
+import org.apache.ignite.internal.commandline.meta.MetadataSubCommandsList;
+import org.apache.ignite.internal.commandline.meta.tasks.MetadataInfoTask;
+import org.apache.ignite.internal.commandline.meta.tasks.MetadataListResult;
+import org.apache.ignite.internal.commandline.meta.tasks.MetadataTypeArgs;
+import org.apache.ignite.internal.util.typedef.F;
+
+import static org.apache.ignite.internal.commandline.CommandLogger.INDENT;
+
+/** */
+public class MetadataDetailsCommand
+ extends MetadataAbstractSubCommand<MetadataTypeArgs, MetadataListResult>
+{
+ /** {@inheritDoc} */
+ @Override protected String taskName() {
+ return MetadataInfoTask.class.getName();
+ }
+
+ /** {@inheritDoc} */
+ @Override public MetadataTypeArgs parseArguments0(CommandArgIterator argIter) {
+ return MetadataTypeArgs.parseArguments(argIter);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void printResult(MetadataListResult res, Logger log) {
+ if (res.metadata() == null) {
+ log.info("Type not found");
+
+ return;
+ }
+
+ assert res.metadata().size() == 1 : "Unexpected metadata results: " + res.metadata();
+
+ BinaryMetadata m = F.first(res.metadata());
+
+ log.info("typeId=" + printInt(m.typeId()));
+ log.info("typeName=" + m.typeName());
+ log.info("Fields:");
+
+ final Map<Integer, String> fldMap = new HashMap<>();
+ m.fieldsMap().forEach((name, fldMeta) -> {
+ log.info(INDENT +
+ "name=" + name +
+ ", type=" + BinaryUtils.fieldTypeName(fldMeta.typeId()) +
+ ", fieldId=" + printInt(fldMeta.fieldId()));
+
+ fldMap.put(fldMeta.fieldId(), name);
+ });
+
+ log.info("Schemas:");
+
+ m.schemas().forEach(s ->
+ log.info(INDENT +
+ "schemaId=" + printInt(s.schemaId()) +
+ ", fields=" + Arrays.stream(s.fieldIds())
+ .mapToObj(fldMap::get)
+ .collect(Collectors.toList())));
+ }
+
+ /** {@inheritDoc} */
+ @Override public String name() {
+ return MetadataSubCommandsList.LIST.text();
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/subcommands/MetadataHelpCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/subcommands/MetadataHelpCommand.java
new file mode 100644
index 0000000..4f63be5
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/subcommands/MetadataHelpCommand.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.commandline.meta.subcommands;
+
+import java.util.logging.Logger;
+import org.apache.ignite.internal.client.GridClientConfiguration;
+import org.apache.ignite.internal.commandline.Command;
+import org.apache.ignite.internal.commandline.meta.MetadataCommand;
+import org.apache.ignite.internal.commandline.meta.MetadataSubCommandsList;
+
+/** */
+public class MetadataHelpCommand implements Command<Void> {
+ /** {@inheritDoc} */
+ @Override public void printUsage(Logger log) {
+ throw new UnsupportedOperationException("printUsage");
+ }
+
+ /** {@inheritDoc} */
+ @Override public Object execute(GridClientConfiguration clientCfg, Logger log) throws Exception {
+ new MetadataCommand().printUsage(log);
+
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean experimentalEnabled() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public Void arg() {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String name() {
+ return MetadataSubCommandsList.HELP.text();
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/subcommands/MetadataListCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/subcommands/MetadataListCommand.java
new file mode 100644
index 0000000..a559f12
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/subcommands/MetadataListCommand.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.commandline.meta.subcommands;
+
+import java.util.logging.Logger;
+import org.apache.ignite.internal.binary.BinaryMetadata;
+import org.apache.ignite.internal.commandline.CommandArgIterator;
+import org.apache.ignite.internal.commandline.meta.MetadataSubCommandsList;
+import org.apache.ignite.internal.commandline.meta.tasks.MetadataInfoTask;
+import org.apache.ignite.internal.commandline.meta.tasks.MetadataListResult;
+
+/** */
+public class MetadataListCommand
+ extends MetadataAbstractSubCommand<MetadataAbstractSubCommand.VoidDto, MetadataListResult>
+{
+ /** {@inheritDoc} */
+ @Override protected String taskName() {
+ return MetadataInfoTask.class.getName();
+ }
+
+ /** {@inheritDoc} */
+ @Override public VoidDto parseArguments0(CommandArgIterator argIter) {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void printResult(MetadataListResult res, Logger log) {
+ for (BinaryMetadata m : res.metadata()) {
+ log.info("typeId=" + printInt(m.typeId()) +
+ ", typeName=" + m.typeName() +
+ ", fields=" + m.fields().size() +
+ ", schemas=" + m.schemas().size() +
+ ", isEnum=" + m.isEnum());
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override public String name() {
+ return MetadataSubCommandsList.LIST.text();
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/subcommands/MetadataRemoveCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/subcommands/MetadataRemoveCommand.java
new file mode 100644
index 0000000..0e2929b
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/subcommands/MetadataRemoveCommand.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.commandline.meta.subcommands;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.logging.Logger;
+import org.apache.ignite.internal.binary.BinaryMetadata;
+import org.apache.ignite.internal.commandline.CommandArgIterator;
+import org.apache.ignite.internal.commandline.CommandLogger;
+import org.apache.ignite.internal.commandline.meta.MetadataSubCommandsList;
+import org.apache.ignite.internal.commandline.meta.tasks.MetadataMarshalled;
+import org.apache.ignite.internal.commandline.meta.tasks.MetadataRemoveTask;
+import org.apache.ignite.internal.commandline.meta.tasks.MetadataTypeArgs;
+
+/**
+ *
+ */
+public class MetadataRemoveCommand
+ extends MetadataAbstractSubCommand<MetadataTypeArgs, MetadataMarshalled> {
+ /** Output file name. */
+ public static final String OUT_FILE_NAME = "--out";
+
+ /** Output file. */
+ private Path outFile;
+
+ /** {@inheritDoc} */
+ @Override protected String taskName() {
+ return MetadataRemoveTask.class.getName();
+ }
+
+ /** {@inheritDoc} */
+ @Override public String confirmationPrompt() {
+ return "Warning: the command will remove the binary metadata for a type \""
+ + arg().toString() + "\" from cluster.";
+ }
+
+ /** {@inheritDoc} */
+ @Override public MetadataTypeArgs parseArguments0(CommandArgIterator argIter) {
+ outFile = null;
+
+ MetadataTypeArgs argType = MetadataTypeArgs.parseArguments(argIter);
+
+ while (argIter.hasNextSubArg() && outFile == null) {
+ String opt = argIter.nextArg("");
+
+ if (OUT_FILE_NAME.equalsIgnoreCase(opt)) {
+ String fileName = argIter.nextArg("output file name");
+
+ outFile = FS.getPath(fileName);
+ }
+ }
+
+ if (outFile != null) {
+ try (OutputStream os = Files.newOutputStream(outFile)) {
+ os.close();
+
+ Files.delete(outFile);
+ }
+ catch (IOException e) {
+ throw new IllegalArgumentException("Cannot write to output file " + outFile +
+ ". Error: " + e.toString(), e);
+ }
+ }
+
+ return argType;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void printResult(MetadataMarshalled res, Logger log) {
+ if (res.metadata() == null) {
+ log.info("Type not found");
+
+ return;
+ }
+
+ BinaryMetadata m = res.metadata();
+
+ if (outFile == null)
+ outFile = FS.getPath(m.typeId() + ".bin");
+
+ try (OutputStream os = Files.newOutputStream(outFile)) {
+ os.write(res.metadataMarshalled());
+ }
+ catch (IOException e) {
+ log.severe("Cannot store removed type '" + m.typeName() + "' to: " + outFile);
+ log.severe(CommandLogger.errorMessage(e));
+
+ return;
+ }
+
+ log.info("Type '" + m.typeName() + "' is removed. Metadata is stored at: " + outFile);
+ }
+
+ /** {@inheritDoc} */
+ @Override public String name() {
+ return MetadataSubCommandsList.REMOVE.text();
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/subcommands/MetadataUpdateCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/subcommands/MetadataUpdateCommand.java
new file mode 100644
index 0000000..5b7f46c
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/subcommands/MetadataUpdateCommand.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.commandline.meta.subcommands;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.logging.Logger;
+import org.apache.ignite.internal.binary.BinaryMetadata;
+import org.apache.ignite.internal.commandline.CommandArgIterator;
+import org.apache.ignite.internal.commandline.meta.MetadataSubCommandsList;
+import org.apache.ignite.internal.commandline.meta.tasks.MetadataMarshalled;
+import org.apache.ignite.internal.commandline.meta.tasks.MetadataUpdateTask;
+import org.apache.ignite.internal.util.typedef.internal.U;
+
+/** */
+public class MetadataUpdateCommand
+ extends MetadataAbstractSubCommand<MetadataMarshalled, MetadataMarshalled>
+{
+ /** Output file name. */
+ public static final String IN_FILE_NAME = "--in";
+
+ /** {@inheritDoc} */
+ @Override protected String taskName() {
+ return MetadataUpdateTask.class.getName();
+ }
+
+ /** {@inheritDoc} */
+ @Override public String confirmationPrompt() {
+ return "Warning: the command will update the binary metadata at the cluster.";
+ }
+
+ /** {@inheritDoc} */
+ @Override public MetadataMarshalled parseArguments0(CommandArgIterator argIter) {
+ String opt = argIter.nextArg(IN_FILE_NAME);
+
+ if (!IN_FILE_NAME.equalsIgnoreCase(opt))
+ throw new IllegalArgumentException("The option '" + IN_FILE_NAME + "' is required");
+
+ Path inFile = FS.getPath(argIter.nextArg("input file name"));
+
+ try (InputStream is = Files.newInputStream(inFile)) {
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+
+ U.copy(is, buf);
+
+ return new MetadataMarshalled(buf.toByteArray(), null);
+ }
+ catch (IOException e) {
+ throw new IllegalArgumentException("Cannot read metadata from " + inFile, e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void printResult(MetadataMarshalled res, Logger log) {
+ if (res.metadata() == null) {
+ log.info("Type not found");
+
+ return;
+ }
+
+ BinaryMetadata m = res.metadata();
+
+ log.info("Metadata updated for the type: '" + m.typeName() + '\'');
+ }
+
+ /** {@inheritDoc} */
+ @Override public String name() {
+ return MetadataSubCommandsList.UPDATE.text();
+ }
+}
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
new file mode 100644
index 0000000..e7a28b5
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/tasks/MetadataInfoTask.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.commandline.meta.tasks;
+
+import java.util.Collections;
+import java.util.List;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.compute.ComputeJobResult;
+import org.apache.ignite.internal.commandline.cache.CheckIndexInlineSizes;
+import org.apache.ignite.internal.commandline.meta.subcommands.MetadataDetailsCommand;
+import org.apache.ignite.internal.commandline.meta.subcommands.MetadataListCommand;
+import org.apache.ignite.internal.processors.cache.binary.CacheObjectBinaryProcessorImpl;
+import org.apache.ignite.internal.processors.task.GridInternal;
+import org.apache.ignite.internal.visor.VisorJob;
+import org.apache.ignite.internal.visor.VisorMultiNodeTask;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Task for {@link MetadataListCommand} and {@link MetadataDetailsCommand} commands.
+ */
+@GridInternal
+public class MetadataInfoTask extends VisorMultiNodeTask<MetadataTypeArgs, MetadataListResult, MetadataListResult> {
+ /** */
+ private static final long serialVersionUID = 0L;
+
+ /** {@inheritDoc} */
+ @Override protected VisorJob<MetadataTypeArgs, MetadataListResult> job(MetadataTypeArgs arg) {
+ return new MetadataListJob(arg, debug);
+ }
+
+ /** {@inheritDoc} */
+ @Nullable @Override protected MetadataListResult reduce0(List<ComputeJobResult> results) {
+ if (results.isEmpty())
+ throw new IgniteException("Empty job results");
+
+ if (results.size() > 1)
+ throw new IgniteException("Invalid job results: " + results);
+
+ if (results.get(0).getException() != null)
+ throw results.get(0).getException();
+ else
+ return results.get(0).getData();
+ }
+
+ /**
+ * Job for {@link CheckIndexInlineSizes} command.
+ */
+ private static class MetadataListJob extends VisorJob<MetadataTypeArgs, MetadataListResult> {
+ /** */
+ private static final long serialVersionUID = 0L;
+
+ /**
+ * @param arg Argument.
+ * @param debug Debug.
+ */
+ protected MetadataListJob(@Nullable MetadataTypeArgs arg, boolean debug) {
+ super(arg, debug);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected MetadataListResult run(@Nullable MetadataTypeArgs arg) throws IgniteException {
+ if (arg == null) {
+ // returns full metadata
+ return new MetadataListResult(
+ ((CacheObjectBinaryProcessorImpl)ignite.context().cacheObjects()).binaryMetadata());
+ }
+ else {
+ // returns specified metadata
+ int typeId = arg.typeId(ignite.context());
+
+ return new MetadataListResult(Collections.singleton(
+ ((CacheObjectBinaryProcessorImpl)ignite.context().cacheObjects()).binaryMetadata(typeId)));
+ }
+ }
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/tasks/MetadataListResult.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/tasks/MetadataListResult.java
new file mode 100644
index 0000000..7bcf6af
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/tasks/MetadataListResult.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.commandline.meta.tasks;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.Collection;
+import java.util.Collections;
+import org.apache.ignite.internal.binary.BinaryMetadata;
+import org.apache.ignite.internal.dto.IgniteDataTransferObject;
+import org.apache.ignite.internal.processors.task.GridInternal;
+import org.apache.ignite.internal.util.typedef.internal.U;
+
+/**
+ * Represents information about cluster metadata.
+ */
+@GridInternal
+public class MetadataListResult extends IgniteDataTransferObject {
+ /** */
+ private static final long serialVersionUID = 0L;
+
+ /** Cluster metadata. */
+ private Collection<BinaryMetadata> meta = Collections.emptyList();
+
+ /**
+ * Constructor for optimized marshaller.
+ */
+ public MetadataListResult() {
+ // No-op.
+ }
+
+ /**
+ * @param meta Meta.
+ */
+ public MetadataListResult(Collection<BinaryMetadata> meta) {
+ this.meta = meta;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void writeExternalData(ObjectOutput out) throws IOException {
+ U.writeCollection(out, meta);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void readExternalData(
+ byte protoVer,
+ ObjectInput in
+ ) throws IOException, ClassNotFoundException {
+ meta = U.readCollection(in);
+ }
+
+ /**
+ * @return Cluster binary metadata.
+ */
+ public Collection<BinaryMetadata> metadata() {
+ return meta;
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/tasks/MetadataMarshalled.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/tasks/MetadataMarshalled.java
new file mode 100644
index 0000000..fbe7c67
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/tasks/MetadataMarshalled.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.commandline.meta.tasks;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import org.apache.ignite.internal.binary.BinaryMetadata;
+import org.apache.ignite.internal.dto.IgniteDataTransferObject;
+import org.apache.ignite.internal.processors.task.GridInternal;
+import org.apache.ignite.internal.util.typedef.internal.U;
+
+/**
+ * Represents information about cluster metadata.
+ */
+@GridInternal
+public class MetadataMarshalled extends IgniteDataTransferObject {
+ /** */
+ private static final long serialVersionUID = 0L;
+
+ /** Marshaled metadata. */
+ private byte[] metaMarshalled;
+
+ /** Metadata. */
+ private BinaryMetadata meta;
+
+ /**
+ * Constructor for optimized marshaller.
+ */
+ public MetadataMarshalled() {
+ // No-op.
+ }
+
+ /**
+ * @param metaMarshalled Marshaled metadata.
+ * @param meta Meta.
+ */
+ public MetadataMarshalled(byte[] metaMarshalled, BinaryMetadata meta) {
+ this.metaMarshalled = metaMarshalled;
+ this.meta = meta;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void writeExternalData(ObjectOutput out) throws IOException {
+ U.writeByteArray(out, metaMarshalled);
+ out.writeObject(meta);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void readExternalData(
+ byte protoVer,
+ ObjectInput in
+ ) throws IOException, ClassNotFoundException {
+ metaMarshalled = U.readByteArray(in);
+ meta = (BinaryMetadata)in.readObject();
+ }
+
+ /**
+ * @return Cluster binary metadata.
+ */
+ public BinaryMetadata metadata() {
+ return meta;
+ }
+
+ /**
+ * @return Marshalled metadata.
+ */
+ public byte[] metadataMarshalled() {
+ return metaMarshalled;
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/tasks/MetadataRemoveTask.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/tasks/MetadataRemoveTask.java
new file mode 100644
index 0000000..5bd7b7a
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/tasks/MetadataRemoveTask.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.commandline.meta.tasks;
+
+import java.util.List;
+import java.util.Objects;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.compute.ComputeJobContext;
+import org.apache.ignite.compute.ComputeJobResult;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.binary.BinaryMetadata;
+import org.apache.ignite.internal.commandline.cache.CheckIndexInlineSizes;
+import org.apache.ignite.internal.processors.cache.binary.CacheObjectBinaryProcessorImpl;
+import org.apache.ignite.internal.processors.task.GridInternal;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.internal.visor.VisorJob;
+import org.apache.ignite.internal.visor.VisorMultiNodeTask;
+import org.apache.ignite.lang.IgniteFuture;
+import org.apache.ignite.lang.IgniteInClosure;
+import org.apache.ignite.lang.IgniteRunnable;
+import org.apache.ignite.plugin.security.SecurityPermission;
+import org.apache.ignite.resources.IgniteInstanceResource;
+import org.apache.ignite.resources.JobContextResource;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Task for {@link MetadataRemoveTask} command.
+ */
+@GridInternal
+public class MetadataRemoveTask extends VisorMultiNodeTask<MetadataTypeArgs, MetadataMarshalled, MetadataMarshalled> {
+ /** */
+ private static final long serialVersionUID = 0L;
+
+ /** {@inheritDoc} */
+ @Override protected VisorJob<MetadataTypeArgs, MetadataMarshalled> job(MetadataTypeArgs arg) {
+ return new MetadataRemoveJob(arg, debug);
+ }
+
+ /** {@inheritDoc} */
+ @Nullable @Override protected MetadataMarshalled reduce0(List<ComputeJobResult> results) {
+ if (results.size() != 1)
+ throw new IgniteException("Invalid job results. Expected exactly 1 result, but was: " + results);
+
+ if (results.get(0).getException() != null)
+ throw results.get(0).getException();
+ else
+ return results.get(0).getData();
+ }
+
+ /**
+ * Job for {@link CheckIndexInlineSizes} command.
+ */
+ private static class MetadataRemoveJob extends VisorJob<MetadataTypeArgs, MetadataMarshalled> {
+ /** */
+ private static final long serialVersionUID = 0L;
+
+ /** Auto-inject job context. */
+ @JobContextResource
+ private transient ComputeJobContext jobCtx;
+
+ /** Metadata future. */
+ private transient IgniteFuture<Void> future;
+
+ /** Job result: metadata info for removed type (used for job continuation). */
+ private transient MetadataMarshalled res;
+
+ /**
+ * @param arg Argument.
+ * @param debug Debug.
+ */
+ protected MetadataRemoveJob(@Nullable MetadataTypeArgs arg, boolean debug) {
+ super(arg, debug);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected MetadataMarshalled run(@Nullable MetadataTypeArgs arg) throws IgniteException {
+ try {
+ if (future == null) {
+ ignite.context().security().authorize(null, SecurityPermission.ADMIN_METADATA_OPS);
+
+ assert Objects.nonNull(arg);
+
+ int typeId = arg.typeId(ignite.context());
+
+ BinaryMetadata meta = ((CacheObjectBinaryProcessorImpl)ignite.context().cacheObjects())
+ .binaryMetadata(typeId);
+
+ byte[] marshalled = U.marshal(ignite.context(), meta);
+
+ res = new MetadataMarshalled(marshalled, meta);
+
+ ignite.context().cacheObjects().removeType(typeId);
+
+ future = ignite.compute().broadcastAsync(new DropAllThinSessionsJob());
+
+ jobCtx.holdcc();
+
+ future.listen((IgniteInClosure<IgniteFuture<Void>>)f -> {
+ if (f.isDone())
+ jobCtx.callcc();
+ });
+
+ return null;
+ }
+
+ return res;
+ }
+ catch (IgniteCheckedException e) {
+ throw new IgniteException(e);
+ }
+ }
+ }
+
+ /**
+ * Job to drop all thin session.
+ */
+ @GridInternal
+ private static class DropAllThinSessionsJob implements IgniteRunnable {
+ /** */
+ private static final long serialVersionUID = 0L;
+
+ /** Grid */
+ @IgniteInstanceResource
+ private IgniteEx ignite;
+
+ /** {@inheritDoc} */
+ @Override public void run() throws IgniteException {
+ ignite.context().security().authorize(null, SecurityPermission.ADMIN_METADATA_OPS);
+
+ ignite.context().sqlListener().closeAllSessions();
+ }
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/tasks/MetadataTypeArgs.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/tasks/MetadataTypeArgs.java
new file mode 100644
index 0000000..b57ca3f
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/tasks/MetadataTypeArgs.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.commandline.meta.tasks;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import org.apache.ignite.internal.GridKernalContext;
+import org.apache.ignite.internal.commandline.CommandArgIterator;
+import org.apache.ignite.internal.dto.IgniteDataTransferObject;
+import org.apache.ignite.internal.util.typedef.internal.U;
+
+/** */
+public class MetadataTypeArgs extends IgniteDataTransferObject {
+ /** Serial version uid. */
+ private static final long serialVersionUID = 0L;
+
+ /** Type name argument. */
+ public static final String TYPE_NAME = "--typeName";
+
+ /** Type ID argument. */
+ public static final String TYPE_ID = "--typeId";
+
+ /** Config. */
+ private String typeName;
+
+ /** Metrics. */
+ private Integer typeId;
+
+ /**
+ * Default constructor.
+ */
+ public MetadataTypeArgs() {
+ // No-op.
+ }
+
+ /** */
+ public MetadataTypeArgs(String typeName, Integer typeId) {
+ assert typeName != null ^ typeId != null;
+
+ this.typeName = typeName;
+ this.typeId = typeId;
+ }
+
+ /** */
+ public String typeName() {
+ return typeName;
+ }
+
+ /** */
+ public int typeId(GridKernalContext ctx) {
+ if (typeId != null)
+ return typeId;
+ else
+ return ctx.cacheObjects().typeId(typeName);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void writeExternalData(ObjectOutput out) throws IOException {
+ out.writeBoolean(typeName != null);
+
+ if (typeName != null)
+ U.writeString(out, typeName);
+ else
+ out.writeInt(typeId);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void readExternalData(byte protoVer, ObjectInput in) throws IOException, ClassNotFoundException {
+ boolean useName = in.readBoolean();
+
+ if (useName)
+ typeName = U.readString(in);
+ else
+ typeId = in.readInt();
+ }
+
+ /**
+ * @param argIter Command line arguments iterator.
+ * @return Metadata type argument.
+ */
+ public static MetadataTypeArgs parseArguments(CommandArgIterator argIter) {
+ String typeName = null;
+ Integer typeId = null;
+
+ while (argIter.hasNextSubArg() && typeName == null && typeId == null) {
+ String optName = argIter.nextArg("Expecting " + TYPE_NAME + " or " + TYPE_ID);
+
+ switch (optName) {
+ case TYPE_NAME:
+ typeName = argIter.nextArg("type name");
+
+ break;
+
+ case TYPE_ID:
+ typeId = argIter.nextIntArg("typeId");
+
+ break;
+ }
+ }
+
+ if (typeName == null && typeId == null) {
+ throw new IllegalArgumentException("Type to remove is not specified. " +
+ "Please add one of the options: --typeName <type_name> or --typeId <type_id>");
+ }
+
+ return new MetadataTypeArgs(typeName, typeId);
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return typeId != null ? "0x" + Integer.toHexString(typeId).toUpperCase() : typeName;
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/tasks/MetadataUpdateTask.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/tasks/MetadataUpdateTask.java
new file mode 100644
index 0000000..a8dfa68
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/meta/tasks/MetadataUpdateTask.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.commandline.meta.tasks;
+
+import java.util.List;
+import java.util.Objects;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.compute.ComputeJobResult;
+import org.apache.ignite.internal.binary.BinaryMetadata;
+import org.apache.ignite.internal.commandline.cache.CheckIndexInlineSizes;
+import org.apache.ignite.internal.commandline.meta.subcommands.MetadataUpdateCommand;
+import org.apache.ignite.internal.processors.cache.binary.CacheObjectBinaryProcessorImpl;
+import org.apache.ignite.internal.processors.task.GridInternal;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.internal.visor.VisorJob;
+import org.apache.ignite.internal.visor.VisorMultiNodeTask;
+import org.apache.ignite.plugin.security.SecurityPermission;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Task for {@link MetadataUpdateCommand} command.
+ */
+@GridInternal
+public class MetadataUpdateTask extends VisorMultiNodeTask<MetadataMarshalled, MetadataMarshalled, MetadataMarshalled> {
+ /** */
+ private static final long serialVersionUID = 0L;
+
+ /** {@inheritDoc} */
+ @Override protected VisorJob<MetadataMarshalled, MetadataMarshalled> job(MetadataMarshalled arg) {
+ return new MetadataUpdateJob(arg, debug);
+ }
+
+ /** {@inheritDoc} */
+ @Nullable @Override protected MetadataMarshalled reduce0(List<ComputeJobResult> results) {
+ if (results.size() != 1)
+ throw new IgniteException("Invalid job results. Expected exactly 1 result, but was: " + results);
+
+ if (results.get(0).getException() != null)
+ throw results.get(0).getException();
+ else
+ return results.get(0).getData();
+ }
+
+ /**
+ * Job for {@link CheckIndexInlineSizes} command.
+ */
+ private static class MetadataUpdateJob extends VisorJob<MetadataMarshalled, MetadataMarshalled> {
+ /** */
+ private static final long serialVersionUID = 0L;
+
+ /**
+ * @param arg Argument.
+ * @param debug Debug.
+ */
+ protected MetadataUpdateJob(@Nullable MetadataMarshalled arg, boolean debug) {
+ super(arg, debug);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected MetadataMarshalled run(@Nullable MetadataMarshalled arg) throws IgniteException {
+ ignite.context().security().authorize(null, SecurityPermission.ADMIN_METADATA_OPS);
+
+ assert Objects.nonNull(arg);
+
+ byte[] marshalled = arg.metadataMarshalled();
+
+ try {
+ BinaryMetadata meta = U.unmarshal(
+ ignite.context(),
+ marshalled,
+ U.resolveClassLoader(ignite.context().config()));
+
+ ((CacheObjectBinaryProcessorImpl)ignite.context().cacheObjects()).binaryContext()
+ .updateMetadata(meta.typeId(), meta, false);
+
+ return new MetadataMarshalled(null, meta);
+ }
+ catch (IgniteCheckedException e) {
+ throw new IgniteException(e);
+ }
+ }
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinConnection.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinConnection.java
index c35ee28..bb530fd 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinConnection.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinConnection.java
@@ -250,10 +250,10 @@ public class JdbcThinConnection implements Connection {
private final IgniteProductVersion baseEndpointVer;
/** Binary context. */
- private final BinaryContext ctx;
+ private volatile BinaryContext ctx;
/** Binary metadata handler. */
- private final JdbcBinaryMetadataHandler metaHnd;
+ private volatile JdbcBinaryMetadataHandler metaHnd;
/** Marshaller context. */
private final JdbcMarshallerContext marshCtx;
@@ -1333,6 +1333,10 @@ public class JdbcThinConnection implements Connection {
stmts.clear();
}
+
+ // Clear local metadata cache on disconnect.
+ metaHnd = new JdbcBinaryMetadataHandler();
+ ctx = createBinaryCtx(metaHnd, marshCtx);
}
/**
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/binary/BinaryMetadataFileStore.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/binary/BinaryMetadataFileStore.java
index 1614a0a..1c9540c 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/binary/BinaryMetadataFileStore.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/binary/BinaryMetadataFileStore.java
@@ -167,6 +167,32 @@ class BinaryMetadataFileStore {
}
/**
+ * Remove metadata for specified type.
+ *
+ * @param typeId Type identifier.
+ */
+ private void removeMeta(int typeId) {
+ if (!isPersistenceEnabled)
+ return;
+
+ File file = new File(metadataDir, typeId + ".bin");
+
+ if (!file.delete()) {
+ final String msg = "Failed to remove metadata for typeId: " + typeId;
+
+ U.error(log, msg);
+
+ writer.cancel();
+
+ IgniteException e = new IgniteException(msg);
+
+ ctx.failure().process(new FailureContext(FailureType.CRITICAL_ERROR, e));
+
+ throw e;
+ }
+ }
+
+ /**
* Restores metadata on startup of {@link CacheObjectBinaryProcessorImpl} but before starting discovery.
*/
void restoreMetadata() {
@@ -244,7 +270,17 @@ class BinaryMetadataFileStore {
if (!isPersistenceEnabled)
return;
- writer.startWritingAsync(typeId, typeVer);
+ writer.startTaskAsync(typeId, typeVer);
+ }
+
+ /**
+ * @param typeId Type ID.
+ */
+ public void removeMetadataAsync(int typeId) {
+ if (!isPersistenceEnabled)
+ return;
+
+ writer.startTaskAsync(typeId, BinaryMetadataTransport.REMOVED_VERSION);
}
/**
@@ -272,7 +308,7 @@ class BinaryMetadataFileStore {
if (!isPersistenceEnabled)
return;
- writer.finishWriteFuture(typeId, typeVer);
+ writer.finishWriteFuture(typeId, typeVer, null);
}
/**
@@ -315,33 +351,46 @@ class BinaryMetadataFileStore {
}
/**
+ * @param typeId Type ID.
+ */
+ void prepareMetadataRemove(int typeId) {
+ if (!isPersistenceEnabled)
+ return;
+
+ writer.cancelTasksForType(typeId);
+
+ writer.prepareRemoveFuture(typeId);
+ }
+
+ /**
*
*/
private class BinaryMetadataAsyncWriter extends GridWorker {
/**
* Queue of write tasks submitted for execution.
*/
- private final BlockingQueue<WriteOperationTask> queue = new LinkedBlockingQueue<>();
+ private final BlockingQueue<OperationTask> queue = new LinkedBlockingQueue<>();
/**
* Write operation tasks prepared for writing (but not yet submitted to execution (actual writing).
*/
- private final ConcurrentMap<OperationSyncKey, WriteOperationTask> preparedWriteTasks = new ConcurrentHashMap<>();
+ private final ConcurrentMap<OperationSyncKey, OperationTask> preparedTasks = new ConcurrentHashMap<>();
/** */
BinaryMetadataAsyncWriter() {
- super(ctx.igniteInstanceName(), "binary-metadata-writer", BinaryMetadataFileStore.this.log, ctx.workersRegistry());
+ super(ctx.igniteInstanceName(), "binary-metadata-writer",
+ BinaryMetadataFileStore.this.log, ctx.workersRegistry());
}
/**
* @param typeId Type ID.
* @param typeVer Type version.
*/
- synchronized void startWritingAsync(int typeId, int typeVer) {
+ synchronized void startTaskAsync(int typeId, int typeVer) {
if (isCancelled())
return;
- WriteOperationTask task = preparedWriteTasks.get(new OperationSyncKey(typeId, typeVer));
+ OperationTask task = preparedTasks.get(new OperationSyncKey(typeId, typeVer));
if (task != null) {
if (log.isDebugEnabled())
@@ -371,7 +420,7 @@ class BinaryMetadataFileStore {
IgniteCheckedException err = new IgniteCheckedException("Operation has been cancelled (node is stopping).");
- for (Map.Entry<OperationSyncKey, WriteOperationTask> e : preparedWriteTasks.entrySet()) {
+ for (Map.Entry<OperationSyncKey, OperationTask> e : preparedTasks.entrySet()) {
if (log.isDebugEnabled())
log.debug(
"Cancelling future for write operation for" +
@@ -382,7 +431,7 @@ class BinaryMetadataFileStore {
e.getValue().future.onDone(err);
}
- preparedWriteTasks.clear();
+ preparedTasks.clear();
}
/** {@inheritDoc} */
@@ -403,7 +452,7 @@ class BinaryMetadataFileStore {
/** */
private void body0() throws InterruptedException {
- WriteOperationTask task;
+ OperationTask task;
blockingSectionBegin();
@@ -413,27 +462,52 @@ class BinaryMetadataFileStore {
if (log.isDebugEnabled())
log.debug(
"Starting write operation for" +
- " [typeId=" + task.meta.typeId() +
- ", typeVer=" + task.typeVer + ']'
+ " [typeId=" + task.typeId() +
+ ", typeVer=" + task.typeVersion() + ']'
);
- writeMetadata(task.meta);
+ task.execute(BinaryMetadataFileStore.this);
}
finally {
blockingSectionEnd();
}
- finishWriteFuture(task.meta.typeId(), task.typeVer);
+ finishWriteFuture(task.typeId(), task.typeVersion(), task);
+ }
+
+ /**
+ * @param typeId Binary metadata type id.
+ */
+ synchronized void cancelTasksForType(int typeId) {
+ final IgniteCheckedException err = new IgniteCheckedException("Operation has been cancelled by type remove.");
+
+ preparedTasks.entrySet().removeIf(entry -> {
+ if (entry.getKey().typeId == typeId) {
+ entry.getValue().future().onDone(err);
+
+ return true;
+ }
+ return false;
+ });
}
/**
* @param typeId Binary metadata type id.
* @param typeVer Type version.
+ * @param task Task to remove.
*/
- void finishWriteFuture(int typeId, int typeVer) {
- WriteOperationTask task = preparedWriteTasks.remove(new OperationSyncKey(typeId, typeVer));
+ void finishWriteFuture(int typeId, int typeVer, OperationTask task) {
+ boolean removed;
- if (task != null) {
+ if (task != null)
+ removed = preparedTasks.remove(new OperationSyncKey(typeId, typeVer), task);
+ else {
+ task = preparedTasks.remove(new OperationSyncKey(typeId, typeVer));
+
+ removed = task != null;
+ }
+
+ if (removed) {
if (log.isDebugEnabled())
log.debug(
"Future for write operation for" +
@@ -471,7 +545,23 @@ class BinaryMetadataFileStore {
", typeVersion=" + typeVer + ']'
);
- preparedWriteTasks.putIfAbsent(new OperationSyncKey(meta.typeId(), typeVer), new WriteOperationTask(meta, typeVer));
+ preparedTasks.putIfAbsent(new OperationSyncKey(meta.typeId(), typeVer), new WriteOperationTask(meta, typeVer));
+ }
+
+ /**
+ */
+ synchronized void prepareRemoveFuture(int typeId) {
+ if (isCancelled())
+ return;
+
+ if (log.isDebugEnabled())
+ log.debug(
+ "Prepare task for async remove for" +
+ "[typeId=" + typeId + ']'
+ );
+
+ preparedTasks.putIfAbsent(new OperationSyncKey(typeId, BinaryMetadataTransport.REMOVED_VERSION),
+ new RemoveOperationTask(typeId));
}
/**
@@ -488,7 +578,7 @@ class BinaryMetadataFileStore {
return;
}
- WriteOperationTask task = preparedWriteTasks.get(new OperationSyncKey(typeId, typeVer));
+ OperationTask task = preparedTasks.get(new OperationSyncKey(typeId, typeVer));
if (task != null) {
if (log.isDebugEnabled())
@@ -524,21 +614,89 @@ class BinaryMetadataFileStore {
/**
*
*/
- private static final class WriteOperationTask {
+ private abstract static class OperationTask {
/** */
- private final BinaryMetadata meta;
+ private final GridFutureAdapter<Void> future = new GridFutureAdapter<>();
/** */
- private final int typeVer;
+ abstract void execute(BinaryMetadataFileStore store);
/** */
- private final GridFutureAdapter future = new GridFutureAdapter();
+ abstract int typeId();
/** */
+ abstract int typeVersion();
+
+ /**
+ * @return Task future.
+ */
+ GridFutureAdapter<Void> future() {
+ return future;
+ }
+ }
+
+ /**
+ *
+ */
+ private static final class WriteOperationTask extends OperationTask {
+ /** */
+ private final BinaryMetadata meta;
+
+ /** */
+ private final int typeVer;
+
+ /**
+ * @param meta Metadata for binary type.
+ * @param ver Version of type.
+ */
private WriteOperationTask(BinaryMetadata meta, int ver) {
this.meta = meta;
typeVer = ver;
}
+
+ /** {@inheritDoc} */
+ @Override void execute(BinaryMetadataFileStore store) {
+ store.writeMetadata(meta);
+ }
+
+ /** {@inheritDoc} */
+ @Override int typeId() {
+ return meta.typeId();
+ }
+
+ /** {@inheritDoc} */
+ @Override int typeVersion() {
+ return typeVer;
+ }
+ }
+
+ /**
+ *
+ */
+ private static final class RemoveOperationTask extends OperationTask {
+ /** */
+ private final int typeId;
+
+ /**
+ */
+ private RemoveOperationTask(int typeId) {
+ this.typeId = typeId;
+ }
+
+ /** {@inheritDoc} */
+ @Override void execute(BinaryMetadataFileStore store) {
+ store.removeMeta(typeId);
+ }
+
+ /** {@inheritDoc} */
+ @Override int typeId() {
+ return typeId;
+ }
+
+ /** {@inheritDoc} */
+ @Override int typeVersion() {
+ return BinaryMetadataTransport.REMOVED_VERSION;
+ }
}
/**
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/binary/BinaryMetadataHolder.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/binary/BinaryMetadataHolder.java
index 4e783ca..923434d 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/binary/BinaryMetadataHolder.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/binary/BinaryMetadataHolder.java
@@ -36,17 +36,38 @@ final class BinaryMetadataHolder implements Serializable {
/** */
private final int acceptedVer;
+ /** */
+ private final transient boolean removing;
+
/**
* @param metadata Metadata.
* @param pendingVer Version of this metadata - how many updates were issued for this type.
* @param acceptedVer Pending updates count.
*/
BinaryMetadataHolder(BinaryMetadata metadata, int pendingVer, int acceptedVer) {
+ this(metadata, pendingVer, acceptedVer, false);
+ }
+
+ /**
+ * @param metadata Metadata.
+ * @param pendingVer Pending updates count.
+ * @param acceptedVer Version of this metadata - how many updates were issued for this type.
+ * @param removing Flag means the metadata is removing now.
+ */
+ private BinaryMetadataHolder(BinaryMetadata metadata, int pendingVer, int acceptedVer, boolean removing) {
assert metadata != null;
this.metadata = metadata;
this.pendingVer = pendingVer;
this.acceptedVer = acceptedVer;
+ this.removing = removing;
+ }
+
+ /**
+ * @return Holder metadata with remove state where remove pending message has been handled.
+ */
+ BinaryMetadataHolder createRemoving() {
+ return new BinaryMetadataHolder(metadata, pendingVer, acceptedVer, true);
}
/**
@@ -70,10 +91,19 @@ final class BinaryMetadataHolder implements Serializable {
return acceptedVer;
}
+ /**
+ * @return {@code true} is the metadata is removing now.
+ */
+ boolean removing() {
+ return removing;
+ }
+
/** {@inheritDoc} */
@Override public String toString() {
return "[typeId=" + metadata.typeId() +
", pendingVer=" + pendingVer +
- ", acceptedVer=" + acceptedVer + "]";
+ ", acceptedVer=" + acceptedVer +
+ ", removing=" + removing +
+ "]";
}
}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/binary/BinaryMetadataTransport.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/binary/BinaryMetadataTransport.java
index 7b89632..aa52237 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/binary/BinaryMetadataTransport.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/binary/BinaryMetadataTransport.java
@@ -29,6 +29,7 @@ import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteException;
import org.apache.ignite.IgniteLogger;
import org.apache.ignite.binary.BinaryObjectException;
import org.apache.ignite.cluster.ClusterNode;
@@ -37,6 +38,7 @@ import org.apache.ignite.events.Event;
import org.apache.ignite.internal.GridKernalContext;
import org.apache.ignite.internal.GridTopic;
import org.apache.ignite.internal.IgniteInternalFuture;
+import org.apache.ignite.internal.binary.BinaryContext;
import org.apache.ignite.internal.binary.BinaryMetadata;
import org.apache.ignite.internal.cluster.ClusterTopologyCheckedException;
import org.apache.ignite.internal.managers.communication.GridIoManager;
@@ -46,6 +48,7 @@ import org.apache.ignite.internal.managers.discovery.GridDiscoveryManager;
import org.apache.ignite.internal.managers.eventstorage.GridLocalEventListener;
import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
import org.apache.ignite.internal.util.future.GridFutureAdapter;
+import org.apache.ignite.internal.util.typedef.internal.CU;
import org.apache.ignite.internal.util.typedef.internal.S;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.lang.IgniteInClosure;
@@ -65,6 +68,9 @@ import static org.apache.ignite.internal.managers.communication.GridIoPolicy.SYS
* around protocols.
*/
final class BinaryMetadataTransport {
+ /** Special metadata version for remove. */
+ public static final int REMOVED_VERSION = -2;
+
/** */
private final GridDiscoveryManager discoMgr;
@@ -90,7 +96,7 @@ final class BinaryMetadataTransport {
private final ConcurrentMap<SyncKey, MetadataUpdateResultFuture> syncMap = new ConcurrentHashMap<>();
/** It store pending update future for typeId. It allow to do only one update in one moment. */
- private final ConcurrentMap<Integer, MetadataUpdateResultFuture> pendingTypeIdMap = new ConcurrentHashMap<>();
+ private final ConcurrentMap<Integer, GridFutureAdapter<?>> pendingTypeIdMap = new ConcurrentHashMap<>();
/** */
private final ConcurrentMap<Integer, ClientMetadataRequestFuture> clientReqSyncMap = new ConcurrentHashMap<>();
@@ -99,10 +105,16 @@ final class BinaryMetadataTransport {
private final ConcurrentMap<SyncKey, GridFutureAdapter<?>> schemaWaitFuts = new ConcurrentHashMap<>();
/** */
- private volatile boolean stopping;
+ private final List<BinaryMetadataUpdatedListener> binaryUpdatedLsnrs = new CopyOnWriteArrayList<>();
/** */
- private final List<BinaryMetadataUpdatedListener> binaryUpdatedLsnrs = new CopyOnWriteArrayList<>();
+ private final BinaryContext binCtx;
+
+ /** */
+ private final boolean isPersistenceEnabled;
+
+ /** */
+ private volatile boolean stopping;
/**
* @param metaLocCache Metadata locale cache.
@@ -113,24 +125,25 @@ final class BinaryMetadataTransport {
BinaryMetadataTransport(
ConcurrentMap<Integer, BinaryMetadataHolder> metaLocCache,
BinaryMetadataFileStore metadataFileStore,
+ BinaryContext binCtx,
final GridKernalContext ctx,
IgniteLogger log
) {
this.metaLocCache = metaLocCache;
-
this.metadataFileStore = metadataFileStore;
-
this.ctx = ctx;
-
+ this.binCtx = binCtx;
this.log = log;
discoMgr = ctx.discovery();
clientNode = ctx.clientNode();
+ isPersistenceEnabled = CU.isPersistenceEnabled(ctx.config()) && !clientNode;
discoMgr.setCustomEventListener(MetadataUpdateProposedMessage.class, new MetadataUpdateProposedListener());
-
discoMgr.setCustomEventListener(MetadataUpdateAcceptedMessage.class, new MetadataUpdateAcceptedListener());
+ discoMgr.setCustomEventListener(MetadataRemoveProposedMessage.class, new MetadataRemoveProposedListener());
+ discoMgr.setCustomEventListener(MetadataRemoveAcceptedMessage.class, new MetadataRemoveAcceptedListener());
GridIoManager ioMgr = ctx.io();
@@ -153,6 +166,19 @@ final class BinaryMetadataTransport {
}
/**
+ * Method checks if arrived metadata is obsolete comparing to the one from local cache.
+ *
+ * @param locP pendingVersion of metadata from local cache.
+ * @param locA acceptedVersion of metadata from local cache.
+ * @param remP pendingVersion of metadata from arrived message (client response/proposed/accepted).
+ * @param remA acceptedVersion of metadata from arrived message (client response/proposed/accepted).
+ * @return {@code true} is
+ */
+ private static boolean obsoleteUpdate(int locP, int locA, int remP, int remA) {
+ return (remP < locP) || (remP == locP && remA < locA);
+ }
+
+ /**
* Adds BinaryMetadata updates {@link BinaryMetadataUpdatedListener listener} to transport.
*
* @param lsnr Listener.
@@ -175,6 +201,9 @@ final class BinaryMetadataTransport {
do {
BinaryMetadataHolder metaHolder = metaLocCache.get(typeId);
+ if (metaHolder != null && metaHolder.removing())
+ throw new IgniteException("The metadata is removing for type [typeId=" + typeId + ']');
+
BinaryMetadata oldMeta = Optional.ofNullable(metaHolder)
.map(BinaryMetadataHolder::metadata)
.orElse(null);
@@ -242,11 +271,11 @@ final class BinaryMetadataTransport {
* Put new update future and it are waiting pending future if it exists.
*
* @param typeId Type id.
- * @param metaUpdateFut New metadata update future.
+ * @param newMetaFut New metadata update / remove future.
* @return {@code true} If given future put successfully.
*/
- private boolean putAndWaitPendingUpdate(int typeId, MetadataUpdateResultFuture metaUpdateFut) {
- MetadataUpdateResultFuture oldFut = pendingTypeIdMap.putIfAbsent(typeId, metaUpdateFut);
+ private boolean putAndWaitPendingUpdate(int typeId, GridFutureAdapter<?> newMetaFut) {
+ GridFutureAdapter<?> oldFut = pendingTypeIdMap.putIfAbsent(typeId, newMetaFut);
if (oldFut != null) {
try {
@@ -288,6 +317,27 @@ final class BinaryMetadataTransport {
}
/**
+ * Allows thread to wait for a metadata of given typeId and version to be removed.
+ *
+ * @param typeId ID of binary type.
+ * @return future to wait for update result on.
+ */
+ GridFutureAdapter<MetadataUpdateResult> awaitMetadataRemove(int typeId) {
+ SyncKey key = new SyncKey(typeId, REMOVED_VERSION);
+ MetadataUpdateResultFuture resFut = new MetadataUpdateResultFuture(key);
+
+ MetadataUpdateResultFuture oldFut = syncMap.putIfAbsent(key, resFut);
+
+ if (oldFut != null)
+ resFut = oldFut;
+
+ if (!metaLocCache.containsKey(typeId))
+ resFut.onDone(MetadataUpdateResult.createSuccessfulResult(-1));
+
+ return resFut;
+ }
+
+ /**
* Await specific schema update.
*
* @param typeId Type id.
@@ -351,14 +401,137 @@ final class BinaryMetadataTransport {
fut.onDone(res);
}
- /** */
- private final class MetadataUpdateProposedListener implements CustomEventListener<MetadataUpdateProposedMessage> {
+ /**
+ * Remove metadata from cluster for specified type.
+ *
+ * @param typeId Type ID to remove metadata.
+ */
+ public GridFutureAdapter<MetadataUpdateResult> requestMetadataRemove(int typeId) {
+ MetadataUpdateResultFuture resFut;
+
+ do {
+ resFut = new MetadataUpdateResultFuture(typeId);
+ }
+ while (!putAndWaitPendingUpdate(typeId, resFut));
+
+ try {
+ synchronized (this) {
+ unlabeledFutures.add(resFut);
+
+ if (!stopping)
+ discoMgr.sendCustomEvent(new MetadataRemoveProposedMessage(typeId, ctx.localNodeId()));
+ else
+ resFut.onDone(MetadataUpdateResult.createUpdateDisabledResult());
+ }
+ }
+ catch (Exception e) {
+ resFut.onDone(MetadataUpdateResult.createUpdateDisabledResult(), e);
+ }
+
+ if (ctx.clientDisconnected())
+ onDisconnected();
+
+ return resFut;
+ }
+
+ /**
+ * @param typeId Type ID.
+ * @param pendingVer Pending version.
+ * @param fut Future.
+ */
+ private void initSyncFor(int typeId, int pendingVer, final MetadataUpdateResultFuture fut) {
+ if (stopping) {
+ fut.onDone(MetadataUpdateResult.createUpdateDisabledResult());
+
+ return;
+ }
+
+ SyncKey key = new SyncKey(typeId, pendingVer);
+
+ MetadataUpdateResultFuture oldFut = syncMap.putIfAbsent(key, fut);
+
+ if (oldFut != null) {
+ oldFut.listen(new IgniteInClosure<IgniteInternalFuture<MetadataUpdateResult>>() {
+ @Override public void apply(IgniteInternalFuture<MetadataUpdateResult> doneFut) {
+ fut.onDone(doneFut.result(), doneFut.error());
+ }
+ });
+ }
+
+ fut.key(key);
+ }
+
+ /**
+ * Key for mapping arriving {@link MetadataUpdateAcceptedMessage} messages to {@link MetadataUpdateResultFuture}s
+ * other threads may be waiting on.
+ */
+ private static final class SyncKey {
+ /**
+ *
+ */
+ private final int typeId;
+
+ /**
+ *
+ */
+ private final int ver;
+
+ /**
+ * @param typeId Type id.
+ * @param ver Version.
+ */
+ private SyncKey(int typeId, int ver) {
+ this.typeId = typeId;
+ this.ver = ver;
+ }
+ /**
+ * @return Type ID.
+ */
+ int typeId() {
+ return typeId;
+ }
+
+ /**
+ * @return Version.
+ */
+ int version() {
+ return ver;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int hashCode() {
+ return typeId + ver;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean equals(Object o) {
+ if (o == this)
+ return true;
+
+ if (!(o instanceof SyncKey))
+ return false;
+
+ SyncKey that = (SyncKey)o;
+
+ return (typeId == that.typeId) && (ver == that.ver);
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return S.toString(SyncKey.class, this);
+ }
+ }
+
+ /**
+ *
+ */
+ private final class MetadataUpdateProposedListener implements CustomEventListener<MetadataUpdateProposedMessage> {
/** {@inheritDoc} */
@Override public void onCustomEvent(AffinityTopologyVersion topVer, ClusterNode snd,
MetadataUpdateProposedMessage msg) {
if (log.isDebugEnabled())
- log.debug("Received MetadataUpdateProposedListener [typeId=" + msg.typeId() +
+ log.debug("Received MetadataUpdateProposed message [typeId=" + msg.typeId() +
", typeName=" + msg.metadata().typeName() +
", pendingVer=" + msg.pendingVersion() +
", acceptedVer=" + msg.acceptedVersion() +
@@ -374,38 +547,48 @@ final class BinaryMetadataTransport {
if (msg.pendingVersion() == 0) {
//coordinator receives update request
if (holder != null) {
- pendingVer = holder.pendingVersion() + 1;
- acceptedVer = holder.acceptedVersion();
+ if (holder.removing()) {
+ msg.markRejected(new BinaryObjectException("The type is removing now [typeId=" + typeId + ']'));
+
+ pendingVer = REMOVED_VERSION;
+ acceptedVer = REMOVED_VERSION;
+ }
+ else {
+ pendingVer = holder.pendingVersion() + 1;
+ acceptedVer = holder.acceptedVersion();
+ }
}
else {
pendingVer = 1;
acceptedVer = 0;
}
- msg.pendingVersion(pendingVer);
- msg.acceptedVersion(acceptedVer);
+ if (!msg.rejected()) {
+ msg.pendingVersion(pendingVer);
+ msg.acceptedVersion(acceptedVer);
- BinaryMetadata locMeta = holder != null ? holder.metadata() : null;
+ BinaryMetadata locMeta = holder != null ? holder.metadata() : null;
- try {
- Set<Integer> changedSchemas = new LinkedHashSet<>();
+ try {
+ Set<Integer> changedSchemas = new LinkedHashSet<>();
- BinaryMetadata mergedMeta = mergeMetadata(locMeta, msg.metadata(), changedSchemas);
+ BinaryMetadata mergedMeta = mergeMetadata(locMeta, msg.metadata(), changedSchemas);
- if (log.isDebugEnabled())
- log.debug("Versions are stamped on coordinator" +
- " [typeId=" + typeId +
- ", changedSchemas=" + changedSchemas +
- ", pendingVer=" + pendingVer +
- ", acceptedVer=" + acceptedVer + "]"
- );
-
- msg.metadata(mergedMeta);
- }
- catch (BinaryObjectException err) {
- log.warning("Exception with merging metadata for typeId: " + typeId, err);
+ if (log.isDebugEnabled())
+ log.debug("Versions are stamped on coordinator" +
+ " [typeId=" + typeId +
+ ", changedSchemas=" + changedSchemas +
+ ", pendingVer=" + pendingVer +
+ ", acceptedVer=" + acceptedVer + "]"
+ );
+
+ msg.metadata(mergedMeta);
+ }
+ catch (BinaryObjectException err) {
+ log.warning("Exception with merging metadata for typeId: " + typeId, err);
- msg.markRejected(err);
+ msg.markRejected(err);
+ }
}
}
else {
@@ -466,7 +649,7 @@ final class BinaryMetadataTransport {
}
else {
if (!msg.rejected()) {
- BinaryMetadata locMeta = holder != null ? holder.metadata() : null;
+ BinaryMetadata locMeta = holder != null && !holder.removing() ? holder.metadata() : null;
Set<Integer> changedSchemas = new LinkedHashSet<>();
@@ -512,37 +695,9 @@ final class BinaryMetadataTransport {
}
/**
- * @param typeId Type ID.
- * @param pendingVer Pending version.
- * @param fut Future.
- */
- private void initSyncFor(int typeId, int pendingVer, final MetadataUpdateResultFuture fut) {
- if (stopping) {
- fut.onDone(MetadataUpdateResult.createUpdateDisabledResult());
-
- return;
- }
-
- SyncKey key = new SyncKey(typeId, pendingVer);
-
- MetadataUpdateResultFuture oldFut = syncMap.putIfAbsent(key, fut);
-
- if (oldFut != null) {
- oldFut.listen(new IgniteInClosure<IgniteInternalFuture<MetadataUpdateResult>>() {
- @Override public void apply(IgniteInternalFuture<MetadataUpdateResult> doneFut) {
- fut.onDone(doneFut.result(), doneFut.error());
- }
- });
- }
-
- fut.key(key);
- }
-
- /**
*
*/
private final class MetadataUpdateAcceptedListener implements CustomEventListener<MetadataUpdateAcceptedMessage> {
-
/** {@inheritDoc} */
@Override public void onCustomEvent(AffinityTopologyVersion topVer, ClusterNode snd,
MetadataUpdateAcceptedMessage msg) {
@@ -592,7 +747,8 @@ final class BinaryMetadataTransport {
metadataFileStore.writeMetadataAsync(typeId, newAcceptedVer);
- metaLocCache.put(typeId, new BinaryMetadataHolder(holder.metadata(), holder.pendingVersion(), newAcceptedVer));
+ metaLocCache.put(typeId,
+ new BinaryMetadataHolder(holder.metadata(), holder.pendingVersion(), newAcceptedVer));
}
for (BinaryMetadataUpdatedListener lsnr : binaryUpdatedLsnrs)
@@ -632,6 +788,9 @@ final class BinaryMetadataTransport {
*/
public final class MetadataUpdateResultFuture extends GridFutureAdapter<MetadataUpdateResult> {
/** */
+ private SyncKey key;
+
+ /** */
MetadataUpdateResultFuture(int typeId) {
this.key = new SyncKey(typeId, 0);
}
@@ -643,9 +802,6 @@ final class BinaryMetadataTransport {
this.key = key;
}
- /** */
- private SyncKey key;
-
/** {@inheritDoc} */
@Override public boolean onDone(@Nullable MetadataUpdateResult res, @Nullable Throwable err) {
assert res != null;
@@ -679,64 +835,6 @@ final class BinaryMetadataTransport {
}
/**
- * Key for mapping arriving {@link MetadataUpdateAcceptedMessage} messages to {@link MetadataUpdateResultFuture}s
- * other threads may be waiting on.
- */
- private static final class SyncKey {
- /** */
- private final int typeId;
-
- /** */
- private final int ver;
-
- /**
- * @param typeId Type id.
- * @param ver Version.
- */
- private SyncKey(int typeId, int ver) {
- this.typeId = typeId;
- this.ver = ver;
- }
-
- /**
- * @return Type ID.
- */
- int typeId() {
- return typeId;
- }
-
- /**
- * @return Version.
- */
- int version() {
- return ver;
- }
-
- /** {@inheritDoc} */
- @Override public int hashCode() {
- return typeId + ver;
- }
-
- /** {@inheritDoc} */
- @Override public boolean equals(Object o) {
- if (o == this)
- return true;
-
- if (!(o instanceof SyncKey))
- return false;
-
- SyncKey that = (SyncKey)o;
-
- return (typeId == that.typeId) && (ver == that.ver);
- }
-
- /** {@inheritDoc} */
- @Override public String toString() {
- return S.toString(SyncKey.class, this);
- }
- }
-
- /**
* Listener is registered on each server node in cluster waiting for metadata requests from clients.
*/
private final class MetadataRequestListener implements GridMessageListener {
@@ -844,15 +942,97 @@ final class BinaryMetadataTransport {
}
/**
- * Method checks if arrived metadata is obsolete comparing to the one from local cache.
*
- * @param locP pendingVersion of metadata from local cache.
- * @param locA acceptedVersion of metadata from local cache.
- * @param remP pendingVersion of metadata from arrived message (client response/proposed/accepted).
- * @param remA acceptedVersion of metadata from arrived message (client response/proposed/accepted).
- * @return {@code true} is
*/
- private static boolean obsoleteUpdate(int locP, int locA, int remP, int remA) {
- return (remP < locP) || (remP == locP && remA < locA);
+ private final class MetadataRemoveProposedListener implements CustomEventListener<MetadataRemoveProposedMessage> {
+ /** {@inheritDoc} */
+ @Override public void onCustomEvent(AffinityTopologyVersion topVer, ClusterNode snd,
+ MetadataRemoveProposedMessage msg) {
+ if (log.isDebugEnabled())
+ log.debug("Received MetadataRemoveProposed message: " + msg);
+
+ int typeId = msg.typeId();
+
+ BinaryMetadataHolder metaHld = metaLocCache.get(typeId);
+
+ assert metaHld != null : "No metadata found for typeId: " + typeId;
+
+ if (msg.isOnCoordinator()) {
+ if (metaHld == null)
+ msg.markRejected(new BinaryObjectException("Type not found [typeId=" + typeId + ']'));
+
+ if (metaHld.pendingVersion() != metaHld.acceptedVersion()) {
+ msg.markRejected(new BinaryObjectException(
+ "Remove type failed. " +
+ "Type is being updated now [typeId=" + typeId
+ + ", pendingVersion=" + metaHld.pendingVersion()
+ + ", acceptedVersion=" + metaHld.acceptedVersion()
+ + ']'));
+ }
+
+ if (metaHld.removing()) {
+ msg.markRejected(new BinaryObjectException(
+ "Remove type failed. " +
+ "Type is being removed now [typeId=" + typeId
+ + ']'));
+ }
+
+ msg.setOnCoordinator(false);
+ }
+
+ MetadataUpdateResultFuture fut = null;
+
+ if (msg.origNodeId().equals(ctx.localNodeId()))
+ fut = unlabeledFutures.poll();
+
+ if (msg.rejected()) {
+ if (fut != null)
+ fut.onDone(MetadataUpdateResult.createFailureResult(msg.rejectionError()));
+ }
+ else {
+ if (fut != null)
+ initSyncFor(typeId, REMOVED_VERSION, fut);
+
+ metaLocCache.put(typeId, metaHld.createRemoving());
+
+ if (isPersistenceEnabled)
+ metadataFileStore.prepareMetadataRemove(typeId);
+ }
+ }
+ }
+
+ /**
+ *
+ */
+ private final class MetadataRemoveAcceptedListener implements CustomEventListener<MetadataRemoveAcceptedMessage> {
+ /** {@inheritDoc} */
+ @Override public void onCustomEvent(AffinityTopologyVersion topVer, ClusterNode snd,
+ MetadataRemoveAcceptedMessage msg) {
+ if (log.isDebugEnabled())
+ log.debug("Received MetadataRemoveAccepted message: " + msg);
+
+ if (msg.duplicated())
+ return;
+
+ final int typeId = msg.typeId();
+
+ if (!metaLocCache.containsKey(typeId)) {
+ msg.duplicated(true);
+
+ return;
+ }
+
+ if (isPersistenceEnabled)
+ metadataFileStore.removeMetadataAsync(typeId);
+
+ GridFutureAdapter<MetadataUpdateResult> fut = syncMap.get(new SyncKey(typeId, REMOVED_VERSION));
+
+ metaLocCache.remove(typeId);
+
+ binCtx.removeType(typeId);
+
+ if (fut != null)
+ fut.onDone(MetadataUpdateResult.createSuccessfulResult(REMOVED_VERSION));
+ }
}
}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/binary/CacheObjectBinaryProcessorImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/binary/CacheObjectBinaryProcessorImpl.java
index 61e05e3..cdc9e41 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/binary/CacheObjectBinaryProcessorImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/binary/CacheObjectBinaryProcessorImpl.java
@@ -52,6 +52,7 @@ import org.apache.ignite.configuration.CacheConfiguration;
import org.apache.ignite.configuration.DataStorageConfiguration;
import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.internal.GridKernalContext;
+import org.apache.ignite.internal.IgniteFeatures;
import org.apache.ignite.internal.IgniteFutureTimeoutCheckedException;
import org.apache.ignite.internal.IgniteInternalFuture;
import org.apache.ignite.internal.IgniteNodeAttributes;
@@ -246,8 +247,6 @@ public class CacheObjectBinaryProcessorImpl extends GridProcessorAdapter impleme
metadataFileStore.start();
}
- transport = new BinaryMetadataTransport(metadataLocCache, metadataFileStore, ctx, log);
-
BinaryMetadataHandler metaHnd = new BinaryMetadataHandler() {
@Override public void addMeta(
int typeId,
@@ -306,6 +305,8 @@ public class CacheObjectBinaryProcessorImpl extends GridProcessorAdapter impleme
new TestBinaryContext(metaHnd, ctx.config(), ctx.log(BinaryContext.class)) :
new BinaryContext(metaHnd, ctx.config(), ctx.log(BinaryContext.class));
+ transport = new BinaryMetadataTransport(metadataLocCache, metadataFileStore, binaryCtx, ctx, log);
+
IgniteUtils.invoke(BinaryMarshaller.class, bMarsh0, "setBinaryContext", binaryCtx, ctx.config());
binaryMarsh = new GridBinaryMarshaller(binaryCtx);
@@ -740,6 +741,19 @@ public class CacheObjectBinaryProcessorImpl extends GridProcessorAdapter impleme
}
if (holder != null) {
+ if (holder.removing()) {
+ GridFutureAdapter<MetadataUpdateResult> fut = transport.awaitMetadataRemove(typeId);
+
+ try {
+ fut.get();
+ }
+ catch (IgniteCheckedException ignored) {
+ // No-op.
+ }
+
+ return null;
+ }
+
if (curThread instanceof IgniteDiscoveryThread || (curThread != null && curThread.isForbiddenToRequestBinaryMetadata()))
return holder.metadata();
@@ -924,6 +938,28 @@ public class CacheObjectBinaryProcessorImpl extends GridProcessorAdapter impleme
});
}
+ /**
+ * @return Cluster binary metadata.
+ * @throws BinaryObjectException on error.
+ */
+ public Collection<BinaryMetadata> binaryMetadata() throws BinaryObjectException {
+ return F.viewReadOnly(metadataLocCache.values(), new IgniteClosure<BinaryMetadataHolder, BinaryMetadata>() {
+ @Override public BinaryMetadata apply(BinaryMetadataHolder metaHolder) {
+ return metaHolder.metadata();
+ }
+ });
+ }
+
+ /**
+ * @return Binary metadata for specified type.
+ * @throws BinaryObjectException on error.
+ */
+ public BinaryMetadata binaryMetadata(int typeId) throws BinaryObjectException {
+ BinaryMetadataHolder hld = metadataLocCache.get(typeId);
+
+ return hld != null ? hld.metadata() : null;
+ }
+
/** {@inheritDoc} */
@Override public void saveMetadata(Collection<BinaryType> types, File dir) {
try {
@@ -1433,8 +1469,10 @@ public class CacheObjectBinaryProcessorImpl extends GridProcessorAdapter impleme
if (!dataBag.commonDataCollectedFor(BINARY_PROC.ordinal())) {
Map<Integer, BinaryMetadataHolder> res = U.newHashMap(metadataLocCache.size());
- for (Map.Entry<Integer,BinaryMetadataHolder> e : metadataLocCache.entrySet())
- res.put(e.getKey(), e.getValue());
+ for (Map.Entry<Integer,BinaryMetadataHolder> e : metadataLocCache.entrySet()) {
+ if (!e.getValue().removing())
+ res.put(e.getKey(), e.getValue());
+ }
dataBag.addGridCommonData(BINARY_PROC.ordinal(), (Serializable) res);
}
@@ -1536,6 +1574,42 @@ public class CacheObjectBinaryProcessorImpl extends GridProcessorAdapter impleme
this.binaryMetadataFileStoreDir = binaryMetadataFileStoreDir;
}
+ /** {@inheritDoc} */
+ @Override public void removeType(int typeId) {
+ BinaryMetadataHolder oldHld = metadataLocCache.get(typeId);
+
+ if (oldHld == null)
+ throw new IgniteException("Failed to remove metadata, type not found: " + typeId);
+
+ if (oldHld.removing())
+ throw new IgniteException("Failed to remove metadata, type is being removed: " + typeId);
+
+ if (!IgniteFeatures.allNodesSupports(ctx.discovery().allNodes(), IgniteFeatures.REMOVE_METADATA)) {
+ throw new IgniteException("Failed to remove metadata, " +
+ "all cluster nodes must support the remove type feature");
+ }
+
+ try {
+ GridFutureAdapter<MetadataUpdateResult> fut = transport.requestMetadataRemove(typeId);
+
+ MetadataUpdateResult res = fut.get();
+
+ if (res.rejected())
+ throw res.error();
+ }
+ catch (IgniteCheckedException e) {
+ IgniteCheckedException ex = e;
+
+ if (ctx.isStopping()) {
+ ex = new NodeStoppingException("Node is stopping.");
+
+ ex.addSuppressed(e);
+ }
+
+ throw new BinaryObjectException("Failed to remove metadata for type: " + typeId, ex);
+ }
+ }
+
/** */
@SuppressWarnings("PublicInnerClass")
public static class TestBinaryContext extends BinaryContext {
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/binary/MetadataRemoveAcceptedMessage.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/binary/MetadataRemoveAcceptedMessage.java
new file mode 100644
index 0000000..190e1c3
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/binary/MetadataRemoveAcceptedMessage.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.cache.binary;
+
+import org.apache.ignite.internal.managers.discovery.DiscoCache;
+import org.apache.ignite.internal.managers.discovery.DiscoveryCustomMessage;
+import org.apache.ignite.internal.managers.discovery.GridDiscoveryManager;
+import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.lang.IgniteUuid;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Acknowledge message for {@link MetadataRemoveProposedMessage}: see its javadoc for detailed description of protocol.
+ *
+ * As discovery messaging doesn't guarantee that message makes only one pass across the cluster
+ * <b>MetadataRemoveAcceptedMessage</b> enables to mark it as duplicated so other nodes won't process it but skip.
+ */
+public class MetadataRemoveAcceptedMessage implements DiscoveryCustomMessage {
+ /** */
+ private static final long serialVersionUID = 0L;
+
+ /** */
+ private final IgniteUuid id = IgniteUuid.randomUuid();
+
+ /** */
+ private final int typeId;
+
+ /** */
+ private boolean duplicated;
+
+ /**
+ * @param typeId Type id.
+ */
+ MetadataRemoveAcceptedMessage(int typeId) {
+ this.typeId = typeId;
+ }
+
+ /** {@inheritDoc} */
+ @Override public IgniteUuid id() {
+ return id;
+ }
+
+ /** {@inheritDoc} */
+ @Nullable @Override public DiscoveryCustomMessage ackMessage() {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean isMutable() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Nullable @Override public DiscoCache createDiscoCache(GridDiscoveryManager mgr,
+ AffinityTopologyVersion topVer, DiscoCache discoCache) {
+ throw new UnsupportedOperationException();
+ }
+
+ /** */
+ public int typeId() {
+ return typeId;
+ }
+
+ /** */
+ public boolean duplicated() {
+ return duplicated;
+ }
+
+ /**
+ * @param duplicated duplicated flag.
+ */
+ public void duplicated(boolean duplicated) {
+ this.duplicated = duplicated;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return S.toString(MetadataRemoveAcceptedMessage.class, this);
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/binary/MetadataRemoveProposedMessage.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/binary/MetadataRemoveProposedMessage.java
new file mode 100644
index 0000000..eded65c
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/binary/MetadataRemoveProposedMessage.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.cache.binary;
+
+import java.util.UUID;
+import org.apache.ignite.binary.BinaryObjectException;
+import org.apache.ignite.internal.binary.BinaryMetadata;
+import org.apache.ignite.internal.managers.discovery.DiscoCache;
+import org.apache.ignite.internal.managers.discovery.DiscoveryCustomMessage;
+import org.apache.ignite.internal.managers.discovery.GridDiscoveryManager;
+import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.lang.IgniteUuid;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * <b>MetadataRemoveProposedMessage</b> and {@link MetadataRemoveAcceptedMessage} messages make a basis for
+ * discovery-based protocol for manage {@link BinaryMetadata metadata} describing objects in binary format
+ * stored in Ignite caches.
+ */
+public final class MetadataRemoveProposedMessage implements DiscoveryCustomMessage {
+ /** */
+ private static final long serialVersionUID = 0L;
+
+ /** */
+ private final IgniteUuid id = IgniteUuid.randomUuid();
+
+ /** Node UUID which initiated metadata update. */
+ private final UUID origNodeId;
+
+ /** Metadata type id. */
+ private final int typeId;
+
+ /** Message acceptance status. */
+ private ProposalStatus status = ProposalStatus.SUCCESSFUL;
+
+ /** Message received on coordinator. */
+ private boolean onCoordinator = true;
+
+ /** */
+ private BinaryObjectException err;
+
+ /**
+ * @param typeId Binary type ID.
+ * @param origNodeId ID of node requested update.
+ */
+ public MetadataRemoveProposedMessage(int typeId, UUID origNodeId) {
+ assert origNodeId != null;
+
+ this.origNodeId = origNodeId;
+
+ this.typeId = typeId;
+ }
+
+ /** {@inheritDoc} */
+ @Override public IgniteUuid id() {
+ return id;
+ }
+
+ /** {@inheritDoc} */
+ @Nullable @Override public DiscoveryCustomMessage ackMessage() {
+ return (status == ProposalStatus.SUCCESSFUL) ? new MetadataRemoveAcceptedMessage(typeId) : null;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean isMutable() {
+ return true;
+ }
+
+
+ /** {@inheritDoc} */
+ @Nullable @Override public DiscoCache createDiscoCache(GridDiscoveryManager mgr,
+ AffinityTopologyVersion topVer, DiscoCache discoCache) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @param err Error caused this update to be rejected.
+ */
+ void markRejected(BinaryObjectException err) {
+ status = ProposalStatus.REJECTED;
+ this.err = err;
+ }
+
+ /** */
+ boolean rejected() {
+ return status == ProposalStatus.REJECTED;
+ }
+
+ /** */
+ BinaryObjectException rejectionError() {
+ return err;
+ }
+
+ /** */
+ UUID origNodeId() {
+ return origNodeId;
+ }
+
+ /** */
+ public int typeId() {
+ return typeId;
+ }
+
+ /** */
+ public boolean isOnCoordinator() {
+ return onCoordinator;
+ }
+
+ /** */
+ public void setOnCoordinator(boolean onCoordinator) {
+ this.onCoordinator = onCoordinator;
+ }
+
+ /** Message acceptance status. */
+ private enum ProposalStatus {
+ /** */
+ SUCCESSFUL,
+
+ /** */
+ REJECTED
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return S.toString(MetadataRemoveProposedMessage.class, this);
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cacheobject/IgniteCacheObjectProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cacheobject/IgniteCacheObjectProcessor.java
index 62e15df..7ccfee0 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cacheobject/IgniteCacheObjectProcessor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cacheobject/IgniteCacheObjectProcessor.java
@@ -338,4 +338,14 @@ public interface IgniteCacheObjectProcessor extends GridProcessor {
* @throws IgniteException If failed.
*/
public Object marshalToBinary(Object obj, boolean failIfUnregistered) throws IgniteException;
+
+ /**
+ * Remove registered binary type from grid.
+ *
+ * Attention: this is not safe feature, the grid must not contain binary objects
+ * with specified type, operations with specified type must not be processed on the cluster.
+ *
+ * @param typeId Type ID.
+ */
+ public void removeType(int typeId);
}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerProcessor.java
index 36fe752..d467878 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerProcessor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerProcessor.java
@@ -377,6 +377,76 @@ public class ClientListenerProcessor extends GridProcessorAdapter {
}
/**
+ *
+ */
+ public void closeAllSessions() {
+ Collection<? extends GridNioSession> sessions = srv.sessions();
+
+ for (GridNioSession ses : sessions) {
+ ClientListenerConnectionContext connCtx = ses.meta(CONN_CTX_META_KEY);
+
+ if (connCtx == null || ses.closeTime() != 0)
+ continue; // Skip non-initialized or closed session.
+
+ srv.close(ses);
+
+ if (log.isInfoEnabled()) {
+ log.info("Client session has been dropped: "
+ + clientConnectionDescription(ses, connCtx));
+ }
+ }
+ }
+
+ /**
+ * Compose connection description string.
+ * @param ses Client's NIO session.
+ * @param ctx Client's connection context.
+ * @return connection description.
+ */
+ @SuppressWarnings("StringConcatenationInsideStringBufferAppend")
+ private static String clientConnectionDescription(
+ GridNioSession ses,
+ ClientListenerConnectionContext ctx
+ )
+ {
+ AuthorizationContext authCtx = ctx.authorizationContext();
+
+ StringBuilder sb = new StringBuilder();
+
+ if (ctx instanceof JdbcConnectionContext)
+ sb.append("JdbcClient [");
+ else if (ctx instanceof OdbcConnectionContext)
+ sb.append("OdbcClient [");
+ else
+ sb.append("ThinClient [");
+
+ InetSocketAddress rmtAddr = ses.remoteAddress();
+ InetSocketAddress locAddr = ses.localAddress();
+
+ assert rmtAddr != null;
+ assert locAddr != null;
+
+ String rmtAddrStr = rmtAddr.getHostString() + ":" + rmtAddr.getPort();
+ String locAddrStr = locAddr.getHostString() + ":" + locAddr.getPort();
+
+ String login;
+
+ if (authCtx != null)
+ login = authCtx.userName();
+ else if (ctx.securityContext() != null)
+ login = "@" + ctx.securityContext().subject().login();
+ else
+ login = "<anonymous>";
+
+ sb.append("id=" + ctx.connectionId());
+ sb.append(", user=").append(login);
+ sb.append(", rmtAddr=" + rmtAddrStr);
+ sb.append(", locAddr=" + locAddrStr);
+
+ return sb.append(']').toString();
+ }
+
+ /**
* @return Server port.
*/
public int port() {
@@ -426,11 +496,13 @@ public class ClientListenerProcessor extends GridProcessorAdapter {
cliConnCfg.setTcpNoDelay(sqlConnCfg.isTcpNoDelay());
cliConnCfg.setThreadPoolSize(sqlConnCfg.getThreadPoolSize());
- U.warn(log, "Automatically converted deprecated " + SqlConnectorConfiguration.class.getSimpleName() +
+ U.warn(log, "Automatically converted deprecated "
+ + SqlConnectorConfiguration.class.getSimpleName() +
" to " + ClientConnectorConfiguration.class.getSimpleName() + ".");
if (odbcCfg != null) {
- U.warn(log, "Deprecated " + OdbcConfiguration.class.getSimpleName() + " will be ignored because " +
+ U.warn(log, "Deprecated " + OdbcConfiguration.class.getSimpleName() +
+ " will be ignored because " +
SqlConnectorConfiguration.class.getSimpleName() + " is set.");
}
}
@@ -534,19 +606,7 @@ public class ClientListenerProcessor extends GridProcessorAdapter {
/** {@inheritDoc} */
@Override public void dropAllConnections() {
- Collection<? extends GridNioSession> sessions = srv.sessions();
-
- for (GridNioSession ses : sessions) {
- ClientListenerConnectionContext connCtx = ses.meta(CONN_CTX_META_KEY);
-
- if (connCtx == null || ses.closeTime() != 0)
- continue; // Skip non-initialized or closed session.
-
- srv.close(ses);
-
- if (log.isInfoEnabled())
- log.info("Client session has been dropped: " + clientConnectionDescription(ses, connCtx));
- }
+ closeAllSessions();
}
/** {@inheritDoc} */
@@ -562,57 +622,25 @@ public class ClientListenerProcessor extends GridProcessorAdapter {
continue;
if (ses.closeTime() != 0) {
- if (log.isDebugEnabled())
- log.debug("Client session is already closed: " + clientConnectionDescription(ses, connCtx));
+ if (log.isDebugEnabled()) {
+ log.debug("Client session is already closed: " +
+ clientConnectionDescription(ses, connCtx));
+ }
return false;
}
srv.close(ses);
- if (log.isInfoEnabled())
- log.info("Client session has been dropped: " + clientConnectionDescription(ses, connCtx));
+ if (log.isInfoEnabled()) {
+ log.info("Client session has been dropped: " +
+ clientConnectionDescription(ses, connCtx));
+ }
return true;
}
return false;
}
-
- /**
- * Compose connection description string.
- * @param ses client NIO session.
- * @param ctx client connection context.
- * @return connection description
- */
- @SuppressWarnings("StringConcatenationInsideStringBufferAppend")
- private String clientConnectionDescription(GridNioSession ses, ClientListenerConnectionContext ctx) {
- AuthorizationContext authCtx = ctx.authorizationContext();
-
- StringBuilder sb = new StringBuilder();
-
- if (ctx instanceof JdbcConnectionContext)
- sb.append("JdbcClient [");
- else if (ctx instanceof OdbcConnectionContext)
- sb.append("OdbcClient [");
- else
- sb.append("ThinClient [");
-
- InetSocketAddress rmtAddr = ses.remoteAddress();
- InetSocketAddress locAddr = ses.localAddress();
-
- assert rmtAddr != null;
- assert locAddr != null;
-
- String rmtAddrStr = rmtAddr.getHostString() + ":" + rmtAddr.getPort();
- String locAddrStr = locAddr.getHostString() + ":" + locAddr.getPort();
-
- sb.append("id=" + ctx.connectionId());
- sb.append(", user=").append(authCtx == null ? "<anonymous>" : authCtx.userName());
- sb.append(", rmtAddr=" + rmtAddrStr);
- sb.append(", locAddr=" + locAddrStr);
-
- return sb.append(']').toString();
- }
}
}
diff --git a/modules/core/src/main/java/org/apache/ignite/plugin/security/SecurityPermission.java b/modules/core/src/main/java/org/apache/ignite/plugin/security/SecurityPermission.java
index 86206c2..e062bc3 100644
--- a/modules/core/src/main/java/org/apache/ignite/plugin/security/SecurityPermission.java
+++ b/modules/core/src/main/java/org/apache/ignite/plugin/security/SecurityPermission.java
@@ -73,7 +73,10 @@ public enum SecurityPermission {
CACHE_DESTROY,
/** Join as server node permission. */
- JOIN_AS_SERVER;
+ JOIN_AS_SERVER,
+
+ /** Administration operation with cluster metadata (REMOVE, UPDATE). */
+ ADMIN_METADATA_OPS;
/** Enumerated values. */
private static final SecurityPermission[] VALS = values();
diff --git a/modules/core/src/main/resources/META-INF/classnames.properties b/modules/core/src/main/resources/META-INF/classnames.properties
index fc82e59..1077ad9 100644
--- a/modules/core/src/main/resources/META-INF/classnames.properties
+++ b/modules/core/src/main/resources/META-INF/classnames.properties
@@ -361,6 +361,17 @@ org.apache.ignite.internal.commandline.cache.reset_lost_partitions.CacheResetLos
org.apache.ignite.internal.commandline.cache.reset_lost_partitions.CacheResetLostPartitionsTask$CacheResetLostPartitionsJob
org.apache.ignite.internal.commandline.cache.reset_lost_partitions.CacheResetLostPartitionsTaskArg
org.apache.ignite.internal.commandline.cache.reset_lost_partitions.CacheResetLostPartitionsTaskResult
+org.apache.ignite.internal.commandline.meta.subcommands.MetadataAbstractSubCommand.VoidDto
+org.apache.ignite.internal.commandline.meta.tasks.MetadataListResult
+org.apache.ignite.internal.commandline.meta.tasks.MetadataInfoTask
+org.apache.ignite.internal.commandline.meta.tasks.MetadataInfoTask.MetadataListJob
+org.apache.ignite.internal.commandline.meta.tasks.MetadataMarshalled
+org.apache.ignite.internal.commandline.meta.tasks.MetadataRemoveTask
+org.apache.ignite.internal.commandline.meta.tasks.MetadataRemoveTask.MetadataRemoveJob
+org.apache.ignite.internal.commandline.meta.tasks.MetadataRemoveTask.DropAllThinSessionsJob
+org.apache.ignite.internal.commandline.meta.tasks.MetadataTypeArgs
+org.apache.ignite.internal.commandline.meta.tasks.MetadataUpdateTask
+org.apache.ignite.internal.commandline.meta.tasks.MetadataUpdateTask.MetadataUpdateJob
org.apache.ignite.internal.compute.ComputeTaskCancelledCheckedException
org.apache.ignite.internal.compute.ComputeTaskTimeoutCheckedException
org.apache.ignite.internal.direct.DirectMessageReader$1
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/commandline/CommandHandlerParsingTest.java b/modules/core/src/test/java/org/apache/ignite/internal/commandline/CommandHandlerParsingTest.java
index ed72177..b8acdd0 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/commandline/CommandHandlerParsingTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/commandline/CommandHandlerParsingTest.java
@@ -630,6 +630,7 @@ public class CommandHandlerParsingTest {
cmd == CommandList.SET_STATE ||
cmd == CommandList.ENCRYPTION ||
cmd == CommandList.KILL ||
- cmd == CommandList.SNAPSHOT;
+ cmd == CommandList.SNAPSHOT ||
+ cmd == CommandList.METADATA;
}
}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/binary/BinaryMetadataRemoveTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/binary/BinaryMetadataRemoveTest.java
new file mode 100644
index 0000000..eb82266
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/binary/BinaryMetadataRemoveTest.java
@@ -0,0 +1,308 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.cache.binary;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.binary.BinaryObjectBuilder;
+import org.apache.ignite.binary.BinaryObjectException;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.IgniteInterruptedCheckedException;
+import org.apache.ignite.internal.managers.discovery.DiscoveryCustomMessage;
+import org.apache.ignite.internal.util.IgniteUtils;
+import org.apache.ignite.internal.util.typedef.G;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.spi.discovery.DiscoverySpiCustomMessage;
+import org.apache.ignite.spi.discovery.DiscoverySpiListener;
+import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
+import org.apache.ignite.testframework.GridTestUtils;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.jetbrains.annotations.Nullable;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class BinaryMetadataRemoveTest extends GridCommonAbstractTest {
+ /** Max retry cont. */
+ private static final int MAX_RETRY_CONT = 10;
+
+ /** */
+ private static final String CACHE_NAME = "cache";
+
+ /** */
+ private GridTestUtils.DiscoveryHook discoveryHook;
+
+ /** {@inheritDoc} */
+ @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
+ IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName);
+
+ TcpDiscoverySpi discoSpi;
+
+ final GridTestUtils.DiscoveryHook discoveryHook0 = discoveryHook;
+
+ if (discoveryHook0 != null) {
+ discoSpi = new TcpDiscoverySpi() {
+ @Override public void setListener(@Nullable DiscoverySpiListener lsnr) {
+ if (discoveryHook0 != null)
+ super.setListener(GridTestUtils.DiscoverySpiListenerWrapper.wrap(lsnr, discoveryHook0));
+ }
+ };
+ }
+ else
+ discoSpi = new TcpDiscoverySpi();
+
+ cfg.setDiscoverySpi(discoSpi);
+
+ cfg.setCacheConfiguration(new CacheConfiguration().setName(CACHE_NAME));
+
+ return cfg;
+ }
+
+ /**
+ *
+ */
+ protected void startCluster() throws Exception {
+ startGrid("srv0");
+ startGrid("srv1");
+ startGrid("srv2");
+ startClientGrid("cli0");
+ startClientGrid("cli1");
+ startClientGrid("cli2");
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void beforeTest() throws Exception {
+ super.beforeTest();
+
+ startCluster();
+
+ discoveryHook = null;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void afterTest() throws Exception {
+ stopAllGrids();
+
+ super.afterTest();
+ }
+
+ /**
+ * Tests remove not existent type and checks the exception.
+ */
+ @Test
+ public void testRemoveNotExistentType() {
+ for (Ignite testNode : G.allGrids()) {
+ GridTestUtils.assertThrows(log, () -> {
+ ((IgniteEx)testNode).context().cacheObjects().removeType(
+ ((IgniteEx)testNode).context().cacheObjects().typeId("NotExistentType"));
+
+ return null;
+ },
+ IgniteException.class, "Failed to remove metadata, type not found");
+ }
+ }
+
+ /**
+ * Tests remove type metadata at all nodes (coordinator, server, client).
+ */
+ @Test
+ public void testRemoveTypeOnNodes() throws Exception {
+ List<IgniteEx[]> testNodeSets = new ArrayList<>();
+
+ // Add all servers permutations to tests sets.
+ for (Ignite ign0 : G.allGrids()) {
+ for (Ignite ign1 : G.allGrids()) {
+ for (Ignite ign2 : G.allGrids()) {
+ IgniteEx ignx0 = (IgniteEx)ign0;
+ IgniteEx ignx1 = (IgniteEx)ign1;
+ IgniteEx ignx2 = (IgniteEx)ign2;
+
+ if (!ignx0.context().clientNode()
+ && !ignx1.context().clientNode()
+ && !ignx2.context().clientNode())
+ testNodeSets.add(new IgniteEx[] {ignx0, ignx1, ignx2});
+ }
+ }
+ }
+
+ testNodeSets.add(new IgniteEx[] {grid("srv0"), grid("cli0"), grid("cli0")});
+ testNodeSets.add(new IgniteEx[] {grid("cli0"), grid("cli0"), grid("cli0")});
+ testNodeSets.add(new IgniteEx[] {grid("cli0"), grid("cli1"), grid("cli2")});
+
+ for (IgniteEx[] testNodeSet : testNodeSets) {
+ IgniteEx ignCreateType = testNodeSet[0];
+ IgniteEx ignRemoveType = testNodeSet[1];
+ IgniteEx ignRecreateType = testNodeSet[2];
+
+ log.info("+++ Check [createOn=" + ignCreateType.name() +
+ ", removeOn=" + ignRemoveType.name() + ", recreateOn=" + ignRecreateType.name());
+
+ BinaryObjectBuilder builder0 = ignCreateType.binary().builder("Type0");
+
+ builder0.setField("f", 1);
+ builder0.build();
+
+ delayIfClient(ignCreateType, ignRemoveType, ignRecreateType);
+
+ removeType(ignRemoveType, "Type0");
+
+ delayIfClient(ignCreateType, ignRemoveType, ignRecreateType);
+
+ BinaryObjectBuilder builder1 = ignRecreateType.binary().builder("Type0");
+ builder1.setField("f", "string");
+ builder1.build();
+
+ delayIfClient(ignCreateType, ignRemoveType, ignRecreateType);
+
+ // Remove type at the end of test case.
+ removeType(grid("srv0"), "Type0");
+
+ delayIfClient(ignCreateType, ignRemoveType, ignRecreateType);
+ }
+ }
+
+ /**
+ * Tests reject metadata update on coordinator when remove type is processed.
+ */
+ @Test
+ public void testChangeMetaWhenTypeRemoving() throws Exception {
+ final CyclicBarrier barrier0 = new CyclicBarrier(2);
+ final CyclicBarrier barrier1 = new CyclicBarrier(2);
+
+ AtomicBoolean hookMsgs = new AtomicBoolean(true);
+
+ discoveryHook = new GridTestUtils.DiscoveryHook() {
+ @Override public void beforeDiscovery(DiscoverySpiCustomMessage msg) {
+ if (!hookMsgs.get())
+ return;
+
+ DiscoveryCustomMessage customMsg = msg == null ? null
+ : (DiscoveryCustomMessage)IgniteUtils.field(msg, "delegate");
+
+ if (customMsg instanceof MetadataRemoveProposedMessage) {
+ try {
+ barrier0.await();
+
+ barrier1.await();
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ };
+
+ // Install discovery hoot at the node 'srv1'
+ stopGrid("srv1");
+ IgniteEx ign = startGrid("srv1");
+
+ discoveryHook = null;
+
+ // Move srv2 node an the end of the discovery circle.
+ stopGrid("srv2");
+ startGrid("srv2");
+
+ BinaryObjectBuilder builder0 = ign.binary().builder("Type0");
+
+ builder0.setField("f", 1);
+ builder0.build();
+
+ GridTestUtils.runAsync(() -> {
+ try {
+ removeType(ign, "Type0");
+ }
+ catch (Exception e) {
+ log.error("Unexpected exception", e);
+
+ fail("Unexpected exception.");
+ }
+ });
+
+ barrier0.await();
+
+ GridTestUtils.assertThrowsAnyCause(log, () -> {
+ BinaryObjectBuilder bld = grid("srv2").binary().builder("Type0");
+
+ bld.setField("f1", 1);
+
+ // Short delay guarantee that we go into update metadata before remove metadata continue processing.
+ GridTestUtils.runAsync(() -> {
+ try {
+ U.sleep(200);
+
+ hookMsgs.set(false);
+
+ barrier1.await();
+ }
+ catch (Exception e) {
+ // No-op.
+ }
+ });
+
+ bld.build();
+
+ return null;
+ }, BinaryObjectException.class, "The type is removing now");
+ }
+
+ /**
+ * @param ign Node to remove type.
+ * @param typeName Binary type name.
+ */
+ protected void removeType(IgniteEx ign, String typeName) throws Exception {
+ Exception err = null;
+
+ for (int i = 0; i < MAX_RETRY_CONT; ++i) {
+ try {
+ ign.context().cacheObjects().removeType(ign.context().cacheObjects().typeId(typeName));
+
+ err = null;
+
+ break;
+ }
+ catch (Exception e) {
+ err = e;
+
+ U.sleep(200);
+ }
+ }
+
+ if (err != null)
+ throw err;
+ }
+
+ /**
+ * Delay operation if an operation is executed on a client node.
+ *
+ * @param igns Tests nodes.
+ */
+ protected void delayIfClient(Ignite... igns) throws IgniteInterruptedCheckedException {
+ boolean isThereCli = Arrays.stream(igns).anyMatch(ign -> ((IgniteEx)ign).context().clientNode());
+
+ if (isThereCli)
+ U.sleep(500);
+ }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/binary/BinaryMetadataRemoveWithPersistenceTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/binary/BinaryMetadataRemoveWithPersistenceTest.java
new file mode 100644
index 0000000..9133b1e
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/binary/BinaryMetadataRemoveWithPersistenceTest.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.cache.binary;
+
+import org.apache.ignite.binary.BinaryObjectBuilder;
+import org.apache.ignite.configuration.DataRegionConfiguration;
+import org.apache.ignite.configuration.DataStorageConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.junit.Test;
+
+/**
+ */
+public class BinaryMetadataRemoveWithPersistenceTest extends BinaryMetadataRemoveTest {
+ /** {@inheritDoc} */
+ @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
+ return super.getConfiguration(igniteInstanceName)
+ .setDataStorageConfiguration(new DataStorageConfiguration()
+ .setDefaultDataRegionConfiguration(new DataRegionConfiguration()
+ .setPersistenceEnabled(true)));
+ }
+
+
+ /** {@inheritDoc} */
+ @Override protected void beforeTest() throws Exception {
+ cleanPersistenceDir();
+
+ super.beforeTest();
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void startCluster() throws Exception {
+ super.startCluster();
+
+ grid("srv0").cluster().active(true);
+ }
+
+ /**
+ * Remove type metadata and restart cluster.
+ */
+ @Test
+ public void testRemoveTypeAndClusterRestart() throws Exception {
+ for (String nodeName : new String[]{"srv0", "srv2", "cli0"}) {
+ log.info("+++ Check on " + nodeName);
+
+ BinaryObjectBuilder builder0 = grid(nodeName).binary().builder("Type0");
+
+ builder0.setField("f", 1);
+ builder0.build();
+
+ delayIfClient(grid(nodeName));
+
+ removeType(grid(nodeName), "Type0");
+
+ delayIfClient(grid(nodeName));
+
+ stopAllGrids();
+
+ startCluster();
+
+ BinaryObjectBuilder builder1 = grid(nodeName).binary().builder("Type0");
+ builder1.setField("f", "string");
+ builder1.build();
+
+ delayIfClient(grid(nodeName));
+
+ removeType(grid(nodeName), "Type0");
+
+ delayIfClient(grid(nodeName));
+ }
+ }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/IgnitePdsBinaryMetadataAsyncWritingTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/IgnitePdsBinaryMetadataAsyncWritingTest.java
index 60524e0..575d03f 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/IgnitePdsBinaryMetadataAsyncWritingTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/IgnitePdsBinaryMetadataAsyncWritingTest.java
@@ -467,7 +467,7 @@ public class IgnitePdsBinaryMetadataAsyncWritingTest extends GridCommonAbstractT
//internal map in BinaryMetadataFileStore with futures awaiting write operations
Map map = GridTestUtils.getFieldValue(
- (CacheObjectBinaryProcessorImpl)ig1.context().cacheObjects(), "metadataFileStore", "writer", "preparedWriteTasks");
+ ig1.context().cacheObjects(), "metadataFileStore", "writer", "preparedTasks");
assertTrue(!map.isEmpty());
diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBinaryObjectsTestSuite.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBinaryObjectsTestSuite.java
index 7e69f30..63f557b 100644
--- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBinaryObjectsTestSuite.java
+++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBinaryObjectsTestSuite.java
@@ -60,6 +60,8 @@ import org.apache.ignite.internal.processors.cache.binary.BinaryMetadataMoveLega
import org.apache.ignite.internal.processors.cache.binary.BinaryMetadataRegistrationCacheApiTest;
import org.apache.ignite.internal.processors.cache.binary.BinaryMetadataRegistrationCacheStoreTest;
import org.apache.ignite.internal.processors.cache.binary.BinaryMetadataRegistrationEntryProcessorTest;
+import org.apache.ignite.internal.processors.cache.binary.BinaryMetadataRemoveTest;
+import org.apache.ignite.internal.processors.cache.binary.BinaryMetadataRemoveWithPersistenceTest;
import org.apache.ignite.internal.processors.cache.binary.BinaryMetadataUpdatesFlowTest;
import org.apache.ignite.internal.processors.cache.binary.BinaryTxCacheLocalEntriesSelfTest;
import org.apache.ignite.internal.processors.cache.binary.GridCacheBinaryObjectMetadataExchangeMultinodeTest;
@@ -85,6 +87,9 @@ import org.junit.runners.Suite;
*/
@RunWith(Suite.class)
@Suite.SuiteClasses({
+ BinaryMetadataRemoveTest.class,
+ BinaryMetadataRemoveWithPersistenceTest.class,
+
BinarySimpleNameTestPropertySelfTest.class,
BinaryBasicIdMapperSelfTest.class,
diff --git a/modules/core/src/test/java/org/apache/ignite/util/GridCommandHandlerClusterByClassTest.java b/modules/core/src/test/java/org/apache/ignite/util/GridCommandHandlerClusterByClassTest.java
index b963920..09722a1 100644
--- a/modules/core/src/test/java/org/apache/ignite/util/GridCommandHandlerClusterByClassTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/util/GridCommandHandlerClusterByClassTest.java
@@ -23,6 +23,8 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.EnumMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -41,8 +43,6 @@ import java.util.logging.Logger;
import java.util.logging.StreamHandler;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import java.util.Collection;
-import java.util.EnumMap;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteCache;
import org.apache.ignite.IgniteCheckedException;
@@ -98,6 +98,7 @@ 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.internal.commandline.CommandHandler.UTILITY_NAME;
import static org.apache.ignite.internal.commandline.CommandList.BASELINE;
+import static org.apache.ignite.internal.commandline.CommandList.METADATA;
import static org.apache.ignite.internal.commandline.CommandList.WAL;
import static org.apache.ignite.internal.commandline.CommonArgParser.CMD_VERBOSE;
import static org.apache.ignite.internal.commandline.OutputFormat.MULTI_LINE;
@@ -1592,6 +1593,7 @@ public class GridCommandHandlerClusterByClassTest extends GridCommandHandlerClus
Map<CommandList, Collection<String>> cmdArgs = new EnumMap<>(CommandList.class);
cmdArgs.put(WAL, asList("print", "delete"));
+ cmdArgs.put(METADATA, asList("help", "list"));
String warning = String.format(
"For use experimental command add %s=true to JVM_OPTS in %s",
@@ -1600,7 +1602,7 @@ public class GridCommandHandlerClusterByClassTest extends GridCommandHandlerClus
);
stream(CommandList.values()).filter(cmd -> cmd.command().experimental())
- .peek(cmd -> assertTrue(cmdArgs.containsKey(cmd)))
+ .peek(cmd -> assertTrue("Not contains " + cmd, cmdArgs.containsKey(cmd)))
.forEach(cmd -> cmdArgs.get(cmd).forEach(cmdArg -> {
assertEquals(EXIT_CODE_OK, execute(cmd.text(), cmdArg));
diff --git a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output
index 6048c94..9660e9c 100644
--- a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output
+++ b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output
@@ -125,6 +125,22 @@ This utility can do the following commands:
Parameters:
snapshot_name - Snapshot name.
+ Print metadata command help:
+ control.(sh|bat) --meta help
+
+ Print list of binary metadata types:
+ control.(sh|bat) --meta list
+
+ Print detailed info about specified binary type (the type must be specified by type name or by type identifier):
+ control.(sh|bat) --meta details [--typeId <typeId>] [--typeName <typeName>]
+
+ Remove the metadata of the specified type (the type must be specified by type name or by type identifier) from cluster and saves the removed metadata to the specified file.
+If the file name isn't specified the output file name is: '<typeId>.bin'
+ control.(sh|bat) --meta remove [--typeId <typeId>] [--typeName <typeName>] [--out <fileName>]
+
+ Update cluster metadata from specified file (file name is required)
+ control.(sh|bat) --meta update --in <fileName>
+
By default commands affecting the cluster require interactive confirmation.
Use --yes option to disable it.
diff --git a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output
index 6048c94..9660e9c 100644
--- a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output
+++ b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output
@@ -125,6 +125,22 @@ This utility can do the following commands:
Parameters:
snapshot_name - Snapshot name.
+ Print metadata command help:
+ control.(sh|bat) --meta help
+
+ Print list of binary metadata types:
+ control.(sh|bat) --meta list
+
+ Print detailed info about specified binary type (the type must be specified by type name or by type identifier):
+ control.(sh|bat) --meta details [--typeId <typeId>] [--typeName <typeName>]
+
+ Remove the metadata of the specified type (the type must be specified by type name or by type identifier) from cluster and saves the removed metadata to the specified file.
+If the file name isn't specified the output file name is: '<typeId>.bin'
+ control.(sh|bat) --meta remove [--typeId <typeId>] [--typeName <typeName>] [--out <fileName>]
+
+ Update cluster metadata from specified file (file name is required)
+ control.(sh|bat) --meta update --in <fileName>
+
By default commands affecting the cluster require interactive confirmation.
Use --yes option to disable it.
diff --git a/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheWithIndexingAndPersistenceTestSuite.java b/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheWithIndexingAndPersistenceTestSuite.java
index 9bca9c2..c5369f5 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheWithIndexingAndPersistenceTestSuite.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheWithIndexingAndPersistenceTestSuite.java
@@ -26,6 +26,7 @@ import org.apache.ignite.util.GridCommandHandlerIndexingClusterByClassTest;
import org.apache.ignite.util.GridCommandHandlerIndexingClusterByClassWithSSLTest;
import org.apache.ignite.util.GridCommandHandlerIndexingTest;
import org.apache.ignite.util.GridCommandHandlerIndexingWithSSLTest;
+import org.apache.ignite.util.GridCommandHandlerMetadataTest;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@@ -42,7 +43,8 @@ import org.junit.runners.Suite;
GridCommandHandlerIndexingCheckSizeTest.class,
GridCommandHandlerCheckIndexesInlineSizeTest.class,
StartCachesInParallelTest.class,
- IoStatisticsBasicIndexSelfTest.class
+ IoStatisticsBasicIndexSelfTest.class,
+ GridCommandHandlerMetadataTest.class
})
public class IgniteCacheWithIndexingAndPersistenceTestSuite {
}
diff --git a/modules/indexing/src/test/java/org/apache/ignite/util/GridCommandHandlerMetadataTest.java b/modules/indexing/src/test/java/org/apache/ignite/util/GridCommandHandlerMetadataTest.java
new file mode 100644
index 0000000..d7a1d1e
--- /dev/null
+++ b/modules/indexing/src/test/java/org/apache/ignite/util/GridCommandHandlerMetadataTest.java
@@ -0,0 +1,384 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.util;
+
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.UUID;
+import org.apache.ignite.IgniteBinary;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.binary.BinaryObject;
+import org.apache.ignite.binary.BinaryObjectBuilder;
+import org.apache.ignite.binary.BinaryObjectException;
+import org.apache.ignite.binary.BinaryType;
+import org.apache.ignite.client.ClientCacheConfiguration;
+import org.apache.ignite.client.IgniteClient;
+import org.apache.ignite.configuration.ClientConfiguration;
+import org.apache.ignite.internal.binary.BinarySchema;
+import org.apache.ignite.internal.binary.BinaryTypeImpl;
+import org.apache.ignite.testframework.GridTestUtils;
+import org.jetbrains.annotations.Nullable;
+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;
+
+/**
+ * Checks command line metadata commands.
+ */
+public class GridCommandHandlerMetadataTest extends GridCommandHandlerClusterByClassAbstractTest {
+ /** File system. */
+ public static final FileSystem FS = FileSystems.getDefault();
+
+ /** Types count. */
+ private static final int TYPES_CNT = 10;
+
+ /**
+ * Check the command '--meta list'.
+ * Steps:
+ * - Creates binary types for a test (by BinaryObjectBuilder);
+ * - execute the command '--meta list'.
+ * - Check command output (must contains all created types).
+ */
+ @Test
+ public void testMetadataList() {
+ injectTestSystemOut();
+
+ for (int typeNum = 0; typeNum < TYPES_CNT; ++typeNum) {
+ BinaryObjectBuilder bob = crd.binary().builder("Type_" + typeNum);
+
+ for (int fldNum = 0; fldNum <= typeNum; ++fldNum)
+ bob.setField("fld_" + fldNum, 0);
+
+ bob.build();
+ }
+
+ assertEquals(EXIT_CODE_OK, execute("--meta", "list"));
+
+ String out = testOut.toString();
+
+ for (int typeNum = 0; typeNum < TYPES_CNT; ++typeNum)
+ assertContains(log, out, "typeName=Type_" + typeNum);
+ }
+
+ /**
+ * Check the command '--meta details'.
+ * Steps:
+ * - Creates binary two types for a test (by BinaryObjectBuilder) with several fields and shemas;
+ * - execute the command '--meta details' for the type Type0 by name
+ * - check metadata print.
+ * - execute the command '--meta details' for the type Type0 by type ID on different formats.
+ * - check metadata print.
+ */
+ @Test
+ public void testMetadataDetails() {
+ injectTestSystemOut();
+
+ BinaryObjectBuilder bob0 = crd.binary().builder("TypeName0");
+ bob0.setField("fld0", 0);
+ bob0.build();
+
+ bob0 = crd.binary().builder("TypeName0");
+ bob0.setField("fld1", "0");
+ bob0.build();
+
+ bob0 = crd.binary().builder("TypeName0");
+ bob0.setField("fld0", 1);
+ bob0.setField("fld2", UUID.randomUUID());
+ BinaryObject bo0 = bob0.build();
+
+ BinaryObjectBuilder bob1 = crd.binary().builder("TypeName1");
+ bob1.setField("fld0", 0);
+ bob1.build();
+
+ bob1 = crd.binary().builder("TypeName1");
+ bob1.setField("fld0", 0);
+ bob1.setField("fld1", new Date());
+ bob1.setField("fld2", 0.1);
+ bob1.setField("fld3", new long[]{0, 1, 2, 3});
+
+ BinaryObject bo1 = bob1.build();
+
+ assertEquals(EXIT_CODE_OK, execute("--meta", "details", "--typeName", "TypeName0"));
+ checkTypeDetails(log, testOut.toString(), crd.context().cacheObjects().metadata(bo0.type().typeId()));
+
+ assertEquals(EXIT_CODE_OK, execute("--meta", "details", "--typeId",
+ "0x" + Integer.toHexString(crd.context().cacheObjects().typeId("TypeName1"))));
+ checkTypeDetails(log, testOut.toString(), crd.context().cacheObjects().metadata(bo1.type().typeId()));
+
+ assertEquals(EXIT_CODE_OK, execute("--meta", "details", "--typeId",
+ Integer.toString(crd.context().cacheObjects().typeId("TypeName1"))));
+ checkTypeDetails(log, testOut.toString(), crd.context().cacheObjects().metadata(bo1.type().typeId()));
+ }
+
+ /**
+ * Check the command '--meta remove' and '--meta update' with invalid arguments.
+ * Steps:
+ * - executes 'remove' command without specified type.
+ * - checks error code and command output.
+ * - executes 'remove' command with '--out' option specified as the exist directory
+ * ('--out' parameter must be a file name).
+ * - checks error code and command output.
+ * - executes 'update' command with '--in' option specified as the exist directory
+ * ('--in' parameter must be a file name)
+ * - checks error code and command output.
+ */
+ @Test
+ public void testInvalidArguments() {
+ injectTestSystemOut();
+
+ String out;
+
+ assertEquals(EXIT_CODE_INVALID_ARGUMENTS, execute("--meta", "remove"));
+ out = testOut.toString();
+ assertContains(log, out, "Check arguments.");
+ assertContains(log, out, "Type to remove is not specified");
+ assertContains(log, out, "Please add one of the options: --typeName <type_name> or --typeId <type_id>");
+
+ assertEquals(EXIT_CODE_INVALID_ARGUMENTS, execute("--meta", "remove", "--typeId", "0", "--out", "target"));
+ out = testOut.toString();
+ assertContains(log, out, "Check arguments.");
+ assertContains(log, out, "Cannot write to output file target.");
+
+ assertEquals(EXIT_CODE_INVALID_ARGUMENTS, execute("--meta", "update", "--in", "target"));
+ out = testOut.toString();
+ assertContains(log, out, "Check arguments.");
+ assertContains(log, out, "Cannot read metadata from target");
+ }
+
+ /**
+ * Check the command '--meta remove' and '--meta update'.
+ * Steps:
+ * - creates the type 'Type0'.
+ * - removes the type by cmdline utility (store removed metadata to specified file).
+ * - checks that type removed (try to create Type0 with the same field and different type).
+ * - removes the new type 'Type0' by cmd line utility.
+ * - restores Typo0 from the file
+ * - checks restored type.
+ */
+ @Test
+ public void testRemoveUpdate() throws Exception {
+ injectTestSystemOut();
+
+ Path typeFile = FS.getPath("type0.bin");
+
+ try {
+ createType("Type0", 0);
+
+ // Type of the field cannot be changed.
+ GridTestUtils.assertThrowsAnyCause(log, () -> {
+ createType("Type0", "string");
+
+ return null;
+ }, BinaryObjectException.class, "Wrong value has been set");
+
+ assertEquals(EXIT_CODE_OK, execute("--meta", "remove",
+ "--typeName", "Type0",
+ "--out", typeFile.toString()));
+
+ // New type must be created successfully after remove the type.
+ createType("Type0", "string");
+
+ assertEquals(EXIT_CODE_OK, execute("--meta", "remove", "--typeName", "Type0"));
+
+ // Restore the metadata from file.
+ assertEquals(EXIT_CODE_OK, execute("--meta", "update", "--in", typeFile.toString()));
+
+ // Type of the field cannot be changed.
+ GridTestUtils.assertThrowsAnyCause(log, () -> {
+ createType("Type0", "string");
+
+ return null;
+ }, BinaryObjectException.class, "Wrong value has been set");
+
+ createType("Type0", 1);
+
+ crd.context().cacheObjects().removeType(crd.context().cacheObjects().typeId("Type0"));
+
+ createType("Type0", "string");
+
+ // Restore the metadata from file.
+ assertEquals(EXIT_CODE_UNEXPECTED_ERROR, execute("--meta", "update", "--in", typeFile.toString()));
+
+ String out = testOut.toString();
+
+ assertContains(log, out, "Failed to execute metadata command='update'");
+ assertContains(log, out, "Type 'Type0' with typeId 110843958 has a " +
+ "different/incorrect type for field 'fld'.");
+ assertContains(log, out, "Expected 'String' but 'int' was provided. " +
+ "The type of an existing field can not be changed");
+ }
+ finally {
+ if (Files.exists(typeFile))
+ Files.delete(typeFile);
+ }
+ }
+
+ /**
+ * Check the all thin connections are dropped on the command '--meta remove' and '--meta update'.
+ * Steps:
+ * - opens thin client connection.
+ * - creates Type0 on thin client side.
+ * - removes type by cmd line util.
+ * - executes any command on thin client to detect disconnect.
+ * - creates Type0 on thin client side with other type of field (checks the type was removed).
+ */
+ @Test
+ public void testDropThinConnectionsOnRemove() throws Exception {
+ injectTestSystemOut();
+
+ Path typeFile = FS.getPath("type0.bin");
+
+ try (IgniteClient cli = Ignition.startClient(clientConfiguration())) {
+ createType(cli.binary(), "Type0", 1);
+
+ assertEquals(EXIT_CODE_OK, execute("--meta", "remove",
+ "--typeName", "Type0",
+ "--out", typeFile.toString()));
+
+ // Executes command to check disconnect / reconnect.
+ GridTestUtils.assertThrows(log, () ->
+ cli.createCache(new ClientCacheConfiguration().setName("test")),
+ Exception.class, null);
+
+ createType(cli.binary(), "Type0", "str");
+ }
+ finally {
+ if (Files.exists(typeFile))
+ Files.delete(typeFile);
+ }
+ }
+
+ /**
+ * Check the all thin connections are dropped on the command '--meta remove' and '--meta update'.
+ * Steps:
+ * - opens JDBC thin client connection.
+ * - executes: CREATE TABLE test(id INT PRIMARY KEY, objVal OTHER).
+ * - inserts the instance of the 'TestValue' class to the table.
+ * - removes the type 'TestValue' by cmd line.
+ * - executes any command on JDBC driver to detect disconnect.
+ * - checks metadata on client side. It must be empty.
+ */
+ @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 (final Statement stmt = conn.createStatement()) {
+ stmt.execute("CREATE TABLE test(id INT PRIMARY KEY, objVal OTHER)");
+
+ try (PreparedStatement pstmt = conn.prepareStatement("INSERT INTO test(id, objVal) VALUES (?, ?)")) {
+ pstmt.setInt(1, 0);
+ pstmt.setObject(2, new TestValue());
+
+ pstmt.execute();
+ }
+
+ stmt.execute("DELETE FROM test WHERE id >= 0");
+ }
+
+ HashMap<Integer, BinaryType> metasOld = GridTestUtils.getFieldValue(conn, "metaHnd", "cache", "metas");
+
+ assertFalse(metasOld.isEmpty());
+
+ assertEquals(EXIT_CODE_OK, execute("--meta", "remove",
+ "--typeName", TestValue.class.getName(),
+ "--out", typeFile.toString()));
+
+ // Executes any command to check disconnect.
+ GridTestUtils.assertThrows(log, () -> {
+ try (Statement stmt = conn.createStatement()) {
+ stmt.execute("SELECT * FROM test");
+ }
+
+ return null;
+ },
+ SQLException.class, "Failed to communicate with Ignite cluster");
+
+ HashMap<Integer, BinaryType> metas = GridTestUtils.getFieldValue(conn, "metaHnd", "cache", "metas");
+
+ assertNotSame(metasOld, metas);
+ assertTrue(metas.isEmpty());
+ }
+ finally {
+ if (Files.exists(typeFile))
+ Files.delete(typeFile);
+ }
+ }
+
+ /**
+ * @param t Binary type.
+ */
+ private void checkTypeDetails(@Nullable IgniteLogger log, String cmdOut, BinaryType t) {
+ assertContains(log, cmdOut, "typeId=" + "0x" + Integer.toHexString(t.typeId()).toUpperCase());
+ assertContains(log, cmdOut, "typeName=" + t.typeName());
+ assertContains(log, cmdOut, "Fields:");
+
+ for (String fldName : t.fieldNames())
+ assertContains(log, cmdOut, "name=" + fldName + ", type=" + t.fieldTypeName(fldName));
+
+ for (BinarySchema s : ((BinaryTypeImpl)t).metadata().schemas())
+ assertContains(log, cmdOut, "schemaId=0x" + Integer.toHexString(s.schemaId()).toUpperCase());
+ }
+
+ /**
+ * @param typeName Type name
+ * @param val Field value.
+ */
+ void createType(String typeName, Object val) {
+ 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();
+ }
+
+ /** */
+ private ClientConfiguration clientConfiguration() {
+ return new ClientConfiguration()
+ .setAddresses("127.0.0.1:10800");
+ }
+
+ /**
+ *
+ */
+ public static class TestValue {
+ /** */
+ public final int val = 3;
+ }
+}