Web console - add enable/disable actions for middle manager workers (#7642)

* Overlord console - add enable/disable button for remote workers.

* Overlord console - add proxy for remote workers API.

* WorkerResourceTest - revert newline change.

* Remote worker proxy tests - remove empty line.

* Refactor remote worker proxy for readability and security

* Rename method in remote task runner tests for readability

* Remove enable/disable button for remote workers from old web console

* Add enable/disable actions for middle manager worker in new web console

* Fix variable type

* Add worker task runner query adapter

* Fix web console tests: segments-view, servers-view

* Fix overlord resource tests
This commit is contained in:
Bartosz Ługowski 2019-05-24 01:47:23 +02:00 committed by Clint Wylie
parent ec4d09a02f
commit cbdac49ab3
11 changed files with 1045 additions and 406 deletions

View File

@ -30,6 +30,12 @@ import java.util.Collection;
@PublicApi @PublicApi
public interface WorkerTaskRunner extends TaskRunner public interface WorkerTaskRunner extends TaskRunner
{ {
enum ActionType
{
ENABLE,
DISABLE
}
/** /**
* List of known workers who can accept tasks for running * List of known workers who can accept tasks for running
*/ */

View File

@ -0,0 +1,133 @@
/*
* 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.indexing.overlord;
import com.google.common.base.Optional;
import com.google.common.base.Throwables;
import com.google.common.collect.Iterables;
import io.netty.handler.timeout.TimeoutException;
import org.apache.druid.guice.annotations.EscalatedGlobal;
import org.apache.druid.indexing.overlord.hrtr.HttpRemoteTaskRunner;
import org.apache.druid.java.util.common.RE;
import org.apache.druid.java.util.emitter.EmittingLogger;
import org.apache.druid.java.util.http.client.HttpClient;
import org.apache.druid.java.util.http.client.Request;
import org.apache.druid.java.util.http.client.response.StatusResponseHandler;
import org.apache.druid.java.util.http.client.response.StatusResponseHolder;
import org.jboss.netty.handler.codec.http.HttpMethod;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import javax.inject.Inject;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ExecutionException;
public class WorkerTaskRunnerQueryAdapter
{
private static final EmittingLogger log = new EmittingLogger(HttpRemoteTaskRunner.class);
private final TaskMaster taskMaster;
private final HttpClient httpClient;
@Inject
public WorkerTaskRunnerQueryAdapter(TaskMaster taskMaster, @EscalatedGlobal final HttpClient httpClient)
{
this.taskMaster = taskMaster;
this.httpClient = httpClient;
}
public void enableWorker(String host)
{
sendRequestToWorker(host, WorkerTaskRunner.ActionType.ENABLE);
}
public void disableWorker(String host)
{
sendRequestToWorker(host, WorkerTaskRunner.ActionType.DISABLE);
}
private void sendRequestToWorker(String workerHost, WorkerTaskRunner.ActionType action)
{
WorkerTaskRunner workerTaskRunner = getWorkerTaskRunner();
if (workerTaskRunner == null) {
throw new RE("Task Runner does not support enable/disable worker actions");
}
Optional<ImmutableWorkerInfo> workerInfo = Iterables.tryFind(
workerTaskRunner.getWorkers(),
entry -> entry.getWorker()
.getHost()
.equals(workerHost)
);
if (!workerInfo.isPresent()) {
throw new RE(
"Worker on host %s does not exists",
workerHost
);
}
String actionName = WorkerTaskRunner.ActionType.ENABLE.equals(action) ? "enable" : "disable";
final URL workerUrl = TaskRunnerUtils.makeWorkerURL(
workerInfo.get().getWorker(),
"/druid/worker/v1/%s",
actionName
);
try {
final StatusResponseHolder response = httpClient.go(
new Request(HttpMethod.POST, workerUrl),
new StatusResponseHandler(StandardCharsets.UTF_8)
).get();
log.info(
"Sent %s action request to worker: %s, status: %s, response: %s",
action,
workerHost,
response.getStatus(),
response.getContent()
);
if (!HttpResponseStatus.OK.equals(response.getStatus())) {
throw new RE(
"Action [%s] failed for worker [%s] with status %s(%s)",
action,
workerHost,
response.getStatus().getCode(),
response.getStatus().getReasonPhrase()
);
}
}
catch (ExecutionException | InterruptedException | TimeoutException e) {
Throwables.propagate(e);
}
}
private WorkerTaskRunner getWorkerTaskRunner()
{
Optional<TaskRunner> taskRunnerOpt = taskMaster.getTaskRunner();
if (taskRunnerOpt.isPresent() && taskRunnerOpt.get() instanceof WorkerTaskRunner) {
return (WorkerTaskRunner) taskRunnerOpt.get();
} else {
return null;
}
}
}

View File

@ -50,6 +50,7 @@ import org.apache.druid.indexing.overlord.TaskRunner;
import org.apache.druid.indexing.overlord.TaskRunnerWorkItem; import org.apache.druid.indexing.overlord.TaskRunnerWorkItem;
import org.apache.druid.indexing.overlord.TaskStorageQueryAdapter; import org.apache.druid.indexing.overlord.TaskStorageQueryAdapter;
import org.apache.druid.indexing.overlord.WorkerTaskRunner; import org.apache.druid.indexing.overlord.WorkerTaskRunner;
import org.apache.druid.indexing.overlord.WorkerTaskRunnerQueryAdapter;
import org.apache.druid.indexing.overlord.autoscaling.ScalingStats; import org.apache.druid.indexing.overlord.autoscaling.ScalingStats;
import org.apache.druid.indexing.overlord.http.security.TaskResourceFilter; import org.apache.druid.indexing.overlord.http.security.TaskResourceFilter;
import org.apache.druid.indexing.overlord.setup.WorkerBehaviorConfig; import org.apache.druid.indexing.overlord.setup.WorkerBehaviorConfig;
@ -104,6 +105,7 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
*
*/ */
@Path("/druid/indexer/v1") @Path("/druid/indexer/v1")
public class OverlordResource public class OverlordResource
@ -117,6 +119,7 @@ public class OverlordResource
private final JacksonConfigManager configManager; private final JacksonConfigManager configManager;
private final AuditManager auditManager; private final AuditManager auditManager;
private final AuthorizerMapper authorizerMapper; private final AuthorizerMapper authorizerMapper;
private final WorkerTaskRunnerQueryAdapter workerTaskRunnerQueryAdapter;
private AtomicReference<WorkerBehaviorConfig> workerConfigRef = null; private AtomicReference<WorkerBehaviorConfig> workerConfigRef = null;
private static final List API_TASK_STATES = ImmutableList.of("pending", "waiting", "running", "complete"); private static final List API_TASK_STATES = ImmutableList.of("pending", "waiting", "running", "complete");
@ -129,7 +132,8 @@ public class OverlordResource
TaskLogStreamer taskLogStreamer, TaskLogStreamer taskLogStreamer,
JacksonConfigManager configManager, JacksonConfigManager configManager,
AuditManager auditManager, AuditManager auditManager,
AuthorizerMapper authorizerMapper AuthorizerMapper authorizerMapper,
WorkerTaskRunnerQueryAdapter workerTaskRunnerQueryAdapter
) )
{ {
this.taskMaster = taskMaster; this.taskMaster = taskMaster;
@ -139,6 +143,7 @@ public class OverlordResource
this.configManager = configManager; this.configManager = configManager;
this.auditManager = auditManager; this.auditManager = auditManager;
this.authorizerMapper = authorizerMapper; this.authorizerMapper = authorizerMapper;
this.workerTaskRunnerQueryAdapter = workerTaskRunnerQueryAdapter;
} }
@POST @POST
@ -726,6 +731,47 @@ public class OverlordResource
); );
} }
@POST
@Path("/worker/{host}/enable")
@Produces(MediaType.APPLICATION_JSON)
@ResourceFilters(StateResourceFilter.class)
public Response enableWorker(@PathParam("host") final String host)
{
return changeWorkerStatus(host, WorkerTaskRunner.ActionType.ENABLE);
}
@POST
@Path("/worker/{host}/disable")
@Produces(MediaType.APPLICATION_JSON)
@ResourceFilters(StateResourceFilter.class)
public Response disableWorker(@PathParam("host") final String host)
{
return changeWorkerStatus(host, WorkerTaskRunner.ActionType.DISABLE);
}
private Response changeWorkerStatus(String host, WorkerTaskRunner.ActionType action)
{
try {
if (WorkerTaskRunner.ActionType.DISABLE.equals(action)) {
workerTaskRunnerQueryAdapter.disableWorker(host);
return Response.ok(ImmutableMap.of(host, "disabled")).build();
} else if (WorkerTaskRunner.ActionType.ENABLE.equals(action)) {
workerTaskRunnerQueryAdapter.enableWorker(host);
return Response.ok(ImmutableMap.of(host, "enabled")).build();
} else {
return Response.serverError()
.entity(ImmutableMap.of("error", "Worker does not support " + action + " action!"))
.build();
}
}
catch (Exception e) {
log.error(e, "Error in posting [%s] action to [%s]", action, host);
return Response.serverError()
.entity(ImmutableMap.of("error", e.getMessage()))
.build();
}
}
@GET @GET
@Path("/scaling") @Path("/scaling")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)

View File

@ -0,0 +1,197 @@
/*
* 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.indexing.overlord;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.SettableFuture;
import org.apache.druid.indexing.worker.Worker;
import org.apache.druid.java.util.common.DateTimes;
import org.apache.druid.java.util.common.RE;
import org.apache.druid.java.util.http.client.HttpClient;
import org.apache.druid.java.util.http.client.Request;
import org.apache.druid.java.util.http.client.response.HttpResponseHandler;
import org.apache.druid.java.util.http.client.response.StatusResponseHolder;
import org.easymock.Capture;
import org.easymock.EasyMock;
import org.jboss.netty.handler.codec.http.HttpMethod;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import java.net.URL;
public class WorkerTaskRunnerQueryAdpaterTest
{
private WorkerTaskRunnerQueryAdapter workerTaskRunnerQueryAdapter;
private HttpClient httpClient;
private WorkerTaskRunner workerTaskRunner;
private TaskMaster taskMaster;
@Before
public void setup()
{
httpClient = EasyMock.createNiceMock(HttpClient.class);
workerTaskRunner = EasyMock.createMock(WorkerTaskRunner.class);
taskMaster = EasyMock.createStrictMock(TaskMaster.class);
workerTaskRunnerQueryAdapter = new WorkerTaskRunnerQueryAdapter(taskMaster, httpClient);
EasyMock.expect(taskMaster.getTaskRunner()).andReturn(
Optional.of(workerTaskRunner)
).once();
EasyMock.expect(workerTaskRunner.getWorkers()).andReturn(
ImmutableList.of(
new ImmutableWorkerInfo(
new Worker(
"http", "worker-host1", "192.0.0.1", 10, "v1"
),
2,
ImmutableSet.of("grp1", "grp2"),
ImmutableSet.of("task1", "task2"),
DateTimes.of("2015-01-01T01:01:01Z")
),
new ImmutableWorkerInfo(
new Worker(
"https", "worker-host2", "192.0.0.2", 4, "v1"
),
1,
ImmutableSet.of("grp1"),
ImmutableSet.of("task1"),
DateTimes.of("2015-01-01T01:01:01Z")
)
)
).once();
}
@After
public void tearDown()
{
EasyMock.verify(workerTaskRunner, taskMaster, httpClient);
}
@Test
public void testDisableWorker() throws Exception
{
final URL workerUrl = new URL("http://worker-host1/druid/worker/v1/disable");
final String workerResponse = "{\"worker-host1\":\"disabled\"}";
Capture<Request> capturedRequest = getHttpClientRequestCapture(HttpResponseStatus.OK, workerResponse);
EasyMock.replay(workerTaskRunner, taskMaster, httpClient);
workerTaskRunnerQueryAdapter.disableWorker("worker-host1");
Assert.assertEquals(HttpMethod.POST, capturedRequest.getValue().getMethod());
Assert.assertEquals(workerUrl, capturedRequest.getValue().getUrl());
}
@Test
public void testDisableWorkerWhenWorkerRaisesError() throws Exception
{
final URL workerUrl = new URL("http://worker-host1/druid/worker/v1/disable");
Capture<Request> capturedRequest = getHttpClientRequestCapture(HttpResponseStatus.INTERNAL_SERVER_ERROR, "");
EasyMock.replay(workerTaskRunner, taskMaster, httpClient);
try {
workerTaskRunnerQueryAdapter.disableWorker("worker-host1");
Assert.fail("Should raise RE exception!");
}
catch (RE re) {
}
Assert.assertEquals(HttpMethod.POST, capturedRequest.getValue().getMethod());
Assert.assertEquals(workerUrl, capturedRequest.getValue().getUrl());
}
@Test(expected = RE.class)
public void testDisableWorkerWhenWorkerNotExists()
{
EasyMock.replay(workerTaskRunner, taskMaster, httpClient);
workerTaskRunnerQueryAdapter.disableWorker("not-existing-worker");
}
@Test
public void testEnableWorker() throws Exception
{
final URL workerUrl = new URL("https://worker-host2/druid/worker/v1/enable");
final String workerResponse = "{\"worker-host2\":\"enabled\"}";
Capture<Request> capturedRequest = getHttpClientRequestCapture(HttpResponseStatus.OK, workerResponse);
EasyMock.replay(workerTaskRunner, taskMaster, httpClient);
workerTaskRunnerQueryAdapter.enableWorker("worker-host2");
Assert.assertEquals(HttpMethod.POST, capturedRequest.getValue().getMethod());
Assert.assertEquals(workerUrl, capturedRequest.getValue().getUrl());
}
@Test
public void testEnableWorkerWhenWorkerRaisesError() throws Exception
{
final URL workerUrl = new URL("https://worker-host2/druid/worker/v1/enable");
Capture<Request> capturedRequest = getHttpClientRequestCapture(HttpResponseStatus.INTERNAL_SERVER_ERROR, "");
EasyMock.replay(workerTaskRunner, taskMaster, httpClient);
try {
workerTaskRunnerQueryAdapter.enableWorker("worker-host2");
Assert.fail("Should raise RE exception!");
}
catch (RE re) {
}
Assert.assertEquals(HttpMethod.POST, capturedRequest.getValue().getMethod());
Assert.assertEquals(workerUrl, capturedRequest.getValue().getUrl());
}
@Test(expected = RE.class)
public void testEnableWorkerWhenWorkerNotExists()
{
EasyMock.replay(workerTaskRunner, taskMaster, httpClient);
workerTaskRunnerQueryAdapter.enableWorker("not-existing-worker");
}
private Capture<Request> getHttpClientRequestCapture(HttpResponseStatus httpStatus, String responseContent)
{
SettableFuture<StatusResponseHolder> futureResult = SettableFuture.create();
futureResult.set(
new StatusResponseHolder(httpStatus, new StringBuilder(responseContent))
);
Capture<Request> capturedRequest = EasyMock.newCapture();
EasyMock.expect(
httpClient.go(
EasyMock.capture(capturedRequest),
EasyMock.<HttpResponseHandler>anyObject()
)
)
.andReturn(futureResult)
.once();
return capturedRequest;
}
}

View File

@ -40,7 +40,9 @@ import org.apache.druid.indexing.overlord.TaskQueue;
import org.apache.druid.indexing.overlord.TaskRunner; import org.apache.druid.indexing.overlord.TaskRunner;
import org.apache.druid.indexing.overlord.TaskRunnerWorkItem; import org.apache.druid.indexing.overlord.TaskRunnerWorkItem;
import org.apache.druid.indexing.overlord.TaskStorageQueryAdapter; import org.apache.druid.indexing.overlord.TaskStorageQueryAdapter;
import org.apache.druid.indexing.overlord.WorkerTaskRunnerQueryAdapter;
import org.apache.druid.java.util.common.DateTimes; import org.apache.druid.java.util.common.DateTimes;
import org.apache.druid.java.util.common.RE;
import org.apache.druid.segment.TestHelper; import org.apache.druid.segment.TestHelper;
import org.apache.druid.server.security.Access; import org.apache.druid.server.security.Access;
import org.apache.druid.server.security.Action; import org.apache.druid.server.security.Action;
@ -51,6 +53,7 @@ import org.apache.druid.server.security.AuthorizerMapper;
import org.apache.druid.server.security.ForbiddenException; import org.apache.druid.server.security.ForbiddenException;
import org.apache.druid.server.security.Resource; import org.apache.druid.server.security.Resource;
import org.easymock.EasyMock; import org.easymock.EasyMock;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.joda.time.Duration; import org.joda.time.Duration;
import org.joda.time.Interval; import org.joda.time.Interval;
@ -79,6 +82,7 @@ public class OverlordResourceTest
private IndexerMetadataStorageAdapter indexerMetadataStorageAdapter; private IndexerMetadataStorageAdapter indexerMetadataStorageAdapter;
private HttpServletRequest req; private HttpServletRequest req;
private TaskRunner taskRunner; private TaskRunner taskRunner;
private WorkerTaskRunnerQueryAdapter workerTaskRunnerQueryAdapter;
@Rule @Rule
public ExpectedException expectedException = ExpectedException.none(); public ExpectedException expectedException = ExpectedException.none();
@ -91,6 +95,7 @@ public class OverlordResourceTest
taskStorageQueryAdapter = EasyMock.createStrictMock(TaskStorageQueryAdapter.class); taskStorageQueryAdapter = EasyMock.createStrictMock(TaskStorageQueryAdapter.class);
indexerMetadataStorageAdapter = EasyMock.createStrictMock(IndexerMetadataStorageAdapter.class); indexerMetadataStorageAdapter = EasyMock.createStrictMock(IndexerMetadataStorageAdapter.class);
req = EasyMock.createStrictMock(HttpServletRequest.class); req = EasyMock.createStrictMock(HttpServletRequest.class);
workerTaskRunnerQueryAdapter = EasyMock.createStrictMock(WorkerTaskRunnerQueryAdapter.class);
EasyMock.expect(taskMaster.getTaskRunner()).andReturn( EasyMock.expect(taskMaster.getTaskRunner()).andReturn(
Optional.of(taskRunner) Optional.of(taskRunner)
@ -124,21 +129,36 @@ public class OverlordResourceTest
null, null,
null, null,
null, null,
authMapper authMapper,
workerTaskRunnerQueryAdapter
); );
} }
@After @After
public void tearDown() public void tearDown()
{ {
EasyMock.verify(taskRunner, taskMaster, taskStorageQueryAdapter, indexerMetadataStorageAdapter, req); EasyMock.verify(
taskRunner,
taskMaster,
taskStorageQueryAdapter,
indexerMetadataStorageAdapter,
req,
workerTaskRunnerQueryAdapter
);
} }
@Test @Test
public void testLeader() public void testLeader()
{ {
EasyMock.expect(taskMaster.getCurrentLeader()).andReturn("boz").once(); EasyMock.expect(taskMaster.getCurrentLeader()).andReturn("boz").once();
EasyMock.replay(taskRunner, taskMaster, taskStorageQueryAdapter, indexerMetadataStorageAdapter, req); EasyMock.replay(
taskRunner,
taskMaster,
taskStorageQueryAdapter,
indexerMetadataStorageAdapter,
req,
workerTaskRunnerQueryAdapter
);
final Response response = overlordResource.getLeader(); final Response response = overlordResource.getLeader();
Assert.assertEquals("boz", response.getEntity()); Assert.assertEquals("boz", response.getEntity());
@ -150,7 +170,14 @@ public class OverlordResourceTest
{ {
EasyMock.expect(taskMaster.isLeader()).andReturn(true).once(); EasyMock.expect(taskMaster.isLeader()).andReturn(true).once();
EasyMock.expect(taskMaster.isLeader()).andReturn(false).once(); EasyMock.expect(taskMaster.isLeader()).andReturn(false).once();
EasyMock.replay(taskRunner, taskMaster, taskStorageQueryAdapter, indexerMetadataStorageAdapter, req); EasyMock.replay(
taskRunner,
taskMaster,
taskStorageQueryAdapter,
indexerMetadataStorageAdapter,
req,
workerTaskRunnerQueryAdapter
);
// true // true
final Response response1 = overlordResource.isLeader(); final Response response1 = overlordResource.isLeader();
@ -207,7 +234,14 @@ public class OverlordResourceTest
) )
); );
EasyMock.replay(taskRunner, taskMaster, taskStorageQueryAdapter, indexerMetadataStorageAdapter, req); EasyMock.replay(
taskRunner,
taskMaster,
taskStorageQueryAdapter,
indexerMetadataStorageAdapter,
req,
workerTaskRunnerQueryAdapter
);
List<TaskStatusPlus> responseObjects = (List<TaskStatusPlus>) overlordResource.getWaitingTasks(req) List<TaskStatusPlus> responseObjects = (List<TaskStatusPlus>) overlordResource.getWaitingTasks(req)
.getEntity(); .getEntity();
@ -224,7 +258,8 @@ public class OverlordResourceTest
ImmutableList.of( ImmutableList.of(
new MockTaskRunnerWorkItem(tasksIds.get(0), null), new MockTaskRunnerWorkItem(tasksIds.get(0), null),
new MockTaskRunnerWorkItem(tasksIds.get(1), null), new MockTaskRunnerWorkItem(tasksIds.get(1), null),
new MockTaskRunnerWorkItem(tasksIds.get(2), null))); new MockTaskRunnerWorkItem(tasksIds.get(2), null)
));
EasyMock.expect(taskStorageQueryAdapter.getCompletedTaskInfoByCreatedTimeDuration(null, null, null)).andStubReturn( EasyMock.expect(taskStorageQueryAdapter.getCompletedTaskInfoByCreatedTimeDuration(null, null, null)).andStubReturn(
ImmutableList.of( ImmutableList.of(
@ -251,7 +286,14 @@ public class OverlordResourceTest
) )
) )
); );
EasyMock.replay(taskRunner, taskMaster, taskStorageQueryAdapter, indexerMetadataStorageAdapter, req); EasyMock.replay(
taskRunner,
taskMaster,
taskStorageQueryAdapter,
indexerMetadataStorageAdapter,
req,
workerTaskRunnerQueryAdapter
);
Assert.assertTrue(taskStorageQueryAdapter.getCompletedTaskInfoByCreatedTimeDuration(null, null, null).size() == 3); Assert.assertTrue(taskStorageQueryAdapter.getCompletedTaskInfoByCreatedTimeDuration(null, null, null).size() == 3);
Assert.assertTrue(taskRunner.getRunningTasks().size() == 3); Assert.assertTrue(taskRunner.getRunningTasks().size() == 3);
List<TaskStatusPlus> responseObjects = (List) overlordResource List<TaskStatusPlus> responseObjects = (List) overlordResource
@ -292,7 +334,14 @@ public class OverlordResourceTest
) )
); );
EasyMock.replay(taskRunner, taskMaster, taskStorageQueryAdapter, indexerMetadataStorageAdapter, req); EasyMock.replay(
taskRunner,
taskMaster,
taskStorageQueryAdapter,
indexerMetadataStorageAdapter,
req,
workerTaskRunnerQueryAdapter
);
List<TaskStatusPlus> responseObjects = (List) overlordResource.getRunningTasks(null, req) List<TaskStatusPlus> responseObjects = (List) overlordResource.getRunningTasks(null, req)
.getEntity(); .getEntity();
@ -384,7 +433,14 @@ public class OverlordResourceTest
) )
); );
EasyMock.replay(taskRunner, taskMaster, taskStorageQueryAdapter, indexerMetadataStorageAdapter, req); EasyMock.replay(
taskRunner,
taskMaster,
taskStorageQueryAdapter,
indexerMetadataStorageAdapter,
req,
workerTaskRunnerQueryAdapter
);
List<TaskStatusPlus> responseObjects = (List<TaskStatusPlus>) overlordResource List<TaskStatusPlus> responseObjects = (List<TaskStatusPlus>) overlordResource
.getTasks(null, null, null, null, null, req) .getTasks(null, null, null, null, null, req)
.getEntity(); .getEntity();
@ -396,7 +452,8 @@ public class OverlordResourceTest
{ {
expectAuthorizationTokenCheck(); expectAuthorizationTokenCheck();
//completed tasks //completed tasks
EasyMock.expect(taskStorageQueryAdapter.getCompletedTaskInfoByCreatedTimeDuration(null, null, "allow")).andStubReturn( EasyMock.expect(taskStorageQueryAdapter.getCompletedTaskInfoByCreatedTimeDuration(null, null, "allow"))
.andStubReturn(
ImmutableList.of( ImmutableList.of(
new TaskInfo( new TaskInfo(
"id_5", "id_5",
@ -471,7 +528,14 @@ public class OverlordResourceTest
new MockTaskRunnerWorkItem("id_1", null) new MockTaskRunnerWorkItem("id_1", null)
) )
); );
EasyMock.replay(taskRunner, taskMaster, taskStorageQueryAdapter, indexerMetadataStorageAdapter, req); EasyMock.replay(
taskRunner,
taskMaster,
taskStorageQueryAdapter,
indexerMetadataStorageAdapter,
req,
workerTaskRunnerQueryAdapter
);
List<TaskStatusPlus> responseObjects = (List<TaskStatusPlus>) overlordResource List<TaskStatusPlus> responseObjects = (List<TaskStatusPlus>) overlordResource
.getTasks(null, "allow", null, null, null, req) .getTasks(null, "allow", null, null, null, req)
@ -526,7 +590,14 @@ public class OverlordResourceTest
) )
); );
EasyMock.replay(taskRunner, taskMaster, taskStorageQueryAdapter, indexerMetadataStorageAdapter, req); EasyMock.replay(
taskRunner,
taskMaster,
taskStorageQueryAdapter,
indexerMetadataStorageAdapter,
req,
workerTaskRunnerQueryAdapter
);
List<TaskStatusPlus> responseObjects = (List<TaskStatusPlus>) overlordResource List<TaskStatusPlus> responseObjects = (List<TaskStatusPlus>) overlordResource
.getTasks( .getTasks(
"waiting", "waiting",
@ -586,7 +657,14 @@ public class OverlordResourceTest
); );
EasyMock.replay(taskRunner, taskMaster, taskStorageQueryAdapter, indexerMetadataStorageAdapter, req); EasyMock.replay(
taskRunner,
taskMaster,
taskStorageQueryAdapter,
indexerMetadataStorageAdapter,
req,
workerTaskRunnerQueryAdapter
);
List<TaskStatusPlus> responseObjects = (List) overlordResource List<TaskStatusPlus> responseObjects = (List) overlordResource
.getTasks("running", "allow", null, null, null, req) .getTasks("running", "allow", null, null, null, req)
@ -644,7 +722,14 @@ public class OverlordResourceTest
); );
EasyMock.replay(taskRunner, taskMaster, taskStorageQueryAdapter, indexerMetadataStorageAdapter, req); EasyMock.replay(
taskRunner,
taskMaster,
taskStorageQueryAdapter,
indexerMetadataStorageAdapter,
req,
workerTaskRunnerQueryAdapter
);
List<TaskStatusPlus> responseObjects = (List<TaskStatusPlus>) overlordResource List<TaskStatusPlus> responseObjects = (List<TaskStatusPlus>) overlordResource
.getTasks("pending", null, null, null, null, req) .getTasks("pending", null, null, null, null, req)
@ -685,7 +770,14 @@ public class OverlordResourceTest
) )
) )
); );
EasyMock.replay(taskRunner, taskMaster, taskStorageQueryAdapter, indexerMetadataStorageAdapter, req); EasyMock.replay(
taskRunner,
taskMaster,
taskStorageQueryAdapter,
indexerMetadataStorageAdapter,
req,
workerTaskRunnerQueryAdapter
);
List<TaskStatusPlus> responseObjects = (List<TaskStatusPlus>) overlordResource List<TaskStatusPlus> responseObjects = (List<TaskStatusPlus>) overlordResource
.getTasks("complete", null, null, null, null, req) .getTasks("complete", null, null, null, null, req)
.getEntity(); .getEntity();
@ -700,7 +792,8 @@ public class OverlordResourceTest
expectAuthorizationTokenCheck(); expectAuthorizationTokenCheck();
List<String> tasksIds = ImmutableList.of("id_1", "id_2", "id_3"); List<String> tasksIds = ImmutableList.of("id_1", "id_2", "id_3");
Duration duration = new Period("PT86400S").toStandardDuration(); Duration duration = new Period("PT86400S").toStandardDuration();
EasyMock.expect(taskStorageQueryAdapter.getCompletedTaskInfoByCreatedTimeDuration(null, duration, null)).andStubReturn( EasyMock.expect(taskStorageQueryAdapter.getCompletedTaskInfoByCreatedTimeDuration(null, duration, null))
.andStubReturn(
ImmutableList.of( ImmutableList.of(
new TaskInfo( new TaskInfo(
"id_1", "id_1",
@ -726,7 +819,14 @@ public class OverlordResourceTest
) )
); );
EasyMock.replay(taskRunner, taskMaster, taskStorageQueryAdapter, indexerMetadataStorageAdapter, req); EasyMock.replay(
taskRunner,
taskMaster,
taskStorageQueryAdapter,
indexerMetadataStorageAdapter,
req,
workerTaskRunnerQueryAdapter
);
String interval = "2010-01-01_P1D"; String interval = "2010-01-01_P1D";
List<TaskStatusPlus> responseObjects = (List<TaskStatusPlus>) overlordResource List<TaskStatusPlus> responseObjects = (List<TaskStatusPlus>) overlordResource
.getTasks("complete", null, interval, null, null, req) .getTasks("complete", null, interval, null, null, req)
@ -765,7 +865,14 @@ public class OverlordResourceTest
) )
) )
); );
EasyMock.replay(taskRunner, taskMaster, taskStorageQueryAdapter, indexerMetadataStorageAdapter, req); EasyMock.replay(
taskRunner,
taskMaster,
taskStorageQueryAdapter,
indexerMetadataStorageAdapter,
req,
workerTaskRunnerQueryAdapter
);
List<TaskStatusPlus> responseObjects = (List<TaskStatusPlus>) overlordResource List<TaskStatusPlus> responseObjects = (List<TaskStatusPlus>) overlordResource
.getTasks("complete", null, null, null, null, req) .getTasks("complete", null, null, null, null, req)
.getEntity(); .getEntity();
@ -779,7 +886,14 @@ public class OverlordResourceTest
@Test @Test
public void testGetTasksNegativeState() public void testGetTasksNegativeState()
{ {
EasyMock.replay(taskRunner, taskMaster, taskStorageQueryAdapter, indexerMetadataStorageAdapter, req); EasyMock.replay(
taskRunner,
taskMaster,
taskStorageQueryAdapter,
indexerMetadataStorageAdapter,
req,
workerTaskRunnerQueryAdapter
);
Object responseObject = overlordResource Object responseObject = overlordResource
.getTasks("blah", "ds_test", null, null, null, req) .getTasks("blah", "ds_test", null, null, null, req)
.getEntity(); .getEntity();
@ -795,7 +909,14 @@ public class OverlordResourceTest
expectedException.expect(ForbiddenException.class); expectedException.expect(ForbiddenException.class);
expectAuthorizationTokenCheck(); expectAuthorizationTokenCheck();
EasyMock.replay(taskRunner, taskMaster, taskStorageQueryAdapter, indexerMetadataStorageAdapter, req); EasyMock.replay(
taskRunner,
taskMaster,
taskStorageQueryAdapter,
indexerMetadataStorageAdapter,
req,
workerTaskRunnerQueryAdapter
);
Task task = NoopTask.create(); Task task = NoopTask.create();
overlordResource.taskPost(task, req); overlordResource.taskPost(task, req);
} }
@ -815,7 +936,14 @@ public class OverlordResourceTest
) )
.andReturn(2); .andReturn(2);
EasyMock.replay(taskRunner, taskMaster, taskStorageQueryAdapter, indexerMetadataStorageAdapter, req); EasyMock.replay(
taskRunner,
taskMaster,
taskStorageQueryAdapter,
indexerMetadataStorageAdapter,
req,
workerTaskRunnerQueryAdapter
);
final Map<String, Integer> response = (Map<String, Integer>) overlordResource final Map<String, Integer> response = (Map<String, Integer>) overlordResource
.killPendingSegments("allow", new Interval(DateTimes.MIN, DateTimes.nowUtc()).toString(), req) .killPendingSegments("allow", new Interval(DateTimes.MIN, DateTimes.nowUtc()).toString(), req)
@ -837,7 +965,14 @@ public class OverlordResourceTest
EasyMock.expect(taskStorageQueryAdapter.getTask("othertask")) EasyMock.expect(taskStorageQueryAdapter.getTask("othertask"))
.andReturn(Optional.absent()); .andReturn(Optional.absent());
EasyMock.replay(taskRunner, taskMaster, taskStorageQueryAdapter, indexerMetadataStorageAdapter, req); EasyMock.replay(
taskRunner,
taskMaster,
taskStorageQueryAdapter,
indexerMetadataStorageAdapter,
req,
workerTaskRunnerQueryAdapter
);
final Response response1 = overlordResource.getTaskPayload("mytask"); final Response response1 = overlordResource.getTaskPayload("mytask");
final TaskPayloadResponse taskPayloadResponse1 = TestHelper.makeJsonMapper().readValue( final TaskPayloadResponse taskPayloadResponse1 = TestHelper.makeJsonMapper().readValue(
@ -873,7 +1008,14 @@ public class OverlordResourceTest
EasyMock.<Collection<? extends TaskRunnerWorkItem>>expect(taskRunner.getKnownTasks()) EasyMock.<Collection<? extends TaskRunnerWorkItem>>expect(taskRunner.getKnownTasks())
.andReturn(ImmutableList.of()); .andReturn(ImmutableList.of());
EasyMock.replay(taskRunner, taskMaster, taskStorageQueryAdapter, indexerMetadataStorageAdapter, req); EasyMock.replay(
taskRunner,
taskMaster,
taskStorageQueryAdapter,
indexerMetadataStorageAdapter,
req,
workerTaskRunnerQueryAdapter
);
final Response response1 = overlordResource.getTaskStatus("mytask"); final Response response1 = overlordResource.getTaskStatus("mytask");
final TaskStatusResponse taskStatusResponse1 = TestHelper.makeJsonMapper().readValue( final TaskStatusResponse taskStatusResponse1 = TestHelper.makeJsonMapper().readValue(
@ -926,7 +1068,15 @@ public class OverlordResourceTest
mockQueue.shutdown("id_1", "Shutdown request from user"); mockQueue.shutdown("id_1", "Shutdown request from user");
EasyMock.expectLastCall(); EasyMock.expectLastCall();
EasyMock.replay(taskRunner, taskMaster, taskStorageQueryAdapter, indexerMetadataStorageAdapter, req, mockQueue); EasyMock.replay(
taskRunner,
taskMaster,
taskStorageQueryAdapter,
indexerMetadataStorageAdapter,
req,
mockQueue,
workerTaskRunnerQueryAdapter
);
final Map<String, Integer> response = (Map<String, Integer>) overlordResource final Map<String, Integer> response = (Map<String, Integer>) overlordResource
.doShutdown("id_1") .doShutdown("id_1")
@ -969,7 +1119,15 @@ public class OverlordResourceTest
mockQueue.shutdown("id_2", "Shutdown request from user"); mockQueue.shutdown("id_2", "Shutdown request from user");
EasyMock.expectLastCall(); EasyMock.expectLastCall();
EasyMock.replay(taskRunner, taskMaster, taskStorageQueryAdapter, indexerMetadataStorageAdapter, req, mockQueue); EasyMock.replay(
taskRunner,
taskMaster,
taskStorageQueryAdapter,
indexerMetadataStorageAdapter,
req,
mockQueue,
workerTaskRunnerQueryAdapter
);
final Map<String, Integer> response = (Map<String, Integer>) overlordResource final Map<String, Integer> response = (Map<String, Integer>) overlordResource
.shutdownTasksForDataSource("datasource") .shutdownTasksForDataSource("datasource")
@ -984,12 +1142,111 @@ public class OverlordResourceTest
EasyMock.expect(taskMaster.isLeader()).andReturn(true).anyTimes(); EasyMock.expect(taskMaster.isLeader()).andReturn(true).anyTimes();
EasyMock.expect(taskMaster.getTaskQueue()).andReturn(Optional.of(taskQueue)).anyTimes(); EasyMock.expect(taskMaster.getTaskQueue()).andReturn(Optional.of(taskQueue)).anyTimes();
EasyMock.expect(taskStorageQueryAdapter.getActiveTaskInfo(EasyMock.anyString())).andReturn(Collections.emptyList()); EasyMock.expect(taskStorageQueryAdapter.getActiveTaskInfo(EasyMock.anyString())).andReturn(Collections.emptyList());
EasyMock.replay(taskRunner, taskMaster, taskStorageQueryAdapter, indexerMetadataStorageAdapter, req); EasyMock.replay(
taskRunner,
taskMaster,
taskStorageQueryAdapter,
indexerMetadataStorageAdapter,
req,
workerTaskRunnerQueryAdapter
);
final Response response = overlordResource.shutdownTasksForDataSource("notExisting"); final Response response = overlordResource.shutdownTasksForDataSource("notExisting");
Assert.assertEquals(Status.NOT_FOUND.getStatusCode(), response.getStatus()); Assert.assertEquals(Status.NOT_FOUND.getStatusCode(), response.getStatus());
} }
@Test
public void testEnableWorker()
{
final String host = "worker-host";
workerTaskRunnerQueryAdapter.enableWorker(host);
EasyMock.expectLastCall().once();
EasyMock.replay(
taskRunner,
taskMaster,
taskStorageQueryAdapter,
indexerMetadataStorageAdapter,
req,
workerTaskRunnerQueryAdapter
);
final Response response = overlordResource.enableWorker(host);
Assert.assertEquals(HttpResponseStatus.OK.getCode(), response.getStatus());
Assert.assertEquals(ImmutableMap.of(host, "enabled"), response.getEntity());
}
@Test
public void testDisableWorker()
{
final String host = "worker-host";
workerTaskRunnerQueryAdapter.disableWorker(host);
EasyMock.expectLastCall().once();
EasyMock.replay(
taskRunner,
taskMaster,
taskStorageQueryAdapter,
indexerMetadataStorageAdapter,
req,
workerTaskRunnerQueryAdapter
);
final Response response = overlordResource.disableWorker(host);
Assert.assertEquals(HttpResponseStatus.OK.getCode(), response.getStatus());
Assert.assertEquals(ImmutableMap.of(host, "disabled"), response.getEntity());
}
@Test
public void testEnableWorkerWhenWorkerAPIRaisesError()
{
final String host = "worker-host";
workerTaskRunnerQueryAdapter.enableWorker(host);
EasyMock.expectLastCall().andThrow(new RE("Worker API returns error!")).once();
EasyMock.replay(
taskRunner,
taskMaster,
taskStorageQueryAdapter,
indexerMetadataStorageAdapter,
req,
workerTaskRunnerQueryAdapter
);
final Response response = overlordResource.enableWorker(host);
Assert.assertEquals(HttpResponseStatus.INTERNAL_SERVER_ERROR.getCode(), response.getStatus());
Assert.assertEquals(ImmutableMap.of("error", "Worker API returns error!"), response.getEntity());
}
@Test
public void testDisableWorkerWhenWorkerAPIRaisesError()
{
final String host = "worker-host";
workerTaskRunnerQueryAdapter.disableWorker(host);
EasyMock.expectLastCall().andThrow(new RE("Worker API returns error!")).once();
EasyMock.replay(
taskRunner,
taskMaster,
taskStorageQueryAdapter,
indexerMetadataStorageAdapter,
req,
workerTaskRunnerQueryAdapter
);
final Response response = overlordResource.disableWorker(host);
Assert.assertEquals(HttpResponseStatus.INTERNAL_SERVER_ERROR.getCode(), response.getStatus());
Assert.assertEquals(ImmutableMap.of("error", "Worker API returns error!"), response.getEntity());
}
private void expectAuthorizationTokenCheck() private void expectAuthorizationTokenCheck()
{ {
AuthenticationResult authenticationResult = new AuthenticationResult("druid", "druid", null, null); AuthenticationResult authenticationResult = new AuthenticationResult("druid", "druid", null, null);

View File

@ -52,6 +52,7 @@ import org.apache.druid.indexing.overlord.TaskRunnerListener;
import org.apache.druid.indexing.overlord.TaskRunnerWorkItem; import org.apache.druid.indexing.overlord.TaskRunnerWorkItem;
import org.apache.druid.indexing.overlord.TaskStorage; import org.apache.druid.indexing.overlord.TaskStorage;
import org.apache.druid.indexing.overlord.TaskStorageQueryAdapter; import org.apache.druid.indexing.overlord.TaskStorageQueryAdapter;
import org.apache.druid.indexing.overlord.WorkerTaskRunnerQueryAdapter;
import org.apache.druid.indexing.overlord.autoscaling.ScalingStats; import org.apache.druid.indexing.overlord.autoscaling.ScalingStats;
import org.apache.druid.indexing.overlord.config.TaskQueueConfig; import org.apache.druid.indexing.overlord.config.TaskQueueConfig;
import org.apache.druid.indexing.overlord.helpers.OverlordHelperManager; import org.apache.druid.indexing.overlord.helpers.OverlordHelperManager;
@ -212,6 +213,7 @@ public class OverlordTest
Assert.assertEquals(taskMaster.getCurrentLeader(), druidNode.getHostAndPort()); Assert.assertEquals(taskMaster.getCurrentLeader(), druidNode.getHostAndPort());
final TaskStorageQueryAdapter taskStorageQueryAdapter = new TaskStorageQueryAdapter(taskStorage); final TaskStorageQueryAdapter taskStorageQueryAdapter = new TaskStorageQueryAdapter(taskStorage);
final WorkerTaskRunnerQueryAdapter workerTaskRunnerQueryAdapter = new WorkerTaskRunnerQueryAdapter(taskMaster, null);
// Test Overlord resource stuff // Test Overlord resource stuff
overlordResource = new OverlordResource( overlordResource = new OverlordResource(
taskMaster, taskMaster,
@ -220,7 +222,8 @@ public class OverlordTest
null, null,
null, null,
null, null,
AuthTestUtils.TEST_AUTHORIZER_MAPPER AuthTestUtils.TEST_AUTHORIZER_MAPPER,
workerTaskRunnerQueryAdapter
); );
Response response = overlordResource.getLeader(); Response response = overlordResource.getLeader();
Assert.assertEquals(druidNode.getHostAndPort(), response.getEntity()); Assert.assertEquals(druidNode.getHostAndPort(), response.getEntity());

View File

@ -55,6 +55,7 @@ Generated/copied dynamically
``` ```
GET /status GET /status
GET /druid/indexer/v1/supervisor?full GET /druid/indexer/v1/supervisor?full
POST /druid/indexer/v1/worker
GET /druid/indexer/v1/workers GET /druid/indexer/v1/workers
GET /druid/coordinator/v1/loadqueue?simple GET /druid/coordinator/v1/loadqueue?simple
GET /druid/coordinator/v1/config GET /druid/coordinator/v1/config

View File

@ -2,10 +2,10 @@
exports[`describe segments-view segments view snapshot 1`] = ` exports[`describe segments-view segments view snapshot 1`] = `
<div <div
className="servers-view app-view" className="segments-view app-view"
> >
<ViewControlBar <ViewControlBar
label="Historicals" label="Segments"
> >
<Blueprint3.Button <Blueprint3.Button
icon="refresh" icon="refresh"
@ -13,256 +13,27 @@ exports[`describe segments-view segments view snapshot 1`] = `
text="Refresh" text="Refresh"
/> />
<Blueprint3.Button <Blueprint3.Button
hidden={false}
icon="application" icon="application"
onClick={[Function]} onClick={[Function]}
text="Go to SQL" text="Go to SQL"
/> />
<Blueprint3.Switch
checked={false}
label="Group by tier"
onChange={[Function]}
/>
<TableColumnSelection <TableColumnSelection
columns={ columns={
Array [ Array [
"Server", "Segment ID",
"Tier", "Datasource",
"Curr size", "Start",
"Max size", "End",
"Usage", "Version",
"Load/drop queues", "Partition",
"Host", "Size",
"Port", "Num rows",
] "Replicas",
} "Is published",
onChange={[Function]} "Is realtime",
tableColumnsHidden={Array []} "Is available",
/> "Is overshadowed",
</ViewControlBar>
<ReactTable
AggregatedComponent={[Function]}
ExpanderComponent={[Function]}
FilterComponent={[Function]}
LoadingComponent={[Function]}
NoDataComponent={[Function]}
PadRowComponent={[Function]}
PaginationComponent={[Function]}
PivotValueComponent={[Function]}
ResizerComponent={[Function]}
TableComponent={[Function]}
TbodyComponent={[Function]}
TdComponent={[Function]}
TfootComponent={[Function]}
ThComponent={[Function]}
TheadComponent={[Function]}
TrComponent={[Function]}
TrGroupComponent={[Function]}
aggregatedKey="_aggregated"
className="-striped -highlight"
collapseOnDataChange={true}
collapseOnPageChange={true}
collapseOnSortingChange={true}
column={
Object {
"Aggregated": undefined,
"Cell": undefined,
"Expander": undefined,
"Filter": undefined,
"Footer": undefined,
"Header": undefined,
"Pivot": undefined,
"PivotValue": undefined,
"aggregate": undefined,
"className": "",
"filterAll": false,
"filterMethod": undefined,
"filterable": undefined,
"footerClassName": "",
"footerStyle": Object {},
"getFooterProps": [Function],
"getHeaderProps": [Function],
"getProps": [Function],
"headerClassName": "",
"headerStyle": Object {},
"minWidth": 100,
"resizable": undefined,
"show": true,
"sortMethod": undefined,
"sortable": undefined,
"style": Object {},
}
}
columns={
Array [
Object {
"Aggregated": [Function],
"Header": "Server",
"accessor": "server",
"show": true,
"width": 300,
},
Object {
"Cell": [Function],
"Header": "Tier",
"accessor": "tier",
"show": true,
},
Object {
"Aggregated": [Function],
"Cell": [Function],
"Header": "Curr size",
"accessor": "curr_size",
"filterable": false,
"id": "curr_size",
"show": true,
"width": 100,
},
Object {
"Aggregated": [Function],
"Cell": [Function],
"Header": "Max size",
"accessor": "max_size",
"filterable": false,
"id": "max_size",
"show": true,
"width": 100,
},
Object {
"Aggregated": [Function],
"Cell": [Function],
"Header": "Usage",
"accessor": [Function],
"filterable": false,
"id": "usage",
"show": true,
"width": 100,
},
Object {
"Aggregated": [Function],
"Cell": [Function],
"Header": "Load/drop queues",
"accessor": [Function],
"filterable": false,
"id": "queue",
"show": true,
"width": 400,
},
Object {
"Aggregated": [Function],
"Header": "Host",
"accessor": "host",
"show": true,
},
Object {
"Aggregated": [Function],
"Header": "Port",
"accessor": [Function],
"id": "port",
"show": true,
},
]
}
data={Array []}
defaultExpanded={Object {}}
defaultFilterMethod={[Function]}
defaultFiltered={Array []}
defaultPageSize={10}
defaultResized={Array []}
defaultSortDesc={false}
defaultSortMethod={[Function]}
defaultSorted={Array []}
expanderDefaults={
Object {
"filterable": false,
"resizable": false,
"sortable": false,
"width": 35,
}
}
filterable={true}
filtered={Array []}
freezeWhenExpanded={false}
getLoadingProps={[Function]}
getNoDataProps={[Function]}
getPaginationProps={[Function]}
getProps={[Function]}
getResizerProps={[Function]}
getTableProps={[Function]}
getTbodyProps={[Function]}
getTdProps={[Function]}
getTfootProps={[Function]}
getTfootTdProps={[Function]}
getTfootTrProps={[Function]}
getTheadFilterProps={[Function]}
getTheadFilterThProps={[Function]}
getTheadFilterTrProps={[Function]}
getTheadGroupProps={[Function]}
getTheadGroupThProps={[Function]}
getTheadGroupTrProps={[Function]}
getTheadProps={[Function]}
getTheadThProps={[Function]}
getTheadTrProps={[Function]}
getTrGroupProps={[Function]}
getTrProps={[Function]}
groupedByPivotKey="_groupedByPivot"
indexKey="_index"
loading={true}
loadingText="Loading..."
multiSort={true}
nestingLevelKey="_nestingLevel"
nextText="Next"
noDataText=""
ofText="of"
onFetchData={[Function]}
onFilteredChange={[Function]}
originalKey="_original"
pageSizeOptions={
Array [
5,
10,
20,
25,
50,
100,
]
}
pageText="Page"
pivotBy={Array []}
pivotDefaults={Object {}}
pivotIDKey="_pivotID"
pivotValKey="_pivotVal"
previousText="Previous"
resizable={true}
resolveData={[Function]}
rowsText="rows"
showPageJump={true}
showPageSizeOptions={true}
showPagination={true}
showPaginationBottom={true}
showPaginationTop={false}
sortable={true}
style={Object {}}
subRowsKey="_subRows"
/>
<div
className="control-separator"
/>
<ViewControlBar
label="MiddleManagers"
>
<Blueprint3.Button
icon="refresh"
onClick={[Function]}
text="Refresh"
/>
<TableColumnSelection
columns={
Array [
"Host",
"Usage",
"Availability groups",
"Last completed task time",
"Blacklisted until",
] ]
} }
onChange={[Function]} onChange={[Function]}
@ -325,37 +96,98 @@ exports[`describe segments-view segments view snapshot 1`] = `
} }
columns={ columns={
Array [ Array [
Object {
"Header": "Segment ID",
"accessor": "segment_id",
"show": true,
"width": 300,
},
Object { Object {
"Cell": [Function], "Cell": [Function],
"Header": "Host", "Header": "Datasource",
"accessor": [Function], "accessor": "datasource",
"id": "host",
"show": true, "show": true,
}, },
Object { Object {
"Header": "Usage", "Cell": [Function],
"accessor": [Function], "Header": "Start",
"accessor": "start",
"defaultSortDesc": true,
"show": true,
"width": 120,
},
Object {
"Cell": [Function],
"Header": "End",
"accessor": "end",
"defaultSortDesc": true,
"show": true,
"width": 120,
},
Object {
"Header": "Version",
"accessor": "version",
"defaultSortDesc": true,
"show": true,
"width": 120,
},
Object {
"Header": "Partition",
"accessor": "partition_num",
"filterable": false, "filterable": false,
"id": "usage",
"show": true, "show": true,
"width": 60, "width": 60,
}, },
Object { Object {
"Header": "Availability groups", "Cell": [Function],
"accessor": [Function], "Header": "Size",
"accessor": "size",
"defaultSortDesc": true,
"filterable": false,
"show": true,
},
Object {
"Cell": [Function],
"Header": "Num rows",
"accessor": "num_rows",
"defaultSortDesc": true,
"filterable": false,
"show": true,
},
Object {
"Header": "Replicas",
"accessor": "num_replicas",
"defaultSortDesc": true,
"filterable": false, "filterable": false,
"id": "availabilityGroups",
"show": true, "show": true,
"width": 60, "width": 60,
}, },
Object { Object {
"Header": "Last completed task time", "Filter": [Function],
"accessor": "lastCompletedTaskTime", "Header": "Is published",
"accessor": [Function],
"id": "is_published",
"show": true, "show": true,
}, },
Object { Object {
"Header": "Blacklisted until", "Filter": [Function],
"accessor": "blacklistedUntil", "Header": "Is realtime",
"accessor": [Function],
"id": "is_realtime",
"show": true,
},
Object {
"Filter": [Function],
"Header": "Is available",
"accessor": [Function],
"id": "is_available",
"show": true,
},
Object {
"Filter": [Function],
"Header": "Is overshadowed",
"accessor": [Function],
"id": "is_overshadowed",
"show": true, "show": true,
}, },
] ]
@ -364,11 +196,18 @@ exports[`describe segments-view segments view snapshot 1`] = `
defaultExpanded={Object {}} defaultExpanded={Object {}}
defaultFilterMethod={[Function]} defaultFilterMethod={[Function]}
defaultFiltered={Array []} defaultFiltered={Array []}
defaultPageSize={10} defaultPageSize={50}
defaultResized={Array []} defaultResized={Array []}
defaultSortDesc={false} defaultSortDesc={false}
defaultSortMethod={[Function]} defaultSortMethod={[Function]}
defaultSorted={Array []} defaultSorted={
Array [
Object {
"desc": true,
"id": "start",
},
]
}
expanderDefaults={ expanderDefaults={
Object { Object {
"filterable": false, "filterable": false,
@ -381,7 +220,7 @@ exports[`describe segments-view segments view snapshot 1`] = `
filtered={ filtered={
Array [ Array [
Object { Object {
"id": "host", "id": "datasource",
"value": "test", "value": "test",
}, },
] ]
@ -413,11 +252,12 @@ exports[`describe segments-view segments view snapshot 1`] = `
indexKey="_index" indexKey="_index"
loading={true} loading={true}
loadingText="Loading..." loadingText="Loading..."
manual={true}
multiSort={true} multiSort={true}
nestingLevelKey="_nestingLevel" nestingLevelKey="_nestingLevel"
nextText="Next" nextText="Next"
noDataText="" noDataText=""
ofText="of" ofText=""
onFetchData={[Function]} onFetchData={[Function]}
onFilteredChange={[Function]} onFilteredChange={[Function]}
originalKey="_original" originalKey="_original"
@ -432,6 +272,7 @@ exports[`describe segments-view segments view snapshot 1`] = `
] ]
} }
pageText="Page" pageText="Page"
pages={10000000}
pivotDefaults={Object {}} pivotDefaults={Object {}}
pivotIDKey="_pivotID" pivotIDKey="_pivotID"
pivotValKey="_pivotVal" pivotValKey="_pivotVal"
@ -439,7 +280,7 @@ exports[`describe segments-view segments view snapshot 1`] = `
resizable={true} resizable={true}
resolveData={[Function]} resolveData={[Function]}
rowsText="rows" rowsText="rows"
showPageJump={true} showPageJump={false}
showPageSizeOptions={true} showPageSizeOptions={true}
showPagination={true} showPagination={true}
showPaginationBottom={true} showPaginationBottom={true}

View File

@ -22,16 +22,16 @@ import { shallow } from 'enzyme';
import * as enzymeAdapterReact16 from 'enzyme-adapter-react-16'; import * as enzymeAdapterReact16 from 'enzyme-adapter-react-16';
import * as React from 'react'; import * as React from 'react';
import {ServersView} from '../servers-view/servers-view'; import {SegmentsView} from '../segments-view/segments-view';
Enzyme.configure({ adapter: new enzymeAdapterReact16() }); Enzyme.configure({ adapter: new enzymeAdapterReact16() });
describe('describe segments-view', () => { describe('describe segments-view', () => {
it('segments view snapshot', () => { it('segments view snapshot', () => {
const segmentsView = shallow( const segmentsView = shallow(
<ServersView <SegmentsView
middleManager={'test'} datasource={'test'}
onlyUnavailable={false}
goToSql={(initSql: string) => {}} goToSql={(initSql: string) => {}}
goToTask={(taskId: string) => {}}
noSqlMode={false} noSqlMode={false}
/>); />);
expect(segmentsView).toMatchSnapshot(); expect(segmentsView).toMatchSnapshot();

View File

@ -263,6 +263,8 @@ exports[`describe servers view action servers view 1`] = `
"Availability groups", "Availability groups",
"Last completed task time", "Last completed task time",
"Blacklisted until", "Blacklisted until",
"Status",
"Actions",
] ]
} }
onChange={[Function]} onChange={[Function]}
@ -358,6 +360,21 @@ exports[`describe servers view action servers view 1`] = `
"accessor": "blacklistedUntil", "accessor": "blacklistedUntil",
"show": true, "show": true,
}, },
Object {
"Header": "Status",
"accessor": [Function],
"id": "status",
"show": true,
},
Object {
"Cell": [Function],
"Header": "Actions",
"accessor": [Function],
"filterable": false,
"id": "actions",
"show": true,
"width": 70,
},
] ]
} }
data={Array []} data={Array []}
@ -448,5 +465,29 @@ exports[`describe servers view action servers view 1`] = `
style={Object {}} style={Object {}}
subRowsKey="_subRows" subRowsKey="_subRows"
/> />
<AsyncActionDialog
action={null}
confirmButtonText="Disable worker"
failText="Could not disable worker"
intent="danger"
onClose={[Function]}
successText="Worker has been disabled"
>
<p>
Are you sure you want to disable worker 'null'?
</p>
</AsyncActionDialog>
<AsyncActionDialog
action={null}
confirmButtonText="Enable worker"
failText="Could not enable worker"
intent="primary"
onClose={[Function]}
successText="Worker has been enabled"
>
<p>
Are you sure you want to enable worker 'null'?
</p>
</AsyncActionDialog>
</div> </div>
`; `;

View File

@ -16,7 +16,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { Button, Switch } from '@blueprintjs/core'; import { Button, Icon, Intent, Popover, Position, Switch } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons'; import { IconNames } from '@blueprintjs/icons';
import axios from 'axios'; import axios from 'axios';
import { sum } from 'd3-array'; import { sum } from 'd3-array';
@ -24,7 +24,12 @@ import * as React from 'react';
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import { Filter } from 'react-table'; import { Filter } from 'react-table';
import { TableColumnSelection, ViewControlBar } from '../../components/index'; import {
ActionCell,
TableColumnSelection,
ViewControlBar
} from '../../components/index';
import { AsyncActionDialog } from '../../dialogs/index';
import { import {
addFilter, addFilter,
formatBytes, formatBytes,
@ -32,11 +37,12 @@ import {
queryDruidSql, queryDruidSql,
QueryManager, TableColumnSelectionHandler QueryManager, TableColumnSelectionHandler
} from '../../utils'; } from '../../utils';
import { BasicAction, basicActionsToMenu } from '../../utils/basic-action';
import './servers-view.scss'; import './servers-view.scss';
const serverTableColumns: string[] = ['Server', 'Tier', 'Curr size', 'Max size', 'Usage', 'Load/drop queues', 'Host', 'Port']; const serverTableColumns: string[] = ['Server', 'Tier', 'Curr size', 'Max size', 'Usage', 'Load/drop queues', 'Host', 'Port'];
const middleManagerTableColumns: string[] = ['Host', 'Usage', 'Availability groups', 'Last completed task time', 'Blacklisted until']; const middleManagerTableColumns: string[] = ['Host', 'Usage', 'Availability groups', 'Last completed task time', 'Blacklisted until', 'Status', 'Actions'];
function formatQueues(segmentsToLoad: number, segmentsToLoadSize: number, segmentsToDrop: number, segmentsToDropSize: number): string { function formatQueues(segmentsToLoad: number, segmentsToLoadSize: number, segmentsToDrop: number, segmentsToDropSize: number): string {
const queueParts: string[] = []; const queueParts: string[] = [];
@ -67,6 +73,9 @@ export interface ServersViewState {
middleManagers: any[] | null; middleManagers: any[] | null;
middleManagersError: string | null; middleManagersError: string | null;
middleManagerFilter: Filter[]; middleManagerFilter: Filter[];
middleManagerDisableWorkerHost: string | null;
middleManagerEnableWorkerHost: string | null;
} }
interface ServerQueryResultRow { interface ServerQueryResultRow {
@ -110,7 +119,10 @@ export class ServersView extends React.Component<ServersViewProps, ServersViewSt
middleManagersLoading: true, middleManagersLoading: true,
middleManagers: null, middleManagers: null,
middleManagersError: null, middleManagersError: null,
middleManagerFilter: props.middleManager ? [{ id: 'host', value: props.middleManager }] : [] middleManagerFilter: props.middleManager ? [{ id: 'host', value: props.middleManager }] : [],
middleManagerDisableWorkerHost: null,
middleManagerEnableWorkerHost: null
}; };
this.serverTableColumnSelectionHandler = new TableColumnSelectionHandler( this.serverTableColumnSelectionHandler = new TableColumnSelectionHandler(
@ -345,7 +357,8 @@ WHERE "server_type" = 'historical'`);
const { middleManagers, middleManagersLoading, middleManagersError, middleManagerFilter } = this.state; const { middleManagers, middleManagersLoading, middleManagersError, middleManagerFilter } = this.state;
const { middleManagerTableColumnSelectionHandler } = this; const { middleManagerTableColumnSelectionHandler } = this;
return <ReactTable return <>
<ReactTable
data={middleManagers || []} data={middleManagers || []}
loading={middleManagersLoading} loading={middleManagersLoading}
noDataText={!middleManagersLoading && middleManagers && !middleManagers.length ? 'No MiddleManagers' : (middleManagersError || '')} noDataText={!middleManagersLoading && middleManagers && !middleManagers.length ? 'No MiddleManagers' : (middleManagersError || '')}
@ -390,6 +403,34 @@ WHERE "server_type" = 'historical'`);
Header: 'Blacklisted until', Header: 'Blacklisted until',
accessor: 'blacklistedUntil', accessor: 'blacklistedUntil',
show: middleManagerTableColumnSelectionHandler.showColumn('Blacklisted until') show: middleManagerTableColumnSelectionHandler.showColumn('Blacklisted until')
},
{
Header: 'Status',
id: 'status',
accessor: (row) => row.worker.version === '' ? 'Disabled' : 'Enabled',
show: middleManagerTableColumnSelectionHandler.showColumn('Status')
},
{
Header: 'Actions',
id: 'actions',
width: 70,
accessor: (row) => row.worker,
filterable: false,
Cell: row => {
const disabled = row.value.version === '';
const workerActions = this.getWorkerActions(row.value.host, disabled);
const workerMenu = basicActionsToMenu(workerActions);
return <ActionCell>
{
workerMenu &&
<Popover content={workerMenu} position={Position.BOTTOM_RIGHT}>
<Icon icon={IconNames.WRENCH}/>
</Popover>
}
</ActionCell>;
},
show: middleManagerTableColumnSelectionHandler.showColumn('Actions')
} }
]} ]}
defaultPageSize={10} defaultPageSize={10}
@ -407,7 +448,80 @@ WHERE "server_type" = 'historical'`);
} }
</div>; </div>;
}} }}
/>; />
{this.renderDisableWorkerAction()}
{this.renderEnableWorkerAction()}
</>;
}
private getWorkerActions(workerHost: string, disabled: boolean): BasicAction[] {
if (disabled) {
return [
{
icon: IconNames.TICK,
title: 'Enable',
onAction: () => this.setState({ middleManagerEnableWorkerHost: workerHost })
}
];
} else {
return [
{
icon: IconNames.DISABLE,
title: 'Disable',
onAction: () => this.setState({ middleManagerDisableWorkerHost: workerHost })
}
];
}
}
renderDisableWorkerAction() {
const { middleManagerDisableWorkerHost } = this.state;
return <AsyncActionDialog
action={
middleManagerDisableWorkerHost ? async () => {
const resp = await axios.post(`/druid/indexer/v1/worker/${middleManagerDisableWorkerHost}/disable`, {});
return resp.data;
} : null
}
confirmButtonText="Disable worker"
successText="Worker has been disabled"
failText="Could not disable worker"
intent={Intent.DANGER}
onClose={(success) => {
this.setState({ middleManagerDisableWorkerHost: null });
if (success) this.middleManagerQueryManager.rerunLastQuery();
}}
>
<p>
{`Are you sure you want to disable worker '${middleManagerDisableWorkerHost}'?`}
</p>
</AsyncActionDialog>;
}
renderEnableWorkerAction() {
const { middleManagerEnableWorkerHost } = this.state;
return <AsyncActionDialog
action={
middleManagerEnableWorkerHost ? async () => {
const resp = await axios.post(`/druid/indexer/v1/worker/${middleManagerEnableWorkerHost}/enable`, {});
return resp.data;
} : null
}
confirmButtonText="Enable worker"
successText="Worker has been enabled"
failText="Could not enable worker"
intent={Intent.PRIMARY}
onClose={(success) => {
this.setState({ middleManagerEnableWorkerHost: null });
if (success) this.middleManagerQueryManager.rerunLastQuery();
}}
>
<p>
{`Are you sure you want to enable worker '${middleManagerEnableWorkerHost}'?`}
</p>
</AsyncActionDialog>;
} }
render() { render() {