blob: 2ea34b9d3b2d1fd6579b63e98e7de7a099b6a992 [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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.hadoop.hdds.scm.cli.container;
import org.apache.hadoop.hdds.client.ECReplicationConfig;
import org.apache.hadoop.hdds.client.RatisReplicationConfig;
import org.apache.hadoop.hdds.protocol.DatanodeDetails;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
import org.apache.hadoop.hdds.scm.client.ScmClient;
import org.apache.hadoop.hdds.scm.container.ContainerInfo;
import org.apache.hadoop.hdds.scm.container.ContainerReplicaInfo;
import org.apache.hadoop.hdds.scm.container.common.helpers.ContainerWithPipeline;
import org.apache.hadoop.hdds.scm.pipeline.Pipeline;
import org.apache.hadoop.hdds.scm.pipeline.PipelineID;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.spi.LoggingEvent;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import picocli.CommandLine;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static org.apache.hadoop.hdds.protocol.proto.HddsProtos.LifeCycleState.CLOSED;
import static org.apache.hadoop.hdds.protocol.proto.HddsProtos.ReplicationFactor.THREE;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.mock;
/**
* Tests for InfoSubCommand class.
*/
public class TestInfoSubCommand {
private ScmClient scmClient;
private InfoSubcommand cmd;
private List<DatanodeDetails> datanodes;
private Logger logger;
private TestAppender appender;
@Before
public void setup() throws IOException {
scmClient = mock(ScmClient.class);
datanodes = createDatanodeDetails(3);
Mockito.when(scmClient.getContainerWithPipeline(anyLong()))
.thenReturn(getContainerWithPipeline());
appender = new TestAppender();
logger = Logger.getLogger(
org.apache.hadoop.hdds.scm.cli.container.InfoSubcommand.class);
logger.addAppender(appender);
}
@After
public void after() {
logger.removeAppender(appender);
}
@Test
public void testReplicasIncludedInOutput() throws Exception {
testReplicaIncludedInOutput(false);
}
@Test
public void testReplicaIndexInOutput() throws Exception {
testReplicaIncludedInOutput(true);
}
private void testReplicaIncludedInOutput(boolean includeIndex)
throws IOException {
Mockito.when(scmClient.getContainerReplicas(anyLong()))
.thenReturn(getReplicas(includeIndex));
cmd = new InfoSubcommand();
CommandLine c = new CommandLine(cmd);
c.parseArgs("1");
cmd.execute(scmClient);
// Ensure we have a line for Replicas:
List<LoggingEvent> logs = appender.getLog();
List<LoggingEvent> replica = logs.stream()
.filter(m -> m.getRenderedMessage().matches("(?s)^Replicas:.*"))
.collect(Collectors.toList());
Assert.assertEquals(1, replica.size());
// Ensure each DN UUID is mentioned in the message:
for (DatanodeDetails dn : datanodes) {
Pattern pattern = Pattern.compile(".*" + dn.getUuid().toString() + ".*",
Pattern.DOTALL);
Matcher matcher = pattern.matcher(replica.get(0).getRenderedMessage());
Assert.assertTrue(matcher.matches());
}
// Ensure ReplicaIndex is not mentioned as it was not passed in the proto:
Pattern pattern = Pattern.compile(".*ReplicaIndex.*",
Pattern.DOTALL);
Matcher matcher = pattern.matcher(replica.get(0).getRenderedMessage());
Assert.assertEquals(includeIndex, matcher.matches());
}
@Test
public void testReplicasNotOutputIfError() throws IOException {
Mockito.when(scmClient.getContainerReplicas(anyLong()))
.thenThrow(new IOException("Error getting Replicas"));
cmd = new InfoSubcommand();
CommandLine c = new CommandLine(cmd);
c.parseArgs("1");
cmd.execute(scmClient);
// Ensure we have no lines for Replicas:
List<LoggingEvent> logs = appender.getLog();
List<LoggingEvent> replica = logs.stream()
.filter(m -> m.getRenderedMessage().matches("(?s)^Replicas:.*"))
.collect(Collectors.toList());
Assert.assertEquals(0, replica.size());
// Ensure we have an error logged:
List<LoggingEvent> error = logs.stream()
.filter(m -> m.getLevel() == Level.ERROR)
.collect(Collectors.toList());
Assert.assertEquals(1, error.size());
Assert.assertTrue(error.get(0).getRenderedMessage()
.matches("(?s)^Unable to retrieve the replica details.*"));
}
@Test
public void testReplicasNotOutputIfErrorWithJson() throws IOException {
Mockito.when(scmClient.getContainerReplicas(anyLong()))
.thenThrow(new IOException("Error getting Replicas"));
cmd = new InfoSubcommand();
CommandLine c = new CommandLine(cmd);
c.parseArgs("1", "--json");
cmd.execute(scmClient);
List<LoggingEvent> logs = appender.getLog();
Assert.assertEquals(2, logs.size());
String error = logs.get(0).getRenderedMessage();
String json = logs.get(1).getRenderedMessage();
Assert.assertTrue(error
.matches("(?s)^Unable to retrieve the replica details.*"));
Assert.assertFalse(json.matches("(?s).*replicas.*"));
}
@Test
public void testReplicasOutputWithJson() throws IOException {
Mockito.when(scmClient.getContainerReplicas(anyLong()))
.thenReturn(getReplicas(true));
testJsonOutput();
}
@Test
public void testECContainerReplicasOutputWithJson() throws IOException {
Mockito.when(scmClient.getContainerReplicas(anyLong()))
.thenReturn(getReplicas(true));
Mockito.when(scmClient.getContainerWithPipeline(anyLong()))
.thenReturn(getECContainerWithPipeline());
testJsonOutput();
}
private void testJsonOutput() throws IOException {
cmd = new InfoSubcommand();
CommandLine c = new CommandLine(cmd);
c.parseArgs("1", "--json");
cmd.execute(scmClient);
List<LoggingEvent> logs = appender.getLog();
Assert.assertEquals(1, logs.size());
// Ensure each DN UUID is mentioned in the message after replicas:
String json = logs.get(0).getRenderedMessage();
Assert.assertTrue(json.matches("(?s).*replicas.*"));
for (DatanodeDetails dn : datanodes) {
Pattern pattern = Pattern.compile(
".*replicas.*" + dn.getUuid().toString() + ".*", Pattern.DOTALL);
Matcher matcher = pattern.matcher(json);
Assert.assertTrue(matcher.matches());
}
Pattern pattern = Pattern.compile(".*replicaIndex.*",
Pattern.DOTALL);
Matcher matcher = pattern.matcher(json);
Assert.assertTrue(matcher.matches());
}
private List<ContainerReplicaInfo> getReplicas(boolean includeIndex) {
List<ContainerReplicaInfo> replicas = new ArrayList<>();
for (DatanodeDetails dn : datanodes) {
ContainerReplicaInfo.Builder container
= new ContainerReplicaInfo.Builder()
.setContainerID(1)
.setBytesUsed(1234)
.setState("CLOSED")
.setPlaceOfBirth(dn.getUuid())
.setDatanodeDetails(dn)
.setKeyCount(1)
.setSequenceId(1);
if (includeIndex) {
container.setReplicaIndex(4);
}
replicas.add(container.build());
}
return replicas;
}
private ContainerWithPipeline getContainerWithPipeline() {
Pipeline pipeline = new Pipeline.Builder()
.setState(Pipeline.PipelineState.CLOSED)
.setReplicationConfig(new RatisReplicationConfig(THREE))
.setId(PipelineID.randomId())
.setNodes(datanodes)
.build();
ContainerInfo container = new ContainerInfo.Builder()
.setSequenceId(1)
.setPipelineID(pipeline.getId())
.setUsedBytes(1234)
.setReplicationConfig(new RatisReplicationConfig(THREE))
.setNumberOfKeys(1)
.setState(CLOSED)
.build();
return new ContainerWithPipeline(container, pipeline);
}
private ContainerWithPipeline getECContainerWithPipeline() {
Pipeline pipeline = new Pipeline.Builder()
.setState(Pipeline.PipelineState.CLOSED)
.setReplicationConfig(new ECReplicationConfig(3, 2))
.setId(PipelineID.randomId())
.setNodes(datanodes)
.build();
ContainerInfo container = new ContainerInfo.Builder()
.setSequenceId(1)
.setPipelineID(pipeline.getId())
.setUsedBytes(1234)
.setReplicationConfig(new RatisReplicationConfig(THREE))
.setNumberOfKeys(1)
.setState(CLOSED)
.build();
return new ContainerWithPipeline(container, pipeline);
}
private List<DatanodeDetails> createDatanodeDetails(int count) {
List<DatanodeDetails> dns = new ArrayList<>();
for (int i = 0; i < count; i++) {
HddsProtos.DatanodeDetailsProto dnd =
HddsProtos.DatanodeDetailsProto.newBuilder()
.setHostName("host" + i)
.setIpAddress("1.2.3." + i + 1)
.setNetworkLocation("/default")
.setNetworkName("host" + i)
.addPorts(HddsProtos.Port.newBuilder()
.setName("ratis").setValue(5678).build())
.setUuid(UUID.randomUUID().toString())
.build();
dns.add(DatanodeDetails.getFromProtoBuf(dnd));
}
return dns;
}
private static class TestAppender extends AppenderSkeleton {
private final List<LoggingEvent> log = new ArrayList<>();
@Override
public boolean requiresLayout() {
return false;
}
@Override
protected void append(final LoggingEvent loggingEvent) {
log.add(loggingEvent);
}
@Override
public void close() {
}
public List<LoggingEvent> getLog() {
return new ArrayList<>(log);
}
}
}