Add REST endpoint to list tasks

GET /config/server/tasks/ lists the task from the background work
queues.

The implementation of the SSH show-queue command was adapted so that
is uses the new REST endpoint to get the information about the tasks.
This avoids code duplication.

Change-Id: Iad0a94e8ed1395033aa7dbfe5fae0557601c3526
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
This commit is contained in:
Edwin Kempin 2014-06-10 22:55:13 +02:00
parent d00285e4ab
commit 49098b8717
6 changed files with 338 additions and 168 deletions

View File

@ -632,6 +632,65 @@ The entries in the map are sorted by capability ID.
}
----
[[list-tasks]]
=== List Tasks
--
'GET /config/server/tasks/'
--
Lists the tasks from the background work queues that the Gerrit daemon
is currently performing, or will perform in the near future.
Gerrit contains an internal scheduler, similar to cron, that it uses to
queue and dispatch both short and long term tasks.
Tasks that are completed or canceled exit the queue very quickly once
they enter this state, but it can be possible to observe tasks in these
states.
End-users may see a task only if they can also see the project the task
is associated with. Tasks operating on other projects, or that do not
have a specific project, are hidden.
Members of a group that is granted the
link:access-control.html#capability_viewQueue[View Queue] capability or
the link:access-control.html#capability_administrateServer[Administrate
Server] capability can see all tasks.
As result a list of link:#task-info[TaskInfo] entities is returned.
The entries in the list are sorted by task state, remaining delay and
command.
.Request
----
GET /config/server/tasks/ HTTP/1.0
----
.Response
----
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
)]}'
[
{
"id": "1e688bea",
"state": "SLEEPING",
"start_time": "2014-06-11 12:58:51.991000000",
"delay": 3453,
"command": "Reload Submit Queue"
},
{
"id": "3e6d4ffa",
"state": "SLEEPING",
"start_time": "2014-06-11 12:58:51.508000000",
"delay": 3287966,
"command": "Log File Compressor"
}
]
----
[[get-top-menus]]
=== Get Top Menus
--
@ -844,6 +903,27 @@ Summary about the JVM link:#jvm-summary-info[JvmSummaryInfo] entity.
Only set if the `jvm` option was set.
|============================
[[task-info]]
=== TaskInfo
The `TaskInfo` entity contains information about a task in a background
work queue.
[options="header",width="50%",cols="1,^1,5"]
|====================================
|Field Name ||Description
|`id` ||The ID of the task.
|`state` ||
The state of the task, can be `DONE`, `CANCELLED`, `RUNNING`, `READY`,
`SLEEPING` and `OTHER`.
|`start_time` ||The start time of the task.
|`delay` ||The remaining delay of the task.
|`command` ||The command of the task.
|`remote_name`|optional|
The remote name. May only be set for tasks that are associated with a
project.
|`project` |optional|The project the task is associated with.
|====================================
[[task-summary-info]]
=== TaskSummaryInfo
The `TaskSummaryInfo` entity contains information about the current

View File

@ -0,0 +1,132 @@
// Copyright (C) 2014 The Android Open Source Project
//
// Licensed 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 com.google.gerrit.server.config;
import com.google.common.collect.ComparisonChain;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.git.TaskInfoFactory;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.git.WorkQueue.ProjectTask;
import com.google.gerrit.server.git.WorkQueue.Task;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.util.IdGenerator;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Singleton
public class ListTasks implements RestReadView<ConfigResource> {
private final WorkQueue workQueue;
private final ProjectCache projectCache;
private final Provider<IdentifiedUser> self;
@Inject
public ListTasks(WorkQueue workQueue, ProjectCache projectCache,
Provider<IdentifiedUser> self) {
this.workQueue = workQueue;
this.projectCache = projectCache;
this.self = self;
}
@Override
public List<TaskInfo> apply(ConfigResource resource) throws AuthException {
CurrentUser user = self.get();
if (!user.isIdentifiedUser()) {
throw new AuthException("Authentication required");
}
List<TaskInfo> allTasks = getTasks();
if (user.getCapabilities().canViewQueue()) {
return allTasks;
} else {
Map<String, Boolean> visibilityCache = new HashMap<>();
List<TaskInfo> visibleTasks = new ArrayList<>();
for (TaskInfo task : allTasks) {
if (task.projectName != null) {
Boolean visible = visibilityCache.get(task.projectName);
if (visible == null) {
ProjectState e = projectCache.get(new Project.NameKey(task.projectName));
visible = e != null ? e.controlFor(user).isVisible() : false;
visibilityCache.put(task.projectName, visible);
}
if (visible) {
visibleTasks.add(task);
}
}
}
return visibleTasks;
}
}
private List<TaskInfo> getTasks() {
List<TaskInfo> taskInfos =
workQueue.getTaskInfos(new TaskInfoFactory<TaskInfo>() {
@Override
public TaskInfo getTaskInfo(Task<?> task) {
return new TaskInfo(task);
}
});
Collections.sort(taskInfos, new Comparator<TaskInfo>() {
@Override
public int compare(TaskInfo a, TaskInfo b) {
return ComparisonChain.start()
.compare(a.state.ordinal(), b.state.ordinal())
.compare(a.delay, b.delay)
.compare(a.command, b.command)
.result();
}
});
return taskInfos;
}
public static class TaskInfo {
public String id;
public Task.State state;
public Timestamp startTime;
public long delay;
public String command;
public String remoteName;
public String projectName;
public TaskInfo(Task<?> task) {
this.id = IdGenerator.format(task.getTaskId());
this.state = task.getState();
this.startTime = new Timestamp(task.getStartTime().getTime());
this.delay = task.getDelay(TimeUnit.MILLISECONDS);
this.command = task.toString();
if (task instanceof ProjectTask) {
ProjectTask<?> projectTask = ((ProjectTask<?>) task);
this.projectName = projectTask.getProjectNameKey().get();
this.remoteName = projectTask.getRemoteName();
}
}
}
}

View File

@ -16,6 +16,7 @@ package com.google.gerrit.server.config;
import static com.google.gerrit.server.config.CapabilityResource.CAPABILITY_KIND;
import static com.google.gerrit.server.config.ConfigResource.CONFIG_KIND;
import static com.google.gerrit.server.config.TaskResource.TASK_KIND;
import static com.google.gerrit.server.config.TopMenuResource.TOP_MENU_KIND;
import com.google.gerrit.extensions.registration.DynamicMap;
@ -26,8 +27,10 @@ public class Module extends RestApiModule {
protected void configure() {
DynamicMap.mapOf(binder(), CAPABILITY_KIND);
DynamicMap.mapOf(binder(), CONFIG_KIND);
DynamicMap.mapOf(binder(), TASK_KIND);
DynamicMap.mapOf(binder(), TOP_MENU_KIND);
child(CONFIG_KIND, "capabilities").to(CapabilitiesCollection.class);
child(CONFIG_KIND, "tasks").to(TasksCollection.class);
child(CONFIG_KIND, "top-menus").to(TopMenuCollection.class);
get(CONFIG_KIND, "version").to(GetVersion.class);
get(CONFIG_KIND, "preferences").to(GetPreferences.class);

View File

@ -0,0 +1,23 @@
// Copyright (C) 2014 The Android Open Source Project
//
// Licensed 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 com.google.gerrit.server.config;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.inject.TypeLiteral;
public class TaskResource extends ConfigResource {
public static final TypeLiteral<RestView<TaskResource>> TASK_KIND =
new TypeLiteral<RestView<TaskResource>>() {};
}

View File

@ -0,0 +1,53 @@
// Copyright (C) 2014 The Android Open Source Project
//
// Licensed 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 com.google.gerrit.server.config;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@Singleton
public class TasksCollection implements
ChildCollection<ConfigResource, TaskResource> {
private final DynamicMap<RestView<TaskResource>> views;
private final ListTasks list;
@Inject
TasksCollection(DynamicMap<RestView<TaskResource>> views,
ListTasks list) {
this.views = views;
this.list = list;
}
@Override
public RestView<ConfigResource> list() {
return list;
}
@Override
public TaskResource parse(ConfigResource parent, IdString id)
throws ResourceNotFoundException {
throw new ResourceNotFoundException(id);
}
@Override
public DynamicMap<RestView<TaskResource>> views() {
return views;
}
}

View File

@ -17,15 +17,12 @@ package com.google.gerrit.sshd.commands;
import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
import com.google.common.base.Objects;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.git.TaskInfoFactory;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.git.WorkQueue.ProjectTask;
import com.google.gerrit.server.config.ConfigResource;
import com.google.gerrit.server.config.ListTasks;
import com.google.gerrit.server.config.ListTasks.TaskInfo;
import com.google.gerrit.server.git.WorkQueue.Task;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.util.IdGenerator;
import com.google.gerrit.server.util.TimeUtil;
import com.google.gerrit.sshd.AdminHighPriorityCommand;
import com.google.gerrit.sshd.CommandMetaData;
@ -37,11 +34,8 @@ import org.kohsuke.args4j.Option;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
/** Display the current work queue. */
@AdminHighPriorityCommand
@ -52,10 +46,7 @@ final class ShowQueue extends SshCommand {
private boolean wide;
@Inject
private WorkQueue workQueue;
@Inject
private ProjectCache projectCache;
private ListTasks listTasks;
@Inject
private IdentifiedUser currentUser;
@ -64,7 +55,7 @@ final class ShowQueue extends SshCommand {
private int taskNameWidth;
@Override
public void start(final Environment env) throws IOException {
public void start(Environment env) throws IOException {
String s = env.getEnv().get(Environment.ENV_COLUMNS);
if (s != null && !s.isEmpty()) {
try {
@ -77,125 +68,59 @@ final class ShowQueue extends SshCommand {
}
@Override
protected void run() {
protected void run() throws UnloggedFailure {
taskNameWidth = wide ? Integer.MAX_VALUE : columns - 8 - 12 - 12 - 4 - 4;
final List<QueueTaskInfo> pending = getSortedTaskInfoList();
stdout.print(String.format("%-8s %-12s %-12s %-4s %s\n", //
"Task", "State", "StartTime", "", "Command"));
stdout.print("----------------------------------------------"
+ "--------------------------------\n");
int numberOfPendingTasks = 0;
final long now = TimeUtil.nowMs();
final boolean viewAll = currentUser.getCapabilities().canViewQueue();
for (final QueueTaskInfo taskInfo : pending) {
final long delay = taskInfo.delayMillis;
final Task.State state = taskInfo.state;
final String start;
switch (state) {
case DONE:
case CANCELLED:
case RUNNING:
case READY:
start = format(state);
break;
default:
start = time(now, delay);
break;
}
boolean regularUserCanSee = false;
boolean hasCustomizedPrint = true;
// If the user is not administrator, check if has rights to see
// the Task
Project.NameKey projectName = null;
String remoteName = null;
if (!viewAll) {
projectName = taskInfo.getProjectNameKey();
remoteName = taskInfo.getRemoteName();
hasCustomizedPrint = taskInfo.hasCustomizedPrint();
ProjectState e = null;
if (projectName != null) {
e = projectCache.get(projectName);
try {
List<TaskInfo> tasks = listTasks.apply(new ConfigResource());
long now = TimeUtil.nowMs();
boolean viewAll = currentUser.getCapabilities().canViewQueue();
for (TaskInfo task : tasks) {
String start;
switch (task.state) {
case DONE:
case CANCELLED:
case RUNNING:
case READY:
start = format(task.state);
break;
default:
start = time(now, task.delay);
break;
}
regularUserCanSee = e != null && e.controlFor(currentUser).isVisible();
// Shows information about tasks depending on the user rights
if (viewAll || task.projectName == null) {
String command = task.command.length() < taskNameWidth
? task.command
: task.command.substring(0, taskNameWidth);
if (regularUserCanSee) {
numberOfPendingTasks++;
stdout.print(String.format("%8s %-12s %-12s %-4s %s\n",
task.id, start, startTime(task.startTime), "", command));
} else {
String remoteName = task.remoteName != null
? task.remoteName + "/" + task.projectName
: task.projectName;
stdout.print(String.format("%8s %-12s %-4s %s\n",
task.id, start, startTime(task.startTime),
Objects.firstNonNull(remoteName, "n/a")));
}
}
String startTime = startTime(taskInfo.getStartTime());
// Shows information about tasks depending on the user rights
if (viewAll || (!hasCustomizedPrint && regularUserCanSee)) {
stdout.print(String.format("%8s %-12s %-12s %-4s %s\n", //
id(taskInfo.getTaskId()), start, startTime, "",
taskInfo.getTaskString(taskNameWidth)));
} else if (regularUserCanSee) {
if (projectName != null) {
if (remoteName == null) {
remoteName = projectName.get();
} else {
remoteName = remoteName + "/" + projectName.get();
}
}
stdout.print(String.format("%8s %-12s %-4s %s\n",
id(taskInfo.getTaskId()), start, startTime,
Objects.firstNonNull(remoteName, "n/a")));
}
stdout.print("----------------------------------------------"
+ "--------------------------------\n");
stdout.print(" " + tasks.size() + " tasks\n");
} catch (AuthException e) {
throw die(e);
}
stdout.print("----------------------------------------------"
+ "--------------------------------\n");
if (viewAll) {
numberOfPendingTasks = pending.size();
}
stdout.print(" " + numberOfPendingTasks + " tasks\n");
}
private List<QueueTaskInfo> getSortedTaskInfoList() {
final List<QueueTaskInfo> taskInfos =
workQueue.getTaskInfos(new TaskInfoFactory<QueueTaskInfo>() {
@Override
public QueueTaskInfo getTaskInfo(Task<?> task) {
return new QueueTaskInfo(task);
}
});
Collections.sort(taskInfos, new Comparator<QueueTaskInfo>() {
@Override
public int compare(QueueTaskInfo a, QueueTaskInfo b) {
if (a.state != b.state) {
return a.state.ordinal() - b.state.ordinal();
}
int cmp = Long.signum(a.delayMillis - b.delayMillis);
if (cmp != 0) {
return cmp;
}
return a.getTaskString(taskNameWidth)
.compareTo(b.getTaskString(taskNameWidth));
}
});
return taskInfos;
}
private static String id(final int id) {
return IdGenerator.format(id);
}
private static String time(final long now, final long delay) {
final Date when = new Date(now + delay);
private static String time(long now, long delay) {
Date when = new Date(now + delay);
return format(when, delay);
}
@ -203,14 +128,14 @@ final class ShowQueue extends SshCommand {
return format(when, TimeUtil.nowMs() - when.getTime());
}
private static String format(final Date when, final long timeFromNow) {
private static String format(Date when, long timeFromNow) {
if (timeFromNow < 24 * 60 * 60 * 1000L) {
return new SimpleDateFormat("HH:mm:ss.SSS").format(when);
}
return new SimpleDateFormat("MMM-dd HH:mm").format(when);
}
private static String format(final Task.State state) {
private static String format(Task.State state) {
switch (state) {
case DONE:
return "....... done";
@ -226,50 +151,4 @@ final class ShowQueue extends SshCommand {
return state.toString();
}
}
private static class QueueTaskInfo {
private final long delayMillis;
private final Task.State state;
private final Task<?> task;
QueueTaskInfo(Task<?> task) {
this.task = task;
this.delayMillis = task.getDelay(TimeUnit.MILLISECONDS);
this.state = task.getState();
}
String getRemoteName() {
if (task instanceof ProjectTask) {
return ((ProjectTask<?>) task).getRemoteName();
}
return null;
}
Project.NameKey getProjectNameKey() {
if (task instanceof ProjectTask<?>) {
return ((ProjectTask<?>) task).getProjectNameKey();
}
return null;
}
boolean hasCustomizedPrint() {
if (task instanceof ProjectTask<?>) {
return ((ProjectTask<?>) task).hasCustomizedPrint();
}
return false;
}
int getTaskId() {
return task.getTaskId();
}
Date getStartTime() {
return task.getStartTime();
}
String getTaskString(int maxLength) {
String s = task.toString();
return s.length() < maxLength ? s : s.substring(0, maxLength);
}
}
}