blob: b135ed993300aa196a9bb981012914d904e47aa5 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ignite.util;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.PrintStream;
import java.nio.file.DirectoryStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.logging.Handler;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteCache;
import org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction;
import org.apache.ignite.cluster.ClusterNode;
import org.apache.ignite.configuration.AtomicConfiguration;
import org.apache.ignite.configuration.CacheConfiguration;
import org.apache.ignite.configuration.ConnectorConfiguration;
import org.apache.ignite.configuration.DataRegionConfiguration;
import org.apache.ignite.configuration.DataStorageConfiguration;
import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.configuration.WALMode;
import org.apache.ignite.internal.IgniteEx;
import org.apache.ignite.internal.TestRecordingCommunicationSpi;
import org.apache.ignite.internal.commandline.CommandHandler;
import org.apache.ignite.internal.processors.cache.GridCacheFuture;
import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxPrepareFuture;
import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxPrepareFutureAdapter;
import org.apache.ignite.internal.processors.cache.transactions.IgniteInternalTx;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.G;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.lang.IgnitePredicate;
import org.apache.ignite.spi.encryption.keystore.KeystoreEncryptionSpi;
import org.apache.ignite.testframework.GridTestUtils;
import org.apache.ignite.testframework.junits.WithSystemProperty;
import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
import org.jetbrains.annotations.Nullable;
import static java.lang.String.join;
import static java.lang.System.lineSeparator;
import static java.nio.file.Files.delete;
import static java.nio.file.Files.newDirectoryStream;
import static java.util.Arrays.asList;
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.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;
import static org.apache.ignite.testframework.GridTestUtils.cleanIdleVerifyLogFiles;
import static org.apache.ignite.util.GridCommandHandlerTestUtils.addSslParams;
/**
* Common abstract class for testing {@link CommandHandler}.
* I advise you to look at the heirs classes:
* {@link GridCommandHandlerClusterPerMethodAbstractTest}
* {@link GridCommandHandlerClusterByClassAbstractTest}
*/
@WithSystemProperty(key = IGNITE_ENABLE_EXPERIMENTAL_COMMAND, value = "true")
public abstract class GridCommandHandlerAbstractTest extends GridCommonAbstractTest {
/** */
protected static final String CLIENT_NODE_NAME_PREFIX = "client";
/** Option is used for auto confirmation. */
protected static final String CMD_AUTO_CONFIRMATION = "--yes";
/** System out. */
protected static PrintStream sysOut;
/** System in. */
private static InputStream sysIn;
/**
* Test out - can be injected via {@link #injectTestSystemOut()} instead of System.out and analyzed in test.
* Will be as well passed as a handler output for an anonymous logger in the test.
*/
protected static ByteArrayOutputStream testOut;
/** Atomic configuration. */
protected AtomicConfiguration atomicConfiguration;
/** Additional data region configuration. */
protected DataRegionConfiguration dataRegionConfiguration;
/** Checkpoint frequency. */
protected long checkpointFreq = DFLT_CHECKPOINT_FREQ;
/** Enable automatic confirmation to avoid user interaction. */
protected boolean autoConfirmation = true;
/** {@code True} if encription is enabled. */
protected boolean encriptionEnabled;
/** Last operation result. */
protected Object lastOperationResult;
/** Persistence flag. */
private boolean persistent = true;
/**
* Persistence setter.
*
* @param pr {@code True} If persistence enable.
**/
protected void persistenceEnable(boolean pr) {
persistent = pr;
}
/**
* Persistence getter.
*
* @return Persistence enable flag.
*/
protected boolean persistenceEnable() {
return persistent;
}
/** {@inheritDoc} */
@Override protected void beforeTestsStarted() throws Exception {
super.beforeTestsStarted();
testOut = new ByteArrayOutputStream(16 * 1024);
sysOut = System.out;
sysIn = System.in;
}
/** {@inheritDoc} */
@Override protected void afterTestsStopped() throws Exception {
super.afterTestsStopped();
cleanIdleVerifyLogFiles();
}
/** {@inheritDoc} */
@Override protected void afterTest() throws Exception {
super.afterTest();
log.info("Test output for " + currentTestMethod());
log.info("----------------------------------------");
System.setOut(sysOut);
System.setIn(sysIn);
log.info(testOut.toString());
testOut.reset();
encriptionEnabled = false;
}
/** {@inheritDoc} */
@Override public String getTestIgniteInstanceName() {
return "gridCommandHandlerTest";
}
/**
* @return Logger.
*/
private Logger createTestLogger() {
Logger log = CommandHandler.initLogger(null);
// Adding logging to console.
log.addHandler(CommandHandler.setupStreamHandler());
return log;
}
/** */
protected boolean sslEnabled() {
return false;
}
/** */
protected boolean idleVerifyRes(Path p) {
return p.toFile().getName().startsWith(IDLE_DUMP_FILE_PREFIX);
}
/** {@inheritDoc} */
@Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName);
if (atomicConfiguration != null)
cfg.setAtomicConfiguration(atomicConfiguration);
cfg.setCommunicationSpi(new TestRecordingCommunicationSpi());
cfg.setConnectorConfiguration(new ConnectorConfiguration().setSslEnabled(sslEnabled()));
if (sslEnabled())
cfg.setSslContextFactory(GridTestUtils.sslFactory());
DataStorageConfiguration dsCfg = new DataStorageConfiguration()
.setWalMode(WALMode.LOG_ONLY)
.setCheckpointFrequency(checkpointFreq)
.setDefaultDataRegionConfiguration(
new DataRegionConfiguration().setMaxSize(50L * 1024 * 1024).setPersistenceEnabled(persistent)
);
if (dataRegionConfiguration != null)
dsCfg.setDataRegionConfigurations(dataRegionConfiguration);
cfg.setDataStorageConfiguration(dsCfg);
cfg.setConsistentId(igniteInstanceName);
cfg.setClientMode(igniteInstanceName.startsWith(CLIENT_NODE_NAME_PREFIX));
if (encriptionEnabled) {
KeystoreEncryptionSpi encSpi = new KeystoreEncryptionSpi();
encSpi.setKeyStorePath(KEYSTORE_PATH);
encSpi.setKeyStorePassword(KEYSTORE_PASSWORD.toCharArray());
cfg.setEncryptionSpi(encSpi);
}
return cfg;
}
/** {@inheritDoc} */
@Override protected void cleanPersistenceDir() throws Exception {
super.cleanPersistenceDir();
try (DirectoryStream<Path> files = newDirectoryStream(Paths.get(U.defaultWorkDirectory()), this::idleVerifyRes)) {
for (Path path : files)
delete(path);
}
}
/**
* Before command executed {@link #testOut} reset.
*
* @param args Arguments.
* @return Result of execution.
*/
protected int execute(String... args) {
return execute(new ArrayList<>(asList(args)));
}
/**
* Before command executed {@link #testOut} reset.
*
* @param args Arguments.
* @return Result of execution
*/
protected int execute(List<String> args) {
return execute(new CommandHandler(createTestLogger()), args);
}
/**
* Before command executed {@link #testOut} reset.
*
* @param hnd Handler.
* @param args Arguments.
* @return Result of execution
*/
protected int execute(CommandHandler hnd, String... args) {
return execute(hnd, new ArrayList<>(asList(args)));
}
/**
* Before command executed {@link #testOut} reset.
*/
protected int execute(CommandHandler hnd, List<String> args) {
if (!F.isEmpty(args) && !"--help".equalsIgnoreCase(args.get(0)))
addExtraArguments(args);
testOut.reset();
int exitCode = hnd.execute(args);
lastOperationResult = hnd.getLastOperationResult();
// Flush all Logger handlers to make log data available to test.
Logger logger = U.field(hnd, "logger");
Arrays.stream(logger.getHandlers()).forEach(Handler::flush);
return exitCode;
}
/**
* Adds extra arguments required for tests.
*
* @param args Incoming arguments;
*/
protected void addExtraArguments(List<String> args) {
if (autoConfirmation)
args.add(CMD_AUTO_CONFIRMATION);
if (sslEnabled()) {
// We shouldn't add extra args for --cache help.
if (args.size() < 2 || !args.get(0).equals("--cache") || !args.get(1).equals("help"))
addSslParams(args);
}
}
/** */
protected void injectTestSystemOut() {
System.setOut(new PrintStream(testOut));
}
/**
* Emulates user input.
*
* @param inputStrings User input strings.
* */
protected void injectTestSystemIn(String... inputStrings) {
assert nonNull(inputStrings);
String inputStr = join(lineSeparator(), inputStrings);
System.setIn(new ByteArrayInputStream(inputStr.getBytes()));
}
/**
* Checks if all non-system txs and non-system mvcc futures are finished.
*/
protected void checkUserFutures() {
for (Ignite ignite : G.allGrids()) {
IgniteEx ig = (IgniteEx)ignite;
final Collection<GridCacheFuture<?>> futs = ig.context().cache().context().mvcc().activeFutures();
boolean hasFutures = false;
for (GridCacheFuture<?> fut : futs) {
if (!fut.isDone()) {
//skipping system tx futures if possible
if (fut instanceof GridNearTxPrepareFutureAdapter
&& ((GridNearTxPrepareFutureAdapter) fut).tx().system())
continue;
if (fut instanceof GridDhtTxPrepareFuture
&& ((GridDhtTxPrepareFuture) fut).tx().system())
continue;
log.error("Expecting no active future [node=" + ig.localNode().id() + ", fut=" + fut + ']');
hasFutures = true;
}
}
if (hasFutures)
fail("Some mvcc futures are not finished");
Collection<IgniteInternalTx> txs = ig.context().cache().context().tm().activeTransactions()
.stream()
.filter(tx -> !tx.system())
.collect(Collectors.toSet());
for (IgniteInternalTx tx : txs)
log.error("Expecting no active transaction [node=" + ig.localNode().id() + ", tx=" + tx + ']');
if (!txs.isEmpty())
fail("Some transaction are not finished");
}
}
/**
* Creates default cache and preload some data entries.
* <br/>
* <table class="doctable">
* <th>Cache parameter</th>
* <th>Value</th>
* <tr>
* <td>Name</td>
* <td>{@link #DEFAULT_CACHE_NAME}</td>
* </tr>
* <tr>
* <td>Affinity</td>
* <td>{@link RendezvousAffinityFunction} with exclNeighbors = false, parts = 32</td>
* </tr>
* <tr>
* <td>Number of backup</td>
* <td>1</td>
* </tr>
*
* </table>
*
* @param ignite Ignite.
* @param countEntries Count of entries.
* @param partitions Partitions count.
* @param filter Node filter.
*/
protected void createCacheAndPreload(
Ignite ignite,
int countEntries,
int partitions,
@Nullable IgnitePredicate<ClusterNode> filter
) {
assert nonNull(ignite);
CacheConfiguration<?, ?> ccfg = new CacheConfiguration<>(DEFAULT_CACHE_NAME)
.setAffinity(new RendezvousAffinityFunction(false, partitions))
.setBackups(1);
if (filter != null)
ccfg.setNodeFilter(filter);
ignite.createCache(ccfg);
IgniteCache<Object, Object> cache = ignite.cache(DEFAULT_CACHE_NAME);
for (int i = 0; i < countEntries; i++)
cache.put(i, i);
}
/**
* Creates default cache and preload some data entries.
*
* @param ignite Ignite.
* @param countEntries Count of entries.
*/
protected void createCacheAndPreload(Ignite ignite, int countEntries) {
createCacheAndPreload(ignite, countEntries, 32, null);
}
}