Optimize rollout group start (#318)

Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>
This commit is contained in:
Kai Zimmermann
2016-10-20 16:27:33 +02:00
committed by GitHub
parent 21d8636811
commit 3f49567cdc
55 changed files with 194 additions and 181 deletions

View File

@@ -37,6 +37,7 @@ import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.hateoas.Identifiable;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
@@ -146,7 +147,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
* @return a list of actions according to the searched target
*/
@Query("Select a from JpaAction a where a.target = :target order by a.id")
List<Action> findByTarget(@Param("target") final JpaTarget target);
List<Action> findByTarget(@Param("target") JpaTarget target);
/**
* Retrieves all {@link Action}s of a specific target and given active flag
@@ -347,8 +348,8 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
* @return the actions referring a specific rollout and a specific parent
* rolloutgroup in a specific status
*/
List<Action> findByRolloutAndRolloutGroupParentAndStatus(JpaRollout rollout, JpaRolloutGroup rolloutGroupParent,
Status actionStatus);
List<Identifiable<Long>> findByRolloutAndRolloutGroupParentAndStatus(JpaRollout rollout,
JpaRolloutGroup rolloutGroupParent, Status actionStatus);
/**
* Retrieves all actions for a specific rollout and in a specific status.

View File

@@ -18,7 +18,6 @@ import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.RepositoryConstants;
@@ -235,7 +234,7 @@ public class JpaControllerManagement implements ControllerManagement {
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Transactional(isolation = Isolation.READ_COMMITTED)
public Action addCancelActionStatus(final ActionStatus actionStatus) {
final JpaAction action = (JpaAction) actionStatus.getAction();
@@ -274,26 +273,27 @@ public class JpaControllerManagement implements ControllerManagement {
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public Action addUpdateActionStatus(@NotNull final ActionStatus actionStatus) {
final JpaAction action = (JpaAction) actionStatus.getAction();
@Transactional(isolation = Isolation.READ_COMMITTED)
public Action addUpdateActionStatus(final ActionStatus actionStatus) {
final Action action = actionStatus.getAction();
final Long actionId = action.getId();
// if action is already closed we accept further status updates if
// permitted so by configuration. This is especially useful if the
// action status feedback channel order from the device cannot be
// guaranteed. However, if an action is closed we do not accept further
// close messages.
if (actionIsNotActiveButIntermediateFeedbackStillAllowed(actionStatus, action)) {
if (actionIsNotActiveButIntermediateFeedbackStillAllowed(actionStatus, action.isActive())) {
LOG.debug("Update of actionStatus {} for action {} not possible since action not active anymore.",
actionStatus.getId(), action.getId());
actionStatus.getStatus(), actionId);
return action;
}
return handleAddUpdateActionStatus((JpaActionStatus) actionStatus, action);
return handleAddUpdateActionStatus((JpaActionStatus) actionStatus, actionId);
}
private boolean actionIsNotActiveButIntermediateFeedbackStillAllowed(final ActionStatus actionStatus,
final JpaAction action) {
return !action.isActive() && (repositoryProperties.isRejectActionStatusForClosedAction()
final boolean actionActive) {
return !actionActive && (repositoryProperties.isRejectActionStatusForClosedAction()
|| (Status.ERROR.equals(actionStatus.getStatus()) || Status.FINISHED.equals(actionStatus.getStatus())));
}
@@ -304,22 +304,22 @@ public class JpaControllerManagement implements ControllerManagement {
* @param action
* @return
*/
private Action handleAddUpdateActionStatus(final JpaActionStatus actionStatus, final JpaAction action) {
LOG.debug("addUpdateActionStatus for action {}", action.getId());
private Action handleAddUpdateActionStatus(final JpaActionStatus actionStatus, final Long actionId) {
LOG.debug("addUpdateActionStatus for action {}", actionId);
final JpaAction mergedAction = entityManager.merge(action);
JpaTarget mergedTarget = (JpaTarget) mergedAction.getTarget();
final JpaAction action = actionRepository.findById(actionId);
JpaTarget target = (JpaTarget) action.getTarget();
// check for a potential DOS attack
checkForToManyStatusEntries(action);
switch (actionStatus.getStatus()) {
case ERROR:
mergedTarget = DeploymentHelper.updateTargetInfo(mergedTarget, TargetUpdateStatus.ERROR, false,
targetInfoRepository, entityManager);
handleErrorOnAction(mergedAction, mergedTarget);
target = DeploymentHelper.updateTargetInfo(target, TargetUpdateStatus.ERROR, false, targetInfoRepository,
entityManager);
handleErrorOnAction(action, target);
break;
case FINISHED:
handleFinishedAndStoreInTargetStatus(mergedTarget, mergedAction);
handleFinishedAndStoreInTargetStatus(target, action);
break;
case CANCELED:
case WARNING:
@@ -330,9 +330,9 @@ public class JpaControllerManagement implements ControllerManagement {
actionStatusRepository.save(actionStatus);
LOG.debug("addUpdateActionStatus {} for target {} is finished.", action.getId(), mergedTarget.getId());
LOG.debug("addUpdateActionStatus {} for target {} is finished.", action, target.getId());
return actionRepository.save(mergedAction);
return actionRepository.save(action);
}
private void handleErrorOnAction(final JpaAction mergedAction, final JpaTarget mergedTarget) {

View File

@@ -31,6 +31,7 @@ import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.repository.ActionFields;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.DistributionSetAssignmentResult;
import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.eventbus.event.CancelTargetAssignmentEvent;
import org.eclipse.hawkbit.repository.eventbus.event.TargetAssignDistributionSetEvent;
@@ -188,8 +189,7 @@ public class JpaDeploymentManagement implements DeploymentManagement {
@Transactional(isolation = Isolation.READ_COMMITTED)
@CacheEvict(value = { "distributionUsageAssigned" }, allEntries = true)
public DistributionSetAssignmentResult assignDistributionSet(final Long dsID,
final Collection<TargetWithActionType> targets,
final String actionMessage) {
final Collection<TargetWithActionType> targets, final String actionMessage) {
final JpaDistributionSet set = distributoinSetRepository.findOne(dsID);
if (set == null) {
throw new EntityNotFoundException(
@@ -304,16 +304,7 @@ public class JpaDeploymentManagement implements DeploymentManagement {
// the initial running status because we will change the status
// of the action itself and with this action status we have a nicer
// action history.
targetIdsToActions.values().forEach(action -> {
final JpaActionStatus actionStatus = new JpaActionStatus();
actionStatus.setAction(action);
actionStatus.setOccurredAt(action.getCreatedAt());
actionStatus.setStatus(Status.RUNNING);
if(actionMessage != null) {
actionStatus.addMessage(actionMessage);
}
actionStatusRepository.save(actionStatus);
});
targetIdsToActions.values().forEach(action -> setRunningActionStatus(action, actionMessage));
// flush to get action IDs
entityManager.flush();
@@ -380,15 +371,12 @@ public class JpaDeploymentManagement implements DeploymentManagement {
*/
private Set<Long> overrideObsoleteUpdateActions(final List<Long> targetsIds) {
final Set<Long> cancelledTargetIds = new HashSet<>();
// Figure out if there are potential target/action combinations that
// need to be considered
// for cancelation
// need to be considered for cancellation
final List<JpaAction> activeActions = actionRepository
.findByActiveAndTargetIdInAndActionStatusNotEqualToAndDistributionSetRequiredMigrationStep(targetsIds,
Action.Status.CANCELING);
activeActions.forEach(action -> {
final Set<Long> cancelledTargetIds = activeActions.stream().map(action -> {
action.setStatus(Status.CANCELING);
// document that the status has been retrieved
@@ -397,8 +385,8 @@ public class JpaDeploymentManagement implements DeploymentManagement {
cancelAssignDistributionSetEvent(action.getTarget(), action.getId());
cancelledTargetIds.add(action.getTarget().getId());
});
return action.getTarget().getId();
}).collect(Collectors.toSet());
actionRepository.save(activeActions);
@@ -410,8 +398,8 @@ public class JpaDeploymentManagement implements DeploymentManagement {
@NotEmpty final List<String> tIDs, final ActionType actionType, final long forcedTime) {
return assignDistributionSetToTargets(set, tIDs.stream()
.map(t -> new TargetWithActionType(t, actionType, forcedTime)).collect(Collectors.toList()), null,
null, null);
.map(t -> new TargetWithActionType(t, actionType, forcedTime)).collect(Collectors.toList()), null, null,
null);
}
@Override
@@ -511,55 +499,72 @@ public class JpaDeploymentManagement implements DeploymentManagement {
@Override
@Modifying
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
public Action startScheduledAction(final Action action) {
public Action startScheduledAction(final Long actionId) {
final JpaAction mergedAction = (JpaAction) entityManager.merge(action);
final JpaTarget mergedTarget = (JpaTarget) entityManager.merge(action.getTarget());
final JpaAction action = actionRepository.findById(actionId);
// check if we need to override running update actions
final Set<Long> overrideObsoleteUpdateActions = overrideObsoleteUpdateActions(
Collections.singletonList(action.getTarget().getId()));
final boolean hasDistributionSetAlreadyAssigned = targetRepository
.count(TargetSpecifications.hasControllerIdAndAssignedDistributionSetIdNot(
Collections.singletonList(mergedTarget.getControllerId()),
action.getDistributionSet().getId())) == 0;
if (hasDistributionSetAlreadyAssigned) {
if (action.getTarget().getAssignedDistributionSet() != null && action.getDistributionSet().getId()
.equals(action.getTarget().getAssignedDistributionSet().getId())) {
// the target has already the distribution set assigned, we don't
// need to start the scheduled action, just finished it.
mergedAction.setStatus(Status.FINISHED);
mergedAction.setActive(false);
return actionRepository.save(mergedAction);
// need to start the scheduled action, just finish it.
action.setStatus(Status.FINISHED);
action.setActive(false);
setSkipActionStatus(action);
return actionRepository.save(action);
}
mergedAction.setActive(true);
mergedAction.setStatus(Status.RUNNING);
final Action savedAction = actionRepository.save(mergedAction);
action.setActive(true);
action.setStatus(Status.RUNNING);
final JpaAction savedAction = actionRepository.save(action);
final JpaActionStatus actionStatus = new JpaActionStatus();
actionStatus.setAction(action);
actionStatus.setOccurredAt(action.getCreatedAt());
actionStatus.setStatus(Status.RUNNING);
actionStatusRepository.save(actionStatus);
setRunningActionStatus(savedAction, null);
mergedTarget.setAssignedDistributionSet(action.getDistributionSet());
final JpaTargetInfo targetInfo = (JpaTargetInfo) mergedTarget.getTargetInfo();
final JpaTarget target = (JpaTarget) savedAction.getTarget();
target.setAssignedDistributionSet(savedAction.getDistributionSet());
final JpaTargetInfo targetInfo = (JpaTargetInfo) target.getTargetInfo();
targetInfo.setUpdateStatus(TargetUpdateStatus.PENDING);
targetRepository.save(mergedTarget);
targetRepository.save(target);
targetInfoRepository.save(targetInfo);
// in case we canceled an action before for this target, then don't fire
// assignment event
if (!overrideObsoleteUpdateActions.contains(savedAction.getId())) {
final List<JpaSoftwareModule> softwareModules = softwareModuleRepository
.findByAssignedTo((JpaDistributionSet) action.getDistributionSet());
.findByAssignedTo((JpaDistributionSet) savedAction.getDistributionSet());
// send distribution set assignment event
assignDistributionSetEvent((JpaTarget) mergedAction.getTarget(), mergedAction.getId(), softwareModules);
assignDistributionSetEvent((JpaTarget) savedAction.getTarget(), savedAction.getId(), softwareModules);
}
return savedAction;
}
private void setRunningActionStatus(final JpaAction action, final String actionMessage) {
final JpaActionStatus actionStatus = new JpaActionStatus();
actionStatus.setAction(action);
actionStatus.setOccurredAt(action.getCreatedAt());
actionStatus.setStatus(Status.RUNNING);
if (actionMessage != null) {
actionStatus.addMessage(actionMessage);
}
actionStatusRepository.save(actionStatus);
}
private void setSkipActionStatus(final JpaAction action) {
final JpaActionStatus actionStatus = new JpaActionStatus();
actionStatus.setAction(action);
actionStatus.setOccurredAt(action.getCreatedAt());
actionStatus.setStatus(Status.RUNNING);
actionStatus.addMessage(RepositoryConstants.SERVER_MESSAGE_PREFIX
+ "Distribution Set is already assigned. Skipping this action.");
actionStatusRepository.save(actionStatus);
}
@Override
public Action findAction(final Long actionId) {
return actionRepository.findOne(actionId);
@@ -679,10 +684,11 @@ public class JpaDeploymentManagement implements DeploymentManagement {
}
@Override
public List<Action> findActionsByRolloutGroupParentAndStatus(final Rollout rollout,
public List<Long> findActionsByRolloutGroupParentAndStatus(final Rollout rollout,
final RolloutGroup rolloutGroupParent, final Action.Status actionStatus) {
return actionRepository.findByRolloutAndRolloutGroupParentAndStatus((JpaRollout) rollout,
(JpaRolloutGroup) rolloutGroupParent, actionStatus);
(JpaRolloutGroup) rolloutGroupParent, actionStatus).stream().map(ident -> ident.getId())
.collect(Collectors.toList());
}
@Override

View File

@@ -12,6 +12,8 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.repository.TargetFields;
import org.eclipse.hawkbit.repository.TargetFilterQueryFields;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
@@ -37,8 +39,6 @@ import org.springframework.validation.annotation.Validated;
import com.google.common.base.Strings;
import javax.validation.constraints.NotNull;
/**
* JPA implementation of {@link TargetFilterQueryManagement}.
*

View File

@@ -11,6 +11,8 @@ package org.eclipse.hawkbit.repository.jpa.autoassign;
import java.util.List;
import java.util.stream.Collectors;
import javax.persistence.PersistenceException;
import org.eclipse.hawkbit.exception.AbstractServerRtException;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
@@ -32,8 +34,6 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionTemplate;
import javax.persistence.PersistenceException;
/**
* Checks if targets need a new distribution set (DS) based on the target filter
* queries and assigns the new DS when necessary. First all target filter

View File

@@ -54,10 +54,10 @@ public class StartNextGroupRolloutGroupSuccessAction implements RolloutGroupActi
}
private void startNextGroup(final Rollout rollout, final RolloutGroup rolloutGroup) {
// retrieve all actions accroding to the parent group of the finished
// retrieve all actions according to the parent group of the finished
// rolloutGroup, so retrieve all child-group actions which need to be
// started.
final List<Action> rolloutGroupActions = deploymentManagement.findActionsByRolloutGroupParentAndStatus(rollout,
final List<Long> rolloutGroupActions = deploymentManagement.findActionsByRolloutGroupParentAndStatus(rollout,
rolloutGroup, Action.Status.SCHEDULED);
logger.debug("{} Next actions to start for rollout {} and parent group {}", rolloutGroupActions.size(), rollout,
rolloutGroup);
@@ -65,14 +65,8 @@ public class StartNextGroupRolloutGroupSuccessAction implements RolloutGroupActi
logger.debug("{} actions started for rollout {} and parent group {}", rolloutGroupActions.size(), rollout,
rolloutGroup);
if (!rolloutGroupActions.isEmpty()) {
// get all next scheduled groups based on the found actions and set
// them in state running
rolloutGroupActions.forEach(action -> {
final RolloutGroup nextGroup = action.getRolloutGroup();
logger.debug("Rolloutgroup {} is now running", nextGroup);
nextGroup.setStatus(RolloutGroupStatus.RUNNING);
rolloutGroupRepository.save((JpaRolloutGroup) nextGroup);
});
// get all next scheduled groups and set them in state running
rolloutGroupRepository.setStatusForCildren(RolloutGroupStatus.RUNNING, rolloutGroup);
} else {
logger.info("No actions to start for next rolloutgroup of parent {}", rolloutGroup);
// nothing for next group, just finish the group, this can happen