diff --git a/pom.xml b/pom.xml
index b774edbd8e1..4e94a04e351 100644
--- a/pom.xml
+++ b/pom.xml
@@ -108,6 +108,7 @@
1.7.12
2.8.5
+ 3.2.4
2.0.2
1.11.199
2.8.0
@@ -1040,6 +1041,12 @@
4.12
test
+
+ org.mockito
+ mockito-core
+ ${mockito.version}
+ test
+
org.powermock
powermock-core
diff --git a/server/pom.xml b/server/pom.xml
index ddce092c1cd..bfc7a95dd67 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -318,6 +318,11 @@
junit
test
+
+ org.mockito
+ mockito-core
+ test
+
org.hamcrest
hamcrest-all
diff --git a/server/src/main/java/org/apache/druid/guice/StorageNodeModule.java b/server/src/main/java/org/apache/druid/guice/StorageNodeModule.java
index 73c9e5e29cf..f26810c381d 100644
--- a/server/src/main/java/org/apache/druid/guice/StorageNodeModule.java
+++ b/server/src/main/java/org/apache/druid/guice/StorageNodeModule.java
@@ -19,6 +19,7 @@
package org.apache.druid.guice;
+import com.google.common.annotations.VisibleForTesting;
import com.google.inject.Binder;
import com.google.inject.Module;
import com.google.inject.Provides;
@@ -43,7 +44,8 @@ import javax.annotation.Nullable;
public class StorageNodeModule implements Module
{
private static final EmittingLogger log = new EmittingLogger(StorageNodeModule.class);
- private static final String IS_SEGMENT_CACHE_CONFIGURED = "IS_SEGMENT_CACHE_CONFIGURED";
+ @VisibleForTesting
+ static final String IS_SEGMENT_CACHE_CONFIGURED = "IS_SEGMENT_CACHE_CONFIGURED";
@Override
public void configure(Binder binder)
@@ -87,7 +89,7 @@ public class StorageNodeModule implements Module
)
{
if (serverTypeConfig == null) {
- throw new ProvisionException("Must override the binding for ServerTypeConfig if you want a DruidServerMetadata.");
+ throw new ProvisionException("Must override the binding for ServerTypeConfig if you want a DataNodeService.");
}
if (!isSegmentCacheConfigured) {
log.info("Segment cache not configured on ServerType [%s]", serverTypeConfig.getServerType());
diff --git a/server/src/test/java/org/apache/druid/guice/StorageNodeModuleTest.java b/server/src/test/java/org/apache/druid/guice/StorageNodeModuleTest.java
new file mode 100644
index 00000000000..c844cdd1e80
--- /dev/null
+++ b/server/src/test/java/org/apache/druid/guice/StorageNodeModuleTest.java
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.guice;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.ProvisionException;
+import com.google.inject.Scopes;
+import com.google.inject.name.Names;
+import com.google.inject.util.Modules;
+import org.apache.druid.discovery.DataNodeService;
+import org.apache.druid.guice.annotations.Self;
+import org.apache.druid.query.DruidProcessingConfig;
+import org.apache.druid.segment.loading.SegmentLoaderConfig;
+import org.apache.druid.segment.loading.StorageLocationConfig;
+import org.apache.druid.server.DruidNode;
+import org.apache.druid.server.coordination.DruidServerMetadata;
+import org.apache.druid.server.coordination.ServerType;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import javax.validation.Validation;
+import javax.validation.Validator;
+import java.util.Collections;
+
+@RunWith(MockitoJUnitRunner.class)
+public class StorageNodeModuleTest
+{
+ private static final boolean INJECT_SERVER_TYPE_CONFIG = true;
+
+ @Rule
+ public ExpectedException exceptionRule = ExpectedException.none();
+
+ @Mock(answer = Answers.RETURNS_MOCKS)
+ private ObjectMapper mapper;
+ @Mock(answer = Answers.RETURNS_MOCKS)
+ private DruidNode self;
+ @Mock(answer = Answers.RETURNS_MOCKS)
+ private ServerTypeConfig serverTypeConfig;
+ @Mock
+ private DruidProcessingConfig druidProcessingConfig;
+ @Mock
+ private SegmentLoaderConfig segmentLoaderConfig;
+ @Mock
+ private StorageLocationConfig storageLocation;
+
+ private Injector injector;
+ private StorageNodeModule target;
+
+ @Before
+ public void setUp()
+ {
+ Mockito.when(segmentLoaderConfig.getLocations()).thenReturn(Collections.singletonList(storageLocation));
+
+ target = new StorageNodeModule();
+ injector = makeInjector(INJECT_SERVER_TYPE_CONFIG);
+ }
+
+ @Test
+ public void testIsSegmentCacheConfiguredIsInjected()
+ {
+ Boolean isSegmentCacheConfigured = injector.getInstance(
+ Key.get(Boolean.class, Names.named(StorageNodeModule.IS_SEGMENT_CACHE_CONFIGURED))
+ );
+ Assert.assertNotNull(isSegmentCacheConfigured);
+ Assert.assertTrue(isSegmentCacheConfigured);
+ }
+
+ @Test
+ public void testIsSegmentCacheConfiguredWithNoLocationsConfiguredIsInjected()
+ {
+ mockSegmentCacheNotConfigured();
+ Boolean isSegmentCacheConfigured = injector.getInstance(
+ Key.get(Boolean.class, Names.named(StorageNodeModule.IS_SEGMENT_CACHE_CONFIGURED))
+ );
+ Assert.assertNotNull(isSegmentCacheConfigured);
+ Assert.assertFalse(isSegmentCacheConfigured);
+ }
+
+ @Test
+ public void getDataNodeServiceWithNoServerTypeConfigShouldThrowProvisionException()
+ {
+ exceptionRule.expect(ProvisionException.class);
+ exceptionRule.expectMessage("Must override the binding for ServerTypeConfig if you want a DataNodeService.");
+ injector = makeInjector(!INJECT_SERVER_TYPE_CONFIG);
+ injector.getInstance(DataNodeService.class);
+ }
+
+ @Test
+ public void getDataNodeServiceWithNoSegmentCacheConfiguredThrowProvisionException()
+ {
+ exceptionRule.expect(ProvisionException.class);
+ exceptionRule.expectMessage("Segment cache locations must be set on historicals.");
+ Mockito.doReturn(ServerType.HISTORICAL).when(serverTypeConfig).getServerType();
+ mockSegmentCacheNotConfigured();
+ injector.getInstance(DataNodeService.class);
+ }
+
+ @Test
+ public void getDataNodeServiceIsInjectedAsSingleton()
+ {
+ DataNodeService dataNodeService = injector.getInstance(DataNodeService.class);
+ Assert.assertNotNull(dataNodeService);
+ DataNodeService other = injector.getInstance(DataNodeService.class);
+ Assert.assertSame(dataNodeService, other);
+ }
+
+ @Test
+ public void getDataNodeServiceIsInjectedAndDiscoverable()
+ {
+ DataNodeService dataNodeService = injector.getInstance(DataNodeService.class);
+ Assert.assertNotNull(dataNodeService);
+ Assert.assertTrue(dataNodeService.isDiscoverable());
+ }
+
+ @Test
+ public void getDataNodeServiceWithSegmentCacheNotConfiguredIsInjectedAndDiscoverable()
+ {
+ mockSegmentCacheNotConfigured();
+ DataNodeService dataNodeService = injector.getInstance(DataNodeService.class);
+ Assert.assertNotNull(dataNodeService);
+ Assert.assertFalse(dataNodeService.isDiscoverable());
+ }
+
+ @Test
+ public void testDruidServerMetadataIsInjectedAsSingleton()
+ {
+ DruidServerMetadata druidServerMetadata = injector.getInstance(DruidServerMetadata.class);
+ Assert.assertNotNull(druidServerMetadata);
+ DruidServerMetadata other = injector.getInstance(DruidServerMetadata.class);
+ Assert.assertSame(druidServerMetadata, other);
+ }
+
+ @Test
+ public void testDruidServerMetadataWithNoServerTypeConfigShouldThrowProvisionException()
+ {
+ exceptionRule.expect(ProvisionException.class);
+ exceptionRule.expectMessage("Must override the binding for ServerTypeConfig if you want a DruidServerMetadata.");
+ injector = makeInjector(!INJECT_SERVER_TYPE_CONFIG);
+ injector.getInstance(DruidServerMetadata.class);
+ }
+
+ private Injector makeInjector(boolean withServerTypeConfig)
+ {
+ return Guice.createInjector(
+ Modules.override(
+ (binder) -> {
+ binder.bind(DruidNode.class).annotatedWith(Self.class).toInstance(self);
+ binder.bind(Validator.class).toInstance(Validation.buildDefaultValidatorFactory().getValidator());
+ binder.bind(DruidProcessingConfig.class).toInstance(druidProcessingConfig);
+ binder.bindScope(LazySingleton.class, Scopes.SINGLETON);
+ },
+ target
+ ).with(
+ (binder) -> {
+ binder.bind(SegmentLoaderConfig.class).toInstance(segmentLoaderConfig);
+ if (withServerTypeConfig) {
+ binder.bind(ServerTypeConfig.class).toInstance(serverTypeConfig);
+ }
+ }
+ )
+ );
+ }
+
+ private void mockSegmentCacheNotConfigured()
+ {
+ Mockito.doReturn(Collections.emptyList()).when(segmentLoaderConfig).getLocations();
+ }
+}
diff --git a/server/src/test/java/org/apache/druid/server/coordination/SegmentLoadDropHandlerTest.java b/server/src/test/java/org/apache/druid/server/coordination/SegmentLoadDropHandlerTest.java
index 6d8ef0a8cc4..32368b700c6 100644
--- a/server/src/test/java/org/apache/druid/server/coordination/SegmentLoadDropHandlerTest.java
+++ b/server/src/test/java/org/apache/druid/server/coordination/SegmentLoadDropHandlerTest.java
@@ -25,7 +25,6 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.ListenableFuture;
import org.apache.druid.guice.ServerTypeConfig;
-import org.apache.druid.java.util.common.IAE;
import org.apache.druid.java.util.common.Intervals;
import org.apache.druid.java.util.common.concurrent.Execs;
import org.apache.druid.java.util.common.concurrent.ScheduledExecutorFactory;
@@ -276,40 +275,6 @@ public class SegmentLoadDropHandlerTest
segmentLoadDropHandler.stop();
}
- @Test
- public void testSegmentLoading1BrokerWithNoLocations() throws Exception
- {
- SegmentLoadDropHandler segmentLoadDropHandlerBrokerWithNoLocations = new SegmentLoadDropHandler(
- jsonMapper,
- segmentLoaderConfigNoLocations,
- announcer,
- EasyMock.createNiceMock(DataSegmentServerAnnouncer.class),
- segmentManager,
- scheduledExecutorFactory.create(5, "SegmentLoadDropHandlerTest-brokerNoLocations-[%d]"),
- new ServerTypeConfig(ServerType.BROKER)
- );
-
- segmentLoadDropHandlerBrokerWithNoLocations.start();
- segmentLoadDropHandler.stop();
- }
-
- @Test
- public void testSegmentLoading1HistoricalWithNoLocations()
- {
- expectedException.expect(IAE.class);
- expectedException.expectMessage("Segment cache locations must be set on historicals.");
-
- new SegmentLoadDropHandler(
- jsonMapper,
- segmentLoaderConfigNoLocations,
- announcer,
- EasyMock.createNiceMock(DataSegmentServerAnnouncer.class),
- segmentManager,
- scheduledExecutorFactory.create(5, "SegmentLoadDropHandlerTest-[%d]"),
- new ServerTypeConfig(ServerType.HISTORICAL)
- );
- }
-
/**
* Steps:
* 1. addSegment() succesfully loads the segment and annouces it
diff --git a/services/pom.xml b/services/pom.xml
index abf820e2a82..2d27d30fa67 100644
--- a/services/pom.xml
+++ b/services/pom.xml
@@ -166,6 +166,11 @@
junit
test
+
+ org.mockito
+ mockito-core
+ test
+
org.hamcrest
hamcrest-core
diff --git a/services/src/main/java/org/apache/druid/cli/ServerRunnable.java b/services/src/main/java/org/apache/druid/cli/ServerRunnable.java
index 99a9416d52a..af205382496 100644
--- a/services/src/main/java/org/apache/druid/cli/ServerRunnable.java
+++ b/services/src/main/java/org/apache/druid/cli/ServerRunnable.java
@@ -19,6 +19,7 @@
package org.apache.druid.cli;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.inject.Binder;
@@ -201,6 +202,28 @@ public abstract class ServerRunnable extends GuiceRunnable
this.useLegacyAnnouncer = useLegacyAnnouncer;
}
+ @VisibleForTesting
+ DiscoverySideEffectsProvider(
+ final NodeRole nodeRole,
+ final List> serviceClasses,
+ final boolean useLegacyAnnouncer,
+ final DruidNode druidNode,
+ final DruidNodeAnnouncer announcer,
+ final ServiceAnnouncer legacyAnnouncer,
+ final Lifecycle lifecycle,
+ final Injector injector
+ )
+ {
+ this.nodeRole = nodeRole;
+ this.serviceClasses = serviceClasses;
+ this.useLegacyAnnouncer = useLegacyAnnouncer;
+ this.druidNode = druidNode;
+ this.announcer = announcer;
+ this.legacyAnnouncer = legacyAnnouncer;
+ this.lifecycle = lifecycle;
+ this.injector = injector;
+ }
+
@Override
public Child get()
{
diff --git a/services/src/test/java/org/apache/druid/cli/DiscoverySideEffectsProviderTest.java b/services/src/test/java/org/apache/druid/cli/DiscoverySideEffectsProviderTest.java
new file mode 100644
index 00000000000..f0eb32b1b19
--- /dev/null
+++ b/services/src/test/java/org/apache/druid/cli/DiscoverySideEffectsProviderTest.java
@@ -0,0 +1,138 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.cli;
+
+import com.google.common.collect.ImmutableList;
+import com.google.inject.Injector;
+import org.apache.druid.curator.discovery.ServiceAnnouncer;
+import org.apache.druid.discovery.DiscoveryDruidNode;
+import org.apache.druid.discovery.DruidNodeAnnouncer;
+import org.apache.druid.discovery.DruidService;
+import org.apache.druid.discovery.NodeRole;
+import org.apache.druid.java.util.common.lifecycle.Lifecycle;
+import org.apache.druid.server.DruidNode;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(MockitoJUnitRunner.class)
+public class DiscoverySideEffectsProviderTest
+{
+ private static final boolean USE_LEGACY_ANNOUNCER = true;
+
+ private NodeRole nodeRole;
+ @Mock
+ private DruidNode druidNode;
+ @Mock
+ private DruidNodeAnnouncer announcer;
+ @Mock
+ private ServiceAnnouncer legacyAnnouncer;
+ @Mock
+ private Lifecycle lifecycle;
+ @Mock
+ private Injector injector;
+ private List lifecycleHandlers;
+
+ private ServerRunnable.DiscoverySideEffectsProvider target;
+
+ @Before
+ public void setUp()
+ {
+ nodeRole = NodeRole.HISTORICAL;
+ lifecycleHandlers = new ArrayList<>();
+ Mockito.when(injector.getInstance(DiscoverableDruidService.class)).thenReturn(new DiscoverableDruidService());
+ Mockito.when(injector.getInstance(UnDiscoverableDruidService.class)).thenReturn(new UnDiscoverableDruidService());
+ Mockito.doAnswer((invocation) -> {
+ DiscoveryDruidNode discoveryDruidNode = invocation.getArgument(0);
+ boolean isAllServicesDiscoverable =
+ discoveryDruidNode.getServices().values().stream().allMatch(DruidService::isDiscoverable);
+ Assert.assertTrue(isAllServicesDiscoverable);
+ return null;
+ }).when(announcer).announce(ArgumentMatchers.any(DiscoveryDruidNode.class));
+ Mockito.doAnswer((invocation) -> lifecycleHandlers.add(invocation.getArgument(0)))
+ .when(lifecycle).addHandler(
+ ArgumentMatchers.any(Lifecycle.Handler.class),
+ ArgumentMatchers.eq(Lifecycle.Stage.ANNOUNCEMENTS)
+ );
+ target = new ServerRunnable.DiscoverySideEffectsProvider(
+ nodeRole,
+ ImmutableList.of(DiscoverableDruidService.class, UnDiscoverableDruidService.class),
+ USE_LEGACY_ANNOUNCER,
+ druidNode,
+ announcer,
+ legacyAnnouncer,
+ lifecycle,
+ injector
+ );
+ }
+
+ @Test
+ public void testGetShouldAddAnnouncementsForDiscoverableServices() throws Exception
+ {
+ ServerRunnable.DiscoverySideEffectsProvider.Child child = target.get();
+ Assert.assertNotNull(child);
+ Assert.assertEquals(1, lifecycleHandlers.size());
+ lifecycleHandlers.get(0).start();
+ }
+
+ /**
+ * Dummy service which is discoverable.
+ */
+ private static class DiscoverableDruidService extends DruidService
+ {
+ @Override
+ public String getName()
+ {
+ return "DiscoverableDruidService";
+ }
+
+ @Override
+ public boolean isDiscoverable()
+ {
+ return true;
+ }
+ }
+
+ /**
+ * Dummy service which is not discoverable.
+ */
+ private static class UnDiscoverableDruidService extends DruidService
+ {
+ @Override
+ public String getName()
+ {
+ return "UnDiscoverableDruidService";
+ }
+
+ @Override
+ public boolean isDiscoverable()
+ {
+ return false;
+ }
+ }
+}