Skip to content

Commit

Permalink
Unbind an instance from the cluster.
Browse files Browse the repository at this point in the history
Using the neo4j config to determine the database location.
Re-using existing validator code, and deleted proprietary version of same.
  • Loading branch information
jimwebber committed Oct 20, 2016
1 parent 4b439d6 commit d975153
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 99 deletions.
Expand Up @@ -19,13 +19,20 @@
*/
package org.neo4j.commandline.admin;

import org.neo4j.kernel.StoreLockException;

public class CommandFailed extends Exception
{
public CommandFailed( String message, Exception cause )
{
super( message, cause );
}

public CommandFailed( StoreLockException exception )
{
super( exception );
}

public CommandFailed( String message )
{
super( message );
Expand Down
@@ -0,0 +1,28 @@
/*
* Copyright (c) 2002-2016 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.commandline.dbms;

public class CannotWriteException extends Exception
{
public CannotWriteException( String message )
{
super(message);
}
}
Expand Up @@ -38,8 +38,8 @@
import org.neo4j.dbms.archive.Dumper;
import org.neo4j.graphdb.factory.GraphDatabaseSettings;
import org.neo4j.helpers.Args;
import org.neo4j.io.fs.DefaultFileSystemAbstraction;
import org.neo4j.kernel.StoreLockException;
import org.neo4j.kernel.impl.transaction.command.Command;
import org.neo4j.kernel.internal.StoreLocker;
import org.neo4j.server.configuration.ConfigLoader;

Expand All @@ -53,6 +53,9 @@

public class DumpCommand implements AdminCommand
{

private final StoreLockChecker storeLockChecker;

public static class Provider extends AdminCommand.Provider
{
public Provider()
Expand Down Expand Up @@ -90,6 +93,7 @@ public DumpCommand( Path homeDir, Path configDir, Dumper dumper )
this.homeDir = homeDir;
this.configDir = configDir;
this.dumper = dumper;
this.storeLockChecker = new StoreLockChecker();
}

@Override
Expand All @@ -99,7 +103,7 @@ public void execute( String[] args ) throws IncorrectUsage, CommandFailed
Path archive = calculateArchive( database, parse( args, "to", Paths::get ) );
Path databaseDirectory = toDatabaseDirectory( database );

try ( Closeable ignored = withLock( databaseDirectory ) )
try ( Closeable ignored = storeLockChecker.withLock( databaseDirectory ) )
{
dump( database, databaseDirectory, archive );
}
Expand All @@ -111,6 +115,11 @@ public void execute( String[] args ) throws IncorrectUsage, CommandFailed
{
wrapIOException( e );
}
catch ( CannotWriteException e )
{
throw new CommandFailed( "you do not have permission to dump the database -- is Neo4j running as a " +
"different user?", e );
}
}

private <T> T parse( String[] args, String argument, Function<String, T> converter ) throws IncorrectUsage
Expand Down Expand Up @@ -141,28 +150,6 @@ private Path calculateArchive( String database, Path to )
return Files.isDirectory( to ) ? to.resolve( database + ".dump" ) : to;
}

private Closeable withLock( Path databaseDirectory ) throws CommandFailed
{
Path lockFile = databaseDirectory.resolve( StoreLocker.STORE_LOCK_FILENAME );
if ( Files.exists( lockFile ) )
{
if ( Files.isWritable( lockFile ) )
{
StoreLocker storeLocker = new StoreLocker( new DefaultFileSystemAbstraction() );
storeLocker.checkLock( databaseDirectory.toFile() );
return storeLocker::release;
}
else
{
throw new CommandFailed( "you do not have permission to dump the database -- is Neo4j running as a " +
"different user?" );
}
}
return () ->
{
};
}

private void dump( String database, Path databaseDirectory, Path archive ) throws CommandFailed
{
try
Expand Down
@@ -0,0 +1,54 @@
/*
* Copyright (c) 2002-2016 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.commandline.dbms;

import java.io.Closeable;
import java.nio.file.Files;
import java.nio.file.Path;

import org.neo4j.commandline.admin.CommandFailed;
import org.neo4j.io.fs.DefaultFileSystemAbstraction;
import org.neo4j.kernel.internal.StoreLocker;

public class StoreLockChecker
{
public Closeable withLock( Path databaseDirectory ) throws CommandFailed, CannotWriteException
{
Path lockFile = databaseDirectory.resolve( StoreLocker.STORE_LOCK_FILENAME );
if ( Files.exists( lockFile ) )
{
if ( Files.isWritable( lockFile ) )
{
StoreLocker storeLocker = new StoreLocker( new DefaultFileSystemAbstraction() );

storeLocker.checkLock( databaseDirectory.toFile() );

return storeLocker::release;
}
else
{
throw new CannotWriteException( "Store is not writable. Check permissions and try again." );
}
}
return () ->
{
};
}
}
Expand Up @@ -22,6 +22,7 @@
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.nio.file.AccessDeniedException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
Expand Down Expand Up @@ -117,7 +118,7 @@ public void shouldNotCalculateTheArchiveNameIfPassedAnExistingFile()
}

@Test
public void shouldRespectTheStoreLock() throws IOException, IncorrectUsage
public void shouldRespectTheStoreLock() throws IOException, IncorrectUsage, CommandFailed
{
Path databaseDirectory = homeDir.resolve( "data/databases/foo.db" );
Files.createDirectories( databaseDirectory );
Expand Down Expand Up @@ -318,7 +319,7 @@ public void shouldGiveAClearMessageIfTheArchivesParentDoesntExist() throws IOExc
}
}

private void execute( final String database ) throws IncorrectUsage, CommandFailed
private void execute( final String database ) throws IncorrectUsage, CommandFailed, AccessDeniedException
{
new DumpCommand( homeDir, configDir, dumper )
.execute( new String[]{"--database=" + database, "--to=" + archive} );
Expand Down
Expand Up @@ -20,21 +20,27 @@
package org.neo4j.commandline.dbms;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import org.neo4j.commandline.admin.AdminCommand;
import org.neo4j.commandline.admin.CommandFailed;
import org.neo4j.commandline.admin.IncorrectUsage;
import org.neo4j.commandline.admin.OutsideWorld;
import org.neo4j.dbms.DatabaseManagementSystemSettings;
import org.neo4j.graphdb.factory.GraphDatabaseSettings;
import org.neo4j.helpers.Args;
import org.neo4j.io.fs.DefaultFileSystemAbstraction;
import org.neo4j.io.fs.FileSystemAbstraction;
import org.neo4j.io.fs.FileUtils;
import org.neo4j.kernel.StoreLockException;
import org.neo4j.kernel.internal.StoreLocker;
import org.neo4j.kernel.configuration.Config;
import org.neo4j.kernel.impl.util.Converters;
import org.neo4j.kernel.impl.util.Validators;
import org.neo4j.server.configuration.ConfigLoader;

import static java.lang.String.format;
import static java.nio.file.Files.exists;
Expand Down Expand Up @@ -65,17 +71,17 @@ public String description()
@Override
public AdminCommand create( Path homeDir, Path configDir, OutsideWorld outsideWorld )
{
return new UnbindFromClusterCommand( homeDir, new DefaultFileSystemAbstraction() );
return new UnbindFromClusterCommand( homeDir, configDir );
}
}

private Path homeDir;
private FileSystemAbstraction fsa;
private Path configDir;

UnbindFromClusterCommand( Path homeDir, FileSystemAbstraction fsa )
UnbindFromClusterCommand( Path homeDir, Path configDir )
{
this.homeDir = homeDir;
this.fsa = fsa;
this.configDir = configDir;
}

@Override
Expand All @@ -85,54 +91,38 @@ public void execute( String[] args ) throws IncorrectUsage, CommandFailed

try
{
Path pathToSpecificDatabase =
homeDir.resolve( "data" ).resolve( "databases" ).resolve( databaseNameFrom( parsedArgs ) );
confirmTargetDatabaseDirectoryExists( pathToSpecificDatabase );
Config config = loadNeo4jConfig( homeDir, configDir, databaseNameFrom( parsedArgs ) );
Path pathToSpecificDatabase = config.get( DatabaseManagementSystemSettings.database_path ).toPath();

Validators.CONTAINS_EXISTING_DATABASE.validate( pathToSpecificDatabase.toFile() );
confirmClusterStateDirectoryExists( Paths.get( pathToSpecificDatabase.toString(), "cluster-state" ) );
confirmTargetDirectoryIsWritable( pathToSpecificDatabase );
deleteClusterStateIn( clusterStateFrom( pathToSpecificDatabase ) );
}
catch ( StoreLockException e )
{
throw new CommandFailed(
"Database is currently locked. Please check that no other Neo4j instance using it.", e );
}
catch ( IllegalArgumentException e )
{
throw new IncorrectUsage( e.getMessage() );
}
catch ( UnbindFailureException e )
catch ( UnbindFailureException | CannotWriteException e )
{
throw new CommandFailed( "Unbind failed: " + e.getMessage(), e );
}
}

private Path clusterStateFrom( Path target )
{
return Paths.get( target.toString(), CLUSTER_STATE_DIRECTORY_NAME );
}

private void confirmTargetDirectoryIsWritable( Path dbDir ) throws CommandFailed
private void confirmTargetDirectoryIsWritable( Path pathToSpecificDatabase )
throws CommandFailed, CannotWriteException
{
Path lockFile = dbDir.resolve( StoreLocker.STORE_LOCK_FILENAME );
if ( exists( lockFile ) )
{
if ( Files.isWritable( lockFile ) )
{
StoreLocker storeLocker = new StoreLocker( fsa );
try
{
storeLocker.checkLock( dbDir.toFile() );
}
catch ( StoreLockException e )
{
throw new CommandFailed( "Database is currently locked. Is a Neo4j instance still using it?" );
}
}
}
new StoreLockChecker().withLock( pathToSpecificDatabase );
}

private void confirmTargetDatabaseDirectoryExists( Path target )
private Path clusterStateFrom( Path target )
{
if ( !exists( target ) )
{
throw new IllegalArgumentException( format( "Database %s does not exist", target ) );
}
return Paths.get( target.toString(), CLUSTER_STATE_DIRECTORY_NAME );
}

private void confirmClusterStateDirectoryExists( Path clusterStateDirectory ) throws UnbindFailureException
Expand All @@ -157,16 +147,7 @@ private void deleteClusterStateIn( Path target ) throws UnbindFailureException

private String databaseNameFrom( Args parsedArgs )
{
String databaseName = parsedArgs.get( "database" );
if ( databaseName == null )
{
throw new IllegalArgumentException(
"No database name specified. Usage: neo4j-admin " + "unbind --database-<name>" );
}
else
{
return databaseName;
}
return parsedArgs.interpretOption( "database", Converters.mandatory(), s -> s );
}

private class UnbindFailureException extends Exception
Expand All @@ -181,4 +162,19 @@ private class UnbindFailureException extends Exception
super( format( message, args ) );
}
}

private static Config loadNeo4jConfig( Path homeDir, Path configDir, String databaseName )
{
ConfigLoader configLoader = new ConfigLoader( settings() );
Config config = configLoader.loadConfig( Optional.of( homeDir.toFile() ),
Optional.of( configDir.resolve( "neo4j.conf" ).toFile() ) );
Map<String,String> additionalConfig = new HashMap<>();
additionalConfig.put( DatabaseManagementSystemSettings.active_database.name(), databaseName );
return config.with( additionalConfig );
}

private static List<Class<?>> settings()
{
return Arrays.asList( GraphDatabaseSettings.class, DatabaseManagementSystemSettings.class );
}
}

0 comments on commit d975153

Please sign in to comment.