From 34dd0613503f683403fe385961cf16a7c5d8cd51 Mon Sep 17 00:00:00 2001 From: lutovich Date: Wed, 12 Sep 2018 15:11:39 +0200 Subject: [PATCH] Allow disabling HTTP in Neo4j server Before this change, HTTP connector had to be enabled in order to start the database. HTTPS and Bolt connectors were optional. This commit allows disabling the HTTP connector via: ``` dbms.connector.http.enabled=false ``` setting. It is also possible to disable both HTTP and HTTPS making the database only accessible via Bolt: ``` dbms.connector.http.enabled=false dbms.connector.https.enabled=false ``` Such configuration will make Neo4j not start an embedded Jetty server. Neo4j browser and REST endpoints will thus not be accessible on the usual addresses 'http://localhost:7474' and 'https://localhost:7473'. Disabling HTTP can be useful when operators need to enforce only secure connections. HTTP connector was mandatory because server bootstrappers were only able to instantiate Neo4j server, which created the database. So lifecycle of the core database was managed entirely by the server. This commit makes bootstrappers able to create core database separately from a server component. Based on connector configuration bootstrappers can decide if server component is needed or not. Dummy `DisabledNeoServer` is instantiated when HTTP and HTTPS are turned off. `AbstractNeoServer`, which is the base for all server implementations, is now able to start when either HTTP or HTTPS is enabled. --- .../main/java/org/neo4j/bolt/BoltServer.java | 2 +- .../factory/GraphDatabaseSettings.java | 5 - .../org/neo4j/helpers/PortBindException.java | 23 ++- .../neo4j/kernel/configuration/Config.java | 7 +- .../ServerConfigurationValidator.java | 66 ------- .../ServerConfigurationValidatorTest.java | 47 ----- ...est.java => DefaultPluginManagerTest.java} | 12 +- .../org/neo4j/server/AbstractNeoServer.java | 142 ++++++++------- .../neo4j/server/CommunityBootstrapper.java | 18 +- .../org/neo4j/server/CommunityNeoServer.java | 28 +-- .../org/neo4j/server/DisabledNeoServer.java | 116 +++++++++++++ .../main/java/org/neo4j/server/NeoServer.java | 2 - .../org/neo4j/server/ServerBootstrapper.java | 30 +++- .../database/CommunityGraphFactory.java | 42 +++++ .../org/neo4j/server/database/Database.java | 7 - .../neo4j/server/database/GraphFactory.java | 31 ++++ .../database/LifecycleManagingDatabase.java | 10 -- .../server/exception/ServerStartupErrors.java | 30 +--- .../neo4j/server/modules/RESTApiModule.java | 9 +- .../server/plugins/DefaultPluginManager.java | 164 ++++++++++++++++++ .../server/plugins/DisabledPluginManager.java | 68 ++++++++ .../neo4j/server/plugins/PluginManager.java | 146 +--------------- .../DisabledTransactionRegistry.java | 63 +++++++ .../org/neo4j/server/web/Jetty9WebServer.java | 104 ++++++----- .../java/org/neo4j/server/web/WebServer.java | 5 +- ...perTestIT.java => BaseBootstrapperIT.java} | 154 +++++++++++++++- ...stIT.java => CommunityBootstrapperIT.java} | 2 +- .../java/org/neo4j/server/HttpsAccessIT.java | 124 ++++++++++--- .../neo4j/server/ServerBootstrapperTest.java | 17 +- .../server/database/InMemoryGraphFactory.java | 44 +++++ .../LifecycleManagingDatabaseTest.java | 2 +- .../server/database/SimpleGraphFactory.java | 40 +++++ .../TestLifecycleManagedDatabase.java | 9 +- .../helpers/CommunityServerBuilder.java | 30 ++-- .../neo4j/server/web/Jetty9WebServerIT.java | 4 +- .../neo4j/server/web/JettyThreadLimitIT.java | 2 +- .../kernel/GraphDatabaseShellServer.java | 9 +- .../metrics/source/Neo4jMetricsBuilder.java | 3 +- .../source/Neo4jMetricsBuilderTest.java | 97 +++++++++++ .../database/EnterpriseGraphFactory.java | 64 +++++++ .../enterprise/OpenEnterpriseNeoServer.java | 75 +------- .../java/org/neo4j/TransactionGuardIT.java | 6 +- 42 files changed, 1226 insertions(+), 633 deletions(-) delete mode 100644 community/kernel/src/main/java/org/neo4j/kernel/configuration/ServerConfigurationValidator.java delete mode 100644 community/kernel/src/test/java/org/neo4j/kernel/configuration/ServerConfigurationValidatorTest.java rename community/server-plugin-test/src/test/java/org/neo4j/server/plugins/{PluginManagerTest.java => DefaultPluginManagerTest.java} (95%) create mode 100644 community/server/src/main/java/org/neo4j/server/DisabledNeoServer.java create mode 100644 community/server/src/main/java/org/neo4j/server/database/CommunityGraphFactory.java create mode 100644 community/server/src/main/java/org/neo4j/server/database/GraphFactory.java create mode 100644 community/server/src/main/java/org/neo4j/server/plugins/DefaultPluginManager.java create mode 100644 community/server/src/main/java/org/neo4j/server/plugins/DisabledPluginManager.java create mode 100644 community/server/src/main/java/org/neo4j/server/rest/transactional/DisabledTransactionRegistry.java rename community/server/src/test/java/org/neo4j/server/{BaseBootstrapperTestIT.java => BaseBootstrapperIT.java} (53%) rename community/server/src/test/java/org/neo4j/server/{CommunityBootstrapperTestIT.java => CommunityBootstrapperIT.java} (92%) create mode 100644 community/server/src/test/java/org/neo4j/server/database/InMemoryGraphFactory.java create mode 100644 community/server/src/test/java/org/neo4j/server/database/SimpleGraphFactory.java create mode 100644 enterprise/metrics/src/test/java/org/neo4j/metrics/source/Neo4jMetricsBuilderTest.java create mode 100644 enterprise/server-enterprise/src/main/java/org/neo4j/server/database/EnterpriseGraphFactory.java diff --git a/community/bolt/src/main/java/org/neo4j/bolt/BoltServer.java b/community/bolt/src/main/java/org/neo4j/bolt/BoltServer.java index c2ae7796692e2..9f05a29a31045 100644 --- a/community/bolt/src/main/java/org/neo4j/bolt/BoltServer.java +++ b/community/bolt/src/main/java/org/neo4j/bolt/BoltServer.java @@ -119,7 +119,7 @@ public void start() throws Throwable BoltProtocolFactory boltProtocolFactory = createBoltProtocolFactory( boltConnectionFactory, boltStateMachineFactory ); - if ( !config.enabledBoltConnectors().isEmpty() && !config.get( GraphDatabaseSettings.disconnected ) ) + if ( !config.enabledBoltConnectors().isEmpty() ) { NettyServer server = new NettyServer( jobScheduler.threadFactory( Group.BOLT_NETWORK_IO ), createConnectors( boltProtocolFactory, throttleGroup, log ), connectorPortRegister, userLog ); diff --git a/community/kernel/src/main/java/org/neo4j/graphdb/factory/GraphDatabaseSettings.java b/community/kernel/src/main/java/org/neo4j/graphdb/factory/GraphDatabaseSettings.java index d168c33c1c6c0..42f1bde2058d6 100644 --- a/community/kernel/src/main/java/org/neo4j/graphdb/factory/GraphDatabaseSettings.java +++ b/community/kernel/src/main/java/org/neo4j/graphdb/factory/GraphDatabaseSettings.java @@ -146,11 +146,6 @@ public class GraphDatabaseSettings implements LoadableConfig @Internal public static final Setting editionName = setting( "unsupported.dbms.edition", STRING, Edition.unknown.toString() ); - @Title( "Disconnected" ) - @Internal - @Description( "Disable all protocol connectors." ) - public static final Setting disconnected = setting( "unsupported.dbms.disconnected", BOOLEAN, FALSE ); - @Description( "Print out the effective Neo4j configuration after startup." ) @Internal public static final Setting dump_configuration = setting( "unsupported.dbms.report_configuration", diff --git a/community/kernel/src/main/java/org/neo4j/helpers/PortBindException.java b/community/kernel/src/main/java/org/neo4j/helpers/PortBindException.java index e517f5c867c2a..d0a16dfee7360 100644 --- a/community/kernel/src/main/java/org/neo4j/helpers/PortBindException.java +++ b/community/kernel/src/main/java/org/neo4j/helpers/PortBindException.java @@ -30,13 +30,28 @@ public class PortBindException extends BindException { public PortBindException( ListenSocketAddress address, Throwable original ) { - super( String.format("Address %s is already in use, cannot bind to it.", address) ); - setStackTrace( original.getStackTrace() ); + this( address, null, original ); } - public PortBindException( ListenSocketAddress address, ListenSocketAddress other, Throwable original ) + public PortBindException( ListenSocketAddress address1, ListenSocketAddress address2, Throwable original ) { - super( String.format("At least one of the addresses %s or %s is already in use, cannot bind to it.", address, other) ); + super( createMessage( address1, address2 ) ); setStackTrace( original.getStackTrace() ); } + + private static String createMessage( ListenSocketAddress address1, ListenSocketAddress address2 ) + { + if ( address1 == null && address2 == null ) + { + throw new IllegalArgumentException( "At least one address should not be null" ); + } + else if ( address1 != null && address2 != null ) + { + return String.format( "At least one of the addresses %s or %s is already in use, cannot bind to it.", address1, address2 ); + } + else + { + return String.format( "Address %s is already in use, cannot bind to it.", address1 != null ? address1 : address2 ); + } + } } 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 0d87fc4870d32..ffdcd58e0586a 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 @@ -44,8 +44,6 @@ import org.neo4j.configuration.ConfigOptions; import org.neo4j.configuration.ConfigValue; import org.neo4j.configuration.LoadableConfig; -import org.neo4j.internal.diagnostics.DiagnosticsPhase; -import org.neo4j.internal.diagnostics.DiagnosticsProvider; import org.neo4j.graphdb.config.BaseSetting; import org.neo4j.graphdb.config.Configuration; import org.neo4j.graphdb.config.InvalidSettingException; @@ -53,6 +51,8 @@ import org.neo4j.graphdb.config.SettingValidator; import org.neo4j.graphdb.factory.GraphDatabaseSettings; import org.neo4j.helpers.collection.MapUtil; +import org.neo4j.internal.diagnostics.DiagnosticsPhase; +import org.neo4j.internal.diagnostics.DiagnosticsProvider; import org.neo4j.kernel.configuration.HttpConnector.Encryption; import org.neo4j.kernel.impl.util.CopyOnWriteHashMap; import org.neo4j.logging.BufferingLog; @@ -208,9 +208,6 @@ public Builder withServerDefaults() overriddenDefaults.put( https.enabled.name(), TRUE ); overriddenDefaults.put( bolt.enabled.name(), TRUE ); - // Add server validator - validators.add( new ServerConfigurationValidator() ); - return this; } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/configuration/ServerConfigurationValidator.java b/community/kernel/src/main/java/org/neo4j/kernel/configuration/ServerConfigurationValidator.java deleted file mode 100644 index 1d50687b26887..0000000000000 --- a/community/kernel/src/main/java/org/neo4j/kernel/configuration/ServerConfigurationValidator.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2002-2018 "Neo4j," - * Neo4j Sweden AB [http://neo4j.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 . - */ -package org.neo4j.kernel.configuration; - -import java.util.HashMap; -import java.util.Map; -import javax.annotation.Nonnull; - -import org.neo4j.graphdb.config.InvalidSettingException; -import org.neo4j.logging.Log; - -import static org.neo4j.kernel.configuration.Connector.ConnectorType.BOLT; -import static org.neo4j.kernel.configuration.Connector.ConnectorType.HTTP; - - -public class ServerConfigurationValidator implements ConfigurationValidator -{ - /** - * Verifies that at least one http connector is specified and enabled. - */ - @Override - public Map validate( @Nonnull Config config, @Nonnull Log log ) throws InvalidSettingException - { - Map validSettings = new HashMap<>(); - - // Add missing type info -- validation has succeeded so we can do this with confidence - boolean hasEnabledConnector = false; - for ( String identifier : config.identifiersFromGroup( Connector.class ) ) - { - Connector connector = new Connector( identifier ); - if ( "http".equalsIgnoreCase( connector.group.groupKey ) || "https".equalsIgnoreCase( connector.group.groupKey ) ) - { - validSettings.put( connector.type.name(), HTTP.name() ); - hasEnabledConnector = hasEnabledConnector ? true : config.get( connector.enabled ); - } - else - { - validSettings.put( connector.type.name(), BOLT.name() ); - } - } - - if ( !hasEnabledConnector ) - { - throw new InvalidSettingException( String.format( "Missing mandatory enabled connector of type '%s'", HTTP ) ); - } - - return validSettings; - } -} diff --git a/community/kernel/src/test/java/org/neo4j/kernel/configuration/ServerConfigurationValidatorTest.java b/community/kernel/src/test/java/org/neo4j/kernel/configuration/ServerConfigurationValidatorTest.java deleted file mode 100644 index f1201a88e5ad4..0000000000000 --- a/community/kernel/src/test/java/org/neo4j/kernel/configuration/ServerConfigurationValidatorTest.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2002-2018 "Neo4j," - * Neo4j Sweden AB [http://neo4j.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 . - */ -package org.neo4j.kernel.configuration; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; - -import org.neo4j.graphdb.config.InvalidSettingException; - -import static org.neo4j.helpers.collection.MapUtil.stringMap; - -public class ServerConfigurationValidatorTest -{ - @Rule - public ExpectedException expected = ExpectedException.none(); - - @Test - public void httpConnectorIsRequired() - { - // then - expected.expect( InvalidSettingException.class ); - expected.expectMessage( "Missing mandatory enabled connector of type 'HTTP'" ); - - // when - Config.fromSettings( - stringMap( "dbms.connector.http.enabled", "false", - "dbms.connector.https.enabled", "false") ).withServerDefaults().build(); - } -} diff --git a/community/server-plugin-test/src/test/java/org/neo4j/server/plugins/PluginManagerTest.java b/community/server-plugin-test/src/test/java/org/neo4j/server/plugins/DefaultPluginManagerTest.java similarity index 95% rename from community/server-plugin-test/src/test/java/org/neo4j/server/plugins/PluginManagerTest.java rename to community/server-plugin-test/src/test/java/org/neo4j/server/plugins/DefaultPluginManagerTest.java index f2c5c73b2a637..f706faf38dbb4 100644 --- a/community/server-plugin-test/src/test/java/org/neo4j/server/plugins/PluginManagerTest.java +++ b/community/server-plugin-test/src/test/java/org/neo4j/server/plugins/DefaultPluginManagerTest.java @@ -19,14 +19,14 @@ */ package org.neo4j.server.plugins; -import java.util.List; -import java.util.Map; -import javax.ws.rs.core.MediaType; - import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; +import java.util.List; +import java.util.Map; +import javax.ws.rs.core.MediaType; + import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.kernel.internal.GraphDatabaseAPI; import org.neo4j.logging.NullLogProvider; @@ -37,7 +37,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; -public class PluginManagerTest +public class DefaultPluginManagerTest { private static PluginManager manager; private static GraphDatabaseAPI graphDb; @@ -46,7 +46,7 @@ public class PluginManagerTest public static void loadExtensionManager() { graphDb = (GraphDatabaseAPI) new TestGraphDatabaseFactory().newImpermanentDatabase(); - manager = new PluginManager( null, NullLogProvider.getInstance() ); + manager = new DefaultPluginManager( NullLogProvider.getInstance() ); } @AfterClass diff --git a/community/server/src/main/java/org/neo4j/server/AbstractNeoServer.java b/community/server/src/main/java/org/neo4j/server/AbstractNeoServer.java index 833714e9bba3d..8ab9f3c04a07d 100644 --- a/community/server/src/main/java/org/neo4j/server/AbstractNeoServer.java +++ b/community/server/src/main/java/org/neo4j/server/AbstractNeoServer.java @@ -32,12 +32,12 @@ import java.util.function.Supplier; import java.util.regex.Pattern; -import org.neo4j.internal.diagnostics.DiagnosticsManager; import org.neo4j.graphdb.DependencyResolver; import org.neo4j.graphdb.facade.GraphDatabaseFacadeFactory; import org.neo4j.helpers.AdvertisedSocketAddress; import org.neo4j.helpers.ListenSocketAddress; import org.neo4j.helpers.RunCarefully; +import org.neo4j.internal.diagnostics.DiagnosticsManager; import org.neo4j.io.fs.FileSystemAbstraction; import org.neo4j.kernel.GraphDatabaseQueryService; import org.neo4j.kernel.api.security.AuthManager; @@ -61,7 +61,9 @@ import org.neo4j.server.database.Database; import org.neo4j.server.database.DatabaseProvider; import org.neo4j.server.database.GraphDatabaseServiceProvider; +import org.neo4j.server.database.GraphFactory; import org.neo4j.server.database.InjectableProvider; +import org.neo4j.server.database.LifecycleManagingDatabase; import org.neo4j.server.modules.RESTApiModule; import org.neo4j.server.modules.ServerModule; import org.neo4j.server.plugins.ConfigAdapter; @@ -112,7 +114,7 @@ public abstract class AbstractNeoServer implements NeoServer }; public static final String NEO4J_IS_STARTING_MESSAGE = "======== Neo4j " + Version.getNeo4jVersion() + " ========"; - private final Database.Factory dbFactory; + private final GraphFactory graphFactory; private final GraphDatabaseFacadeFactory.Dependencies dependencies; protected final LogProvider logProvider; protected final Log log; @@ -122,9 +124,9 @@ public abstract class AbstractNeoServer implements NeoServer private final Config config; private final LifeSupport life = new LifeSupport(); private final ListenSocketAddress httpListenAddress; - private final Optional httpsListenAddress; + private final ListenSocketAddress httpsListenAddress; private AdvertisedSocketAddress httpAdvertisedAddress; - private Optional httpsAdvertisedAddress; + private AdvertisedSocketAddress httpsAdvertisedAddress; protected Database database; private DependencyResolver dependencyResolver; @@ -137,63 +139,44 @@ public abstract class AbstractNeoServer implements NeoServer private TransactionFacade transactionFacade; private TransactionHandleRegistry transactionRegistry; - private boolean initialized; private ConnectorPortRegister connectorPortRegister; private HttpConnector httpConnector; - private Optional httpsConnector; + private HttpConnector httpsConnector; private AsyncRequestLog requestLog; protected abstract Iterable createServerModules(); protected abstract WebServer createWebServer(); - public AbstractNeoServer( Config config, Database.Factory dbFactory, + public AbstractNeoServer( Config config, GraphFactory graphFactory, GraphDatabaseFacadeFactory.Dependencies dependencies, LogProvider logProvider ) { this.config = config; - this.dbFactory = dbFactory; + this.graphFactory = graphFactory; this.dependencies = dependencies; this.logProvider = logProvider; this.log = logProvider.getLog( getClass() ); log.info( NEO4J_IS_STARTING_MESSAGE ); - List httpConnectors = config.enabledHttpConnectors(); - - httpConnector = httpConnectors.stream() - .filter( c -> Encryption.NONE.equals( c.encryptionLevel() ) ) - .findFirst().orElseThrow( () -> - new IllegalArgumentException( "An HTTP connector must be configured to run the server" ) ); + verifyConnectorsConfiguration( config ); - httpListenAddress = config.get( httpConnector.listen_address ); - httpAdvertisedAddress = config.get( httpConnector.advertised_address ); + httpConnector = findConnector( config, Encryption.NONE ); + httpListenAddress = listenAddressFor( config, httpConnector ); + httpAdvertisedAddress = advertisedAddressFor( config, httpConnector ); - httpsConnector = httpConnectors.stream() - .filter( c -> Encryption.TLS.equals( c.encryptionLevel() ) ) - .findFirst(); - httpsListenAddress = httpsConnector.map( connector -> config.get( connector.listen_address ) ); - httpsAdvertisedAddress = httpsConnector.map( connector -> config.get( connector.advertised_address ) ); + httpsConnector = findConnector( config, Encryption.TLS ); + httpsListenAddress = listenAddressFor( config, httpsConnector ); + httpsAdvertisedAddress = advertisedAddressFor( config, httpsConnector ); } @Override - public void init() + public void start() throws ServerStartupException { - if ( initialized ) - { - return; - } - - this.database = dbFactory.newDatabase( config, dependencies ); + database = new LifecycleManagingDatabase( config, graphFactory, dependencies ); life.add( database ); life.add( new ServerDependenciesLifeCycleAdapter() ); life.add( new ServerComponentsLifecycleAdapter() ); - this.initialized = true; - } - - @Override - public void start() throws ServerStartupException - { - init(); try { life.start(); @@ -288,7 +271,7 @@ public Config getConfig() private void configureWebServer() { - webServer.setAddress( httpListenAddress ); + webServer.setHttpAddress( httpListenAddress ); webServer.setHttpsAddress( httpsListenAddress ); webServer.setMaxThreads( config.get( ServerSettings.webserver_max_threads ) ); webServer.setWadlEnabled( config.get( ServerSettings.wadl_enabled ) ); @@ -308,39 +291,41 @@ private void startWebServer() throws Exception { setUpHttpLogging(); webServer.start(); - InetSocketAddress localHttpAddress = webServer.getLocalHttpAddress(); - connectorPortRegister.register( httpConnector.key(), localHttpAddress ); - httpsConnector.ifPresent( connector -> connectorPortRegister.register( connector.key(), webServer - .getLocalHttpsAddress() ) ); - checkHttpAdvertisedAddress( localHttpAddress ); - checkHttpsAdvertisedAddress(); + registerHttpAddressAfterStartup(); + registerHttpsAddressAfterStartup(); log.info( "Remote interface available at %s", baseUri() ); } catch ( Exception e ) { - log.error( "Failed to start Neo4j on %s: %s", getAddress(), e.getMessage() ); + ListenSocketAddress address = httpListenAddress != null ? httpListenAddress : httpsListenAddress; + log.error( "Failed to start Neo4j on %s: %s", address, e.getMessage() ); throw e; } } - private void checkHttpsAdvertisedAddress() + private void registerHttpAddressAfterStartup() { - httpsAdvertisedAddress.ifPresent( address -> + if ( httpConnector != null ) { - if ( address.getPort() == 0 ) + InetSocketAddress localHttpAddress = webServer.getLocalHttpAddress(); + connectorPortRegister.register( httpConnector.key(), localHttpAddress ); + if ( httpAdvertisedAddress.getPort() == 0 ) { - InetSocketAddress localHttpsAddress = webServer.getLocalHttpsAddress(); - httpsAdvertisedAddress = Optional - .of( new AdvertisedSocketAddress( localHttpsAddress.getHostString(), localHttpsAddress.getPort() ) ); + httpAdvertisedAddress = new AdvertisedSocketAddress( localHttpAddress.getHostString(), localHttpAddress.getPort() ); } - } ); + } } - private void checkHttpAdvertisedAddress( InetSocketAddress localHttpAddress ) + private void registerHttpsAddressAfterStartup() { - if ( httpAdvertisedAddress.getPort() == 0 ) + if ( httpsConnector != null ) { - httpAdvertisedAddress = new AdvertisedSocketAddress( localHttpAddress.getHostString(), localHttpAddress.getPort() ); + InetSocketAddress localHttpsAddress = webServer.getLocalHttpsAddress(); + connectorPortRegister.register( httpsConnector.key(), localHttpsAddress ); + if ( httpsAdvertisedAddress.getPort() == 0 ) + { + httpsAdvertisedAddress = new AdvertisedSocketAddress( localHttpsAddress.getHostString(), localHttpsAddress.getPort() ); + } } } @@ -360,16 +345,6 @@ private void setUpHttpLogging() throws IOException webServer.setRequestLog( requestLog ); } - public ListenSocketAddress getAddress() - { - return httpListenAddress; - } - - protected boolean httpsIsEnabled() - { - return httpsListenAddress.isPresent(); - } - protected Pattern[] getUriWhitelist() { return DEFAULT_URI_WHITELIST; @@ -378,7 +353,11 @@ protected Pattern[] getUriWhitelist() @Override public void stop() { + // Stop and clear the nested life. + // It needs to be cleared because server can be restarted, for example using a JMX bean. + // See `ServerManagementMBean` and its implementation. life.stop(); + life.clear(); } private void stopWebServer() throws Exception @@ -408,12 +387,15 @@ public TransactionRegistry getTransactionRegistry() @Override public URI baseUri() { - return uriBuilder.buildURI( httpAdvertisedAddress, false ); + return httpAdvertisedAddress != null + ? uriBuilder.buildURI( httpAdvertisedAddress, false ) + : uriBuilder.buildURI( httpsAdvertisedAddress, true ); } public Optional httpsUri() { - return httpsAdvertisedAddress.map( address -> uriBuilder.buildURI( address, true ) ); + return Optional.ofNullable( httpsAdvertisedAddress ) + .map( address -> uriBuilder.buildURI( address, true ) ); } public WebServer getWebServer() @@ -485,6 +467,36 @@ protected T resolveDependency( Class type ) return dependencyResolver.resolveDependency( type ); } + private static void verifyConnectorsConfiguration( Config config ) + { + HttpConnector httpConnector = findConnector( config, Encryption.NONE ); + HttpConnector httpsConnector = findConnector( config, Encryption.TLS ); + + if ( httpConnector == null && httpsConnector == null ) + { + throw new IllegalArgumentException( "Either HTTP or HTTPS connector must be configured to run the server" ); + } + } + + private static HttpConnector findConnector( Config config, Encryption encryption ) + { + return config.enabledHttpConnectors() + .stream() + .filter( connector -> connector.encryptionLevel() == encryption ) + .findFirst() + .orElse( null ); + } + + private static ListenSocketAddress listenAddressFor( Config config, HttpConnector connector ) + { + return connector == null ? null : config.get( connector.listen_address ); + } + + private static AdvertisedSocketAddress advertisedAddressFor( Config config, HttpConnector connector ) + { + return connector == null ? null : config.get( connector.advertised_address ); + } + private class ServerDependenciesLifeCycleAdapter extends LifecycleAdapter { @Override 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 4204028d34d38..d2cec139602c4 100644 --- a/community/server/src/main/java/org/neo4j/server/CommunityBootstrapper.java +++ b/community/server/src/main/java/org/neo4j/server/CommunityBootstrapper.java @@ -19,29 +19,23 @@ */ package org.neo4j.server; -import java.util.Collection; -import java.util.Collections; -import javax.annotation.Nonnull; - import org.neo4j.graphdb.facade.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.database.CommunityGraphFactory; +import org.neo4j.server.database.GraphFactory; public class CommunityBootstrapper extends ServerBootstrapper { @Override - protected NeoServer createNeoServer( Config config, GraphDatabaseDependencies dependencies, - LogProvider logProvider ) + protected GraphFactory createGraphFactory( Config config ) { - return new CommunityNeoServer( config, dependencies, logProvider ); + return new CommunityGraphFactory(); } @Override - @Nonnull - protected Collection configurationValidators() + protected NeoServer createNeoServer( GraphFactory graphFactory, Config config, GraphDatabaseDependencies dependencies, LogProvider userLogProvider ) { - return Collections.singletonList( new ServerConfigurationValidator() ); + return new CommunityNeoServer( config, graphFactory, dependencies, userLogProvider ); } } diff --git a/community/server/src/main/java/org/neo4j/server/CommunityNeoServer.java b/community/server/src/main/java/org/neo4j/server/CommunityNeoServer.java index b4812d86623ed..12e74f88738c6 100644 --- a/community/server/src/main/java/org/neo4j/server/CommunityNeoServer.java +++ b/community/server/src/main/java/org/neo4j/server/CommunityNeoServer.java @@ -19,22 +19,17 @@ */ package org.neo4j.server; -import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.function.Supplier; -import org.neo4j.graphdb.facade.GraphDatabaseFacadeFactory; -import org.neo4j.graphdb.factory.GraphDatabaseSettings; -import org.neo4j.graphdb.factory.module.edition.CommunityEditionModule; import org.neo4j.kernel.api.net.NetworkConnectionTracker; import org.neo4j.kernel.configuration.Config; import org.neo4j.kernel.configuration.ConnectorPortRegister; -import org.neo4j.kernel.impl.factory.DatabaseInfo; import org.neo4j.logging.LogProvider; -import org.neo4j.server.database.Database; -import org.neo4j.server.database.LifecycleManagingDatabase.GraphFactory; +import org.neo4j.server.database.CommunityGraphFactory; +import org.neo4j.server.database.GraphFactory; import org.neo4j.server.modules.AuthorizationModule; import org.neo4j.server.modules.ConsoleModule; import org.neo4j.server.modules.DBMSModule; @@ -52,28 +47,19 @@ import org.neo4j.server.web.WebServer; import org.neo4j.udc.UsageData; -import static org.neo4j.server.database.LifecycleManagingDatabase.lifecycleManagingDatabase; +import static org.neo4j.graphdb.facade.GraphDatabaseFacadeFactory.Dependencies; import static org.neo4j.server.rest.discovery.CommunityDiscoverableURIs.communityDiscoverableURIs; public class CommunityNeoServer extends AbstractNeoServer { - protected static final GraphFactory COMMUNITY_FACTORY = ( config, dependencies ) -> + public CommunityNeoServer( Config config, Dependencies dependencies, LogProvider logProvider ) { - File storeDir = config.get( GraphDatabaseSettings.databases_root_path ); - return new GraphDatabaseFacadeFactory( DatabaseInfo.COMMUNITY, CommunityEditionModule::new ) - .newFacade( storeDir, config, dependencies ); - }; - - public CommunityNeoServer( Config config, GraphDatabaseFacadeFactory.Dependencies dependencies, - LogProvider logProvider ) - { - this( config, lifecycleManagingDatabase( COMMUNITY_FACTORY ), dependencies, logProvider ); + this( config, new CommunityGraphFactory(), dependencies, logProvider ); } - public CommunityNeoServer( Config config, Database.Factory dbFactory, GraphDatabaseFacadeFactory.Dependencies - dependencies, LogProvider logProvider ) + public CommunityNeoServer( Config config, GraphFactory graphFactory, Dependencies dependencies, LogProvider logProvider ) { - super( config, dbFactory, dependencies, logProvider ); + super( config, graphFactory, dependencies, logProvider ); } @Override diff --git a/community/server/src/main/java/org/neo4j/server/DisabledNeoServer.java b/community/server/src/main/java/org/neo4j/server/DisabledNeoServer.java new file mode 100644 index 0000000000000..fd7d213710635 --- /dev/null +++ b/community/server/src/main/java/org/neo4j/server/DisabledNeoServer.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.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 . + */ +package org.neo4j.server; + +import java.net.URI; +import java.util.Collections; + +import org.neo4j.kernel.configuration.Config; +import org.neo4j.kernel.lifecycle.LifeSupport; +import org.neo4j.logging.LogProvider; +import org.neo4j.server.database.Database; +import org.neo4j.server.database.GraphFactory; +import org.neo4j.server.database.LifecycleManagingDatabase; +import org.neo4j.server.plugins.DisabledPluginManager; +import org.neo4j.server.plugins.PluginManager; +import org.neo4j.server.rest.management.AdvertisableService; +import org.neo4j.server.rest.transactional.DisabledTransactionRegistry; +import org.neo4j.server.rest.transactional.TransactionRegistry; + +import static org.neo4j.graphdb.facade.GraphDatabaseFacadeFactory.Dependencies; +import static org.neo4j.server.AbstractNeoServer.NEO4J_IS_STARTING_MESSAGE; +import static org.neo4j.server.exception.ServerStartupErrors.translateToServerStartupError; + +class DisabledNeoServer implements NeoServer +{ + private final GraphFactory graphFactory; + private final Dependencies dependencies; + private final Config config; + + private Database db; + private final LifeSupport life = new LifeSupport(); + + DisabledNeoServer( GraphFactory graphFactory, Dependencies dependencies, Config config, LogProvider logProvider ) + { + this.graphFactory = graphFactory; + this.dependencies = dependencies; + this.config = config; + logProvider.getLog( getClass() ).info( NEO4J_IS_STARTING_MESSAGE ); + } + + @Override + public void start() + { + db = new LifecycleManagingDatabase( config, graphFactory, dependencies ); + life.add( db ); + + try + { + life.start(); + } + catch ( Throwable t ) + { + life.shutdown(); + throw translateToServerStartupError( t ); + } + } + + @Override + public void stop() + { + life.stop(); + } + + @Override + public Config getConfig() + { + return config; + } + + @Override + public Database getDatabase() + { + return db; + } + + @Override + public TransactionRegistry getTransactionRegistry() + { + return DisabledTransactionRegistry.INSTANCE; + } + + @Override + public PluginManager getExtensionManager() + { + return DisabledPluginManager.INSTANCE; + } + + @Override + public URI baseUri() + { + throw new UnsupportedOperationException( "Neo4j server is disabled" ); + } + + @Override + public Iterable getServices() + { + return Collections.emptyList(); + } +} diff --git a/community/server/src/main/java/org/neo4j/server/NeoServer.java b/community/server/src/main/java/org/neo4j/server/NeoServer.java index 19e5b0392e681..049667538e553 100644 --- a/community/server/src/main/java/org/neo4j/server/NeoServer.java +++ b/community/server/src/main/java/org/neo4j/server/NeoServer.java @@ -29,8 +29,6 @@ public interface NeoServer { - void init(); - void start(); void stop(); 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 d4d55aecddb9b..f3d6d5ea6f7e8 100644 --- a/community/server/src/main/java/org/neo4j/server/ServerBootstrapper.java +++ b/community/server/src/main/java/org/neo4j/server/ServerBootstrapper.java @@ -23,11 +23,11 @@ import java.io.File; import java.util.Collection; +import java.util.Collections; import java.util.Map; 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.facade.GraphDatabaseDependencies; @@ -40,6 +40,7 @@ import org.neo4j.logging.FormattedLogProvider; import org.neo4j.logging.Log; import org.neo4j.logging.LogProvider; +import org.neo4j.server.database.GraphFactory; import org.neo4j.server.logging.JULBridge; import org.neo4j.server.logging.JettyLogBridge; @@ -164,11 +165,30 @@ public NeoServer getServer() return server; } - protected abstract NeoServer createNeoServer( Config config, GraphDatabaseDependencies dependencies, - LogProvider userLogProvider ); + private NeoServer createNeoServer( Config config, GraphDatabaseDependencies dependencies, LogProvider userLogProvider ) + { + GraphFactory graphFactory = createGraphFactory( config ); + + boolean httpAndHttpsDisabled = config.enabledHttpConnectors().isEmpty(); + if ( httpAndHttpsDisabled ) + { + return new DisabledNeoServer( graphFactory, dependencies, config, userLogProvider ); + } + return createNeoServer( graphFactory, config, dependencies, userLogProvider ); + } - @Nonnull - protected abstract Collection configurationValidators(); + protected abstract GraphFactory createGraphFactory( Config config ); + + /** + * Create a new server component. This method is invoked only when at least one HTTP connector is enabled. + */ + protected abstract NeoServer createNeoServer( GraphFactory graphFactory, Config config, GraphDatabaseDependencies dependencies, + LogProvider userLogProvider ); + + protected Collection configurationValidators() + { + return Collections.emptyList(); + } private static LogProvider setupLogging( Config config ) { diff --git a/community/server/src/main/java/org/neo4j/server/database/CommunityGraphFactory.java b/community/server/src/main/java/org/neo4j/server/database/CommunityGraphFactory.java new file mode 100644 index 0000000000000..99f82b70b4e06 --- /dev/null +++ b/community/server/src/main/java/org/neo4j/server/database/CommunityGraphFactory.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.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 . + */ +package org.neo4j.server.database; + +import java.io.File; + +import org.neo4j.graphdb.facade.GraphDatabaseFacadeFactory; +import org.neo4j.graphdb.facade.GraphDatabaseFacadeFactory.Dependencies; +import org.neo4j.graphdb.factory.GraphDatabaseSettings; +import org.neo4j.graphdb.factory.module.edition.CommunityEditionModule; +import org.neo4j.kernel.configuration.Config; +import org.neo4j.kernel.impl.factory.GraphDatabaseFacade; + +import static org.neo4j.kernel.impl.factory.DatabaseInfo.COMMUNITY; + +public class CommunityGraphFactory implements GraphFactory +{ + @Override + public GraphDatabaseFacade newGraphDatabase( Config config, Dependencies dependencies ) + { + File storeDir = config.get( GraphDatabaseSettings.databases_root_path ); + GraphDatabaseFacadeFactory facadeFactory = new GraphDatabaseFacadeFactory( COMMUNITY, CommunityEditionModule::new ); + return facadeFactory.newFacade( storeDir, config, dependencies ); + } +} diff --git a/community/server/src/main/java/org/neo4j/server/database/Database.java b/community/server/src/main/java/org/neo4j/server/database/Database.java index 3949987710df5..f98f4d78c9a79 100644 --- a/community/server/src/main/java/org/neo4j/server/database/Database.java +++ b/community/server/src/main/java/org/neo4j/server/database/Database.java @@ -21,18 +21,11 @@ import java.io.File; -import org.neo4j.graphdb.facade.GraphDatabaseFacadeFactory; -import org.neo4j.kernel.configuration.Config; import org.neo4j.kernel.impl.factory.GraphDatabaseFacade; import org.neo4j.kernel.lifecycle.Lifecycle; public interface Database extends Lifecycle { - interface Factory - { - Database newDatabase( Config config, GraphDatabaseFacadeFactory.Dependencies dependencies ); - } - File getLocation(); GraphDatabaseFacade getGraph(); diff --git a/community/server/src/main/java/org/neo4j/server/database/GraphFactory.java b/community/server/src/main/java/org/neo4j/server/database/GraphFactory.java new file mode 100644 index 0000000000000..4e1bc1e86f40a --- /dev/null +++ b/community/server/src/main/java/org/neo4j/server/database/GraphFactory.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.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 . + */ +package org.neo4j.server.database; + +import org.neo4j.kernel.configuration.Config; +import org.neo4j.kernel.impl.factory.GraphDatabaseFacade; + +import static org.neo4j.graphdb.facade.GraphDatabaseFacadeFactory.Dependencies; + +@FunctionalInterface +public interface GraphFactory +{ + GraphDatabaseFacade newGraphDatabase( Config config, Dependencies dependencies ); +} diff --git a/community/server/src/main/java/org/neo4j/server/database/LifecycleManagingDatabase.java b/community/server/src/main/java/org/neo4j/server/database/LifecycleManagingDatabase.java index 9e8b3ce022eed..2886eaeb374c1 100644 --- a/community/server/src/main/java/org/neo4j/server/database/LifecycleManagingDatabase.java +++ b/community/server/src/main/java/org/neo4j/server/database/LifecycleManagingDatabase.java @@ -37,16 +37,6 @@ public class LifecycleManagingDatabase implements Database static final String CYPHER_WARMUP_QUERY = "MATCH (a:` This query is just used to load the cypher compiler during warmup. Please ignore `) RETURN a LIMIT 0"; - public interface GraphFactory - { - GraphDatabaseFacade newGraphDatabase( Config config, GraphDatabaseFacadeFactory.Dependencies dependencies ); - } - - public static Database.Factory lifecycleManagingDatabase( final GraphFactory graphDbFactory ) - { - return ( config, dependencies ) -> new LifecycleManagingDatabase( config, graphDbFactory, dependencies ); - } - private final Config config; private final GraphFactory dbFactory; private final GraphDatabaseFacadeFactory.Dependencies dependencies; diff --git a/community/server/src/main/java/org/neo4j/server/exception/ServerStartupErrors.java b/community/server/src/main/java/org/neo4j/server/exception/ServerStartupErrors.java index c357df99d0367..1d22564efadde 100644 --- a/community/server/src/main/java/org/neo4j/server/exception/ServerStartupErrors.java +++ b/community/server/src/main/java/org/neo4j/server/exception/ServerStartupErrors.java @@ -19,8 +19,6 @@ */ package org.neo4j.server.exception; -import java.util.function.Function; - import org.neo4j.helpers.Exceptions; import org.neo4j.kernel.impl.storemigration.UpgradeNotAllowedException; import org.neo4j.server.ServerStartupException; @@ -37,35 +35,13 @@ private ServerStartupErrors() { } - /** - * Each function in this array handles translating one case. If it doesn't know how to translate a given - * throwable, it simply returns null. - */ - private static final Function[] translators = new Function[] { - // Handle upgrade errors - (Function) o -> - { - Throwable rootCause = Exceptions.rootCause( o ); - if ( rootCause instanceof UpgradeNotAllowedException ) - { - return new UpgradeDisallowedStartupException( (UpgradeNotAllowedException)rootCause ); - } - return null; - } - }; - public static ServerStartupException translateToServerStartupError( Throwable cause ) { - for ( Function translator : translators ) + Throwable rootCause = Exceptions.rootCause( cause ); + if ( rootCause instanceof UpgradeNotAllowedException ) { - ServerStartupException r = translator.apply( cause ); - if ( r != null ) - { - return r; - } + return new UpgradeDisallowedStartupException( (UpgradeNotAllowedException) rootCause ); } - return new ServerStartupException( format( "Starting Neo4j failed: %s", cause.getMessage() ), cause ); } - } diff --git a/community/server/src/main/java/org/neo4j/server/modules/RESTApiModule.java b/community/server/src/main/java/org/neo4j/server/modules/RESTApiModule.java index 5e37e8b985051..afe5c627b1c52 100644 --- a/community/server/src/main/java/org/neo4j/server/modules/RESTApiModule.java +++ b/community/server/src/main/java/org/neo4j/server/modules/RESTApiModule.java @@ -26,6 +26,7 @@ import org.neo4j.kernel.configuration.Config; import org.neo4j.logging.LogProvider; import org.neo4j.server.configuration.ServerSettings; +import org.neo4j.server.plugins.DefaultPluginManager; import org.neo4j.server.plugins.PluginManager; import org.neo4j.server.rest.web.BatchOperationService; import org.neo4j.server.rest.web.CollectUserAgentFilter; @@ -96,7 +97,6 @@ private List getClassNames() public void stop() { webServer.removeJAXRSClasses( getClassNames(), restApiUri().toString() ); - unloadPlugins(); } private URI restApiUri() @@ -106,12 +106,7 @@ private URI restApiUri() private void loadPlugins() { - plugins = new PluginManager( config, logProvider ); - } - - private void unloadPlugins() - { - // TODO + plugins = new DefaultPluginManager( logProvider ); } public PluginManager getPlugins() diff --git a/community/server/src/main/java/org/neo4j/server/plugins/DefaultPluginManager.java b/community/server/src/main/java/org/neo4j/server/plugins/DefaultPluginManager.java new file mode 100644 index 0000000000000..e17fd87c97c0e --- /dev/null +++ b/community/server/src/main/java/org/neo4j/server/plugins/DefaultPluginManager.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.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 . + */ +package org.neo4j.server.plugins; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.neo4j.helpers.collection.Pair; +import org.neo4j.kernel.internal.GraphDatabaseAPI; +import org.neo4j.logging.Log; +import org.neo4j.logging.LogProvider; +import org.neo4j.server.rest.repr.BadInputException; +import org.neo4j.server.rest.repr.ExtensionPointRepresentation; +import org.neo4j.server.rest.repr.Representation; + +public final class DefaultPluginManager implements PluginManager +{ + private final Map extensions = new HashMap<>(); + + public DefaultPluginManager( LogProvider logProvider ) + { + Map> extensions = new HashMap<>(); + Log log = logProvider.getLog( getClass() ); + Iterable loadedPlugins = ServerPlugin.load(); + for ( ServerPlugin plugin : loadedPlugins ) + { + PluginPointFactory factory = new PluginPointFactoryImpl(); + final ServerExtender extender = new ServerExtender( factory ); + try + { + plugin.loadServerExtender( extender ); + } + catch ( Exception | LinkageError ex ) + { + log.warn( "Failed to load plugin [%s]: %s", plugin.toString(), ex.getMessage() ); + continue; + } + Pair old = extensions.put( plugin.name, Pair.of( plugin, extender ) ); + if ( old != null ) + { + log.warn( String.format( "Extension naming conflict \"%s\" between \"%s\" and \"%s\"", plugin.name, + old.first().getClass(), plugin.getClass() ) ); + } + } + for ( Pair extension : extensions.values() ) + { + log.info( String.format( "Loaded server plugin \"%s\"", extension.first().name ) ); + for ( PluginPoint point : extension.other().all() ) + { + log.info( String.format( " %s.%s: %s", point.forType().getSimpleName(), point.name(), + point.getDescription() ) ); + } + this.extensions.put( extension.first().name, extension.other() ); + } + } + + @Override + public Map> getExensionsFor( Class type ) + { + Map> result = new HashMap<>(); + for ( Map.Entry extension : extensions.entrySet() ) + { + List methods = new ArrayList<>(); + for ( PluginPoint method : extension.getValue() + .getExtensionsFor( type ) ) + { + methods.add( method.name() ); + } + if ( !methods.isEmpty() ) + { + result.put( extension.getKey(), methods ); + } + } + return result; + } + + private PluginPoint extension( String name, Class type, String method ) throws PluginLookupException + { + ServerExtender extender = extensions.get( name ); + if ( extender == null ) + { + throw new PluginLookupException( "No such ServerPlugin: \"" + name + "\"" ); + } + return extender.getExtensionPoint( type, method ); + } + + @Override + public ExtensionPointRepresentation describe( String name, Class type, String method ) + throws PluginLookupException + { + return describe( extension( name, type, method ) ); + } + + private ExtensionPointRepresentation describe( PluginPoint extension ) + { + ExtensionPointRepresentation representation = new ExtensionPointRepresentation( extension.name(), + extension.forType(), extension.getDescription() ); + extension.describeParameters( representation ); + return representation; + } + + @Override + public List describeAll( String name ) throws PluginLookupException + { + ServerExtender extender = extensions.get( name ); + if ( extender == null ) + { + throw new PluginLookupException( "No such ServerPlugin: \"" + name + "\"" ); + } + List result = new ArrayList<>(); + for ( PluginPoint plugin : extender.all() ) + { + result.add( describe( plugin ) ); + } + return result; + } + + @Override + public Representation invoke( GraphDatabaseAPI graphDb, String name, Class type, String method, + T context, ParameterList params ) throws PluginLookupException, BadInputException, + PluginInvocationFailureException, BadPluginInvocationException + { + PluginPoint plugin = extension( name, type, method ); + try + { + return plugin.invoke( graphDb, context, params ); + } + catch ( BadInputException | PluginInvocationFailureException | BadPluginInvocationException e ) + { + throw e; + } + catch ( Exception e ) + { + throw new PluginInvocationFailureException( e ); + } + } + + @Override + public Set extensionNames() + { + return Collections.unmodifiableSet( extensions.keySet() ); + } +} diff --git a/community/server/src/main/java/org/neo4j/server/plugins/DisabledPluginManager.java b/community/server/src/main/java/org/neo4j/server/plugins/DisabledPluginManager.java new file mode 100644 index 0000000000000..aa55229ffec96 --- /dev/null +++ b/community/server/src/main/java/org/neo4j/server/plugins/DisabledPluginManager.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.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 . + */ +package org.neo4j.server.plugins; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.neo4j.kernel.internal.GraphDatabaseAPI; +import org.neo4j.server.rest.repr.ExtensionPointRepresentation; +import org.neo4j.server.rest.repr.Representation; + +public class DisabledPluginManager implements PluginManager +{ + public static final PluginManager INSTANCE = new DisabledPluginManager(); + + private DisabledPluginManager() + { + } + + @Override + public Representation invoke( GraphDatabaseAPI graphDb, String name, Class type, String method, T context, ParameterList params ) + { + throw new UnsupportedOperationException(); + } + + @Override + public ExtensionPointRepresentation describe( String name, Class type, String method ) + { + throw new UnsupportedOperationException(); + } + + @Override + public List describeAll( String extensionName ) + { + return Collections.emptyList(); + } + + @Override + public Set extensionNames() + { + return Collections.emptySet(); + } + + @Override + public Map> getExensionsFor( Class type ) + { + return Collections.emptyMap(); + } +} diff --git a/community/server/src/main/java/org/neo4j/server/plugins/PluginManager.java b/community/server/src/main/java/org/neo4j/server/plugins/PluginManager.java index 47c41fb3761e1..edffa13ef48b8 100644 --- a/community/server/src/main/java/org/neo4j/server/plugins/PluginManager.java +++ b/community/server/src/main/java/org/neo4j/server/plugins/PluginManager.java @@ -19,152 +19,8 @@ */ package org.neo4j.server.plugins; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.neo4j.helpers.collection.Pair; -import org.neo4j.kernel.configuration.Config; -import org.neo4j.kernel.internal.GraphDatabaseAPI; -import org.neo4j.logging.Log; -import org.neo4j.logging.LogProvider; -import org.neo4j.server.rest.repr.BadInputException; import org.neo4j.server.rest.repr.ExtensionInjector; -import org.neo4j.server.rest.repr.ExtensionPointRepresentation; -import org.neo4j.server.rest.repr.Representation; -public final class PluginManager implements ExtensionInjector, PluginInvocator +public interface PluginManager extends ExtensionInjector, PluginInvocator { - private final Map extensions = new HashMap<>(); - - public PluginManager( Config serverConfig, LogProvider logProvider ) - { - this( serverConfig, ServerPlugin.load(), logProvider ); - } - - PluginManager( Config serverConfig, Iterable plugins, LogProvider logProvider ) - { - Map> extensions = new HashMap<>(); - Log log = logProvider.getLog( getClass() ); - for ( ServerPlugin plugin : plugins ) - { - PluginPointFactory factory = new PluginPointFactoryImpl(); - final ServerExtender extender = new ServerExtender( factory ); - try - { - plugin.loadServerExtender( extender ); - } - catch ( Exception | LinkageError ex ) - { - log.warn( "Failed to load plugin [%s]: %s", plugin.toString(), ex.getMessage() ); - continue; - } - Pair old = extensions.put( plugin.name, Pair.of( plugin, extender ) ); - if ( old != null ) - { - log.warn( String.format( "Extension naming conflict \"%s\" between \"%s\" and \"%s\"", plugin.name, - old.first().getClass(), plugin.getClass() ) ); - } - } - for ( Pair extension : extensions.values() ) - { - log.info( String.format( "Loaded server plugin \"%s\"", extension.first().name ) ); - for ( PluginPoint point : extension.other().all() ) - { - log.info( String.format( " %s.%s: %s", point.forType().getSimpleName(), point.name(), - point.getDescription() ) ); - } - this.extensions.put( extension.first().name, extension.other() ); - } - } - - @Override - public Map> getExensionsFor( Class type ) - { - Map> result = new HashMap<>(); - for ( Map.Entry extension : extensions.entrySet() ) - { - List methods = new ArrayList<>(); - for ( PluginPoint method : extension.getValue() - .getExtensionsFor( type ) ) - { - methods.add( method.name() ); - } - if ( !methods.isEmpty() ) - { - result.put( extension.getKey(), methods ); - } - } - return result; - } - - private PluginPoint extension( String name, Class type, String method ) throws PluginLookupException - { - ServerExtender extender = extensions.get( name ); - if ( extender == null ) - { - throw new PluginLookupException( "No such ServerPlugin: \"" + name + "\"" ); - } - return extender.getExtensionPoint( type, method ); - } - - @Override - public ExtensionPointRepresentation describe( String name, Class type, String method ) - throws PluginLookupException - { - return describe( extension( name, type, method ) ); - } - - private ExtensionPointRepresentation describe( PluginPoint extension ) - { - ExtensionPointRepresentation representation = new ExtensionPointRepresentation( extension.name(), - extension.forType(), extension.getDescription() ); - extension.describeParameters( representation ); - return representation; - } - - @Override - public List describeAll( String name ) throws PluginLookupException - { - ServerExtender extender = extensions.get( name ); - if ( extender == null ) - { - throw new PluginLookupException( "No such ServerPlugin: \"" + name + "\"" ); - } - List result = new ArrayList<>(); - for ( PluginPoint plugin : extender.all() ) - { - result.add( describe( plugin ) ); - } - return result; - } - - @Override - public Representation invoke( GraphDatabaseAPI graphDb, String name, Class type, String method, - T context, ParameterList params ) throws PluginLookupException, BadInputException, - PluginInvocationFailureException, BadPluginInvocationException - { - PluginPoint plugin = extension( name, type, method ); - try - { - return plugin.invoke( graphDb, context, params ); - } - catch ( BadInputException | PluginInvocationFailureException | BadPluginInvocationException e ) - { - throw e; - } - catch ( Exception e ) - { - throw new PluginInvocationFailureException( e ); - } - } - - @Override - public Set extensionNames() - { - return Collections.unmodifiableSet( extensions.keySet() ); - } } diff --git a/community/server/src/main/java/org/neo4j/server/rest/transactional/DisabledTransactionRegistry.java b/community/server/src/main/java/org/neo4j/server/rest/transactional/DisabledTransactionRegistry.java new file mode 100644 index 0000000000000..550216482d8dc --- /dev/null +++ b/community/server/src/main/java/org/neo4j/server/rest/transactional/DisabledTransactionRegistry.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.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 . + */ +package org.neo4j.server.rest.transactional; + +public class DisabledTransactionRegistry implements TransactionRegistry +{ + public static final TransactionRegistry INSTANCE = new DisabledTransactionRegistry(); + + private DisabledTransactionRegistry() + { + } + + @Override + public long begin( TransactionHandle handle ) + { + throw new UnsupportedOperationException(); + } + + @Override + public long release( long id, TransactionHandle transactionHandle ) + { + throw new UnsupportedOperationException(); + } + + @Override + public TransactionHandle acquire( long id ) + { + throw new UnsupportedOperationException(); + } + + @Override + public void forget( long id ) + { + } + + @Override + public TransactionHandle terminate( long id ) + { + throw new UnsupportedOperationException(); + } + + @Override + public void rollbackAllSuspendedTransactions() + { + } +} diff --git a/community/server/src/main/java/org/neo4j/server/web/Jetty9WebServer.java b/community/server/src/main/java/org/neo4j/server/web/Jetty9WebServer.java index e3e4dd9e0f318..78a02f06040f9 100644 --- a/community/server/src/main/java/org/neo4j/server/web/Jetty9WebServer.java +++ b/community/server/src/main/java/org/neo4j/server/web/Jetty9WebServer.java @@ -48,11 +48,12 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Optional; +import java.util.Objects; import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.BlockingQueue; import java.util.function.Consumer; +import java.util.stream.Stream; import javax.servlet.DispatcherType; import javax.servlet.Filter; import javax.servlet.ServletException; @@ -71,11 +72,10 @@ import org.neo4j.ssl.SslPolicy; import static java.lang.String.format; +import static java.util.stream.Collectors.joining; /** * This class handles the configuration and runtime management of a Jetty web server. The server is restartable. - * - * TODO: it really should be split up into a builder that returns a Closeable, to separate between the conf and runtime part. */ public class Jetty9WebServer implements WebServer { @@ -90,11 +90,11 @@ public class Jetty9WebServer implements WebServer private Server jetty; private HandlerCollection handlers; - private ListenSocketAddress jettyAddress = DEFAULT_ADDRESS; - private Optional jettyHttpsAddress = Optional.empty(); + private ListenSocketAddress httpAddress = DEFAULT_ADDRESS; + private ListenSocketAddress httpsAddress; - private ServerConnector serverConnector; - private ServerConnector secureServerConnector; + private ServerConnector httpConnector; + private ServerConnector httpsConnector; private final HashMap staticContent = new HashMap<>(); private final Map jaxRSPackages = new HashMap<>(); @@ -119,21 +119,26 @@ public void start() throws Exception { if ( jetty == null ) { - JettyThreadCalculator jettyThreadCalculator = new JettyThreadCalculator( jettyMaxThreads ); + verifyAddressConfiguration(); + JettyThreadCalculator jettyThreadCalculator = new JettyThreadCalculator( jettyMaxThreads ); jetty = new Server( createQueuedThreadPool( jettyThreadCalculator ) ); - serverConnector = connectorFactory.createConnector( jetty, jettyAddress, jettyThreadCalculator ); - jetty.addConnector( serverConnector ); - jettyHttpsAddress.ifPresent( address -> + if ( httpAddress != null ) + { + httpConnector = connectorFactory.createConnector( jetty, httpAddress, jettyThreadCalculator ); + jetty.addConnector( httpConnector ); + } + + if ( httpsAddress != null ) { if ( sslPolicy == null ) { throw new RuntimeException( "HTTPS set to enabled, but no SSL policy provided" ); } - secureServerConnector = sslSocketFactory.createConnector( jetty, sslPolicy, address, jettyThreadCalculator ); - jetty.addConnector( secureServerConnector ); - } ); + httpsConnector = sslSocketFactory.createConnector( jetty, sslPolicy, httpsAddress, jettyThreadCalculator ); + jetty.addConnector( httpsConnector ); + } if ( jettyCreatedCallback != null ) { @@ -189,9 +194,21 @@ public void stop() } @Override - public void setAddress( ListenSocketAddress address ) + public void setHttpAddress( ListenSocketAddress address ) { - jettyAddress = address; + httpAddress = address; + } + + @Override + public void setHttpsAddress( ListenSocketAddress address ) + { + httpsAddress = address; + } + + @Override + public void setSslPolicy( SslPolicy policy ) + { + sslPolicy = policy; } @Override @@ -301,24 +318,12 @@ public void setRequestLog( RequestLog requestLog ) this.requestLog = requestLog; } - @Override - public void setHttpsAddress( Optional address ) - { - jettyHttpsAddress = address; - } - - @Override - public void setSslPolicy( SslPolicy sslPolicy ) - { - this.sslPolicy = sslPolicy; - } - public Server getJetty() { return jetty; } - protected void startJetty() throws Exception + private void startJetty() throws Exception { try { @@ -326,29 +331,20 @@ protected void startJetty() throws Exception } catch ( BindException e ) { - if ( jettyHttpsAddress.isPresent() ) - { - throw new PortBindException( jettyAddress, jettyHttpsAddress.get(), e ); - } - else - { - throw new PortBindException( jettyAddress, e ); - } + throw new PortBindException( httpAddress, httpsAddress, e ); } } @Override public InetSocketAddress getLocalHttpAddress() { - return toSocketAddress( serverConnector ); + return getAddress( "HTTP", httpConnector ); } @Override public InetSocketAddress getLocalHttpsAddress() { - return Optional.ofNullable( secureServerConnector) - .map( Jetty9WebServer::toSocketAddress ) - .orElseThrow( () -> new IllegalStateException( "Secure connector is not configured" ) ); + return getAddress( "HTTPS", httpsConnector ); } private void loadAllMounts() @@ -474,8 +470,8 @@ private void loadStaticContent( String mountPoint ) } else { - log.warn( "No static content available for Neo Server at %s, management console may not be available.", - jettyAddress ); + log.warn( "No static content available for Neo4j Server at %s. management console may not be available.", + addressConfigurationDescription() ); } } catch ( Exception e ) @@ -522,11 +518,31 @@ private void addFiltersTo( ServletContextHandler context ) } } - private static InetSocketAddress toSocketAddress( ServerConnector connector ) + private static InetSocketAddress getAddress( String name, ServerConnector connector ) { + if ( connector == null ) + { + throw new IllegalStateException( name + " connector is not configured" ); + } return new InetSocketAddress( connector.getHost(), connector.getLocalPort() ); } + private void verifyAddressConfiguration() + { + if ( httpAddress == null && httpsAddress == null ) + { + throw new IllegalStateException( "Either HTTP or HTTPS address must be configured to run the server" ); + } + } + + private String addressConfigurationDescription() + { + return Stream.of( httpAddress, httpsAddress ) + .filter( Objects::nonNull ) + .map( Object::toString ) + .collect( joining( ", " ) ); + } + private static class FilterDefinition { private final Filter filter; diff --git a/community/server/src/main/java/org/neo4j/server/web/WebServer.java b/community/server/src/main/java/org/neo4j/server/web/WebServer.java index 55983230c37b6..657008962dad5 100644 --- a/community/server/src/main/java/org/neo4j/server/web/WebServer.java +++ b/community/server/src/main/java/org/neo4j/server/web/WebServer.java @@ -26,7 +26,6 @@ import java.net.InetSocketAddress; import java.util.Collection; import java.util.List; -import java.util.Optional; import java.util.function.Consumer; import javax.servlet.Filter; import javax.servlet.ServletException; @@ -40,9 +39,9 @@ public interface WebServer { - void setAddress( ListenSocketAddress address ); + void setHttpAddress( ListenSocketAddress address ); - void setHttpsAddress( Optional address ); + void setHttpsAddress( ListenSocketAddress address ); void setSslPolicy( SslPolicy sslPolicy ); diff --git a/community/server/src/test/java/org/neo4j/server/BaseBootstrapperTestIT.java b/community/server/src/test/java/org/neo4j/server/BaseBootstrapperIT.java similarity index 53% rename from community/server/src/test/java/org/neo4j/server/BaseBootstrapperTestIT.java rename to community/server/src/test/java/org/neo4j/server/BaseBootstrapperIT.java index 19a6bf6afc4be..7e2f6f16ce280 100644 --- a/community/server/src/test/java/org/neo4j/server/BaseBootstrapperTestIT.java +++ b/community/server/src/test/java/org/neo4j/server/BaseBootstrapperIT.java @@ -26,28 +26,41 @@ import org.junit.rules.TemporaryFolder; import java.io.File; +import java.net.Socket; import java.util.Map; import java.util.concurrent.TimeUnit; +import org.neo4j.graphdb.Label; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Transaction; import org.neo4j.graphdb.config.Setting; +import org.neo4j.helpers.HostnamePort; import org.neo4j.kernel.configuration.BoltConnector; import org.neo4j.kernel.configuration.Config; +import org.neo4j.kernel.configuration.ConnectorPortRegister; +import org.neo4j.kernel.impl.factory.GraphDatabaseFacade; +import org.neo4j.kernel.internal.GraphDatabaseAPI; import org.neo4j.ports.allocation.PortAuthority; import org.neo4j.test.server.ExclusiveServerTestBase; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; import static org.neo4j.bolt.v1.transport.integration.Neo4jWithSocket.DEFAULT_CONNECTOR_KEY; import static org.neo4j.graphdb.factory.GraphDatabaseSettings.data_directory; import static org.neo4j.graphdb.factory.GraphDatabaseSettings.forced_kernel_id; import static org.neo4j.graphdb.factory.GraphDatabaseSettings.logs_directory; +import static org.neo4j.helpers.collection.Iterators.single; import static org.neo4j.helpers.collection.MapUtil.store; import static org.neo4j.helpers.collection.MapUtil.stringMap; import static org.neo4j.test.assertion.Assert.assertEventually; -public abstract class BaseBootstrapperTestIT extends ExclusiveServerTestBase +public abstract class BaseBootstrapperIT extends ExclusiveServerTestBase { @Rule public TemporaryFolder tempDir = new TemporaryFolder(); @@ -101,7 +114,7 @@ public void canSpecifyConfigFile() throws Throwable // Given File configFile = tempDir.newFile( Config.DEFAULT_CONFIG_FILE_NAME ); - Map properties = stringMap( forced_kernel_id.name(), "ourcustomvalue" ); + Map properties = stringMap( forced_kernel_id.name(), "ourcustomvalue" ); properties.putAll( ServerTestUtils.getDefaultRelativeProperties() ); properties.put( "dbms.connector.http.type", "HTTP" ); properties.put( "dbms.connector.http.enabled", "true" ); @@ -123,9 +136,9 @@ public void canSpecifyConfigFile() throws Throwable public void canOverrideConfigValues() throws Throwable { // Given - File configFile = tempDir.newFile( Config.DEFAULT_CONFIG_FILE_NAME); + File configFile = tempDir.newFile( Config.DEFAULT_CONFIG_FILE_NAME ); - Map properties = stringMap( forced_kernel_id.name(), "thisshouldnotshowup" ); + Map properties = stringMap( forced_kernel_id.name(), "thisshouldnotshowup" ); properties.putAll( ServerTestUtils.getDefaultRelativeProperties() ); properties.put( "dbms.connector.http.type", "HTTP" ); properties.put( "dbms.connector.http.enabled", "true" ); @@ -144,8 +157,141 @@ public void canOverrideConfigValues() throws Throwable assertThat( bootstrapper.getServer().getConfig().get( forced_kernel_id ), equalTo( "mycustomvalue" ) ); } + @Test + public void shouldStartWithHttpHttpsAndBoltDisabled() throws Exception + { + testStartupWithConnectors( false, false, false ); + } + + @Test + public void shouldStartWithHttpEnabledAndHttpsBoltDisabled() throws Exception + { + testStartupWithConnectors( true, false, false ); + } + + @Test + public void shouldStartWithHttpsEnabledAndHttpBoltDisabled() throws Exception + { + testStartupWithConnectors( false, true, false ); + } + + @Test + public void shouldStartWithBoltEnabledAndHttpHttpsDisabled() throws Exception + { + testStartupWithConnectors( false, false, true ); + } + + @Test + public void shouldStartWithHttpHttpsEnabledAndBoltDisabled() throws Exception + { + testStartupWithConnectors( true, true, false ); + } + + @Test + public void shouldStartWithHttpBoltEnabledAndHttpsDisabled() throws Exception + { + testStartupWithConnectors( true, false, true ); + } + + @Test + public void shouldStartWithHttpsBoltEnabledAndHttpDisabled() throws Exception + { + testStartupWithConnectors( false, true, true ); + } + + private void testStartupWithConnectors( boolean httpEnabled, boolean httpsEnabled, boolean boltEnabled ) throws Exception + { + int httpPort = httpEnabled ? 0 : 7474; + int httpsPort = httpsEnabled ? 0 : 7473; + int boltPort = boltEnabled ? 0 : 7687; + + int resultCode = ServerBootstrapper.start( bootstrapper, + "--home-dir", tempDir.newFolder( "home-dir" ).getAbsolutePath(), + "-c", configOption( data_directory, tempDir.getRoot().getAbsolutePath() ), + "-c", configOption( logs_directory, tempDir.getRoot().getAbsolutePath() ), + + "-c", "dbms.connector.http.enabled=" + httpEnabled, + "-c", "dbms.connector.http.listen_address=:" + httpPort, + + "-c", "dbms.connector.https.enabled=" + httpsEnabled, + "-c", "dbms.connector.https.listen_address=:" + httpsPort, + + "-c", "dbms.connector.bolt.enabled=" + boltEnabled, + "-c", "dbms.connector.bolt.listen_address=:" + boltPort + ); + + assertEquals( ServerBootstrapper.OK, resultCode ); + assertEventually( "Server was not started", bootstrapper::isRunning, is( true ), 1, TimeUnit.MINUTES ); + assertDbAccessibleAsEmbedded(); + + verifyConnector( "http", 7474, httpEnabled ); + verifyConnector( "https", 7473, httpsEnabled ); + verifyConnector( "bolt", 7687, boltEnabled ); + } + protected String configOption( Setting setting, String value ) { return setting.name() + "=" + value; } + + private void assertDbAccessibleAsEmbedded() + { + GraphDatabaseAPI db = db(); + + Label label = () -> "Node"; + String propertyKey = "key"; + String propertyValue = "value"; + + try ( Transaction tx = db.beginTx() ) + { + db.createNode( label ).setProperty( propertyKey, propertyValue ); + tx.success(); + } + try ( Transaction tx = db.beginTx() ) + { + Node node = single( db.findNodes( label ) ); + assertEquals( propertyValue, node.getProperty( propertyKey ) ); + tx.success(); + } + } + + private void verifyConnector( String name, int defaultPort, boolean enabled ) + { + HostnamePort address = connectorAddress( name ); + if ( enabled ) + { + assertNotNull( address ); + assertTrue( canConnectToSocket( address.getHost(), address.getPort() ) ); + } + else + { + assertNull( address ); + assertFalse( canConnectToSocket( "localhost", defaultPort ) ); + } + } + + private HostnamePort connectorAddress( String name ) + { + ConnectorPortRegister portRegister = db().getDependencyResolver().resolveDependency( ConnectorPortRegister.class ); + return portRegister.getLocalAddress( name ); + + } + + private GraphDatabaseFacade db() + { + return bootstrapper.getServer().getDatabase().getGraph(); + } + + private static boolean canConnectToSocket( String host, int port ) + { + try + { + new Socket( host, port ).close(); + return true; + } + catch ( Throwable ignore ) + { + return false; + } + } } diff --git a/community/server/src/test/java/org/neo4j/server/CommunityBootstrapperTestIT.java b/community/server/src/test/java/org/neo4j/server/CommunityBootstrapperIT.java similarity index 92% rename from community/server/src/test/java/org/neo4j/server/CommunityBootstrapperTestIT.java rename to community/server/src/test/java/org/neo4j/server/CommunityBootstrapperIT.java index ebb461b40ff87..84214489e4683 100644 --- a/community/server/src/test/java/org/neo4j/server/CommunityBootstrapperTestIT.java +++ b/community/server/src/test/java/org/neo4j/server/CommunityBootstrapperIT.java @@ -19,7 +19,7 @@ */ package org.neo4j.server; -public class CommunityBootstrapperTestIT extends BaseBootstrapperTestIT +public class CommunityBootstrapperIT extends BaseBootstrapperIT { @Override protected ServerBootstrapper newBootstrapper() diff --git a/community/server/src/test/java/org/neo4j/server/HttpsAccessIT.java b/community/server/src/test/java/org/neo4j/server/HttpsAccessIT.java index dba56f62c8930..e88383a2f097c 100644 --- a/community/server/src/test/java/org/neo4j/server/HttpsAccessIT.java +++ b/community/server/src/test/java/org/neo4j/server/HttpsAccessIT.java @@ -19,24 +19,30 @@ */ package org.neo4j.server; +import org.apache.http.client.utils.URIBuilder; import org.junit.After; import org.junit.Before; import org.junit.Test; -import java.io.IOException; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; +import java.net.URI; import java.security.SecureRandom; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; +import org.neo4j.graphdb.DependencyResolver; +import org.neo4j.helpers.HostnamePort; +import org.neo4j.kernel.configuration.ConnectorPortRegister; +import org.neo4j.server.helpers.CommunityServerBuilder; import org.neo4j.test.server.ExclusiveServerTestBase; import org.neo4j.test.server.HTTP; import org.neo4j.test.server.InsecureTrustManager; import static org.hamcrest.CoreMatchers.startsWith; import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.neo4j.server.helpers.CommunityServerBuilder.serverOnRandomPorts; import static org.neo4j.test.server.HTTP.GET; @@ -45,20 +51,88 @@ public class HttpsAccessIT extends ExclusiveServerTestBase { + private SSLSocketFactory originalSslSocketFactory; private CommunityNeoServer server; + @Before + public void setUp() + { + originalSslSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory(); + } + @After - public void stopTheServer() + public void tearDown() { + HttpsURLConnection.setDefaultSSLSocketFactory( originalSslSocketFactory ); server.stop(); } - @Before - public void startServer() throws NoSuchAlgorithmException, KeyManagementException, IOException + @Test + public void serverShouldSupportSsl() throws Exception + { + startServer(); + + assertThat( GET( httpsUri() ).status(), is( 200 ) ); + assertThat( GET(server.baseUri().toString()).status(), is( 200 ) ); + } + + @Test + public void txEndpointShouldReplyWithHttpsWhenItReturnsURLs() throws Exception { - server = serverOnRandomPorts().withHttpsEnabled() - .usingDataDir( folder.directory( name.getMethodName() ).getAbsolutePath() ) - .build(); + startServer(); + + String baseUri = server.baseUri().toString(); + HTTP.Response response = POST( baseUri + "db/data/transaction", quotedJson( "{'statements':[]}" ) ); + + assertThat( response.location(), startsWith( baseUri ) ); + assertThat( response.get( "commit" ).asText(), startsWith( baseUri ) ); + } + + @Test + public void shouldExposeBaseUriWhenHttpEnabledAndHttpsDisabled() throws Exception + { + startServer( true, false ); + + URI uri = server.baseUri(); + + assertEquals( "http", uri.getScheme() ); + HostnamePort expectedHostPort = addressForConnector( "http" ); + assertEquals( expectedHostPort.getHost(), uri.getHost() ); + assertEquals( expectedHostPort.getPort(), uri.getPort() ); + } + + @Test + public void shouldExposeBaseUriWhenHttpDisabledAndHttpsEnabled() throws Exception + { + startServer( false, true ); + + URI uri = server.baseUri(); + + assertEquals( "https", uri.getScheme() ); + HostnamePort expectedHostPort = addressForConnector( "https" ); + assertEquals( expectedHostPort.getHost(), uri.getHost() ); + assertEquals( expectedHostPort.getPort(), uri.getPort() ); + } + + private void startServer() throws Exception + { + startServer( true, true ); + } + + private void startServer( boolean httpEnabled, boolean httpsEnabled ) throws Exception + { + CommunityServerBuilder serverBuilder = serverOnRandomPorts().usingDataDir( folder.directory( name.getMethodName() ).getAbsolutePath() ); + if ( !httpEnabled ) + { + serverBuilder.withHttpDisabled(); + } + if ( httpsEnabled ) + { + serverBuilder.withHttpsEnabled(); + } + + server = serverBuilder.build(); + server.start(); // Because we are generating a non-CA-signed certificate, we need to turn off verification in the client. // This is ironic, since there is no proper verification on the CA side in the first place, but I digress. @@ -70,29 +144,23 @@ public void startServer() throws NoSuchAlgorithmException, KeyManagementExceptio HttpsURLConnection.setDefaultSSLSocketFactory( sc.getSocketFactory() ); } - @Test - public void serverShouldSupportSsl() + private String httpsUri() throws Exception { - // When - server.start(); + HostnamePort hostPort = addressForConnector( "https" ); + assertNotNull( hostPort ); - // Then - assertThat( server.httpsIsEnabled(), is( true ) ); - assertThat( GET(server.baseUri().toString()).status(), is( 200 ) ); + return new URIBuilder() + .setScheme( "https" ) + .setHost( hostPort.getHost() ) + .setPort( hostPort.getPort() ) + .build() + .toString(); } - @Test - public void txEndpointShouldReplyWithHttpsWhenItReturnsURLs() throws Exception + private HostnamePort addressForConnector( String name ) { - // Given - server.start(); - - // When - String baseUri = server.baseUri().toString(); - HTTP.Response response = POST( baseUri + "db/data/transaction", quotedJson( "{'statements':[]}" ) ); - - // Then - assertThat( response.location(), startsWith( baseUri ) ); - assertThat( response.get( "commit" ).asText(), startsWith( baseUri ) ); + DependencyResolver resolver = server.database.getGraph().getDependencyResolver(); + ConnectorPortRegister portRegister = resolver.resolveDependency( ConnectorPortRegister.class ); + return portRegister.getLocalAddress( name ); } } 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 ea2400dfeea8a..65ee0d12efa37 100644 --- a/community/server/src/test/java/org/neo4j/server/ServerBootstrapperTest.java +++ b/community/server/src/test/java/org/neo4j/server/ServerBootstrapperTest.java @@ -24,16 +24,14 @@ import java.io.File; import java.nio.file.Files; -import java.util.Collection; -import java.util.Collections; import java.util.Optional; -import javax.annotation.Nonnull; import org.neo4j.graphdb.facade.GraphDatabaseDependencies; import org.neo4j.helpers.collection.MapUtil; import org.neo4j.kernel.configuration.Config; -import org.neo4j.kernel.configuration.ConfigurationValidator; import org.neo4j.logging.LogProvider; +import org.neo4j.server.database.CommunityGraphFactory; +import org.neo4j.server.database.GraphFactory; import org.neo4j.test.rule.SuppressOutput; import org.neo4j.test.rule.TestDirectory; @@ -58,17 +56,16 @@ public void shouldNotThrowNullPointerExceptionIfConfigurationValidationFails() t ServerBootstrapper serverBootstrapper = new ServerBootstrapper() { @Override - protected NeoServer createNeoServer( Config config, GraphDatabaseDependencies dependencies, - LogProvider userLogProvider ) + protected GraphFactory createGraphFactory( Config config ) { - return mock( NeoServer.class ); + return new CommunityGraphFactory(); } - @Nonnull @Override - protected Collection configurationValidators() + protected NeoServer createNeoServer( GraphFactory graphFactory, Config config, GraphDatabaseDependencies dependencies, + LogProvider userLogProvider ) { - return Collections.emptyList(); + return mock( NeoServer.class ); } }; diff --git a/community/server/src/test/java/org/neo4j/server/database/InMemoryGraphFactory.java b/community/server/src/test/java/org/neo4j/server/database/InMemoryGraphFactory.java new file mode 100644 index 0000000000000..bb441c3efe1a7 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/database/InMemoryGraphFactory.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.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 . + */ +package org.neo4j.server.database; + +import java.io.File; + +import org.neo4j.graphdb.facade.GraphDatabaseDependencies; +import org.neo4j.graphdb.facade.GraphDatabaseFacadeFactory; +import org.neo4j.graphdb.factory.GraphDatabaseSettings; +import org.neo4j.kernel.configuration.BoltConnector; +import org.neo4j.kernel.configuration.Config; +import org.neo4j.kernel.impl.factory.GraphDatabaseFacade; +import org.neo4j.test.ImpermanentGraphDatabase; + +import static org.neo4j.helpers.collection.MapUtil.stringMap; + +public class InMemoryGraphFactory implements GraphFactory +{ + @Override + public GraphDatabaseFacade newGraphDatabase( Config config, GraphDatabaseFacadeFactory.Dependencies dependencies ) + { + File storeDir = config.get( GraphDatabaseSettings.database_path ); + config.augment( stringMap( GraphDatabaseSettings.ephemeral.name(), "true", + new BoltConnector( "bolt" ).listen_address.name(), "localhost:0" ) ); + return new ImpermanentGraphDatabase( storeDir, config, GraphDatabaseDependencies.newDependencies( dependencies ) ); + } +} diff --git a/community/server/src/test/java/org/neo4j/server/database/LifecycleManagingDatabaseTest.java b/community/server/src/test/java/org/neo4j/server/database/LifecycleManagingDatabaseTest.java index 7e6170a99c1f9..83f350f8d5354 100644 --- a/community/server/src/test/java/org/neo4j/server/database/LifecycleManagingDatabaseTest.java +++ b/community/server/src/test/java/org/neo4j/server/database/LifecycleManagingDatabaseTest.java @@ -42,7 +42,7 @@ public void mustIgnoreExceptionsFromPreLoadingCypherQuery() Config config = Config.defaults(); GraphDatabaseFacadeFactory.Dependencies deps = GraphDatabaseDependencies.newDependencies().userLogProvider( NullLogProvider.getInstance() ); - LifecycleManagingDatabase.GraphFactory factory = ( conf, dependencies ) -> mockDb; + GraphFactory factory = new SimpleGraphFactory( mockDb ); LifecycleManagingDatabase db = new LifecycleManagingDatabase( config, factory, deps ) { @Override diff --git a/community/server/src/test/java/org/neo4j/server/database/SimpleGraphFactory.java b/community/server/src/test/java/org/neo4j/server/database/SimpleGraphFactory.java new file mode 100644 index 0000000000000..7396f21adab85 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/database/SimpleGraphFactory.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.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 . + */ +package org.neo4j.server.database; + +import org.neo4j.graphdb.facade.GraphDatabaseFacadeFactory; +import org.neo4j.kernel.configuration.Config; +import org.neo4j.kernel.impl.factory.GraphDatabaseFacade; + +public class SimpleGraphFactory implements GraphFactory +{ + private final GraphDatabaseFacade db; + + public SimpleGraphFactory( GraphDatabaseFacade db ) + { + this.db = db; + } + + @Override + public GraphDatabaseFacade newGraphDatabase( Config config, GraphDatabaseFacadeFactory.Dependencies dependencies ) + { + return db; + } +} diff --git a/community/server/src/test/java/org/neo4j/server/database/TestLifecycleManagedDatabase.java b/community/server/src/test/java/org/neo4j/server/database/TestLifecycleManagedDatabase.java index c007aca94195f..49190888574ac 100644 --- a/community/server/src/test/java/org/neo4j/server/database/TestLifecycleManagedDatabase.java +++ b/community/server/src/test/java/org/neo4j/server/database/TestLifecycleManagedDatabase.java @@ -57,7 +57,7 @@ public class TestLifecycleManagedDatabase private File dataDirectory; private Database theDatabase; private boolean deletionFailureOk; - private LifecycleManagingDatabase.GraphFactory dbFactory; + private GraphFactory dbFactory; private Config dbConfig; @Before @@ -65,7 +65,7 @@ public void setup() throws Exception { dataDirectory = createTempDir(); - dbFactory = createGraphFactory(); + dbFactory = new SimpleGraphFactory( (GraphDatabaseFacade) dbRule.getGraphDatabaseAPI() ); dbConfig = Config.defaults( GraphDatabaseSettings.data_directory, dataDirectory.getAbsolutePath() ); theDatabase = newDatabase(); } @@ -144,9 +144,4 @@ public void shouldBeAbleToGetLocation() throws Throwable assertThat( theDatabase.getLocation().getAbsolutePath(), is( dbConfig.get( GraphDatabaseSettings.database_path ).getAbsolutePath() ) ); } - - private LifecycleManagingDatabase.GraphFactory createGraphFactory() - { - return ( config, dependencies ) -> (GraphDatabaseFacade) dbRule.getGraphDatabaseAPI(); - } } diff --git a/community/server/src/test/java/org/neo4j/server/helpers/CommunityServerBuilder.java b/community/server/src/test/java/org/neo4j/server/helpers/CommunityServerBuilder.java index 0d2e014bbba7e..24c4505ea3190 100644 --- a/community/server/src/test/java/org/neo4j/server/helpers/CommunityServerBuilder.java +++ b/community/server/src/test/java/org/neo4j/server/helpers/CommunityServerBuilder.java @@ -31,7 +31,6 @@ import org.neo4j.graphdb.facade.GraphDatabaseFacadeFactory; import org.neo4j.graphdb.factory.GraphDatabaseSettings; import org.neo4j.helpers.ListenSocketAddress; -import org.neo4j.kernel.configuration.BoltConnector; import org.neo4j.kernel.configuration.Config; import org.neo4j.kernel.configuration.HttpConnector; import org.neo4j.kernel.configuration.HttpConnector.Encryption; @@ -43,15 +42,14 @@ import org.neo4j.server.CommunityNeoServer; import org.neo4j.server.ServerTestUtils; import org.neo4j.server.configuration.ServerSettings; +import org.neo4j.server.database.CommunityGraphFactory; import org.neo4j.server.database.Database; -import org.neo4j.server.database.LifecycleManagingDatabase; +import org.neo4j.server.database.InMemoryGraphFactory; import org.neo4j.server.preflight.PreFlightTasks; import org.neo4j.server.rest.web.DatabaseActions; -import org.neo4j.test.ImpermanentGraphDatabase; import static org.neo4j.helpers.collection.MapUtil.stringMap; import static org.neo4j.server.ServerTestUtils.asOneLine; -import static org.neo4j.server.database.LifecycleManagingDatabase.lifecycleManagingDatabase; public class CommunityServerBuilder { @@ -73,19 +71,11 @@ public class CommunityServerBuilder System.setProperty( "sun.net.http.allowRestrictedHeaders", "true" ); } - private static LifecycleManagingDatabase.GraphFactory IN_MEMORY_DB = ( config, dependencies ) -> - { - File storeDir = config.get( GraphDatabaseSettings.database_path ); - config.augment( stringMap( GraphDatabaseSettings.ephemeral.name(), "true", - new BoltConnector( "bolt" ).listen_address.name(), "localhost:0" ) ); - return new ImpermanentGraphDatabase( storeDir, config, - GraphDatabaseDependencies.newDependencies( dependencies ) ); - }; - private String[] autoIndexedNodeKeys; private final String[] autoIndexedRelationshipKeys = null; private String[] securityRuleClassNames; private boolean persistent; + private boolean httpEnabled = true; private boolean httpsEnabled; public static CommunityServerBuilder server( LogProvider logProvider ) @@ -181,7 +171,7 @@ public Map createConfiguration( File temporaryFolder ) HttpConnector httpsConnector = new HttpConnector( "https", Encryption.TLS ); properties.put( httpConnector.type.name(), "HTTP" ); - properties.put( httpConnector.enabled.name(), "true" ); + properties.put( httpConnector.enabled.name(), String.valueOf( httpEnabled ) ); properties.put( httpConnector.address.name(), address.toString() ); properties.put( httpConnector.encryption.name(), "NONE" ); @@ -319,6 +309,12 @@ public CommunityServerBuilder withHttpsEnabled() return this; } + public CommunityServerBuilder withHttpDisabled() + { + httpEnabled = false; + return this; + } + public CommunityServerBuilder withProperty( String key, String value ) { arbitraryProperties.put( key, value ); @@ -352,11 +348,9 @@ private class TestCommunityNeoServer extends CommunityNeoServer { private final File configFile; - private TestCommunityNeoServer( Config config, File configFile, GraphDatabaseFacadeFactory - .Dependencies dependencies, LogProvider logProvider ) + private TestCommunityNeoServer( Config config, File configFile, GraphDatabaseFacadeFactory.Dependencies dependencies, LogProvider logProvider ) { - super( config, lifecycleManagingDatabase( persistent ? COMMUNITY_FACTORY : IN_MEMORY_DB ), dependencies, - logProvider ); + super( config, persistent ? new CommunityGraphFactory() : new InMemoryGraphFactory(), dependencies, logProvider ); this.configFile = configFile; } diff --git a/community/server/src/test/java/org/neo4j/server/web/Jetty9WebServerIT.java b/community/server/src/test/java/org/neo4j/server/web/Jetty9WebServerIT.java index c476b92cd0ef8..b5a3f005129be 100644 --- a/community/server/src/test/java/org/neo4j/server/web/Jetty9WebServerIT.java +++ b/community/server/src/test/java/org/neo4j/server/web/Jetty9WebServerIT.java @@ -48,7 +48,7 @@ public void shouldBeAbleToUsePortZero() throws Exception // Given webServer = new Jetty9WebServer( NullLogProvider.getInstance(), Config.defaults(), NetworkConnectionTracker.NO_OP ); - webServer.setAddress( new ListenSocketAddress( "localhost", 0 ) ); + webServer.setHttpAddress( new ListenSocketAddress( "localhost", 0 ) ); // When webServer.start(); @@ -61,7 +61,7 @@ public void shouldBeAbleToRestart() throws Throwable { // given webServer = new Jetty9WebServer( NullLogProvider.getInstance(), Config.defaults(), NetworkConnectionTracker.NO_OP ); - webServer.setAddress( new ListenSocketAddress( "127.0.0.1", 7878 ) ); + webServer.setHttpAddress( new ListenSocketAddress( "127.0.0.1", 7878 ) ); // when webServer.start(); diff --git a/community/server/src/test/java/org/neo4j/server/web/JettyThreadLimitIT.java b/community/server/src/test/java/org/neo4j/server/web/JettyThreadLimitIT.java index 452e044eeddcf..53c7eed55e6f5 100644 --- a/community/server/src/test/java/org/neo4j/server/web/JettyThreadLimitIT.java +++ b/community/server/src/test/java/org/neo4j/server/web/JettyThreadLimitIT.java @@ -50,7 +50,7 @@ public void shouldHaveConfigurableJettyThreadPoolSize() throws Exception int selectorThreads = 1; // ... and 1 thread will become a selector... int jobThreads = configuredMaxThreads - acceptorThreads - selectorThreads; // ... and the rest are job threads server.setMaxThreads( numCores ); - server.setAddress( new ListenSocketAddress( "localhost", PortAuthority.allocatePort() ) ); + server.setHttpAddress( new ListenSocketAddress( "localhost", PortAuthority.allocatePort() ) ); try { server.start(); diff --git a/community/shell/src/main/java/org/neo4j/shell/kernel/GraphDatabaseShellServer.java b/community/shell/src/main/java/org/neo4j/shell/kernel/GraphDatabaseShellServer.java index 1a348fd514194..0ef7f4fb0ec43 100644 --- a/community/shell/src/main/java/org/neo4j/shell/kernel/GraphDatabaseShellServer.java +++ b/community/shell/src/main/java/org/neo4j/shell/kernel/GraphDatabaseShellServer.java @@ -31,6 +31,7 @@ import org.neo4j.internal.kernel.api.TokenRead; import org.neo4j.kernel.api.KernelTransaction; import org.neo4j.kernel.api.exceptions.Status; +import org.neo4j.kernel.configuration.BoltConnector; import org.neo4j.kernel.impl.core.ThreadToStatementContextBridge; import org.neo4j.kernel.internal.GraphDatabaseAPI; import org.neo4j.shell.Output; @@ -217,14 +218,14 @@ protected String getPrompt( Session session ) throws ShellException private static GraphDatabaseAPI instantiateGraphDb( GraphDatabaseFactory factory, File path, boolean readOnly, String configFileOrNull ) { - GraphDatabaseBuilder builder = factory. - newEmbeddedDatabaseBuilder( path ). - setConfig( GraphDatabaseSettings.disconnected, Boolean.toString( true ) ). - setConfig( GraphDatabaseSettings.read_only, Boolean.toString( readOnly ) ); + GraphDatabaseBuilder builder = factory.newEmbeddedDatabaseBuilder( path ) + .setConfig( GraphDatabaseSettings.read_only, Boolean.toString( readOnly ) ); + if ( configFileOrNull != null ) { builder.loadPropertiesFromFile( configFileOrNull ); } + builder.setConfig( new BoltConnector( "bolt" ).enabled, "false" ); return (GraphDatabaseAPI) builder.newGraphDatabase(); } diff --git a/enterprise/metrics/src/main/java/org/neo4j/metrics/source/Neo4jMetricsBuilder.java b/enterprise/metrics/src/main/java/org/neo4j/metrics/source/Neo4jMetricsBuilder.java index be5e13a315b43..8adf2a65c2fb3 100644 --- a/enterprise/metrics/src/main/java/org/neo4j/metrics/source/Neo4jMetricsBuilder.java +++ b/enterprise/metrics/src/main/java/org/neo4j/metrics/source/Neo4jMetricsBuilder.java @@ -207,7 +207,8 @@ else if ( mode == OperationalMode.read_replica ) } } - if ( config.get( MetricsSettings.neoServerEnabled ) ) + boolean httpAndHttpsDisabled = config.enabledHttpConnectors().isEmpty(); + if ( !httpAndHttpsDisabled && config.get( MetricsSettings.neoServerEnabled ) ) { life.add( new ServerMetrics( registry, logService, kernelContext.dependencySatisfier() ) ); result = true; diff --git a/enterprise/metrics/src/test/java/org/neo4j/metrics/source/Neo4jMetricsBuilderTest.java b/enterprise/metrics/src/test/java/org/neo4j/metrics/source/Neo4jMetricsBuilderTest.java new file mode 100644 index 0000000000000..0055286a49b61 --- /dev/null +++ b/enterprise/metrics/src/test/java/org/neo4j/metrics/source/Neo4jMetricsBuilderTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j Enterprise Edition. The included source + * code can be redistributed and/or modified under the terms of the + * GNU AFFERO GENERAL PUBLIC LICENSE Version 3 + * (http://www.fsf.org/licensing/licenses/agpl-3.0.html) with the + * Commons Clause, as found in the associated LICENSE.txt file. + * + * 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. + * + * Neo4j object code can be licensed independently from the source + * under separate terms from the AGPL. Inquiries can be directed to: + * licensing@neo4j.com + * + * More information is also available at: + * https://neo4j.com/licensing/ + */ +package org.neo4j.metrics.source; + +import com.codahale.metrics.MetricRegistry; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.neo4j.kernel.configuration.Config; +import org.neo4j.kernel.configuration.HttpConnector; +import org.neo4j.kernel.impl.spi.KernelContext; +import org.neo4j.kernel.impl.spi.SimpleKernelContext; +import org.neo4j.kernel.impl.util.DependencySatisfier; +import org.neo4j.kernel.lifecycle.LifeSupport; +import org.neo4j.logging.internal.NullLogService; +import org.neo4j.metrics.MetricsSettings; +import org.neo4j.metrics.output.EventReporter; +import org.neo4j.metrics.source.server.ServerMetrics; +import org.neo4j.test.extension.Inject; +import org.neo4j.test.extension.TestDirectoryExtension; +import org.neo4j.test.rule.TestDirectory; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.not; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.neo4j.kernel.impl.factory.DatabaseInfo.COMMUNITY; + +@ExtendWith( TestDirectoryExtension.class ) +class Neo4jMetricsBuilderTest +{ + @Inject + private TestDirectory testDir; + + @Test + void shouldAddServerMetricsWhenServerEnabled() + { + testBuildingWithServerMetrics( true ); + } + + @Test + void shouldNotAddServerMetricsWhenServerDisabled() + { + testBuildingWithServerMetrics( false ); + } + + private void testBuildingWithServerMetrics( boolean serverMetricsEnabled ) + { + Config config = configWithServerMetrics( serverMetricsEnabled ); + KernelContext kernelContext = new SimpleKernelContext( testDir.databaseDir(), COMMUNITY, mock( DependencySatisfier.class ) ); + LifeSupport life = new LifeSupport(); + + Neo4jMetricsBuilder builder = new Neo4jMetricsBuilder( new MetricRegistry(), mock( EventReporter.class ), config, NullLogService.getInstance(), + kernelContext, mock( Neo4jMetricsBuilder.Dependencies.class ), life ); + + assertTrue( builder.build() ); + + if ( serverMetricsEnabled ) + { + assertThat( life.getLifecycleInstances(), hasItem( instanceOf( ServerMetrics.class ) ) ); + } + else + { + assertThat( life.getLifecycleInstances(), not( hasItem( instanceOf( ServerMetrics.class ) ) ) ); + } + } + + private static Config configWithServerMetrics( boolean enabled ) + { + return Config.builder() + .withSetting( new HttpConnector( "http" ).enabled, Boolean.toString( enabled ) ) + .withSetting( MetricsSettings.neoServerEnabled, "true" ) + .build(); + } +} diff --git a/enterprise/server-enterprise/src/main/java/org/neo4j/server/database/EnterpriseGraphFactory.java b/enterprise/server-enterprise/src/main/java/org/neo4j/server/database/EnterpriseGraphFactory.java new file mode 100644 index 0000000000000..3ab752a421682 --- /dev/null +++ b/enterprise/server-enterprise/src/main/java/org/neo4j/server/database/EnterpriseGraphFactory.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j Enterprise Edition. The included source + * code can be redistributed and/or modified under the terms of the + * GNU AFFERO GENERAL PUBLIC LICENSE Version 3 + * (http://www.fsf.org/licensing/licenses/agpl-3.0.html) with the + * Commons Clause, as found in the associated LICENSE.txt file. + * + * 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. + * + * Neo4j object code can be licensed independently from the source + * under separate terms from the AGPL. Inquiries can be directed to: + * licensing@neo4j.com + * + * More information is also available at: + * https://neo4j.com/licensing/ + */ +package org.neo4j.server.database; + +import java.io.File; + +import org.neo4j.causalclustering.core.CoreGraphDatabase; +import org.neo4j.causalclustering.discovery.DiscoveryServiceFactory; +import org.neo4j.causalclustering.discovery.EnterpriseDiscoveryServiceFactorySelector; +import org.neo4j.causalclustering.readreplica.ReadReplicaGraphDatabase; +import org.neo4j.graphdb.factory.GraphDatabaseSettings; +import org.neo4j.kernel.configuration.Config; +import org.neo4j.kernel.enterprise.EnterpriseGraphDatabase; +import org.neo4j.kernel.ha.HighlyAvailableGraphDatabase; +import org.neo4j.kernel.impl.enterprise.configuration.EnterpriseEditionSettings; +import org.neo4j.kernel.impl.factory.GraphDatabaseFacade; + +import static org.neo4j.graphdb.facade.GraphDatabaseFacadeFactory.Dependencies; + +public class EnterpriseGraphFactory implements GraphFactory +{ + @Override + public GraphDatabaseFacade newGraphDatabase( Config config, Dependencies dependencies ) + { + EnterpriseEditionSettings.Mode mode = config.get( EnterpriseEditionSettings.mode ); + File storeDir = config.get( GraphDatabaseSettings.databases_root_path ); + DiscoveryServiceFactory discoveryServiceFactory = new EnterpriseDiscoveryServiceFactorySelector().select( config ); + + switch ( mode ) + { + case HA: + return new HighlyAvailableGraphDatabase( storeDir, config, dependencies ); + case ARBITER: + // Should never reach here because this mode is handled separately by the scripts. + throw new IllegalArgumentException( "The server cannot be started in ARBITER mode." ); + case CORE: + return new CoreGraphDatabase( storeDir, config, dependencies, discoveryServiceFactory ); + case READ_REPLICA: + return new ReadReplicaGraphDatabase( storeDir, config, dependencies, discoveryServiceFactory ); + default: + return new EnterpriseGraphDatabase( storeDir, config, dependencies ); + } + } +} diff --git a/enterprise/server-enterprise/src/main/java/org/neo4j/server/enterprise/OpenEnterpriseNeoServer.java b/enterprise/server-enterprise/src/main/java/org/neo4j/server/enterprise/OpenEnterpriseNeoServer.java index bbc49713d95db..2b756d2f26106 100644 --- a/enterprise/server-enterprise/src/main/java/org/neo4j/server/enterprise/OpenEnterpriseNeoServer.java +++ b/enterprise/server-enterprise/src/main/java/org/neo4j/server/enterprise/OpenEnterpriseNeoServer.java @@ -24,7 +24,6 @@ import org.eclipse.jetty.util.thread.ThreadPool; -import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -32,28 +31,19 @@ import java.util.regex.Pattern; import org.neo4j.causalclustering.core.CausalClusteringSettings; -import org.neo4j.causalclustering.core.CoreGraphDatabase; -import org.neo4j.causalclustering.discovery.DiscoveryServiceFactory; -import org.neo4j.causalclustering.discovery.EnterpriseDiscoveryServiceFactorySelector; -import org.neo4j.causalclustering.readreplica.ReadReplicaGraphDatabase; -import org.neo4j.graphdb.facade.GraphDatabaseFacadeFactory; import org.neo4j.graphdb.facade.GraphDatabaseFacadeFactory.Dependencies; -import org.neo4j.graphdb.factory.GraphDatabaseSettings; import org.neo4j.helpers.collection.Iterables; import org.neo4j.kernel.configuration.Config; import org.neo4j.kernel.configuration.ConnectorPortRegister; -import org.neo4j.kernel.enterprise.EnterpriseGraphDatabase; import org.neo4j.kernel.ha.HaSettings; import org.neo4j.kernel.ha.HighlyAvailableGraphDatabase; -import org.neo4j.kernel.impl.enterprise.configuration.EnterpriseEditionSettings; -import org.neo4j.kernel.impl.enterprise.configuration.EnterpriseEditionSettings.Mode; import org.neo4j.kernel.impl.util.UnsatisfiedDependencyException; import org.neo4j.logging.LogProvider; import org.neo4j.metrics.source.server.ServerThreadView; import org.neo4j.metrics.source.server.ServerThreadViewSetter; import org.neo4j.server.CommunityNeoServer; -import org.neo4j.server.database.Database; -import org.neo4j.server.database.LifecycleManagingDatabase.GraphFactory; +import org.neo4j.server.database.EnterpriseGraphFactory; +import org.neo4j.server.database.GraphFactory; import org.neo4j.server.enterprise.modules.EnterpriseAuthorizationModule; import org.neo4j.server.enterprise.modules.JMXManagementModule; import org.neo4j.server.modules.AuthorizationModule; @@ -68,72 +58,17 @@ import org.neo4j.server.web.WebServer; import static org.neo4j.server.configuration.ServerSettings.jmx_module_enabled; -import static org.neo4j.server.database.LifecycleManagingDatabase.lifecycleManagingDatabase; public class OpenEnterpriseNeoServer extends CommunityNeoServer { - - private static final GraphFactory HA_FACTORY = ( config, dependencies ) -> - { - File storeDir = config.get( GraphDatabaseSettings.databases_root_path ); - return new HighlyAvailableGraphDatabase( storeDir, config, dependencies ); - }; - - private static final GraphFactory ENTERPRISE_FACTORY = ( config, dependencies ) -> - { - File storeDir = config.get( GraphDatabaseSettings.databases_root_path ); - return new EnterpriseGraphDatabase( storeDir, config, dependencies ); - }; - - private static GraphFactory coreFactory( DiscoveryServiceFactory discoveryServiceFactory ) - { - return ( config, dependencies ) -> - { - File storeDir = config.get( GraphDatabaseSettings.databases_root_path ); - return new CoreGraphDatabase( storeDir, config, dependencies, discoveryServiceFactory ); - }; - } - - private static GraphFactory readReplicaFactory( DiscoveryServiceFactory discoveryServiceFactory ) - { - return ( config, dependencies ) -> - { - File storeDir = config.get( GraphDatabaseSettings.databases_root_path ); - return new ReadReplicaGraphDatabase( storeDir, config, dependencies, discoveryServiceFactory ); - }; - } - public OpenEnterpriseNeoServer( Config config, Dependencies dependencies, LogProvider logProvider ) { - super( config, createDbFactory( config ), dependencies, logProvider ); - } - - public OpenEnterpriseNeoServer( Config config, Database.Factory dbFactory, GraphDatabaseFacadeFactory.Dependencies - dependencies, LogProvider logProvider ) - { - super( config, dbFactory, dependencies, logProvider ); + super( config, new EnterpriseGraphFactory(), dependencies, logProvider ); } - protected static Database.Factory createDbFactory( Config config ) + public OpenEnterpriseNeoServer( Config config, GraphFactory graphFactory, Dependencies dependencies, LogProvider logProvider ) { - final Mode mode = config.get( EnterpriseEditionSettings.mode ); - - final DiscoveryServiceFactory discoveryServiceFactory = new EnterpriseDiscoveryServiceFactorySelector().select( config ); - - switch ( mode ) - { - case HA: - return lifecycleManagingDatabase( HA_FACTORY ); - case ARBITER: - // Should never reach here because this mode is handled separately by the scripts. - throw new IllegalArgumentException( "The server cannot be started in ARBITER mode." ); - case CORE: - return lifecycleManagingDatabase( coreFactory( discoveryServiceFactory ) ); - case READ_REPLICA: - return lifecycleManagingDatabase( readReplicaFactory( discoveryServiceFactory ) ); - default: - return lifecycleManagingDatabase( ENTERPRISE_FACTORY ); - } + super( config, graphFactory, dependencies, logProvider ); } @Override diff --git a/integrationtests/src/test/java/org/neo4j/TransactionGuardIT.java b/integrationtests/src/test/java/org/neo4j/TransactionGuardIT.java index d95c2ab99dc86..c134f70528edb 100644 --- a/integrationtests/src/test/java/org/neo4j/TransactionGuardIT.java +++ b/integrationtests/src/test/java/org/neo4j/TransactionGuardIT.java @@ -82,7 +82,7 @@ import org.neo4j.logging.NullLogProvider; import org.neo4j.ports.allocation.PortAuthority; import org.neo4j.server.CommunityNeoServer; -import org.neo4j.server.database.LifecycleManagingDatabase; +import org.neo4j.server.database.SimpleGraphFactory; import org.neo4j.server.enterprise.OpenEnterpriseNeoServer; import org.neo4j.server.enterprise.helpers.EnterpriseServerBuilder; import org.neo4j.server.web.HttpHeaderUtils; @@ -665,7 +665,6 @@ void tickAndCheck() private class GuardingServerBuilder extends EnterpriseServerBuilder { private GraphDatabaseFacade graphDatabaseFacade; - final LifecycleManagingDatabase.GraphFactory PRECREATED_FACADE_FACTORY = ( config, dependencies ) -> graphDatabaseFacade; GuardingServerBuilder( GraphDatabaseFacade graphDatabaseAPI ) { @@ -684,8 +683,7 @@ private class GuardTestServer extends OpenEnterpriseNeoServer { GuardTestServer( Config config, GraphDatabaseFacadeFactory.Dependencies dependencies, LogProvider logProvider ) { - super( config, LifecycleManagingDatabase.lifecycleManagingDatabase( PRECREATED_FACADE_FACTORY ), - dependencies, logProvider ); + super( config, new SimpleGraphFactory( graphDatabaseFacade ), dependencies, logProvider ); } } }