Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,26 +41,67 @@ public abstract class SubCommandBase {
private String peers;

public static RaftPeer[] parsePeers(String peers) {
return Stream.of(peers.split(",")).map(address -> {
String[] addressParts = address.split(":");
if (addressParts.length < 3) {
throw new IllegalArgumentException(
"Raft peer " + address + " is not a legitimate format. "
+ "(format: name:host:port:dataStreamPort:clientPort:adminPort)");
return Stream.of(peers.split(",")).map(SubCommandBase::parsePeer).toArray(RaftPeer[]::new);
}

/**
* Parse a single peer definition in the format
* {@code name:host:port:dataStreamPort:clientPort:adminPort}, where the trailing
* ports are optional. The host may be an IPv6 literal enclosed in brackets,
* e.g. {@code n0:[::1]:9000:9001:9002:9003}.
*/
private static RaftPeer parsePeer(String address) {
final int idEnd = address.indexOf(':');
if (idEnd < 0) {
throw illegalFormat(address);
}
final String id = address.substring(0, idEnd);
final String hostAndPorts = address.substring(idEnd + 1);

// Separate the host from the port list, honoring IPv6 bracketed literals.
final String host;
final String portList;
if (hostAndPorts.startsWith("[")) {
final int bracketEnd = hostAndPorts.indexOf("]:");
if (bracketEnd < 0) {
throw illegalFormat(address);
}
RaftPeer.Builder builder = RaftPeer.newBuilder();
builder.setId(addressParts[0]).setAddress(addressParts[1] + ":" + addressParts[2]);
if (addressParts.length >= 4) {
builder.setDataStreamAddress(addressParts[1] + ":" + addressParts[3]);
if (addressParts.length >= 5) {
builder.setClientAddress(addressParts[1] + ":" + addressParts[4]);
if (addressParts.length >= 6) {
builder.setAdminAddress(addressParts[1] + ":" + addressParts[5]);
}
}
host = hostAndPorts.substring(0, bracketEnd + 1); // include the closing ']'
portList = hostAndPorts.substring(bracketEnd + 2);
} else {
final int hostEnd = hostAndPorts.indexOf(':');
if (hostEnd < 0) {
throw illegalFormat(address);
}
host = hostAndPorts.substring(0, hostEnd);
portList = hostAndPorts.substring(hostEnd + 1);
}

// Use -1 limit so trailing/embedded empty segments (e.g. "6000:" or "6000::6002")
// are preserved and can be rejected rather than silently building "host:".
final String[] ports = portList.split(":", -1);
for (String port : ports) {
if (port.isEmpty()) {
throw illegalFormat(address);
}
return builder.build();
}).toArray(RaftPeer[]::new);
}
final RaftPeer.Builder builder = RaftPeer.newBuilder();
builder.setId(id).setAddress(host + ":" + ports[0]);
if (ports.length >= 2) {
builder.setDataStreamAddress(host + ":" + ports[1]);
}
if (ports.length >= 3) {
builder.setClientAddress(host + ":" + ports[2]);
}
if (ports.length >= 4) {
builder.setAdminAddress(host + ":" + ports[3]);
}
return builder.build();
}

private static IllegalArgumentException illegalFormat(String address) {
return new IllegalArgumentException("Raft peer " + address + " is not a legitimate format. "
+ "(format: name:host:port:dataStreamPort:clientPort:adminPort)");
}

public RaftPeer[] getPeers() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@

import java.util.Collection;
import java.util.Collections;
import org.apache.ratis.protocol.RaftPeer;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
Comment on lines 22 to 28

Expand All @@ -38,4 +40,45 @@ public void testParsePeers(String peers) {
() -> SubCommandBase.parsePeers(peers));
}

@Test
public void testParseIpv4Peer() {
final RaftPeer[] peers = SubCommandBase.parsePeers("n0:127.0.0.1:6000:6001:6002:6003");
Assertions.assertEquals(1, peers.length);
Assertions.assertEquals("n0", peers[0].getId().toString());
Assertions.assertEquals("127.0.0.1:6000", peers[0].getAddress());
Assertions.assertEquals("127.0.0.1:6001", peers[0].getDataStreamAddress());
Assertions.assertEquals("127.0.0.1:6002", peers[0].getClientAddress());
Assertions.assertEquals("127.0.0.1:6003", peers[0].getAdminAddress());
}

@Test
public void testParseIpv6Peer() {
final RaftPeer[] peers = SubCommandBase.parsePeers("n0:[::1]:6000:6001:6002:6003");
Assertions.assertEquals(1, peers.length);
Assertions.assertEquals("n0", peers[0].getId().toString());
Assertions.assertEquals("[::1]:6000", peers[0].getAddress());
Assertions.assertEquals("[::1]:6001", peers[0].getDataStreamAddress());
Assertions.assertEquals("[::1]:6002", peers[0].getClientAddress());
Assertions.assertEquals("[::1]:6003", peers[0].getAdminAddress());
}

@Test
public void testParseMixedPeers() {
final RaftPeer[] peers = SubCommandBase.parsePeers("n0:[::1]:6000,n1:127.0.0.1:6001");
Assertions.assertEquals(2, peers.length);
Assertions.assertEquals("[::1]:6000", peers[0].getAddress());
Assertions.assertEquals("127.0.0.1:6001", peers[1].getAddress());
}

@Test
public void testParseEmptyEmbeddedPortRejected() {
Assertions.assertThrows(IllegalArgumentException.class,
() -> SubCommandBase.parsePeers("n0:127.0.0.1:6000::6002"));
}

@Test
public void testParseEmptyTrailingPortRejected() {
Assertions.assertThrows(IllegalArgumentException.class,
() -> SubCommandBase.parsePeers("n0:[::1]:6000:"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.apache.ratis.protocol.RaftPeer;
import org.apache.ratis.protocol.RaftPeerId;
import org.apache.ratis.protocol.exceptions.RaftException;
import org.apache.ratis.util.NetUtils;
import org.apache.ratis.util.function.CheckedFunction;

import java.io.IOException;
Expand Down Expand Up @@ -164,11 +165,12 @@ public static void checkReply(RaftClientReply reply, Supplier<String> message, P
/** Parse the given string as a {@link InetSocketAddress}. */
public static InetSocketAddress parseInetSocketAddress(String address) {
try {
final String[] hostPortPair = address.split(":");
if (hostPortPair.length < 2) {
throw new IllegalArgumentException("Unexpected address format <HOST:PORT>.");
// NetUtils.createSocketAddr also accepts scheme://host:port; the shell only
// expects <HOST:PORT>, so reject a scheme to avoid masking invalid input.
if (address.contains("://")) {
throw new IllegalArgumentException("Unexpected scheme in \"" + address + "\"; expected format <HOST:PORT>.");
}
return new InetSocketAddress(hostPortPair[0], Integer.parseInt(hostPortPair[1]));
return NetUtils.createSocketAddr(address);
} catch (Exception e) {
throw new IllegalArgumentException("Failed to parse the server address parameter \"" + address + "\".", e);
}
Comment thread
smengcl marked this conversation as resolved.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.apache.ratis.shell.cli.sh.command.AbstractCommand;
import org.apache.ratis.shell.cli.sh.command.Context;
import org.apache.ratis.thirdparty.com.google.protobuf.ByteString;
import org.apache.ratis.util.NetUtils;

import java.io.IOException;
import java.io.InputStream;
Expand Down Expand Up @@ -91,7 +92,8 @@ public int run(CommandLine cl) throws IOException {
}
InetSocketAddress inetSocketAddress = CliUtils.parseInetSocketAddress(
peerIdWithAddressArray[peerIdWithAddressArray.length - 1]);
String addressString = inetSocketAddress.getHostString() + ":" + inetSocketAddress.getPort();
// Use address2String so that IPv6 literals are surrounded with '[', ']'.
String addressString = NetUtils.address2String(inetSocketAddress);
if (addresses.contains(addressString)) {
printf("Found duplicated address: %s. Please make sure the address of peer have no duplicated value.",
addressString);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* 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.ratis.shell.cli;

import org.apache.ratis.protocol.RaftPeer;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetSocketAddress;
import java.util.List;

public class TestCliUtils {

@Test
public void testParseIpv4Address() {
final InetSocketAddress addr = CliUtils.parseInetSocketAddress("127.0.0.1:6000");
Assertions.assertEquals(6000, addr.getPort());
Assertions.assertTrue(addr.getAddress() instanceof Inet4Address);
}

@Test
public void testParseIpv6Address() {
final InetSocketAddress addr = CliUtils.parseInetSocketAddress("[::1]:6000");
Assertions.assertEquals(6000, addr.getPort());
Assertions.assertTrue(addr.getAddress() instanceof Inet6Address);
}

@Test
public void testParseIpv6Peers() {
final List<RaftPeer> peers = CliUtils.parseRaftPeers("[::1]:6000,[::1]:6001");
Assertions.assertEquals(2, peers.size());
Assertions.assertEquals(6000, CliUtils.parseInetSocketAddress(peers.get(0).getAddress()).getPort());
Assertions.assertEquals(6001, CliUtils.parseInetSocketAddress(peers.get(1).getAddress()).getPort());
}

@Test
public void testParseMissingPort() {
Assertions.assertThrows(IllegalArgumentException.class,
() -> CliUtils.parseInetSocketAddress("127.0.0.1"));
}

@Test
public void testParseRejectsScheme() {
Assertions.assertThrows(IllegalArgumentException.class,
() -> CliUtils.parseInetSocketAddress("http://127.0.0.1:6000"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,16 @@
import org.apache.ratis.proto.RaftProtos.RaftConfigurationProto;
import org.apache.ratis.proto.RaftProtos.RaftPeerProto;
import org.apache.ratis.thirdparty.com.google.protobuf.ByteString;
import org.apache.ratis.util.NetUtils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Inet6Address;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
Expand Down Expand Up @@ -121,6 +124,38 @@ public void testRunMethod(@TempDir Path tempDir) throws Exception {
}


@Test
public void testRunMethodWithIpv6(@TempDir Path tempDir) throws Exception {
final int index = 1;
generateRaftConf(tempDir.resolve(RAFT_META_CONF), index);

// IPv6 literals must be enclosed in brackets, e.g. [2001:db8::1]:9872.
final String peersListStr = "peer1_ID|[2001:db8::1]:9872,peer2_ID|[2001:db8::2]:9873";
StringPrintStream out = new StringPrintStream();
RatisShell shell = new RatisShell(out.getPrintStream());
int ret = shell.run("local", "raftMetaConf", "-peers", peersListStr, "-path", tempDir.toString());
Assertions.assertEquals(0, ret);

final List<RaftPeerProto> peers;
try (InputStream in = Files.newInputStream(tempDir.resolve(NEW_RAFT_META_CONF))) {
peers = LogEntryProto.newBuilder().mergeFrom(in).build()
.getConfigurationEntry().getPeersList();
}
Assertions.assertEquals(2, peers.size());

// Each written address must stay bracketed and round-trip to an IPv6 address with the same port.
final int[] expectedPorts = {9872, 9873};
for (int i = 0; i < peers.size(); i++) {
final String address = peers.get(i).getAddress();
Assertions.assertTrue(address.startsWith("[") && address.contains("]:"),
() -> "IPv6 address is not bracketed: " + address);
final InetSocketAddress parsed = NetUtils.createSocketAddr(address);
Assertions.assertTrue(parsed.getAddress() instanceof Inet6Address,
() -> "Not an IPv6 address: " + address);
Assertions.assertEquals(expectedPorts[i], parsed.getPort());
}
}

private void generateRaftConf(Path path, int index) throws IOException {
Map<String, String> map = new HashMap<>();
map.put("peer1_ID", "host1:9872");
Expand Down
Loading