Skip to content

Commit

Permalink
IPv6 support in backup client
Browse files Browse the repository at this point in the history
Added support for parsing command line input including IPv6 addresses
  • Loading branch information
phughk committed Jul 24, 2018
1 parent 36f3e11 commit cb7b886
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

import org.neo4j.helpers.HostnamePort;
import org.neo4j.helpers.collection.Pair;

import static org.neo4j.function.Predicates.not;

public class Converters
{
Expand Down Expand Up @@ -109,36 +118,26 @@ public static Function<String,Integer> toInt()
return Integer::new;
}

/**
* Takes a raw address that can have a single port or 2 ports (lower and upper bounds of port range) and
* processes it to a clean separation of host and ports. When only one port is specified, it is in the lower bound.
* The presence of an upper bound implies a range.
*
* @param rawAddress the raw address that a user can provide via config or command line
* @return the host, lower bound port, and upper bound port
*/
public static OptionalHostnamePort toOptionalHostnamePortFromRawAddress( String rawAddress )
{
return new OptionalHostnamePort(
toHostnameFromRawAddress( rawAddress ),
toPortFromRawAddress( rawAddress ),
toPortUpperRangeFromRawAddress( rawAddress ) );
}

private static Optional<String> toHostnameFromRawAddress( String rawAddress )
{
return Optional.ofNullable( rawAddress )
.map( addr -> addr.split( ":" )[0] )
.filter( addr -> !"".equals( addr ) );
}

private static Optional<Integer> toPortFromRawAddress( String rawAddress )
{
return Optional.ofNullable( rawAddress )
.map( addr -> addr.split( ":" ) )
.filter( parts -> parts.length >= 2 )
.map( parts -> parts[1] )
.map( Integer::parseInt );
HostnamePort hostnamePort = new HostnamePort( rawAddress );
Optional<String> processedHost = Optional.ofNullable( hostnamePort.getHost() )
.map( str -> str.replaceAll( "\\[", "" ) )
.map( str -> str.replaceAll( "]", "" ) );
return new OptionalHostnamePort( processedHost, optionalFromZeroable( hostnamePort.getPorts()[0] ),
optionalFromZeroable( hostnamePort.getPorts()[1] ) );
}

private static Optional<Integer> toPortUpperRangeFromRawAddress( String rawAddress )
private static Optional<Integer> optionalFromZeroable( int port )
{
return Optional.ofNullable( rawAddress )
.map( addr -> addr.split( ":" ) )
.filter( parts -> parts.length == 3 )
.map( parts -> parts[2] )
.map( Integer::parseInt );
return port == 0 ? Optional.empty() : Optional.of( port );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,21 +54,10 @@ public Optional<Integer> getPort()
return port;
}

public HostnamePort resolve()
public Optional<Integer> getUpperRangePort()
{
if ( !hostname.isPresent() )
{
throw new IllegalStateException( "Hostname must be established before resolving" );
}
if ( !port.isPresent() )
{
throw new IllegalStateException( "Port must be established before resolving" );
}
return upperRangePort
.map( upperRange -> new HostnamePort( hostname.get(), port.get(), upperRange ) )
.orElseGet( () -> new HostnamePort( hostname.get(), port.get() ) );
return upperRangePort;
}

@Override
public String toString()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

import org.neo4j.helpers.collection.Pair;
import org.neo4j.test.rule.TestDirectory;

import static org.junit.Assert.assertArrayEquals;
Expand Down Expand Up @@ -125,6 +128,47 @@ public void emptyOptionalWhenOnlyPort()
assertFalse( hostname.isPresent() );
}

@Test
public void ipv6Works()
{
// with
String full = "1234:5678:9abc:def0:1234:5678:9abc:def0";
List<Pair<String,OptionalHostnamePort>> cases = Arrays.asList(
Pair.of( "[::1]", new OptionalHostnamePort( "::1", null, null ) ),
Pair.of( "[3FFe::1]", new OptionalHostnamePort( "3FFe::1", null, null ) ),
Pair.of( "[::1]:2", new OptionalHostnamePort( "::1", 2, 2 ) ),
Pair.of( "[" + full + "]", new OptionalHostnamePort( full, null, null ) ),
Pair.of( "[" + full + "]" + ":5432", new OptionalHostnamePort( full, 5432, 5432 ) ),
Pair.of( "[1::2]:3-4", new OptionalHostnamePort( "1::2", 3, 4 ) ) );
for ( Pair<String,OptionalHostnamePort> useCase : cases )
{
// given
String caseInput = useCase.first();
OptionalHostnamePort caseOutput = useCase.other();

// when
OptionalHostnamePort optionalHostnamePort = toOptionalHostnamePortFromRawAddress( caseInput );

// then
String msg = String.format( "\"%s\" -> %s", caseInput, caseOutput );
assertEquals( msg, caseOutput.getHostname(), optionalHostnamePort.getHostname() );
assertEquals( msg, caseOutput.getPort(), optionalHostnamePort.getPort() );
assertEquals( msg, caseOutput.getUpperRangePort(), optionalHostnamePort.getUpperRangePort() );
}
}

@Test
public void trailingColonIgnored()
{
// when
OptionalHostnamePort optionalHostnamePort = toOptionalHostnamePortFromRawAddress( "localhost::" );

// then
assertEquals( "localhost", optionalHostnamePort.getHostname().get() );
assertFalse( optionalHostnamePort.getPort().isPresent() );
assertFalse( optionalHostnamePort.getUpperRangePort().isPresent() );
}

private File existenceOfFile( String name ) throws IOException
{
File file = directory.file( name );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
package org.neo4j.backup.impl;

import java.nio.file.Path;
import java.util.Arrays;

import org.neo4j.commandline.admin.AdminCommand;
import org.neo4j.commandline.admin.CommandFailed;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,22 @@
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.IntFunction;

import org.neo4j.causalclustering.ClusterHelper;
import org.neo4j.causalclustering.core.CausalClusteringSettings;
import org.neo4j.causalclustering.core.CoreGraphDatabase;
import org.neo4j.causalclustering.core.consensus.roles.Role;
import org.neo4j.causalclustering.discovery.Cluster;
import org.neo4j.causalclustering.discovery.CoreClusterMember;
import org.neo4j.causalclustering.discovery.DiscoveryServiceFactory;
import org.neo4j.causalclustering.discovery.IpFamily;
import org.neo4j.causalclustering.discovery.SharedDiscoveryServiceFactory;
import org.neo4j.causalclustering.helpers.CausalClusteringTestHelpers;
import org.neo4j.graphdb.factory.GraphDatabaseSettings;
import org.neo4j.kernel.configuration.Config;
Expand All @@ -66,6 +73,7 @@
import static java.lang.String.format;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeFalse;

Expand Down Expand Up @@ -269,6 +277,46 @@ public void reportsProgress() throws Exception
assertTrue( output.contains( "Finished receiving index snapshots" ) );
}

@Test
public void ipv6Enabled() throws Exception
{
// given
Cluster cluster = startIpv6Cluster();
try
{
assertNotNull( DbRepresentation.of( clusterDatabase( cluster ) ) );
int port = clusterLeader( cluster ).config().get( CausalClusteringSettings.transaction_listen_address ).getPort();
String customAddress = String.format( "[%s]:%d", IpFamily.IPV6.localhostAddress(), port );
String backupName = "backup_" + recordFormat;

// when full backup
assertEquals( 0, runBackupToolFromSameJvmToGetExitCode(
"--from", customAddress,
"--protocol=catchup",
"--cc-report-dir=" + backupDir,
"--backup-dir=" + backupDir,
"--name=" + backupName ) );

// and
createSomeData( cluster );

// and incremental backup
assertEquals( 0, runBackupToolFromSameJvmToGetExitCode(
"--from", customAddress,
"--protocol=catchup",
"--cc-report-dir=" + backupDir,
"--backup-dir=" + backupDir,
"--name=" + backupName ) );

// then
assertEquals( DbRepresentation.of( clusterDatabase( cluster ) ), getBackupDbRepresentation( backupName, backupDir ) );
}
finally
{
cluster.shutdown();
}
}

static PrintStream wrapWithNormalOutput( PrintStream normalOutput, PrintStream nullAbleOutput )
{
if ( nullAbleOutput == null )
Expand Down Expand Up @@ -342,6 +390,25 @@ private Cluster startCluster( String recordFormat ) throws Exception
return cluster;
}

private Cluster startIpv6Cluster() throws ExecutionException, InterruptedException
{
DiscoveryServiceFactory discoveryServiceFactory = new SharedDiscoveryServiceFactory();
File parentDir = testDirectory.directory( "ipv6_cluster" );
Map<String,String> coreParams = new HashMap<>();
coreParams.put( GraphDatabaseSettings.record_format.name(), recordFormat );
Map<String,IntFunction<String>> instanceCoreParams = new HashMap<>();

Map<String,String> readReplicaParams = new HashMap<>();
readReplicaParams.put( GraphDatabaseSettings.record_format.name(), recordFormat );
Map<String,IntFunction<String>> instanceReadReplicaParams = new HashMap<>();

Cluster cluster = new Cluster( parentDir, 3, 3, discoveryServiceFactory, coreParams, instanceCoreParams, readReplicaParams, instanceReadReplicaParams,
recordFormat, IpFamily.IPV6, false );
cluster.start();
createSomeData( cluster );
return cluster;
}

public static DbRepresentation createSomeData( Cluster cluster )
{
try
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,20 @@ public void prometheusShouldBeDisabledToAvoidPortConflicts() throws CommandFaile
assertEquals( Settings.FALSE, context.getConfig().getRaw().get( "metrics.prometheus.enabled" ) );
}

@Test
public void ipv6CanBeProcessed() throws CommandFailed, IncorrectUsage
{
// given
OnlineBackupContextBuilder builder = new OnlineBackupContextBuilder( homeDir, configDir );

// when
OnlineBackupContext context = builder.createContext( requiredAnd( "--from=[fd00:ce10::2]:6362" ) );

// then
assertEquals( "fd00:ce10::2", context.getRequiredArguments().getAddress().getHostname().get() );
assertEquals( Integer.valueOf( 6362 ), context.getRequiredArguments().getAddress().getPort().get() );
}

private String[] requiredAnd( String... additionalArgs )
{
List<String> args = new ArrayList<>();
Expand Down

0 comments on commit cb7b886

Please sign in to comment.