blob: 5d2a5d33e377ce36d23b5df6eb4afb56e556b319 [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.processors.cache.verify;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.PrintWriter;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.cluster.ClusterNode;
import org.apache.ignite.internal.util.tostring.GridToStringInclude;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.internal.S;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.internal.visor.VisorDataTransferObject;
import org.jetbrains.annotations.Nullable;
import static org.apache.ignite.internal.commandline.cache.CacheSubcommands.IDLE_VERIFY;
/**
* Encapsulates result of {@link VerifyBackupPartitionsTaskV2}.
*/
public class IdleVerifyResultV2 extends VisorDataTransferObject {
/** */
public static final String IDLE_VERIFY_FILE_PREFIX = IDLE_VERIFY + "-";
/** Time formatter for log file name. */
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH-mm-ss_SSS");
/** */
private static final long serialVersionUID = 0L;
/** Counter conflicts. */
@GridToStringInclude
private Map<PartitionKeyV2, List<PartitionHashRecordV2>> cntrConflicts;
/** Hash conflicts. */
@GridToStringInclude
private Map<PartitionKeyV2, List<PartitionHashRecordV2>> hashConflicts;
/** Moving partitions. */
@GridToStringInclude
private Map<PartitionKeyV2, List<PartitionHashRecordV2>> movingPartitions;
/** Lost partitions. */
@GridToStringInclude
private Map<PartitionKeyV2, List<PartitionHashRecordV2>> lostPartitions;
/** Exceptions. */
@GridToStringInclude
private Map<ClusterNode, Exception> exceptions;
/**
* @param cntrConflicts Counter conflicts.
* @param hashConflicts Hash conflicts.
* @param movingPartitions Moving partitions.
* @param exceptions Occured exceptions.
*/
public IdleVerifyResultV2(
Map<PartitionKeyV2, List<PartitionHashRecordV2>> cntrConflicts,
Map<PartitionKeyV2, List<PartitionHashRecordV2>> hashConflicts,
Map<PartitionKeyV2, List<PartitionHashRecordV2>> movingPartitions,
Map<PartitionKeyV2, List<PartitionHashRecordV2>> lostPartitions,
Map<ClusterNode, Exception> exceptions
) {
this.cntrConflicts = cntrConflicts;
this.hashConflicts = hashConflicts;
this.movingPartitions = movingPartitions;
this.lostPartitions = lostPartitions;
this.exceptions = exceptions;
}
/**
* Default constructor for Externalizable.
*/
public IdleVerifyResultV2() {
}
/** {@inheritDoc} */
@Override public byte getProtocolVersion() {
return V3;
}
/** {@inheritDoc} */
@Override protected void writeExternalData(ObjectOutput out) throws IOException {
U.writeMap(out, cntrConflicts);
U.writeMap(out, hashConflicts);
U.writeMap(out, movingPartitions);
U.writeMap(out, exceptions);
U.writeMap(out, lostPartitions);
}
/** {@inheritDoc} */
@Override protected void readExternalData(byte protoVer,
ObjectInput in) throws IOException, ClassNotFoundException {
cntrConflicts = U.readMap(in);
hashConflicts = U.readMap(in);
movingPartitions = U.readMap(in);
if (protoVer >= V2)
exceptions = U.readMap(in);
if (protoVer >= V3)
lostPartitions = U.readMap(in);
}
/**
* @return Counter conflicts.
*/
public Map<PartitionKeyV2, List<PartitionHashRecordV2>> counterConflicts() {
return cntrConflicts;
}
/**
* @return Hash conflicts.
*/
public Map<PartitionKeyV2, List<PartitionHashRecordV2>> hashConflicts() {
return hashConflicts;
}
/**
* @return Moving partitions.
*/
public Map<PartitionKeyV2, List<PartitionHashRecordV2>> movingPartitions() {
return Collections.unmodifiableMap(movingPartitions);
}
/**
* @return Lost partitions.
*/
public Map<PartitionKeyV2, List<PartitionHashRecordV2>> lostPartitions() {
return lostPartitions;
}
/**
* @return <code>true</code> if any conflicts were discovered during idle_verify check.
*/
public boolean hasConflicts() {
return !F.isEmpty(hashConflicts()) || !F.isEmpty(counterConflicts());
}
/**
* @return Exceptions on nodes.
*/
public Map<ClusterNode, Exception> exceptions() {
return exceptions;
}
/**
* Print formatted result to given printer. If exceptions presented exception messages will be written to log file.
*
* @param printer Consumer for handle formatted result.
* @return Path to log file if exceptions presented and {@code null} otherwise.
*/
public @Nullable String print(Consumer<String> printer) {
print(printer, false);
if (!F.isEmpty(exceptions)) {
File wd = null;
try {
wd = U.resolveWorkDirectory(U.defaultWorkDirectory(), "", false);
}
catch (IgniteCheckedException e) {
printer.accept("Can't find work directory. " + e.getMessage() + "\n");
e.printStackTrace();
}
File f = new File(wd, IDLE_VERIFY_FILE_PREFIX + LocalDateTime.now().format(TIME_FORMATTER) + ".txt");
try (PrintWriter pw = new PrintWriter(f)) {
print(pw::write, true);
pw.flush();
printer.accept("See log for additional information. " + f.getAbsolutePath() + "\n");
return f.getAbsolutePath();
}
catch (FileNotFoundException e) {
printer.accept("Can't write exceptions to file " + f.getAbsolutePath() + " " + e.getMessage() + "\n");
e.printStackTrace();
}
}
return null;
}
/** */
private void print(Consumer<String> printer, boolean printExceptionMessages) {
boolean noMatchingCaches = false;
boolean succeeded = true;
for (Exception e : exceptions.values()) {
if (e instanceof NoMatchingCachesException) {
noMatchingCaches = true;
succeeded = false;
break;
}
}
if (succeeded) {
if (!F.isEmpty(exceptions)) {
int size = exceptions.size();
printer.accept("idle_verify failed on " + size + " node" + (size == 1 ? "" : "s") + ".\n");
}
if (!hasConflicts())
printer.accept("idle_verify check has finished, no conflicts have been found.\n");
else
printConflicts(printer);
Map<PartitionKeyV2, List<PartitionHashRecordV2>> moving = movingPartitions();
if (!moving.isEmpty())
printer.accept("Possible results are not full due to rebalance still in progress." + U.nl());
printSkippedPartitions(printer, moving, "MOVING");
printSkippedPartitions(printer, lostPartitions(), "LOST");
}
else {
printer.accept("\nidle_verify failed.\n");
if (noMatchingCaches)
printer.accept("\nThere are no caches matching given filter options.\n");
}
if (!F.isEmpty(exceptions())) {
printer.accept("\nIdle verify failed on nodes:\n");
for (Map.Entry<ClusterNode, Exception> e : exceptions().entrySet()) {
ClusterNode n = e.getKey();
printer.accept("\nNode ID: " + n.id() + " " + n.addresses() + "\nConsistent ID: " + n.consistentId() + "\n");
if (printExceptionMessages) {
String msg = e.getValue().getMessage();
printer.accept("Exception: " + e.getValue().getClass().getCanonicalName() + "\n");
printer.accept(msg == null ? "" : msg + "\n");
}
}
}
}
/**
* Print partitions which were skipped.
*
* @param printer Consumer for printing.
* @param map Partitions storage.
* @param partitionState Partition state.
*/
private void printSkippedPartitions(
Consumer<String> printer,
Map<PartitionKeyV2, List<PartitionHashRecordV2>> map,
String partitionState
) {
if (!F.isEmpty(map)) {
printer.accept("Verification was skipped for " + map.size() + " " + partitionState + " partitions:\n");
for (Map.Entry<PartitionKeyV2, List<PartitionHashRecordV2>> entry : map.entrySet()) {
printer.accept("Skipped partition: " + entry.getKey() + "\n");
printer.accept("Partition instances: " + entry.getValue() + "\n");
}
printer.accept("\n");
}
}
/** */
private void printConflicts(Consumer<String> printer) {
int cntrConflictsSize = counterConflicts().size();
int hashConflictsSize = hashConflicts().size();
printer.accept("idle_verify check has finished, found " + (cntrConflictsSize + hashConflictsSize) +
" conflict partitions: [counterConflicts=" + cntrConflictsSize + ", hashConflicts=" +
hashConflictsSize + "]\n");
if (!F.isEmpty(counterConflicts())) {
printer.accept("Update counter conflicts:\n");
for (Map.Entry<PartitionKeyV2, List<PartitionHashRecordV2>> entry : counterConflicts().entrySet()) {
printer.accept("Conflict partition: " + entry.getKey() + "\n");
printer.accept("Partition instances: " + entry.getValue() + "\n");
}
printer.accept("\n");
}
if (!F.isEmpty(hashConflicts())) {
printer.accept("Hash conflicts:\n");
for (Map.Entry<PartitionKeyV2, List<PartitionHashRecordV2>> entry : hashConflicts().entrySet()) {
printer.accept("Conflict partition: " + entry.getKey() + "\n");
printer.accept("Partition instances: " + entry.getValue() + "\n");
}
printer.accept("\n");
}
}
/** {@inheritDoc} */
@Override public String toString() {
return S.toString(IdleVerifyResultV2.class, this);
}
}