From 698e41860acd4c7ac8395263295171d081eaf44e Mon Sep 17 00:00:00 2001 From: Jonas Kalderstam Date: Tue, 29 Nov 2016 16:47:13 +0100 Subject: [PATCH] Add HA and CC configuration validation --- .../server/configuration/ConfigLoader.java | 9 +- .../neo4j/kernel/configuration/Config.java | 64 +++++++- .../configuration/ConfigurationValidator.java | 1 + .../impl/proc/ProcedureAllowedConfig.java | 34 +++-- .../kernel/configuration/ConfigTest.java | 33 +++++ community/server/pom.xml | 5 + .../neo4j/server/CommunityBootstrapper.java | 19 +-- .../org/neo4j/server/ServerBootstrapper.java | 10 +- .../neo4j/server/ServerBootstrapperTest.java | 11 +- enterprise/causal-clustering/pom.xml | 5 + .../CausalClusterConfigurationValidator.java | 75 ++++++++++ .../core/CausalClusteringSettings.java | 4 +- .../discovery/ClientConnectorAddresses.java | 24 +-- .../readreplica/ReadReplicaGraphDatabase.java | 1 - .../causalclustering/backup/BackupCoreIT.java | 2 +- ...usalClusterConfigurationValidatorTest.java | 115 ++++++++++++++ enterprise/cluster/pom.xml | 1 - .../org/neo4j/cluster/ClusterSettings.java | 21 ++- .../HaConfigurationValidator.java | 79 ++++++++++ .../HaConfigurationValidatorTest.java | 115 ++++++++++++++ .../impl/proc/ProcedureAllowedConfigTest.java | 140 ++++++++++++++++++ enterprise/server-enterprise/pom.xml | 5 + .../enterprise/EnterpriseBootstrapper.java | 49 ++---- .../enterprise/EnterpriseNeoServer.java | 32 +--- .../enterprise/EnterpriseServerSettings.java | 7 - .../EnterpriseBootstrapperTest.java | 4 +- .../functional/EnterpriseServerIT.java | 4 +- .../neo4j/tools/rebuild/RebuildFromLogs.java | 5 +- 28 files changed, 733 insertions(+), 141 deletions(-) create mode 100644 enterprise/causal-clustering/src/main/java/org/neo4j/causalclustering/core/CausalClusterConfigurationValidator.java create mode 100644 enterprise/causal-clustering/src/test/java/org/neo4j/causalclustering/core/CausalClusterConfigurationValidatorTest.java create mode 100644 enterprise/cluster/src/main/java/org/neo4j/configuration/HaConfigurationValidator.java create mode 100644 enterprise/cluster/src/test/java/org/neo4j/configuration/HaConfigurationValidatorTest.java create mode 100644 enterprise/security/src/test/java/org/neo4j/kernel/impl/proc/ProcedureAllowedConfigTest.java diff --git a/community/dbms/src/main/java/org/neo4j/server/configuration/ConfigLoader.java b/community/dbms/src/main/java/org/neo4j/server/configuration/ConfigLoader.java index bfaa7336250ed..cb0b8a65ae233 100644 --- a/community/dbms/src/main/java/org/neo4j/server/configuration/ConfigLoader.java +++ b/community/dbms/src/main/java/org/neo4j/server/configuration/ConfigLoader.java @@ -21,6 +21,7 @@ import java.io.File; import java.io.IOException; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -29,8 +30,8 @@ import org.neo4j.graphdb.factory.GraphDatabaseSettings; import org.neo4j.helpers.collection.Pair; -import org.neo4j.kernel.configuration.BoltConnector; import org.neo4j.kernel.configuration.Config; +import org.neo4j.kernel.configuration.ConfigurationValidator; import org.neo4j.kernel.configuration.Connector; import org.neo4j.kernel.configuration.Settings; @@ -58,14 +59,14 @@ public static Config loadConfig( Optional homeDir, Optional configFi public static Config loadServerConfig( Optional configFile, Pair... configOverrides ) throws IOException { - return loadServerConfig( Optional.empty(), configFile, configOverrides ); + return loadServerConfig( Optional.empty(), configFile, configOverrides, Collections.emptyList() ); } public static Config loadServerConfig( Optional homeDir, Optional configFile, - Pair[] configOverrides ) + Pair[] configOverrides, Collection additionalValidators ) { Map overriddenSettings = calculateSettings( homeDir, configOverrides ); - return Config.serverDefaults( configFile, overriddenSettings, Collections.emptyList() ); + return Config.serverDefaults( configFile, overriddenSettings, additionalValidators ); } public static Config loadConfigWithConnectorsDisabled( Optional homeDir, Optional configFile, diff --git a/community/kernel/src/main/java/org/neo4j/kernel/configuration/Config.java b/community/kernel/src/main/java/org/neo4j/kernel/configuration/Config.java index 737b3a13d7481..a87057c5d3ab6 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/configuration/Config.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/configuration/Config.java @@ -171,7 +171,7 @@ public static Config serverDefaults( Map additionalConfig ) public static Config serverDefaults( Optional configFile, Map additionalConfig, Collection additionalValidators ) { - ArrayList validators = new ArrayList<>( ); + ArrayList validators = new ArrayList<>(); validators.addAll( additionalValidators ); validators.add( new ServerConfigurationValidator() ); @@ -288,7 +288,7 @@ public void setLogger( Log log ) { if ( this.log instanceof BufferingLog ) { - ( (BufferingLog) this.log ).replayInto( log ); + ((BufferingLog) this.log).replayInto( log ); } this.log = log; } @@ -407,6 +407,7 @@ private synchronized void replaceSettings( Map newSettings ) params.putAll( validSettings ); } + @Nonnull private static Map initSettings( @Nonnull Optional configFile, @Nonnull Consumer> settingsPostProcessor, @Nonnull Map overriddenSettings, @@ -419,6 +420,7 @@ private static Map initSettings( @Nonnull Optional configFi return settings; } + @Nonnull private static Map loadFromFile( @Nonnull File file, @Nonnull Log log ) { if ( !file.exists() ) @@ -440,7 +442,17 @@ private static Map loadFromFile( @Nonnull File file, @Nonnull Log /** * @return a list of all connector names like 'http' in 'dbms.connector.http.enabled = true' */ + @Nonnull public List allConnectorIdentifiers() + { + return allConnectorIdentifiers( params ); + } + + /** + * @return a list of all connector names like 'http' in 'dbms.connector.http.enabled = true' + */ + @Nonnull + public static List allConnectorIdentifiers( @Nonnull Map params ) { Pattern pattern = Pattern.compile( Pattern.quote( "dbms.connector." ) + "([^\\.]+)\\.(.+)" ); @@ -456,9 +468,19 @@ public List allConnectorIdentifiers() /** * @return list of all configured bolt connectors */ + @Nonnull public List boltConnectors() { - return allConnectorIdentifiers().stream() + return boltConnectors( params ); + } + + /** + * @return list of all configured bolt connectors + */ + @Nonnull + public static List boltConnectors( @Nonnull Map params ) + { + return allConnectorIdentifiers( params ).stream() .map( BoltConnector::new ) .filter( c -> c.group.groupKey.equalsIgnoreCase( "bolt" ) || BOLT.equals( c.type.apply( params::get ) ) ) @@ -468,9 +490,19 @@ public List boltConnectors() /** * @return list of all configured bolt connectors which are enabled */ + @Nonnull public List enabledBoltConnectors() { - return boltConnectors().stream() + return enabledBoltConnectors( params ); + } + + /** + * @return list of all configured bolt connectors which are enabled + */ + @Nonnull + public static List enabledBoltConnectors( @Nonnull Map params ) + { + return boltConnectors( params ).stream() .filter( c -> c.enabled.apply( params::get ) ) .collect( Collectors.toList() ); } @@ -478,9 +510,19 @@ public List enabledBoltConnectors() /** * @return list of all configured http connectors */ + @Nonnull public List httpConnectors() { - return allConnectorIdentifiers().stream() + return httpConnectors( params ); + } + + /** + * @return list of all configured http connectors + */ + @Nonnull + public static List httpConnectors( @Nonnull Map params ) + { + return allConnectorIdentifiers( params ).stream() .map( name -> new Connector( name, "ignored" ) ) .filter( c -> c.group.groupKey.equalsIgnoreCase( "http" ) || c.group.groupKey.equalsIgnoreCase( "https" ) || @@ -509,9 +551,19 @@ public List httpConnectors() /** * @return list of all configured http connectors which are enabled */ + @Nonnull public List enabledHttpConnectors() { - return httpConnectors().stream() + return enabledHttpConnectors( params ); + } + + /** + * @return list of all configured http connectors which are enabled + */ + @Nonnull + public static List enabledHttpConnectors( @Nonnull Map params ) + { + return httpConnectors( params ).stream() .filter( c -> c.enabled.apply( params::get ) ) .collect( Collectors.toList() ); } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/configuration/ConfigurationValidator.java b/community/kernel/src/main/java/org/neo4j/kernel/configuration/ConfigurationValidator.java index 2fe52e926b825..c8e8043ef881e 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/configuration/ConfigurationValidator.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/configuration/ConfigurationValidator.java @@ -43,6 +43,7 @@ public interface ConfigurationValidator * @return a Map of valid keys and values. * @throws InvalidSettingException in case of invalid values */ + @Nonnull Map validate( @Nonnull Collection settingValidators, @Nonnull Map rawConfig, @Nonnull Log log ) throws InvalidSettingException; diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/proc/ProcedureAllowedConfig.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/proc/ProcedureAllowedConfig.java index a6a37f4d2cc04..4a9b27c978c3b 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/proc/ProcedureAllowedConfig.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/proc/ProcedureAllowedConfig.java @@ -21,13 +21,10 @@ import java.util.Collections; import java.util.List; -import java.util.Map; -import java.util.Optional; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.neo4j.configuration.ConfigValue; import org.neo4j.kernel.configuration.Config; import static java.util.Arrays.stream; @@ -54,19 +51,24 @@ public ProcedureAllowedConfig( Config config ) { this.defaultValue = config.getValue( PROC_ALLOWED_SETTING_DEFAULT_NAME ) .map( Object::toString ) - .orElse( null ); - Optional allowedRoles = config.getValue( PROC_ALLOWED_SETTING_ROLES ); - this.matchers = allowedRoles - .map( o -> Stream.of( o.toString().split( SETTING_DELIMITER ) ) - .map( procToRoleSpec -> - { - String[] spec = procToRoleSpec.split( MAPPING_DELIMITER ); - String[] roles = stream( spec[1].split( ROLES_DELIMITER ) ) - .map( String::trim ).toArray( String[]::new ); - return new ProcMatcher( spec[0].trim(), roles ); - } ) - .collect( Collectors.toList() ) ) - .orElseGet( Collections::emptyList ); + .orElse( "" ); + String allowedRoles = config.getValue( PROC_ALLOWED_SETTING_ROLES ).map( Object::toString ).orElse( "" ); + if ( allowedRoles.isEmpty() ) + { + this.matchers = Collections.emptyList(); + } + else + { + this.matchers = Stream.of( allowedRoles.split( SETTING_DELIMITER ) ) + .map( procToRoleSpec -> + { + String[] spec = procToRoleSpec.split( MAPPING_DELIMITER ); + String[] roles = stream( spec[1].split( ROLES_DELIMITER ) ) + .map( String::trim ).toArray( String[]::new ); + return new ProcMatcher( spec[0].trim(), roles ); + } ) + .collect( Collectors.toList() ); + } } String[] rolesFor( String procedureName ) diff --git a/community/kernel/src/test/java/org/neo4j/kernel/configuration/ConfigTest.java b/community/kernel/src/test/java/org/neo4j/kernel/configuration/ConfigTest.java index 9298ace7ab9eb..3807bdeebba52 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/configuration/ConfigTest.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/configuration/ConfigTest.java @@ -22,20 +22,27 @@ import org.junit.Test; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.Optional; +import javax.annotation.Nonnull; + import org.neo4j.configuration.LoadableConfig; import org.neo4j.graphdb.config.InvalidSettingException; import org.neo4j.graphdb.config.Setting; +import org.neo4j.graphdb.config.SettingValidator; import org.neo4j.logging.Log; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.neo4j.helpers.collection.MapUtil.stringMap; @@ -202,4 +209,30 @@ public void shouldPassOnBufferedLogInWithDefaults() throws Exception verify( log ).warn( "Unknown config option: %s", "third.jibberish" ); verifyNoMoreInteractions( log ); } + + @Test + public void shouldPassOnValidatorsOnWithMethods() throws Exception + { + // Given + ConfigurationValidator validator = spy( new ConfigurationValidator() + { + @Nonnull + @Override + public Map validate( @Nonnull Collection settingValidators, + @Nonnull Map rawConfig, @Nonnull Log log ) throws InvalidSettingException + { + return rawConfig; + } + } ); + + Config first = Config.embeddedDefaults( stringMap( "first.jibberish", "bah" ), + Collections.singleton( validator ) ); + + // When + Config second = first.withDefaults( stringMap( "second.jibberish", "baah" ) ); + second.with( stringMap( "third.jibberish", "baah" ) ); + + // Then + verify( validator, times( 3 ) ).validate( any(), any(), any() ); + } } diff --git a/community/server/pom.xml b/community/server/pom.xml index 4818c9a5750a4..df995ec665104 100644 --- a/community/server/pom.xml +++ b/community/server/pom.xml @@ -181,6 +181,11 @@ ${project.version} + + com.google.code.findbugs + annotations + + org.eclipse.jetty jetty-server diff --git a/community/server/src/main/java/org/neo4j/server/CommunityBootstrapper.java b/community/server/src/main/java/org/neo4j/server/CommunityBootstrapper.java index 13f4d0ad5b8a3..e186ca322659c 100644 --- a/community/server/src/main/java/org/neo4j/server/CommunityBootstrapper.java +++ b/community/server/src/main/java/org/neo4j/server/CommunityBootstrapper.java @@ -19,22 +19,18 @@ */ package org.neo4j.server; -import java.util.List; -import java.util.Map; +import java.util.Collection; +import java.util.Collections; +import javax.annotation.Nonnull; -import org.neo4j.dbms.DatabaseManagementSystemSettings; -import org.neo4j.graphdb.factory.GraphDatabaseSettings; import org.neo4j.kernel.GraphDatabaseDependencies; import org.neo4j.kernel.configuration.Config; +import org.neo4j.kernel.configuration.ConfigurationValidator; +import org.neo4j.kernel.configuration.ServerConfigurationValidator; import org.neo4j.logging.LogProvider; -import org.neo4j.server.configuration.ServerSettings; - -import static java.util.Arrays.asList; public class CommunityBootstrapper extends ServerBootstrapper { - public static final List> settingsClasses = - asList( ServerSettings.class, GraphDatabaseSettings.class, DatabaseManagementSystemSettings.class ); @Override protected NeoServer createNeoServer( Config config, GraphDatabaseDependencies dependencies, @@ -44,9 +40,10 @@ protected NeoServer createNeoServer( Config config, GraphDatabaseDependencies de } @Override - protected Iterable> settingsClasses( Map settings ) + @Nonnull + protected Collection configurationValidators() { - return settingsClasses; + return Collections.singletonList( new ServerConfigurationValidator() ); } } diff --git a/community/server/src/main/java/org/neo4j/server/ServerBootstrapper.java b/community/server/src/main/java/org/neo4j/server/ServerBootstrapper.java index 34958b4a04ef1..51ffdf07f3774 100644 --- a/community/server/src/main/java/org/neo4j/server/ServerBootstrapper.java +++ b/community/server/src/main/java/org/neo4j/server/ServerBootstrapper.java @@ -20,16 +20,18 @@ package org.neo4j.server; import java.io.File; -import java.util.Map; +import java.util.Collection; import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; +import javax.annotation.Nonnull; import org.neo4j.graphdb.TransactionFailureException; import org.neo4j.graphdb.factory.GraphDatabaseSettings; import org.neo4j.helpers.collection.Pair; import org.neo4j.kernel.GraphDatabaseDependencies; import org.neo4j.kernel.configuration.Config; +import org.neo4j.kernel.configuration.ConfigurationValidator; import org.neo4j.kernel.configuration.HttpConnector.Encryption; import org.neo4j.kernel.info.JvmChecker; import org.neo4j.kernel.info.JvmMetadataRepository; @@ -149,7 +151,8 @@ public NeoServer getServer() protected abstract NeoServer createNeoServer( Config config, GraphDatabaseDependencies dependencies, LogProvider userLogProvider ); - protected abstract Iterable> settingsClasses( Map settings ); + @Nonnull + protected abstract Collection configurationValidators(); private static LogProvider setupLogging( Config config ) { @@ -165,7 +168,8 @@ private static LogProvider setupLogging( Config config ) private Config createConfig( File homeDir, Optional file, Pair[] configOverrides ) { - return ConfigLoader.loadConfig( Optional.of( homeDir ), file, configOverrides ); + return ConfigLoader.loadServerConfig( Optional.of( homeDir ), file, configOverrides, + configurationValidators() ); } private void addShutdownHook() diff --git a/community/server/src/test/java/org/neo4j/server/ServerBootstrapperTest.java b/community/server/src/test/java/org/neo4j/server/ServerBootstrapperTest.java index b15f9ffc36a61..4fb0b77a6cafd 100644 --- a/community/server/src/test/java/org/neo4j/server/ServerBootstrapperTest.java +++ b/community/server/src/test/java/org/neo4j/server/ServerBootstrapperTest.java @@ -24,11 +24,14 @@ import java.io.File; import java.nio.file.Files; -import java.util.Map; +import java.util.Collection; +import java.util.Collections; import java.util.Optional; +import javax.annotation.Nonnull; import org.neo4j.kernel.GraphDatabaseDependencies; import org.neo4j.kernel.configuration.Config; +import org.neo4j.kernel.configuration.ConfigurationValidator; import org.neo4j.logging.LogProvider; import org.neo4j.test.rule.SuppressOutput; @@ -55,11 +58,11 @@ protected NeoServer createNeoServer( Config config, GraphDatabaseDependencies de return mock( NeoServer.class ); } + @Nonnull @Override - protected Iterable> settingsClasses( Map settings ) + protected Collection configurationValidators() { - // simulate validation failure - throw new IllegalArgumentException( "Missing mandatory setting" ); + return Collections.emptyList(); } }; diff --git a/enterprise/causal-clustering/pom.xml b/enterprise/causal-clustering/pom.xml index 804ac6c7a8b86..09b5dcb8e1582 100644 --- a/enterprise/causal-clustering/pom.xml +++ b/enterprise/causal-clustering/pom.xml @@ -81,6 +81,11 @@ 3.7.2 + + com.google.code.findbugs + annotations + + org.neo4j neo4j-io diff --git a/enterprise/causal-clustering/src/main/java/org/neo4j/causalclustering/core/CausalClusterConfigurationValidator.java b/enterprise/causal-clustering/src/main/java/org/neo4j/causalclustering/core/CausalClusterConfigurationValidator.java new file mode 100644 index 0000000000000..1675c87985059 --- /dev/null +++ b/enterprise/causal-clustering/src/main/java/org/neo4j/causalclustering/core/CausalClusterConfigurationValidator.java @@ -0,0 +1,75 @@ +/* + * 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 Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.neo4j.causalclustering.core; + +import java.util.Collection; +import java.util.Map; +import java.util.function.Function; +import javax.annotation.Nonnull; + +import org.neo4j.cluster.ClusterSettings; +import org.neo4j.cluster.ClusterSettings.Mode; +import org.neo4j.graphdb.config.InvalidSettingException; +import org.neo4j.graphdb.config.SettingValidator; +import org.neo4j.kernel.configuration.Config; +import org.neo4j.kernel.configuration.ConfigurationValidator; +import org.neo4j.logging.Log; + +import static org.neo4j.causalclustering.core.CausalClusteringSettings.initial_discovery_members; + +public class CausalClusterConfigurationValidator implements ConfigurationValidator +{ + @Override + @Nonnull + public Map validate( @Nonnull Collection settingValidators, + @Nonnull Map rawConfig, @Nonnull Log log ) throws InvalidSettingException + { + // Make sure mode is CC + Mode mode = ClusterSettings.mode.apply( rawConfig::get ); + if ( !mode.equals( Mode.CORE ) && !mode.equals( Mode.READ_REPLICA ) ) + { + // Nothing to validate + return rawConfig; + } + + validateInitialDiscoveryMembers( rawConfig::get ); + validateBoltConnector( rawConfig ); + + return rawConfig; + } + + private static void validateBoltConnector( Map rawConfig ) + { + if ( Config.enabledBoltConnectors( rawConfig ).isEmpty() ) + { + throw new InvalidSettingException( "A Bolt connector must be configured to run a cluster" ); + } + } + + private static void validateInitialDiscoveryMembers( Function provider ) + { + if ( initial_discovery_members.apply( provider ) == null ) + { + throw new InvalidSettingException( + String.format( "Missing mandatory non-empty value for '%s'", initial_discovery_members.name() ) ); + } + } +} diff --git a/enterprise/causal-clustering/src/main/java/org/neo4j/causalclustering/core/CausalClusteringSettings.java b/enterprise/causal-clustering/src/main/java/org/neo4j/causalclustering/core/CausalClusteringSettings.java index 720ce8b041f8f..fbc9731f0b47a 100644 --- a/enterprise/causal-clustering/src/main/java/org/neo4j/causalclustering/core/CausalClusteringSettings.java +++ b/enterprise/causal-clustering/src/main/java/org/neo4j/causalclustering/core/CausalClusteringSettings.java @@ -34,7 +34,7 @@ import static org.neo4j.kernel.configuration.Settings.BYTES; import static org.neo4j.kernel.configuration.Settings.DURATION; import static org.neo4j.kernel.configuration.Settings.INTEGER; -import static org.neo4j.kernel.configuration.Settings.MANDATORY; +import static org.neo4j.kernel.configuration.Settings.NO_DEFAULT; import static org.neo4j.kernel.configuration.Settings.STRING; import static org.neo4j.kernel.configuration.Settings.TRUE; import static org.neo4j.kernel.configuration.Settings.advertisedAddress; @@ -102,7 +102,7 @@ public class CausalClusteringSettings implements LoadableConfig @Description("A comma-separated list of other members of the cluster to join.") public static final Setting> initial_discovery_members = - setting( "causal_clustering.initial_discovery_members", list( ",", ADVERTISED_SOCKET_ADDRESS ), MANDATORY ); + setting( "causal_clustering.initial_discovery_members", list( ",", ADVERTISED_SOCKET_ADDRESS ), NO_DEFAULT ); @Description("Prevents the network middleware from dumping its own logs. Defaults to true.") public static final Setting disable_middleware_logging = diff --git a/enterprise/causal-clustering/src/main/java/org/neo4j/causalclustering/discovery/ClientConnectorAddresses.java b/enterprise/causal-clustering/src/main/java/org/neo4j/causalclustering/discovery/ClientConnectorAddresses.java index 672c7a5d5321d..44b27e491ea59 100644 --- a/enterprise/causal-clustering/src/main/java/org/neo4j/causalclustering/discovery/ClientConnectorAddresses.java +++ b/enterprise/causal-clustering/src/main/java/org/neo4j/causalclustering/discovery/ClientConnectorAddresses.java @@ -29,14 +29,13 @@ import java.util.stream.Stream; import org.neo4j.helpers.AdvertisedSocketAddress; +import org.neo4j.kernel.configuration.BoltConnector; import org.neo4j.kernel.configuration.Config; -import org.neo4j.server.configuration.ClientConnectorSettings.HttpConnector.Encryption; +import org.neo4j.kernel.configuration.HttpConnector.Encryption; import static org.neo4j.causalclustering.discovery.ClientConnectorAddresses.Scheme.bolt; import static org.neo4j.causalclustering.discovery.ClientConnectorAddresses.Scheme.http; import static org.neo4j.causalclustering.discovery.ClientConnectorAddresses.Scheme.https; -import static org.neo4j.graphdb.factory.GraphDatabaseSettings.boltConnectors; -import static org.neo4j.server.configuration.ClientConnectorSettings.httpConnector; public class ClientConnectorAddresses implements Iterable { @@ -51,17 +50,18 @@ static ClientConnectorAddresses extractFromConfig( Config config ) { List connectorUris = new ArrayList<>(); - connectorUris.add( new ConnectorUri( bolt, boltConnectors( config ).stream().findFirst() - .map( boltConnector -> config.get( boltConnector.advertised_address ) ).orElseThrow( () -> - new IllegalArgumentException( "A Bolt connector must be configured to run a cluster" ) ) ) ); + List boltConnectors = config.enabledBoltConnectors(); - httpConnector( config, Encryption.NONE ) - .map( ( connector ) -> config.get( connector.advertised_address ) ) - .ifPresent( httpsAddress -> connectorUris.add( new ConnectorUri( http, httpsAddress ) ) ); + if (boltConnectors.isEmpty()) { + throw new IllegalArgumentException( "A Bolt connector must be configured to run a cluster" ); + } + + boltConnectors + .forEach( c -> connectorUris.add( new ConnectorUri( bolt, config.get( c.advertised_address ) ) ) ); - httpConnector( config, Encryption.TLS ) - .map( ( connector ) -> config.get( connector.advertised_address ) ) - .ifPresent( httpsAddress -> connectorUris.add( new ConnectorUri( https, httpsAddress ) ) ); + config.enabledHttpConnectors() + .forEach( c -> connectorUris.add( new ConnectorUri( Encryption.NONE.equals(c.encryptionLevel() ) ? + http : https, config.get( c.advertised_address ) ) ) ); return new ClientConnectorAddresses( connectorUris ); } diff --git a/enterprise/causal-clustering/src/main/java/org/neo4j/causalclustering/readreplica/ReadReplicaGraphDatabase.java b/enterprise/causal-clustering/src/main/java/org/neo4j/causalclustering/readreplica/ReadReplicaGraphDatabase.java index 7fcad5fb81637..45ff2694f6d7e 100644 --- a/enterprise/causal-clustering/src/main/java/org/neo4j/causalclustering/readreplica/ReadReplicaGraphDatabase.java +++ b/enterprise/causal-clustering/src/main/java/org/neo4j/causalclustering/readreplica/ReadReplicaGraphDatabase.java @@ -24,7 +24,6 @@ import java.util.Map; import java.util.function.Function; -import org.neo4j.causalclustering.core.CausalClusterConfigurationValidator; import org.neo4j.causalclustering.discovery.DiscoveryServiceFactory; import org.neo4j.causalclustering.discovery.HazelcastDiscoveryServiceFactory; import org.neo4j.kernel.configuration.Config; diff --git a/enterprise/causal-clustering/src/test/java/org/neo4j/causalclustering/backup/BackupCoreIT.java b/enterprise/causal-clustering/src/test/java/org/neo4j/causalclustering/backup/BackupCoreIT.java index 79aa69670fee5..23acef05d0469 100644 --- a/enterprise/causal-clustering/src/test/java/org/neo4j/causalclustering/backup/BackupCoreIT.java +++ b/enterprise/causal-clustering/src/test/java/org/neo4j/causalclustering/backup/BackupCoreIT.java @@ -115,6 +115,6 @@ static String[] backupArguments( String from, File backupsDir, String name ) static Config getConfig() { - return new Config( MapUtil.stringMap( GraphDatabaseSettings.record_format.name(), Standard.LATEST_NAME ) ); + return Config.embeddedDefaults( MapUtil.stringMap( GraphDatabaseSettings.record_format.name(), Standard.LATEST_NAME ) ); } } diff --git a/enterprise/causal-clustering/src/test/java/org/neo4j/causalclustering/core/CausalClusterConfigurationValidatorTest.java b/enterprise/causal-clustering/src/test/java/org/neo4j/causalclustering/core/CausalClusterConfigurationValidatorTest.java new file mode 100644 index 0000000000000..b484e56b31ff3 --- /dev/null +++ b/enterprise/causal-clustering/src/test/java/org/neo4j/causalclustering/core/CausalClusterConfigurationValidatorTest.java @@ -0,0 +1,115 @@ +/* + * 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 Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.neo4j.causalclustering.core; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.neo4j.cluster.ClusterSettings; +import org.neo4j.graphdb.config.InvalidSettingException; +import org.neo4j.graphdb.factory.GraphDatabaseSettings; +import org.neo4j.helpers.AdvertisedSocketAddress; +import org.neo4j.kernel.configuration.Config; + +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; +import static org.neo4j.causalclustering.core.CausalClusteringSettings.initial_discovery_members; +import static org.neo4j.helpers.collection.MapUtil.stringMap; + + +@RunWith( Parameterized.class ) +public class CausalClusterConfigurationValidatorTest +{ + @Rule + public ExpectedException expected = ExpectedException.none(); + + @Parameterized.Parameter + public ClusterSettings.Mode mode; + + @Parameterized.Parameters( name = "{0}" ) + public static List recordFormats() + { + return Arrays.asList( ClusterSettings.Mode.CORE, ClusterSettings.Mode.READ_REPLICA ); + } + + @Test + public void validateOnlyIfModeIsCoreOrReplica() throws Exception + { + // when + Config config = Config.embeddedDefaults( + stringMap( ClusterSettings.mode.name(), ClusterSettings.Mode.SINGLE.name(), + initial_discovery_members.name(), "" ), + Collections.singleton( new CausalClusterConfigurationValidator() ) ); + + // then + assertEquals( "", config.getRaw( initial_discovery_members.name() ).get() ); + } + + @Test + public void validateSuccess() throws Exception + { + // when + Config config = Config.embeddedDefaults( + stringMap( ClusterSettings.mode.name(), mode.name(), + initial_discovery_members.name(), "localhost:99,remotehost:2", + GraphDatabaseSettings.boltConnector( "bolt" ).enabled.name(), "true" ), + Collections.singleton( new CausalClusterConfigurationValidator() ) ); + + // then + assertEquals( asList( new AdvertisedSocketAddress( "localhost", 99 ), + new AdvertisedSocketAddress( "remotehost", 2 ) ), + config.get( initial_discovery_members ) ); + } + + @Test + public void missingInitialMembers() throws Exception + { + // then + expected.expect( InvalidSettingException.class ); + expected.expectMessage( "Missing mandatory non-empty value for 'causal_clustering.initial_discovery_members'" ); + + // when + Config.embeddedDefaults( + stringMap( ClusterSettings.mode.name(), mode.name() ), + Collections.singleton( new CausalClusterConfigurationValidator() ) ); + } + + @Test + public void missingBoltConnector() throws Exception + { + // then + expected.expect( InvalidSettingException.class ); + expected.expectMessage( "A Bolt connector must be configured to run a cluster" ); + + // when + Config.embeddedDefaults( + stringMap( ClusterSettings.mode.name(), mode.name(), + initial_discovery_members.name(), "localhost:99,remotehost:2" ), + Collections.singleton( new CausalClusterConfigurationValidator() ) ); + } +} diff --git a/enterprise/cluster/pom.xml b/enterprise/cluster/pom.xml index f95feb95cc1a6..2ea30dd874d45 100644 --- a/enterprise/cluster/pom.xml +++ b/enterprise/cluster/pom.xml @@ -61,7 +61,6 @@ com.google.code.findbugs annotations - test org.neo4j diff --git a/enterprise/cluster/src/main/java/org/neo4j/cluster/ClusterSettings.java b/enterprise/cluster/src/main/java/org/neo4j/cluster/ClusterSettings.java index 116f6ecbd11da..16ca605d72a48 100644 --- a/enterprise/cluster/src/main/java/org/neo4j/cluster/ClusterSettings.java +++ b/enterprise/cluster/src/main/java/org/neo4j/cluster/ClusterSettings.java @@ -33,13 +33,14 @@ import static org.neo4j.kernel.configuration.Settings.DURATION; import static org.neo4j.kernel.configuration.Settings.HOSTNAME_PORT; import static org.neo4j.kernel.configuration.Settings.INTEGER; -import static org.neo4j.kernel.configuration.Settings.MANDATORY; +import static org.neo4j.kernel.configuration.Settings.NO_DEFAULT; import static org.neo4j.kernel.configuration.Settings.STRING; import static org.neo4j.kernel.configuration.Settings.TRUE; import static org.neo4j.kernel.configuration.Settings.illegalValueMessage; import static org.neo4j.kernel.configuration.Settings.list; import static org.neo4j.kernel.configuration.Settings.matches; import static org.neo4j.kernel.configuration.Settings.min; +import static org.neo4j.kernel.configuration.Settings.options; import static org.neo4j.kernel.configuration.Settings.setting; /** @@ -70,8 +71,22 @@ public String toString() } }; + public enum Mode + { + SINGLE, + HA, + ARBITER, + CORE, + READ_REPLICA + } + + @Description( "Configure the operating mode of the database -- 'SINGLE' for stand-alone operation, 'HA' for " + + "operating as a member in a cluster, 'ARBITER' for an HA-only cluster member with no database, " + + "CORE for a core member of a Causal Clustering cluster, or READ_REPLICA for read replica." ) + public static final Setting mode = setting( "dbms.mode", options( Mode.class ), Mode.SINGLE.name() ); + @Description( "Id for a cluster instance. Must be unique within the cluster." ) - public static final Setting server_id = setting( "ha.server_id", INSTANCE_ID, MANDATORY ); + public static final Setting server_id = setting( "ha.server_id", INSTANCE_ID, NO_DEFAULT ); @Description( "The name of a cluster." ) @Internal @@ -80,7 +95,7 @@ public String toString() @Description( "A comma-separated list of other members of the cluster to join." ) public static final Setting> initial_hosts = setting( "ha.initial_hosts", - list( ",", HOSTNAME_PORT ), MANDATORY ); + list( ",", HOSTNAME_PORT ), NO_DEFAULT ); @Description( "Host and port to bind the cluster management communication." ) public static final Setting cluster_server = setting( "ha.host.coordination", HOSTNAME_PORT, diff --git a/enterprise/cluster/src/main/java/org/neo4j/configuration/HaConfigurationValidator.java b/enterprise/cluster/src/main/java/org/neo4j/configuration/HaConfigurationValidator.java new file mode 100644 index 0000000000000..26d8b8e511300 --- /dev/null +++ b/enterprise/cluster/src/main/java/org/neo4j/configuration/HaConfigurationValidator.java @@ -0,0 +1,79 @@ +/* + * 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 Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.neo4j.configuration; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import javax.annotation.Nonnull; + +import org.neo4j.cluster.ClusterSettings; +import org.neo4j.cluster.ClusterSettings.Mode; +import org.neo4j.graphdb.config.InvalidSettingException; +import org.neo4j.graphdb.config.SettingValidator; +import org.neo4j.helpers.HostnamePort; +import org.neo4j.kernel.configuration.ConfigurationValidator; +import org.neo4j.logging.Log; + +import static org.neo4j.cluster.ClusterSettings.initial_hosts; +import static org.neo4j.cluster.ClusterSettings.server_id; + +public class HaConfigurationValidator implements ConfigurationValidator +{ + @Override + @Nonnull + public Map validate( @Nonnull Collection settingValidators, + @Nonnull Map rawConfig, @Nonnull Log log ) throws InvalidSettingException + { + // Make sure mode is HA + Mode mode = ClusterSettings.mode.apply( rawConfig::get ); + if ( !mode.equals( Mode.HA ) && !mode.equals( Mode.ARBITER ) ) + { + // Nothing to validate + return rawConfig; + } + + validateServerId( rawConfig::get ); + + validateInitialHosts( rawConfig::get ); + + return rawConfig; + } + + private static void validateServerId( Function provider ) + { + if ( server_id.apply( provider ) == null ) + { + throw new InvalidSettingException( String.format( "Missing mandatory value for '%s'", server_id.name() ) ); + } + } + + private static void validateInitialHosts( Function provider ) + { + List hosts = initial_hosts.apply( provider ); + if ( hosts == null || hosts.isEmpty() ) + { + throw new InvalidSettingException( + String.format( "Missing mandatory non-empty value for '%s'", initial_hosts.name() ) ); + } + } +} diff --git a/enterprise/cluster/src/test/java/org/neo4j/configuration/HaConfigurationValidatorTest.java b/enterprise/cluster/src/test/java/org/neo4j/configuration/HaConfigurationValidatorTest.java new file mode 100644 index 0000000000000..8d3ced7c91169 --- /dev/null +++ b/enterprise/cluster/src/test/java/org/neo4j/configuration/HaConfigurationValidatorTest.java @@ -0,0 +1,115 @@ +/* + * 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 Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.neo4j.configuration; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.util.Collections; + +import org.neo4j.cluster.ClusterSettings; +import org.neo4j.cluster.InstanceId; +import org.neo4j.graphdb.config.InvalidSettingException; +import org.neo4j.helpers.HostnamePort; +import org.neo4j.kernel.configuration.Config; + +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; +import static org.neo4j.helpers.collection.MapUtil.stringMap; + +public class HaConfigurationValidatorTest +{ + @Rule + public ExpectedException expected = ExpectedException.none(); + + @Test + public void validateOnlyIfModeIsHA() throws Exception + { + // when + Config config = Config.embeddedDefaults( + stringMap( ClusterSettings.mode.name(), ClusterSettings.Mode.SINGLE.name(), + ClusterSettings.initial_hosts.name(), "" ), + Collections.singleton( new HaConfigurationValidator() ) ); + + // then + assertEquals( "", config.getRaw( ClusterSettings.initial_hosts.name() ).get() ); + } + + @Test + public void validateSuccess() throws Exception + { + // when + Config config = Config.embeddedDefaults( + stringMap( ClusterSettings.mode.name(), ClusterSettings.Mode.HA.name(), + ClusterSettings.server_id.name(), "1", + ClusterSettings.initial_hosts.name(), "localhost,remotehost" ), + Collections.singleton( new HaConfigurationValidator() ) ); + + // then + assertEquals( asList( new HostnamePort( "localhost" ), + new HostnamePort( "remotehost" ) ), + config.get( ClusterSettings.initial_hosts ) ); + assertEquals( new InstanceId( 1 ), config.get( ClusterSettings.server_id ) ); + } + + @Test + public void missingServerId() throws Exception + { + // then + expected.expect( InvalidSettingException.class ); + expected.expectMessage( "Missing mandatory value for 'ha.server_id'" ); + + // when + Config.embeddedDefaults( + stringMap( ClusterSettings.mode.name(), ClusterSettings.Mode.HA.name() ), + Collections.singleton( new HaConfigurationValidator() ) ); + } + + @Test + public void missingInitialHosts() throws Exception + { + // then + expected.expect( InvalidSettingException.class ); + expected.expectMessage( "Missing mandatory non-empty value for 'ha.initial_hosts'" ); + + // when + Config.embeddedDefaults( + stringMap( ClusterSettings.mode.name(), ClusterSettings.Mode.HA.name(), + ClusterSettings.server_id.name(), "1" ), + Collections.singleton( new HaConfigurationValidator() ) ); + } + + @Test + public void initialHostsEmpty() throws Exception + { + // then + expected.expect( InvalidSettingException.class ); + expected.expectMessage( "Missing mandatory non-empty value for 'ha.initial_hosts'" ); + + // when + Config.embeddedDefaults( + stringMap( ClusterSettings.mode.name(), ClusterSettings.Mode.HA.name(), + ClusterSettings.server_id.name(), "1", + ClusterSettings.initial_hosts.name(), "," ), + Collections.singleton( new HaConfigurationValidator() ) ); + } +} diff --git a/enterprise/security/src/test/java/org/neo4j/kernel/impl/proc/ProcedureAllowedConfigTest.java b/enterprise/security/src/test/java/org/neo4j/kernel/impl/proc/ProcedureAllowedConfigTest.java new file mode 100644 index 0000000000000..a5a2819b07725 --- /dev/null +++ b/enterprise/security/src/test/java/org/neo4j/kernel/impl/proc/ProcedureAllowedConfigTest.java @@ -0,0 +1,140 @@ +/* + * 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 Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.neo4j.kernel.impl.proc; + +import org.junit.Test; + +import org.neo4j.kernel.configuration.Config; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.neo4j.helpers.collection.MapUtil.genericMap; +import static org.neo4j.kernel.impl.proc.ProcedureAllowedConfig.PROC_ALLOWED_SETTING_DEFAULT_NAME; +import static org.neo4j.kernel.impl.proc.ProcedureAllowedConfig.PROC_ALLOWED_SETTING_ROLES; + +public class ProcedureAllowedConfigTest +{ + private static final String[] EMPTY = new String[]{}; + + private static String[] arrayOf( String... values ) + { + return values; + } + + @Test + public void shouldHaveEmptyDefaultConfigs() + { + Config config = Config.defaults(); + ProcedureAllowedConfig procConfig = new ProcedureAllowedConfig( config ); + assertThat( procConfig.rolesFor( "x" ), equalTo( EMPTY ) ); + } + + @Test + public void shouldHaveConfigsWithDefaultProcedureAllowed() + { + Config config = Config.defaults() + .with( genericMap( PROC_ALLOWED_SETTING_DEFAULT_NAME, "role1" ) ); + ProcedureAllowedConfig procConfig = new ProcedureAllowedConfig( config ); + assertThat( procConfig.rolesFor( "x" ), equalTo( arrayOf( "role1" ) ) ); + } + + @Test + public void shouldHaveConfigsWithExactMatchProcedureAllowed() + { + Config config = Config.defaults() + .with( genericMap( PROC_ALLOWED_SETTING_DEFAULT_NAME, "role1", + PROC_ALLOWED_SETTING_ROLES, "xyz:anotherRole" ) ); + ProcedureAllowedConfig procConfig = new ProcedureAllowedConfig( config ); + assertThat( procConfig.rolesFor( "xyz" ), equalTo( arrayOf( "anotherRole" ) ) ); + assertThat( procConfig.rolesFor( "abc" ), equalTo( arrayOf( "role1" ) ) ); + } + + @Test + public void shouldHaveConfigsWithWildcardProcedureAllowed() + { + Config config = Config.defaults() + .with( genericMap( PROC_ALLOWED_SETTING_DEFAULT_NAME, "role1", + PROC_ALLOWED_SETTING_ROLES, "xyz*:anotherRole" ) ); + ProcedureAllowedConfig procConfig = new ProcedureAllowedConfig( config ); + assertThat( procConfig.rolesFor( "xyzabc" ), equalTo( arrayOf( "anotherRole" ) ) ); + assertThat( procConfig.rolesFor( "abcxyz" ), equalTo( arrayOf( "role1" ) ) ); + } + + @Test + public void shouldHaveConfigsWithWildcardProcedureAllowedAndNoDefault() + { + Config config = Config.defaults() + .with( genericMap( PROC_ALLOWED_SETTING_ROLES, "xyz*:anotherRole" ) ); + ProcedureAllowedConfig procConfig = new ProcedureAllowedConfig( config ); + assertThat( procConfig.rolesFor( "xyzabc" ), equalTo( arrayOf( "anotherRole" ) ) ); + assertThat( procConfig.rolesFor( "abcxyz" ), equalTo( EMPTY ) ); + } + + @Test + public void shouldHaveConfigsWithMultipleWildcardProcedureAllowedAndNoDefault() + { + Config config = Config.defaults() + .with( genericMap( + PROC_ALLOWED_SETTING_ROLES, + "apoc.convert.*:apoc_reader;apoc.load.json:apoc_writer;apoc.trigger.add:TriggerHappy" + ) ); + ProcedureAllowedConfig procConfig = new ProcedureAllowedConfig( config ); + assertThat( procConfig.rolesFor( "xyz" ), equalTo( EMPTY ) ); + assertThat( procConfig.rolesFor( "apoc.convert.xml" ), equalTo( arrayOf( "apoc_reader" ) ) ); + assertThat( procConfig.rolesFor( "apoc.convert.json" ), equalTo( arrayOf( "apoc_reader" ) ) ); + assertThat( procConfig.rolesFor( "apoc.load.xml" ), equalTo( EMPTY ) ); + assertThat( procConfig.rolesFor( "apoc.load.json" ), equalTo( arrayOf( "apoc_writer" ) ) ); + assertThat( procConfig.rolesFor( "apoc.trigger.add" ), equalTo( arrayOf( "TriggerHappy" ) ) ); + assertThat( procConfig.rolesFor( "apoc.convert-json" ), equalTo( EMPTY ) ); + assertThat( procConfig.rolesFor( "apoc.load-xml" ), equalTo( EMPTY ) ); + assertThat( procConfig.rolesFor( "apoc.load-json" ), equalTo( EMPTY ) ); + assertThat( procConfig.rolesFor( "apoc.trigger-add" ), equalTo( EMPTY ) ); + } + + @Test + public void shouldHaveConfigsWithOverlappingMatchingWildcards() + { + Config config = Config.defaults() + .with( genericMap( + PROC_ALLOWED_SETTING_DEFAULT_NAME, "default", + PROC_ALLOWED_SETTING_ROLES, + "apoc.*:apoc;apoc.load.*:loader;apoc.trigger.*:trigger;apoc.trigger.add:TriggerHappy" + ) ); + ProcedureAllowedConfig procConfig = new ProcedureAllowedConfig( config ); + assertThat( procConfig.rolesFor( "xyz" ), equalTo( arrayOf( "default" ) ) ); + assertThat( procConfig.rolesFor( "apoc.convert.xml" ), equalTo( arrayOf( "apoc" ) ) ); + assertThat( procConfig.rolesFor( "apoc.load.xml" ), equalTo( arrayOf( "apoc", "loader" ) ) ); + assertThat( procConfig.rolesFor( "apoc.trigger.add" ), equalTo( arrayOf( "apoc", "trigger", "TriggerHappy" ) ) ); + assertThat( procConfig.rolesFor( "apoc.trigger.remove" ), equalTo( arrayOf( "apoc", "trigger" ) ) ); + assertThat( procConfig.rolesFor( "apoc.load-xml" ), equalTo( arrayOf( "apoc" ) ) ); + } + + @Test + public void shouldSupportSeveralRolesPerPattern() + { + Config config = Config.defaults().with( genericMap( PROC_ALLOWED_SETTING_ROLES, + "xyz*:role1,role2, role3 , role4 ; abc: role3 ,role1" ) ); + ProcedureAllowedConfig procConfig = new ProcedureAllowedConfig( config ); + + assertThat( procConfig.rolesFor( "xyzabc" ), equalTo( arrayOf( "role1", "role2", "role3", "role4" ) ) ); + assertThat( procConfig.rolesFor( "abc" ), equalTo( arrayOf( "role3", "role1" ) ) ); + assertThat( procConfig.rolesFor( "abcxyz" ), equalTo( EMPTY ) ); + } +} diff --git a/enterprise/server-enterprise/pom.xml b/enterprise/server-enterprise/pom.xml index 9f2c1de8aa1dd..0ef4dbfdc535c 100644 --- a/enterprise/server-enterprise/pom.xml +++ b/enterprise/server-enterprise/pom.xml @@ -80,6 +80,11 @@ ${project.version} + + com.google.code.findbugs + annotations + + junit diff --git a/enterprise/server-enterprise/src/main/java/org/neo4j/server/enterprise/EnterpriseBootstrapper.java b/enterprise/server-enterprise/src/main/java/org/neo4j/server/enterprise/EnterpriseBootstrapper.java index 109dca3d57c19..100d88bbe04c0 100644 --- a/enterprise/server-enterprise/src/main/java/org/neo4j/server/enterprise/EnterpriseBootstrapper.java +++ b/enterprise/server-enterprise/src/main/java/org/neo4j/server/enterprise/EnterpriseBootstrapper.java @@ -19,22 +19,18 @@ */ package org.neo4j.server.enterprise; -import java.util.Map; +import java.util.ArrayList; +import java.util.Collection; +import javax.annotation.Nonnull; -import org.neo4j.cluster.ClusterSettings; -import org.neo4j.causalclustering.core.CausalClusteringSettings; -import org.neo4j.helpers.collection.Iterables; +import org.neo4j.causalclustering.core.CausalClusterConfigurationValidator; +import org.neo4j.configuration.HaConfigurationValidator; import org.neo4j.kernel.GraphDatabaseDependencies; import org.neo4j.kernel.configuration.Config; -import org.neo4j.kernel.ha.HaSettings; +import org.neo4j.kernel.configuration.ConfigurationValidator; import org.neo4j.logging.LogProvider; import org.neo4j.server.CommunityBootstrapper; import org.neo4j.server.NeoServer; -import org.neo4j.server.security.enterprise.configuration.SecuritySettings; - -import static java.util.Arrays.asList; - -import static org.neo4j.server.enterprise.EnterpriseServerSettings.mode; public class EnterpriseBootstrapper extends CommunityBootstrapper { @@ -46,32 +42,13 @@ protected NeoServer createNeoServer( Config configurator, GraphDatabaseDependenc } @Override - protected Iterable> settingsClasses( Map settings ) - { - if ( isHAMode( settings ) ) - { - return Iterables.concat( super.settingsClasses( settings ), - asList( HaSettings.class, ClusterSettings.class, SecuritySettings.class ) ); - } - if ( isCCMode( settings ) ) - { - return Iterables.concat( super.settingsClasses( settings ), - asList( CausalClusteringSettings.class, SecuritySettings.class ) ); - } - else - { - return super.settingsClasses( settings ); - } - } - - private boolean isHAMode( Map settings ) - { - return new Config( settings, EnterpriseServerSettings.class ).get( mode ).equals( "HA" ); - } - - private boolean isCCMode( Map settings ) + @Nonnull + protected Collection configurationValidators() { - String mode = new Config( settings, EnterpriseServerSettings.class ).get( EnterpriseServerSettings.mode ); - return mode.equals( "CORE" ) || mode.equals( "READ_REPLICA" ); + ArrayList validators = new ArrayList<>(); + validators.addAll( super.configurationValidators() ); + validators.add( new HaConfigurationValidator() ); + validators.add( new CausalClusterConfigurationValidator() ); + return validators; } } diff --git a/enterprise/server-enterprise/src/main/java/org/neo4j/server/enterprise/EnterpriseNeoServer.java b/enterprise/server-enterprise/src/main/java/org/neo4j/server/enterprise/EnterpriseNeoServer.java index 16bfcca506593..18928f4bff1d4 100644 --- a/enterprise/server-enterprise/src/main/java/org/neo4j/server/enterprise/EnterpriseNeoServer.java +++ b/enterprise/server-enterprise/src/main/java/org/neo4j/server/enterprise/EnterpriseNeoServer.java @@ -29,6 +29,8 @@ import org.neo4j.causalclustering.core.CoreGraphDatabase; import org.neo4j.causalclustering.readreplica.ReadReplicaGraphDatabase; +import org.neo4j.cluster.ClusterSettings; +import org.neo4j.cluster.ClusterSettings.Mode; import org.neo4j.dbms.DatabaseManagementSystemSettings; import org.neo4j.graphdb.EnterpriseGraphDatabase; import org.neo4j.helpers.collection.Iterables; @@ -59,45 +61,25 @@ public class EnterpriseNeoServer extends CommunityNeoServer { - public enum Mode - { - SINGLE, - HA, - ARBITER, - CORE, - READ_REPLICA; - - public static Mode fromString( String value ) - { - try - { - return Mode.valueOf( value ); - } - catch ( IllegalArgumentException ex ) - { - return SINGLE; - } - } - } private static final GraphFactory HA_FACTORY = ( config, dependencies ) -> { File storeDir = config.get( DatabaseManagementSystemSettings.database_path ); - return new HighlyAvailableGraphDatabase( storeDir, config.getParams(), dependencies ); + return new HighlyAvailableGraphDatabase( storeDir, config, dependencies ); }; private static final GraphFactory ENTERPRISE_FACTORY = ( config, dependencies ) -> { File storeDir = config.get( DatabaseManagementSystemSettings.database_path ); - return new EnterpriseGraphDatabase( storeDir, config.getParams(), dependencies ); + return new EnterpriseGraphDatabase( storeDir, config, dependencies ); }; private static final GraphFactory CORE_FACTORY = ( config, dependencies ) -> { File storeDir = config.get( DatabaseManagementSystemSettings.database_path ); - return new CoreGraphDatabase( storeDir, config.getParams(), dependencies ); + return new CoreGraphDatabase( storeDir, config, dependencies ); }; private static final GraphFactory READ_REPLICA_FACTORY = ( config, dependencies ) -> { File storeDir = config.get( DatabaseManagementSystemSettings.database_path ); - return new ReadReplicaGraphDatabase( storeDir, config.getParams(), dependencies ); + return new ReadReplicaGraphDatabase( storeDir, config, dependencies ); }; public EnterpriseNeoServer( Config config, Dependencies dependencies, LogProvider logProvider ) @@ -107,7 +89,7 @@ public EnterpriseNeoServer( Config config, Dependencies dependencies, LogProvide protected static Database.Factory createDbFactory( Config config ) { - final Mode mode = Mode.fromString( config.get( EnterpriseServerSettings.mode ).toUpperCase() ); + final Mode mode =config.get( ClusterSettings.mode ); switch ( mode ) { diff --git a/enterprise/server-enterprise/src/main/java/org/neo4j/server/enterprise/EnterpriseServerSettings.java b/enterprise/server-enterprise/src/main/java/org/neo4j/server/enterprise/EnterpriseServerSettings.java index 5f2b89ea3cd40..0742631636ab6 100644 --- a/enterprise/server-enterprise/src/main/java/org/neo4j/server/enterprise/EnterpriseServerSettings.java +++ b/enterprise/server-enterprise/src/main/java/org/neo4j/server/enterprise/EnterpriseServerSettings.java @@ -32,13 +32,6 @@ @Description("Settings available in the Enterprise server") public class EnterpriseServerSettings implements LoadableConfig { - @Description( "Configure the operating mode of the database -- 'SINGLE' for stand-alone operation, 'HA' for " + - "operating as a member in a cluster, 'ARBITER' for an HA-only cluster member with no database, " + - "CORE for a core member of a Causal Clustering cluster, or READ_REPLICA for read replica." + - " cluster" + - ". " ) - public static final Setting mode = setting( "dbms.mode", STRING, "SINGLE" ); - @SuppressWarnings("unused") // accessed from the browser @Description( "Configure the Neo4j Browser to time out logged in users after this idle period. " + "Setting this to 0 indicates no limit." ) diff --git a/enterprise/server-enterprise/src/test/java/org/neo4j/server/enterprise/EnterpriseBootstrapperTest.java b/enterprise/server-enterprise/src/test/java/org/neo4j/server/enterprise/EnterpriseBootstrapperTest.java index 724f4be6da813..0d60a2bc8b847 100644 --- a/enterprise/server-enterprise/src/test/java/org/neo4j/server/enterprise/EnterpriseBootstrapperTest.java +++ b/enterprise/server-enterprise/src/test/java/org/neo4j/server/enterprise/EnterpriseBootstrapperTest.java @@ -76,7 +76,7 @@ public void shouldBeAbleToStartInSingleMode() throws Exception // When int resultCode = ServerBootstrapper.start( bootstrapper, "--home-dir", tempDir.newFolder( "home-dir" ).getAbsolutePath(), - "-c", configOption( EnterpriseServerSettings.mode, "SINGLE" ), + "-c", configOption( ClusterSettings.mode, "SINGLE" ), "-c", configOption( data_directory, getRelativePath( folder.getRoot(), data_directory ) ), "-c", configOption( logs_directory, tempDir.getRoot().getAbsolutePath() ), "-c", configOption( certificates_directory, getRelativePath( folder.getRoot(), certificates_directory ) ), @@ -95,7 +95,7 @@ public void shouldBeAbleToStartInHAMode() throws Exception // When int resultCode = ServerBootstrapper.start( bootstrapper, "--home-dir", tempDir.newFolder( "home-dir" ).getAbsolutePath(), - "-c", configOption( EnterpriseServerSettings.mode, "HA" ), + "-c", configOption( ClusterSettings.mode, "HA" ), "-c", configOption( ClusterSettings.server_id, "1" ), "-c", configOption( ClusterSettings.initial_hosts, "127.0.0.1:5001" ), "-c", configOption( data_directory, getRelativePath( folder.getRoot(), data_directory ) ), diff --git a/enterprise/server-enterprise/src/test/java/org/neo4j/server/enterprise/functional/EnterpriseServerIT.java b/enterprise/server-enterprise/src/test/java/org/neo4j/server/enterprise/functional/EnterpriseServerIT.java index 5f3811a2fa44a..e2856eee7523a 100644 --- a/enterprise/server-enterprise/src/test/java/org/neo4j/server/enterprise/functional/EnterpriseServerIT.java +++ b/enterprise/server-enterprise/src/test/java/org/neo4j/server/enterprise/functional/EnterpriseServerIT.java @@ -33,16 +33,14 @@ import org.neo4j.server.enterprise.helpers.EnterpriseServerBuilder; import static javax.ws.rs.core.MediaType.APPLICATION_JSON; - import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.instanceOf; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; - import static org.neo4j.cluster.ClusterSettings.initial_hosts; +import static org.neo4j.cluster.ClusterSettings.mode; import static org.neo4j.cluster.ClusterSettings.server_id; -import static org.neo4j.server.enterprise.EnterpriseServerSettings.mode; public class EnterpriseServerIT { diff --git a/tools/src/main/java/org/neo4j/tools/rebuild/RebuildFromLogs.java b/tools/src/main/java/org/neo4j/tools/rebuild/RebuildFromLogs.java index b6c224c98091b..3b4db5ad4eef9 100644 --- a/tools/src/main/java/org/neo4j/tools/rebuild/RebuildFromLogs.java +++ b/tools/src/main/java/org/neo4j/tools/rebuild/RebuildFromLogs.java @@ -28,14 +28,12 @@ import org.neo4j.com.storecopy.ExternallyManagedPageCache; import org.neo4j.consistency.ConsistencyCheckService; -import org.neo4j.consistency.ConsistencyCheckSettings; import org.neo4j.consistency.checking.full.ConsistencyCheckIncompleteException; import org.neo4j.consistency.checking.full.FullCheck; import org.neo4j.consistency.statistics.Statistics; import org.neo4j.cursor.IOCursor; import org.neo4j.graphdb.DependencyResolver; import org.neo4j.graphdb.factory.GraphDatabaseFactory; -import org.neo4j.graphdb.factory.GraphDatabaseSettings; import org.neo4j.helpers.Args; import org.neo4j.helpers.progress.ProgressMonitorFactory; import org.neo4j.io.fs.DefaultFileSystemAbstraction; @@ -270,8 +268,7 @@ private static class ConsistencyChecker implements AutoCloseable { private final GraphDatabaseAPI graphdb; private final LabelScanStore labelScanStore; - private final Config tuningConfiguration = - Config.embeddedDefaults( stringMap(), GraphDatabaseSettings.class, ConsistencyCheckSettings.class ); + private final Config tuningConfiguration = Config.embeddedDefaults(); private final SchemaIndexProvider indexes; ConsistencyChecker( File dbDirectory, PageCache pageCache )