Add raw recovery progress to cat recovery API

This commit adds fields bytes_recovered and files_recovered to the cat
recovery API. These fields, respectively, indicate the total number of
bytes and files recovered. Additionally, for consistency, some totals
fields and translog recovery fields have been renamed.

Closes #17064
This commit is contained in:
Jason Tedor 2016-03-09 21:52:38 -05:00
parent a7e78c91ed
commit f465d98eb3
4 changed files with 222 additions and 14 deletions

View File

@ -92,14 +92,16 @@ public class RestRecoveryAction extends AbstractCatAction {
.addCell("repository", "alias:rep;desc:repository")
.addCell("snapshot", "alias:snap;desc:snapshot")
.addCell("files", "alias:f;desc:number of files to recover")
.addCell("files_recovered", "alias:fr;desc:files recovered")
.addCell("files_percent", "alias:fp;desc:percent of files recovered")
.addCell("bytes", "alias:b;desc:size to recover in bytes")
.addCell("files_total", "alias:tf;desc:total number of files")
.addCell("bytes", "alias:b;desc:number of bytes to recover")
.addCell("bytes_recovered", "alias:br;desc:bytes recovered")
.addCell("bytes_percent", "alias:bp;desc:percent of bytes recovered")
.addCell("total_files", "alias:tf;desc:total number of files")
.addCell("total_bytes", "alias:tb;desc:total number of bytes")
.addCell("translog", "alias:tr;desc:translog operations recovered")
.addCell("translog_percent", "alias:trp;desc:percent of translog recovery")
.addCell("total_translog", "alias:trt;desc:current total translog operations")
.addCell("bytes_total", "alias:tb;desc:total number of bytes")
.addCell("translog_ops", "alias:to;desc:number of translog ops to recover")
.addCell("translog_ops_recovered", "alias:tor;desc:translog ops recovered")
.addCell("translog_ops_percent", "alias:top;desc:percent of translog ops recovered")
.endHeaders();
return t;
}
@ -151,14 +153,16 @@ public class RestRecoveryAction extends AbstractCatAction {
t.addCell(state.getRestoreSource() == null ? "n/a" : state.getRestoreSource().snapshotId().getRepository());
t.addCell(state.getRestoreSource() == null ? "n/a" : state.getRestoreSource().snapshotId().getSnapshot());
t.addCell(state.getIndex().totalRecoverFiles());
t.addCell(state.getIndex().recoveredFileCount());
t.addCell(String.format(Locale.ROOT, "%1.1f%%", state.getIndex().recoveredFilesPercent()));
t.addCell(state.getIndex().totalRecoverBytes());
t.addCell(String.format(Locale.ROOT, "%1.1f%%", state.getIndex().recoveredBytesPercent()));
t.addCell(state.getIndex().totalFileCount());
t.addCell(state.getIndex().totalRecoverBytes());
t.addCell(state.getIndex().recoveredBytes());
t.addCell(String.format(Locale.ROOT, "%1.1f%%", state.getIndex().recoveredBytesPercent()));
t.addCell(state.getIndex().totalBytes());
t.addCell(state.getTranslog().totalOperations());
t.addCell(state.getTranslog().recoveredOperations());
t.addCell(String.format(Locale.ROOT, "%1.1f%%", state.getTranslog().recoveredPercent()));
t.addCell(state.getTranslog().totalOperations());
t.endRow();
}
}

View File

@ -0,0 +1,187 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch 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.elasticsearch.rest.action.cat;
import org.elasticsearch.action.ShardOperationFailedException;
import org.elasticsearch.action.admin.indices.recovery.RecoveryResponse;
import org.elasticsearch.cluster.metadata.SnapshotId;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.cluster.routing.RestoreSource;
import org.elasticsearch.common.Randomness;
import org.elasticsearch.common.Table;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.shard.ShardId;
import org.elasticsearch.indices.recovery.RecoveryState;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.test.ESTestCase;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import static org.elasticsearch.mock.orig.Mockito.when;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.mockito.Mockito.mock;
public class RestRecoveryActionTests extends ESTestCase {
public void testRestRecoveryAction() {
final Settings settings = Settings.EMPTY;
final RestController restController = new RestController(settings);
final RestRecoveryAction action = new RestRecoveryAction(settings, restController, restController, null);
final int totalShards = randomIntBetween(1, 32);
final int successfulShards = Math.max(0, totalShards - randomIntBetween(1, 2));
final int failedShards = totalShards - successfulShards;
final boolean detailed = randomBoolean();
final Map<String, List<RecoveryState>> shardRecoveryStates = new HashMap<>();
final List<RecoveryState> recoveryStates = new ArrayList<>();
for (int i = 0; i < successfulShards; i++) {
final RecoveryState state = mock(RecoveryState.class);
when(state.getShardId()).thenReturn(new ShardId(new Index("index", "_na_"), i));
final RecoveryState.Timer timer = mock(RecoveryState.Timer.class);
when(timer.time()).thenReturn((long)randomIntBetween(1000000, 10 * 1000000));
when(state.getTimer()).thenReturn(timer);
when(state.getType()).thenReturn(randomFrom(RecoveryState.Type.values()));
when(state.getStage()).thenReturn(randomFrom(RecoveryState.Stage.values()));
final DiscoveryNode sourceNode = randomBoolean() ? mock(DiscoveryNode.class) : null;
if (sourceNode != null) {
when(sourceNode.getHostName()).thenReturn(randomAsciiOfLength(8));
}
when(state.getSourceNode()).thenReturn(sourceNode);
final DiscoveryNode targetNode = mock(DiscoveryNode.class);
when(targetNode.getHostName()).thenReturn(randomAsciiOfLength(8));
when(state.getTargetNode()).thenReturn(targetNode);
final RestoreSource restoreSource = randomBoolean() ? mock(RestoreSource.class) : null;
if (restoreSource != null) {
final SnapshotId snapshotId = mock(SnapshotId.class);
when(snapshotId.getRepository()).thenReturn(randomAsciiOfLength(8));
when(snapshotId.getSnapshot()).thenReturn(randomAsciiOfLength(8));
when(restoreSource.snapshotId()).thenReturn(snapshotId);
}
RecoveryState.Index index = mock(RecoveryState.Index.class);
final int totalRecoveredFiles = randomIntBetween(1, 64);
when(index.totalRecoverFiles()).thenReturn(totalRecoveredFiles);
final int recoveredFileCount = randomIntBetween(0, totalRecoveredFiles);
when(index.recoveredFileCount()).thenReturn(recoveredFileCount);
when(index.recoveredFilesPercent()).thenReturn((100f * recoveredFileCount) / totalRecoveredFiles);
when(index.totalFileCount()).thenReturn(randomIntBetween(totalRecoveredFiles, 2 * totalRecoveredFiles));
final int totalRecoveredBytes = randomIntBetween(1, 1 << 24);
when(index.totalRecoverBytes()).thenReturn((long)totalRecoveredBytes);
final int recoveredBytes = randomIntBetween(0, totalRecoveredBytes);
when(index.recoveredBytes()).thenReturn((long)recoveredBytes);
when(index.recoveredBytesPercent()).thenReturn((100f * recoveredBytes) / totalRecoveredBytes);
when(index.totalRecoverBytes()).thenReturn((long)randomIntBetween(totalRecoveredBytes, 2 * totalRecoveredBytes));
when(state.getIndex()).thenReturn(index);
final RecoveryState.Translog translog = mock(RecoveryState.Translog.class);
final int translogOps = randomIntBetween(0, 1 << 18);
when(translog.totalOperations()).thenReturn(translogOps);
final int translogOpsRecovered = randomIntBetween(0, translogOps);
when(translog.recoveredOperations()).thenReturn(translogOpsRecovered);
when(translog.recoveredPercent()).thenReturn(translogOps == 0 ? 100f : (100f * translogOpsRecovered / translogOps));
when(state.getTranslog()).thenReturn(translog);
recoveryStates.add(state);
}
final List<RecoveryState> shuffle = new ArrayList<>(recoveryStates);
Randomness.shuffle(shuffle);
shardRecoveryStates.put("index", shuffle);
final List<ShardOperationFailedException> shardFailures = new ArrayList<>();
final RecoveryResponse response = new RecoveryResponse(
totalShards,
successfulShards,
failedShards,
detailed,
shardRecoveryStates,
shardFailures);
final Table table = action.buildRecoveryTable(null, response);
assertNotNull(table);
List<Table.Cell> headers = table.getHeaders();
assertThat(headers.get(0).value, equalTo("index"));
assertThat(headers.get(1).value, equalTo("shard"));
assertThat(headers.get(2).value, equalTo("time"));
assertThat(headers.get(3).value, equalTo("type"));
assertThat(headers.get(4).value, equalTo("stage"));
assertThat(headers.get(5).value, equalTo("source_host"));
assertThat(headers.get(6).value, equalTo("target_host"));
assertThat(headers.get(7).value, equalTo("repository"));
assertThat(headers.get(8).value, equalTo("snapshot"));
assertThat(headers.get(9).value, equalTo("files"));
assertThat(headers.get(10).value, equalTo("files_recovered"));
assertThat(headers.get(11).value, equalTo("files_percent"));
assertThat(headers.get(12).value, equalTo("files_total"));
assertThat(headers.get(13).value, equalTo("bytes"));
assertThat(headers.get(14).value, equalTo("bytes_recovered"));
assertThat(headers.get(15).value, equalTo("bytes_percent"));
assertThat(headers.get(16).value, equalTo("bytes_total"));
assertThat(headers.get(17).value, equalTo("translog_ops"));
assertThat(headers.get(18).value, equalTo("translog_ops_recovered"));
assertThat(headers.get(19).value, equalTo("translog_ops_percent"));
assertThat(table.getRows().size(), equalTo(successfulShards));
for (int i = 0; i < successfulShards; i++) {
final RecoveryState state = recoveryStates.get(i);
List<Table.Cell> cells = table.getRows().get(i);
assertThat(cells.get(0).value, equalTo("index"));
assertThat(cells.get(1).value, equalTo(i));
assertThat(cells.get(2).value, equalTo(new TimeValue(state.getTimer().time())));
assertThat(cells.get(3).value, equalTo(state.getType().name().toLowerCase(Locale.ROOT)));
assertThat(cells.get(4).value, equalTo(state.getStage().name().toLowerCase(Locale.ROOT)));
assertThat(cells.get(5).value, equalTo(state.getSourceNode() == null ? "n/a" : state.getSourceNode().getHostName()));
assertThat(cells.get(6).value, equalTo(state.getTargetNode().getHostName()));
assertThat(
cells.get(7).value,
equalTo(state.getRestoreSource() == null ? "n/a" : state.getRestoreSource().snapshotId().getRepository()));
assertThat(
cells.get(8).value,
equalTo(state.getRestoreSource() == null ? "n/a" : state.getRestoreSource().snapshotId().getSnapshot()));
assertThat(cells.get(9).value, equalTo(state.getIndex().totalRecoverFiles()));
assertThat(cells.get(10).value, equalTo(state.getIndex().recoveredFileCount()));
assertThat(cells.get(11).value, equalTo(percent(state.getIndex().recoveredFilesPercent())));
assertThat(cells.get(12).value, equalTo(state.getIndex().totalFileCount()));
assertThat(cells.get(13).value, equalTo(state.getIndex().totalRecoverBytes()));
assertThat(cells.get(14).value, equalTo(state.getIndex().recoveredBytes()));
assertThat(cells.get(15).value, equalTo(percent(state.getIndex().recoveredBytesPercent())));
assertThat(cells.get(16).value, equalTo(state.getIndex().totalBytes()));
assertThat(cells.get(17).value, equalTo(state.getTranslog().totalOperations()));
assertThat(cells.get(18).value, equalTo(state.getTranslog().recoveredOperations()));
assertThat(cells.get(19).value, equalTo(percent(state.getTranslog().recoveredPercent())));
}
}
private static String percent(float percent) {
return String.format(Locale.ROOT, "%1.1f%%", percent);
}
}

View File

@ -191,6 +191,21 @@ The `host` field has been removed from the cat nodes API as its value
is always equal to the `ip` field. The `name` field is available in the
cat nodes API and should be used instead of the `host` field.
==== Changes to cat recovery API
The fields `bytes_recovered` and `files_recovered` have been added to
the cat recovery API. These fields, respectively, indicate the total
number of bytes and files that have been recovered.
The fields `total_files` and `total_bytes` have been renamed to
`files_total` and `bytes_total`, respectively.
Additionally, the field `translog` has been renamed to
`translog_ops_recovered`, the field `translog_total` to
`translog_ops` and the field `translog_percent` to
`translog_ops_percent`. The short aliases for these fields are `tor`,
`to`, and `top`, respectively.
[[breaking_50_parent_child_changes]]
=== Parent/Child changes

View File

@ -35,14 +35,16 @@
[-\w./]+ \s+ # repository
[-\w./]+ \s+ # snapshot
\d+ \s+ # files
\d+ \s+ # files_recovered
\d+\.\d+% \s+ # files_percent
\d+ \s+ # files_total
\d+ \s+ # bytes
\d+ \s+ # bytes_recovered
\d+\.\d+% \s+ # bytes_percent
\d+ \s+ # total_files
\d+ \s+ # total_bytes
\d+ \s+ # translog
-?\d+\.\d+% \s+ # translog_percent
-?\d+ # total_translog
\d+ \s+ # bytes_total
-?\d+ \s+ # translog_ops
\d+ \s+ # translog_ops_recovered
-?\d+\.\d+% # translog_ops_percent
\n
)+
$/