blob: 3082f6b3c7b8ac8b4e1d695963c70861b5e5e794 [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.internal.cli.commands.recovery;
import static java.util.stream.Collectors.toSet;
import static org.apache.ignite.internal.TestDefaultProfilesNames.DEFAULT_AIPERSIST_PROFILE_NAME;
import static org.apache.ignite.internal.cli.commands.Options.Constants.CLUSTER_URL_OPTION;
import static org.apache.ignite.internal.cli.commands.Options.Constants.PLAIN_OPTION;
import static org.apache.ignite.internal.cli.commands.Options.Constants.RECOVERY_NODE_NAMES_OPTION;
import static org.apache.ignite.internal.cli.commands.Options.Constants.RECOVERY_PARTITION_GLOBAL_OPTION;
import static org.apache.ignite.internal.cli.commands.Options.Constants.RECOVERY_PARTITION_IDS_OPTION;
import static org.apache.ignite.internal.cli.commands.Options.Constants.RECOVERY_PARTITION_LOCAL_OPTION;
import static org.apache.ignite.internal.cli.commands.Options.Constants.RECOVERY_ZONE_NAMES_OPTION;
import java.util.HashSet;
import java.util.Set;
import org.apache.ignite.internal.app.IgniteImpl;
import org.apache.ignite.internal.cli.CliIntegrationTest;
import org.apache.ignite.internal.util.CollectionUtils;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
/** Base test class for Recovery partition states commands. */
public abstract class ItPartitionStatesTest extends CliIntegrationTest {
private static final int DEFAULT_PARTITION_COUNT = 25;
private static final Set<String> ZONES = Set.of("first_ZONE", "second_ZONE", "third_ZONE");
private static final Set<String> MIXED_CASE_ZONES = Set.of("mixed_first_zone", "MIXED_FIRST_ZONE", "mixed_second_zone",
"MIXED_SECOND_ZONE");
private static final Set<String> ZONES_CONTAINING_TABLES = new HashSet<>(CollectionUtils.concat(ZONES, MIXED_CASE_ZONES));
private static final String EMPTY_ZONE = "empty_ZONE";
private static final Set<String> STATES = Set.of("HEALTHY", "AVAILABLE");
private static final int DONT_CHECK_PARTITIONS = -1;
private static Set<String> nodeNames;
@BeforeAll
public static void createTables() {
ZONES_CONTAINING_TABLES.forEach(name -> {
sql(String.format("CREATE ZONE \"%s\" WITH storage_profiles='%s'", name, DEFAULT_AIPERSIST_PROFILE_NAME));
sql(String.format("CREATE TABLE \"%s_table\" (id INT PRIMARY KEY, val INT) WITH PRIMARY_ZONE = '%1$s'", name));
});
sql(String.format("CREATE ZONE \"%s\" WITH storage_profiles='%s'", EMPTY_ZONE, DEFAULT_AIPERSIST_PROFILE_NAME));
nodeNames = CLUSTER.runningNodes().map(IgniteImpl::name).collect(toSet());
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testPartitionStates(boolean global) {
execute(CLUSTER_URL_OPTION, NODE_URL,
global ? RECOVERY_PARTITION_GLOBAL_OPTION : RECOVERY_PARTITION_LOCAL_OPTION,
PLAIN_OPTION);
checkOutput(global, ZONES_CONTAINING_TABLES, nodeNames, DEFAULT_PARTITION_COUNT);
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testPartitionStatesByZones(boolean global) {
execute(CLUSTER_URL_OPTION, NODE_URL,
RECOVERY_ZONE_NAMES_OPTION, String.join(",", ZONES),
global ? RECOVERY_PARTITION_GLOBAL_OPTION : RECOVERY_PARTITION_LOCAL_OPTION,
PLAIN_OPTION
);
checkOutput(global, ZONES, nodeNames, DEFAULT_PARTITION_COUNT);
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testPartitionStatesByPartitions(boolean global) {
String partitions = "0,1";
execute(CLUSTER_URL_OPTION, NODE_URL,
RECOVERY_PARTITION_IDS_OPTION, partitions,
global ? RECOVERY_PARTITION_GLOBAL_OPTION : RECOVERY_PARTITION_LOCAL_OPTION,
PLAIN_OPTION
);
checkOutput(global, ZONES, nodeNames, 2);
}
@Test
void testLocalPartitionStatesByNodes() {
Set<String> nodes = nodeNames.stream().limit(nodeNames.size() - 1).collect(toSet());
execute(CLUSTER_URL_OPTION, NODE_URL,
RECOVERY_NODE_NAMES_OPTION, String.join(",", nodes),
RECOVERY_PARTITION_LOCAL_OPTION,
PLAIN_OPTION
);
checkOutput(false, ZONES, nodes, DONT_CHECK_PARTITIONS);
}
@Test
void testLocalPartitionStatesByNodesIsCaseSensitive() {
Set<String> nodeNames = Set.of(CLUSTER.node(0).node().name(), CLUSTER.node(1).node().name());
String url = "state/local?nodeNames=" + String.join(",", nodeNames).toUpperCase();
execute(CLUSTER_URL_OPTION, NODE_URL,
RECOVERY_NODE_NAMES_OPTION, String.join(",", nodeNames).toUpperCase(),
RECOVERY_PARTITION_LOCAL_OPTION,
PLAIN_OPTION
);
assertErrOutputContains("Some nodes are missing: ");
nodeNames.forEach(name -> assertErrOutputContains(name.toUpperCase()));
assertOutputIsEmpty();
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testPartitionStatesZonesMixedCase(boolean global) {
execute(CLUSTER_URL_OPTION, NODE_URL,
RECOVERY_ZONE_NAMES_OPTION, String.join(",", MIXED_CASE_ZONES),
global ? RECOVERY_PARTITION_GLOBAL_OPTION : RECOVERY_PARTITION_LOCAL_OPTION,
PLAIN_OPTION
);
checkOutput(global, MIXED_CASE_ZONES, nodeNames, DEFAULT_PARTITION_COUNT);
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testPartitionStatesMissingZone(boolean global) {
String unknownZone = "UNKNOWN_ZONE";
execute(CLUSTER_URL_OPTION, NODE_URL,
RECOVERY_ZONE_NAMES_OPTION, unknownZone,
global ? RECOVERY_PARTITION_GLOBAL_OPTION : RECOVERY_PARTITION_LOCAL_OPTION,
PLAIN_OPTION
);
assertErrOutputContains("Some distribution zones are missing: [UNKNOWN_ZONE]");
assertOutputIsEmpty();
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testLocalPartitionStatesNegativePartition(boolean global) {
String partitions = "1,-100,0";
execute(CLUSTER_URL_OPTION, NODE_URL,
RECOVERY_PARTITION_IDS_OPTION, partitions,
global ? RECOVERY_PARTITION_GLOBAL_OPTION : RECOVERY_PARTITION_LOCAL_OPTION,
PLAIN_OPTION
);
assertErrOutputContains("Partition ID can't be negative, found: -100");
assertOutputIsEmpty();
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testLocalPartitionStatesPartitionOutOfRange(boolean global) {
String partitions = "0,1," + DEFAULT_PARTITION_COUNT;
String zoneName = ZONES_CONTAINING_TABLES.stream().findAny().get();
execute(CLUSTER_URL_OPTION, NODE_URL,
RECOVERY_PARTITION_IDS_OPTION, partitions,
RECOVERY_ZONE_NAMES_OPTION, zoneName,
global ? RECOVERY_PARTITION_GLOBAL_OPTION : RECOVERY_PARTITION_LOCAL_OPTION,
PLAIN_OPTION
);
assertErrOutputContains(String.format(
"Partition IDs should be in range [0, %d] for zone %s, found: %d",
DEFAULT_PARTITION_COUNT - 1,
zoneName,
DEFAULT_PARTITION_COUNT
));
assertOutputIsEmpty();
}
@Test
void testPartitionStatesMissingNode() {
String unknownNode = "unknown_node";
execute(CLUSTER_URL_OPTION, NODE_URL,
RECOVERY_NODE_NAMES_OPTION, unknownNode,
RECOVERY_PARTITION_LOCAL_OPTION, PLAIN_OPTION
);
assertErrOutputContains("Some nodes are missing: [unknown_node]");
assertOutputIsEmpty();
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void testPartitionStatesEmptyResult(boolean global) {
execute(CLUSTER_URL_OPTION, NODE_URL,
RECOVERY_ZONE_NAMES_OPTION, EMPTY_ZONE,
global ? RECOVERY_PARTITION_GLOBAL_OPTION : RECOVERY_PARTITION_LOCAL_OPTION,
PLAIN_OPTION
);
checkOutput(global, Set.of(), Set.of(), 0);
}
@Test
void testOutputFormatGlobal() {
String zoneName = ZONES.stream().findAny().get();
execute(CLUSTER_URL_OPTION, NODE_URL,
RECOVERY_PARTITION_GLOBAL_OPTION,
RECOVERY_ZONE_NAMES_OPTION, zoneName,
RECOVERY_PARTITION_IDS_OPTION, "1",
PLAIN_OPTION);
assertErrOutputIsEmpty();
assertOutputMatches(String.format(
"Zone name\tTable name\tPartition ID\tState\\r?\\n%1$s\t%1$s_table\t1\t(HEALTHY|AVAILABLE)\\r?\\n",
zoneName));
}
@Test
void testOutputFormatLocal() {
String zoneName = ZONES.stream().findAny().get();
String possibleNodeNames = String.join("|", nodeNames);
execute(CLUSTER_URL_OPTION, NODE_URL,
RECOVERY_ZONE_NAMES_OPTION, zoneName,
RECOVERY_PARTITION_IDS_OPTION, "1",
RECOVERY_PARTITION_LOCAL_OPTION,
PLAIN_OPTION
);
assertErrOutputIsEmpty();
assertOutputMatches(String.format(
"Node name\tZone name\tTable name\tPartition ID\tState\\r?\\n(%1$s)\t%2$s\t%2$s_table\t1\t(HEALTHY|AVAILABLE)\\r?\\n",
possibleNodeNames,
zoneName)
);
}
private void checkOutput(boolean global, Set<String> zoneNames, Set<String> nodes, int partitions) {
assertErrOutputIsEmpty();
assertOutputStartsWith((global ? "" : "Node name\t") + "Zone name\tTable name\tPartition ID\tState");
if (!global) {
if (!nodes.isEmpty()) {
assertOutputContainsAny(nodes);
}
Set<String> anotherNodes = CollectionUtils.difference(nodeNames, nodes);
if (!anotherNodes.isEmpty()) {
assertOutputDoesNotContainIgnoreCase(anotherNodes);
}
}
if (!zoneNames.isEmpty()) {
assertOutputContainsAll(zoneNames);
Set<String> tableNames = zoneNames.stream().map(it -> it + "_table").collect(toSet());
assertOutputContainsAllIgnoringCase(tableNames);
}
Set<String> anotherZones = CollectionUtils.difference(ZONES, zoneNames);
if (!anotherZones.isEmpty()) {
assertOutputDoesNotContain(anotherZones);
}
if (!zoneNames.isEmpty() && nodeNames.isEmpty()) {
assertOutputContainsAny(STATES);
}
if (partitions != DONT_CHECK_PARTITIONS) {
for (int i = 0; i < partitions; i++) {
assertOutputContains("\t" + i + "\t");
}
for (int i = partitions; i < DEFAULT_PARTITION_COUNT; i++) {
assertOutputDoesNotContain("\t" + i + "\t");
}
}
}
}