* [#1548] Add support for dynamic rollouts -- Current status -- Initial draft only !!!, to be improved TODO: * evaluate the target count - if update group/rollout total count fails dynamic updates could (?), actually, contain more targets * is it needed to break handler on group creating? * if dynamic group schedulers occur to be heavy - maybe a handler per tenant will ensure that one tenant won't break all *Concept for dynamic groups*: Rollouts are static and dynamic. Static rollouts consist of static groups only while dynamic rollouts have a number of static groups (first groups) and then an unlimited number of dynamic groups. Group targets assignments: * static groups include ALL matching targets created at the time the rollout was created, nevertheless they have active actions with bigger weight or not. Actions for the rollout and included targeets however are created at the start time. * dynamic groups however are filled in when started and consider the action weight. The targets included in a dynamic group are: * matching (filter and distribution set compatible) * not included in this or following rollout static groups (if already included in any of the following rollouts - it's intended to be overridden) * not in active actions of any rollouts with equal or bigger weight In general, when you create a rollout it contains all matching targets available at create time overriding any previous rollouts, actions, and so on. If the rollout is dynamic when its dynamic group becomes running it gets only matching targets that doesn't belong to static groups or have actions with great or equal weight Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com> * [#1548] Add 1000 weight for actions, rollouts and auto assignments without weight Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com> --------- Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>
This commit is contained in:
@@ -9,8 +9,11 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
@@ -19,6 +22,7 @@ import javax.persistence.EntityManager;
|
||||
|
||||
import org.eclipse.hawkbit.repository.DeploymentManagement;
|
||||
import org.eclipse.hawkbit.repository.QuotaManagement;
|
||||
import org.eclipse.hawkbit.repository.RepositoryProperties;
|
||||
import org.eclipse.hawkbit.repository.RolloutApprovalStrategy;
|
||||
import org.eclipse.hawkbit.repository.RolloutExecutor;
|
||||
import org.eclipse.hawkbit.repository.RolloutGroupManagement;
|
||||
@@ -28,9 +32,9 @@ import org.eclipse.hawkbit.repository.TargetManagement;
|
||||
import org.eclipse.hawkbit.repository.event.remote.RolloutGroupDeletedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.RolloutStoppedEvent;
|
||||
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.management.JpaRolloutManagement;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup;
|
||||
@@ -64,7 +68,8 @@ import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionException;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.eclipse.hawkbit.repository.jpa.builder.JpaRolloutGroupCreate.addSuccessAndErrorConditionsAndActions;
|
||||
|
||||
/**
|
||||
* A Jpa implementation of {@link RolloutExecutor}
|
||||
@@ -186,7 +191,8 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
||||
continue;
|
||||
}
|
||||
|
||||
final RolloutGroup filledGroup = fillRolloutGroupWithTargets(rollout, group);
|
||||
final RolloutGroup filledGroup = fillRolloutGroupWithTargets(rollout, (JpaRolloutGroup) group,
|
||||
rolloutGroups);
|
||||
if (RolloutGroupStatus.READY == filledGroup.getStatus()) {
|
||||
readyGroups++;
|
||||
totalTargets += filledGroup.getTotalTargets();
|
||||
@@ -196,6 +202,11 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
||||
// When all groups are ready the rollout status can be changed to be
|
||||
// ready, too.
|
||||
if (readyGroups == rolloutGroups.size()) {
|
||||
if (rollout.isDynamic()) {
|
||||
// add first dynamic group one by using the last as a parent and as a pattern
|
||||
createDynamicGroup(rollout, rolloutGroups.get(rolloutGroups.size() - 1), rolloutGroups.size(), RolloutGroupStatus.READY);
|
||||
}
|
||||
|
||||
if (!rolloutApprovalStrategy.isApprovalNeeded(rollout)) {
|
||||
rollout.setStatus(RolloutStatus.READY);
|
||||
LOGGER.debug("rollout {} creation done. Switch to READY.", rollout.getId());
|
||||
@@ -306,8 +317,18 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
||||
private void handleRunningRollout(final JpaRollout rollout) {
|
||||
LOGGER.debug("handleRunningRollout called for rollout {}", rollout.getId());
|
||||
|
||||
final List<JpaRolloutGroup> rolloutGroupsRunning = rolloutGroupRepository.findByRolloutAndStatus(rollout,
|
||||
RolloutGroupStatus.RUNNING);
|
||||
if (rollout.isDynamic()) {
|
||||
if (fillDynamicRolloutGroupsWithTargets(rollout)) {
|
||||
LOGGER.debug("Dynamic group created for rollout {}", rollout.getId());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final List<JpaRolloutGroup> rolloutGroupsRunning =
|
||||
rollout.getRolloutGroups().stream()
|
||||
.filter(group -> group.getStatus() == RolloutGroupStatus.RUNNING)
|
||||
.map(JpaRolloutGroup.class::cast)
|
||||
.toList();
|
||||
|
||||
if (rolloutGroupsRunning.isEmpty()) {
|
||||
// no running rollouts, probably there was an error
|
||||
@@ -317,7 +338,7 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
||||
executeLatestRolloutGroup(rollout);
|
||||
} else {
|
||||
LOGGER.debug("Rollout {} has {} running groups", rollout.getId(), rolloutGroupsRunning.size());
|
||||
executeRolloutGroups(rollout, rolloutGroupsRunning);
|
||||
executeRolloutGroups(rollout, rolloutGroupsRunning, rollout.getRolloutGroups().get(rollout.getRolloutGroups().size() - 1));
|
||||
}
|
||||
|
||||
if (isRolloutComplete(rollout)) {
|
||||
@@ -371,35 +392,77 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
||||
return groupsActiveLeft == 0;
|
||||
}
|
||||
|
||||
private static final Comparator<RolloutGroup> DESC_COMP = Comparator.comparingLong(RolloutGroup::getId).reversed();
|
||||
private void executeLatestRolloutGroup(final JpaRollout rollout) {
|
||||
final List<JpaRolloutGroup> latestRolloutGroup = rolloutGroupRepository
|
||||
.findByRolloutAndStatusNotOrderByIdDesc(rollout, RolloutGroupStatus.SCHEDULED);
|
||||
// was - rolloutGroupRepository.findByRolloutAndStatusNotOrderByIdDesc(rollout, RolloutGroupStatus.SCHEDULED);
|
||||
final List<JpaRolloutGroup> latestRolloutGroup = rollout.getRolloutGroups().stream()
|
||||
.filter(group -> group.getStatus() != RolloutGroupStatus.SCHEDULED)
|
||||
.sorted(DESC_COMP)
|
||||
.map(JpaRolloutGroup.class::cast)
|
||||
.toList();
|
||||
if (latestRolloutGroup.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
executeRolloutGroupSuccessAction(rollout, latestRolloutGroup.get(0));
|
||||
}
|
||||
|
||||
private void executeRolloutGroups(final JpaRollout rollout, final List<JpaRolloutGroup> rolloutGroups) {
|
||||
for (final JpaRolloutGroup rolloutGroup : rolloutGroups) {
|
||||
private static final int DEFAULT_DYNAMIC_GROUP_EXPECTED = 100;
|
||||
private static int expectedDynamicGroupSize(final List<? extends RolloutGroup> rolloutGroups) {
|
||||
int expected = 0;
|
||||
// gets the size of last static group (it is a pattern for dynamic groups)
|
||||
for (final RolloutGroup rolloutGroup : rolloutGroups) {
|
||||
if (rolloutGroup.isDynamic()) {
|
||||
break;
|
||||
}
|
||||
expected = rolloutGroup.getTotalTargets();
|
||||
}
|
||||
return expected <= 0 ? DEFAULT_DYNAMIC_GROUP_EXPECTED /* default if the last static group has been empty */ : expected;
|
||||
}
|
||||
|
||||
// fakes getTotalTargets count to match expected for the last dynamic group
|
||||
// so the evaluation to use total targets to properly
|
||||
private RolloutGroup evalProxy(final RolloutGroup group, final List<JpaRolloutGroup> rolloutGroups) {
|
||||
if (group.isDynamic()) {
|
||||
final int expected = expectedDynamicGroupSize(rolloutGroups);
|
||||
return (RolloutGroup) Proxy.newProxyInstance(
|
||||
RolloutGroup.class.getClassLoader(),
|
||||
new Class<?>[] {RolloutGroup.class},
|
||||
(proxy, method, args) -> {
|
||||
if ("getTotalTargets".equals(method.getName())) {
|
||||
return expected;
|
||||
} else {
|
||||
try {
|
||||
return method.invoke(group, args);
|
||||
} catch (final InvocationTargetException e) {
|
||||
throw e.getCause() == null ? e : e.getCause();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return group;
|
||||
}
|
||||
}
|
||||
|
||||
private void executeRolloutGroups(final JpaRollout rollout, final List<JpaRolloutGroup> rolloutGroups, final RolloutGroup lastRolloutGroup) {
|
||||
for (final JpaRolloutGroup rolloutGroup : rolloutGroups) {
|
||||
final long targetCount = countTargetsFrom(rolloutGroup);
|
||||
if (rolloutGroup.getTotalTargets() != targetCount) {
|
||||
updateTotalTargetCount(rolloutGroup, targetCount);
|
||||
}
|
||||
|
||||
final RolloutGroup evalProxy = rolloutGroup == rolloutGroups.get(rolloutGroups.size() - 1) ?
|
||||
evalProxy(rolloutGroup, rolloutGroups) : rolloutGroup;
|
||||
// error state check, do we need to stop the whole
|
||||
// rollout because of error?
|
||||
final boolean isError = checkErrorState(rollout, rolloutGroup);
|
||||
final boolean isError = checkErrorState(rollout, evalProxy);
|
||||
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)) {
|
||||
checkSuccessCondition(rollout, rolloutGroup, evalProxy, rolloutGroup.getSuccessCondition());
|
||||
if (!(rolloutGroup == lastRolloutGroup && rolloutGroup.isDynamic()) && isRolloutGroupComplete(rollout, rolloutGroup)) {
|
||||
rolloutGroup.setStatus(RolloutGroupStatus.FINISHED);
|
||||
rolloutGroupRepository.save(rolloutGroup);
|
||||
}
|
||||
@@ -418,7 +481,11 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
||||
}
|
||||
|
||||
private long countTargetsFrom(final JpaRolloutGroup rolloutGroup) {
|
||||
return rolloutGroupManagement.countTargetsOfRolloutsGroup(rolloutGroup.getId());
|
||||
if (rolloutGroup.isDynamic()) {
|
||||
return targetManagement.countByActionsInRolloutGroup(rolloutGroup.getId());
|
||||
} else {
|
||||
return rolloutGroupManagement.countTargetsOfRolloutsGroup(rolloutGroup.getId());
|
||||
}
|
||||
}
|
||||
|
||||
private void callErrorAction(final Rollout rollout, final RolloutGroup rolloutGroup) {
|
||||
@@ -431,11 +498,11 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
||||
}
|
||||
|
||||
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);
|
||||
final Long actionsLeftForRollout =
|
||||
actionRepository.countByRolloutAndRolloutGroupAndStatusNotIn(
|
||||
rollout, rolloutGroup,
|
||||
ActionType.DOWNLOAD_ONLY == rollout.getActionType() ?
|
||||
DOWNLOAD_ONLY_ACTION_TERMINATION_STATUSES : DEFAULT_ACTION_TERMINATION_STATUSES);
|
||||
return actionsLeftForRollout == 0;
|
||||
}
|
||||
|
||||
@@ -456,12 +523,12 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkFinishCondition(final Rollout rollout, final RolloutGroup rolloutGroup,
|
||||
final RolloutGroupSuccessCondition finishCondition) {
|
||||
LOGGER.trace("Checking finish condition {} on rolloutgroup {}", finishCondition, rolloutGroup);
|
||||
private boolean checkSuccessCondition(final Rollout rollout, final RolloutGroup rolloutGroup, final RolloutGroup evalProxy,
|
||||
final RolloutGroupSuccessCondition successCondition) {
|
||||
LOGGER.trace("Checking finish condition {} on rolloutgroup {}", successCondition, rolloutGroup);
|
||||
try {
|
||||
final boolean isFinished = evaluationManager.getSuccessConditionEvaluator(finishCondition).eval(rollout,
|
||||
rolloutGroup, rolloutGroup.getSuccessConditionExp());
|
||||
final boolean isFinished = evaluationManager.getSuccessConditionEvaluator(successCondition).eval(rollout,
|
||||
evalProxy, rolloutGroup.getSuccessConditionExp());
|
||||
if (isFinished) {
|
||||
LOGGER.debug("Rolloutgroup {} is finished, starting next group", rolloutGroup);
|
||||
executeRolloutGroupSuccessAction(rollout, rolloutGroup);
|
||||
@@ -471,7 +538,7 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
||||
return isFinished;
|
||||
} catch (final EvaluatorNotConfiguredException e) {
|
||||
LOGGER.error("Something bad happened when accessing the finish condition or success action bean {}",
|
||||
finishCondition.name(), e);
|
||||
successCondition.name(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -513,18 +580,12 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
||||
return scheduledGroups == groupsToBeScheduled.size();
|
||||
}
|
||||
|
||||
private RolloutGroup fillRolloutGroupWithTargets(final JpaRollout rollout, final RolloutGroup group1) {
|
||||
private RolloutGroup fillRolloutGroupWithTargets(final JpaRollout rollout, final JpaRolloutGroup group,
|
||||
final List<RolloutGroup> rolloutGroups) {
|
||||
RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.CREATING);
|
||||
|
||||
final JpaRolloutGroup group = (JpaRolloutGroup) group1;
|
||||
|
||||
final String baseFilter = RolloutHelper.getTargetFilterQuery(rollout);
|
||||
final String groupTargetFilter;
|
||||
if (StringUtils.hasText(group.getTargetFilterQuery())) {
|
||||
groupTargetFilter = baseFilter + ";" + group.getTargetFilterQuery();
|
||||
} else {
|
||||
groupTargetFilter = baseFilter;
|
||||
}
|
||||
final String groupTargetFilter = RolloutHelper.getGroupTargetFilter(
|
||||
RolloutHelper.getTargetFilterQuery(rollout), group);
|
||||
|
||||
final List<Long> readyGroups = RolloutHelper.getGroupsByStatusIncludingGroup(rollout.getRolloutGroups(),
|
||||
RolloutGroupStatus.READY, group);
|
||||
@@ -541,28 +602,38 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
||||
count -> targetManagement.countByFailedRolloutAndNotInRolloutGroups(readyGroups,
|
||||
RolloutHelper.getIdFromRetriedTargetFilter(rollout.getTargetFilterQuery())));
|
||||
}
|
||||
final long expectedInGroup = Math
|
||||
.round((double) (group.getTargetPercentage() / 100) * (double) targetsInGroupFilter);
|
||||
|
||||
final double percentFromTheRest;
|
||||
if (rollout.isNewStyleTargetPercent()) { // new style percent - total percent
|
||||
percentFromTheRest = RolloutHelper.toPercentFromTheRest(group, rolloutGroups);
|
||||
} else { // old style percent - percent from rest
|
||||
percentFromTheRest = group.getTargetPercentage();
|
||||
}
|
||||
|
||||
final long expectedInGroup = Math.round(percentFromTheRest * targetsInGroupFilter / 100);
|
||||
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
|
||||
// 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,
|
||||
final long assigned = assignTargetsToGroupInNewTransaction(rollout, group, groupTargetFilter,
|
||||
Math.min(TRANSACTION_TARGETS, targetsLeftToAdd));
|
||||
if (assigned == 0) {
|
||||
break; // percent > 100 or some could have disappeared
|
||||
} else {
|
||||
targetsLeftToAdd -= assigned;
|
||||
}
|
||||
} while (targetsLeftToAdd > 0);
|
||||
|
||||
group.setStatus(RolloutGroupStatus.READY);
|
||||
@@ -579,7 +650,6 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
||||
|
||||
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(),
|
||||
@@ -587,10 +657,10 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
||||
Slice<Target> targets;
|
||||
if (!RolloutHelper.isRolloutRetried(rollout.getTargetFilterQuery())) {
|
||||
targets = targetManagement.findByTargetFilterQueryAndNotInRolloutGroupsAndCompatibleAndUpdatable(
|
||||
pageRequest, readyGroups, targetFilter, rollout.getDistributionSet().getType());
|
||||
pageRequest, readyGroups, targetFilter, rollout.getDistributionSet().getType());
|
||||
} else {
|
||||
targets = targetManagement.findByFailedRolloutAndNotInRolloutGroups(
|
||||
pageRequest, readyGroups, RolloutHelper.getIdFromRetriedTargetFilter(rollout.getTargetFilterQuery()));
|
||||
pageRequest, readyGroups, RolloutHelper.getIdFromRetriedTargetFilter(rollout.getTargetFilterQuery()));
|
||||
}
|
||||
|
||||
createAssignmentOfTargetsToGroup(targets, group);
|
||||
@@ -599,6 +669,121 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
||||
});
|
||||
}
|
||||
|
||||
// return if group change is made
|
||||
private boolean fillDynamicRolloutGroupsWithTargets(final JpaRollout rollout) {
|
||||
RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.RUNNING);
|
||||
final List<RolloutGroup> rolloutGroups = rollout.getRolloutGroups();
|
||||
|
||||
final JpaRolloutGroup group = (JpaRolloutGroup)rolloutGroups.get(rolloutGroups.size() - 1);
|
||||
|
||||
if (group.getStatus() == RolloutGroupStatus.FINISHED) {
|
||||
createDynamicGroup(rollout, group, rolloutGroups.size(), RolloutGroupStatus.RUNNING);
|
||||
return true;
|
||||
} else if (group.getStatus() != RolloutGroupStatus.RUNNING) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// expected as last full group - last static or previously filled in dynamic group
|
||||
final long expectedInGroup = expectedDynamicGroupSize(rolloutGroups);
|
||||
|
||||
final long currentlyInGroup = group.getTotalTargets();
|
||||
if (currentlyInGroup >= expectedInGroup) {
|
||||
// the last one is filled. create new and start filling it
|
||||
createDynamicGroup(rollout, group, rolloutGroups.size(), RolloutGroupStatus.SCHEDULED);
|
||||
return true;
|
||||
}
|
||||
|
||||
// there are more to be filled for that group
|
||||
// do this until there are more matching
|
||||
try {
|
||||
long targetsLeftToAdd = expectedInGroup - currentlyInGroup;
|
||||
final String groupTargetFilter = RolloutHelper.getGroupTargetFilter(
|
||||
// don't use RolloutHelper.getTargetFilterQuery(rollout)
|
||||
// since it contains condition for device to be created
|
||||
// before the rollout
|
||||
rollout.getTargetFilterQuery(), group);
|
||||
long newActions = 0;
|
||||
do {
|
||||
// Add up to TRANSACTION_TARGETS actions of the left targets
|
||||
// In case a TransactionException is thrown this loop aborts
|
||||
final long createdActions = createActionsForDynamicGroupInNewTransaction(rollout, group, groupTargetFilter,
|
||||
Math.min(TRANSACTION_TARGETS, targetsLeftToAdd));
|
||||
if (createdActions == 0) {
|
||||
break; // no more to assign
|
||||
} else {
|
||||
newActions += createdActions;
|
||||
targetsLeftToAdd -= createdActions;
|
||||
}
|
||||
} while (targetsLeftToAdd > 0);
|
||||
|
||||
if (newActions > 0) {
|
||||
updateTotalTargetCount(group, group.getTotalTargets() + newActions);
|
||||
|
||||
if (targetsLeftToAdd == 0) {
|
||||
// this is filled create a new one in sheduled state
|
||||
createDynamicGroup(rollout, group, rolloutGroups.size(), RolloutGroupStatus.SCHEDULED);
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO - try to return false and proceed with handleRunningRollout
|
||||
// the problem is that OptimisticLockException is thrown in that case
|
||||
return true;
|
||||
}
|
||||
} catch (final TransactionException e) {
|
||||
LOGGER.warn("Transaction assigning Targets to RolloutGroup failed", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void createDynamicGroup(final JpaRollout rollout, final RolloutGroup lastGroup, final int groupCount, final RolloutGroupStatus status) {
|
||||
final JpaRolloutGroup group = new JpaRolloutGroup();
|
||||
final String nameAndDesc = "group-" + (groupCount + 1) + "-dynamic";
|
||||
group.setDynamic(true);
|
||||
group.setName(nameAndDesc);
|
||||
group.setDescription(nameAndDesc);
|
||||
group.setRollout(rollout);
|
||||
group.setParent(lastGroup);
|
||||
// no need to be filled with targets, directly in ready (if first on create - it will be scheduled on start)
|
||||
// or scheduled state (for next dynamic groups)
|
||||
group.setStatus(status);
|
||||
group.setConfirmationRequired(lastGroup.isConfirmationRequired());
|
||||
|
||||
group.setTargetPercentage(lastGroup.getTargetPercentage());
|
||||
group.setTargetFilterQuery(lastGroup.getTargetFilterQuery());
|
||||
|
||||
addSuccessAndErrorConditionsAndActions(group, lastGroup.getSuccessCondition(),
|
||||
lastGroup.getSuccessConditionExp(), lastGroup.getSuccessAction(),
|
||||
lastGroup.getSuccessActionExp(), lastGroup.getErrorCondition(),
|
||||
lastGroup.getErrorConditionExp(), lastGroup.getErrorAction(),
|
||||
lastGroup.getErrorActionExp());
|
||||
|
||||
final JpaRolloutGroup savedGroup = rolloutGroupRepository.save(group);
|
||||
rollout.setRolloutGroupsCreated(rollout.getRolloutGroupsCreated() + 1);
|
||||
rolloutRepository.save(rollout);
|
||||
((JpaRolloutManagement) rolloutManagement).publishRolloutGroupCreatedEventAfterCommit(savedGroup, rollout);
|
||||
}
|
||||
|
||||
private Long createActionsForDynamicGroupInNewTransaction(final JpaRollout rollout, final RolloutGroup group,
|
||||
final String targetFilter, final long limit) {
|
||||
return DeploymentHelper.runInNewTransaction(txManager, "createActionsForRolloutDynamicGroup", status -> {
|
||||
final PageRequest pageRequest = PageRequest.of(0, Math.toIntExact(limit));
|
||||
final Slice<Target> targets = targetManagement.findByNotInGEGroupAndNotInActiveActionGEWeightOrInRolloutAndTargetFilterQueryAndCompatibleAndUpdatable(
|
||||
pageRequest,
|
||||
rollout.getId(), rollout.getWeight().orElse(1000), // Dynamic rollouts shall always have weight!
|
||||
rolloutGroupRepository.findByRolloutOrderByIdAsc(rollout).get(0).getId(),
|
||||
targetFilter, rollout.getDistributionSet().getType());
|
||||
|
||||
if (targets.getNumberOfElements() > 0) {
|
||||
final DistributionSet distributionSet = rollout.getDistributionSet();
|
||||
final ActionType actionType = rollout.getActionType();
|
||||
final long forceTime = rollout.getForcedTime();
|
||||
createActions(targets.getContent(), distributionSet, actionType, forceTime, rollout, 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.
|
||||
@@ -613,8 +798,10 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
||||
}
|
||||
|
||||
if (actionsLeft <= 0) {
|
||||
group.setStatus(RolloutGroupStatus.SCHEDULED);
|
||||
rolloutGroupRepository.save(group);
|
||||
if (group.getStatus() != RolloutGroupStatus.SCHEDULED && group.getStatus() != RolloutGroupStatus.RUNNING) { // dynamic groups could already be running
|
||||
group.setStatus(RolloutGroupStatus.SCHEDULED);
|
||||
rolloutGroupRepository.save(group);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -625,8 +812,7 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
||||
try {
|
||||
long actionsCreated;
|
||||
do {
|
||||
actionsCreated = createActionsForTargetsInNewTransaction(rollout.getId(), group.getId(),
|
||||
TRANSACTION_TARGETS);
|
||||
actionsCreated = createActionsForTargetsInNewTransaction(rollout, group, TRANSACTION_TARGETS);
|
||||
totalActionsCreated += actionsCreated;
|
||||
} while (actionsCreated > 0);
|
||||
|
||||
@@ -637,21 +823,17 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
||||
return totalActionsCreated;
|
||||
}
|
||||
|
||||
private Long createActionsForTargetsInNewTransaction(final long rolloutId, final long groupId, final int limit) {
|
||||
private Long createActionsForTargetsInNewTransaction(
|
||||
final Rollout rollout, final RolloutGroup group, 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 Slice<Target> targets =
|
||||
targetManagement.findByInRolloutGroupWithoutAction(PageRequest.of(0, limit), group.getId());
|
||||
|
||||
final DistributionSet distributionSet = rollout.getDistributionSet();
|
||||
final ActionType actionType = rollout.getActionType();
|
||||
final long forceTime = rollout.getForcedTime();
|
||||
|
||||
final Slice<Target> targets = targetManagement.findByInRolloutGroupWithoutAction(pageRequest, groupId);
|
||||
if (targets.getNumberOfElements() > 0) {
|
||||
createScheduledAction(targets.getContent(), distributionSet, actionType, forceTime, rollout, group);
|
||||
final DistributionSet distributionSet = rollout.getDistributionSet();
|
||||
final ActionType actionType = rollout.getActionType();
|
||||
final long forceTime = rollout.getForcedTime();
|
||||
createActions(targets.getContent(), distributionSet, actionType, forceTime, rollout, group);
|
||||
}
|
||||
|
||||
return Long.valueOf(targets.getNumberOfElements());
|
||||
@@ -665,9 +847,9 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
||||
/**
|
||||
* 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.
|
||||
* is created in-active for static and running for dynamic groups.
|
||||
*/
|
||||
private void createScheduledAction(final Collection<Target> targets, final DistributionSet distributionSet,
|
||||
private void createActions(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
|
||||
@@ -677,16 +859,15 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
||||
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.setActive(rolloutGroup.isDynamic());
|
||||
action.setDistributionSet(distributionSet);
|
||||
action.setActionType(actionType);
|
||||
action.setForcedTime(forcedTime);
|
||||
action.setStatus(Status.SCHEDULED);
|
||||
action.setStatus(rolloutGroup.isDynamic() ? Status.RUNNING : Status.SCHEDULED);
|
||||
action.setRollout(rollout);
|
||||
action.setRolloutGroup(rolloutGroup);
|
||||
action.setInitiatedBy(rollout.getCreatedBy());
|
||||
|
||||
@@ -655,10 +655,11 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
|
||||
final VirtualPropertyReplacer virtualPropertyReplacer,
|
||||
final DistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement,
|
||||
final JpaProperties properties, final TenantConfigurationManagement tenantConfigurationManagement,
|
||||
final RepositoryProperties repositoryProperties,
|
||||
final SystemSecurityContext systemSecurityContext, final ContextAware contextAware) {
|
||||
return new JpaTargetFilterQueryManagement(targetFilterQueryRepository, targetManagement,
|
||||
virtualPropertyReplacer, distributionSetManagement, quotaManagement, properties.getDatabase(),
|
||||
tenantConfigurationManagement, systemSecurityContext, contextAware);
|
||||
tenantConfigurationManagement, repositoryProperties, systemSecurityContext, contextAware);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -16,11 +16,17 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
|
||||
|
||||
public class JpaRolloutCreate extends AbstractRolloutUpdateCreate<RolloutCreate> implements RolloutCreate {
|
||||
private final DistributionSetManagement distributionSetManagement;
|
||||
private boolean dynamic;
|
||||
|
||||
JpaRolloutCreate(final DistributionSetManagement distributionSetManagement) {
|
||||
this.distributionSetManagement = distributionSetManagement;
|
||||
}
|
||||
|
||||
public RolloutCreate dynamic(final boolean dynamic) {
|
||||
this.dynamic = dynamic;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaRollout build() {
|
||||
final JpaRollout rollout = new JpaRollout();
|
||||
@@ -31,6 +37,7 @@ public class JpaRolloutCreate extends AbstractRolloutUpdateCreate<RolloutCreate>
|
||||
rollout.setTargetFilterQuery(targetFilterQuery);
|
||||
rollout.setStartAt(startAt);
|
||||
rollout.setWeight(weight);
|
||||
rollout.setDynamic(dynamic);
|
||||
|
||||
if (actionType != null) {
|
||||
rollout.setActionType(actionType);
|
||||
|
||||
@@ -19,6 +19,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.hawkbit.repository.QuotaManagement;
|
||||
import org.eclipse.hawkbit.repository.RepositoryConstants;
|
||||
import org.eclipse.hawkbit.repository.RepositoryProperties;
|
||||
import org.eclipse.hawkbit.repository.event.remote.CancelTargetAssignmentEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
|
||||
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
|
||||
@@ -67,12 +68,13 @@ public abstract class AbstractDsAssignmentStrategy {
|
||||
private final QuotaManagement quotaManagement;
|
||||
private final BooleanSupplier multiAssignmentsConfig;
|
||||
private final BooleanSupplier confirmationFlowConfig;
|
||||
private final RepositoryProperties repositoryProperties;
|
||||
|
||||
AbstractDsAssignmentStrategy(final TargetRepository targetRepository,
|
||||
final AfterTransactionCommitExecutor afterCommit, final EventPublisherHolder eventPublisherHolder,
|
||||
final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository,
|
||||
final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig,
|
||||
final BooleanSupplier confirmationFlowConfig) {
|
||||
final BooleanSupplier confirmationFlowConfig, final RepositoryProperties repositoryProperties) {
|
||||
this.targetRepository = targetRepository;
|
||||
this.afterCommit = afterCommit;
|
||||
this.eventPublisherHolder = eventPublisherHolder;
|
||||
@@ -81,6 +83,7 @@ public abstract class AbstractDsAssignmentStrategy {
|
||||
this.quotaManagement = quotaManagement;
|
||||
this.multiAssignmentsConfig = multiAssignmentsConfig;
|
||||
this.confirmationFlowConfig = confirmationFlowConfig;
|
||||
this.repositoryProperties = repositoryProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -252,7 +255,9 @@ public abstract class AbstractDsAssignmentStrategy {
|
||||
final JpaAction actionForTarget = new JpaAction();
|
||||
actionForTarget.setActionType(targetWithActionType.getActionType());
|
||||
actionForTarget.setForcedTime(targetWithActionType.getForceTime());
|
||||
actionForTarget.setWeight(targetWithActionType.getWeight());
|
||||
actionForTarget.setWeight(
|
||||
targetWithActionType.getWeight() == null ?
|
||||
repositoryProperties.getActionWeightIfAbsent() : targetWithActionType.getWeight());
|
||||
actionForTarget.setActive(true);
|
||||
actionForTarget.setTarget(target);
|
||||
actionForTarget.setDistributionSet(set);
|
||||
|
||||
@@ -176,10 +176,10 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
||||
this.txManager = txManager;
|
||||
onlineDsAssignmentStrategy = new OnlineDsAssignmentStrategy(targetRepository, afterCommit, eventPublisherHolder,
|
||||
actionRepository, actionStatusRepository, quotaManagement, this::isMultiAssignmentsEnabled,
|
||||
this::isConfirmationFlowEnabled);
|
||||
this::isConfirmationFlowEnabled, repositoryProperties);
|
||||
offlineDsAssignmentStrategy = new OfflineDsAssignmentStrategy(targetRepository, afterCommit,
|
||||
eventPublisherHolder, actionRepository, actionStatusRepository, quotaManagement,
|
||||
this::isMultiAssignmentsEnabled, this::isConfirmationFlowEnabled);
|
||||
this::isMultiAssignmentsEnabled, this::isConfirmationFlowEnabled, repositoryProperties);
|
||||
this.tenantConfigurationManagement = tenantConfigurationManagement;
|
||||
this.systemSecurityContext = systemSecurityContext;
|
||||
this.tenantAware = tenantAware;
|
||||
|
||||
@@ -17,6 +17,7 @@ import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -26,6 +27,7 @@ import javax.validation.ValidationException;
|
||||
|
||||
import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
||||
import org.eclipse.hawkbit.repository.QuotaManagement;
|
||||
import org.eclipse.hawkbit.repository.RepositoryProperties;
|
||||
import org.eclipse.hawkbit.repository.RolloutApprovalStrategy;
|
||||
import org.eclipse.hawkbit.repository.RolloutFields;
|
||||
import org.eclipse.hawkbit.repository.RolloutHelper;
|
||||
@@ -109,6 +111,9 @@ public class JpaRolloutManagement implements RolloutManagement {
|
||||
RolloutStatus.CREATING, RolloutStatus.PAUSED, RolloutStatus.READY, RolloutStatus.STARTING,
|
||||
RolloutStatus.WAITING_FOR_APPROVAL, RolloutStatus.APPROVAL_DENIED);
|
||||
|
||||
@Autowired
|
||||
private RepositoryProperties repositoryProperties;
|
||||
|
||||
@Autowired
|
||||
private RolloutRepository rolloutRepository;
|
||||
|
||||
@@ -219,6 +224,9 @@ public class JpaRolloutManagement implements RolloutManagement {
|
||||
throw new ValidationException(errMsg);
|
||||
}
|
||||
rollout.setTotalTargets(totalTargets);
|
||||
if (rollout.getWeight().isEmpty()) {
|
||||
rollout.setWeight(repositoryProperties.getActionWeightIfAbsent());
|
||||
}
|
||||
contextAware.getCurrentContext().ifPresent(rollout::setAccessControlContext);
|
||||
return rolloutRepository.save(rollout);
|
||||
}
|
||||
@@ -247,7 +255,15 @@ public class JpaRolloutManagement implements RolloutManagement {
|
||||
|
||||
addSuccessAndErrorConditionsAndActions(group, conditions);
|
||||
|
||||
group.setTargetPercentage(1.0F / (amountOfGroups - i) * 100);
|
||||
// total percent of the all devices. Before, it was relative percent -
|
||||
// the percent of the "rest" of the devices. Thus, if you have
|
||||
// first a group 10% (the rest is 90%) and the second group is 50%
|
||||
// then the percent would be 50% of 90% - 45%.
|
||||
// This is very unintuitive and is switched in order to be interpreted easier.
|
||||
// the "new style" (vs "old style") rollouts could be detected by
|
||||
// JpaRollout#isNewStyleTargetPercent (which uses that old style rollouts
|
||||
// have null as dynamic
|
||||
group.setTargetPercentage(100.0F / amountOfGroups);
|
||||
|
||||
lastSavedGroup = rolloutGroupRepository.save(group);
|
||||
publishRolloutGroupCreatedEventAfterCommit(lastSavedGroup, rollout);
|
||||
@@ -350,7 +366,7 @@ public class JpaRolloutManagement implements RolloutManagement {
|
||||
return group;
|
||||
}
|
||||
|
||||
private void publishRolloutGroupCreatedEventAfterCommit(final RolloutGroup group, final Rollout rollout) {
|
||||
public void publishRolloutGroupCreatedEventAfterCommit(final RolloutGroup group, final Rollout rollout) {
|
||||
afterCommit.afterCommit(() -> eventPublisherHolder.getEventPublisher().publishEvent(
|
||||
new RolloutGroupCreatedEvent(group, rollout.getId(), eventPublisherHolder.getApplicationId())));
|
||||
}
|
||||
@@ -672,11 +688,13 @@ public class JpaRolloutManagement implements RolloutManagement {
|
||||
realTargetsInGroup = targetsInGroupFilter - overlappingTargets;
|
||||
}
|
||||
|
||||
// new style percent - total percent
|
||||
final double percentFromRest = RolloutHelper.toPercentFromTheRest(group, groups);
|
||||
|
||||
final long reducedTargetsInGroup = Math
|
||||
.round(group.getTargetPercentage() / 100 * (double) realTargetsInGroup);
|
||||
.round(percentFromRest / 100 * (double) realTargetsInGroup);
|
||||
groupTargetCounts.add(reducedTargetsInGroup);
|
||||
unusedTargetsCount += realTargetsInGroup - reducedTargetsInGroup;
|
||||
|
||||
}
|
||||
|
||||
return new RolloutGroupsValidation(totalTargets, groupTargetCounts);
|
||||
|
||||
@@ -17,6 +17,7 @@ import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
||||
import org.eclipse.hawkbit.repository.QuotaManagement;
|
||||
import org.eclipse.hawkbit.repository.RepositoryProperties;
|
||||
import org.eclipse.hawkbit.repository.TargetFields;
|
||||
import org.eclipse.hawkbit.repository.TargetFilterQueryFields;
|
||||
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
|
||||
@@ -84,6 +85,7 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
|
||||
private final DistributionSetManagement distributionSetManagement;
|
||||
private final QuotaManagement quotaManagement;
|
||||
private final TenantConfigurationManagement tenantConfigurationManagement;
|
||||
private final RepositoryProperties repositoryProperties;
|
||||
private final SystemSecurityContext systemSecurityContext;
|
||||
private final ContextAware contextAware;
|
||||
|
||||
@@ -93,6 +95,7 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
|
||||
final TargetManagement targetManagement, final VirtualPropertyReplacer virtualPropertyReplacer,
|
||||
final DistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement,
|
||||
final Database database, final TenantConfigurationManagement tenantConfigurationManagement,
|
||||
final RepositoryProperties repositoryProperties,
|
||||
final SystemSecurityContext systemSecurityContext, final ContextAware contextAware) {
|
||||
this.targetFilterQueryRepository = targetFilterQueryRepository;
|
||||
this.targetManagement = targetManagement;
|
||||
@@ -101,6 +104,7 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
|
||||
this.quotaManagement = quotaManagement;
|
||||
this.database = database;
|
||||
this.tenantConfigurationManagement = tenantConfigurationManagement;
|
||||
this.repositoryProperties = repositoryProperties;
|
||||
this.systemSecurityContext = systemSecurityContext;
|
||||
this.contextAware = contextAware;
|
||||
}
|
||||
@@ -270,7 +274,7 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
|
||||
targetFilterQuery.setAccessControlContext(null);
|
||||
targetFilterQuery.setAutoAssignDistributionSet(null);
|
||||
targetFilterQuery.setAutoAssignActionType(null);
|
||||
targetFilterQuery.setAutoAssignWeight(null);
|
||||
targetFilterQuery.setAutoAssignWeight(0);
|
||||
targetFilterQuery.setAutoAssignInitiatedBy(null);
|
||||
targetFilterQuery.setConfirmationRequired(false);
|
||||
} else {
|
||||
@@ -284,9 +288,11 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
|
||||
contextAware.getCurrentContext().ifPresent(targetFilterQuery::setAccessControlContext);
|
||||
targetFilterQuery.setAutoAssignInitiatedBy(contextAware.getCurrentUsername());
|
||||
targetFilterQuery.setAutoAssignActionType(sanitizeAutoAssignActionType(update.getActionType()));
|
||||
targetFilterQuery.setAutoAssignWeight(update.getWeight());
|
||||
final boolean confirmationRequired = update.isConfirmationRequired() == null ? isConfirmationFlowEnabled()
|
||||
: update.isConfirmationRequired();
|
||||
targetFilterQuery.setAutoAssignWeight(
|
||||
update.getWeight() == null ? repositoryProperties.getActionWeightIfAbsent() : update.getWeight());
|
||||
final boolean confirmationRequired =
|
||||
update.isConfirmationRequired() == null ?
|
||||
isConfirmationFlowEnabled() : update.isConfirmationRequired();
|
||||
targetFilterQuery.setConfirmationRequired(confirmationRequired);
|
||||
}
|
||||
return targetFilterQueryRepository.save(targetFilterQuery);
|
||||
|
||||
@@ -698,6 +698,27 @@ public class JpaTargetManagement implements TargetManagement {
|
||||
.map(Target.class::cast);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Slice<Target> findByNotInGEGroupAndNotInActiveActionGEWeightOrInRolloutAndTargetFilterQueryAndCompatibleAndUpdatable(
|
||||
final Pageable pageRequest, final long rolloutId, final int weight, final long firstGroupId, final String targetFilterQuery,
|
||||
final DistributionSetType distributionSetType) {
|
||||
return targetRepository
|
||||
.findAllWithoutCount(AccessController.Operation.UPDATE,
|
||||
combineWithAnd(List.of(
|
||||
RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class,
|
||||
virtualPropertyReplacer, database),
|
||||
TargetSpecifications.isNotInGERolloutGroup(firstGroupId),
|
||||
TargetSpecifications.hasNoActiveActionWithGEWeightOrInRollout(weight, rolloutId),
|
||||
TargetSpecifications.isCompatibleWithDistributionSetType(distributionSetType.getId()))),
|
||||
pageRequest)
|
||||
.map(Target.class::cast);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long countByActionsInRolloutGroup(final long rolloutGroupId) {
|
||||
return targetRepository.count(TargetSpecifications.isInActionRolloutGroup(rolloutGroupId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Slice<Target> findByFailedRolloutAndNotInRolloutGroups(Pageable pageRequest, Collection<Long> groups,
|
||||
String rolloutId) {
|
||||
|
||||
@@ -19,6 +19,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.hawkbit.repository.QuotaManagement;
|
||||
import org.eclipse.hawkbit.repository.RepositoryConstants;
|
||||
import org.eclipse.hawkbit.repository.RepositoryProperties;
|
||||
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
|
||||
import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
|
||||
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
|
||||
@@ -52,9 +53,9 @@ public class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
|
||||
final AfterTransactionCommitExecutor afterCommit, final EventPublisherHolder eventPublisherHolder,
|
||||
final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository,
|
||||
final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig,
|
||||
final BooleanSupplier confirmationFlowConfig) {
|
||||
final BooleanSupplier confirmationFlowConfig, final RepositoryProperties repositoryProperties) {
|
||||
super(targetRepository, afterCommit, eventPublisherHolder, actionRepository, actionStatusRepository,
|
||||
quotaManagement, multiAssignmentsConfig, confirmationFlowConfig);
|
||||
quotaManagement, multiAssignmentsConfig, confirmationFlowConfig, repositoryProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -19,6 +19,7 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.hawkbit.repository.QuotaManagement;
|
||||
import org.eclipse.hawkbit.repository.RepositoryProperties;
|
||||
import org.eclipse.hawkbit.repository.event.remote.MultiActionAssignEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.MultiActionCancelEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
|
||||
@@ -55,9 +56,9 @@ public class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
|
||||
final AfterTransactionCommitExecutor afterCommit, final EventPublisherHolder eventPublisherHolder,
|
||||
final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository,
|
||||
final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig,
|
||||
final BooleanSupplier confirmationFlowConfig) {
|
||||
final BooleanSupplier confirmationFlowConfig, final RepositoryProperties repositoryProperties) {
|
||||
super(targetRepository, afterCommit, eventPublisherHolder, actionRepository, actionStatusRepository,
|
||||
quotaManagement, multiAssignmentsConfig, confirmationFlowConfig);
|
||||
quotaManagement, multiAssignmentsConfig, confirmationFlowConfig, repositoryProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -140,6 +140,9 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
|
||||
@Max(Action.WEIGHT_MAX)
|
||||
private Integer weight;
|
||||
|
||||
@Column(name = "is_dynamic") // dynamic is reserved keyword in some databases
|
||||
private Boolean dynamic;
|
||||
|
||||
@Column(name = "access_control_context", nullable = true)
|
||||
private String accessControlContext;
|
||||
|
||||
@@ -225,6 +228,21 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
|
||||
this.weight = weight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDynamic() {
|
||||
return Boolean.TRUE.equals(dynamic);
|
||||
}
|
||||
|
||||
public void setDynamic(final Boolean dynamic) {
|
||||
this.dynamic = dynamic;
|
||||
}
|
||||
|
||||
// dynamic is null only for old rollouts - could be used for distinguishing
|
||||
// old once from the other
|
||||
public boolean isNewStyleTargetPercent() {
|
||||
return dynamic != null;
|
||||
}
|
||||
|
||||
public Optional<String> getAccessControlContext() {
|
||||
return Optional.ofNullable(accessControlContext);
|
||||
}
|
||||
|
||||
@@ -78,6 +78,9 @@ public class JpaRolloutGroup extends AbstractJpaNamedEntity implements RolloutGr
|
||||
@JoinColumn(name = "parent_id")
|
||||
private JpaRolloutGroup parent;
|
||||
|
||||
@Column(name = "is_dynamic") // dynamic is reserved keyword in some databases
|
||||
private boolean dynamic;
|
||||
|
||||
@Column(name = "success_condition", nullable = false)
|
||||
@NotNull
|
||||
private RolloutGroupSuccessCondition successCondition = RolloutGroupSuccessCondition.THRESHOLD;
|
||||
@@ -156,6 +159,15 @@ public class JpaRolloutGroup extends AbstractJpaNamedEntity implements RolloutGr
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDynamic() {
|
||||
return dynamic;
|
||||
}
|
||||
|
||||
public void setDynamic(final boolean dynamic) {
|
||||
this.dynamic = dynamic;
|
||||
}
|
||||
|
||||
public void setParent(final RolloutGroup parent) {
|
||||
this.parent = (JpaRolloutGroup) parent;
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ public class JpaTargetFilterQuery extends AbstractJpaTenantAwareBaseEntity
|
||||
this.query = query;
|
||||
this.autoAssignDistributionSet = (JpaDistributionSet) autoAssignDistributionSet;
|
||||
this.autoAssignActionType = autoAssignActionType;
|
||||
this.autoAssignWeight = autoAssignWeight;
|
||||
this.autoAssignWeight = autoAssignWeight == null ? 0 : autoAssignWeight;
|
||||
this.confirmationRequired = confirmationRequired;
|
||||
}
|
||||
|
||||
|
||||
@@ -75,11 +75,15 @@ public class StartNextGroupRolloutGroupSuccessAction implements RolloutGroupActi
|
||||
final List<JpaRolloutGroup> findByRolloutGroupParent = rolloutGroupRepository
|
||||
.findByParentIdAndStatus(rolloutGroup.getId(), RolloutGroupStatus.SCHEDULED);
|
||||
findByRolloutGroupParent.forEach(nextGroup -> {
|
||||
logger.debug("Rolloutgroup {} is finished, starting next group", nextGroup);
|
||||
nextGroup.setStatus(RolloutGroupStatus.FINISHED);
|
||||
rolloutGroupRepository.save(nextGroup);
|
||||
// find the next group to set in running state
|
||||
startNextGroup(rollout, nextGroup);
|
||||
if (nextGroup.isDynamic()) {
|
||||
nextGroup.setStatus(RolloutGroupStatus.RUNNING);
|
||||
} else {
|
||||
logger.debug("Rolloutgroup {} is finished, starting next group", nextGroup);
|
||||
nextGroup.setStatus(RolloutGroupStatus.FINISHED);
|
||||
rolloutGroupRepository.save(nextGroup);
|
||||
// find the next group to set in running state
|
||||
startNextGroup(rollout, nextGroup);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,8 +39,7 @@ public class ThresholdRolloutGroupErrorCondition
|
||||
|
||||
@Override
|
||||
public boolean eval(final Rollout rollout, final RolloutGroup rolloutGroup, final String expression) {
|
||||
final Long totalGroup = actionRepository.countByRolloutAndRolloutGroup((JpaRollout) rollout,
|
||||
(JpaRolloutGroup) rolloutGroup);
|
||||
final long totalGroup = rolloutGroup.getTotalTargets();
|
||||
final Long error = actionRepository.countByRolloutIdAndRolloutGroupIdAndStatus(rollout.getId(),
|
||||
rolloutGroup.getId(), Action.Status.ERROR);
|
||||
try {
|
||||
|
||||
@@ -37,7 +37,6 @@ public class ThresholdRolloutGroupSuccessCondition
|
||||
|
||||
@Override
|
||||
public boolean eval(final Rollout rollout, final RolloutGroup rolloutGroup, final String expression) {
|
||||
|
||||
final long totalGroup = rolloutGroup.getTotalTargets();
|
||||
if (totalGroup == 0) {
|
||||
// in case e.g. targets has been deleted we don't have any
|
||||
|
||||
@@ -33,7 +33,9 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType_;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup_;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout_;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag_;
|
||||
@@ -459,9 +461,24 @@ public final class TargetSpecifications {
|
||||
return (targetRoot, query, cb) -> {
|
||||
final ListJoin<JpaTarget, RolloutTargetGroup> rolloutTargetJoin = targetRoot
|
||||
.join(JpaTarget_.rolloutTargetGroup, JoinType.LEFT);
|
||||
final Predicate inRolloutGroups = rolloutTargetJoin.get(RolloutTargetGroup_.rolloutGroup)
|
||||
.get(JpaRolloutGroup_.id).in(groups);
|
||||
rolloutTargetJoin.on(inRolloutGroups);
|
||||
rolloutTargetJoin.on(rolloutTargetJoin.get(RolloutTargetGroup_.rolloutGroup)
|
||||
.get(JpaRolloutGroup_.id).in(groups));
|
||||
return cb.isNull(rolloutTargetJoin.get(RolloutTargetGroup_.target));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Specification} for retrieving {@link Target}s that are not in
|
||||
* any {@link RolloutGroup}s
|
||||
*
|
||||
* @return the {@link Target} {@link Specification}
|
||||
*/
|
||||
public static Specification<JpaTarget> isNotInGERolloutGroup(final long groupId) {
|
||||
return (targetRoot, query, cb) -> {
|
||||
final ListJoin<JpaTarget, RolloutTargetGroup> rolloutTargetJoin = targetRoot
|
||||
.join(JpaTarget_.rolloutTargetGroup, JoinType.LEFT);
|
||||
rolloutTargetJoin.on(cb.ge(rolloutTargetJoin.get(RolloutTargetGroup_.rolloutGroup)
|
||||
.get(JpaRolloutGroup_.id), groupId));
|
||||
return cb.isNull(rolloutTargetJoin.get(RolloutTargetGroup_.target));
|
||||
};
|
||||
}
|
||||
@@ -605,4 +622,36 @@ public final class TargetSpecifications {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Specification} for retrieving {@link Target}s that have no active (non-finished) action
|
||||
* with great or equal weight (GEWeight.
|
||||
*
|
||||
* @param weight the referent weight
|
||||
* @return the {@link Target} {@link Specification}
|
||||
*/
|
||||
public static Specification<JpaTarget> hasNoActiveActionWithGEWeightOrInRollout(final int weight, final long rolloutId) {
|
||||
return (targetRoot, query, cb) -> {
|
||||
final ListJoin<JpaTarget, JpaAction> actionsJoin = targetRoot.join(JpaTarget_.actions, JoinType.LEFT);
|
||||
actionsJoin.on(
|
||||
cb.or(
|
||||
cb.gt(actionsJoin.get(JpaAction_.weight), weight),
|
||||
cb.and(
|
||||
cb.equal(actionsJoin.get(JpaAction_.weight), weight),
|
||||
cb.ge(actionsJoin.get(JpaAction_.ROLLOUT).get(JpaRollout_.ID), rolloutId))));
|
||||
// another, but probably heavier variant
|
||||
// actionsJoin.on(
|
||||
// cb.or(
|
||||
// // in rollout
|
||||
// cb.equal(actionsJoin.get(JpaAction_.ROLLOUT).get(JpaRollout_.ID), rolloutId),
|
||||
// // or, in newer rollout with greater or equal weight
|
||||
// cb.and(
|
||||
// cb.gt(actionsJoin.get(JpaAction_.ROLLOUT).get(JpaRollout_.ID), rolloutId),
|
||||
// cb.ge(actionsJoin.get(JpaAction_.weight), weight)),
|
||||
// // or, in older with greater status
|
||||
// cb.and(
|
||||
// cb.lt(actionsJoin.get(JpaAction_.ROLLOUT).get(JpaRollout_.ID), rolloutId),
|
||||
// cb.gt(actionsJoin.get(JpaAction_.weight), weight))));
|
||||
return cb.isNull(actionsJoin.get(JpaAction_.id));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,9 +123,7 @@ public final class WeightValidationHelper {
|
||||
final boolean bypassWeightEnforcement = true;
|
||||
final boolean multiAssignmentsEnabled = TenantConfigHelper
|
||||
.usingContext(systemSecurityContext, tenantConfigurationManagement).isMultiAssignmentsEnabled();
|
||||
if (!multiAssignmentsEnabled && hasWeight) {
|
||||
throw new MultiAssignmentIsNotEnabledException();
|
||||
} else if (bypassWeightEnforcement) {
|
||||
if (bypassWeightEnforcement) {
|
||||
return;
|
||||
} else if (multiAssignmentsEnabled && hasNoWeight) {
|
||||
throw new NoWeightProvidedInMultiAssignmentModeException();
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
ALTER TABLE sp_rollout ADD COLUMN is_dynamic BOOLEAN;
|
||||
ALTER TABLE sp_rolloutgroup ADD COLUMN is_dynamic BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
UPDATE sp_rollout SET weight = 1000 WHERE weight IS NULL;
|
||||
UPDATE sp_action SET weight = 1000 WHERE weight IS NULL;
|
||||
UPDATE sp_target_filter_query SET auto_assign_weight = 1000 WHERE auto_assign_weight IS NULL;
|
||||
ALTER TABLE sp_rollout ALTER COLUMN weight SET NOT NULL;
|
||||
ALTER TABLE sp_action ALTER COLUMN weight SET NOT NULL;
|
||||
ALTER TABLE sp_target_filter_query ALTER COLUMN auto_assign_weight SET NOT NULL;
|
||||
@@ -0,0 +1,9 @@
|
||||
ALTER TABLE sp_rollout ADD COLUMN is_dynamic BOOLEAN;
|
||||
ALTER TABLE sp_rolloutgroup ADD COLUMN is_dynamic BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
UPDATE sp_rollout SET weight = 1000 WHERE weight IS NULL;
|
||||
UPDATE sp_action SET weight = 1000 WHERE weight IS NULL;
|
||||
UPDATE sp_target_filter_query SET auto_assign_weight = 1000 WHERE auto_assign_weight IS NULL;
|
||||
ALTER TABLE sp_rollout ALTER COLUMN weight INT NOT NULL;
|
||||
ALTER TABLE sp_action ALTER COLUMN weight INT NOT NULL;
|
||||
ALTER TABLE sp_target_filter_query ALTER COLUMN auto_assign_weight INT NOT NULL;
|
||||
@@ -0,0 +1,9 @@
|
||||
ALTER TABLE sp_rollout ADD COLUMN is_dynamic BOOLEAN;
|
||||
ALTER TABLE sp_rolloutgroup ADD COLUMN is_dynamic BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
UPDATE sp_rollout SET weight = 1000 WHERE weight IS NULL;
|
||||
UPDATE sp_action SET weight = 1000 WHERE weight IS NULL;
|
||||
UPDATE sp_target_filter_query SET auto_assign_weight = 1000 WHERE auto_assign_weight IS NULL;
|
||||
ALTER TABLE sp_rollout MODIFY COLUMN weight INT NOT NULL;
|
||||
ALTER TABLE sp_action MODIFY COLUMN weight INT NOT NULL;
|
||||
ALTER TABLE sp_target_filter_query MODIFY COLUMN auto_assign_weight INT NOT NULL;
|
||||
@@ -0,0 +1,9 @@
|
||||
ALTER TABLE sp_rollout ADD COLUMN is_dynamic BOOLEAN;
|
||||
ALTER TABLE sp_rolloutgroup ADD COLUMN is_dynamic BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
UPDATE sp_rollout SET weight = 1000 WHERE weight IS NULL;
|
||||
UPDATE sp_action SET weight = 1000 WHERE weight IS NULL;
|
||||
UPDATE sp_target_filter_query SET auto_assign_weight = 1000 WHERE auto_assign_weight IS NULL;
|
||||
ALTER TABLE sp_rollout ALTER COLUMN weight SET NOT NULL;
|
||||
ALTER TABLE sp_action ALTER COLUMN weight SET NOT NULL;
|
||||
ALTER TABLE sp_target_filter_query ALTER COLUMN auto_assign_weight SET NOT NULL;
|
||||
@@ -0,0 +1,9 @@
|
||||
ALTER TABLE sp_rollout ADD is_dynamic BIT;
|
||||
ALTER TABLE sp_rolloutgroup ADD is_dynamic BIT NOT NULL DEFAULT 0;
|
||||
|
||||
UPDATE sp_rollout SET weight = 1000 WHERE weight IS NULL;
|
||||
UPDATE sp_action SET weight = 1000 WHERE weight IS NULL;
|
||||
UPDATE sp_target_filter_query SET auto_assign_weight = 1000 WHERE auto_assign_weight IS NULL;
|
||||
ALTER TABLE sp_rollout ALTER COLUMN weight INT NOT NULL;
|
||||
ALTER TABLE sp_action ALTER COLUMN weight INT NOT NULL;
|
||||
ALTER TABLE sp_target_filter_query ALTER COLUMN auto_assign_weight INT NOT NULL;
|
||||
Reference in New Issue
Block a user