/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.ce.queue;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableMap;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.sonar.api.ce.ComputeEngineSide;
import org.sonar.core.util.UuidFactory;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.ce.CeActivityDto;
import org.sonar.db.ce.CeQueueDto;
import org.sonar.db.component.ComponentDto;
import org.sonar.server.organization.DefaultOrganizationProvider;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Predicates.notNull;
import static com.google.common.collect.FluentIterable.from;
import static java.util.Collections.singleton;
import static java.util.Objects.requireNonNull;
@ComputeEngineSide
public class CeQueueImpl implements CeQueue {
private final DbClient dbClient;
private final UuidFactory uuidFactory;
private final DefaultOrganizationProvider defaultOrganizationProvider;
// state
private AtomicBoolean submitPaused = new AtomicBoolean(false);
public CeQueueImpl(DbClient dbClient, UuidFactory uuidFactory, DefaultOrganizationProvider defaultOrganizationProvider) {
this.dbClient = dbClient;
this.uuidFactory = uuidFactory;
this.defaultOrganizationProvider = defaultOrganizationProvider;
}
@Override
public CeTaskSubmit.Builder prepareSubmit() {
return new CeTaskSubmit.Builder(uuidFactory.create());
}
@Override
public CeTask submit(CeTaskSubmit submission) {
checkState(!submitPaused.get(), "Compute Engine does not currently accept new tasks");
try (DbSession dbSession = dbClient.openSession(false)) {
CeQueueDto dto = new CeTaskSubmitToInsertedCeQueueDto(dbSession, dbClient).apply(submission);
CeTask task = loadTask(dbSession, dto);
dbSession.commit();
return task;
}
}
@Override
public List<CeTask> massSubmit(Collection<CeTaskSubmit> submissions) {
checkState(!submitPaused.get(), "Compute Engine does not currently accept new tasks");
if (submissions.isEmpty()) {
return Collections.emptyList();
}
try (DbSession dbSession = dbClient.openSession(true)) {
List<CeQueueDto> ceQueueDtos = from(submissions)
.transform(new CeTaskSubmitToInsertedCeQueueDto(dbSession, dbClient))
.toList();
List<CeTask> tasks = loadTasks(dbSession, ceQueueDtos);
dbSession.commit();
return tasks;
}
}
protected CeTask loadTask(DbSession dbSession, CeQueueDto dto) {
if (dto.getComponentUuid() == null) {
return new CeQueueDtoToCeTask(defaultOrganizationProvider.get().getUuid()).apply(dto);
}
com.google.common.base.Optional<ComponentDto> componentDto = dbClient.componentDao().selectByUuid(dbSession, dto.getComponentUuid());
if (componentDto.isPresent()) {
return new CeQueueDtoToCeTask(defaultOrganizationProvider.get().getUuid(), ImmutableMap.of(dto.getComponentUuid(), componentDto.get())).apply(dto);
}
return new CeQueueDtoToCeTask(defaultOrganizationProvider.get().getUuid()).apply(dto);
}
private List<CeTask> loadTasks(DbSession dbSession, List<CeQueueDto> dtos) {
Set<String> componentUuids = from(dtos)
.transform(CeQueueDtoToComponentUuid.INSTANCE)
.filter(notNull())
.toSet();
Map<String, ComponentDto> componentDtoByUuid = from(dbClient.componentDao()
.selectByUuids(dbSession, componentUuids))
.uniqueIndex(ComponentDto::uuid);
return from(dtos)
.transform(new CeQueueDtoToCeTask(defaultOrganizationProvider.get().getUuid(), componentDtoByUuid))
.toList();
}
@Override
public boolean cancel(String taskUuid) {
try (DbSession dbSession = dbClient.openSession(false)) {
Optional<CeQueueDto> queueDto = dbClient.ceQueueDao().selectByUuid(dbSession, taskUuid);
if (queueDto.isPresent()) {
checkState(CeQueueDto.Status.PENDING.equals(queueDto.get().getStatus()), "Task is in progress and can't be canceled [uuid=%s]", taskUuid);
cancelImpl(dbSession, queueDto.get());
return true;
}
return false;
}
}
private void cancelImpl(DbSession dbSession, CeQueueDto q) {
CeActivityDto activityDto = new CeActivityDto(q);
activityDto.setStatus(CeActivityDto.Status.CANCELED);
remove(dbSession, q, activityDto);
}
@Override
public int cancelAll() {
return cancelAll(false);
}
protected int cancelAll(boolean includeInProgress) {
int count = 0;
try (DbSession dbSession = dbClient.openSession(false)) {
for (CeQueueDto queueDto : dbClient.ceQueueDao().selectAllInAscOrder(dbSession)) {
if (includeInProgress || !queueDto.getStatus().equals(CeQueueDto.Status.IN_PROGRESS)) {
cancelImpl(dbSession, queueDto);
count++;
}
}
return count;
}
}
protected void remove(DbSession dbSession, CeQueueDto queueDto, CeActivityDto activityDto) {
dbClient.ceActivityDao().insert(dbSession, activityDto);
dbClient.ceQueueDao().deleteByUuid(dbSession, queueDto.getUuid());
dbClient.ceTaskInputDao().deleteByUuids(dbSession, singleton(queueDto.getUuid()));
dbSession.commit();
}
@Override
public void pauseSubmit() {
this.submitPaused.set(true);
}
@Override
public void resumeSubmit() {
this.submitPaused.set(false);
}
@Override
public boolean isSubmitPaused() {
return submitPaused.get();
}
private static class CeQueueDtoToCeTask implements Function<CeQueueDto, CeTask> {
private final String defaultOrganizationUuid;
private final Map<String, ComponentDto> componentDtoByUuid;
public CeQueueDtoToCeTask(String defaultOrganizationUuid) {
this(defaultOrganizationUuid, Collections.emptyMap());
}
public CeQueueDtoToCeTask(String defaultOrganizationUuid, Map<String, ComponentDto> componentDtoByUuid) {
this.defaultOrganizationUuid = requireNonNull(defaultOrganizationUuid, "defaultOrganizationUuid can't be null");
this.componentDtoByUuid = componentDtoByUuid;
}
@Override
@Nonnull
public CeTask apply(@Nonnull CeQueueDto dto) {
CeTask.Builder builder = new CeTask.Builder();
builder.setUuid(dto.getUuid());
builder.setType(dto.getTaskType());
builder.setSubmitterLogin(dto.getSubmitterLogin());
String componentUuid = dto.getComponentUuid();
if (componentUuid != null) {
builder.setComponentUuid(componentUuid);
ComponentDto component = componentDtoByUuid.get(componentUuid);
if (component != null) {
builder.setOrganizationUuid(component.getOrganizationUuid());
builder.setComponentKey(component.getKey());
builder.setComponentName(component.name());
}
}
// fixme this should be set from the CeQueueDto
if (!builder.hasOrganizationUuid()) {
builder.setOrganizationUuid(defaultOrganizationUuid);
}
return builder.build();
}
}
private static class CeTaskSubmitToInsertedCeQueueDto implements Function<CeTaskSubmit, CeQueueDto> {
private final DbSession dbSession;
private final DbClient dbClient;
public CeTaskSubmitToInsertedCeQueueDto(DbSession dbSession, DbClient dbClient) {
this.dbSession = dbSession;
this.dbClient = dbClient;
}
@Override
@Nonnull
public CeQueueDto apply(@Nonnull CeTaskSubmit submission) {
CeQueueDto dto = new CeQueueDto();
dto.setUuid(submission.getUuid());
dto.setTaskType(submission.getType());
dto.setComponentUuid(submission.getComponentUuid());
dto.setStatus(CeQueueDto.Status.PENDING);
dto.setSubmitterLogin(submission.getSubmitterLogin());
dto.setStartedAt(null);
dbClient.ceQueueDao().insert(dbSession, dto);
return dto;
}
}
private enum CeQueueDtoToComponentUuid implements Function<CeQueueDto, String> {
INSTANCE;
@Override
@Nullable
public String apply(@Nonnull CeQueueDto input) {
return input.getComponentUuid();
}
}
}