com.github.jjYBdx4IL.utils.parser.ZFSStatusParser Maven / Gradle / Ivy
The newest version!
/*
* Copyright © 2014 jjYBdx4IL (https://github.com/jjYBdx4IL)
*
* Licensed 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 com.github.jjYBdx4IL.utils.parser;
//CHECKSTYLE:OFF
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author Github jjYBdx4IL Projects
*/
public class ZFSStatusParser {
private static final Logger LOG = LoggerFactory.getLogger(ZFSStatusParser.class);
private static final Pattern poolLinePattern = Pattern.compile("^ pool:");
private static final Pattern stateLinePattern = Pattern.compile("^ state:\\s*(\\S+)$");
private static final Pattern scrubOrScanLinePattern = Pattern.compile("^( scan| scrub):");
private static final Pattern scrubOrScanActivityLinePattern = Pattern.compile("^( scan| scrub):.* in progress[, ]");
private static final Pattern configLinePattern = Pattern.compile("^config:");
private static final Pattern configWhitelistLinePattern
= Pattern.compile("^(\\s*|\\s+NAME\\s+STATE\\s+READ\\s+WRITE\\s+CKSUM\\s*|.*\\s+0\\s+0\\s+0\\s*)$");
private static final Pattern errorsLinePattern = Pattern.compile("^errors:");
/**
* Transforms the output of the zpool status command into an alert/error level.
*
* - 0: OK (pool state: "ONLINE")
*
- 1: degrading (pool state: "ONLINE"): not yet degraded, but single devices show correctable (due to
* redundancy) CRC, read or write errors without being offline.
*
- 2: pool state: "DEGRADED"
*
- 3: pool state: "UNAVAIL" or everything else
*
*
* @param zpoolStatusCmdOutput the console output of the "zpool status" command
* @return the parsed {@link Result}
* @throws com.github.jjYBdx4IL.utils.parser.ParseException if the input is not correct/not understoof by the parser
*/
public static Result parse(String zpoolStatusCmdOutput) throws ParseException {
int numPools = 0;
// 0: " pool:"
// 1: " state:"
// 2:ignore everything until "scrub:" or "scan:"
// 3: ignore everything until "config:"
// 4: look for read/write/checksum errors until "errors:" or " pool:"
// 5: ignore everything until EOF or " pool:" (->1)
int state = 0;
AlertLevel alertLevel = AlertLevel.OK;
int lineNumber = 0;
boolean scrubOrScanActivityPresent = false;
for (String l : zpoolStatusCmdOutput.split("\r?\n")) {
lineNumber++;
if (LOG.isTraceEnabled()) {
LOG.trace(String.format("line %02d, state %d: %s", lineNumber, state, l));
}
AlertLevel level = AlertLevel.OK;
switch (state) {
case 0:
if (!poolLinePattern.matcher(l).find()) {
throw new ParseException(createExceptionMessage(zpoolStatusCmdOutput, lineNumber, "zpool name"));
}
numPools++;
state++;
break;
case 1:
Matcher m = stateLinePattern.matcher(l);
if (!m.find()) {
throw new ParseException(createExceptionMessage(zpoolStatusCmdOutput, lineNumber, "zpool state"));
}
try {
level = PoolState.valueOf(m.group(1)).getAlertLevel();
} catch (IllegalArgumentException ex) {
throw new ParseException(createExceptionMessage(zpoolStatusCmdOutput, lineNumber, "zpool state, unknown state: " + m.group(1)));
}
state++;
break;
case 2:
if (scrubOrScanLinePattern.matcher(l).find()) {
state++;
}
if (scrubOrScanActivityLinePattern.matcher(l).find()) {
scrubOrScanActivityPresent = true;
}
break;
case 3:
if (configLinePattern.matcher(l).find()) {
state++;
}
break;
case 4:
if (poolLinePattern.matcher(l).find()) {
numPools++;
state = 1;
} else if (errorsLinePattern.matcher(l).find()) {
state++;
} else if (!configWhitelistLinePattern.matcher(l).find()) {
level = AlertLevel.DEGRADING;
}
break;
case 5:
if (poolLinePattern.matcher(l).find()) {
numPools++;
state = 1;
}
break;
}
alertLevel = level.worseThan(alertLevel) ? level : alertLevel;
}
if (state == 0) {
throw new ParseException(createExceptionMessage(zpoolStatusCmdOutput, lineNumber, "zpool name"));
}
if (state == 1) {
throw new ParseException(createExceptionMessage(zpoolStatusCmdOutput, lineNumber, "zpool state"));
}
if (state == 2) {
throw new ParseException(createExceptionMessage(zpoolStatusCmdOutput, lineNumber, "zpool scrub/scan"));
}
if (state == 3) {
throw new ParseException(createExceptionMessage(zpoolStatusCmdOutput, lineNumber, "zpool config"));
}
if (state == 4) {
throw new ParseException(createExceptionMessage(zpoolStatusCmdOutput, lineNumber, "zpool errors"));
}
return new Result(alertLevel, numPools, scrubOrScanActivityPresent);
}
private static String createExceptionMessage(String zpoolStatusCmdOutput, int errorLineNumber, String errorMessage) {
StringBuilder sb = new StringBuilder();
int lineNumber = 0;
for (String l : zpoolStatusCmdOutput.split(System.lineSeparator())) {
if (lineNumber == errorLineNumber) {
sb.append(" ^-- ERROR! invalid zpool status format, expected ");
sb.append(errorMessage);
sb.append(System.lineSeparator());
}
lineNumber++;
sb.append(l);
sb.append(System.lineSeparator());
}
if (lineNumber == errorLineNumber) {
sb.append(" ^-- ERROR! invalid zpool status format, expected ");
sb.append(errorMessage);
sb.append(System.lineSeparator());
}
return sb.toString();
}
private ZFSStatusParser() {
}
public enum AlertLevel {
OK(0), DEGRADING(1), DEGRADED(2), FAILURE(3);
private final int numericLevel;
AlertLevel(int numericLevel) {
this.numericLevel = numericLevel;
}
public boolean worseThan(AlertLevel other) {
return numericLevel > other.numericLevel;
}
public static AlertLevel max(AlertLevel a, AlertLevel b) {
return a.worseThan(b) ? a : b;
}
protected static AlertLevel getByNumericLevel(int numericLevel) {
for (AlertLevel level : values()) {
if (level.numericLevel == numericLevel) {
return level;
}
}
throw new IllegalArgumentException("invalid numeric level: " + numericLevel);
}
};
public static class Result {
/**
* @return the alertLevel
*/
public AlertLevel getAlertLevel() {
return alertLevel;
}
/**
* @return the numPools
*/
public int getNumPools() {
return numPools;
}
private final AlertLevel alertLevel;
private final int numPools;
private final boolean scrubOrScanActivityPresent;
public Result(AlertLevel alertLevel, int numPools, boolean scrubOrScanActivityPresent) {
this.alertLevel = alertLevel;
this.numPools = numPools;
this.scrubOrScanActivityPresent = scrubOrScanActivityPresent;
}
@Override
public int hashCode() {
int hash = 3;
hash = 71 * hash + Objects.hashCode(this.alertLevel);
hash = 71 * hash + this.numPools;
hash = 71 * hash + (this.scrubOrScanActivityPresent ? 1 : 0);
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Result other = (Result) obj;
if (this.alertLevel != other.alertLevel) {
return false;
}
if (this.numPools != other.numPools) {
return false;
}
if (this.scrubOrScanActivityPresent != other.scrubOrScanActivityPresent) {
return false;
}
return true;
}
/**
* @return the scrubOrScanActivityPresent
*/
public boolean isScrubOrScanActivityPresent() {
return scrubOrScanActivityPresent;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Result [");
builder.append("alertLevel=");
builder.append(alertLevel);
builder.append(", numPools=");
builder.append(numPools);
builder.append(", scrubOrScanActivityPresent=");
builder.append(scrubOrScanActivityPresent);
builder.append("]");
return builder.toString();
}
}
private enum PoolState {
UNAVAIL, DEGRADED, DEGRADING, ONLINE, UNKNOWN;
public AlertLevel getAlertLevel() {
switch (this) {
case ONLINE:
return AlertLevel.OK;
case DEGRADING:
return AlertLevel.DEGRADING;
case DEGRADED:
return AlertLevel.DEGRADED;
default:
return AlertLevel.FAILURE;
}
}
}
}