Simplified JpaRolloutManagement, moved rollout handling logic to JpaRolloutHandler (#1094)
* introduce RolloutExecutor * removed unused method Signed-off-by: Ahmed Sayed <ahmed.sayed@bosch.io>
This commit is contained in:
@@ -0,0 +1,51 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 Bosch.IO GmbH and others.
|
||||||
|
*
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
*/
|
||||||
|
package org.eclipse.hawkbit.repository;
|
||||||
|
|
||||||
|
import static org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
|
||||||
|
|
||||||
|
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||||
|
import org.eclipse.hawkbit.repository.model.Target;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the handler service for creating, deleting, and starting a Rollout
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface RolloutExecutor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process rollout based on its current {@link Rollout#getStatus()}.
|
||||||
|
*
|
||||||
|
* For {@link RolloutStatus#CREATING} that means creating the
|
||||||
|
* {@link RolloutGroup}s with {@link Target}s and when finished switch to
|
||||||
|
* {@link RolloutStatus#READY}.
|
||||||
|
*
|
||||||
|
* For {@link RolloutStatus#READY} that means switching to
|
||||||
|
* {@link RolloutStatus#STARTING} if the {@link Rollout#getStartAt()} is set
|
||||||
|
* and time of calling this method is beyond this point in time. This auto
|
||||||
|
* start mechanism is optional. Call {@link #start(Long)} otherwise.
|
||||||
|
*
|
||||||
|
* For {@link RolloutStatus#STARTING} that means starting the first
|
||||||
|
* {@link RolloutGroup}s in line and when finished switch to
|
||||||
|
* {@link RolloutStatus#RUNNING}.
|
||||||
|
*
|
||||||
|
* For {@link RolloutStatus#RUNNING} that means checking to activate further
|
||||||
|
* groups based on the defined thresholds. Switched to
|
||||||
|
* {@link RolloutStatus#FINISHED} is all groups are finished.
|
||||||
|
*
|
||||||
|
* For {@link RolloutStatus#DELETING} that means either soft delete in case
|
||||||
|
* rollout was already {@link RolloutStatus#RUNNING} which results in status
|
||||||
|
* change {@link RolloutStatus#DELETED} or hard delete from the persistence
|
||||||
|
* otherwise.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@PreAuthorize(SpringEvalExpressions.IS_SYSTEM_CODE)
|
||||||
|
void execute(Rollout rollout);
|
||||||
|
}
|
||||||
@@ -0,0 +1,660 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 Bosch.IO GmbH and others.
|
||||||
|
*
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
*/
|
||||||
|
package org.eclipse.hawkbit.repository.jpa;
|
||||||
|
|
||||||
|
import static org.springframework.data.domain.Sort.Direction;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
|
||||||
|
import org.eclipse.hawkbit.repository.DeploymentManagement;
|
||||||
|
import org.eclipse.hawkbit.repository.QuotaManagement;
|
||||||
|
import org.eclipse.hawkbit.repository.RolloutApprovalStrategy;
|
||||||
|
import org.eclipse.hawkbit.repository.RolloutGroupManagement;
|
||||||
|
import org.eclipse.hawkbit.repository.RolloutExecutor;
|
||||||
|
import org.eclipse.hawkbit.repository.RolloutHelper;
|
||||||
|
import org.eclipse.hawkbit.repository.RolloutManagement;
|
||||||
|
import org.eclipse.hawkbit.repository.TargetManagement;
|
||||||
|
import org.eclipse.hawkbit.repository.event.remote.RolloutGroupDeletedEvent;
|
||||||
|
import org.eclipse.hawkbit.repository.event.remote.entity.RolloutUpdatedEvent;
|
||||||
|
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||||
|
import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException;
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup;
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.model.RolloutTargetGroup;
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.rollout.condition.RolloutGroupActionEvaluator;
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.rollout.condition.RolloutGroupConditionEvaluator;
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper;
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
|
||||||
|
import org.eclipse.hawkbit.repository.model.Action;
|
||||||
|
import org.eclipse.hawkbit.repository.model.Action.ActionType;
|
||||||
|
import org.eclipse.hawkbit.repository.model.Action.Status;
|
||||||
|
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||||
|
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||||
|
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
|
||||||
|
import org.eclipse.hawkbit.repository.model.RolloutGroup;
|
||||||
|
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorCondition;
|
||||||
|
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus;
|
||||||
|
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition;
|
||||||
|
import org.eclipse.hawkbit.repository.model.Target;
|
||||||
|
import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
|
||||||
|
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.BeansException;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.PageRequest;
|
||||||
|
import org.springframework.data.domain.Slice;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
|
import org.springframework.transaction.TransactionException;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Jpa implementation of {@link RolloutExecutor}
|
||||||
|
*/
|
||||||
|
public class JpaRolloutExecutor implements RolloutExecutor {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(JpaRolloutExecutor.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Max amount of targets that are handled in one transaction.
|
||||||
|
*/
|
||||||
|
private static final int TRANSACTION_TARGETS = 5_000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum amount of actions that are deleted in one transaction.
|
||||||
|
*/
|
||||||
|
private static final int TRANSACTION_ACTIONS = 5_000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action statuses that result in a terminated action
|
||||||
|
*/
|
||||||
|
private static final List<Status> DEFAULT_ACTION_TERMINATION_STATUSES = Arrays.asList(Status.ERROR, Status.FINISHED,
|
||||||
|
Status.CANCELED);
|
||||||
|
/**
|
||||||
|
* In case of DOWNLOAD_ONLY, actions can be finished with DOWNLOADED status.
|
||||||
|
*/
|
||||||
|
private static final List<Status> DOWNLOAD_ONLY_ACTION_TERMINATION_STATUSES = Arrays.asList(Status.ERROR,
|
||||||
|
Status.FINISHED, Status.CANCELED, Status.DOWNLOADED);
|
||||||
|
|
||||||
|
private final RolloutTargetGroupRepository rolloutTargetGroupRepository;
|
||||||
|
private final EntityManager entityManager;
|
||||||
|
private final RolloutRepository rolloutRepository;
|
||||||
|
private final ActionRepository actionRepository;
|
||||||
|
private final RolloutGroupRepository rolloutGroupRepository;
|
||||||
|
private final AfterTransactionCommitExecutor afterCommit;
|
||||||
|
private final TenantAware tenantAware;
|
||||||
|
private final RolloutGroupManagement rolloutGroupManagement;
|
||||||
|
private final QuotaManagement quotaManagement;
|
||||||
|
private final DeploymentManagement deploymentManagement;
|
||||||
|
private final TargetManagement targetManagement;
|
||||||
|
private final EventPublisherHolder eventPublisherHolder;
|
||||||
|
private final PlatformTransactionManager txManager;
|
||||||
|
private final RolloutApprovalStrategy rolloutApprovalStrategy;
|
||||||
|
private final ApplicationContext context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
public JpaRolloutExecutor(final RolloutTargetGroupRepository rolloutTargetGroupRepository,
|
||||||
|
final EntityManager entityManager, final RolloutRepository rolloutRepository,
|
||||||
|
final ActionRepository actionRepository, final RolloutGroupRepository rolloutGroupRepository,
|
||||||
|
final AfterTransactionCommitExecutor afterCommit, final TenantAware tenantAware,
|
||||||
|
final RolloutGroupManagement rolloutGroupManagement, final QuotaManagement quotaManagement,
|
||||||
|
final DeploymentManagement deploymentManagement, final TargetManagement targetManagement,
|
||||||
|
final EventPublisherHolder eventPublisherHolder, final PlatformTransactionManager txManager,
|
||||||
|
final RolloutApprovalStrategy rolloutApprovalStrategy, final ApplicationContext context) {
|
||||||
|
this.rolloutTargetGroupRepository = rolloutTargetGroupRepository;
|
||||||
|
this.entityManager = entityManager;
|
||||||
|
this.rolloutRepository = rolloutRepository;
|
||||||
|
this.actionRepository = actionRepository;
|
||||||
|
this.rolloutGroupRepository = rolloutGroupRepository;
|
||||||
|
this.afterCommit = afterCommit;
|
||||||
|
this.tenantAware = tenantAware;
|
||||||
|
this.rolloutGroupManagement = rolloutGroupManagement;
|
||||||
|
this.quotaManagement = quotaManagement;
|
||||||
|
this.deploymentManagement = deploymentManagement;
|
||||||
|
this.targetManagement = targetManagement;
|
||||||
|
this.eventPublisherHolder = eventPublisherHolder;
|
||||||
|
this.txManager = txManager;
|
||||||
|
this.rolloutApprovalStrategy = rolloutApprovalStrategy;
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(final Rollout rollout) {
|
||||||
|
LOGGER.debug("handle rollout {}", rollout.getId());
|
||||||
|
|
||||||
|
switch (rollout.getStatus()) {
|
||||||
|
case CREATING:
|
||||||
|
handleCreateRollout((JpaRollout) rollout);
|
||||||
|
break;
|
||||||
|
case DELETING:
|
||||||
|
handleDeleteRollout((JpaRollout) rollout);
|
||||||
|
break;
|
||||||
|
case READY:
|
||||||
|
handleReadyRollout(rollout);
|
||||||
|
break;
|
||||||
|
case STARTING:
|
||||||
|
handleStartingRollout(rollout);
|
||||||
|
break;
|
||||||
|
case RUNNING:
|
||||||
|
handleRunningRollout((JpaRollout) rollout);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOGGER.error("Rollout in status {} not supposed to be handled!", rollout.getStatus());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleCreateRollout(final JpaRollout rollout) {
|
||||||
|
LOGGER.debug("handleCreateRollout called for rollout {}", rollout.getId());
|
||||||
|
|
||||||
|
final List<RolloutGroup> rolloutGroups = rolloutGroupManagement.findByRollout(
|
||||||
|
PageRequest.of(0, quotaManagement.getMaxRolloutGroupsPerRollout(), Sort.by(Direction.ASC, "id")),
|
||||||
|
rollout.getId()).getContent();
|
||||||
|
|
||||||
|
int readyGroups = 0;
|
||||||
|
int totalTargets = 0;
|
||||||
|
for (final RolloutGroup group : rolloutGroups) {
|
||||||
|
if (RolloutGroupStatus.READY == group.getStatus()) {
|
||||||
|
readyGroups++;
|
||||||
|
totalTargets += group.getTotalTargets();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final RolloutGroup filledGroup = fillRolloutGroupWithTargets(rollout, group);
|
||||||
|
if (RolloutGroupStatus.READY == filledGroup.getStatus()) {
|
||||||
|
readyGroups++;
|
||||||
|
totalTargets += filledGroup.getTotalTargets();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When all groups are ready the rollout status can be changed to be
|
||||||
|
// ready, too.
|
||||||
|
if (readyGroups == rolloutGroups.size()) {
|
||||||
|
if (!rolloutApprovalStrategy.isApprovalNeeded(rollout)) {
|
||||||
|
rollout.setStatus(RolloutStatus.READY);
|
||||||
|
LOGGER.debug("rollout {} creation done. Switch to READY.", rollout.getId());
|
||||||
|
} else {
|
||||||
|
LOGGER.debug("rollout {} creation done. Switch to WAITING_FOR_APPROVAL.", rollout.getId());
|
||||||
|
rollout.setStatus(RolloutStatus.WAITING_FOR_APPROVAL);
|
||||||
|
rolloutApprovalStrategy.onApprovalRequired(rollout);
|
||||||
|
}
|
||||||
|
rollout.setLastCheck(0);
|
||||||
|
rollout.setTotalTargets(totalTargets);
|
||||||
|
rolloutRepository.save(rollout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleDeleteRollout(final JpaRollout rollout) {
|
||||||
|
LOGGER.debug("handleDeleteRollout called for {}", rollout.getId());
|
||||||
|
|
||||||
|
// check if there are actions beyond schedule
|
||||||
|
boolean hardDeleteRolloutGroups = !actionRepository.existsByRolloutIdAndStatusNotIn(rollout.getId(),
|
||||||
|
Status.SCHEDULED);
|
||||||
|
if (hardDeleteRolloutGroups) {
|
||||||
|
LOGGER.debug("Rollout {} has no actions other than scheduled -> hard delete", rollout.getId());
|
||||||
|
hardDeleteRollout(rollout);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// clean up all scheduled actions
|
||||||
|
final Slice<JpaAction> scheduledActions = findScheduledActionsByRollout(rollout);
|
||||||
|
deleteScheduledActions(rollout, scheduledActions);
|
||||||
|
|
||||||
|
// avoid another scheduler round and re-check if all scheduled actions
|
||||||
|
// has been cleaned up. we flush first to ensure that the we include the
|
||||||
|
// deletion above
|
||||||
|
entityManager.flush();
|
||||||
|
final boolean hasScheduledActionsLeft = actionRepository.countByRolloutIdAndStatus(rollout.getId(),
|
||||||
|
Status.SCHEDULED) > 0;
|
||||||
|
|
||||||
|
if (hasScheduledActionsLeft) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// only hard delete the rollout if no actions are left for the rollout.
|
||||||
|
// In case actions are left, they are probably are running or were
|
||||||
|
// running before, so only soft delete.
|
||||||
|
hardDeleteRolloutGroups = !actionRepository.existsByRolloutId(rollout.getId());
|
||||||
|
if (hardDeleteRolloutGroups) {
|
||||||
|
hardDeleteRollout(rollout);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set soft delete
|
||||||
|
rollout.setStatus(RolloutStatus.DELETED);
|
||||||
|
rollout.setDeleted(true);
|
||||||
|
rolloutRepository.save(rollout);
|
||||||
|
|
||||||
|
sendRolloutGroupDeletedEvents(rollout);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleReadyRollout(final Rollout rollout) {
|
||||||
|
if (rollout.getStartAt() != null && rollout.getStartAt() <= System.currentTimeMillis()) {
|
||||||
|
LOGGER.debug(
|
||||||
|
"handleReadyRollout called for rollout {} with autostart beyond define time. Switch to STARTING",
|
||||||
|
rollout.getId());
|
||||||
|
context.getBean(RolloutManagement.class).start(rollout.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleStartingRollout(final Rollout rollout) {
|
||||||
|
LOGGER.debug("handleStartingRollout called for rollout {}", rollout.getId());
|
||||||
|
|
||||||
|
if (ensureAllGroupsAreScheduled(rollout)) {
|
||||||
|
startFirstRolloutGroup(rollout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleRunningRollout(final JpaRollout rollout) {
|
||||||
|
LOGGER.debug("handleRunningRollout called for rollout {}", rollout.getId());
|
||||||
|
|
||||||
|
final List<JpaRolloutGroup> rolloutGroupsRunning = rolloutGroupRepository.findByRolloutAndStatus(rollout,
|
||||||
|
RolloutGroupStatus.RUNNING);
|
||||||
|
|
||||||
|
if (rolloutGroupsRunning.isEmpty()) {
|
||||||
|
// no running rollouts, probably there was an error
|
||||||
|
// somewhere at the latest group. And the latest group has
|
||||||
|
// been switched from running into error state. So we need
|
||||||
|
// to find the latest group which
|
||||||
|
executeLatestRolloutGroup(rollout);
|
||||||
|
} else {
|
||||||
|
LOGGER.debug("Rollout {} has {} running groups", rollout.getId(), rolloutGroupsRunning.size());
|
||||||
|
executeRolloutGroups(rollout, rolloutGroupsRunning);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isRolloutComplete(rollout)) {
|
||||||
|
LOGGER.info("Rollout {} is finished, setting FINISHED status", rollout);
|
||||||
|
rollout.setStatus(RolloutStatus.FINISHED);
|
||||||
|
rolloutRepository.save(rollout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hardDeleteRollout(final JpaRollout rollout) {
|
||||||
|
sendRolloutGroupDeletedEvents(rollout);
|
||||||
|
rolloutRepository.delete(rollout);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteScheduledActions(final JpaRollout rollout, final Slice<JpaAction> scheduledActions) {
|
||||||
|
final boolean hasScheduledActions = scheduledActions.getNumberOfElements() > 0;
|
||||||
|
|
||||||
|
if (hasScheduledActions) {
|
||||||
|
try {
|
||||||
|
final Iterable<JpaAction> iterable = scheduledActions::iterator;
|
||||||
|
final List<Long> actionIds = StreamSupport.stream(iterable.spliterator(), false)
|
||||||
|
.map(Action::getId)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
actionRepository.deleteByIdIn(actionIds);
|
||||||
|
afterCommit.afterCommit(() -> eventPublisherHolder.getEventPublisher()
|
||||||
|
.publishEvent(new RolloutUpdatedEvent(rollout, eventPublisherHolder.getApplicationId())));
|
||||||
|
} catch (final RuntimeException e) {
|
||||||
|
LOGGER.error("Exception during deletion of actions of rollout {}", rollout, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Slice<JpaAction> findScheduledActionsByRollout(final JpaRollout rollout) {
|
||||||
|
return actionRepository.findByRolloutIdAndStatus(PageRequest.of(0, TRANSACTION_ACTIONS), rollout.getId(),
|
||||||
|
Status.SCHEDULED);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendRolloutGroupDeletedEvents(final JpaRollout rollout) {
|
||||||
|
final List<Long> groupIds = rollout.getRolloutGroups().stream().map(RolloutGroup::getId)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
afterCommit.afterCommit(() -> groupIds.forEach(rolloutGroupId -> eventPublisherHolder.getEventPublisher()
|
||||||
|
.publishEvent(new RolloutGroupDeletedEvent(tenantAware.getCurrentTenant(), rolloutGroupId,
|
||||||
|
JpaRolloutGroup.class.getName(), eventPublisherHolder.getApplicationId()))));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isRolloutComplete(final JpaRollout rollout) {
|
||||||
|
// ensure that changes in the same transaction count
|
||||||
|
entityManager.flush();
|
||||||
|
final Long groupsActiveLeft = rolloutGroupRepository.countByRolloutIdAndStatusOrStatus(rollout.getId(),
|
||||||
|
RolloutGroupStatus.RUNNING, RolloutGroupStatus.SCHEDULED);
|
||||||
|
return groupsActiveLeft == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeLatestRolloutGroup(final JpaRollout rollout) {
|
||||||
|
final List<JpaRolloutGroup> latestRolloutGroup = rolloutGroupRepository
|
||||||
|
.findByRolloutAndStatusNotOrderByIdDesc(rollout, RolloutGroupStatus.SCHEDULED);
|
||||||
|
if (latestRolloutGroup.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
executeRolloutGroupSuccessAction(rollout, latestRolloutGroup.get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeRolloutGroups(final JpaRollout rollout, final List<JpaRolloutGroup> rolloutGroups) {
|
||||||
|
for (final JpaRolloutGroup rolloutGroup : rolloutGroups) {
|
||||||
|
|
||||||
|
final long targetCount = countTargetsFrom(rolloutGroup);
|
||||||
|
if (rolloutGroup.getTotalTargets() != targetCount) {
|
||||||
|
updateTotalTargetCount(rolloutGroup, targetCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// error state check, do we need to stop the whole
|
||||||
|
// rollout because of error?
|
||||||
|
final boolean isError = checkErrorState(rollout, rolloutGroup);
|
||||||
|
if (isError) {
|
||||||
|
LOGGER.info("Rollout {} {} has error, calling error action", rollout.getName(), rollout.getId());
|
||||||
|
callErrorAction(rollout, rolloutGroup);
|
||||||
|
} else {
|
||||||
|
// not in error so check finished state, do we need to
|
||||||
|
// start the next group?
|
||||||
|
final RolloutGroupSuccessCondition finishedCondition = rolloutGroup.getSuccessCondition();
|
||||||
|
checkFinishCondition(rollout, rolloutGroup, finishedCondition);
|
||||||
|
if (isRolloutGroupComplete(rollout, rolloutGroup)) {
|
||||||
|
rolloutGroup.setStatus(RolloutGroupStatus.FINISHED);
|
||||||
|
rolloutGroupRepository.save(rolloutGroup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTotalTargetCount(final JpaRolloutGroup rolloutGroup, final long countTargetsOfRolloutGroup) {
|
||||||
|
final JpaRollout jpaRollout = (JpaRollout) rolloutGroup.getRollout();
|
||||||
|
final long updatedTargetCount =
|
||||||
|
jpaRollout.getTotalTargets() - (rolloutGroup.getTotalTargets() - countTargetsOfRolloutGroup);
|
||||||
|
jpaRollout.setTotalTargets(updatedTargetCount);
|
||||||
|
rolloutGroup.setTotalTargets((int) countTargetsOfRolloutGroup);
|
||||||
|
rolloutRepository.save(jpaRollout);
|
||||||
|
rolloutGroupRepository.save(rolloutGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
private long countTargetsFrom(final JpaRolloutGroup rolloutGroup) {
|
||||||
|
return rolloutGroupManagement.countTargetsOfRolloutsGroup(rolloutGroup.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void callErrorAction(final Rollout rollout, final RolloutGroup rolloutGroup) {
|
||||||
|
try {
|
||||||
|
context.getBean(rolloutGroup.getErrorAction().getBeanName(), RolloutGroupActionEvaluator.class)
|
||||||
|
.eval(rollout, rolloutGroup, rolloutGroup.getErrorActionExp());
|
||||||
|
} catch (final BeansException e) {
|
||||||
|
LOGGER.error("Something bad happend when accessing the error action bean {}",
|
||||||
|
rolloutGroup.getErrorAction().getBeanName(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isRolloutGroupComplete(final JpaRollout rollout, final JpaRolloutGroup rolloutGroup) {
|
||||||
|
final Long actionsLeftForRollout = ActionType.DOWNLOAD_ONLY == rollout.getActionType()
|
||||||
|
? actionRepository.countByRolloutAndRolloutGroupAndStatusNotIn(rollout, rolloutGroup,
|
||||||
|
DOWNLOAD_ONLY_ACTION_TERMINATION_STATUSES)
|
||||||
|
: actionRepository.countByRolloutAndRolloutGroupAndStatusNotIn(rollout, rolloutGroup,
|
||||||
|
DEFAULT_ACTION_TERMINATION_STATUSES);
|
||||||
|
return actionsLeftForRollout == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkErrorState(final Rollout rollout, final RolloutGroup rolloutGroup) {
|
||||||
|
|
||||||
|
final RolloutGroupErrorCondition errorCondition = rolloutGroup.getErrorCondition();
|
||||||
|
|
||||||
|
if (errorCondition == null) {
|
||||||
|
// there is no error condition, so return false, don't have error.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return context.getBean(errorCondition.getBeanName(), RolloutGroupConditionEvaluator.class).eval(rollout,
|
||||||
|
rolloutGroup, rolloutGroup.getErrorConditionExp());
|
||||||
|
} catch (final BeansException e) {
|
||||||
|
LOGGER.error("Something bad happend when accessing the error condition bean {}",
|
||||||
|
errorCondition.getBeanName(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkFinishCondition(final Rollout rollout, final RolloutGroup rolloutGroup,
|
||||||
|
final RolloutGroupSuccessCondition finishCondition) {
|
||||||
|
LOGGER.trace("Checking finish condition {} on rolloutgroup {}", finishCondition, rolloutGroup);
|
||||||
|
try {
|
||||||
|
final boolean isFinished = context
|
||||||
|
.getBean(finishCondition.getBeanName(), RolloutGroupConditionEvaluator.class)
|
||||||
|
.eval(rollout, rolloutGroup, rolloutGroup.getSuccessConditionExp());
|
||||||
|
if (isFinished) {
|
||||||
|
LOGGER.debug("Rolloutgroup {} is finished, starting next group", rolloutGroup);
|
||||||
|
executeRolloutGroupSuccessAction(rollout, rolloutGroup);
|
||||||
|
} else {
|
||||||
|
LOGGER.debug("Rolloutgroup {} is still running", rolloutGroup);
|
||||||
|
}
|
||||||
|
return isFinished;
|
||||||
|
} catch (final BeansException e) {
|
||||||
|
LOGGER.error("Something bad happend when accessing the finish condition bean {}",
|
||||||
|
finishCondition.getBeanName(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeRolloutGroupSuccessAction(final Rollout rollout, final RolloutGroup rolloutGroup) {
|
||||||
|
context.getBean(rolloutGroup.getSuccessAction().getBeanName(), RolloutGroupActionEvaluator.class)
|
||||||
|
.eval(rollout, rolloutGroup, rolloutGroup.getSuccessActionExp());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startFirstRolloutGroup(final Rollout rollout) {
|
||||||
|
LOGGER.debug("startFirstRolloutGroup called for rollout {}", rollout.getId());
|
||||||
|
RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.STARTING);
|
||||||
|
final JpaRollout jpaRollout = (JpaRollout) rollout;
|
||||||
|
|
||||||
|
final List<JpaRolloutGroup> rolloutGroups = rolloutGroupRepository.findByRolloutOrderByIdAsc(jpaRollout);
|
||||||
|
final JpaRolloutGroup rolloutGroup = rolloutGroups.get(0);
|
||||||
|
if (rolloutGroup.getParent() != null) {
|
||||||
|
throw new RolloutIllegalStateException("First Group is not the first group.");
|
||||||
|
}
|
||||||
|
|
||||||
|
deploymentManagement.startScheduledActionsByRolloutGroupParent(rollout.getId(),
|
||||||
|
rollout.getDistributionSet().getId(), null);
|
||||||
|
|
||||||
|
rolloutGroup.setStatus(RolloutGroupStatus.RUNNING);
|
||||||
|
rolloutGroupRepository.save(rolloutGroup);
|
||||||
|
|
||||||
|
jpaRollout.setStatus(RolloutStatus.RUNNING);
|
||||||
|
jpaRollout.setLastCheck(0);
|
||||||
|
rolloutRepository.save(jpaRollout);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean ensureAllGroupsAreScheduled(final Rollout rollout) {
|
||||||
|
final JpaRollout jpaRollout = (JpaRollout) rollout;
|
||||||
|
|
||||||
|
final List<JpaRolloutGroup> groupsToBeScheduled = rolloutGroupRepository.findByRolloutAndStatus(rollout,
|
||||||
|
RolloutGroupStatus.READY);
|
||||||
|
final long scheduledGroups = groupsToBeScheduled.stream()
|
||||||
|
.filter(group -> scheduleRolloutGroup(jpaRollout, group)).count();
|
||||||
|
|
||||||
|
return scheduledGroups == groupsToBeScheduled.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
private RolloutGroup fillRolloutGroupWithTargets(final JpaRollout rollout, final RolloutGroup group1) {
|
||||||
|
RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.CREATING);
|
||||||
|
|
||||||
|
final JpaRolloutGroup group = (JpaRolloutGroup) group1;
|
||||||
|
|
||||||
|
final String baseFilter = RolloutHelper.getTargetFilterQuery(rollout);
|
||||||
|
final String groupTargetFilter;
|
||||||
|
if (StringUtils.isEmpty(group.getTargetFilterQuery())) {
|
||||||
|
groupTargetFilter = baseFilter;
|
||||||
|
} else {
|
||||||
|
groupTargetFilter = baseFilter + ";" + group.getTargetFilterQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<Long> readyGroups = RolloutHelper.getGroupsByStatusIncludingGroup(rollout.getRolloutGroups(),
|
||||||
|
RolloutGroupStatus.READY, group);
|
||||||
|
|
||||||
|
final long targetsInGroupFilter = DeploymentHelper.runInNewTransaction(txManager,
|
||||||
|
"countAllTargetsByTargetFilterQueryAndNotInRolloutGroups",
|
||||||
|
count -> targetManagement.countByRsqlAndNotInRolloutGroups(readyGroups, groupTargetFilter));
|
||||||
|
final long expectedInGroup = Math.round(group.getTargetPercentage() / 100 * (double) targetsInGroupFilter);
|
||||||
|
final long currentlyInGroup = DeploymentHelper.runInNewTransaction(txManager,
|
||||||
|
"countRolloutTargetGroupByRolloutGroup",
|
||||||
|
count -> rolloutTargetGroupRepository.countByRolloutGroup(group));
|
||||||
|
|
||||||
|
// Switch the Group status to READY, when there are enough Targets in
|
||||||
|
// the Group
|
||||||
|
if (currentlyInGroup >= expectedInGroup) {
|
||||||
|
group.setStatus(RolloutGroupStatus.READY);
|
||||||
|
return rolloutGroupRepository.save(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
long targetsLeftToAdd = expectedInGroup - currentlyInGroup;
|
||||||
|
|
||||||
|
do {
|
||||||
|
// Add up to TRANSACTION_TARGETS of the left targets
|
||||||
|
// In case a TransactionException is thrown this loop aborts
|
||||||
|
targetsLeftToAdd -= assignTargetsToGroupInNewTransaction(rollout, group, groupTargetFilter,
|
||||||
|
Math.min(TRANSACTION_TARGETS, targetsLeftToAdd));
|
||||||
|
} while (targetsLeftToAdd > 0);
|
||||||
|
|
||||||
|
group.setStatus(RolloutGroupStatus.READY);
|
||||||
|
group.setTotalTargets(
|
||||||
|
DeploymentHelper.runInNewTransaction(txManager, "countRolloutTargetGroupByRolloutGroup",
|
||||||
|
count -> rolloutTargetGroupRepository.countByRolloutGroup(group)).intValue());
|
||||||
|
return rolloutGroupRepository.save(group);
|
||||||
|
|
||||||
|
} catch (final TransactionException e) {
|
||||||
|
LOGGER.warn("Transaction assigning Targets to RolloutGroup failed", e);
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long assignTargetsToGroupInNewTransaction(final JpaRollout rollout, final RolloutGroup group,
|
||||||
|
final String targetFilter, final long limit) {
|
||||||
|
|
||||||
|
return DeploymentHelper.runInNewTransaction(txManager, "assignTargetsToRolloutGroup", status -> {
|
||||||
|
final PageRequest pageRequest = PageRequest.of(0, Math.toIntExact(limit));
|
||||||
|
final List<Long> readyGroups = RolloutHelper.getGroupsByStatusIncludingGroup(rollout.getRolloutGroups(),
|
||||||
|
RolloutGroupStatus.READY, group);
|
||||||
|
final Page<Target> targets = targetManagement.findByTargetFilterQueryAndNotInRolloutGroups(pageRequest,
|
||||||
|
readyGroups, targetFilter);
|
||||||
|
|
||||||
|
createAssignmentOfTargetsToGroup(targets, group);
|
||||||
|
|
||||||
|
return Long.valueOf(targets.getNumberOfElements());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedules a group of the rollout. Scheduled Actions are created to
|
||||||
|
* achieve this. The creation of those Actions is allowed to fail.
|
||||||
|
*/
|
||||||
|
private boolean scheduleRolloutGroup(final JpaRollout rollout, final JpaRolloutGroup group) {
|
||||||
|
final long targetsInGroup = rolloutTargetGroupRepository.countByRolloutGroup(group);
|
||||||
|
final long countOfActions = actionRepository.countByRolloutAndRolloutGroup(rollout, group);
|
||||||
|
|
||||||
|
long actionsLeft = targetsInGroup - countOfActions;
|
||||||
|
if (actionsLeft > 0) {
|
||||||
|
actionsLeft -= createActionsForRolloutGroup(rollout, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actionsLeft <= 0) {
|
||||||
|
group.setStatus(RolloutGroupStatus.SCHEDULED);
|
||||||
|
rolloutGroupRepository.save(group);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long createActionsForRolloutGroup(final Rollout rollout, final RolloutGroup group) {
|
||||||
|
long totalActionsCreated = 0;
|
||||||
|
try {
|
||||||
|
long actionsCreated;
|
||||||
|
do {
|
||||||
|
actionsCreated = createActionsForTargetsInNewTransaction(rollout.getId(), group.getId(), TRANSACTION_TARGETS);
|
||||||
|
totalActionsCreated += actionsCreated;
|
||||||
|
} while (actionsCreated > 0);
|
||||||
|
|
||||||
|
} catch (final TransactionException e) {
|
||||||
|
LOGGER.warn("Transaction assigning Targets to RolloutGroup failed", e);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return totalActionsCreated;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long createActionsForTargetsInNewTransaction(final long rolloutId, final long groupId,
|
||||||
|
final int limit) {
|
||||||
|
return DeploymentHelper.runInNewTransaction(txManager, "createActionsForTargets", status -> {
|
||||||
|
final PageRequest pageRequest = PageRequest.of(0, limit);
|
||||||
|
final Rollout rollout = rolloutRepository.findById(rolloutId)
|
||||||
|
.orElseThrow(() -> new EntityNotFoundException(Rollout.class, rolloutId));
|
||||||
|
final RolloutGroup group = rolloutGroupRepository.findById(groupId)
|
||||||
|
.orElseThrow(() -> new EntityNotFoundException(RolloutGroup.class, groupId));
|
||||||
|
|
||||||
|
final DistributionSet distributionSet = rollout.getDistributionSet();
|
||||||
|
final ActionType actionType = rollout.getActionType();
|
||||||
|
final long forceTime = rollout.getForcedTime();
|
||||||
|
|
||||||
|
final Page<Target> targets = targetManagement.findByInRolloutGroupWithoutAction(pageRequest, groupId);
|
||||||
|
if (targets.getTotalElements() > 0) {
|
||||||
|
createScheduledAction(targets.getContent(), distributionSet, actionType, forceTime, rollout, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Long.valueOf(targets.getNumberOfElements());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createAssignmentOfTargetsToGroup(final Page<Target> targets, final RolloutGroup group) {
|
||||||
|
targets.forEach(target -> rolloutTargetGroupRepository.save(new RolloutTargetGroup(group, target)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an action entry into the action repository. In case of existing
|
||||||
|
* scheduled actions the scheduled actions gets canceled. A scheduled action
|
||||||
|
* is created in-active.
|
||||||
|
*/
|
||||||
|
private void createScheduledAction(final Collection<Target> targets, final DistributionSet distributionSet,
|
||||||
|
final ActionType actionType, final Long forcedTime, final Rollout rollout,
|
||||||
|
final RolloutGroup rolloutGroup) {
|
||||||
|
// cancel all current scheduled actions for this target. E.g. an action
|
||||||
|
// is already scheduled and a next action is created then cancel the
|
||||||
|
// current scheduled action to cancel. E.g. a new scheduled action is
|
||||||
|
// created.
|
||||||
|
final List<Long> targetIds = targets.stream().map(Target::getId).collect(Collectors.toList());
|
||||||
|
deploymentManagement.cancelInactiveScheduledActionsForTargets(targetIds);
|
||||||
|
targets.forEach(target -> {
|
||||||
|
|
||||||
|
assertActionsPerTargetQuota(target, 1);
|
||||||
|
|
||||||
|
final JpaAction action = new JpaAction();
|
||||||
|
action.setTarget(target);
|
||||||
|
action.setActive(false);
|
||||||
|
action.setDistributionSet(distributionSet);
|
||||||
|
action.setActionType(actionType);
|
||||||
|
action.setForcedTime(forcedTime);
|
||||||
|
action.setStatus(Status.SCHEDULED);
|
||||||
|
action.setRollout(rollout);
|
||||||
|
action.setRolloutGroup(rolloutGroup);
|
||||||
|
action.setInitiatedBy(rollout.getCreatedBy());
|
||||||
|
rollout.getWeight().ifPresent(action::setWeight);
|
||||||
|
actionRepository.save(action);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enforces the quota defining the maximum number of {@link Action}s per
|
||||||
|
* {@link Target}.
|
||||||
|
*
|
||||||
|
* @param target
|
||||||
|
* The target
|
||||||
|
* @param requested
|
||||||
|
* number of actions to check
|
||||||
|
*/
|
||||||
|
private void assertActionsPerTargetQuota(final Target target, final int requested) {
|
||||||
|
final int quota = quotaManagement.getMaxActionsPerTarget();
|
||||||
|
QuotaHelper.assertAssignmentQuota(target.getId(), requested, quota, Action.class, Target.class,
|
||||||
|
actionRepository::countByTargetId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,16 +11,13 @@ package org.eclipse.hawkbit.repository.jpa;
|
|||||||
import static org.eclipse.hawkbit.repository.jpa.builder.JpaRolloutGroupCreate.addSuccessAndErrorConditionsAndActions;
|
import static org.eclipse.hawkbit.repository.jpa.builder.JpaRolloutGroupCreate.addSuccessAndErrorConditionsAndActions;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.Lock;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.StreamSupport;
|
|
||||||
|
|
||||||
import javax.persistence.EntityManager;
|
|
||||||
import javax.validation.ConstraintDeclarationException;
|
import javax.validation.ConstraintDeclarationException;
|
||||||
import javax.validation.ValidationException;
|
import javax.validation.ValidationException;
|
||||||
|
|
||||||
@@ -29,6 +26,7 @@ import org.eclipse.hawkbit.repository.DeploymentManagement;
|
|||||||
import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
||||||
import org.eclipse.hawkbit.repository.QuotaManagement;
|
import org.eclipse.hawkbit.repository.QuotaManagement;
|
||||||
import org.eclipse.hawkbit.repository.RolloutApprovalStrategy;
|
import org.eclipse.hawkbit.repository.RolloutApprovalStrategy;
|
||||||
|
import org.eclipse.hawkbit.repository.RolloutExecutor;
|
||||||
import org.eclipse.hawkbit.repository.RolloutFields;
|
import org.eclipse.hawkbit.repository.RolloutFields;
|
||||||
import org.eclipse.hawkbit.repository.RolloutGroupManagement;
|
import org.eclipse.hawkbit.repository.RolloutGroupManagement;
|
||||||
import org.eclipse.hawkbit.repository.RolloutHelper;
|
import org.eclipse.hawkbit.repository.RolloutHelper;
|
||||||
@@ -40,37 +38,26 @@ import org.eclipse.hawkbit.repository.builder.GenericRolloutUpdate;
|
|||||||
import org.eclipse.hawkbit.repository.builder.RolloutCreate;
|
import org.eclipse.hawkbit.repository.builder.RolloutCreate;
|
||||||
import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate;
|
import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate;
|
||||||
import org.eclipse.hawkbit.repository.builder.RolloutUpdate;
|
import org.eclipse.hawkbit.repository.builder.RolloutUpdate;
|
||||||
import org.eclipse.hawkbit.repository.event.remote.RolloutGroupDeletedEvent;
|
|
||||||
import org.eclipse.hawkbit.repository.event.remote.entity.RolloutGroupCreatedEvent;
|
import org.eclipse.hawkbit.repository.event.remote.entity.RolloutGroupCreatedEvent;
|
||||||
import org.eclipse.hawkbit.repository.event.remote.entity.RolloutUpdatedEvent;
|
|
||||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||||
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;
|
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;
|
||||||
import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException;
|
import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException;
|
||||||
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
|
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
|
||||||
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
|
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
|
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
|
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup;
|
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup;
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.RolloutTargetGroup;
|
|
||||||
import org.eclipse.hawkbit.repository.jpa.rollout.condition.RolloutGroupActionEvaluator;
|
|
||||||
import org.eclipse.hawkbit.repository.jpa.rollout.condition.RolloutGroupConditionEvaluator;
|
|
||||||
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
|
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
|
||||||
import org.eclipse.hawkbit.repository.jpa.specifications.RolloutSpecification;
|
import org.eclipse.hawkbit.repository.jpa.specifications.RolloutSpecification;
|
||||||
import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
|
import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
|
||||||
import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper;
|
import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper;
|
||||||
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
|
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
|
||||||
import org.eclipse.hawkbit.repository.jpa.utils.WeightValidationHelper;
|
import org.eclipse.hawkbit.repository.jpa.utils.WeightValidationHelper;
|
||||||
import org.eclipse.hawkbit.repository.model.Action;
|
|
||||||
import org.eclipse.hawkbit.repository.model.Action.ActionType;
|
|
||||||
import org.eclipse.hawkbit.repository.model.Action.Status;
|
|
||||||
import org.eclipse.hawkbit.repository.model.BaseEntity;
|
import org.eclipse.hawkbit.repository.model.BaseEntity;
|
||||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||||
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
|
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
|
||||||
import org.eclipse.hawkbit.repository.model.RolloutGroup;
|
import org.eclipse.hawkbit.repository.model.RolloutGroup;
|
||||||
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorCondition;
|
|
||||||
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus;
|
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus;
|
||||||
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition;
|
|
||||||
import org.eclipse.hawkbit.repository.model.RolloutGroupConditions;
|
import org.eclipse.hawkbit.repository.model.RolloutGroupConditions;
|
||||||
import org.eclipse.hawkbit.repository.model.RolloutGroupsValidation;
|
import org.eclipse.hawkbit.repository.model.RolloutGroupsValidation;
|
||||||
import org.eclipse.hawkbit.repository.model.Target;
|
import org.eclipse.hawkbit.repository.model.Target;
|
||||||
@@ -82,16 +69,12 @@ import org.eclipse.hawkbit.security.SystemSecurityContext;
|
|||||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.BeansException;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.dao.ConcurrencyFailureException;
|
import org.springframework.dao.ConcurrencyFailureException;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.PageRequest;
|
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.data.domain.Slice;
|
import org.springframework.data.domain.Slice;
|
||||||
import org.springframework.data.domain.Sort;
|
|
||||||
import org.springframework.data.domain.Sort.Direction;
|
|
||||||
import org.springframework.data.jpa.domain.Specification;
|
import org.springframework.data.jpa.domain.Specification;
|
||||||
import org.springframework.integration.support.locks.LockRegistry;
|
import org.springframework.integration.support.locks.LockRegistry;
|
||||||
import org.springframework.orm.jpa.vendor.Database;
|
import org.springframework.orm.jpa.vendor.Database;
|
||||||
@@ -100,11 +83,9 @@ import org.springframework.retry.annotation.Retryable;
|
|||||||
import org.springframework.scheduling.annotation.Async;
|
import org.springframework.scheduling.annotation.Async;
|
||||||
import org.springframework.scheduling.annotation.AsyncResult;
|
import org.springframework.scheduling.annotation.AsyncResult;
|
||||||
import org.springframework.transaction.PlatformTransactionManager;
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
import org.springframework.transaction.TransactionException;
|
|
||||||
import org.springframework.transaction.annotation.Propagation;
|
import org.springframework.transaction.annotation.Propagation;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
import org.springframework.util.concurrent.ListenableFuture;
|
import org.springframework.util.concurrent.ListenableFuture;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
|
||||||
@@ -118,66 +99,47 @@ import com.google.common.collect.Lists;
|
|||||||
public class JpaRolloutManagement extends AbstractRolloutManagement {
|
public class JpaRolloutManagement extends AbstractRolloutManagement {
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(JpaRolloutManagement.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(JpaRolloutManagement.class);
|
||||||
|
|
||||||
/**
|
|
||||||
* Max amount of targets that are handled in one transaction.
|
|
||||||
*/
|
|
||||||
private static final int TRANSACTION_TARGETS = 5_000;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maximum amount of actions that are deleted in one transaction.
|
|
||||||
*/
|
|
||||||
private static final int TRANSACTION_ACTIONS = 5_000;
|
|
||||||
|
|
||||||
private static final List<RolloutStatus> ACTIVE_ROLLOUTS = Arrays.asList(RolloutStatus.CREATING,
|
private static final List<RolloutStatus> ACTIVE_ROLLOUTS = Arrays.asList(RolloutStatus.CREATING,
|
||||||
RolloutStatus.DELETING, RolloutStatus.STARTING, RolloutStatus.READY, RolloutStatus.RUNNING);
|
RolloutStatus.DELETING, RolloutStatus.STARTING, RolloutStatus.READY, RolloutStatus.RUNNING);
|
||||||
|
|
||||||
// In case of DOWNLOAD_ONLY, actions can be finished with DOWNLOADED status.
|
|
||||||
private static final List<Status> DOWNLOAD_ONLY_ACTION_TERMINATION_STATUSES = Arrays.asList(Status.ERROR,
|
|
||||||
Status.FINISHED, Status.CANCELED, Status.DOWNLOADED);
|
|
||||||
private static final List<Status> DEFAULT_ACTION_TERMINATION_STATUSES = Arrays.asList(Status.ERROR, Status.FINISHED,
|
|
||||||
Status.CANCELED);
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private RolloutRepository rolloutRepository;
|
private RolloutRepository rolloutRepository;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private RolloutGroupRepository rolloutGroupRepository;
|
private RolloutGroupRepository rolloutGroupRepository;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private RolloutTargetGroupRepository rolloutTargetGroupRepository;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ActionRepository actionRepository;
|
private ActionRepository actionRepository;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private AfterTransactionCommitExecutor afterCommit;
|
private AfterTransactionCommitExecutor afterCommit;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private EntityManager entityManager;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private QuotaManagement quotaManagement;
|
private QuotaManagement quotaManagement;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private RolloutStatusCache rolloutStatusCache;
|
private RolloutStatusCache rolloutStatusCache;
|
||||||
|
|
||||||
|
private final RolloutExecutor rolloutExecutor;
|
||||||
|
|
||||||
private final EventPublisherHolder eventPublisherHolder;
|
private final EventPublisherHolder eventPublisherHolder;
|
||||||
|
|
||||||
private final Database database;
|
private final Database database;
|
||||||
|
|
||||||
JpaRolloutManagement(final TargetManagement targetManagement, final DeploymentManagement deploymentManagement,
|
public JpaRolloutManagement(final TargetManagement targetManagement, final DeploymentManagement deploymentManagement,
|
||||||
final RolloutGroupManagement rolloutGroupManagement,
|
final RolloutGroupManagement rolloutGroupManagement,
|
||||||
final DistributionSetManagement distributionSetManagement, final ApplicationContext context,
|
final DistributionSetManagement distributionSetManagement, final ApplicationContext context,
|
||||||
final EventPublisherHolder eventPublisherHolder, final VirtualPropertyReplacer virtualPropertyReplacer,
|
final EventPublisherHolder eventPublisherHolder, final VirtualPropertyReplacer virtualPropertyReplacer,
|
||||||
final PlatformTransactionManager txManager, final TenantAware tenantAware, final LockRegistry lockRegistry,
|
final PlatformTransactionManager txManager, final TenantAware tenantAware, final LockRegistry lockRegistry,
|
||||||
final Database database, final RolloutApprovalStrategy rolloutApprovalStrategy,
|
final Database database, final RolloutApprovalStrategy rolloutApprovalStrategy,
|
||||||
final TenantConfigurationManagement tenantConfigurationManagement,
|
final TenantConfigurationManagement tenantConfigurationManagement,
|
||||||
final SystemSecurityContext systemSecurityContext) {
|
final SystemSecurityContext systemSecurityContext, final RolloutExecutor rolloutExecutor) {
|
||||||
super(targetManagement, deploymentManagement, rolloutGroupManagement, distributionSetManagement, context,
|
super(targetManagement, deploymentManagement, rolloutGroupManagement, distributionSetManagement, context,
|
||||||
virtualPropertyReplacer, txManager, tenantAware, lockRegistry, rolloutApprovalStrategy,
|
virtualPropertyReplacer, txManager, tenantAware, lockRegistry, rolloutApprovalStrategy,
|
||||||
tenantConfigurationManagement, systemSecurityContext);
|
tenantConfigurationManagement, systemSecurityContext);
|
||||||
this.eventPublisherHolder = eventPublisherHolder;
|
this.eventPublisherHolder = eventPublisherHolder;
|
||||||
this.database = database;
|
this.database = database;
|
||||||
|
this.rolloutExecutor = rolloutExecutor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -331,120 +293,6 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
|
|||||||
new RolloutGroupCreatedEvent(group, rollout.getId(), eventPublisherHolder.getApplicationId())));
|
new RolloutGroupCreatedEvent(group, rollout.getId(), eventPublisherHolder.getApplicationId())));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleCreateRollout(final JpaRollout rollout) {
|
|
||||||
LOGGER.debug("handleCreateRollout called for rollout {}", rollout.getId());
|
|
||||||
|
|
||||||
final List<RolloutGroup> rolloutGroups = rolloutGroupManagement.findByRollout(
|
|
||||||
PageRequest.of(0, quotaManagement.getMaxRolloutGroupsPerRollout(), Sort.by(Direction.ASC, "id")),
|
|
||||||
rollout.getId()).getContent();
|
|
||||||
|
|
||||||
int readyGroups = 0;
|
|
||||||
int totalTargets = 0;
|
|
||||||
for (final RolloutGroup group : rolloutGroups) {
|
|
||||||
if (RolloutGroupStatus.READY == group.getStatus()) {
|
|
||||||
readyGroups++;
|
|
||||||
totalTargets += group.getTotalTargets();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
final RolloutGroup filledGroup = fillRolloutGroupWithTargets(rollout, group);
|
|
||||||
if (RolloutGroupStatus.READY == filledGroup.getStatus()) {
|
|
||||||
readyGroups++;
|
|
||||||
totalTargets += filledGroup.getTotalTargets();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// When all groups are ready the rollout status can be changed to be
|
|
||||||
// ready, too.
|
|
||||||
if (readyGroups == rolloutGroups.size()) {
|
|
||||||
if (!rolloutApprovalStrategy.isApprovalNeeded(rollout)) {
|
|
||||||
rollout.setStatus(RolloutStatus.READY);
|
|
||||||
LOGGER.debug("rollout {} creation done. Switch to READY.", rollout.getId());
|
|
||||||
} else {
|
|
||||||
LOGGER.debug("rollout {} creation done. Switch to WAITING_FOR_APPROVAL.", rollout.getId());
|
|
||||||
rollout.setStatus(RolloutStatus.WAITING_FOR_APPROVAL);
|
|
||||||
rolloutApprovalStrategy.onApprovalRequired(rollout);
|
|
||||||
}
|
|
||||||
rollout.setLastCheck(0);
|
|
||||||
rollout.setTotalTargets(totalTargets);
|
|
||||||
rolloutRepository.save(rollout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private RolloutGroup fillRolloutGroupWithTargets(final JpaRollout rollout, final RolloutGroup group1) {
|
|
||||||
RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.CREATING);
|
|
||||||
|
|
||||||
final JpaRolloutGroup group = (JpaRolloutGroup) group1;
|
|
||||||
|
|
||||||
final String baseFilter = RolloutHelper.getTargetFilterQuery(rollout);
|
|
||||||
final String groupTargetFilter;
|
|
||||||
if (StringUtils.isEmpty(group.getTargetFilterQuery())) {
|
|
||||||
groupTargetFilter = baseFilter;
|
|
||||||
} else {
|
|
||||||
groupTargetFilter = baseFilter + ";" + group.getTargetFilterQuery();
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<Long> readyGroups = RolloutHelper.getGroupsByStatusIncludingGroup(rollout.getRolloutGroups(),
|
|
||||||
RolloutGroupStatus.READY, group);
|
|
||||||
|
|
||||||
final long targetsInGroupFilter = DeploymentHelper.runInNewTransaction(txManager,
|
|
||||||
"countAllTargetsByTargetFilterQueryAndNotInRolloutGroups",
|
|
||||||
count -> targetManagement.countByRsqlAndNotInRolloutGroups(readyGroups, groupTargetFilter));
|
|
||||||
final long expectedInGroup = Math.round(group.getTargetPercentage() / 100 * (double) targetsInGroupFilter);
|
|
||||||
final long currentlyInGroup = DeploymentHelper.runInNewTransaction(txManager,
|
|
||||||
"countRolloutTargetGroupByRolloutGroup",
|
|
||||||
count -> rolloutTargetGroupRepository.countByRolloutGroup(group));
|
|
||||||
|
|
||||||
// Switch the Group status to READY, when there are enough Targets in
|
|
||||||
// the Group
|
|
||||||
if (currentlyInGroup >= expectedInGroup) {
|
|
||||||
group.setStatus(RolloutGroupStatus.READY);
|
|
||||||
return rolloutGroupRepository.save(group);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
long targetsLeftToAdd = expectedInGroup - currentlyInGroup;
|
|
||||||
|
|
||||||
do {
|
|
||||||
// Add up to TRANSACTION_TARGETS of the left targets
|
|
||||||
// In case a TransactionException is thrown this loop aborts
|
|
||||||
targetsLeftToAdd -= assignTargetsToGroupInNewTransaction(rollout, group, groupTargetFilter,
|
|
||||||
Math.min(TRANSACTION_TARGETS, targetsLeftToAdd));
|
|
||||||
} while (targetsLeftToAdd > 0);
|
|
||||||
|
|
||||||
group.setStatus(RolloutGroupStatus.READY);
|
|
||||||
group.setTotalTargets(
|
|
||||||
DeploymentHelper.runInNewTransaction(txManager, "countRolloutTargetGroupByRolloutGroup",
|
|
||||||
count -> rolloutTargetGroupRepository.countByRolloutGroup(group)).intValue());
|
|
||||||
return rolloutGroupRepository.save(group);
|
|
||||||
|
|
||||||
} catch (final TransactionException e) {
|
|
||||||
LOGGER.warn("Transaction assigning Targets to RolloutGroup failed", e);
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Long assignTargetsToGroupInNewTransaction(final JpaRollout rollout, final RolloutGroup group,
|
|
||||||
final String targetFilter, final long limit) {
|
|
||||||
|
|
||||||
return DeploymentHelper.runInNewTransaction(txManager, "assignTargetsToRolloutGroup", status -> {
|
|
||||||
final PageRequest pageRequest = PageRequest.of(0, Math.toIntExact(limit));
|
|
||||||
final List<Long> readyGroups = RolloutHelper.getGroupsByStatusIncludingGroup(rollout.getRolloutGroups(),
|
|
||||||
RolloutGroupStatus.READY, group);
|
|
||||||
final Page<Target> targets = targetManagement.findByTargetFilterQueryAndNotInRolloutGroups(pageRequest,
|
|
||||||
readyGroups, targetFilter);
|
|
||||||
|
|
||||||
createAssignmentOfTargetsToGroup(targets, group);
|
|
||||||
|
|
||||||
return Long.valueOf(targets.getNumberOfElements());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createAssignmentOfTargetsToGroup(final Page<Target> targets, final RolloutGroup group) {
|
|
||||||
targets.forEach(target -> rolloutTargetGroupRepository.save(new RolloutTargetGroup(group, target)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Async
|
@Async
|
||||||
public ListenableFuture<RolloutGroupsValidation> validateTargetsInGroups(final List<RolloutGroupCreate> groups,
|
public ListenableFuture<RolloutGroupsValidation> validateTargetsInGroups(final List<RolloutGroupCreate> groups,
|
||||||
@@ -507,131 +355,6 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
|
|||||||
return rolloutRepository.save(rollout);
|
return rolloutRepository.save(rollout);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startFirstRolloutGroup(final Rollout rollout) {
|
|
||||||
LOGGER.debug("startFirstRolloutGroup called for rollout {}", rollout.getId());
|
|
||||||
RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.STARTING);
|
|
||||||
final JpaRollout jpaRollout = (JpaRollout) rollout;
|
|
||||||
|
|
||||||
final List<JpaRolloutGroup> rolloutGroups = rolloutGroupRepository.findByRolloutOrderByIdAsc(jpaRollout);
|
|
||||||
final JpaRolloutGroup rolloutGroup = rolloutGroups.get(0);
|
|
||||||
if (rolloutGroup.getParent() != null) {
|
|
||||||
throw new RolloutIllegalStateException("First Group is not the first group.");
|
|
||||||
}
|
|
||||||
|
|
||||||
deploymentManagement.startScheduledActionsByRolloutGroupParent(rollout.getId(),
|
|
||||||
rollout.getDistributionSet().getId(), null);
|
|
||||||
|
|
||||||
rolloutGroup.setStatus(RolloutGroupStatus.RUNNING);
|
|
||||||
rolloutGroupRepository.save(rolloutGroup);
|
|
||||||
|
|
||||||
jpaRollout.setStatus(RolloutStatus.RUNNING);
|
|
||||||
jpaRollout.setLastCheck(0);
|
|
||||||
rolloutRepository.save(jpaRollout);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean ensureAllGroupsAreScheduled(final Rollout rollout) {
|
|
||||||
final JpaRollout jpaRollout = (JpaRollout) rollout;
|
|
||||||
|
|
||||||
final List<JpaRolloutGroup> groupsToBeScheduled = rolloutGroupRepository.findByRolloutAndStatus(rollout,
|
|
||||||
RolloutGroupStatus.READY);
|
|
||||||
final long scheduledGroups = groupsToBeScheduled.stream()
|
|
||||||
.filter(group -> scheduleRolloutGroup(jpaRollout, group)).count();
|
|
||||||
|
|
||||||
return scheduledGroups == groupsToBeScheduled.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Schedules a group of the rollout. Scheduled Actions are created to
|
|
||||||
* achieve this. The creation of those Actions is allowed to fail.
|
|
||||||
*/
|
|
||||||
private boolean scheduleRolloutGroup(final JpaRollout rollout, final JpaRolloutGroup group) {
|
|
||||||
final long targetsInGroup = rolloutTargetGroupRepository.countByRolloutGroup(group);
|
|
||||||
final long countOfActions = actionRepository.countByRolloutAndRolloutGroup(rollout, group);
|
|
||||||
|
|
||||||
long actionsLeft = targetsInGroup - countOfActions;
|
|
||||||
if (actionsLeft > 0) {
|
|
||||||
actionsLeft -= createActionsForRolloutGroup(rollout, group);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (actionsLeft <= 0) {
|
|
||||||
group.setStatus(RolloutGroupStatus.SCHEDULED);
|
|
||||||
rolloutGroupRepository.save(group);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private long createActionsForRolloutGroup(final Rollout rollout, final RolloutGroup group) {
|
|
||||||
long totalActionsCreated = 0;
|
|
||||||
try {
|
|
||||||
long actionsCreated;
|
|
||||||
do {
|
|
||||||
actionsCreated = createActionsForTargetsInNewTransaction(rollout.getId(), group.getId(), TRANSACTION_TARGETS);
|
|
||||||
totalActionsCreated += actionsCreated;
|
|
||||||
} while (actionsCreated > 0);
|
|
||||||
|
|
||||||
} catch (final TransactionException e) {
|
|
||||||
LOGGER.warn("Transaction assigning Targets to RolloutGroup failed", e);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return totalActionsCreated;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Long createActionsForTargetsInNewTransaction(final long rolloutId, final long groupId,
|
|
||||||
final int limit) {
|
|
||||||
return DeploymentHelper.runInNewTransaction(txManager, "createActionsForTargets", status -> {
|
|
||||||
final PageRequest pageRequest = PageRequest.of(0, limit);
|
|
||||||
final Rollout rollout = rolloutRepository.findById(rolloutId)
|
|
||||||
.orElseThrow(() -> new EntityNotFoundException(Rollout.class, rolloutId));
|
|
||||||
final RolloutGroup group = rolloutGroupRepository.findById(groupId)
|
|
||||||
.orElseThrow(() -> new EntityNotFoundException(RolloutGroup.class, groupId));
|
|
||||||
|
|
||||||
final DistributionSet distributionSet = rollout.getDistributionSet();
|
|
||||||
final ActionType actionType = rollout.getActionType();
|
|
||||||
final long forceTime = rollout.getForcedTime();
|
|
||||||
|
|
||||||
final Page<Target> targets = targetManagement.findByInRolloutGroupWithoutAction(pageRequest, groupId);
|
|
||||||
if (targets.getTotalElements() > 0) {
|
|
||||||
createScheduledAction(targets.getContent(), distributionSet, actionType, forceTime, rollout, group);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Long.valueOf(targets.getNumberOfElements());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an action entry into the action repository. In case of existing
|
|
||||||
* scheduled actions the scheduled actions gets canceled. A scheduled action
|
|
||||||
* is created in-active.
|
|
||||||
*/
|
|
||||||
private void createScheduledAction(final Collection<Target> targets, final DistributionSet distributionSet,
|
|
||||||
final ActionType actionType, final Long forcedTime, final Rollout rollout,
|
|
||||||
final RolloutGroup rolloutGroup) {
|
|
||||||
// cancel all current scheduled actions for this target. E.g. an action
|
|
||||||
// is already scheduled and a next action is created then cancel the
|
|
||||||
// current scheduled action to cancel. E.g. a new scheduled action is
|
|
||||||
// created.
|
|
||||||
final List<Long> targetIds = targets.stream().map(Target::getId).collect(Collectors.toList());
|
|
||||||
deploymentManagement.cancelInactiveScheduledActionsForTargets(targetIds);
|
|
||||||
targets.forEach(target -> {
|
|
||||||
|
|
||||||
assertActionsPerTargetQuota(target, 1);
|
|
||||||
|
|
||||||
final JpaAction action = new JpaAction();
|
|
||||||
action.setTarget(target);
|
|
||||||
action.setActive(false);
|
|
||||||
action.setDistributionSet(distributionSet);
|
|
||||||
action.setActionType(actionType);
|
|
||||||
action.setForcedTime(forcedTime);
|
|
||||||
action.setStatus(Status.SCHEDULED);
|
|
||||||
action.setRollout(rollout);
|
|
||||||
action.setRolloutGroup(rolloutGroup);
|
|
||||||
action.setInitiatedBy(rollout.getCreatedBy());
|
|
||||||
rollout.getWeight().ifPresent(action::setWeight);
|
|
||||||
actionRepository.save(action);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
@Retryable(include = {
|
@Retryable(include = {
|
||||||
@@ -665,151 +388,6 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
|
|||||||
rolloutRepository.save(rollout);
|
rolloutRepository.save(rollout);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleRunningRollout(final JpaRollout rollout) {
|
|
||||||
LOGGER.debug("handleRunningRollout called for rollout {}", rollout.getId());
|
|
||||||
|
|
||||||
final List<JpaRolloutGroup> rolloutGroupsRunning = rolloutGroupRepository.findByRolloutAndStatus(rollout,
|
|
||||||
RolloutGroupStatus.RUNNING);
|
|
||||||
|
|
||||||
if (rolloutGroupsRunning.isEmpty()) {
|
|
||||||
// no running rollouts, probably there was an error
|
|
||||||
// somewhere at the latest group. And the latest group has
|
|
||||||
// been switched from running into error state. So we need
|
|
||||||
// to find the latest group which
|
|
||||||
executeLatestRolloutGroup(rollout);
|
|
||||||
} else {
|
|
||||||
LOGGER.debug("Rollout {} has {} running groups", rollout.getId(), rolloutGroupsRunning.size());
|
|
||||||
executeRolloutGroups(rollout, rolloutGroupsRunning);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isRolloutComplete(rollout)) {
|
|
||||||
LOGGER.info("Rollout {} is finished, setting FINISHED status", rollout);
|
|
||||||
rollout.setStatus(RolloutStatus.FINISHED);
|
|
||||||
rolloutRepository.save(rollout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void executeRolloutGroups(final JpaRollout rollout, final List<JpaRolloutGroup> rolloutGroups) {
|
|
||||||
for (final JpaRolloutGroup rolloutGroup : rolloutGroups) {
|
|
||||||
|
|
||||||
final long targetCount = countTargetsFrom(rolloutGroup);
|
|
||||||
if (rolloutGroup.getTotalTargets() != targetCount) {
|
|
||||||
updateTotalTargetCount(rolloutGroup, targetCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
// error state check, do we need to stop the whole
|
|
||||||
// rollout because of error?
|
|
||||||
final boolean isError = checkErrorState(rollout, rolloutGroup);
|
|
||||||
if (isError) {
|
|
||||||
LOGGER.info("Rollout {} {} has error, calling error action", rollout.getName(), rollout.getId());
|
|
||||||
callErrorAction(rollout, rolloutGroup);
|
|
||||||
} else {
|
|
||||||
// not in error so check finished state, do we need to
|
|
||||||
// start the next group?
|
|
||||||
final RolloutGroupSuccessCondition finishedCondition = rolloutGroup.getSuccessCondition();
|
|
||||||
checkFinishCondition(rollout, rolloutGroup, finishedCondition);
|
|
||||||
if (isRolloutGroupComplete(rollout, rolloutGroup)) {
|
|
||||||
rolloutGroup.setStatus(RolloutGroupStatus.FINISHED);
|
|
||||||
rolloutGroupRepository.save(rolloutGroup);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateTotalTargetCount(final JpaRolloutGroup rolloutGroup, final long countTargetsOfRolloutGroup) {
|
|
||||||
final JpaRollout jpaRollout = (JpaRollout) rolloutGroup.getRollout();
|
|
||||||
final long updatedTargetCount = jpaRollout.getTotalTargets()
|
|
||||||
- (rolloutGroup.getTotalTargets() - countTargetsOfRolloutGroup);
|
|
||||||
jpaRollout.setTotalTargets(updatedTargetCount);
|
|
||||||
rolloutGroup.setTotalTargets((int) countTargetsOfRolloutGroup);
|
|
||||||
rolloutRepository.save(jpaRollout);
|
|
||||||
rolloutGroupRepository.save(rolloutGroup);
|
|
||||||
}
|
|
||||||
|
|
||||||
private long countTargetsFrom(final JpaRolloutGroup rolloutGroup) {
|
|
||||||
return rolloutGroupManagement.countTargetsOfRolloutsGroup(rolloutGroup.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void executeLatestRolloutGroup(final JpaRollout rollout) {
|
|
||||||
final List<JpaRolloutGroup> latestRolloutGroup = rolloutGroupRepository
|
|
||||||
.findByRolloutAndStatusNotOrderByIdDesc(rollout, RolloutGroupStatus.SCHEDULED);
|
|
||||||
if (latestRolloutGroup.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
executeRolloutGroupSuccessAction(rollout, latestRolloutGroup.get(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void callErrorAction(final Rollout rollout, final RolloutGroup rolloutGroup) {
|
|
||||||
try {
|
|
||||||
context.getBean(rolloutGroup.getErrorAction().getBeanName(), RolloutGroupActionEvaluator.class)
|
|
||||||
.eval(rollout, rolloutGroup, rolloutGroup.getErrorActionExp());
|
|
||||||
} catch (final BeansException e) {
|
|
||||||
LOGGER.error("Something bad happend when accessing the error action bean {}",
|
|
||||||
rolloutGroup.getErrorAction().getBeanName(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isRolloutComplete(final JpaRollout rollout) {
|
|
||||||
// ensure that changes in the same transaction count
|
|
||||||
entityManager.flush();
|
|
||||||
final Long groupsActiveLeft = rolloutGroupRepository.countByRolloutIdAndStatusOrStatus(rollout.getId(),
|
|
||||||
RolloutGroupStatus.RUNNING, RolloutGroupStatus.SCHEDULED);
|
|
||||||
return groupsActiveLeft == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isRolloutGroupComplete(final JpaRollout rollout, final JpaRolloutGroup rolloutGroup) {
|
|
||||||
final Long actionsLeftForRollout = ActionType.DOWNLOAD_ONLY == rollout.getActionType()
|
|
||||||
? actionRepository.countByRolloutAndRolloutGroupAndStatusNotIn(rollout, rolloutGroup,
|
|
||||||
DOWNLOAD_ONLY_ACTION_TERMINATION_STATUSES)
|
|
||||||
: actionRepository.countByRolloutAndRolloutGroupAndStatusNotIn(rollout, rolloutGroup,
|
|
||||||
DEFAULT_ACTION_TERMINATION_STATUSES);
|
|
||||||
return actionsLeftForRollout == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean checkErrorState(final Rollout rollout, final RolloutGroup rolloutGroup) {
|
|
||||||
|
|
||||||
final RolloutGroupErrorCondition errorCondition = rolloutGroup.getErrorCondition();
|
|
||||||
|
|
||||||
if (errorCondition == null) {
|
|
||||||
// there is no error condition, so return false, don't have error.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return context.getBean(errorCondition.getBeanName(), RolloutGroupConditionEvaluator.class).eval(rollout,
|
|
||||||
rolloutGroup, rolloutGroup.getErrorConditionExp());
|
|
||||||
} catch (final BeansException e) {
|
|
||||||
LOGGER.error("Something bad happend when accessing the error condition bean {}",
|
|
||||||
errorCondition.getBeanName(), e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean checkFinishCondition(final Rollout rollout, final RolloutGroup rolloutGroup,
|
|
||||||
final RolloutGroupSuccessCondition finishCondition) {
|
|
||||||
LOGGER.trace("Checking finish condition {} on rolloutgroup {}", finishCondition, rolloutGroup);
|
|
||||||
try {
|
|
||||||
final boolean isFinished = context
|
|
||||||
.getBean(finishCondition.getBeanName(), RolloutGroupConditionEvaluator.class)
|
|
||||||
.eval(rollout, rolloutGroup, rolloutGroup.getSuccessConditionExp());
|
|
||||||
if (isFinished) {
|
|
||||||
LOGGER.debug("Rolloutgroup {} is finished, starting next group", rolloutGroup);
|
|
||||||
executeRolloutGroupSuccessAction(rollout, rolloutGroup);
|
|
||||||
} else {
|
|
||||||
LOGGER.debug("Rolloutgroup {} is still running", rolloutGroup);
|
|
||||||
}
|
|
||||||
return isFinished;
|
|
||||||
} catch (final BeansException e) {
|
|
||||||
LOGGER.error("Something bad happend when accessing the finish condition bean {}",
|
|
||||||
finishCondition.getBeanName(), e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void executeRolloutGroupSuccessAction(final Rollout rollout, final RolloutGroup rolloutGroup) {
|
|
||||||
context.getBean(rolloutGroup.getSuccessAction().getBeanName(), RolloutGroupActionEvaluator.class).eval(rollout,
|
|
||||||
rolloutGroup, rolloutGroup.getSuccessActionExp());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
// No transaction, will be created per handled rollout
|
// No transaction, will be created per handled rollout
|
||||||
@Transactional(propagation = Propagation.NEVER)
|
@Transactional(propagation = Propagation.NEVER)
|
||||||
@@ -839,51 +417,10 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
|
|||||||
private long handleRollout(final long rolloutId) {
|
private long handleRollout(final long rolloutId) {
|
||||||
final JpaRollout rollout = rolloutRepository.findById(rolloutId)
|
final JpaRollout rollout = rolloutRepository.findById(rolloutId)
|
||||||
.orElseThrow(() -> new EntityNotFoundException(Rollout.class, rolloutId));
|
.orElseThrow(() -> new EntityNotFoundException(Rollout.class, rolloutId));
|
||||||
runInUserContext(rollout, () -> handleRollout(rollout));
|
runInUserContext(rollout, () -> rolloutExecutor.execute(rollout));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleRollout(final JpaRollout rollout) {
|
|
||||||
LOGGER.debug("Handle rollout {}", rollout.getId());
|
|
||||||
switch (rollout.getStatus()) {
|
|
||||||
case CREATING:
|
|
||||||
handleCreateRollout(rollout);
|
|
||||||
break;
|
|
||||||
case DELETING:
|
|
||||||
handleDeleteRollout(rollout);
|
|
||||||
break;
|
|
||||||
case READY:
|
|
||||||
handleReadyRollout(rollout);
|
|
||||||
break;
|
|
||||||
case STARTING:
|
|
||||||
handleStartingRollout(rollout);
|
|
||||||
break;
|
|
||||||
case RUNNING:
|
|
||||||
handleRunningRollout(rollout);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
LOGGER.error("Rollout in status {} not supposed to be handled!", rollout.getStatus());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleStartingRollout(final Rollout rollout) {
|
|
||||||
LOGGER.debug("handleStartingRollout called for rollout {}", rollout.getId());
|
|
||||||
|
|
||||||
if (ensureAllGroupsAreScheduled(rollout)) {
|
|
||||||
startFirstRolloutGroup(rollout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleReadyRollout(final Rollout rollout) {
|
|
||||||
if (rollout.getStartAt() != null && rollout.getStartAt() <= System.currentTimeMillis()) {
|
|
||||||
LOGGER.debug(
|
|
||||||
"handleReadyRollout called for rollout {} with autostart beyond define time. Switch to STARTING",
|
|
||||||
rollout.getId());
|
|
||||||
start(rollout.getId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
@Retryable(include = {
|
@Retryable(include = {
|
||||||
@@ -904,85 +441,6 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
|
|||||||
rolloutRepository.save(jpaRollout);
|
rolloutRepository.save(jpaRollout);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleDeleteRollout(final JpaRollout rollout) {
|
|
||||||
LOGGER.debug("handleDeleteRollout called for {}", rollout.getId());
|
|
||||||
|
|
||||||
// check if there are actions beyond schedule
|
|
||||||
boolean hardDeleteRolloutGroups = !actionRepository.existsByRolloutIdAndStatusNotIn(rollout.getId(),
|
|
||||||
Status.SCHEDULED);
|
|
||||||
if (hardDeleteRolloutGroups) {
|
|
||||||
LOGGER.debug("Rollout {} has no actions other than scheduled -> hard delete", rollout.getId());
|
|
||||||
hardDeleteRollout(rollout);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// clean up all scheduled actions
|
|
||||||
final Slice<JpaAction> scheduledActions = findScheduledActionsByRollout(rollout);
|
|
||||||
deleteScheduledActions(rollout, scheduledActions);
|
|
||||||
|
|
||||||
// avoid another scheduler round and re-check if all scheduled actions
|
|
||||||
// has been cleaned up. we flush first to ensure that the we include the
|
|
||||||
// deletion above
|
|
||||||
entityManager.flush();
|
|
||||||
final boolean hasScheduledActionsLeft = actionRepository.countByRolloutIdAndStatus(rollout.getId(),
|
|
||||||
Status.SCHEDULED) > 0;
|
|
||||||
|
|
||||||
if (hasScheduledActionsLeft) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// only hard delete the rollout if no actions are left for the rollout.
|
|
||||||
// In case actions are left, they are probably are running or were
|
|
||||||
// running before, so only soft delete.
|
|
||||||
hardDeleteRolloutGroups = !actionRepository.existsByRolloutId(rollout.getId());
|
|
||||||
if (hardDeleteRolloutGroups) {
|
|
||||||
hardDeleteRollout(rollout);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// set soft delete
|
|
||||||
rollout.setStatus(RolloutStatus.DELETED);
|
|
||||||
rollout.setDeleted(true);
|
|
||||||
rolloutRepository.save(rollout);
|
|
||||||
|
|
||||||
sendRolloutGroupDeletedEvents(rollout);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendRolloutGroupDeletedEvents(final JpaRollout rollout) {
|
|
||||||
final List<Long> groupIds = rollout.getRolloutGroups().stream().map(RolloutGroup::getId)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
afterCommit.afterCommit(() -> groupIds.forEach(rolloutGroupId -> eventPublisherHolder.getEventPublisher()
|
|
||||||
.publishEvent(new RolloutGroupDeletedEvent(tenantAware.getCurrentTenant(), rolloutGroupId,
|
|
||||||
JpaRolloutGroup.class.getName(), eventPublisherHolder.getApplicationId()))));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void hardDeleteRollout(final JpaRollout rollout) {
|
|
||||||
sendRolloutGroupDeletedEvents(rollout);
|
|
||||||
rolloutRepository.delete(rollout);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deleteScheduledActions(final JpaRollout rollout, final Slice<JpaAction> scheduledActions) {
|
|
||||||
final boolean hasScheduledActions = scheduledActions.getNumberOfElements() > 0;
|
|
||||||
|
|
||||||
if (hasScheduledActions) {
|
|
||||||
try {
|
|
||||||
final Iterable<JpaAction> iterable = scheduledActions::iterator;
|
|
||||||
final List<Long> actionIds = StreamSupport.stream(iterable.spliterator(), false).map(Action::getId)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
actionRepository.deleteByIdIn(actionIds);
|
|
||||||
afterCommit.afterCommit(() -> eventPublisherHolder.getEventPublisher()
|
|
||||||
.publishEvent(new RolloutUpdatedEvent(rollout, eventPublisherHolder.getApplicationId())));
|
|
||||||
} catch (final RuntimeException e) {
|
|
||||||
LOGGER.error("Exception during deletion of actions of rollout {}", rollout, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Slice<JpaAction> findScheduledActionsByRollout(final JpaRollout rollout) {
|
|
||||||
return actionRepository.findByRolloutIdAndStatus(PageRequest.of(0, TRANSACTION_ACTIONS), rollout.getId(),
|
|
||||||
Status.SCHEDULED);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long count() {
|
public long count() {
|
||||||
return rolloutRepository.count(RolloutSpecification.isDeletedWithDistributionSet(false));
|
return rolloutRepository.count(RolloutSpecification.isDeletedWithDistributionSet(false));
|
||||||
@@ -1137,21 +595,6 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
|
|||||||
QuotaHelper.assertAssignmentQuota(requested, quota, Target.class, RolloutGroup.class);
|
QuotaHelper.assertAssignmentQuota(requested, quota, Target.class, RolloutGroup.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Enforces the quota defining the maximum number of {@link Action}s per
|
|
||||||
* {@link Target}.
|
|
||||||
*
|
|
||||||
* @param target
|
|
||||||
* The target
|
|
||||||
* @param requested
|
|
||||||
* number of actions to check
|
|
||||||
*/
|
|
||||||
private void assertActionsPerTargetQuota(final Target target, final int requested) {
|
|
||||||
final int quota = quotaManagement.getMaxActionsPerTarget();
|
|
||||||
QuotaHelper.assertAssignmentQuota(target.getId(), requested, quota, Action.class, Target.class,
|
|
||||||
actionRepository::countByTargetId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void runInUserContext(final BaseEntity rollout, final Runnable handler) {
|
private void runInUserContext(final BaseEntity rollout, final Runnable handler) {
|
||||||
DeploymentHelper.runInNonSystemContext(handler, () -> Objects.requireNonNull(rollout.getCreatedBy()), tenantAware);
|
DeploymentHelper.runInNonSystemContext(handler, () -> Objects.requireNonNull(rollout.getCreatedBy()), tenantAware);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import org.eclipse.hawkbit.repository.RepositoryDefaultConfiguration;
|
|||||||
import org.eclipse.hawkbit.repository.RepositoryProperties;
|
import org.eclipse.hawkbit.repository.RepositoryProperties;
|
||||||
import org.eclipse.hawkbit.repository.RolloutApprovalStrategy;
|
import org.eclipse.hawkbit.repository.RolloutApprovalStrategy;
|
||||||
import org.eclipse.hawkbit.repository.RolloutGroupManagement;
|
import org.eclipse.hawkbit.repository.RolloutGroupManagement;
|
||||||
|
import org.eclipse.hawkbit.repository.RolloutExecutor;
|
||||||
import org.eclipse.hawkbit.repository.RolloutManagement;
|
import org.eclipse.hawkbit.repository.RolloutManagement;
|
||||||
import org.eclipse.hawkbit.repository.RolloutStatusCache;
|
import org.eclipse.hawkbit.repository.RolloutStatusCache;
|
||||||
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
|
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
|
||||||
@@ -617,6 +618,23 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
|
|||||||
virtualPropertyReplacer, softwareModuleRepository, criteriaNoCountDao, properties.getDatabase());
|
virtualPropertyReplacer, softwareModuleRepository, criteriaNoCountDao, properties.getDatabase());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
RolloutExecutor rolloutExecutor(
|
||||||
|
final RolloutTargetGroupRepository rolloutTargetGroupRepository, final EntityManager entityManager,
|
||||||
|
final RolloutRepository rolloutRepository, final ActionRepository actionRepository,
|
||||||
|
final RolloutGroupRepository rolloutGroupRepository, final AfterTransactionCommitExecutor afterCommit,
|
||||||
|
final TenantAware tenantAware, final RolloutGroupManagement rolloutGroupManagement,
|
||||||
|
final QuotaManagement quotaManagement, final DeploymentManagement deploymentManagement,
|
||||||
|
final TargetManagement targetManagement, final EventPublisherHolder eventPublisherHolder,
|
||||||
|
final PlatformTransactionManager txManager, final RolloutApprovalStrategy rolloutApprovalStrategy,
|
||||||
|
final ApplicationContext context) {
|
||||||
|
return new JpaRolloutExecutor(rolloutTargetGroupRepository, entityManager, rolloutRepository, actionRepository,
|
||||||
|
rolloutGroupRepository, afterCommit, tenantAware, rolloutGroupManagement, quotaManagement,
|
||||||
|
deploymentManagement, targetManagement, eventPublisherHolder, txManager, rolloutApprovalStrategy,
|
||||||
|
context);
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
RolloutManagement rolloutManagement(final TargetManagement targetManagement,
|
RolloutManagement rolloutManagement(final TargetManagement targetManagement,
|
||||||
@@ -626,11 +644,11 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
|
|||||||
final PlatformTransactionManager txManager, final TenantAware tenantAware, final LockRegistry lockRegistry,
|
final PlatformTransactionManager txManager, final TenantAware tenantAware, final LockRegistry lockRegistry,
|
||||||
final JpaProperties properties, final RolloutApprovalStrategy rolloutApprovalStrategy,
|
final JpaProperties properties, final RolloutApprovalStrategy rolloutApprovalStrategy,
|
||||||
final TenantConfigurationManagement tenantConfigurationManagement,
|
final TenantConfigurationManagement tenantConfigurationManagement,
|
||||||
final SystemSecurityContext systemSecurityContext) {
|
final SystemSecurityContext systemSecurityContext, final RolloutExecutor rolloutExecutor) {
|
||||||
return new JpaRolloutManagement(targetManagement, deploymentManagement, rolloutGroupManagement,
|
return new JpaRolloutManagement(targetManagement, deploymentManagement, rolloutGroupManagement,
|
||||||
distributionSetManagement, context, eventPublisherHolder, virtualPropertyReplacer, txManager,
|
distributionSetManagement, context, eventPublisherHolder, virtualPropertyReplacer, txManager,
|
||||||
tenantAware, lockRegistry, properties.getDatabase(), rolloutApprovalStrategy,
|
tenantAware, lockRegistry, properties.getDatabase(), rolloutApprovalStrategy,
|
||||||
tenantConfigurationManagement, systemSecurityContext);
|
tenantConfigurationManagement, systemSecurityContext, rolloutExecutor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user