diff --git a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/CommandList.java b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/CommandList.java index e16acaa97a32e..f00a4c0606067 100644 --- a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/CommandList.java +++ b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/CommandList.java @@ -19,7 +19,7 @@ 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.encryption.EncryptionCommands; import org.apache.ignite.internal.commandline.meta.MetadataCommand; import org.apache.ignite.internal.commandline.metric.MetricCommand; import org.apache.ignite.internal.commandline.property.PropertyCommand; @@ -59,7 +59,7 @@ public enum CommandList { DIAGNOSTIC("--diagnostic", new DiagnosticCommand()), /** Encryption features command. */ - ENCRYPTION("--encryption", new EncryptionCommand()), + ENCRYPTION("--encryption", new EncryptionCommands()), /** Kill command. */ KILL("--kill", new KillCommand()), diff --git a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/encryption/CacheGroupEncryptionCommand.java b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/encryption/CacheGroupEncryptionCommand.java new file mode 100644 index 0000000000000..d4c09b896f96f --- /dev/null +++ b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/encryption/CacheGroupEncryptionCommand.java @@ -0,0 +1,264 @@ +/* + * 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.encryption; + +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.logging.Logger; +import org.apache.ignite.IgniteException; +import org.apache.ignite.internal.client.GridClient; +import org.apache.ignite.internal.client.GridClientConfiguration; +import org.apache.ignite.internal.commandline.AbstractCommand; +import org.apache.ignite.internal.commandline.Command; +import org.apache.ignite.internal.commandline.CommandArgIterator; +import org.apache.ignite.internal.commandline.CommandList; +import org.apache.ignite.internal.commandline.CommandLogger; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.visor.encryption.VisorCacheGroupEncryptionTaskArg; +import org.apache.ignite.internal.visor.encryption.VisorCacheGroupEncryptionTaskResult; +import org.apache.ignite.internal.visor.encryption.VisorEncryptionKeyIdsTask; +import org.apache.ignite.internal.visor.encryption.VisorReencryptionResumeTask; +import org.apache.ignite.internal.visor.encryption.VisorReencryptionStatusTask; +import org.apache.ignite.internal.visor.encryption.VisorReencryptionSuspendTask; + +import static org.apache.ignite.internal.commandline.CommandList.ENCRYPTION; +import static org.apache.ignite.internal.commandline.CommandLogger.DOUBLE_INDENT; +import static org.apache.ignite.internal.commandline.CommandLogger.INDENT; +import static org.apache.ignite.internal.commandline.TaskExecutor.BROADCAST_UUID; +import static org.apache.ignite.internal.commandline.TaskExecutor.executeTaskByNameOnNode; +import static org.apache.ignite.internal.commandline.encryption.EncryptionSubcommands.CACHE_GROUP_KEY_IDS; + +/** + * Base cache group encryption multinode subcommand. + * + * @param Command result type. + */ +public abstract class CacheGroupEncryptionCommand extends AbstractCommand { + /** Cache group reencryption task argument. */ + private VisorCacheGroupEncryptionTaskArg taskArg; + + /** {@inheritDoc} */ + @Override public VisorCacheGroupEncryptionTaskArg arg() { + return taskArg; + } + + /** {@inheritDoc} */ + @Override public void parseArguments(CommandArgIterator argIter) { + String grpName = argIter.nextArg("Сache group name is expected."); + + if (argIter.hasNextSubArg()) + throw new IllegalArgumentException("Unexpected command argument: " + argIter.peekNextArg()); + + taskArg = new VisorCacheGroupEncryptionTaskArg(grpName); + } + + /** {@inheritDoc} */ + @Override public Object execute(GridClientConfiguration clientCfg, Logger log) throws Exception { + try (GridClient client = Command.startClient(clientCfg)) { + VisorCacheGroupEncryptionTaskResult res = executeTaskByNameOnNode( + client, + visorTaskName(), + taskArg, + BROADCAST_UUID, + clientCfg + ); + + printResults(res, taskArg.groupName(), log); + + return res; + } + catch (Throwable e) { + log.severe("Failed to perform operation."); + log.severe(CommandLogger.errorMessage(e)); + + throw e; + } + } + + /** + * @param res Response. + * @param grpName Cache group name. + * @param log Logger. + */ + protected void printResults(VisorCacheGroupEncryptionTaskResult res, String grpName, Logger log) { + Map exceptions = res.exceptions(); + + for (Map.Entry entry : exceptions.entrySet()) { + log.info(INDENT + "Node " + entry.getKey() + ":"); + + log.info(String.format("%sfailed to execute command for the cache group \"%s\": %s.", + DOUBLE_INDENT, grpName, entry.getValue().getMessage())); + } + + Map results = res.results(); + + for (Map.Entry entry : results.entrySet()) { + log.info(INDENT + "Node " + entry.getKey() + ":"); + + printNodeResult(entry.getValue(), grpName, log); + } + } + + /** + * @param res Response. + * @param grpName Cache group name. + * @param log Logger. + */ + protected abstract void printNodeResult(T res, String grpName, Logger log); + + /** + * @return Visor task name. + */ + protected abstract String visorTaskName(); + + /** Subcommand to Display re-encryption status of the cache group. */ + protected static class ReencryptionStatus extends CacheGroupEncryptionCommand { + /** {@inheritDoc} */ + @Override protected void printNodeResult(Long bytesLeft, String grpName, Logger log) { + if (bytesLeft == -1) + log.info(DOUBLE_INDENT + "re-encryption completed or not required"); + else if (bytesLeft == 0) + log.info(DOUBLE_INDENT + "re-encryption will be completed after the next checkpoint"); + else + log.info(String.format("%s%d KB of data left for re-encryption", DOUBLE_INDENT, bytesLeft / 1024)); + } + + /** {@inheritDoc} */ + @Override protected String visorTaskName() { + return VisorReencryptionStatusTask.class.getName(); + } + + /** {@inheritDoc} */ + @Override public String name() { + return EncryptionSubcommands.REENCRYPTION_STATUS.text().toUpperCase(); + } + + /** {@inheritDoc} */ + @Override public void printUsage(Logger log) { + Command.usage(log, "Display re-encryption status of the cache group:", CommandList.ENCRYPTION, + EncryptionSubcommands.REENCRYPTION_STATUS.toString(), "cacheGroupName"); + } + } + + /** Subcommand to view current encryption key IDs of the cache group. */ + protected static class CacheKeyIds extends CacheGroupEncryptionCommand> { + /** {@inheritDoc} */ + @Override protected void printResults( + VisorCacheGroupEncryptionTaskResult> res, + String grpName, + Logger log + ) { + log.info("Encryption key identifiers for cache: " + grpName); + + super.printResults(res, grpName, log); + } + + /** {@inheritDoc} */ + @Override protected void printNodeResult(List keyIds, String grpName, Logger log) { + if (F.isEmpty(keyIds)) { + log.info(DOUBLE_INDENT + "---"); + + return; + } + + for (int i = 0; i < keyIds.size(); i++) + log.info(DOUBLE_INDENT + keyIds.get(i) + (i == 0 ? " (active)" : "")); + } + + /** {@inheritDoc} */ + @Override protected String visorTaskName() { + return VisorEncryptionKeyIdsTask.class.getName(); + } + + /** {@inheritDoc} */ + @Override public String name() { + return CACHE_GROUP_KEY_IDS.text().toUpperCase(); + } + + /** {@inheritDoc} */ + @Override public void printUsage(Logger log) { + Command.usage(log, "View encryption key identifiers of the cache group:", ENCRYPTION, + CACHE_GROUP_KEY_IDS.toString(), "cacheGroupName"); + } + } + + /** Subcommand to suspend re-encryption of the cache group. */ + protected static class SuspendReencryption extends CacheGroupEncryptionCommand { + /** {@inheritDoc} */ + @Override protected String visorTaskName() { + return VisorReencryptionSuspendTask.class.getName(); + } + + /** {@inheritDoc} */ + @Override public String name() { + return EncryptionSubcommands.REENCRYPTION_SUSPEND.text().toUpperCase(); + } + + /** {@inheritDoc} */ + @Override public void printUsage(Logger log) { + Command.usage(log, "Suspend re-encryption of the cache group:", CommandList.ENCRYPTION, + EncryptionSubcommands.REENCRYPTION_SUSPEND.toString(), "cacheGroupName"); + } + + /** {@inheritDoc} */ + @Override protected void printNodeResult(Boolean success, String grpName, Logger log) { + log.info(String.format("%sre-encryption of the cache group \"%s\" has %sbeen suspended.", + DOUBLE_INDENT, grpName, (success ? "" : "already "))); + } + + /** {@inheritDoc} */ + @Override protected void printResults( + VisorCacheGroupEncryptionTaskResult res, + String grpName, + Logger log + ) { + super.printResults(res, grpName, log); + + log.info(""); + log.info("Note: the re-encryption suspend status is not persisted, re-encryption will be started " + + "automatically after the node is restarted."); + log.info(""); + } + } + + /** Subcommand to resume re-encryption of the cache group. */ + protected static class ResumeReencryption extends CacheGroupEncryptionCommand { + /** {@inheritDoc} */ + @Override protected String visorTaskName() { + return VisorReencryptionResumeTask.class.getName(); + } + + /** {@inheritDoc} */ + @Override public String name() { + return EncryptionSubcommands.REENCRYPTION_RESUME.text().toUpperCase(); + } + + /** {@inheritDoc} */ + @Override public void printUsage(Logger log) { + Command.usage(log, "Resume re-encryption of the cache group:", CommandList.ENCRYPTION, + EncryptionSubcommands.REENCRYPTION_RESUME.toString(), "cacheGroupName"); + } + + /** {@inheritDoc} */ + @Override protected void printNodeResult(Boolean success, String grpName, Logger log) { + log.info(String.format("%sre-encryption of the cache group \"%s\" has %sbeen resumed.", + DOUBLE_INDENT, grpName, (success ? "" : "already "))); + } + } +} diff --git a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/encryption/ChangeCacheGroupKeyCommand.java b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/encryption/ChangeCacheGroupKeyCommand.java new file mode 100644 index 0000000000000..8518e5d629019 --- /dev/null +++ b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/encryption/ChangeCacheGroupKeyCommand.java @@ -0,0 +1,95 @@ +/* + * 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.encryption; + +import java.util.logging.Logger; +import org.apache.ignite.internal.client.GridClient; +import org.apache.ignite.internal.client.GridClientConfiguration; +import org.apache.ignite.internal.commandline.AbstractCommand; +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.visor.encryption.VisorCacheGroupEncryptionTaskArg; +import org.apache.ignite.internal.visor.encryption.VisorChangeCacheGroupKeyTask; + +import static org.apache.ignite.internal.commandline.CommandList.ENCRYPTION; +import static org.apache.ignite.internal.commandline.TaskExecutor.executeTaskByNameOnNode; +import static org.apache.ignite.internal.commandline.encryption.EncryptionSubcommands.CHANGE_CACHE_GROUP_KEY; + +/** + * Change cache group key encryption subcommand. + */ +public class ChangeCacheGroupKeyCommand extends AbstractCommand { + /** Change cache group key task argument. */ + private VisorCacheGroupEncryptionTaskArg taskArg; + + /** {@inheritDoc} */ + @Override public Object execute(GridClientConfiguration clientCfg, Logger log) throws Exception { + try (GridClient client = Command.startClient(clientCfg)) { + executeTaskByNameOnNode( + client, + VisorChangeCacheGroupKeyTask.class.getName(), + taskArg, + null, + clientCfg + ); + + log.info("The encryption key has been changed for the cache group \"" + taskArg.groupName() + "\"."); + + return null; + } + catch (Throwable e) { + log.severe("Failed to perform operation."); + log.severe(CommandLogger.errorMessage(e)); + + throw e; + } + } + + /** {@inheritDoc} */ + @Override public String confirmationPrompt() { + return "Warning: the command will change the encryption key of the cache group. Joining a node during " + + "the key change process is prohibited and will be rejected."; + } + + /** {@inheritDoc} */ + @Override public VisorCacheGroupEncryptionTaskArg arg() { + return taskArg; + } + + /** {@inheritDoc} */ + @Override public void parseArguments(CommandArgIterator argIter) { + String argCacheGrpName = argIter.nextArg("Сache group name is expected."); + + taskArg = new VisorCacheGroupEncryptionTaskArg(argCacheGrpName); + + if (argIter.hasNextSubArg()) + throw new IllegalArgumentException("Unexpected command argument: " + argIter.peekNextArg()); + } + + /** {@inheritDoc} */ + @Override public void printUsage(Logger log) { + Command.usage(log, "Change the encryption key of the cache group:", ENCRYPTION, + CHANGE_CACHE_GROUP_KEY.toString(), "cacheGroupName"); + } + + /** {@inheritDoc} */ + @Override public String name() { + return CHANGE_CACHE_GROUP_KEY.text().toUpperCase(); + } +} diff --git a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/encryption/ChangeMasterKeyCommand.java b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/encryption/ChangeMasterKeyCommand.java new file mode 100644 index 0000000000000..a48dc4bcbb4d3 --- /dev/null +++ b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/encryption/ChangeMasterKeyCommand.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.encryption; + +import java.util.logging.Logger; +import org.apache.ignite.internal.client.GridClient; +import org.apache.ignite.internal.client.GridClientConfiguration; +import org.apache.ignite.internal.commandline.AbstractCommand; +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.visor.encryption.VisorChangeMasterKeyTask; + +import static org.apache.ignite.internal.commandline.CommandList.ENCRYPTION; +import static org.apache.ignite.internal.commandline.TaskExecutor.executeTaskByNameOnNode; +import static org.apache.ignite.internal.commandline.encryption.EncryptionSubcommands.CHANGE_MASTER_KEY; + +/** + * Change master key encryption subcommand. + */ +public class ChangeMasterKeyCommand extends AbstractCommand { + /** New master key name. */ + private String argMasterKeyName; + + /** {@inheritDoc} */ + @Override public Object execute(GridClientConfiguration clientCfg, Logger log) throws Exception { + try (GridClient client = Command.startClient(clientCfg)) { + String resMsg = executeTaskByNameOnNode( + client, + VisorChangeMasterKeyTask.class.getName(), + argMasterKeyName, + null, + clientCfg + ); + + log.info(resMsg); + + return resMsg; + } + catch (Throwable e) { + log.severe("Failed to perform operation."); + log.severe(CommandLogger.errorMessage(e)); + + throw e; + } + } + + /** {@inheritDoc} */ + @Override public String confirmationPrompt() { + return "Warning: the command will change the master key. Cache start and node join during the key change " + + "process is prohibited and will be rejected."; + } + + /** {@inheritDoc} */ + @Override public String arg() { + return argMasterKeyName; + } + + /** {@inheritDoc} */ + @Override public void parseArguments(CommandArgIterator argIter) { + argMasterKeyName = argIter.nextArg("Expected master key name."); + } + + /** {@inheritDoc} */ + @Override public void printUsage(Logger log) { + Command.usage(log, "Change the master key:", ENCRYPTION, CHANGE_MASTER_KEY.toString(), "newMasterKeyName"); + } + + /** {@inheritDoc} */ + @Override public String name() { + return CHANGE_MASTER_KEY.text().toUpperCase(); + } +} diff --git a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/encryption/EncryptionCommand.java b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/encryption/EncryptionCommand.java deleted file mode 100644 index 5cbd723ddf73d..0000000000000 --- a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/encryption/EncryptionCommand.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * 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.encryption; - -import java.util.logging.Logger; -import org.apache.ignite.internal.client.GridClient; -import org.apache.ignite.internal.client.GridClientConfiguration; -import org.apache.ignite.internal.commandline.AbstractCommand; -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.visor.encryption.VisorChangeMasterKeyTask; -import org.apache.ignite.internal.visor.encryption.VisorGetMasterKeyNameTask; - -import static org.apache.ignite.internal.commandline.CommandList.ENCRYPTION; -import static org.apache.ignite.internal.commandline.TaskExecutor.executeTaskByNameOnNode; -import static org.apache.ignite.internal.commandline.encryption.EncryptionSubcommand.CHANGE_MASTER_KEY; -import static org.apache.ignite.internal.commandline.encryption.EncryptionSubcommand.GET_MASTER_KEY_NAME; -import static org.apache.ignite.internal.commandline.encryption.EncryptionSubcommand.of; - -/** - * Commands assosiated with encryption features. - * - * @see EncryptionSubcommand - */ -public class EncryptionCommand extends AbstractCommand { - /** Subcommand. */ - EncryptionSubcommand cmd; - - /** The task name. */ - String taskName; - - /** The task arguments. */ - Object taskArgs; - - /** {@inheritDoc} */ - @Override public Object execute(GridClientConfiguration clientCfg, Logger logger) throws Exception { - try (GridClient client = Command.startClient(clientCfg)) { - String res = executeTaskByNameOnNode( - client, - taskName, - taskArgs, - null, - clientCfg - ); - - logger.info(res); - - return res; - } - catch (Throwable e) { - logger.severe("Failed to perform operation."); - logger.severe(CommandLogger.errorMessage(e)); - - throw e; - } - } - - /** {@inheritDoc} */ - @Override public String confirmationPrompt() { - if (CHANGE_MASTER_KEY == cmd) { - return "Warning: the command will change the master key. Cache start and node join during the key change " + - "process is prohibited and will be rejected."; - } - - return null; - } - - /** {@inheritDoc} */ - @Override public void parseArguments(CommandArgIterator argIter) { - EncryptionSubcommand cmd = of(argIter.nextArg("Expected encryption action.")); - - if (cmd == null) - throw new IllegalArgumentException("Expected correct encryption action."); - - switch (cmd) { - case GET_MASTER_KEY_NAME: - taskName = VisorGetMasterKeyNameTask.class.getName(); - - taskArgs = null; - - break; - - case CHANGE_MASTER_KEY: - String masterKeyName = argIter.nextArg("Expected master key name."); - - taskName = VisorChangeMasterKeyTask.class.getName(); - - taskArgs = masterKeyName; - - break; - - default: - throw new IllegalArgumentException("Unknown encryption subcommand: " + cmd); - } - - this.cmd = cmd; - } - - /** {@inheritDoc} */ - @Override public Object arg() { - return taskArgs; - } - - /** {@inheritDoc} */ - @Override public void printUsage(Logger logger) { - Command.usage(logger, "Print the current master key name:", ENCRYPTION, GET_MASTER_KEY_NAME.toString()); - Command.usage(logger, "Change the master key:", ENCRYPTION, CHANGE_MASTER_KEY.toString(), "newMasterKeyName"); - } - - /** {@inheritDoc} */ - @Override public String name() { - return ENCRYPTION.toCommandName(); - } -} diff --git a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/encryption/EncryptionCommands.java b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/encryption/EncryptionCommands.java new file mode 100644 index 0000000000000..fbae770bdc2ed --- /dev/null +++ b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/encryption/EncryptionCommands.java @@ -0,0 +1,70 @@ +/* + * 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.encryption; + +import java.util.logging.Logger; +import org.apache.ignite.internal.client.GridClientConfiguration; +import org.apache.ignite.internal.commandline.AbstractCommand; +import org.apache.ignite.internal.commandline.CommandArgIterator; +import org.apache.ignite.internal.commandline.CommandList; + +/** + * Commands related to encryption functions. + * + * @see EncryptionSubcommands + */ +public class EncryptionCommands extends AbstractCommand { + /** Subcommand. */ + private EncryptionSubcommands cmd; + + /** {@inheritDoc} */ + @Override public Object execute(GridClientConfiguration clientCfg, Logger logger) throws Exception { + return cmd.subcommand().execute(clientCfg, logger); + } + + /** {@inheritDoc} */ + @Override public void parseArguments(CommandArgIterator argIter) { + EncryptionSubcommands cmd = EncryptionSubcommands.of(argIter.nextArg("Expected encryption action.")); + + if (cmd == null) + throw new IllegalArgumentException("Expected correct encryption action."); + + cmd.subcommand().parseArguments(argIter); + + if (argIter.hasNextSubArg()) + throw new IllegalArgumentException("Unexpected argument of --encryption subcommand: " + argIter.peekNextArg()); + + this.cmd = cmd; + } + + /** {@inheritDoc} */ + @Override public EncryptionSubcommands arg() { + return cmd; + } + + /** {@inheritDoc} */ + @Override public void printUsage(Logger logger) { + for (EncryptionSubcommands cmd : EncryptionSubcommands.values()) + cmd.subcommand().printUsage(logger); + } + + /** {@inheritDoc} */ + @Override public String name() { + return CommandList.ENCRYPTION.toCommandName(); + } +} diff --git a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/encryption/EncryptionSubcommand.java b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/encryption/EncryptionSubcommand.java deleted file mode 100644 index 3c47c024ea137..0000000000000 --- a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/encryption/EncryptionSubcommand.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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.encryption; - -import org.jetbrains.annotations.Nullable; - -/** - * Set of encryption subcommands. - * - * @see EncryptionCommand - */ -public enum EncryptionSubcommand { - /** Subcommand to get the current master key name. */ - GET_MASTER_KEY_NAME("get_master_key_name"), - - /** Subcommand to change the master key. */ - CHANGE_MASTER_KEY("change_master_key"); - - /** Subcommand name. */ - private final String name; - - /** @param name Encryption subcommand name. */ - EncryptionSubcommand(String name) { - this.name = name; - } - - /** - * @param text Command text (case insensitive). - * @return Command for the text. {@code Null} if there is no such command. - */ - @Nullable public static EncryptionSubcommand of(String text) { - for (EncryptionSubcommand cmd : EncryptionSubcommand.values()) { - if (cmd.name.equalsIgnoreCase(text)) - return cmd; - } - - return null; - } - - /** {@inheritDoc} */ - @Override public String toString() { - return name; - } -} diff --git a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/encryption/EncryptionSubcommands.java b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/encryption/EncryptionSubcommands.java new file mode 100644 index 0000000000000..c8d09419e39ea --- /dev/null +++ b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/encryption/EncryptionSubcommands.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.encryption; + +import org.apache.ignite.internal.commandline.Command; +import org.jetbrains.annotations.Nullable; + +/** + * Set of encryption subcommands. + * + * @see EncryptionCommands + */ +public enum EncryptionSubcommands { + /** Subcommand to get the current master key name. */ + GET_MASTER_KEY_NAME("get_master_key_name", new GetMasterKeyNameCommand()), + + /** Subcommand to change the master key. */ + CHANGE_MASTER_KEY("change_master_key", new ChangeMasterKeyCommand()), + + /** Subcommand to change the current encryption key for specified cache group. */ + CHANGE_CACHE_GROUP_KEY("change_cache_key", new ChangeCacheGroupKeyCommand()), + + /** Subcommand to view current encryption key IDs of the cache group. */ + CACHE_GROUP_KEY_IDS("cache_key_ids", new CacheGroupEncryptionCommand.CacheKeyIds()), + + /** Subcommand to display re-encryption status of the cache group. */ + REENCRYPTION_STATUS("reencryption_status", new CacheGroupEncryptionCommand.ReencryptionStatus()), + + /** Subcommand to suspend re-encryption of the cache group. */ + REENCRYPTION_SUSPEND("suspend_reencryption", new CacheGroupEncryptionCommand.SuspendReencryption()), + + /** Subcommand to resume re-encryption of the cache group. */ + REENCRYPTION_RESUME("resume_reencryption", new CacheGroupEncryptionCommand.ResumeReencryption()), + + /** Subcommand to view/change cache group re-encryption rate limit. */ + REENCRYPTION_RATE("reencryption_rate_limit", new ReencryptionRateCommand()); + + /** Subcommand name. */ + private final String name; + + /** Command. */ + private final Command cmd; + + /** + * @param name Encryption subcommand name. + * @param cmd Command implementation. + */ + EncryptionSubcommands(String name, Command cmd) { + this.name = name; + this.cmd = cmd; + } + + /** + * @return Name. + */ + public String text() { + return name; + } + + /** + * @return Cache subcommand implementation. + */ + public Command subcommand() { + return cmd; + } + + /** + * @param text Command text (case insensitive). + * @return Command for the text. {@code Null} if there is no such command. + */ + @Nullable public static EncryptionSubcommands of(String text) { + for (EncryptionSubcommands cmd : values()) { + if (cmd.name.equalsIgnoreCase(text)) + return cmd; + } + + return null; + } + + /** {@inheritDoc} */ + @Override public String toString() { + return name; + } +} diff --git a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/encryption/GetMasterKeyNameCommand.java b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/encryption/GetMasterKeyNameCommand.java new file mode 100644 index 0000000000000..02bb8ed9750ef --- /dev/null +++ b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/encryption/GetMasterKeyNameCommand.java @@ -0,0 +1,73 @@ +/* + * 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.encryption; + +import java.util.logging.Logger; +import org.apache.ignite.internal.client.GridClient; +import org.apache.ignite.internal.client.GridClientConfiguration; +import org.apache.ignite.internal.commandline.AbstractCommand; +import org.apache.ignite.internal.commandline.Command; +import org.apache.ignite.internal.commandline.CommandLogger; +import org.apache.ignite.internal.visor.encryption.VisorGetMasterKeyNameTask; + +import static org.apache.ignite.internal.commandline.CommandList.ENCRYPTION; +import static org.apache.ignite.internal.commandline.TaskExecutor.executeTaskByNameOnNode; +import static org.apache.ignite.internal.commandline.encryption.EncryptionSubcommands.GET_MASTER_KEY_NAME; + +/** + * Get master key name encryption subcommand. + */ +public class GetMasterKeyNameCommand extends AbstractCommand { + /** {@inheritDoc} */ + @Override public Object execute(GridClientConfiguration clientCfg, Logger log) throws Exception { + try (GridClient client = Command.startClient(clientCfg)) { + String masterKeyName = executeTaskByNameOnNode( + client, + VisorGetMasterKeyNameTask.class.getName(), + null, + null, + clientCfg + ); + + log.info(masterKeyName); + + return masterKeyName; + } + catch (Throwable e) { + log.severe("Failed to perform operation."); + log.severe(CommandLogger.errorMessage(e)); + + throw e; + } + } + + /** {@inheritDoc} */ + @Override public Void arg() { + return null; + } + + /** {@inheritDoc} */ + @Override public void printUsage(Logger log) { + Command.usage(log, "Print the current master key name:", ENCRYPTION, GET_MASTER_KEY_NAME.toString()); + } + + /** {@inheritDoc} */ + @Override public String name() { + return GET_MASTER_KEY_NAME.text().toUpperCase(); + } +} diff --git a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/encryption/ReencryptionRateCommand.java b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/encryption/ReencryptionRateCommand.java new file mode 100644 index 0000000000000..7eb9f79304984 --- /dev/null +++ b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/encryption/ReencryptionRateCommand.java @@ -0,0 +1,137 @@ +/* + * 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.encryption; + +import java.util.Map; +import java.util.UUID; +import java.util.logging.Logger; +import org.apache.ignite.IgniteException; +import org.apache.ignite.internal.client.GridClient; +import org.apache.ignite.internal.client.GridClientConfiguration; +import org.apache.ignite.internal.commandline.AbstractCommand; +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.visor.encryption.VisorCacheGroupEncryptionTaskResult; +import org.apache.ignite.internal.visor.encryption.VisorReencryptionRateTask; +import org.apache.ignite.internal.visor.encryption.VisorReencryptionRateTaskArg; + +import static java.util.Collections.singletonMap; +import static org.apache.ignite.internal.commandline.CommandList.ENCRYPTION; +import static org.apache.ignite.internal.commandline.CommandLogger.DOUBLE_INDENT; +import static org.apache.ignite.internal.commandline.CommandLogger.INDENT; +import static org.apache.ignite.internal.commandline.CommandLogger.optional; +import static org.apache.ignite.internal.commandline.TaskExecutor.BROADCAST_UUID; +import static org.apache.ignite.internal.commandline.TaskExecutor.executeTaskByNameOnNode; +import static org.apache.ignite.internal.commandline.encryption.EncryptionSubcommands.REENCRYPTION_RATE; + +/** + * View/change cache group re-encryption rate limit subcommand. + */ +public class ReencryptionRateCommand extends AbstractCommand { + /** Re-encryption rate task argument. */ + private VisorReencryptionRateTaskArg taskArg; + + /** {@inheritDoc} */ + @Override public Object execute(GridClientConfiguration clientCfg, Logger log) throws Exception { + try (GridClient client = Command.startClient(clientCfg)) { + VisorCacheGroupEncryptionTaskResult res = executeTaskByNameOnNode( + client, + VisorReencryptionRateTask.class.getName(), + taskArg, + BROADCAST_UUID, + clientCfg + ); + + Map exceptions = res.exceptions(); + + for (Map.Entry entry : exceptions.entrySet()) { + log.info(INDENT + "Node " + entry.getKey() + ":"); + log.info(DOUBLE_INDENT + + "failed to get/set re-encryption rate limit: " + entry.getValue().getMessage()); + } + + Map results = res.results(); + boolean read = taskArg.rate() == null; + + for (Map.Entry entry : results.entrySet()) { + log.info(INDENT + "Node " + entry.getKey() + ":"); + + double rateLimit = read ? entry.getValue() : taskArg.rate(); + + if (rateLimit == 0) + log.info(DOUBLE_INDENT + "re-encryption rate is not limited."); + else { + log.info(String.format("%sre-encryption rate %s limited to %.2f MB/s.", + DOUBLE_INDENT, (read ? "is" : "has been"), rateLimit)); + } + } + + if (read) + return null; + + log.info(""); + log.info("Note: the changed value of the re-encryption rate limit is not persisted. " + + "When the node is restarted, the value will be set from the configuration."); + log.info(""); + + return null; + } + catch (Throwable e) { + log.severe("Failed to perform operation."); + log.severe(CommandLogger.errorMessage(e)); + + throw e; + } + } + + /** {@inheritDoc} */ + @Override public VisorReencryptionRateTaskArg arg() { + return taskArg; + } + + /** {@inheritDoc} */ + @Override public void parseArguments(CommandArgIterator argIter) { + Double rateLimit = null; + + while (argIter.hasNextSubArg()) { + String rateLimitArg = argIter.nextArg("Expected decimal value for re-encryption rate."); + + try { + rateLimit = Double.parseDouble(rateLimitArg); + } + catch (NumberFormatException e) { + throw new IllegalArgumentException("Failed to parse command argument. Decimal value expected.", e); + } + } + + taskArg = new VisorReencryptionRateTaskArg(rateLimit); + } + + /** {@inheritDoc} */ + @Override public void printUsage(Logger log) { + Command.usage(log, "View/change re-encryption rate limit:", ENCRYPTION, + singletonMap("new_limit", "Decimal value to change re-encryption rate limit (MB/s)."), + REENCRYPTION_RATE.toString(), optional("new_limit")); + } + + /** {@inheritDoc} */ + @Override public String name() { + return REENCRYPTION_RATE.text().toUpperCase(); + } +} diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerAbstractTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerAbstractTest.java index bc31ce71304a6..8dfbb371df405 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerAbstractTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerAbstractTest.java @@ -40,6 +40,7 @@ import org.apache.ignite.configuration.ConnectorConfiguration; import org.apache.ignite.configuration.DataRegionConfiguration; import org.apache.ignite.configuration.DataStorageConfiguration; +import org.apache.ignite.configuration.EncryptionConfiguration; import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.configuration.WALMode; import org.apache.ignite.internal.IgniteEx; @@ -68,6 +69,8 @@ import static java.util.Objects.nonNull; import static org.apache.ignite.IgniteSystemProperties.IGNITE_ENABLE_EXPERIMENTAL_COMMAND; import static org.apache.ignite.configuration.DataStorageConfiguration.DFLT_CHECKPOINT_FREQ; +import static org.apache.ignite.configuration.EncryptionConfiguration.DFLT_REENCRYPTION_BATCH_SIZE; +import static org.apache.ignite.configuration.EncryptionConfiguration.DFLT_REENCRYPTION_RATE_MBPS; import static org.apache.ignite.internal.encryption.AbstractEncryptionTest.KEYSTORE_PASSWORD; import static org.apache.ignite.internal.encryption.AbstractEncryptionTest.KEYSTORE_PATH; import static org.apache.ignite.internal.processors.cache.verify.VerifyBackupPartitionsDumpTask.IDLE_DUMP_FILE_PREFIX; @@ -85,6 +88,9 @@ public abstract class GridCommandHandlerAbstractTest extends GridCommonAbstractT /** */ protected static final String CLIENT_NODE_NAME_PREFIX = "client"; + /** */ + protected static final String DAEMON_NODE_NAME_PREFIX = "daemon"; + /** Option is used for auto confirmation. */ protected static final String CMD_AUTO_CONFIRMATION = "--yes"; @@ -113,7 +119,13 @@ public abstract class GridCommandHandlerAbstractTest extends GridCommonAbstractT protected boolean autoConfirmation = true; /** {@code True} if encription is enabled. */ - protected boolean encriptionEnabled; + protected boolean encryptionEnabled; + + /** Re-encryption rate limit in megabytes per second. */ + protected double reencryptSpeed = DFLT_REENCRYPTION_RATE_MBPS; + + /** The number of pages that is scanned during re-encryption under checkpoint lock. */ + protected int reencryptBatchSize = DFLT_REENCRYPTION_BATCH_SIZE; /** Last operation result. */ protected Object lastOperationResult; @@ -171,7 +183,7 @@ protected boolean persistenceEnable() { testOut.reset(); - encriptionEnabled = false; + encryptionEnabled = false; GridClientFactory.stopAll(false); } @@ -233,13 +245,22 @@ protected boolean idleVerifyRes(Path p) { cfg.setClientMode(igniteInstanceName.startsWith(CLIENT_NODE_NAME_PREFIX)); - if (encriptionEnabled) { + cfg.setDaemon(igniteInstanceName.startsWith(DAEMON_NODE_NAME_PREFIX)); + + if (encryptionEnabled) { KeystoreEncryptionSpi encSpi = new KeystoreEncryptionSpi(); encSpi.setKeyStorePath(KEYSTORE_PATH); encSpi.setKeyStorePassword(KEYSTORE_PASSWORD.toCharArray()); cfg.setEncryptionSpi(encSpi); + + EncryptionConfiguration encCfg = new EncryptionConfiguration(); + + encCfg.setReencryptionRateLimit(reencryptSpeed); + encCfg.setReencryptionBatchSize(reencryptBatchSize); + + dsCfg.setEncryptionConfiguration(encCfg); } return cfg; @@ -418,7 +439,8 @@ protected void createCacheAndPreload( CacheConfiguration ccfg = new CacheConfiguration<>(DEFAULT_CACHE_NAME) .setAffinity(new RendezvousAffinityFunction(false, partitions)) - .setBackups(1); + .setBackups(1) + .setEncryptionEnabled(encryptionEnabled); if (filter != null) ccfg.setNodeFilter(filter); diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java index 5557b5e5eb165..f95d79a535185 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java @@ -101,8 +101,10 @@ import org.apache.ignite.internal.processors.cache.warmup.WarmUpTestPluginProvider; import org.apache.ignite.internal.processors.cluster.GridClusterStateProcessor; import org.apache.ignite.internal.util.lang.GridAbsPredicate; +import org.apache.ignite.internal.util.lang.GridFunc; import org.apache.ignite.internal.util.typedef.G; import org.apache.ignite.internal.util.typedef.X; +import org.apache.ignite.internal.util.typedef.internal.CU; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.internal.visor.cache.VisorFindAndDeleteGarbageInPersistenceTaskResult; import org.apache.ignite.internal.visor.tx.VisorTxInfo; @@ -139,6 +141,12 @@ 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.CommandList.DEACTIVATE; +import static org.apache.ignite.internal.commandline.encryption.EncryptionSubcommands.CACHE_GROUP_KEY_IDS; +import static org.apache.ignite.internal.commandline.encryption.EncryptionSubcommands.CHANGE_CACHE_GROUP_KEY; +import static org.apache.ignite.internal.commandline.encryption.EncryptionSubcommands.REENCRYPTION_RATE; +import static org.apache.ignite.internal.commandline.encryption.EncryptionSubcommands.REENCRYPTION_RESUME; +import static org.apache.ignite.internal.commandline.encryption.EncryptionSubcommands.REENCRYPTION_STATUS; +import static org.apache.ignite.internal.commandline.encryption.EncryptionSubcommands.REENCRYPTION_SUSPEND; import static org.apache.ignite.internal.encryption.AbstractEncryptionTest.MASTER_KEY_NAME_2; import static org.apache.ignite.internal.processors.cache.persistence.GridCacheDatabaseSharedManager.IGNITE_PDS_SKIP_CHECKPOINT_ON_NODE_STOP; import static org.apache.ignite.internal.processors.cache.persistence.snapshot.AbstractSnapshotSelfTest.doSnapshotCancellationTest; @@ -2626,7 +2634,7 @@ public void testCacheIdleVerifyPrintLostPartitions() throws Exception { /** @throws Exception If failed. */ @Test public void testMasterKeyChange() throws Exception { - encriptionEnabled = true; + encryptionEnabled = true; injectTestSystemOut(); @@ -2661,10 +2669,189 @@ public void testMasterKeyChange() throws Exception { "Master key change was rejected. Unable to get the master key digest."); } + /** @throws Exception If failed. */ + @Test + public void testCacheGroupKeyChange() throws Exception { + encryptionEnabled = true; + + injectTestSystemOut(); + + int srvNodes = 2; + + IgniteEx ignite = startGrids(srvNodes); + + startGrid(CLIENT_NODE_NAME_PREFIX); + startGrid(DAEMON_NODE_NAME_PREFIX); + + ignite.cluster().state(ACTIVE); + + List srvGrids = GridFunc.asList(grid(0), grid(1)); + + enableCheckpoints(srvGrids, false); + + createCacheAndPreload(ignite, 1000); + + int ret = execute("--encryption", CACHE_GROUP_KEY_IDS.toString(), DEFAULT_CACHE_NAME); + + assertEquals(EXIT_CODE_OK, ret); + assertContains(log, testOut.toString(), "Encryption key identifiers for cache: " + DEFAULT_CACHE_NAME); + assertEquals(srvNodes, countSubstrs(testOut.toString(), "0 (active)")); + + ret = execute("--encryption", CHANGE_CACHE_GROUP_KEY.toString(), DEFAULT_CACHE_NAME); + + assertEquals(EXIT_CODE_OK, ret); + assertContains(log, testOut.toString(), + "The encryption key has been changed for the cache group \"" + DEFAULT_CACHE_NAME + '"'); + + ret = execute("--encryption", CACHE_GROUP_KEY_IDS.toString(), DEFAULT_CACHE_NAME); + + assertEquals(testOut.toString(), EXIT_CODE_OK, ret); + assertContains(log, testOut.toString(), "Encryption key identifiers for cache: " + DEFAULT_CACHE_NAME); + assertEquals(srvNodes, countSubstrs(testOut.toString(), "1 (active)")); + + GridTestUtils.waitForCondition(() -> { + execute("--encryption", REENCRYPTION_STATUS.toString(), DEFAULT_CACHE_NAME); + + return srvNodes == countSubstrs(testOut.toString(), + "re-encryption will be completed after the next checkpoint"); + }, getTestTimeout()); + + enableCheckpoints(srvGrids, true); + forceCheckpoint(srvGrids); + + GridTestUtils.waitForCondition(() -> { + execute("--encryption", REENCRYPTION_STATUS.toString(), DEFAULT_CACHE_NAME); + + return srvNodes == countSubstrs(testOut.toString(), "re-encryption completed or not required"); + }, getTestTimeout()); + } + + /** @throws Exception If failed. */ + @Test + public void testChangeReencryptionRate() throws Exception { + int srvNodes = 2; + + IgniteEx ignite = startGrids(srvNodes); + + ignite.cluster().state(ACTIVE); + + injectTestSystemOut(); + + int ret = execute("--encryption", REENCRYPTION_RATE.toString()); + + assertEquals(EXIT_CODE_OK, ret); + assertEquals(srvNodes, countSubstrs(testOut.toString(), "re-encryption rate is not limited.")); + + double newRate = 0.01; + + ret = execute("--encryption", REENCRYPTION_RATE.toString(), Double.toString(newRate)); + + assertEquals(EXIT_CODE_OK, ret); + assertEquals(srvNodes, countSubstrs(testOut.toString(), + String.format("re-encryption rate has been limited to %.2f MB/s.", newRate))); + + ret = execute("--encryption", REENCRYPTION_RATE.toString()); + + assertEquals(EXIT_CODE_OK, ret); + assertEquals(srvNodes, countSubstrs(testOut.toString(), + String.format("re-encryption rate is limited to %.2f MB/s.", newRate))); + + ret = execute("--encryption", REENCRYPTION_RATE.toString(), "0"); + + assertEquals(EXIT_CODE_OK, ret); + assertEquals(srvNodes, countSubstrs(testOut.toString(), "re-encryption rate is not limited.")); + } + + /** @throws Exception If failed. */ + @Test + public void testReencryptionSuspendAndResume() throws Exception { + encryptionEnabled = true; + reencryptSpeed = 0.01; + reencryptBatchSize = 1; + + int srvNodes = 2; + + IgniteEx ignite = startGrids(srvNodes); + + ignite.cluster().state(ACTIVE); + + injectTestSystemOut(); + + createCacheAndPreload(ignite, 10_000); + + ignite.encryption().changeCacheGroupKey(Collections.singleton(DEFAULT_CACHE_NAME)).get(); + + assertTrue(isReencryptionStarted(DEFAULT_CACHE_NAME)); + + int ret = execute("--encryption", REENCRYPTION_STATUS.toString(), DEFAULT_CACHE_NAME); + + assertEquals(EXIT_CODE_OK, ret); + + Pattern ptrn = Pattern.compile("(?m)Node [-0-9a-f]{36}:\n\\s+(?\\d+) KB of data.+"); + Matcher matcher = ptrn.matcher(testOut.toString()); + int matchesCnt = 0; + + while (matcher.find()) { + assertEquals(1, matcher.groupCount()); + + int pagesLeft = Integer.parseInt(matcher.group("left")); + + assertTrue(pagesLeft > 0); + + matchesCnt++; + } + + assertEquals(srvNodes, matchesCnt); + + ret = execute("--encryption", REENCRYPTION_SUSPEND.toString(), DEFAULT_CACHE_NAME); + + assertEquals(EXIT_CODE_OK, ret); + assertEquals(srvNodes, countSubstrs(testOut.toString(), + "re-encryption of the cache group \"" + DEFAULT_CACHE_NAME + "\" has been suspended.")); + assertFalse(isReencryptionStarted(DEFAULT_CACHE_NAME)); + + ret = execute("--encryption", REENCRYPTION_SUSPEND.toString(), DEFAULT_CACHE_NAME); + + assertEquals(EXIT_CODE_OK, ret); + assertEquals(srvNodes, countSubstrs(testOut.toString(), + "re-encryption of the cache group \"" + DEFAULT_CACHE_NAME + "\" has already been suspended.")); + + ret = execute("--encryption", REENCRYPTION_RESUME.toString(), DEFAULT_CACHE_NAME); + + assertEquals(EXIT_CODE_OK, ret); + assertEquals(srvNodes, countSubstrs(testOut.toString(), + "re-encryption of the cache group \"" + DEFAULT_CACHE_NAME + "\" has been resumed.")); + assertTrue(isReencryptionStarted(DEFAULT_CACHE_NAME)); + + ret = execute("--encryption", REENCRYPTION_RESUME.toString(), DEFAULT_CACHE_NAME); + + assertEquals(EXIT_CODE_OK, ret); + assertEquals(srvNodes, countSubstrs(testOut.toString(), + "re-encryption of the cache group \"" + DEFAULT_CACHE_NAME + "\" has already been resumed.")); + } + + /** + * @param cacheName Cache name. + * @return {@code True} if re-encryption of the specified cache is started on all server nodes. + */ + private boolean isReencryptionStarted(String cacheName) { + for (Ignite grid : G.allGrids()) { + ClusterNode locNode = grid.cluster().localNode(); + + if (locNode.isClient() || locNode.isDaemon()) + continue; + + if (((IgniteEx)grid).context().encryption().reencryptionFuture(CU.cacheId(cacheName)).isDone()) + return false; + } + + return true; + } + /** @throws Exception If failed. */ @Test public void testMasterKeyChangeOnInactiveCluster() throws Exception { - encriptionEnabled = true; + encryptionEnabled = true; injectTestSystemOut(); @@ -2834,4 +3021,18 @@ private VisorFindAndDeleteGarbageInPersistenceTaskResult executeTaskViaControlCo return hnd.getLastOperationResult(); } + + /** + * @param str String. + * @param substr Substring to find in the specified string. + * @return The number of substrings found in the specified string. + */ + private int countSubstrs(String str, String substr) { + int cnt = 0; + + for (int off = 0; (off = str.indexOf(substr, off)) != -1; off++) + ++cnt; + + return cnt; + } } diff --git a/modules/core/src/main/java/org/apache/ignite/configuration/EncryptionConfiguration.java b/modules/core/src/main/java/org/apache/ignite/configuration/EncryptionConfiguration.java index 79e205eb5386f..6b9345d4c7550 100644 --- a/modules/core/src/main/java/org/apache/ignite/configuration/EncryptionConfiguration.java +++ b/modules/core/src/main/java/org/apache/ignite/configuration/EncryptionConfiguration.java @@ -75,7 +75,7 @@ public double getReencryptionRateLimit() { */ public EncryptionConfiguration setReencryptionRateLimit(double reencryptionRateLimit) { A.ensure(reencryptionRateLimit >= 0, - "Reencryption rate limit (" + reencryptionRateLimit + ") must be non-negative."); + "Re-encryption rate limit (" + reencryptionRateLimit + ") must be non-negative."); this.reencryptionRateLimit = reencryptionRateLimit; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/encryption/CacheGroupPageScanner.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/encryption/CacheGroupPageScanner.java index dc0a29b05eb30..c2a68950425d3 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/managers/encryption/CacheGroupPageScanner.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/encryption/CacheGroupPageScanner.java @@ -25,6 +25,7 @@ import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.ReentrantLock; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteLogger; @@ -77,15 +78,15 @@ public class CacheGroupPageScanner implements CheckpointListener { /** Collection of groups waiting for a checkpoint. */ private final Collection cpWaitGrps = new ConcurrentLinkedQueue<>(); - /** Page scanning speed limiter. */ - private final BasicRateLimiter limiter; - /** Single-threaded executor to run cache group scan task. */ private final ThreadPoolExecutor singleExecSvc; /** Number of pages that is scanned during reencryption under checkpoint lock. */ private final int batchSize; + /** Page scanning speed limiter. */ + private final BasicRateLimiter limiter; + /** Stop flag. */ private boolean stopped; @@ -109,8 +110,7 @@ public CacheGroupPageScanner(GridKernalContext ctx) { double rateLimit = dsCfg.getEncryptionConfiguration().getReencryptionRateLimit(); - limiter = rateLimit > 0 ? new BasicRateLimiter(rateLimit * MB / - (dsCfg.getPageSize() == 0 ? DataStorageConfiguration.DFLT_PAGE_SIZE : dsCfg.getPageSize())) : null; + limiter = new BasicRateLimiter(calcPermits(rateLimit, dsCfg)); batchSize = dsCfg.getEncryptionConfiguration().getReencryptionBatchSize(); @@ -210,10 +210,13 @@ public IgniteInternalFuture schedule(int grpId) throws IgniteCheckedExcept } Set parts = new HashSet<>(); + long[] pagesLeft = new long[1]; forEachPageStore(grp, new IgniteInClosureX() { @Override public void applyx(Integer partId) { - if (ctx.encryption().getEncryptionState(grpId, partId) == 0) { + long encState = ctx.encryption().getEncryptionState(grpId, partId); + + if (encState == 0) { if (log.isDebugEnabled()) log.debug("Skipping partition reencryption [grp=" + grpId + ", p=" + partId + "]"); @@ -221,10 +224,12 @@ public IgniteInternalFuture schedule(int grpId) throws IgniteCheckedExcept } parts.add(partId); + + pagesLeft[0] += (ReencryptStateUtils.pageCount(encState) - ReencryptStateUtils.pageIndex(encState)); } }); - GroupScanTask grpScan = new GroupScanTask(grp, parts); + GroupScanTask grpScan = new GroupScanTask(grp, parts, pagesLeft[0]); singleExecSvc.submit(grpScan); @@ -313,6 +318,51 @@ public long[] pagesCount(CacheGroupContext grp) throws IgniteCheckedException { return partStates; } + /** + * @param grpId Cache group ID. + * @return Number of remaining memory pages to scan. + */ + public long remainingPagesCount(int grpId) { + GroupScanTask grpScanTask = grps.get(grpId); + + if (grpScanTask != null) + return grpScanTask.remainingPagesCount(); + + return 0; + } + + /** + * @return Re-encryption rate limit in megabytes per second ({@code 0} - unlimited). + */ + public double getRate() { + DataStorageConfiguration dsCfg = ctx.config().getDataStorageConfiguration(); + + if (CU.isPersistenceEnabled(dsCfg)) + return dsCfg.getPageSize() * limiter.getRate() / MB; + + return 0; + } + + /** + * @param rate Re-encryption rate limit in megabytes per second ({@code 0} - unlimited). + */ + public void setRate(double rate) { + DataStorageConfiguration dsCfg = ctx.config().getDataStorageConfiguration(); + + if (CU.isPersistenceEnabled(dsCfg)) + limiter.setRate(calcPermits(rate, dsCfg)); + } + + /** + * @param rate Maximum scan speed in megabytes per second + * @param dsCfg Datastorage configuration. + * @return The number of permits allowed per second. + */ + private double calcPermits(double rate, DataStorageConfiguration dsCfg) { + return rate * MB / + (dsCfg.getPageSize() == 0 ? DataStorageConfiguration.DFLT_PAGE_SIZE : dsCfg.getPageSize()); + } + /** * @param grp Cache group. * @param hnd Partition handler. @@ -345,13 +395,17 @@ private class GroupScanTask extends GridFutureAdapter implements Runnable /** Page memory. */ private final PageMemoryEx pageMem; + /** Total memory pages left for reencryption. */ + private final AtomicLong remainingPagesCntr; + /** * @param grp Cache group. */ - public GroupScanTask(CacheGroupContext grp, Set parts) { + public GroupScanTask(CacheGroupContext grp, Set parts, long remainingPagesCnt) { this.grp = grp; this.parts = new GridConcurrentHashSet<>(parts); + remainingPagesCntr = new AtomicLong(remainingPagesCnt); pageMem = (PageMemoryEx)grp.dataRegion().pageMemory(); } @@ -367,6 +421,10 @@ public GroupScanTask(CacheGroupContext grp, Set parts) { * @return {@code True} if reencryption was cancelled. */ public synchronized boolean excludePartition(int partId) { + long state = ctx.encryption().getEncryptionState(groupId(), partId); + + remainingPagesCntr.addAndGet(ReencryptStateUtils.pageIndex(state) - ReencryptStateUtils.pageCount(state)); + return parts.remove(partId); } @@ -377,6 +435,13 @@ public int groupId() { return grp.groupId(); } + /** + * @return Number of remaining memory pages to scan. + */ + public long remainingPagesCount() { + return remainingPagesCntr.get(); + } + /** {@inheritDoc} */ @Override public void run() { try { @@ -418,8 +483,7 @@ private void scanPartition(int partId, int off, int cnt) throws IgniteCheckedExc while (off < cnt) { int pagesCnt = Math.min(batchSize, cnt - off); - if (limiter != null) - limiter.acquire(pagesCnt); + limiter.acquire(pagesCnt); synchronized (this) { if (isDone() || !parts.contains(partId)) @@ -435,6 +499,8 @@ private void scanPartition(int partId, int off, int cnt) throws IgniteCheckedExc } } + remainingPagesCntr.addAndGet(-pagesCnt); + ctx.encryption().setEncryptionState(grp, partId, off, cnt); } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/encryption/GridEncryptionManager.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/encryption/GridEncryptionManager.java index 2c31dcdf7f34c..d0d467fefb123 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/managers/encryption/GridEncryptionManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/encryption/GridEncryptionManager.java @@ -823,6 +823,20 @@ public boolean reencryptionInProgress(int grpId) { return reencryptGroups.containsKey(grpId); } + /** + * @return Re-encryption rate limit in megabytes per second ({@code 0} - unlimited). + */ + public double getReencryptionRate() { + return pageScanner.getRate(); + } + + /** + * @param rate Re-encryption rate limit in megabytes per second ({@code 0} - unlimited). + */ + public void setReencryptionRate(double rate) { + pageScanner.setRate(rate); + } + /** * Removes encryption key(s). * @@ -1115,6 +1129,14 @@ public long getEncryptionState(int grpId, int partId) { return states[Math.min(partId, states.length - 1)]; } + /** + * @param grpId Cache group ID. + * @return The number of bytes left for re-ecryption. + */ + public long getBytesLeftForReencryption(int grpId) { + return pageScanner.remainingPagesCount(grpId) * ctx.config().getDataStorageConfiguration().getPageSize(); + } + /** * @param keyCnt Count of keys to generate. * @return Future that will contain results of generation. @@ -1159,6 +1181,32 @@ private void sendGenerateEncryptionKeyRequest(GenerateEncryptionKeyFuture fut) t ctx.io().sendToGridTopic(rndNode.id(), TOPIC_GEN_ENC_KEY, req, SYSTEM_POOL); } + /** + * Suspend re-encryption of the cache group. + * + * @param grpId Cache group ID. + */ + public boolean suspendReencryption(int grpId) throws IgniteCheckedException { + return reencryptionFuture(grpId).cancel(); + } + + /** + * Forces re-encryption of the cache group. + * + * @param grpId Cache group ID. + */ + public boolean resumeReencryption(int grpId) throws IgniteCheckedException { + if (!reencryptionFuture(grpId).isDone()) + return false; + + if (!reencryptionInProgress(grpId)) + throw new IgniteCheckedException("Re-encryption completed or not required [grpId=" + grpId + "]"); + + startReencryption(Collections.singleton(grpId)); + + return true; + } + /** * @param grpIds Cache group IDs. * @throws IgniteCheckedException If failed. diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheGroupMetricsImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheGroupMetricsImpl.java index 75fdd15c85d96..b7c0300b8defb 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheGroupMetricsImpl.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheGroupMetricsImpl.java @@ -25,15 +25,10 @@ import java.util.Map; import java.util.Set; import java.util.UUID; -import org.apache.ignite.IgniteCheckedException; -import org.apache.ignite.IgniteException; import org.apache.ignite.cache.CacheMode; import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.configuration.CacheConfiguration; import org.apache.ignite.configuration.DataStorageConfiguration; -import org.apache.ignite.internal.managers.encryption.GridEncryptionManager; -import org.apache.ignite.internal.managers.encryption.ReencryptStateUtils; -import org.apache.ignite.internal.pagemem.PageIdAllocator; import org.apache.ignite.internal.pagemem.store.PageStore; import org.apache.ignite.internal.processors.affinity.AffinityAssignment; import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion; @@ -44,7 +39,6 @@ import org.apache.ignite.internal.processors.cache.persistence.DataRegion; import org.apache.ignite.internal.processors.cache.persistence.DataRegionMetricsImpl; import org.apache.ignite.internal.processors.cache.persistence.GridCacheDatabaseSharedManager; -import org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager; import org.apache.ignite.internal.processors.metric.MetricRegistry; import org.apache.ignite.internal.processors.metric.impl.AtomicLongMetric; import org.apache.ignite.internal.processors.metric.impl.LongAdderMetric; @@ -187,9 +181,9 @@ public void onTopologyInitialized() { () -> !ctx.shared().kernalContext().encryption().reencryptionInProgress(ctx.groupId()), "The flag indicates whether reencryption is finished or not."); - mreg.register("ReencryptionPagesLeft", - this::getPagesLeftForReencryption, - "Number of pages left for reencryption."); + mreg.register("ReencryptionBytesLeft", + () -> ctx.shared().kernalContext().encryption().getBytesLeftForReencryption(ctx.groupId()), + "The number of bytes left for re-ecryption."); } } @@ -502,40 +496,6 @@ public long getSparseStorageSize() { return sparseStorageSize == null ? 0 : sparseStorageSize.value(); } - /** */ - public long getPagesLeftForReencryption() { - if (!ctx.shared().kernalContext().encryption().reencryptionInProgress(ctx.groupId())) - return 0; - - long pagesLeft = 0; - - FilePageStoreManager mgr = (FilePageStoreManager)ctx.shared().pageStore(); - - GridEncryptionManager encMgr = ctx.shared().kernalContext().encryption(); - - try { - for (int p = 0; p < ctx.affinity().partitions(); p++) { - PageStore pageStore = mgr.getStore(ctx.groupId(), p); - - if (!pageStore.exists()) - continue; - - long state = encMgr.getEncryptionState(ctx.groupId(), p); - - pagesLeft += ReencryptStateUtils.pageCount(state) - ReencryptStateUtils.pageIndex(state); - } - - long state = encMgr.getEncryptionState(ctx.groupId(), PageIdAllocator.INDEX_PARTITION); - - pagesLeft += ReencryptStateUtils.pageCount(state) - ReencryptStateUtils.pageIndex(state); - } - catch (IgniteCheckedException e) { - throw new IgniteException(e); - } - - return pagesLeft; - } - /** Removes all metric for cache group. */ public void remove() { if (ctx.shared().kernalContext().isStopping()) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/BasicRateLimiter.java b/modules/core/src/main/java/org/apache/ignite/internal/util/BasicRateLimiter.java index 8429ccbbcb346..195264e6b1b71 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/util/BasicRateLimiter.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/BasicRateLimiter.java @@ -64,6 +64,11 @@ public class BasicRateLimiter { */ private long nextFreeTicketMicros; + /** + * The flag indicates that the rate is not limited. + */ + private volatile boolean unlimited; + /** * @param permitsPerSecond Estimated number of permits per second. */ @@ -74,11 +79,14 @@ public BasicRateLimiter(double permitsPerSecond) { /** * Updates the stable rate. * - * @param permitsPerSecond The new stable rate of this {@code RateLimiter}. + * @param permitsPerSecond The new stable rate of this {@code RateLimiter}, set {@code 0} for unlimited rate. * @throws IllegalArgumentException If {@code permitsPerSecond} is negative or zero. */ public void setRate(double permitsPerSecond) { - A.ensure(permitsPerSecond > 0, "Requested permits (" + permitsPerSecond + ") must be positive"); + A.ensure(permitsPerSecond >= 0, "Requested permits (" + permitsPerSecond + ") must be non-negative."); + + if (unlimited = (permitsPerSecond == 0)) + return; synchronized (mux) { resync(); @@ -88,9 +96,12 @@ public void setRate(double permitsPerSecond) { } /** - * @return The stable rate (as {@code permits per seconds}). + * @return The stable rate as {@code permits per seconds} ({@code 0} means that the rate is unlimited). */ public double getRate() { + if (unlimited) + return 0; + synchronized (mux) { return SECONDS.toMicros(1L) / stableIntervalMicros; } @@ -104,6 +115,9 @@ public double getRate() { * @throws IllegalArgumentException If the requested number of permits is negative or zero. */ public void acquire(int permits) throws IgniteInterruptedCheckedException { + if (unlimited) + return; + long microsToWait = reserve(permits); try { diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/encryption/VisorCacheGroupEncryptionTask.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/encryption/VisorCacheGroupEncryptionTask.java new file mode 100644 index 0000000000000..37d5096ef1c7e --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/encryption/VisorCacheGroupEncryptionTask.java @@ -0,0 +1,137 @@ +/* + * 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.visor.encryption; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.IgniteException; +import org.apache.ignite.compute.ComputeJobResult; +import org.apache.ignite.internal.dto.IgniteDataTransferObject; +import org.apache.ignite.internal.processors.cache.CacheGroupContext; +import org.apache.ignite.internal.processors.cache.IgniteInternalCache; +import org.apache.ignite.internal.util.typedef.internal.CU; +import org.apache.ignite.internal.visor.VisorJob; +import org.apache.ignite.internal.visor.VisorMultiNodeTask; +import org.jetbrains.annotations.Nullable; + +/** + * Visor encrypted cache group multinode task. + * + * @param The type of the task result. + */ +public abstract class VisorCacheGroupEncryptionTask extends VisorMultiNodeTask, VisorCacheGroupEncryptionTask.VisorSingleFieldDto> +{ + /** {@inheritDoc} */ + @Nullable @Override protected VisorCacheGroupEncryptionTaskResult reduce0(List results) { + Map jobResults = new HashMap<>(); + Map exceptions = new HashMap<>(); + + for (ComputeJobResult res : results) { + UUID nodeId = res.getNode().id(); + + if (res.getException() != null) { + exceptions.put(nodeId, res.getException()); + + continue; + } + + VisorSingleFieldDto dtoRes = res.getData(); + + jobResults.put(nodeId, dtoRes.value()); + } + + return new VisorCacheGroupEncryptionTaskResult<>(jobResults, exceptions); + } + + /** */ + protected abstract static class VisorSingleFieldDto extends IgniteDataTransferObject { + /** Object value. */ + private T val; + + /** + * @return Object value. + */ + protected T value() { + return val; + } + + /** + * @param val Data object. + * @return {@code this} for chaining. + */ + protected VisorSingleFieldDto value(T val) { + this.val = val; + + return this; + } + } + + /** + * @param Type of job result. + */ + protected abstract static class VisorReencryptionBaseJob + extends VisorJob> { + /** + * @param arg Job argument. + * @param debug Flag indicating whether debug information should be printed into node log. + */ + protected VisorReencryptionBaseJob(@Nullable VisorCacheGroupEncryptionTaskArg arg, boolean debug) { + super(arg, debug); + } + + /** {@inheritDoc} */ + @Override protected VisorSingleFieldDto run(VisorCacheGroupEncryptionTaskArg arg) throws IgniteException { + try { + String grpName = arg.groupName(); + CacheGroupContext grp = ignite.context().cache().cacheGroup(CU.cacheId(grpName)); + + if (grp == null) { + IgniteInternalCache cache = ignite.context().cache().cache(grpName); + + if (cache == null) + throw new IgniteException("Cache group " + grpName + " not found."); + + grp = cache.context().group(); + + if (grp.sharedGroup()) { + throw new IgniteException("Cache or group \"" + grpName + "\" is a part of group \"" + + grp.name() + "\". Provide group name instead of cache name for shared groups."); + } + } + + return run0(grp); + } + catch (IgniteCheckedException e) { + throw new IgniteException(e); + } + } + + /** + * Executes internal logic of the job. + * + * @param grp Cache group. + * @return Result. + * @throws IgniteCheckedException In case of error. + */ + protected abstract VisorSingleFieldDto run0(CacheGroupContext grp) throws IgniteCheckedException; + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/encryption/VisorCacheGroupEncryptionTaskArg.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/encryption/VisorCacheGroupEncryptionTaskArg.java new file mode 100644 index 0000000000000..ecea9ed090709 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/encryption/VisorCacheGroupEncryptionTaskArg.java @@ -0,0 +1,68 @@ +/* + * 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.visor.encryption; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import org.apache.ignite.internal.dto.IgniteDataTransferObject; +import org.apache.ignite.internal.util.typedef.internal.S; +import org.apache.ignite.internal.util.typedef.internal.U; + +/** + * Cache group aware task argument. + */ +public class VisorCacheGroupEncryptionTaskArg extends IgniteDataTransferObject { + /** */ + private static final long serialVersionUID = 0L; + + /** Cache group name. */ + private String grpName; + + /** Default constructor. */ + public VisorCacheGroupEncryptionTaskArg() { + // No-op. + } + + /** + * @param grpName Cache group name. + */ + public VisorCacheGroupEncryptionTaskArg(String grpName) { + this.grpName = grpName; + } + + /** @return Cache group name. */ + public String groupName() { + return grpName; + } + + /** {@inheritDoc} */ + @Override protected void writeExternalData(ObjectOutput out) throws IOException { + U.writeString(out, grpName); + } + + /** {@inheritDoc} */ + @Override protected void readExternalData(byte ver, ObjectInput in) throws IOException, ClassNotFoundException { + grpName = U.readString(in); + } + + /** {@inheritDoc} */ + @Override public String toString() { + return S.toString(VisorCacheGroupEncryptionTaskArg.class, this); + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/encryption/VisorCacheGroupEncryptionTaskResult.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/encryption/VisorCacheGroupEncryptionTaskResult.java new file mode 100644 index 0000000000000..f29a99b295742 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/encryption/VisorCacheGroupEncryptionTaskResult.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.visor.encryption; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.util.Collections; +import java.util.Map; +import java.util.UUID; +import org.apache.ignite.IgniteException; +import org.apache.ignite.internal.dto.IgniteDataTransferObject; +import org.apache.ignite.internal.util.tostring.GridToStringInclude; +import org.apache.ignite.internal.util.typedef.internal.S; +import org.apache.ignite.internal.util.typedef.internal.U; + +/** + * Multinode cache group encryption task result. + * + * @param Job result type. + */ +@SuppressWarnings("AssignmentOrReturnOfFieldWithMutableType") +public class VisorCacheGroupEncryptionTaskResult extends IgniteDataTransferObject { + /** */ + private static final long serialVersionUID = 0L; + + /** Per node job result. */ + @GridToStringInclude + private Map results; + + /** Per node execution problems. */ + @GridToStringInclude + private Map exceptions; + + /** + * @param results Per node job result. + * @param exceptions Per node execution problems. + */ + public VisorCacheGroupEncryptionTaskResult(Map results, Map exceptions) { + this.results = results; + this.exceptions = exceptions; + } + + /** */ + public VisorCacheGroupEncryptionTaskResult() { + // No-op. + } + + /** @return Per node job result. */ + public Map results() { + return results == null ? Collections.emptyMap() : results; + } + + /** @return Per node execution problems. */ + public Map exceptions() { + return exceptions == null ? Collections.emptyMap() : exceptions; + } + + /** {@inheritDoc} */ + @Override protected void writeExternalData(ObjectOutput out) throws IOException { + U.writeMap(out, results); + U.writeMap(out, exceptions); + } + + /** {@inheritDoc} */ + @Override protected void readExternalData(byte ver, ObjectInput in) throws IOException, ClassNotFoundException { + results = U.readMap(in); + exceptions = U.readMap(in); + } + + /** {@inheritDoc} */ + @Override public String toString() { + return S.toString(VisorCacheGroupEncryptionTaskResult.class, this); + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/encryption/VisorChangeCacheGroupKeyTask.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/encryption/VisorChangeCacheGroupKeyTask.java new file mode 100644 index 0000000000000..d6659412f6427 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/encryption/VisorChangeCacheGroupKeyTask.java @@ -0,0 +1,61 @@ +/* + * 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.visor.encryption; + +import java.util.Collection; +import java.util.Collections; +import org.apache.ignite.IgniteEncryption; +import org.apache.ignite.IgniteException; +import org.apache.ignite.internal.visor.VisorJob; +import org.apache.ignite.internal.visor.VisorOneNodeTask; + +/** + * The task for changing the encryption key of the cache group. + * + * @see IgniteEncryption#changeCacheGroupKey(Collection) + */ +public class VisorChangeCacheGroupKeyTask extends VisorOneNodeTask { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** {@inheritDoc} */ + @Override protected VisorJob job(VisorCacheGroupEncryptionTaskArg arg) { + return new VisorChangeCacheGroupKeyJob(arg, debug); + } + + /** The job for changing the encryption key of the cache group. */ + private static class VisorChangeCacheGroupKeyJob extends VisorJob { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** + * @param arg Job argument. + * @param debug Flag indicating whether debug information should be printed into node log. + */ + protected VisorChangeCacheGroupKeyJob(VisorCacheGroupEncryptionTaskArg arg, boolean debug) { + super(arg, debug); + } + + /** {@inheritDoc} */ + @Override protected Void run(VisorCacheGroupEncryptionTaskArg taskArg) throws IgniteException { + ignite.encryption().changeCacheGroupKey(Collections.singleton(taskArg.groupName())).get(); + + return null; + } + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/encryption/VisorEncryptionKeyIdsTask.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/encryption/VisorEncryptionKeyIdsTask.java new file mode 100644 index 0000000000000..ca5a25475fa90 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/encryption/VisorEncryptionKeyIdsTask.java @@ -0,0 +1,83 @@ +/* + * 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.visor.encryption; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.util.List; +import org.apache.ignite.internal.processors.cache.CacheGroupContext; +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.jetbrains.annotations.Nullable; + +/** + * Get current encryption key IDs of the cache group. + */ +@GridInternal +public class VisorEncryptionKeyIdsTask extends VisorCacheGroupEncryptionTask> { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** {@inheritDoc} */ + @Override protected VisorJob>> job( + VisorCacheGroupEncryptionTaskArg arg) { + return new VisorEncryptionKeyIdsJob(arg, debug); + } + + /** The job for get current encryption key IDs of the cache group. */ + private static class VisorEncryptionKeyIdsJob extends VisorReencryptionBaseJob> { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** + * @param arg Job argument. + * @param debug Flag indicating whether debug information should be printed into node log. + */ + protected VisorEncryptionKeyIdsJob(@Nullable VisorCacheGroupEncryptionTaskArg arg, boolean debug) { + super(arg, debug); + } + + /** {@inheritDoc} */ + @Override protected VisorSingleFieldDto> run0(CacheGroupContext grp) { + return new VisorEncryptionKeyIdsResult().value(ignite.context().encryption().groupKeyIds(grp.groupId())); + } + } + + /** */ + protected static class VisorEncryptionKeyIdsResult extends VisorSingleFieldDto> { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** */ + public VisorEncryptionKeyIdsResult() { + // No-op. + } + + /** {@inheritDoc} */ + @Override protected void writeExternalData(ObjectOutput out) throws IOException { + U.writeCollection(out, value()); + } + + /** {@inheritDoc} */ + @Override protected void readExternalData(byte ver, ObjectInput in) throws IOException, ClassNotFoundException { + value(U.readList(in)); + } + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/encryption/VisorReencryptionRateTask.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/encryption/VisorReencryptionRateTask.java new file mode 100644 index 0000000000000..425fab52eb2e2 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/encryption/VisorReencryptionRateTask.java @@ -0,0 +1,133 @@ +/* + * 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.visor.encryption; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.apache.ignite.IgniteException; +import org.apache.ignite.compute.ComputeJobResult; +import org.apache.ignite.internal.dto.IgniteDataTransferObject; +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; + +/** + * View/change cache group re-encryption rate limit . + */ +@GridInternal +public class VisorReencryptionRateTask extends VisorMultiNodeTask, VisorReencryptionRateTask.ReencryptionRateJobResult> +{ + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** {@inheritDoc} */ + @Override protected VisorJob job( + VisorReencryptionRateTaskArg arg) { + return new VisorReencryptionRateJob(arg, debug); + } + + /** {@inheritDoc} */ + @Nullable @Override protected VisorCacheGroupEncryptionTaskResult reduce0(List results) { + Map jobResults = new HashMap<>(); + Map exceptions = new HashMap<>(); + + for (ComputeJobResult res : results) { + UUID nodeId = res.getNode().id(); + + if (res.getException() != null) { + exceptions.put(nodeId, res.getException()); + + continue; + } + + ReencryptionRateJobResult dtoRes = res.getData(); + + jobResults.put(nodeId, dtoRes.limit()); + } + + return new VisorCacheGroupEncryptionTaskResult<>(jobResults, exceptions); + } + + /** The job for view/change cache group re-encryption rate limit. */ + private static class VisorReencryptionRateJob + extends VisorJob { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** + * @param arg Job argument. + * @param debug Flag indicating whether debug information should be printed into node log. + */ + protected VisorReencryptionRateJob(VisorReencryptionRateTaskArg arg, boolean debug) { + super(arg, debug); + } + + /** {@inheritDoc} */ + @Override protected ReencryptionRateJobResult run(VisorReencryptionRateTaskArg arg) throws IgniteException { + double prevRate = ignite.context().encryption().getReencryptionRate(); + + if (arg.rate() != null) + ignite.context().encryption().setReencryptionRate(arg.rate()); + + return new ReencryptionRateJobResult(prevRate); + } + } + + /** */ + protected static class ReencryptionRateJobResult extends IgniteDataTransferObject { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** Re-encryption rate limit. */ + private Double limit; + + /** */ + public ReencryptionRateJobResult() { + // No-op. + } + + /** */ + public ReencryptionRateJobResult(Double limit) { + this.limit = limit; + } + + /** + * @return Re-encryption rate limit. + */ + public Double limit() { + return limit; + } + + /** {@inheritDoc} */ + @Override protected void writeExternalData(ObjectOutput out) throws IOException { + out.writeDouble(limit); + } + + /** {@inheritDoc} */ + @Override protected void readExternalData(byte ver, ObjectInput in) throws IOException, ClassNotFoundException { + limit = in.readDouble(); + } + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/encryption/VisorReencryptionRateTaskArg.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/encryption/VisorReencryptionRateTaskArg.java new file mode 100644 index 0000000000000..2471296fcc65a --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/encryption/VisorReencryptionRateTaskArg.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.visor.encryption; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import org.apache.ignite.internal.dto.IgniteDataTransferObject; +import org.apache.ignite.internal.util.typedef.internal.S; +import org.jetbrains.annotations.Nullable; + +/** + * Re-encryption rate task argument. + */ +public class VisorReencryptionRateTaskArg extends IgniteDataTransferObject { + /** */ + private static final long serialVersionUID = 0L; + + /** Re-encryption rate limit in megabytes per second. */ + private Double rate; + + /** Default constructor. */ + public VisorReencryptionRateTaskArg() { + // No-op. + } + + /** + * @param rate Re-encryption rate limit in megabytes per second. + */ + public VisorReencryptionRateTaskArg(Double rate) { + this.rate = rate; + } + + /** + * @return Re-encryption rate limit in megabytes per second. + */ + public @Nullable Double rate() { + return rate; + } + + /** {@inheritDoc} */ + @Override protected void writeExternalData(ObjectOutput out) throws IOException { + out.writeBoolean(rate != null); + + if (rate != null) + out.writeDouble(rate); + } + + /** {@inheritDoc} */ + @Override protected void readExternalData(byte ver, ObjectInput in) throws IOException, ClassNotFoundException { + if (in.readBoolean()) + rate = in.readDouble(); + } + + /** {@inheritDoc} */ + @Override public String toString() { + return S.toString(VisorReencryptionRateTaskArg.class, this); + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/encryption/VisorReencryptionResumeTask.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/encryption/VisorReencryptionResumeTask.java new file mode 100644 index 0000000000000..171130c26d6df --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/encryption/VisorReencryptionResumeTask.java @@ -0,0 +1,60 @@ +/* + * 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.visor.encryption; + +import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.internal.processors.cache.CacheGroupContext; +import org.apache.ignite.internal.processors.task.GridInternal; +import org.apache.ignite.internal.visor.VisorJob; +import org.apache.ignite.internal.visor.encryption.VisorReencryptionSuspendTask.VisorReencryptionSuspendResumeJobResult; +import org.jetbrains.annotations.Nullable; + +/** + * Resume re-encryption of the cache group. + */ +@GridInternal +public class VisorReencryptionResumeTask extends VisorCacheGroupEncryptionTask { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** {@inheritDoc} */ + @Override protected VisorJob> job( + VisorCacheGroupEncryptionTaskArg arg) { + return new VisorReencryptionResumeJob(arg, debug); + } + + /** The job to resume re-encryption of the cache group. */ + private static class VisorReencryptionResumeJob extends VisorReencryptionBaseJob { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** + * @param arg Job argument. + * @param debug Flag indicating whether debug information should be printed into node log. + */ + protected VisorReencryptionResumeJob(@Nullable VisorCacheGroupEncryptionTaskArg arg, boolean debug) { + super(arg, debug); + } + + /** {@inheritDoc} */ + @Override protected VisorSingleFieldDto run0(CacheGroupContext grp) throws IgniteCheckedException { + return new VisorReencryptionSuspendResumeJobResult().value( + ignite.context().encryption().resumeReencryption(grp.groupId())); + } + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/encryption/VisorReencryptionStatusTask.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/encryption/VisorReencryptionStatusTask.java new file mode 100644 index 0000000000000..df6004747382b --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/encryption/VisorReencryptionStatusTask.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.visor.encryption; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import org.apache.ignite.internal.processors.cache.CacheGroupContext; +import org.apache.ignite.internal.processors.task.GridInternal; +import org.apache.ignite.internal.visor.VisorJob; +import org.jetbrains.annotations.Nullable; + +/** + * Get re-encryption status of the cache group. + */ +@GridInternal +public class VisorReencryptionStatusTask extends VisorCacheGroupEncryptionTask { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** {@inheritDoc} */ + @Override protected VisorJob> job( + VisorCacheGroupEncryptionTaskArg arg) { + return new VisorReencryptionStatusJob(arg, debug); + } + + /** The job to get re-encryption status of the cache group. */ + private static class VisorReencryptionStatusJob extends VisorReencryptionBaseJob { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** + * @param arg Job argument. + * @param debug Flag indicating whether debug information should be printed into node log. + */ + protected VisorReencryptionStatusJob(@Nullable VisorCacheGroupEncryptionTaskArg arg, boolean debug) { + super(arg, debug); + } + + /** {@inheritDoc} */ + @Override protected VisorSingleFieldDto run0(CacheGroupContext grp) { + long res; + + if (!ignite.context().encryption().reencryptionInProgress(grp.groupId())) + res = -1; + else + res = ignite.context().encryption().getBytesLeftForReencryption(grp.groupId()); + + return new VisorReencryptionStatusResult().value(res); + } + } + + /** */ + protected static class VisorReencryptionStatusResult extends VisorSingleFieldDto { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** */ + public VisorReencryptionStatusResult() { + // No-op. + } + + /** {@inheritDoc} */ + @Override protected void writeExternalData(ObjectOutput out) throws IOException { + out.writeLong(value()); + } + + /** {@inheritDoc} */ + @Override protected void readExternalData(byte ver, ObjectInput in) throws IOException, ClassNotFoundException { + value(in.readLong()); + } + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/encryption/VisorReencryptionSuspendTask.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/encryption/VisorReencryptionSuspendTask.java new file mode 100644 index 0000000000000..edbfd464893c9 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/encryption/VisorReencryptionSuspendTask.java @@ -0,0 +1,83 @@ +/* + * 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.visor.encryption; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.internal.processors.cache.CacheGroupContext; +import org.apache.ignite.internal.processors.task.GridInternal; +import org.apache.ignite.internal.visor.VisorJob; +import org.jetbrains.annotations.Nullable; + +/** + * Suspend re-encryption of the cache group. + */ +@GridInternal +public class VisorReencryptionSuspendTask extends VisorCacheGroupEncryptionTask { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** {@inheritDoc} */ + @Override protected VisorJob> job( + VisorCacheGroupEncryptionTaskArg arg) { + return new VisorReencryptionSuspendJob(arg, debug); + } + + /** The job to suspend re-encryption of the cache group. */ + private static class VisorReencryptionSuspendJob extends VisorReencryptionBaseJob { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** + * @param arg Job argument. + * @param debug Flag indicating whether debug information should be printed into node log. + */ + protected VisorReencryptionSuspendJob(@Nullable VisorCacheGroupEncryptionTaskArg arg, boolean debug) { + super(arg, debug); + } + + /** {@inheritDoc} */ + @Override protected VisorSingleFieldDto run0(CacheGroupContext grp) throws IgniteCheckedException { + return new VisorReencryptionSuspendResumeJobResult().value( + ignite.context().encryption().suspendReencryption(grp.groupId())); + } + } + + /** */ + protected static class VisorReencryptionSuspendResumeJobResult extends VisorSingleFieldDto { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** */ + public VisorReencryptionSuspendResumeJobResult() { + // No-op. + } + + /** {@inheritDoc} */ + @Override protected void writeExternalData(ObjectOutput out) throws IOException { + out.writeBoolean(value()); + } + + /** {@inheritDoc} */ + @Override protected void readExternalData(byte ver, ObjectInput in) throws IOException, ClassNotFoundException { + value(in.readBoolean()); + } + } +} diff --git a/modules/core/src/main/resources/META-INF/classnames.properties b/modules/core/src/main/resources/META-INF/classnames.properties index 92d01dda9d92d..3ec37dd7bd8ff 100644 --- a/modules/core/src/main/resources/META-INF/classnames.properties +++ b/modules/core/src/main/resources/META-INF/classnames.properties @@ -2309,6 +2309,25 @@ org.apache.ignite.internal.visor.encryption.VisorGetMasterKeyNameTask org.apache.ignite.internal.visor.encryption.VisorGetMasterKeyNameTask$VisorGetMasterKeyNameJob org.apache.ignite.internal.visor.encryption.VisorChangeMasterKeyTask org.apache.ignite.internal.visor.encryption.VisorChangeMasterKeyTask$VisorChangeMasterKeyJob +org.apache.ignite.internal.visor.encryption.VisorCacheGroupEncryptionTaskArg +org.apache.ignite.internal.visor.encryption.VisorCacheGroupEncryptionTaskResult +org.apache.ignite.internal.visor.encryption.VisorChangeCacheGroupKeyTask +org.apache.ignite.internal.visor.encryption.VisorChangeCacheGroupKeyTask$VisorChangeCacheGroupKeyJob +org.apache.ignite.internal.visor.encryption.VisorEncryptionKeyIdsTask +org.apache.ignite.internal.visor.encryption.VisorEncryptionKeyIdsTask$VisorEncryptionKeyIdsJob +org.apache.ignite.internal.visor.encryption.VisorEncryptionKeyIdsTask$VisorEncryptionKeyIdsResult +org.apache.ignite.internal.visor.encryption.VisorReencryptionRateTask +org.apache.ignite.internal.visor.encryption.VisorReencryptionRateTask$VisorReencryptionRateJob +org.apache.ignite.internal.visor.encryption.VisorReencryptionRateTask$ReencryptionRateJobResult +org.apache.ignite.internal.visor.encryption.VisorReencryptionRateTaskArg +org.apache.ignite.internal.visor.encryption.VisorReencryptionResumeTask +org.apache.ignite.internal.visor.encryption.VisorReencryptionResumeTask$VisorReencryptionResumeJob +org.apache.ignite.internal.visor.encryption.VisorReencryptionStatusTask +org.apache.ignite.internal.visor.encryption.VisorReencryptionStatusTask$VisorReencryptionStatusJob +org.apache.ignite.internal.visor.encryption.VisorReencryptionStatusTask$VisorReencryptionStatusResult +org.apache.ignite.internal.visor.encryption.VisorReencryptionSuspendTask +org.apache.ignite.internal.visor.encryption.VisorReencryptionSuspendTask$VisorReencryptionSuspendJob +org.apache.ignite.internal.visor.encryption.VisorReencryptionSuspendTask$VisorReencryptionSuspendResumeJobResult org.apache.ignite.internal.visor.util.VisorClusterGroupEmptyException org.apache.ignite.internal.visor.util.VisorEventMapper org.apache.ignite.internal.visor.util.VisorExceptionWrapper diff --git a/modules/core/src/test/java/org/apache/ignite/internal/encryption/CacheGroupReencryptionTest.java b/modules/core/src/test/java/org/apache/ignite/internal/encryption/CacheGroupReencryptionTest.java index 1f7cf36aaedab..19c8351184175 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/encryption/CacheGroupReencryptionTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/encryption/CacheGroupReencryptionTest.java @@ -385,7 +385,7 @@ public void testPartitionEvictionDuringReencryption() throws Exception { @Test public void testPartitionFileDestroy() throws Exception { backups = 1; - pageScanRate = 1; + pageScanRate = 0.2; pageScanBatchSize = 10; T2 nodes = startTestGrids(true); @@ -409,6 +409,10 @@ public void testPartitionFileDestroy() throws Exception { assertTrue(isReencryptionInProgress(Collections.singleton(cacheName()))); + // Set unlimited re-encryption rate. + nodes.get1().context().encryption().setReencryptionRate(0); + nodes.get2().context().encryption().setReencryptionRate(0); + checkGroupKey(CU.cacheId(cacheName()), INITIAL_KEY_ID + 1, MAX_AWAIT_MILLIS); } @@ -758,12 +762,12 @@ private void validateMetrics(IgniteEx node, boolean finished) { MetricRegistry registry = node.context().metric().registry(metricName(CacheGroupMetricsImpl.CACHE_GROUP_METRICS_PREFIX, cacheName())); - LongMetric pagesLeft = registry.findMetric("ReencryptionPagesLeft"); + LongMetric bytesLeft = registry.findMetric("ReencryptionBytesLeft"); if (finished) - assertEquals(0, pagesLeft.value()); + assertEquals(0, bytesLeft.value()); else - assertTrue(pagesLeft.value() > 0); + assertTrue(bytesLeft.value() > 0); BooleanMetric reencryptionFinished = registry.findMetric("ReencryptionFinished"); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/util/BasicRateLimiterTest.java b/modules/core/src/test/java/org/apache/ignite/internal/util/BasicRateLimiterTest.java index c136cb8f9db5b..9f4bca931df50 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/util/BasicRateLimiterTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/util/BasicRateLimiterTest.java @@ -67,6 +67,21 @@ private void checkRate(BasicRateLimiter limiter, int totalOps) throws IgniteInte assertEquals(1, Math.round((double)timeSpent / 1000 / totalOps * permitsPerSec)); } + /** + * Check that the rate can be set as unlimited. + */ + @Test + public void testUnlimitedRate() throws IgniteInterruptedCheckedException { + BasicRateLimiter limiter = new BasicRateLimiter(0); + limiter.acquire(Integer.MAX_VALUE); + + limiter.setRate(1); + limiter.acquire(1); + + limiter.setRate(0); + limiter.acquire(Integer.MAX_VALUE); + } + /** * Check rate limit with multiple threads. */ 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 5950ed1f56943..f2c2ad20f6d89 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 @@ -71,6 +71,27 @@ This utility can do the following commands: Change the master key: control.(sh|bat) --encryption change_master_key newMasterKeyName + Change the encryption key of the cache group: + control.(sh|bat) --encryption change_cache_key cacheGroupName + + View encryption key identifiers of the cache group: + control.(sh|bat) --encryption cache_key_ids cacheGroupName + + Display re-encryption status of the cache group: + control.(sh|bat) --encryption reencryption_status cacheGroupName + + Suspend re-encryption of the cache group: + control.(sh|bat) --encryption suspend_reencryption cacheGroupName + + Resume re-encryption of the cache group: + control.(sh|bat) --encryption resume_reencryption cacheGroupName + + View/change re-encryption rate limit: + control.(sh|bat) --encryption reencryption_rate_limit [new_limit] + + Parameters: + new_limit - Decimal value to change re-encryption rate limit (MB/s). + Kill compute task by session id: control.(sh|bat) --kill COMPUTE session_id 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 5950ed1f56943..f2c2ad20f6d89 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 @@ -71,6 +71,27 @@ This utility can do the following commands: Change the master key: control.(sh|bat) --encryption change_master_key newMasterKeyName + Change the encryption key of the cache group: + control.(sh|bat) --encryption change_cache_key cacheGroupName + + View encryption key identifiers of the cache group: + control.(sh|bat) --encryption cache_key_ids cacheGroupName + + Display re-encryption status of the cache group: + control.(sh|bat) --encryption reencryption_status cacheGroupName + + Suspend re-encryption of the cache group: + control.(sh|bat) --encryption suspend_reencryption cacheGroupName + + Resume re-encryption of the cache group: + control.(sh|bat) --encryption resume_reencryption cacheGroupName + + View/change re-encryption rate limit: + control.(sh|bat) --encryption reencryption_rate_limit [new_limit] + + Parameters: + new_limit - Decimal value to change re-encryption rate limit (MB/s). + Kill compute task by session id: control.(sh|bat) --kill COMPUTE session_id