* [#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:
@@ -310,7 +310,7 @@ public interface TargetManagement {
|
|||||||
* the pageRequest to enhance the query for paging and sorting
|
* the pageRequest to enhance the query for paging and sorting
|
||||||
* @param groups
|
* @param groups
|
||||||
* the list of {@link RolloutGroup}s
|
* the list of {@link RolloutGroup}s
|
||||||
* @param rsqlParam
|
* @param targetFilterQuery
|
||||||
* filter definition in RSQL syntax
|
* filter definition in RSQL syntax
|
||||||
* @param distributionSetType
|
* @param distributionSetType
|
||||||
* type of the {@link DistributionSet} the targets must be compatible
|
* type of the {@link DistributionSet} the targets must be compatible
|
||||||
@@ -319,9 +319,17 @@ public interface TargetManagement {
|
|||||||
*/
|
*/
|
||||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
|
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
|
||||||
Slice<Target> findByTargetFilterQueryAndNotInRolloutGroupsAndCompatibleAndUpdatable(@NotNull Pageable pageRequest,
|
Slice<Target> findByTargetFilterQueryAndNotInRolloutGroupsAndCompatibleAndUpdatable(@NotNull Pageable pageRequest,
|
||||||
@NotEmpty Collection<Long> groups, @NotNull String rsqlParam,
|
@NotEmpty Collection<Long> groups, @NotNull String targetFilterQuery,
|
||||||
@NotNull DistributionSetType distributionSetType);
|
@NotNull DistributionSetType distributionSetType);
|
||||||
|
|
||||||
|
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
|
||||||
|
Slice<Target> findByNotInGEGroupAndNotInActiveActionGEWeightOrInRolloutAndTargetFilterQueryAndCompatibleAndUpdatable(
|
||||||
|
@NotNull Pageable pageRequest, final long rolloutId, final int weight, final long firstGroupId, @NotNull String targetFilterQuery,
|
||||||
|
@NotNull DistributionSetType distributionSetType);
|
||||||
|
|
||||||
|
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
|
||||||
|
long countByActionsInRolloutGroup(final long rolloutGroupId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds all targets with failed actions for specific Rollout and that are not
|
* Finds all targets with failed actions for specific Rollout and that are not
|
||||||
* assigned to one of the retried {@link RolloutGroup}s and are compatible with
|
* assigned to one of the retried {@link RolloutGroup}s and are compatible with
|
||||||
|
|||||||
@@ -103,6 +103,15 @@ public interface RolloutCreate {
|
|||||||
*/
|
*/
|
||||||
RolloutCreate weight(Integer weight);
|
RolloutCreate weight(Integer weight);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set if the rollout shall be dynamic
|
||||||
|
*
|
||||||
|
* @param dynamic
|
||||||
|
* for {@link Rollout#isDynamic()}
|
||||||
|
* @return updated builder instance
|
||||||
|
*/
|
||||||
|
RolloutCreate dynamic(boolean dynamic);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* set start
|
* set start
|
||||||
*
|
*
|
||||||
@@ -116,5 +125,4 @@ public interface RolloutCreate {
|
|||||||
* @return peek on current state of {@link Rollout} in the builder
|
* @return peek on current state of {@link Rollout} in the builder
|
||||||
*/
|
*/
|
||||||
Rollout build();
|
Rollout build();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,6 +108,11 @@ public interface Rollout extends NamedEntity {
|
|||||||
*/
|
*/
|
||||||
Optional<Integer> getWeight();
|
Optional<Integer> getWeight();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return if the {@link Rollout} is dynamic.
|
||||||
|
*/
|
||||||
|
boolean isDynamic();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the stored access control context (if present)
|
* @return the stored access control context (if present)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -33,6 +33,11 @@ public interface RolloutGroup extends NamedEntity {
|
|||||||
*/
|
*/
|
||||||
RolloutGroup getParent();
|
RolloutGroup getParent();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return if the group is dynamic
|
||||||
|
*/
|
||||||
|
boolean isDynamic();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the {@link RolloutGroupSuccessCondition} for this group to
|
* @return the {@link RolloutGroupSuccessCondition} for this group to
|
||||||
* indicate when a group is successful
|
* indicate when a group is successful
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository;
|
|||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import javax.validation.ValidationException;
|
import javax.validation.ValidationException;
|
||||||
@@ -229,9 +230,6 @@ public final class RolloutHelper {
|
|||||||
if (StringUtils.isEmpty(group.getTargetFilterQuery())) {
|
if (StringUtils.isEmpty(group.getTargetFilterQuery())) {
|
||||||
return baseFilter;
|
return baseFilter;
|
||||||
}
|
}
|
||||||
if (isRolloutRetried(baseFilter)) {
|
|
||||||
return baseFilter;
|
|
||||||
}
|
|
||||||
return concatAndTargetFilters(baseFilter, group.getTargetFilterQuery());
|
return concatAndTargetFilters(baseFilter, group.getTargetFilterQuery());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,6 +255,23 @@ public final class RolloutHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static double toPercentFromTheRest(final RolloutGroup group, List<? extends RolloutGroup> rolloutGroups) {
|
||||||
|
final double percentFromRest;
|
||||||
|
// assume that the groups are served orderly
|
||||||
|
double toServePercent = 100;
|
||||||
|
for (final RolloutGroup next : rolloutGroups) {
|
||||||
|
if (next == group) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (Objects.equals(next.getTargetFilterQuery(), group.getTargetFilterQuery())) {
|
||||||
|
toServePercent -= next.getTargetPercentage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
percentFromRest =
|
||||||
|
toServePercent <= 1 ? 100 : Math.min(100, group.getTargetPercentage() * 100 / toServePercent);
|
||||||
|
return percentFromRest;
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isRolloutRetried(final String targetFilter) {
|
public static boolean isRolloutRetried(final String targetFilter) {
|
||||||
return targetFilter.contains("failedrollout");
|
return targetFilter.contains("failedrollout");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,11 @@
|
|||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.repository.jpa;
|
package org.eclipse.hawkbit.repository.jpa;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Proxy;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.StreamSupport;
|
import java.util.stream.StreamSupport;
|
||||||
@@ -19,6 +22,7 @@ import javax.persistence.EntityManager;
|
|||||||
|
|
||||||
import org.eclipse.hawkbit.repository.DeploymentManagement;
|
import org.eclipse.hawkbit.repository.DeploymentManagement;
|
||||||
import org.eclipse.hawkbit.repository.QuotaManagement;
|
import org.eclipse.hawkbit.repository.QuotaManagement;
|
||||||
|
import org.eclipse.hawkbit.repository.RepositoryProperties;
|
||||||
import org.eclipse.hawkbit.repository.RolloutApprovalStrategy;
|
import org.eclipse.hawkbit.repository.RolloutApprovalStrategy;
|
||||||
import org.eclipse.hawkbit.repository.RolloutExecutor;
|
import org.eclipse.hawkbit.repository.RolloutExecutor;
|
||||||
import org.eclipse.hawkbit.repository.RolloutGroupManagement;
|
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.RolloutGroupDeletedEvent;
|
||||||
import org.eclipse.hawkbit.repository.event.remote.RolloutStoppedEvent;
|
import org.eclipse.hawkbit.repository.event.remote.RolloutStoppedEvent;
|
||||||
import org.eclipse.hawkbit.repository.event.remote.entity.RolloutUpdatedEvent;
|
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.exception.RolloutIllegalStateException;
|
||||||
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
|
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.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;
|
||||||
@@ -64,7 +68,8 @@ import org.springframework.data.domain.Sort;
|
|||||||
import org.springframework.data.domain.Sort.Direction;
|
import org.springframework.data.domain.Sort.Direction;
|
||||||
import org.springframework.transaction.PlatformTransactionManager;
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
import org.springframework.transaction.TransactionException;
|
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}
|
* A Jpa implementation of {@link RolloutExecutor}
|
||||||
@@ -186,7 +191,8 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
final RolloutGroup filledGroup = fillRolloutGroupWithTargets(rollout, group);
|
final RolloutGroup filledGroup = fillRolloutGroupWithTargets(rollout, (JpaRolloutGroup) group,
|
||||||
|
rolloutGroups);
|
||||||
if (RolloutGroupStatus.READY == filledGroup.getStatus()) {
|
if (RolloutGroupStatus.READY == filledGroup.getStatus()) {
|
||||||
readyGroups++;
|
readyGroups++;
|
||||||
totalTargets += filledGroup.getTotalTargets();
|
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
|
// When all groups are ready the rollout status can be changed to be
|
||||||
// ready, too.
|
// ready, too.
|
||||||
if (readyGroups == rolloutGroups.size()) {
|
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)) {
|
if (!rolloutApprovalStrategy.isApprovalNeeded(rollout)) {
|
||||||
rollout.setStatus(RolloutStatus.READY);
|
rollout.setStatus(RolloutStatus.READY);
|
||||||
LOGGER.debug("rollout {} creation done. Switch to READY.", rollout.getId());
|
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) {
|
private void handleRunningRollout(final JpaRollout rollout) {
|
||||||
LOGGER.debug("handleRunningRollout called for rollout {}", rollout.getId());
|
LOGGER.debug("handleRunningRollout called for rollout {}", rollout.getId());
|
||||||
|
|
||||||
final List<JpaRolloutGroup> rolloutGroupsRunning = rolloutGroupRepository.findByRolloutAndStatus(rollout,
|
if (rollout.isDynamic()) {
|
||||||
RolloutGroupStatus.RUNNING);
|
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()) {
|
if (rolloutGroupsRunning.isEmpty()) {
|
||||||
// no running rollouts, probably there was an error
|
// no running rollouts, probably there was an error
|
||||||
@@ -317,7 +338,7 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
|||||||
executeLatestRolloutGroup(rollout);
|
executeLatestRolloutGroup(rollout);
|
||||||
} else {
|
} else {
|
||||||
LOGGER.debug("Rollout {} has {} running groups", rollout.getId(), rolloutGroupsRunning.size());
|
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)) {
|
if (isRolloutComplete(rollout)) {
|
||||||
@@ -371,35 +392,77 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
|||||||
return groupsActiveLeft == 0;
|
return groupsActiveLeft == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final Comparator<RolloutGroup> DESC_COMP = Comparator.comparingLong(RolloutGroup::getId).reversed();
|
||||||
private void executeLatestRolloutGroup(final JpaRollout rollout) {
|
private void executeLatestRolloutGroup(final JpaRollout rollout) {
|
||||||
final List<JpaRolloutGroup> latestRolloutGroup = rolloutGroupRepository
|
// was - rolloutGroupRepository.findByRolloutAndStatusNotOrderByIdDesc(rollout, RolloutGroupStatus.SCHEDULED);
|
||||||
.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()) {
|
if (latestRolloutGroup.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
executeRolloutGroupSuccessAction(rollout, latestRolloutGroup.get(0));
|
executeRolloutGroupSuccessAction(rollout, latestRolloutGroup.get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void executeRolloutGroups(final JpaRollout rollout, final List<JpaRolloutGroup> rolloutGroups) {
|
private static final int DEFAULT_DYNAMIC_GROUP_EXPECTED = 100;
|
||||||
for (final JpaRolloutGroup rolloutGroup : rolloutGroups) {
|
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);
|
final long targetCount = countTargetsFrom(rolloutGroup);
|
||||||
if (rolloutGroup.getTotalTargets() != targetCount) {
|
if (rolloutGroup.getTotalTargets() != targetCount) {
|
||||||
updateTotalTargetCount(rolloutGroup, 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
|
// error state check, do we need to stop the whole
|
||||||
// rollout because of error?
|
// rollout because of error?
|
||||||
final boolean isError = checkErrorState(rollout, rolloutGroup);
|
final boolean isError = checkErrorState(rollout, evalProxy);
|
||||||
if (isError) {
|
if (isError) {
|
||||||
LOGGER.info("Rollout {} {} has error, calling error action", rollout.getName(), rollout.getId());
|
LOGGER.info("Rollout {} {} has error, calling error action", rollout.getName(), rollout.getId());
|
||||||
callErrorAction(rollout, rolloutGroup);
|
callErrorAction(rollout, rolloutGroup);
|
||||||
} else {
|
} else {
|
||||||
// not in error so check finished state, do we need to
|
// not in error so check finished state, do we need to
|
||||||
// start the next group?
|
// start the next group?
|
||||||
final RolloutGroupSuccessCondition finishedCondition = rolloutGroup.getSuccessCondition();
|
checkSuccessCondition(rollout, rolloutGroup, evalProxy, rolloutGroup.getSuccessCondition());
|
||||||
checkFinishCondition(rollout, rolloutGroup, finishedCondition);
|
if (!(rolloutGroup == lastRolloutGroup && rolloutGroup.isDynamic()) && isRolloutGroupComplete(rollout, rolloutGroup)) {
|
||||||
if (isRolloutGroupComplete(rollout, rolloutGroup)) {
|
|
||||||
rolloutGroup.setStatus(RolloutGroupStatus.FINISHED);
|
rolloutGroup.setStatus(RolloutGroupStatus.FINISHED);
|
||||||
rolloutGroupRepository.save(rolloutGroup);
|
rolloutGroupRepository.save(rolloutGroup);
|
||||||
}
|
}
|
||||||
@@ -418,7 +481,11 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private long countTargetsFrom(final JpaRolloutGroup rolloutGroup) {
|
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) {
|
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) {
|
private boolean isRolloutGroupComplete(final JpaRollout rollout, final JpaRolloutGroup rolloutGroup) {
|
||||||
final Long actionsLeftForRollout = ActionType.DOWNLOAD_ONLY == rollout.getActionType()
|
final Long actionsLeftForRollout =
|
||||||
? actionRepository.countByRolloutAndRolloutGroupAndStatusNotIn(rollout, rolloutGroup,
|
actionRepository.countByRolloutAndRolloutGroupAndStatusNotIn(
|
||||||
DOWNLOAD_ONLY_ACTION_TERMINATION_STATUSES)
|
rollout, rolloutGroup,
|
||||||
: actionRepository.countByRolloutAndRolloutGroupAndStatusNotIn(rollout, rolloutGroup,
|
ActionType.DOWNLOAD_ONLY == rollout.getActionType() ?
|
||||||
DEFAULT_ACTION_TERMINATION_STATUSES);
|
DOWNLOAD_ONLY_ACTION_TERMINATION_STATUSES : DEFAULT_ACTION_TERMINATION_STATUSES);
|
||||||
return actionsLeftForRollout == 0;
|
return actionsLeftForRollout == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -456,12 +523,12 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkFinishCondition(final Rollout rollout, final RolloutGroup rolloutGroup,
|
private boolean checkSuccessCondition(final Rollout rollout, final RolloutGroup rolloutGroup, final RolloutGroup evalProxy,
|
||||||
final RolloutGroupSuccessCondition finishCondition) {
|
final RolloutGroupSuccessCondition successCondition) {
|
||||||
LOGGER.trace("Checking finish condition {} on rolloutgroup {}", finishCondition, rolloutGroup);
|
LOGGER.trace("Checking finish condition {} on rolloutgroup {}", successCondition, rolloutGroup);
|
||||||
try {
|
try {
|
||||||
final boolean isFinished = evaluationManager.getSuccessConditionEvaluator(finishCondition).eval(rollout,
|
final boolean isFinished = evaluationManager.getSuccessConditionEvaluator(successCondition).eval(rollout,
|
||||||
rolloutGroup, rolloutGroup.getSuccessConditionExp());
|
evalProxy, rolloutGroup.getSuccessConditionExp());
|
||||||
if (isFinished) {
|
if (isFinished) {
|
||||||
LOGGER.debug("Rolloutgroup {} is finished, starting next group", rolloutGroup);
|
LOGGER.debug("Rolloutgroup {} is finished, starting next group", rolloutGroup);
|
||||||
executeRolloutGroupSuccessAction(rollout, rolloutGroup);
|
executeRolloutGroupSuccessAction(rollout, rolloutGroup);
|
||||||
@@ -471,7 +538,7 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
|||||||
return isFinished;
|
return isFinished;
|
||||||
} catch (final EvaluatorNotConfiguredException e) {
|
} catch (final EvaluatorNotConfiguredException e) {
|
||||||
LOGGER.error("Something bad happened when accessing the finish condition or success action bean {}",
|
LOGGER.error("Something bad happened when accessing the finish condition or success action bean {}",
|
||||||
finishCondition.name(), e);
|
successCondition.name(), e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -513,18 +580,12 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
|||||||
return scheduledGroups == groupsToBeScheduled.size();
|
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);
|
RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.CREATING);
|
||||||
|
|
||||||
final JpaRolloutGroup group = (JpaRolloutGroup) group1;
|
final String groupTargetFilter = RolloutHelper.getGroupTargetFilter(
|
||||||
|
RolloutHelper.getTargetFilterQuery(rollout), group);
|
||||||
final String baseFilter = RolloutHelper.getTargetFilterQuery(rollout);
|
|
||||||
final String groupTargetFilter;
|
|
||||||
if (StringUtils.hasText(group.getTargetFilterQuery())) {
|
|
||||||
groupTargetFilter = baseFilter + ";" + group.getTargetFilterQuery();
|
|
||||||
} else {
|
|
||||||
groupTargetFilter = baseFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<Long> readyGroups = RolloutHelper.getGroupsByStatusIncludingGroup(rollout.getRolloutGroups(),
|
final List<Long> readyGroups = RolloutHelper.getGroupsByStatusIncludingGroup(rollout.getRolloutGroups(),
|
||||||
RolloutGroupStatus.READY, group);
|
RolloutGroupStatus.READY, group);
|
||||||
@@ -541,28 +602,38 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
|||||||
count -> targetManagement.countByFailedRolloutAndNotInRolloutGroups(readyGroups,
|
count -> targetManagement.countByFailedRolloutAndNotInRolloutGroups(readyGroups,
|
||||||
RolloutHelper.getIdFromRetriedTargetFilter(rollout.getTargetFilterQuery())));
|
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,
|
final long currentlyInGroup = DeploymentHelper.runInNewTransaction(txManager,
|
||||||
"countRolloutTargetGroupByRolloutGroup",
|
"countRolloutTargetGroupByRolloutGroup",
|
||||||
count -> rolloutTargetGroupRepository.countByRolloutGroup(group));
|
count -> rolloutTargetGroupRepository.countByRolloutGroup(group));
|
||||||
|
|
||||||
// Switch the Group status to READY, when there are enough Targets in
|
// Switch the Group status to READY, when there are enough Targets in the Group
|
||||||
// the Group
|
|
||||||
if (currentlyInGroup >= expectedInGroup) {
|
if (currentlyInGroup >= expectedInGroup) {
|
||||||
group.setStatus(RolloutGroupStatus.READY);
|
group.setStatus(RolloutGroupStatus.READY);
|
||||||
return rolloutGroupRepository.save(group);
|
return rolloutGroupRepository.save(group);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
long targetsLeftToAdd = expectedInGroup - currentlyInGroup;
|
long targetsLeftToAdd = expectedInGroup - currentlyInGroup;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
// Add up to TRANSACTION_TARGETS of the left targets
|
// Add up to TRANSACTION_TARGETS of the left targets
|
||||||
// In case a TransactionException is thrown this loop aborts
|
// 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));
|
Math.min(TRANSACTION_TARGETS, targetsLeftToAdd));
|
||||||
|
if (assigned == 0) {
|
||||||
|
break; // percent > 100 or some could have disappeared
|
||||||
|
} else {
|
||||||
|
targetsLeftToAdd -= assigned;
|
||||||
|
}
|
||||||
} while (targetsLeftToAdd > 0);
|
} while (targetsLeftToAdd > 0);
|
||||||
|
|
||||||
group.setStatus(RolloutGroupStatus.READY);
|
group.setStatus(RolloutGroupStatus.READY);
|
||||||
@@ -579,7 +650,6 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
|||||||
|
|
||||||
private Long assignTargetsToGroupInNewTransaction(final JpaRollout rollout, final RolloutGroup group,
|
private Long assignTargetsToGroupInNewTransaction(final JpaRollout rollout, final RolloutGroup group,
|
||||||
final String targetFilter, final long limit) {
|
final String targetFilter, final long limit) {
|
||||||
|
|
||||||
return DeploymentHelper.runInNewTransaction(txManager, "assignTargetsToRolloutGroup", status -> {
|
return DeploymentHelper.runInNewTransaction(txManager, "assignTargetsToRolloutGroup", status -> {
|
||||||
final PageRequest pageRequest = PageRequest.of(0, Math.toIntExact(limit));
|
final PageRequest pageRequest = PageRequest.of(0, Math.toIntExact(limit));
|
||||||
final List<Long> readyGroups = RolloutHelper.getGroupsByStatusIncludingGroup(rollout.getRolloutGroups(),
|
final List<Long> readyGroups = RolloutHelper.getGroupsByStatusIncludingGroup(rollout.getRolloutGroups(),
|
||||||
@@ -587,10 +657,10 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
|||||||
Slice<Target> targets;
|
Slice<Target> targets;
|
||||||
if (!RolloutHelper.isRolloutRetried(rollout.getTargetFilterQuery())) {
|
if (!RolloutHelper.isRolloutRetried(rollout.getTargetFilterQuery())) {
|
||||||
targets = targetManagement.findByTargetFilterQueryAndNotInRolloutGroupsAndCompatibleAndUpdatable(
|
targets = targetManagement.findByTargetFilterQueryAndNotInRolloutGroupsAndCompatibleAndUpdatable(
|
||||||
pageRequest, readyGroups, targetFilter, rollout.getDistributionSet().getType());
|
pageRequest, readyGroups, targetFilter, rollout.getDistributionSet().getType());
|
||||||
} else {
|
} else {
|
||||||
targets = targetManagement.findByFailedRolloutAndNotInRolloutGroups(
|
targets = targetManagement.findByFailedRolloutAndNotInRolloutGroups(
|
||||||
pageRequest, readyGroups, RolloutHelper.getIdFromRetriedTargetFilter(rollout.getTargetFilterQuery()));
|
pageRequest, readyGroups, RolloutHelper.getIdFromRetriedTargetFilter(rollout.getTargetFilterQuery()));
|
||||||
}
|
}
|
||||||
|
|
||||||
createAssignmentOfTargetsToGroup(targets, group);
|
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
|
* Schedules a group of the rollout. Scheduled Actions are created to
|
||||||
* achieve this. The creation of those Actions is allowed to fail.
|
* achieve this. The creation of those Actions is allowed to fail.
|
||||||
@@ -613,8 +798,10 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (actionsLeft <= 0) {
|
if (actionsLeft <= 0) {
|
||||||
group.setStatus(RolloutGroupStatus.SCHEDULED);
|
if (group.getStatus() != RolloutGroupStatus.SCHEDULED && group.getStatus() != RolloutGroupStatus.RUNNING) { // dynamic groups could already be running
|
||||||
rolloutGroupRepository.save(group);
|
group.setStatus(RolloutGroupStatus.SCHEDULED);
|
||||||
|
rolloutGroupRepository.save(group);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -625,8 +812,7 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
|||||||
try {
|
try {
|
||||||
long actionsCreated;
|
long actionsCreated;
|
||||||
do {
|
do {
|
||||||
actionsCreated = createActionsForTargetsInNewTransaction(rollout.getId(), group.getId(),
|
actionsCreated = createActionsForTargetsInNewTransaction(rollout, group, TRANSACTION_TARGETS);
|
||||||
TRANSACTION_TARGETS);
|
|
||||||
totalActionsCreated += actionsCreated;
|
totalActionsCreated += actionsCreated;
|
||||||
} while (actionsCreated > 0);
|
} while (actionsCreated > 0);
|
||||||
|
|
||||||
@@ -637,21 +823,17 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
|||||||
return totalActionsCreated;
|
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 -> {
|
return DeploymentHelper.runInNewTransaction(txManager, "createActionsForTargets", status -> {
|
||||||
final PageRequest pageRequest = PageRequest.of(0, limit);
|
final Slice<Target> targets =
|
||||||
final Rollout rollout = rolloutRepository.findById(rolloutId)
|
targetManagement.findByInRolloutGroupWithoutAction(PageRequest.of(0, limit), group.getId());
|
||||||
.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 Slice<Target> targets = targetManagement.findByInRolloutGroupWithoutAction(pageRequest, groupId);
|
|
||||||
if (targets.getNumberOfElements() > 0) {
|
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());
|
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
|
* Creates an action entry into the action repository. In case of existing
|
||||||
* scheduled actions the scheduled actions gets canceled. A scheduled action
|
* 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 ActionType actionType, final Long forcedTime, final Rollout rollout,
|
||||||
final RolloutGroup rolloutGroup) {
|
final RolloutGroup rolloutGroup) {
|
||||||
// cancel all current scheduled actions for this target. E.g. an action
|
// 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());
|
final List<Long> targetIds = targets.stream().map(Target::getId).collect(Collectors.toList());
|
||||||
deploymentManagement.cancelInactiveScheduledActionsForTargets(targetIds);
|
deploymentManagement.cancelInactiveScheduledActionsForTargets(targetIds);
|
||||||
targets.forEach(target -> {
|
targets.forEach(target -> {
|
||||||
|
|
||||||
assertActionsPerTargetQuota(target, 1);
|
assertActionsPerTargetQuota(target, 1);
|
||||||
|
|
||||||
final JpaAction action = new JpaAction();
|
final JpaAction action = new JpaAction();
|
||||||
action.setTarget(target);
|
action.setTarget(target);
|
||||||
action.setActive(false);
|
action.setActive(rolloutGroup.isDynamic());
|
||||||
action.setDistributionSet(distributionSet);
|
action.setDistributionSet(distributionSet);
|
||||||
action.setActionType(actionType);
|
action.setActionType(actionType);
|
||||||
action.setForcedTime(forcedTime);
|
action.setForcedTime(forcedTime);
|
||||||
action.setStatus(Status.SCHEDULED);
|
action.setStatus(rolloutGroup.isDynamic() ? Status.RUNNING : Status.SCHEDULED);
|
||||||
action.setRollout(rollout);
|
action.setRollout(rollout);
|
||||||
action.setRolloutGroup(rolloutGroup);
|
action.setRolloutGroup(rolloutGroup);
|
||||||
action.setInitiatedBy(rollout.getCreatedBy());
|
action.setInitiatedBy(rollout.getCreatedBy());
|
||||||
|
|||||||
@@ -655,10 +655,11 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
|
|||||||
final VirtualPropertyReplacer virtualPropertyReplacer,
|
final VirtualPropertyReplacer virtualPropertyReplacer,
|
||||||
final DistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement,
|
final DistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement,
|
||||||
final JpaProperties properties, final TenantConfigurationManagement tenantConfigurationManagement,
|
final JpaProperties properties, final TenantConfigurationManagement tenantConfigurationManagement,
|
||||||
|
final RepositoryProperties repositoryProperties,
|
||||||
final SystemSecurityContext systemSecurityContext, final ContextAware contextAware) {
|
final SystemSecurityContext systemSecurityContext, final ContextAware contextAware) {
|
||||||
return new JpaTargetFilterQueryManagement(targetFilterQueryRepository, targetManagement,
|
return new JpaTargetFilterQueryManagement(targetFilterQueryRepository, targetManagement,
|
||||||
virtualPropertyReplacer, distributionSetManagement, quotaManagement, properties.getDatabase(),
|
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 {
|
public class JpaRolloutCreate extends AbstractRolloutUpdateCreate<RolloutCreate> implements RolloutCreate {
|
||||||
private final DistributionSetManagement distributionSetManagement;
|
private final DistributionSetManagement distributionSetManagement;
|
||||||
|
private boolean dynamic;
|
||||||
|
|
||||||
JpaRolloutCreate(final DistributionSetManagement distributionSetManagement) {
|
JpaRolloutCreate(final DistributionSetManagement distributionSetManagement) {
|
||||||
this.distributionSetManagement = distributionSetManagement;
|
this.distributionSetManagement = distributionSetManagement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RolloutCreate dynamic(final boolean dynamic) {
|
||||||
|
this.dynamic = dynamic;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JpaRollout build() {
|
public JpaRollout build() {
|
||||||
final JpaRollout rollout = new JpaRollout();
|
final JpaRollout rollout = new JpaRollout();
|
||||||
@@ -31,6 +37,7 @@ public class JpaRolloutCreate extends AbstractRolloutUpdateCreate<RolloutCreate>
|
|||||||
rollout.setTargetFilterQuery(targetFilterQuery);
|
rollout.setTargetFilterQuery(targetFilterQuery);
|
||||||
rollout.setStartAt(startAt);
|
rollout.setStartAt(startAt);
|
||||||
rollout.setWeight(weight);
|
rollout.setWeight(weight);
|
||||||
|
rollout.setDynamic(dynamic);
|
||||||
|
|
||||||
if (actionType != null) {
|
if (actionType != null) {
|
||||||
rollout.setActionType(actionType);
|
rollout.setActionType(actionType);
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
import org.eclipse.hawkbit.repository.QuotaManagement;
|
import org.eclipse.hawkbit.repository.QuotaManagement;
|
||||||
import org.eclipse.hawkbit.repository.RepositoryConstants;
|
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.CancelTargetAssignmentEvent;
|
||||||
import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
|
import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
|
||||||
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
|
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
|
||||||
@@ -67,12 +68,13 @@ public abstract class AbstractDsAssignmentStrategy {
|
|||||||
private final QuotaManagement quotaManagement;
|
private final QuotaManagement quotaManagement;
|
||||||
private final BooleanSupplier multiAssignmentsConfig;
|
private final BooleanSupplier multiAssignmentsConfig;
|
||||||
private final BooleanSupplier confirmationFlowConfig;
|
private final BooleanSupplier confirmationFlowConfig;
|
||||||
|
private final RepositoryProperties repositoryProperties;
|
||||||
|
|
||||||
AbstractDsAssignmentStrategy(final TargetRepository targetRepository,
|
AbstractDsAssignmentStrategy(final TargetRepository targetRepository,
|
||||||
final AfterTransactionCommitExecutor afterCommit, final EventPublisherHolder eventPublisherHolder,
|
final AfterTransactionCommitExecutor afterCommit, final EventPublisherHolder eventPublisherHolder,
|
||||||
final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository,
|
final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository,
|
||||||
final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig,
|
final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig,
|
||||||
final BooleanSupplier confirmationFlowConfig) {
|
final BooleanSupplier confirmationFlowConfig, final RepositoryProperties repositoryProperties) {
|
||||||
this.targetRepository = targetRepository;
|
this.targetRepository = targetRepository;
|
||||||
this.afterCommit = afterCommit;
|
this.afterCommit = afterCommit;
|
||||||
this.eventPublisherHolder = eventPublisherHolder;
|
this.eventPublisherHolder = eventPublisherHolder;
|
||||||
@@ -81,6 +83,7 @@ public abstract class AbstractDsAssignmentStrategy {
|
|||||||
this.quotaManagement = quotaManagement;
|
this.quotaManagement = quotaManagement;
|
||||||
this.multiAssignmentsConfig = multiAssignmentsConfig;
|
this.multiAssignmentsConfig = multiAssignmentsConfig;
|
||||||
this.confirmationFlowConfig = confirmationFlowConfig;
|
this.confirmationFlowConfig = confirmationFlowConfig;
|
||||||
|
this.repositoryProperties = repositoryProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -252,7 +255,9 @@ public abstract class AbstractDsAssignmentStrategy {
|
|||||||
final JpaAction actionForTarget = new JpaAction();
|
final JpaAction actionForTarget = new JpaAction();
|
||||||
actionForTarget.setActionType(targetWithActionType.getActionType());
|
actionForTarget.setActionType(targetWithActionType.getActionType());
|
||||||
actionForTarget.setForcedTime(targetWithActionType.getForceTime());
|
actionForTarget.setForcedTime(targetWithActionType.getForceTime());
|
||||||
actionForTarget.setWeight(targetWithActionType.getWeight());
|
actionForTarget.setWeight(
|
||||||
|
targetWithActionType.getWeight() == null ?
|
||||||
|
repositoryProperties.getActionWeightIfAbsent() : targetWithActionType.getWeight());
|
||||||
actionForTarget.setActive(true);
|
actionForTarget.setActive(true);
|
||||||
actionForTarget.setTarget(target);
|
actionForTarget.setTarget(target);
|
||||||
actionForTarget.setDistributionSet(set);
|
actionForTarget.setDistributionSet(set);
|
||||||
|
|||||||
@@ -176,10 +176,10 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
|||||||
this.txManager = txManager;
|
this.txManager = txManager;
|
||||||
onlineDsAssignmentStrategy = new OnlineDsAssignmentStrategy(targetRepository, afterCommit, eventPublisherHolder,
|
onlineDsAssignmentStrategy = new OnlineDsAssignmentStrategy(targetRepository, afterCommit, eventPublisherHolder,
|
||||||
actionRepository, actionStatusRepository, quotaManagement, this::isMultiAssignmentsEnabled,
|
actionRepository, actionStatusRepository, quotaManagement, this::isMultiAssignmentsEnabled,
|
||||||
this::isConfirmationFlowEnabled);
|
this::isConfirmationFlowEnabled, repositoryProperties);
|
||||||
offlineDsAssignmentStrategy = new OfflineDsAssignmentStrategy(targetRepository, afterCommit,
|
offlineDsAssignmentStrategy = new OfflineDsAssignmentStrategy(targetRepository, afterCommit,
|
||||||
eventPublisherHolder, actionRepository, actionStatusRepository, quotaManagement,
|
eventPublisherHolder, actionRepository, actionStatusRepository, quotaManagement,
|
||||||
this::isMultiAssignmentsEnabled, this::isConfirmationFlowEnabled);
|
this::isMultiAssignmentsEnabled, this::isConfirmationFlowEnabled, repositoryProperties);
|
||||||
this.tenantConfigurationManagement = tenantConfigurationManagement;
|
this.tenantConfigurationManagement = tenantConfigurationManagement;
|
||||||
this.systemSecurityContext = systemSecurityContext;
|
this.systemSecurityContext = systemSecurityContext;
|
||||||
this.tenantAware = tenantAware;
|
this.tenantAware = tenantAware;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import java.util.Collections;
|
|||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -26,6 +27,7 @@ import javax.validation.ValidationException;
|
|||||||
|
|
||||||
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.RepositoryProperties;
|
||||||
import org.eclipse.hawkbit.repository.RolloutApprovalStrategy;
|
import org.eclipse.hawkbit.repository.RolloutApprovalStrategy;
|
||||||
import org.eclipse.hawkbit.repository.RolloutFields;
|
import org.eclipse.hawkbit.repository.RolloutFields;
|
||||||
import org.eclipse.hawkbit.repository.RolloutHelper;
|
import org.eclipse.hawkbit.repository.RolloutHelper;
|
||||||
@@ -109,6 +111,9 @@ public class JpaRolloutManagement implements RolloutManagement {
|
|||||||
RolloutStatus.CREATING, RolloutStatus.PAUSED, RolloutStatus.READY, RolloutStatus.STARTING,
|
RolloutStatus.CREATING, RolloutStatus.PAUSED, RolloutStatus.READY, RolloutStatus.STARTING,
|
||||||
RolloutStatus.WAITING_FOR_APPROVAL, RolloutStatus.APPROVAL_DENIED);
|
RolloutStatus.WAITING_FOR_APPROVAL, RolloutStatus.APPROVAL_DENIED);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RepositoryProperties repositoryProperties;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private RolloutRepository rolloutRepository;
|
private RolloutRepository rolloutRepository;
|
||||||
|
|
||||||
@@ -219,6 +224,9 @@ public class JpaRolloutManagement implements RolloutManagement {
|
|||||||
throw new ValidationException(errMsg);
|
throw new ValidationException(errMsg);
|
||||||
}
|
}
|
||||||
rollout.setTotalTargets(totalTargets);
|
rollout.setTotalTargets(totalTargets);
|
||||||
|
if (rollout.getWeight().isEmpty()) {
|
||||||
|
rollout.setWeight(repositoryProperties.getActionWeightIfAbsent());
|
||||||
|
}
|
||||||
contextAware.getCurrentContext().ifPresent(rollout::setAccessControlContext);
|
contextAware.getCurrentContext().ifPresent(rollout::setAccessControlContext);
|
||||||
return rolloutRepository.save(rollout);
|
return rolloutRepository.save(rollout);
|
||||||
}
|
}
|
||||||
@@ -247,7 +255,15 @@ public class JpaRolloutManagement implements RolloutManagement {
|
|||||||
|
|
||||||
addSuccessAndErrorConditionsAndActions(group, conditions);
|
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);
|
lastSavedGroup = rolloutGroupRepository.save(group);
|
||||||
publishRolloutGroupCreatedEventAfterCommit(lastSavedGroup, rollout);
|
publishRolloutGroupCreatedEventAfterCommit(lastSavedGroup, rollout);
|
||||||
@@ -350,7 +366,7 @@ public class JpaRolloutManagement implements RolloutManagement {
|
|||||||
return group;
|
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(
|
afterCommit.afterCommit(() -> eventPublisherHolder.getEventPublisher().publishEvent(
|
||||||
new RolloutGroupCreatedEvent(group, rollout.getId(), eventPublisherHolder.getApplicationId())));
|
new RolloutGroupCreatedEvent(group, rollout.getId(), eventPublisherHolder.getApplicationId())));
|
||||||
}
|
}
|
||||||
@@ -672,11 +688,13 @@ public class JpaRolloutManagement implements RolloutManagement {
|
|||||||
realTargetsInGroup = targetsInGroupFilter - overlappingTargets;
|
realTargetsInGroup = targetsInGroupFilter - overlappingTargets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// new style percent - total percent
|
||||||
|
final double percentFromRest = RolloutHelper.toPercentFromTheRest(group, groups);
|
||||||
|
|
||||||
final long reducedTargetsInGroup = Math
|
final long reducedTargetsInGroup = Math
|
||||||
.round(group.getTargetPercentage() / 100 * (double) realTargetsInGroup);
|
.round(percentFromRest / 100 * (double) realTargetsInGroup);
|
||||||
groupTargetCounts.add(reducedTargetsInGroup);
|
groupTargetCounts.add(reducedTargetsInGroup);
|
||||||
unusedTargetsCount += realTargetsInGroup - reducedTargetsInGroup;
|
unusedTargetsCount += realTargetsInGroup - reducedTargetsInGroup;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new RolloutGroupsValidation(totalTargets, groupTargetCounts);
|
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.DistributionSetManagement;
|
||||||
import org.eclipse.hawkbit.repository.QuotaManagement;
|
import org.eclipse.hawkbit.repository.QuotaManagement;
|
||||||
|
import org.eclipse.hawkbit.repository.RepositoryProperties;
|
||||||
import org.eclipse.hawkbit.repository.TargetFields;
|
import org.eclipse.hawkbit.repository.TargetFields;
|
||||||
import org.eclipse.hawkbit.repository.TargetFilterQueryFields;
|
import org.eclipse.hawkbit.repository.TargetFilterQueryFields;
|
||||||
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
|
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
|
||||||
@@ -84,6 +85,7 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
|
|||||||
private final DistributionSetManagement distributionSetManagement;
|
private final DistributionSetManagement distributionSetManagement;
|
||||||
private final QuotaManagement quotaManagement;
|
private final QuotaManagement quotaManagement;
|
||||||
private final TenantConfigurationManagement tenantConfigurationManagement;
|
private final TenantConfigurationManagement tenantConfigurationManagement;
|
||||||
|
private final RepositoryProperties repositoryProperties;
|
||||||
private final SystemSecurityContext systemSecurityContext;
|
private final SystemSecurityContext systemSecurityContext;
|
||||||
private final ContextAware contextAware;
|
private final ContextAware contextAware;
|
||||||
|
|
||||||
@@ -93,6 +95,7 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
|
|||||||
final TargetManagement targetManagement, final VirtualPropertyReplacer virtualPropertyReplacer,
|
final TargetManagement targetManagement, final VirtualPropertyReplacer virtualPropertyReplacer,
|
||||||
final DistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement,
|
final DistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement,
|
||||||
final Database database, final TenantConfigurationManagement tenantConfigurationManagement,
|
final Database database, final TenantConfigurationManagement tenantConfigurationManagement,
|
||||||
|
final RepositoryProperties repositoryProperties,
|
||||||
final SystemSecurityContext systemSecurityContext, final ContextAware contextAware) {
|
final SystemSecurityContext systemSecurityContext, final ContextAware contextAware) {
|
||||||
this.targetFilterQueryRepository = targetFilterQueryRepository;
|
this.targetFilterQueryRepository = targetFilterQueryRepository;
|
||||||
this.targetManagement = targetManagement;
|
this.targetManagement = targetManagement;
|
||||||
@@ -101,6 +104,7 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
|
|||||||
this.quotaManagement = quotaManagement;
|
this.quotaManagement = quotaManagement;
|
||||||
this.database = database;
|
this.database = database;
|
||||||
this.tenantConfigurationManagement = tenantConfigurationManagement;
|
this.tenantConfigurationManagement = tenantConfigurationManagement;
|
||||||
|
this.repositoryProperties = repositoryProperties;
|
||||||
this.systemSecurityContext = systemSecurityContext;
|
this.systemSecurityContext = systemSecurityContext;
|
||||||
this.contextAware = contextAware;
|
this.contextAware = contextAware;
|
||||||
}
|
}
|
||||||
@@ -270,7 +274,7 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
|
|||||||
targetFilterQuery.setAccessControlContext(null);
|
targetFilterQuery.setAccessControlContext(null);
|
||||||
targetFilterQuery.setAutoAssignDistributionSet(null);
|
targetFilterQuery.setAutoAssignDistributionSet(null);
|
||||||
targetFilterQuery.setAutoAssignActionType(null);
|
targetFilterQuery.setAutoAssignActionType(null);
|
||||||
targetFilterQuery.setAutoAssignWeight(null);
|
targetFilterQuery.setAutoAssignWeight(0);
|
||||||
targetFilterQuery.setAutoAssignInitiatedBy(null);
|
targetFilterQuery.setAutoAssignInitiatedBy(null);
|
||||||
targetFilterQuery.setConfirmationRequired(false);
|
targetFilterQuery.setConfirmationRequired(false);
|
||||||
} else {
|
} else {
|
||||||
@@ -284,9 +288,11 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
|
|||||||
contextAware.getCurrentContext().ifPresent(targetFilterQuery::setAccessControlContext);
|
contextAware.getCurrentContext().ifPresent(targetFilterQuery::setAccessControlContext);
|
||||||
targetFilterQuery.setAutoAssignInitiatedBy(contextAware.getCurrentUsername());
|
targetFilterQuery.setAutoAssignInitiatedBy(contextAware.getCurrentUsername());
|
||||||
targetFilterQuery.setAutoAssignActionType(sanitizeAutoAssignActionType(update.getActionType()));
|
targetFilterQuery.setAutoAssignActionType(sanitizeAutoAssignActionType(update.getActionType()));
|
||||||
targetFilterQuery.setAutoAssignWeight(update.getWeight());
|
targetFilterQuery.setAutoAssignWeight(
|
||||||
final boolean confirmationRequired = update.isConfirmationRequired() == null ? isConfirmationFlowEnabled()
|
update.getWeight() == null ? repositoryProperties.getActionWeightIfAbsent() : update.getWeight());
|
||||||
: update.isConfirmationRequired();
|
final boolean confirmationRequired =
|
||||||
|
update.isConfirmationRequired() == null ?
|
||||||
|
isConfirmationFlowEnabled() : update.isConfirmationRequired();
|
||||||
targetFilterQuery.setConfirmationRequired(confirmationRequired);
|
targetFilterQuery.setConfirmationRequired(confirmationRequired);
|
||||||
}
|
}
|
||||||
return targetFilterQueryRepository.save(targetFilterQuery);
|
return targetFilterQueryRepository.save(targetFilterQuery);
|
||||||
|
|||||||
@@ -698,6 +698,27 @@ public class JpaTargetManagement implements TargetManagement {
|
|||||||
.map(Target.class::cast);
|
.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
|
@Override
|
||||||
public Slice<Target> findByFailedRolloutAndNotInRolloutGroups(Pageable pageRequest, Collection<Long> groups,
|
public Slice<Target> findByFailedRolloutAndNotInRolloutGroups(Pageable pageRequest, Collection<Long> groups,
|
||||||
String rolloutId) {
|
String rolloutId) {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
import org.eclipse.hawkbit.repository.QuotaManagement;
|
import org.eclipse.hawkbit.repository.QuotaManagement;
|
||||||
import org.eclipse.hawkbit.repository.RepositoryConstants;
|
import org.eclipse.hawkbit.repository.RepositoryConstants;
|
||||||
|
import org.eclipse.hawkbit.repository.RepositoryProperties;
|
||||||
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
|
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
|
||||||
import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
|
import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
|
||||||
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
|
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
|
||||||
@@ -52,9 +53,9 @@ public class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
|
|||||||
final AfterTransactionCommitExecutor afterCommit, final EventPublisherHolder eventPublisherHolder,
|
final AfterTransactionCommitExecutor afterCommit, final EventPublisherHolder eventPublisherHolder,
|
||||||
final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository,
|
final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository,
|
||||||
final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig,
|
final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig,
|
||||||
final BooleanSupplier confirmationFlowConfig) {
|
final BooleanSupplier confirmationFlowConfig, final RepositoryProperties repositoryProperties) {
|
||||||
super(targetRepository, afterCommit, eventPublisherHolder, actionRepository, actionStatusRepository,
|
super(targetRepository, afterCommit, eventPublisherHolder, actionRepository, actionStatusRepository,
|
||||||
quotaManagement, multiAssignmentsConfig, confirmationFlowConfig);
|
quotaManagement, multiAssignmentsConfig, confirmationFlowConfig, repositoryProperties);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import java.util.stream.Collectors;
|
|||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.eclipse.hawkbit.repository.QuotaManagement;
|
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.MultiActionAssignEvent;
|
||||||
import org.eclipse.hawkbit.repository.event.remote.MultiActionCancelEvent;
|
import org.eclipse.hawkbit.repository.event.remote.MultiActionCancelEvent;
|
||||||
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
|
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
|
||||||
@@ -55,9 +56,9 @@ public class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
|
|||||||
final AfterTransactionCommitExecutor afterCommit, final EventPublisherHolder eventPublisherHolder,
|
final AfterTransactionCommitExecutor afterCommit, final EventPublisherHolder eventPublisherHolder,
|
||||||
final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository,
|
final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository,
|
||||||
final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig,
|
final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig,
|
||||||
final BooleanSupplier confirmationFlowConfig) {
|
final BooleanSupplier confirmationFlowConfig, final RepositoryProperties repositoryProperties) {
|
||||||
super(targetRepository, afterCommit, eventPublisherHolder, actionRepository, actionStatusRepository,
|
super(targetRepository, afterCommit, eventPublisherHolder, actionRepository, actionStatusRepository,
|
||||||
quotaManagement, multiAssignmentsConfig, confirmationFlowConfig);
|
quotaManagement, multiAssignmentsConfig, confirmationFlowConfig, repositoryProperties);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -140,6 +140,9 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
|
|||||||
@Max(Action.WEIGHT_MAX)
|
@Max(Action.WEIGHT_MAX)
|
||||||
private Integer weight;
|
private Integer weight;
|
||||||
|
|
||||||
|
@Column(name = "is_dynamic") // dynamic is reserved keyword in some databases
|
||||||
|
private Boolean dynamic;
|
||||||
|
|
||||||
@Column(name = "access_control_context", nullable = true)
|
@Column(name = "access_control_context", nullable = true)
|
||||||
private String accessControlContext;
|
private String accessControlContext;
|
||||||
|
|
||||||
@@ -225,6 +228,21 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
|
|||||||
this.weight = weight;
|
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() {
|
public Optional<String> getAccessControlContext() {
|
||||||
return Optional.ofNullable(accessControlContext);
|
return Optional.ofNullable(accessControlContext);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,6 +78,9 @@ public class JpaRolloutGroup extends AbstractJpaNamedEntity implements RolloutGr
|
|||||||
@JoinColumn(name = "parent_id")
|
@JoinColumn(name = "parent_id")
|
||||||
private JpaRolloutGroup parent;
|
private JpaRolloutGroup parent;
|
||||||
|
|
||||||
|
@Column(name = "is_dynamic") // dynamic is reserved keyword in some databases
|
||||||
|
private boolean dynamic;
|
||||||
|
|
||||||
@Column(name = "success_condition", nullable = false)
|
@Column(name = "success_condition", nullable = false)
|
||||||
@NotNull
|
@NotNull
|
||||||
private RolloutGroupSuccessCondition successCondition = RolloutGroupSuccessCondition.THRESHOLD;
|
private RolloutGroupSuccessCondition successCondition = RolloutGroupSuccessCondition.THRESHOLD;
|
||||||
@@ -156,6 +159,15 @@ public class JpaRolloutGroup extends AbstractJpaNamedEntity implements RolloutGr
|
|||||||
return parent;
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDynamic() {
|
||||||
|
return dynamic;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDynamic(final boolean dynamic) {
|
||||||
|
this.dynamic = dynamic;
|
||||||
|
}
|
||||||
|
|
||||||
public void setParent(final RolloutGroup parent) {
|
public void setParent(final RolloutGroup parent) {
|
||||||
this.parent = (JpaRolloutGroup) parent;
|
this.parent = (JpaRolloutGroup) parent;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ public class JpaTargetFilterQuery extends AbstractJpaTenantAwareBaseEntity
|
|||||||
this.query = query;
|
this.query = query;
|
||||||
this.autoAssignDistributionSet = (JpaDistributionSet) autoAssignDistributionSet;
|
this.autoAssignDistributionSet = (JpaDistributionSet) autoAssignDistributionSet;
|
||||||
this.autoAssignActionType = autoAssignActionType;
|
this.autoAssignActionType = autoAssignActionType;
|
||||||
this.autoAssignWeight = autoAssignWeight;
|
this.autoAssignWeight = autoAssignWeight == null ? 0 : autoAssignWeight;
|
||||||
this.confirmationRequired = confirmationRequired;
|
this.confirmationRequired = confirmationRequired;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,11 +75,15 @@ public class StartNextGroupRolloutGroupSuccessAction implements RolloutGroupActi
|
|||||||
final List<JpaRolloutGroup> findByRolloutGroupParent = rolloutGroupRepository
|
final List<JpaRolloutGroup> findByRolloutGroupParent = rolloutGroupRepository
|
||||||
.findByParentIdAndStatus(rolloutGroup.getId(), RolloutGroupStatus.SCHEDULED);
|
.findByParentIdAndStatus(rolloutGroup.getId(), RolloutGroupStatus.SCHEDULED);
|
||||||
findByRolloutGroupParent.forEach(nextGroup -> {
|
findByRolloutGroupParent.forEach(nextGroup -> {
|
||||||
logger.debug("Rolloutgroup {} is finished, starting next group", nextGroup);
|
if (nextGroup.isDynamic()) {
|
||||||
nextGroup.setStatus(RolloutGroupStatus.FINISHED);
|
nextGroup.setStatus(RolloutGroupStatus.RUNNING);
|
||||||
rolloutGroupRepository.save(nextGroup);
|
} else {
|
||||||
// find the next group to set in running state
|
logger.debug("Rolloutgroup {} is finished, starting next group", nextGroup);
|
||||||
startNextGroup(rollout, 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
|
@Override
|
||||||
public boolean eval(final Rollout rollout, final RolloutGroup rolloutGroup, final String expression) {
|
public boolean eval(final Rollout rollout, final RolloutGroup rolloutGroup, final String expression) {
|
||||||
final Long totalGroup = actionRepository.countByRolloutAndRolloutGroup((JpaRollout) rollout,
|
final long totalGroup = rolloutGroup.getTotalTargets();
|
||||||
(JpaRolloutGroup) rolloutGroup);
|
|
||||||
final Long error = actionRepository.countByRolloutIdAndRolloutGroupIdAndStatus(rollout.getId(),
|
final Long error = actionRepository.countByRolloutIdAndRolloutGroupIdAndStatus(rollout.getId(),
|
||||||
rolloutGroup.getId(), Action.Status.ERROR);
|
rolloutGroup.getId(), Action.Status.ERROR);
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ public class ThresholdRolloutGroupSuccessCondition
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean eval(final Rollout rollout, final RolloutGroup rolloutGroup, final String expression) {
|
public boolean eval(final Rollout rollout, final RolloutGroup rolloutGroup, final String expression) {
|
||||||
|
|
||||||
final long totalGroup = rolloutGroup.getTotalTargets();
|
final long totalGroup = rolloutGroup.getTotalTargets();
|
||||||
if (totalGroup == 0) {
|
if (totalGroup == 0) {
|
||||||
// in case e.g. targets has been deleted we don't have any
|
// 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.JpaDistributionSetType_;
|
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType_;
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_;
|
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.JpaRolloutGroup_;
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout_;
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
|
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag;
|
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag;
|
||||||
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) -> {
|
return (targetRoot, query, cb) -> {
|
||||||
final ListJoin<JpaTarget, RolloutTargetGroup> rolloutTargetJoin = targetRoot
|
final ListJoin<JpaTarget, RolloutTargetGroup> rolloutTargetJoin = targetRoot
|
||||||
.join(JpaTarget_.rolloutTargetGroup, JoinType.LEFT);
|
.join(JpaTarget_.rolloutTargetGroup, JoinType.LEFT);
|
||||||
final Predicate inRolloutGroups = rolloutTargetJoin.get(RolloutTargetGroup_.rolloutGroup)
|
rolloutTargetJoin.on(rolloutTargetJoin.get(RolloutTargetGroup_.rolloutGroup)
|
||||||
.get(JpaRolloutGroup_.id).in(groups);
|
.get(JpaRolloutGroup_.id).in(groups));
|
||||||
rolloutTargetJoin.on(inRolloutGroups);
|
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));
|
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 bypassWeightEnforcement = true;
|
||||||
final boolean multiAssignmentsEnabled = TenantConfigHelper
|
final boolean multiAssignmentsEnabled = TenantConfigHelper
|
||||||
.usingContext(systemSecurityContext, tenantConfigurationManagement).isMultiAssignmentsEnabled();
|
.usingContext(systemSecurityContext, tenantConfigurationManagement).isMultiAssignmentsEnabled();
|
||||||
if (!multiAssignmentsEnabled && hasWeight) {
|
if (bypassWeightEnforcement) {
|
||||||
throw new MultiAssignmentIsNotEnabledException();
|
|
||||||
} else if (bypassWeightEnforcement) {
|
|
||||||
return;
|
return;
|
||||||
} else if (multiAssignmentsEnabled && hasNoWeight) {
|
} else if (multiAssignmentsEnabled && hasNoWeight) {
|
||||||
throw new NoWeightProvidedInMultiAssignmentModeException();
|
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;
|
||||||
@@ -106,6 +106,7 @@ public class RemoteTenantAwareEventTest extends AbstractRemoteEventTest {
|
|||||||
generateAction.setDistributionSet(dsA);
|
generateAction.setDistributionSet(dsA);
|
||||||
generateAction.setStatus(Status.RUNNING);
|
generateAction.setStatus(Status.RUNNING);
|
||||||
generateAction.setInitiatedBy(tenantAware.getCurrentUsername());
|
generateAction.setInitiatedBy(tenantAware.getCurrentUsername());
|
||||||
|
generateAction.setWeight(1000);
|
||||||
|
|
||||||
final Action action = actionRepository.save(generateAction);
|
final Action action = actionRepository.save(generateAction);
|
||||||
|
|
||||||
@@ -132,6 +133,7 @@ public class RemoteTenantAwareEventTest extends AbstractRemoteEventTest {
|
|||||||
generateAction.setDistributionSet(dsA);
|
generateAction.setDistributionSet(dsA);
|
||||||
generateAction.setStatus(Status.RUNNING);
|
generateAction.setStatus(Status.RUNNING);
|
||||||
generateAction.setInitiatedBy(tenantAware.getCurrentUsername());
|
generateAction.setInitiatedBy(tenantAware.getCurrentUsername());
|
||||||
|
generateAction.setWeight(1000);
|
||||||
|
|
||||||
final Action action = actionRepository.save(generateAction);
|
final Action action = actionRepository.save(generateAction);
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.event.remote.entity;
|
|||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
import net.bytebuddy.agent.builder.AgentBuilder;
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
|
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
|
||||||
import org.eclipse.hawkbit.repository.model.Action;
|
import org.eclipse.hawkbit.repository.model.Action;
|
||||||
import org.eclipse.hawkbit.repository.model.Action.ActionType;
|
import org.eclipse.hawkbit.repository.model.Action.ActionType;
|
||||||
@@ -86,6 +87,7 @@ public class ActionEventTest extends AbstractRemoteEntityEventTest<Action> {
|
|||||||
generateAction.setDistributionSet(distributionSet);
|
generateAction.setDistributionSet(distributionSet);
|
||||||
generateAction.setStatus(Status.RUNNING);
|
generateAction.setStatus(Status.RUNNING);
|
||||||
generateAction.setInitiatedBy(tenantAware.getCurrentUsername());
|
generateAction.setInitiatedBy(tenantAware.getCurrentUsername());
|
||||||
|
generateAction.setWeight(1000);
|
||||||
return actionRepository.save(generateAction);
|
return actionRepository.save(generateAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ import javax.persistence.PersistenceContext;
|
|||||||
import org.assertj.core.api.Assertions;
|
import org.assertj.core.api.Assertions;
|
||||||
import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
|
import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
|
||||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||||
|
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.repository.ActionRepository;
|
import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository;
|
||||||
import org.eclipse.hawkbit.repository.jpa.repository.ActionStatusRepository;
|
import org.eclipse.hawkbit.repository.jpa.repository.ActionStatusRepository;
|
||||||
import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetRepository;
|
import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetRepository;
|
||||||
@@ -40,6 +43,7 @@ import org.eclipse.hawkbit.repository.model.DistributionSet;
|
|||||||
import org.eclipse.hawkbit.repository.model.DistributionSetTag;
|
import org.eclipse.hawkbit.repository.model.DistributionSetTag;
|
||||||
import org.eclipse.hawkbit.repository.model.DistributionSetTagAssignmentResult;
|
import org.eclipse.hawkbit.repository.model.DistributionSetTagAssignmentResult;
|
||||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||||
|
import org.eclipse.hawkbit.repository.model.RolloutGroup;
|
||||||
import org.eclipse.hawkbit.repository.model.Target;
|
import org.eclipse.hawkbit.repository.model.Target;
|
||||||
import org.eclipse.hawkbit.repository.model.TargetTag;
|
import org.eclipse.hawkbit.repository.model.TargetTag;
|
||||||
import org.eclipse.hawkbit.repository.model.TargetTagAssignmentResult;
|
import org.eclipse.hawkbit.repository.model.TargetTagAssignmentResult;
|
||||||
@@ -52,6 +56,7 @@ import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
|
|||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
|
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
|
||||||
import org.springframework.cloud.stream.test.binder.TestSupportBinderAutoConfiguration;
|
import org.springframework.cloud.stream.test.binder.TestSupportBinderAutoConfiguration;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.orm.jpa.vendor.Database;
|
import org.springframework.orm.jpa.vendor.Database;
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
import org.springframework.test.context.TestPropertySource;
|
import org.springframework.test.context.TestPropertySource;
|
||||||
@@ -59,6 +64,8 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
@ContextConfiguration(classes = {
|
@ContextConfiguration(classes = {
|
||||||
RepositoryApplicationConfiguration.class, TestConfiguration.class,
|
RepositoryApplicationConfiguration.class, TestConfiguration.class,
|
||||||
TestSupportBinderAutoConfiguration.class })
|
TestSupportBinderAutoConfiguration.class })
|
||||||
@@ -159,4 +166,42 @@ public abstract class AbstractJpaIntegrationTest extends AbstractIntegrationTest
|
|||||||
targets.stream().map(Target::getControllerId).collect(Collectors.toList()), type.getId());
|
targets.stream().map(Target::getControllerId).collect(Collectors.toList()), type.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void assertRollout(final Rollout rollout, final boolean dynamic, final Rollout.RolloutStatus status, final int groupCreated, final long totalTargets) {
|
||||||
|
final Rollout refreshed = refresh(rollout);
|
||||||
|
assertThat(refreshed.isDynamic()).isEqualTo(dynamic);
|
||||||
|
assertThat(refreshed.getStatus()).isEqualTo(status);
|
||||||
|
assertThat(refreshed.getRolloutGroupsCreated()).isEqualTo(groupCreated);
|
||||||
|
assertThat(refreshed.getTotalTargets()).isEqualTo(totalTargets);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void assertGroup(final RolloutGroup group, final boolean dynamic, final RolloutGroup.RolloutGroupStatus status, final long totalTargets) {
|
||||||
|
final RolloutGroup refreshed = refresh(group);
|
||||||
|
assertThat(refreshed.isDynamic()).isEqualTo(dynamic);
|
||||||
|
assertThat(refreshed.getStatus()).isEqualTo(status);
|
||||||
|
assertThat(refreshed.getTotalTargets()).isEqualTo(totalTargets);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Page<JpaAction> assertAndGetRunning(final Rollout rollout, final int count) {
|
||||||
|
final Page<JpaAction> running = actionRepository.findByRolloutIdAndStatus(PAGE, rollout.getId(), Action.Status.RUNNING);
|
||||||
|
assertThat(running.getTotalElements()).isEqualTo(count);
|
||||||
|
return running;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void assertScheduled(final Rollout rollout, final int count) {
|
||||||
|
final Page<JpaAction> running = actionRepository.findByRolloutIdAndStatus(PAGE, rollout.getId(), Action.Status.SCHEDULED);
|
||||||
|
assertThat(running.getTotalElements()).isEqualTo(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void finishAction(final Action action) {
|
||||||
|
controllerManagement
|
||||||
|
.addUpdateActionStatus(entityFactory.actionStatus().create(action.getId()).status(Action.Status.FINISHED));
|
||||||
|
}
|
||||||
|
|
||||||
|
private JpaRollout refresh(final Rollout rollout) {
|
||||||
|
return rolloutRepository.findById(rollout.getId()).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected JpaRolloutGroup refresh(final RolloutGroup group) {
|
||||||
|
return rolloutGroupRepository.findById(group.getId()).get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -440,7 +440,7 @@ class AutoAssignCheckerIntTest extends AbstractJpaIntegrationTest {
|
|||||||
|
|
||||||
final List<Action> actions = deploymentManagement.findActionsAll(PAGE).getContent();
|
final List<Action> actions = deploymentManagement.findActionsAll(PAGE).getContent();
|
||||||
assertThat(actions).hasSize(amountOfTargets);
|
assertThat(actions).hasSize(amountOfTargets);
|
||||||
assertThat(actions).allMatch(action -> !action.getWeight().isPresent());
|
assertThat(actions).allMatch(action -> action.getWeight().isPresent());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ public class AutoActionCleanupTest extends AbstractJpaIntegrationTest {
|
|||||||
|
|
||||||
assertThat(actionRepository.count()).isEqualTo(3);
|
assertThat(actionRepository.count()).isEqualTo(3);
|
||||||
|
|
||||||
|
waitNextMillis();
|
||||||
autoActionCleanup.run();
|
autoActionCleanup.run();
|
||||||
|
|
||||||
assertThat(actionRepository.count()).isEqualTo(1);
|
assertThat(actionRepository.count()).isEqualTo(1);
|
||||||
|
|||||||
@@ -1101,6 +1101,9 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
controllerManagement.addUpdateActionStatus(entityFactory.actionStatus().create(actionId)
|
controllerManagement.addUpdateActionStatus(entityFactory.actionStatus().create(actionId)
|
||||||
.status(Action.Status.RUNNING).occurredAt(System.currentTimeMillis())
|
.status(Action.Status.RUNNING).occurredAt(System.currentTimeMillis())
|
||||||
.messages(Lists.newArrayList("proceeding message 1")));
|
.messages(Lists.newArrayList("proceeding message 1")));
|
||||||
|
|
||||||
|
final long createTime = System.currentTimeMillis();
|
||||||
|
waitNextMillis();
|
||||||
controllerManagement.addUpdateActionStatus(entityFactory.actionStatus().create(actionId)
|
controllerManagement.addUpdateActionStatus(entityFactory.actionStatus().create(actionId)
|
||||||
.status(Action.Status.RUNNING).occurredAt(System.currentTimeMillis())
|
.status(Action.Status.RUNNING).occurredAt(System.currentTimeMillis())
|
||||||
.messages(Lists.newArrayList("proceeding message 2")));
|
.messages(Lists.newArrayList("proceeding message 2")));
|
||||||
|
|||||||
@@ -972,18 +972,14 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Description("An assignment request containing a weight causes an error when multi assignment in disabled.")
|
@Description("An assignment request containing a weight causes an error when multi assignment in disabled.")
|
||||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
void weightAllowedWhenMultiAssignmentModeNotEnabled() {
|
||||||
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
|
||||||
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3) })
|
|
||||||
void weightNotAllowedWhenMultiAssignmentModeNotEnabled() {
|
|
||||||
final String targetId = testdataFactory.createTarget().getControllerId();
|
final String targetId = testdataFactory.createTarget().getControllerId();
|
||||||
final Long dsId = testdataFactory.createDistributionSet().getId();
|
final Long dsId = testdataFactory.createDistributionSet().getId();
|
||||||
|
|
||||||
final DeploymentRequest assignWithoutWeight = DeploymentManagement.deploymentRequest(targetId, dsId)
|
final DeploymentRequest assignWithoutWeight = DeploymentManagement.deploymentRequest(targetId, dsId)
|
||||||
.setWeight(456).build();
|
.setWeight(456).build();
|
||||||
|
|
||||||
Assertions.assertThatExceptionOfType(MultiAssignmentIsNotEnabledException.class).isThrownBy(
|
deploymentManagement.assignDistributionSets(Collections.singletonList(assignWithoutWeight));
|
||||||
() -> deploymentManagement.assignDistributionSets(Collections.singletonList(assignWithoutWeight)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -0,0 +1,205 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2023 Contributors to the Eclipse Foundation
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made
|
||||||
|
* available under the terms of the Eclipse Public License 2.0
|
||||||
|
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.eclipse.hawkbit.repository.jpa.management;
|
||||||
|
|
||||||
|
import io.qameta.allure.Description;
|
||||||
|
import io.qameta.allure.Feature;
|
||||||
|
import io.qameta.allure.Story;
|
||||||
|
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
|
||||||
|
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.RolloutGroupStatus;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
|
import org.springframework.data.domain.Sort.Direction;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Junit tests for RolloutManagement.
|
||||||
|
*/
|
||||||
|
@Feature("Component Tests - Repository")
|
||||||
|
@Story("Rollout Management (Flow)")
|
||||||
|
class RolloutManagementFlowTest extends AbstractJpaIntegrationTest {
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void reset() {
|
||||||
|
this.approvalStrategy.setApprovalNeeded(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Description("Verifies a simple rollout flow")
|
||||||
|
void rolloutFlow() {
|
||||||
|
final String rolloutName = "rollout-std";
|
||||||
|
final int amountGroups = 5; // static only
|
||||||
|
final String targetPrefix = "controller-rollout-std-";
|
||||||
|
final DistributionSet distributionSet = testdataFactory.createDistributionSet("dsFor" + rolloutName);
|
||||||
|
|
||||||
|
testdataFactory.createTargets(targetPrefix, 0, amountGroups * 3);
|
||||||
|
final Rollout rollout = testdataFactory.createRolloutByVariables(rolloutName, rolloutName, amountGroups,
|
||||||
|
"controllerid==" + targetPrefix + "*", distributionSet, "60", "30", false, false);
|
||||||
|
final List<RolloutGroup> groups = rolloutGroupManagement.findByRollout(
|
||||||
|
new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id")),
|
||||||
|
rollout.getId()).getContent();
|
||||||
|
|
||||||
|
// add 2 targets not to be included
|
||||||
|
testdataFactory.createTargets(targetPrefix, amountGroups * 3, 2);
|
||||||
|
// start rollout
|
||||||
|
rolloutManagement.start(rollout.getId());
|
||||||
|
|
||||||
|
// handleStartingRollout (no handleRunning called yet)
|
||||||
|
rolloutHandler.handleAll();
|
||||||
|
assertRollout(rollout, false, RolloutStatus.RUNNING, amountGroups, amountGroups * 3);
|
||||||
|
for (int i = 0; i < amountGroups; i++) {
|
||||||
|
assertGroup(groups.get(i), false, i == 0 ? RolloutGroupStatus.RUNNING : RolloutGroupStatus.SCHEDULED, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute groups (without on of the last)
|
||||||
|
assertThat(refresh(groups.get(0)).getStatus()).isEqualTo(RolloutGroupStatus.RUNNING);
|
||||||
|
for (int i = 0; i < amountGroups; i++) {
|
||||||
|
if (i + 1 != amountGroups) {
|
||||||
|
assertThat(refresh(groups.get(i + 1)).getStatus()).isEqualTo(RolloutGroupStatus.SCHEDULED);
|
||||||
|
}
|
||||||
|
assertAndGetRunning(rollout, 3)
|
||||||
|
.stream()
|
||||||
|
.filter(action -> !(targetPrefix + (amountGroups * 3 - 1)).equals(action.getTarget().getControllerId()))
|
||||||
|
.forEach(this::finishAction);
|
||||||
|
rolloutHandler.handleAll();
|
||||||
|
assertThat(refresh(groups.get(i)).getStatus()).isEqualTo(i + 1 == amountGroups ? RolloutGroupStatus.RUNNING : RolloutGroupStatus.FINISHED);
|
||||||
|
if (i + 1 != amountGroups) {
|
||||||
|
assertThat(refresh(groups.get(i + 1)).getStatus()).isEqualTo(RolloutGroupStatus.RUNNING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rolloutManagement.pauseRollout(rollout.getId());
|
||||||
|
rolloutHandler.handleAll();
|
||||||
|
assertRollout(rollout, false, RolloutStatus.PAUSED, amountGroups, amountGroups * 3);
|
||||||
|
assertAndGetRunning(rollout, 1); // keep running
|
||||||
|
|
||||||
|
rolloutManagement.resumeRollout(rollout.getId());
|
||||||
|
rolloutHandler.handleAll();
|
||||||
|
assertRollout(rollout, false, RolloutStatus.RUNNING, amountGroups, amountGroups * 3);
|
||||||
|
assertAndGetRunning(rollout, 1); // keep running
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Description("Verifies a simple dynamic rollout flow")
|
||||||
|
void dynamicRolloutFlow() {
|
||||||
|
final String rolloutName = "dynamic-rollout-std";
|
||||||
|
final int amountGroups = 5; // static only
|
||||||
|
final String targetPrefix = "controller-dynamic-rollout-std-";
|
||||||
|
final DistributionSet distributionSet = testdataFactory.createDistributionSet("dsFor" + rolloutName);
|
||||||
|
|
||||||
|
testdataFactory.createTargets(targetPrefix, 0, amountGroups * 3);
|
||||||
|
final Rollout rollout = testdataFactory.createRolloutByVariables(rolloutName, rolloutName, amountGroups,
|
||||||
|
"controllerid==" + targetPrefix + "*", distributionSet, "60", "30", false, true);
|
||||||
|
|
||||||
|
// rollout is READY
|
||||||
|
assertRollout(rollout, true, RolloutStatus.READY, amountGroups + 1, amountGroups * 3);
|
||||||
|
List<RolloutGroup> groups = rolloutGroupManagement.findByRollout(
|
||||||
|
new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id")),
|
||||||
|
rollout.getId()).getContent();
|
||||||
|
final RolloutGroup dynamic1 = groups.get(amountGroups);
|
||||||
|
assertRollout(rollout, true, RolloutStatus.READY, amountGroups + 1, amountGroups * 3); // + dynamic
|
||||||
|
for (int i = 0; i < amountGroups; i++) {
|
||||||
|
assertGroup(groups.get(i), false, RolloutGroupStatus.READY, 3);
|
||||||
|
}
|
||||||
|
assertGroup(dynamic1, true, RolloutGroupStatus.READY, 0);
|
||||||
|
|
||||||
|
// add 2 targets for the first dynamic group
|
||||||
|
testdataFactory.createTargets(targetPrefix, amountGroups * 3, 2);
|
||||||
|
// start rollout
|
||||||
|
rolloutManagement.start(rollout.getId());
|
||||||
|
|
||||||
|
// handleStartingRollout (no handleRunning called yet)
|
||||||
|
rolloutHandler.handleAll();
|
||||||
|
assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 1, amountGroups * 3);
|
||||||
|
for (int i = 0; i < amountGroups; i++) {
|
||||||
|
assertGroup(groups.get(i), false, i == 0 ? RolloutGroupStatus.RUNNING : RolloutGroupStatus.SCHEDULED, 3);
|
||||||
|
}
|
||||||
|
assertGroup(dynamic1, true, RolloutGroupStatus.SCHEDULED, 0);
|
||||||
|
|
||||||
|
// execute statics (without on of the last) which start dynamic
|
||||||
|
assertThat(refresh(groups.get(0)).getStatus()).isEqualTo(RolloutGroupStatus.RUNNING);
|
||||||
|
for (int i = 0; i < amountGroups; i++) {
|
||||||
|
assertThat(refresh(groups.get(i + 1)).getStatus()).isEqualTo(RolloutGroupStatus.SCHEDULED);
|
||||||
|
assertAndGetRunning(rollout, 3)
|
||||||
|
.stream()
|
||||||
|
.filter(action -> !(targetPrefix + (amountGroups * 3 - 1)).equals(action.getTarget().getControllerId()))
|
||||||
|
.forEach(this::finishAction);
|
||||||
|
rolloutHandler.handleAll();
|
||||||
|
assertThat(refresh(groups.get(i)).getStatus()).isEqualTo(i + 1 == amountGroups ? RolloutGroupStatus.RUNNING : RolloutGroupStatus.FINISHED);
|
||||||
|
assertThat(refresh(i + 1 == amountGroups ? dynamic1 : groups.get(i + 1)).getStatus()).isEqualTo(RolloutGroupStatus.RUNNING); // on last round check dynamic
|
||||||
|
}
|
||||||
|
|
||||||
|
// partially fill the first dynamic (it is running and now create actions for 2 targets)
|
||||||
|
rolloutHandler.handleAll();
|
||||||
|
assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 1, amountGroups * 3 + 2);
|
||||||
|
assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 2);
|
||||||
|
|
||||||
|
// fill first and create second
|
||||||
|
testdataFactory.createTargets(targetPrefix, amountGroups * 3 + 2, 2);
|
||||||
|
rolloutHandler.handleAll(); // fill first dynamic group and create a new dynamic2
|
||||||
|
assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 2, amountGroups * 3 + 3);
|
||||||
|
assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 3);
|
||||||
|
groups = rolloutGroupManagement.findByRollout(
|
||||||
|
new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id")),
|
||||||
|
rollout.getId()).getContent();
|
||||||
|
final RolloutGroup dynamic2 = groups.get(amountGroups + 1);
|
||||||
|
assertGroup(dynamic2, true, RolloutGroupStatus.SCHEDULED, 0);
|
||||||
|
|
||||||
|
// create scheduled actions for the dynamic2
|
||||||
|
rolloutHandler.handleAll();
|
||||||
|
assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 2, amountGroups * 3 + 3);
|
||||||
|
assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 3);
|
||||||
|
assertGroup(dynamic2, true, RolloutGroupStatus.SCHEDULED, 0);
|
||||||
|
assertAndGetRunning(rollout, 4); // one from the last static group and 3 from the first dynamic
|
||||||
|
assertScheduled(rollout, 0);
|
||||||
|
|
||||||
|
// executes last from static and dynamic1 without 1 target
|
||||||
|
assertAndGetRunning(rollout, 4)// one from the last static and 3 for the first dynamic
|
||||||
|
.stream()
|
||||||
|
// remove the last assigned to dynamic1 - it could be amountGroups * 3 + 2 or bigger by id
|
||||||
|
.filter(action -> Integer.parseInt(action.getTarget().getControllerId().substring(targetPrefix.length())) < amountGroups * 3 + 2)
|
||||||
|
.forEach(this::finishAction);
|
||||||
|
assertAndGetRunning(rollout, 1); // remains on in the first dynamic
|
||||||
|
|
||||||
|
|
||||||
|
rolloutHandler.handleAll();
|
||||||
|
assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 2, amountGroups * 3 + 3);
|
||||||
|
assertGroup(groups.get(amountGroups - 1), false, RolloutGroupStatus.FINISHED, 3);
|
||||||
|
assertGroup(dynamic1, true, RolloutGroupStatus.RUNNING, 3);
|
||||||
|
assertGroup(dynamic2, true, RolloutGroupStatus.RUNNING, 0);
|
||||||
|
|
||||||
|
rolloutHandler.handleAll(); // add 1 action to now running second dynamic
|
||||||
|
assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 2, amountGroups * 3 + 4);
|
||||||
|
assertAndGetRunning(rollout, 2);
|
||||||
|
assertGroup(dynamic2, true, RolloutGroupStatus.RUNNING, 1);
|
||||||
|
|
||||||
|
testdataFactory.createTargets(targetPrefix, amountGroups * 3 + 4, 1);
|
||||||
|
rolloutManagement.pauseRollout(rollout.getId());
|
||||||
|
rolloutHandler.handleAll();
|
||||||
|
assertRollout(rollout, true, RolloutStatus.PAUSED, amountGroups + 2, amountGroups * 3 + 4);
|
||||||
|
assertAndGetRunning(rollout, 2);
|
||||||
|
assertGroup(dynamic2, true, RolloutGroupStatus.RUNNING, 1); // no new assignment
|
||||||
|
|
||||||
|
rolloutManagement.resumeRollout(rollout.getId());
|
||||||
|
rolloutHandler.handleAll();
|
||||||
|
assertRollout(rollout, true, RolloutStatus.RUNNING, amountGroups + 2, amountGroups * 3 + 5);
|
||||||
|
assertAndGetRunning(rollout, 3);
|
||||||
|
assertGroup(dynamic2, true, RolloutGroupStatus.RUNNING, 2); // assign the target created when paused
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -440,11 +440,6 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void finishAction(final Action action) {
|
|
||||||
controllerManagement
|
|
||||||
.addUpdateActionStatus(entityFactory.actionStatus().create(action.getId()).status(Status.FINISHED));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Description("Verifying that the error handling action of a group is executed to pause the current rollout")
|
@Description("Verifying that the error handling action of a group is executed to pause the current rollout")
|
||||||
void checkErrorHitOfGroupCallsErrorActionToPauseTheRollout() {
|
void checkErrorHitOfGroupCallsErrorActionToPauseTheRollout() {
|
||||||
@@ -1432,7 +1427,7 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
final RolloutGroupCreate group1 = entityFactory.rolloutGroup().create().conditions(conditions).name("group1")
|
final RolloutGroupCreate group1 = entityFactory.rolloutGroup().create().conditions(conditions).name("group1")
|
||||||
.targetPercentage(50.0F);
|
.targetPercentage(50.0F);
|
||||||
final RolloutGroupCreate group2 = entityFactory.rolloutGroup().create().conditions(conditions).name("group2")
|
final RolloutGroupCreate group2 = entityFactory.rolloutGroup().create().conditions(conditions).name("group2")
|
||||||
.targetPercentage(100.0F);
|
.targetPercentage(50.0F);
|
||||||
|
|
||||||
// group1 exceeds the quota
|
// group1 exceeds the quota
|
||||||
assertThatExceptionOfType(AssignmentQuotaExceededException.class).isThrownBy(() -> rolloutManagement.create(
|
assertThatExceptionOfType(AssignmentQuotaExceededException.class).isThrownBy(() -> rolloutManagement.create(
|
||||||
@@ -1456,9 +1451,9 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
final RolloutGroupCreate group5 = entityFactory.rolloutGroup().create().conditions(conditions).name("group5")
|
final RolloutGroupCreate group5 = entityFactory.rolloutGroup().create().conditions(conditions).name("group5")
|
||||||
.targetPercentage(33.3F);
|
.targetPercentage(33.3F);
|
||||||
final RolloutGroupCreate group6 = entityFactory.rolloutGroup().create().conditions(conditions).name("group6")
|
final RolloutGroupCreate group6 = entityFactory.rolloutGroup().create().conditions(conditions).name("group6")
|
||||||
.targetPercentage(66.6F);
|
.targetPercentage(33.3F);
|
||||||
final RolloutGroupCreate group7 = entityFactory.rolloutGroup().create().conditions(conditions).name("group7")
|
final RolloutGroupCreate group7 = entityFactory.rolloutGroup().create().conditions(conditions).name("group7")
|
||||||
.targetPercentage(100.0F);
|
.targetPercentage(33.3F);
|
||||||
|
|
||||||
// should work fine
|
// should work fine
|
||||||
assertThat(rolloutManagement.create(
|
assertThat(rolloutManagement.create(
|
||||||
@@ -1531,17 +1526,17 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
final int amountTargetsInGroup1 = 10;
|
final int amountTargetsInGroup1 = 10;
|
||||||
final int percentTargetsInGroup1 = 100;
|
final int percentTargetsInGroup1 = 100;
|
||||||
|
|
||||||
final int amountTargetsInGroup1and2 = 20;
|
final int amountTargetsInGroup2and3 = 20;
|
||||||
final int percentTargetsInGroup2 = 20;
|
final int percentTargetsInGroup2 = 20;
|
||||||
final int percentTargetsInGroup3 = 100;
|
final int percentTargetsInGroup3 = 80;
|
||||||
|
|
||||||
final int countTargetsInGroup2 = (int) Math
|
final int countTargetsInGroup2 = (int) Math
|
||||||
.ceil((double) percentTargetsInGroup2 / 100 * amountTargetsInGroup1and2);
|
.ceil((double) percentTargetsInGroup2 / 100 * amountTargetsInGroup2and3);
|
||||||
final int countTargetsInGroup3 = amountTargetsInGroup1and2 - countTargetsInGroup2;
|
final int countTargetsInGroup3 = amountTargetsInGroup2and3 - countTargetsInGroup2;
|
||||||
|
|
||||||
final RolloutGroupConditions conditions = new RolloutGroupConditionBuilder().withDefaults().build();
|
final RolloutGroupConditions conditions = new RolloutGroupConditionBuilder().withDefaults().build();
|
||||||
// Generate Targets for group 2 and 3 and generate the Rollout
|
// Generate Targets for group 2 and 3 and generate the Rollout
|
||||||
final RolloutCreate rolloutcreate = generateTargetsAndRollout(rolloutName, amountTargetsInGroup1and2);
|
final RolloutCreate rolloutcreate = generateTargetsAndRollout(rolloutName, amountTargetsInGroup2and3);
|
||||||
|
|
||||||
// Generate Targets for group 1
|
// Generate Targets for group 1
|
||||||
testdataFactory.createTargets(amountTargetsInGroup1, rolloutName + "-gr1-", rolloutName);
|
testdataFactory.createTargets(amountTargetsInGroup1, rolloutName + "-gr1-", rolloutName);
|
||||||
@@ -1568,7 +1563,7 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
|
|
||||||
myRollout = getRollout(myRollout.getId());
|
myRollout = getRollout(myRollout.getId());
|
||||||
assertThat(myRollout.getStatus()).isEqualTo(RolloutStatus.READY);
|
assertThat(myRollout.getStatus()).isEqualTo(RolloutStatus.READY);
|
||||||
assertThat(myRollout.getTotalTargets()).isEqualTo(amountTargetsInGroup1and2 + amountTargetsInGroup1);
|
assertThat(myRollout.getTotalTargets()).isEqualTo(amountTargetsInGroup2and3 + amountTargetsInGroup1);
|
||||||
|
|
||||||
final List<RolloutGroup> groups = rolloutGroupManagement.findByRollout(PAGE, myRollout.getId()).getContent();
|
final List<RolloutGroup> groups = rolloutGroupManagement.findByRollout(PAGE, myRollout.getId()).getContent();
|
||||||
|
|
||||||
@@ -1979,11 +1974,10 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Description("Creating a rollout with a weight causes an error when multi assignment in disabled.")
|
@Description("Creating a rollout with a weight causes an error when multi assignment in disabled.")
|
||||||
void weightNotAllowedWhenMultiAssignmentModeNotEnabled() {
|
void weightAllowedWhenMultiAssignmentModeNotEnabled() {
|
||||||
Assertions.assertThatExceptionOfType(MultiAssignmentIsNotEnabledException.class)
|
testdataFactory.createSimpleTestRolloutWithTargetsAndDistributionSet(10, 10, 2, "50",
|
||||||
.isThrownBy(() -> testdataFactory.createSimpleTestRolloutWithTargetsAndDistributionSet(10, 10, 2, "50",
|
|
||||||
"80",
|
"80",
|
||||||
ActionType.FORCED, 66));
|
ActionType.FORCED, 66);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -2028,7 +2022,7 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Description("Rollout can be created without weight in single assignment and be started in multi assignment")
|
@Description("Rollout can be created without weight in single assignment and be started in multi assignment")
|
||||||
void createInSingleStartInMultiassigMode() {
|
void createInSingleStartInMultiassignMode() {
|
||||||
final int amountOfTargets = 5;
|
final int amountOfTargets = 5;
|
||||||
final Long rolloutId = testdataFactory.createSimpleTestRolloutWithTargetsAndDistributionSet(amountOfTargets, 2,
|
final Long rolloutId = testdataFactory.createSimpleTestRolloutWithTargetsAndDistributionSet(amountOfTargets, 2,
|
||||||
amountOfTargets,
|
amountOfTargets,
|
||||||
@@ -2038,7 +2032,8 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
rolloutManagement.start(rolloutId);
|
rolloutManagement.start(rolloutId);
|
||||||
rolloutHandler.handleAll();
|
rolloutHandler.handleAll();
|
||||||
final List<Action> actions = deploymentManagement.findActionsAll(PAGE).getContent();
|
final List<Action> actions = deploymentManagement.findActionsAll(PAGE).getContent();
|
||||||
assertThat(actions).hasSize(amountOfTargets).allMatch(action -> !action.getWeight().isPresent());
|
// wight replaced with default
|
||||||
|
assertThat(actions).hasSize(5).allMatch(action -> action.getWeight().isPresent());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -2319,4 +2314,66 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
.isThrownBy(() -> rolloutManagement.triggerNextGroup(createdRollout.getId()))
|
.isThrownBy(() -> rolloutManagement.triggerNextGroup(createdRollout.getId()))
|
||||||
.withMessageContaining(errorMessage);
|
.withMessageContaining(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests static assignment aspects of the dynamic group assignment filters.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@Description("Dynamic group doesn't override newer static group assignments")
|
||||||
|
public void dynamicGroupDoesntOverrideItsOrNewerStaticGroups() {
|
||||||
|
final int amountGroups = 1; // static only
|
||||||
|
final String targetPrefix = "controller-dynamic-rollout-";
|
||||||
|
final DistributionSet distributionSet = testdataFactory.createDistributionSet("ds");
|
||||||
|
|
||||||
|
testdataFactory.createTargets(targetPrefix, 0, amountGroups * 2);
|
||||||
|
final Rollout dynamicRollout = testdataFactory.createRolloutByVariables("dynamic", "static rollout", amountGroups,
|
||||||
|
"controllerid==" + targetPrefix + "*", distributionSet, "0", "30", ActionType.FORCED, 1000, false, true);
|
||||||
|
rolloutManagement.start(dynamicRollout.getId());
|
||||||
|
rolloutHandler.handleAll();
|
||||||
|
assertRollout(dynamicRollout, true, RolloutStatus.RUNNING, amountGroups + 1, amountGroups * 2);
|
||||||
|
final List<RolloutGroup> dynamicGroups = rolloutGroupManagement.findByRollout(
|
||||||
|
new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id")),
|
||||||
|
dynamicRollout.getId()).getContent();
|
||||||
|
for (int i = 0; i < dynamicGroups.size(); i++) {
|
||||||
|
final RolloutGroup group = dynamicGroups.get(i);
|
||||||
|
if (i + 1 == dynamicGroups.size()) {
|
||||||
|
assertGroup(group, true, RolloutGroupStatus.SCHEDULED, 0);
|
||||||
|
} else {
|
||||||
|
assertGroup(group, false, RolloutGroupStatus.RUNNING, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertAndGetRunning(dynamicRollout, 2).forEach(this::finishAction);
|
||||||
|
rolloutHandler.handleAll();
|
||||||
|
for (int i = 0; i < dynamicGroups.size(); i++) {
|
||||||
|
final RolloutGroup group = dynamicGroups.get(i);
|
||||||
|
if (i + 1 == dynamicGroups.size()) {
|
||||||
|
assertGroup(group, true, RolloutGroupStatus.RUNNING, 0);
|
||||||
|
} else {
|
||||||
|
assertGroup(group, false, RolloutGroupStatus.FINISHED, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertAndGetRunning(dynamicRollout, 0);
|
||||||
|
rolloutHandler.handleAll();
|
||||||
|
// NB: asserts that dynamic group doesn't get from its static groups (already finished action targets)
|
||||||
|
assertGroup(dynamicGroups.get(dynamicGroups.size() - 1), true, RolloutGroupStatus.RUNNING, 0);
|
||||||
|
assertAndGetRunning(dynamicRollout, 0);
|
||||||
|
rolloutManagement.pauseRollout(dynamicRollout.getId());
|
||||||
|
rolloutHandler.handleAll();
|
||||||
|
|
||||||
|
testdataFactory.createTargets(targetPrefix, amountGroups * 2, amountGroups);
|
||||||
|
final Rollout staticRollout = testdataFactory.createRolloutByVariables("static", "static rollout", amountGroups,
|
||||||
|
"controllerid==" + targetPrefix + "*", distributionSet, "0", "30", ActionType.FORCED, 0, false, false);
|
||||||
|
rolloutManagement.start(staticRollout.getId());
|
||||||
|
rolloutHandler.handleAll();
|
||||||
|
assertRollout(staticRollout, false, RolloutStatus.RUNNING, amountGroups, amountGroups * 3);
|
||||||
|
final List<RolloutGroup> staticGroups = rolloutGroupManagement.findByRollout(
|
||||||
|
new OffsetBasedPageRequest(0, amountGroups + 10, Sort.by(Direction.ASC, "id")),
|
||||||
|
staticRollout.getId()).getContent();
|
||||||
|
staticGroups.forEach(group -> assertGroup(group, false, RolloutGroupStatus.RUNNING, 3));
|
||||||
|
|
||||||
|
rolloutManagement.resumeRollout(dynamicRollout.getId());
|
||||||
|
rolloutHandler.handleAll(); // resume, do not get last devices (they are assigned to a newer group, nevertheless newer is with bigger weight
|
||||||
|
assertGroup(dynamicGroups.get(dynamicGroups.size() - 1), true, RolloutGroupStatus.RUNNING, 0);
|
||||||
|
assertAndGetRunning(dynamicRollout, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -463,17 +463,15 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Description("Creating or updating a target filter query with autoassignment with a weight causes an error when multi assignment in disabled.")
|
@Description("Creating or updating a target filter query with autoassignment with a weight causes an error when multi assignment in disabled.")
|
||||||
public void weightNotAllowedWhenMultiAssignmentModeNotEnabled() {
|
public void weightAllowedWhenMultiAssignmentModeNotEnabled() {
|
||||||
final DistributionSet ds = testdataFactory.createDistributionSet();
|
final DistributionSet ds = testdataFactory.createDistributionSet();
|
||||||
final Long filterId = targetFilterQueryManagement
|
final Long filterId = targetFilterQueryManagement
|
||||||
.create(entityFactory.targetFilterQuery().create().name("a").query("name==*")).getId();
|
.create(entityFactory.targetFilterQuery().create().name("a").query("name==*")).getId();
|
||||||
|
|
||||||
Assertions.assertThatExceptionOfType(MultiAssignmentIsNotEnabledException.class)
|
targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create()
|
||||||
.isThrownBy(() -> targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create()
|
.name("b").query("name==*").autoAssignDistributionSet(ds).autoAssignWeight(342));
|
||||||
.name("b").query("name==*").autoAssignDistributionSet(ds).autoAssignWeight(342)));
|
targetFilterQueryManagement.updateAutoAssignDS(
|
||||||
Assertions.assertThatExceptionOfType(MultiAssignmentIsNotEnabledException.class)
|
entityFactory.targetFilterQuery().updateAutoAssign(filterId).ds(ds.getId()).weight(343));
|
||||||
.isThrownBy(() -> targetFilterQueryManagement.updateAutoAssignDS(
|
|
||||||
entityFactory.targetFilterQuery().updateAutoAssign(filterId).ds(ds.getId()).weight(343)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -53,13 +53,17 @@ import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
|
|||||||
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
|
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
|
||||||
import org.eclipse.hawkbit.repository.exception.TenantNotExistException;
|
import org.eclipse.hawkbit.repository.exception.TenantNotExistException;
|
||||||
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
|
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.model.JpaAction_;
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
|
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetMetadata;
|
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetMetadata;
|
||||||
|
import org.eclipse.hawkbit.repository.model.Action;
|
||||||
import org.eclipse.hawkbit.repository.model.Action.Status;
|
import org.eclipse.hawkbit.repository.model.Action.Status;
|
||||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||||
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
|
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
|
||||||
import org.eclipse.hawkbit.repository.model.MetaData;
|
import org.eclipse.hawkbit.repository.model.MetaData;
|
||||||
import org.eclipse.hawkbit.repository.model.NamedEntity;
|
import org.eclipse.hawkbit.repository.model.NamedEntity;
|
||||||
|
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||||
import org.eclipse.hawkbit.repository.model.Tag;
|
import org.eclipse.hawkbit.repository.model.Tag;
|
||||||
import org.eclipse.hawkbit.repository.model.Target;
|
import org.eclipse.hawkbit.repository.model.Target;
|
||||||
import org.eclipse.hawkbit.repository.model.TargetMetadata;
|
import org.eclipse.hawkbit.repository.model.TargetMetadata;
|
||||||
@@ -1328,6 +1332,65 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
"name==*")).isFalse();
|
"name==*")).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests action based aspects of the dynamic group assignment filters.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@Description("Target matches filter no active action with ge weight.")
|
||||||
|
void findByNotInGEGroupAndNotInActiveActionGEWeightOrInRolloutAndTargetFilterQueryAndCompatibleAndUpdatable() {
|
||||||
|
final String targetPrefix = "dyn_action_filter_";
|
||||||
|
final DistributionSet distributionSet = testdataFactory.createDistributionSet();
|
||||||
|
final List<Target> targets = testdataFactory.createTargets(targetPrefix, 9);
|
||||||
|
final Rollout rolloutOlder = testdataFactory.createRollout();
|
||||||
|
final Rollout rollout = testdataFactory.createRollout();
|
||||||
|
final Rollout rolloutNewer = testdataFactory.createRollout();
|
||||||
|
|
||||||
|
// old ro with less weight - match
|
||||||
|
createAction(targets.get(0), rolloutOlder, 0, Status.RUNNING, distributionSet);
|
||||||
|
// old ro with less weight - match
|
||||||
|
createAction(targets.get(1), rolloutOlder, 5, Status.SCHEDULED, distributionSet);
|
||||||
|
// old ro with equal weight - match
|
||||||
|
createAction(targets.get(2), rolloutOlder, 10, Status.RUNNING, distributionSet);
|
||||||
|
// old ro with BIGGER weight - doesn't match
|
||||||
|
createAction(targets.get(3), rolloutOlder, 20, Status.WAIT_FOR_CONFIRMATION, distributionSet);
|
||||||
|
// same ro - doesn't match
|
||||||
|
createAction(targets.get(4), rollout, 10, Status.RUNNING, distributionSet);
|
||||||
|
// new ro with less weight - match
|
||||||
|
createAction(targets.get(5), rolloutNewer, 0, Status.RUNNING, distributionSet);
|
||||||
|
// new ro with less weight - match
|
||||||
|
createAction(targets.get(6), rolloutNewer, 5, Status.WARNING, distributionSet);
|
||||||
|
// NEW ro with EQUAL weight - doesn't match
|
||||||
|
createAction(targets.get(7), rolloutNewer, 10, Status.RUNNING, distributionSet);
|
||||||
|
// new ro with BIGGER weight - doesn't match
|
||||||
|
createAction(targets.get(8), rolloutNewer, 20, Status.DOWNLOADED, distributionSet);
|
||||||
|
|
||||||
|
final Slice<Target> matching = targetManagement.findByNotInGEGroupAndNotInActiveActionGEWeightOrInRolloutAndTargetFilterQueryAndCompatibleAndUpdatable(
|
||||||
|
PAGE, rollout.getId(), 10, Long.MAX_VALUE,"controllerid==dyn_action_filter_*", distributionSet.getType());
|
||||||
|
|
||||||
|
assertThat(matching.getNumberOfElements()).isEqualTo(5);
|
||||||
|
assertThat(matching.stream()
|
||||||
|
.map(Target::getControllerId)
|
||||||
|
.map(s -> s.substring(targetPrefix.length()))
|
||||||
|
.map(Integer::parseInt)
|
||||||
|
.sorted()
|
||||||
|
.toList()).isEqualTo(List.of(0, 1, 2, 5, 6));
|
||||||
|
}
|
||||||
|
private void createAction(final Target target, final Rollout rollout, final Integer weight, final Action.Status status, final DistributionSet distributionSet) {
|
||||||
|
final JpaAction action = new JpaAction();
|
||||||
|
action.setActionType(Action.ActionType.FORCED);
|
||||||
|
action.setTarget(target);
|
||||||
|
action.setInitiatedBy("test");
|
||||||
|
if (rollout != null) {
|
||||||
|
action.setRollout(rollout);
|
||||||
|
}
|
||||||
|
if (weight != null) {
|
||||||
|
action.setWeight(weight);
|
||||||
|
}
|
||||||
|
action.setStatus(status);
|
||||||
|
action.setDistributionSet(distributionSet);
|
||||||
|
actionRepository.save(action);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Description("Target matches filter for not existing DS.")
|
@Description("Target matches filter for not existing DS.")
|
||||||
void matchesFilterDsNotExists() {
|
void matchesFilterDsNotExists() {
|
||||||
|
|||||||
@@ -513,4 +513,15 @@ public abstract class AbstractIntegrationTest {
|
|||||||
.getConfigurationValue(TenantConfigurationKey.USER_CONFIRMATION_ENABLED, Boolean.class).getValue();
|
.getConfigurationValue(TenantConfigurationKey.USER_CONFIRMATION_ENABLED, Boolean.class).getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ensure that next action will get current time millis AFTER got from the previous
|
||||||
|
protected void waitNextMillis() {
|
||||||
|
final long createTime = System.currentTimeMillis();
|
||||||
|
while (System.currentTimeMillis() == createTime) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(1);
|
||||||
|
} catch (final InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -287,7 +287,7 @@ public class TestdataFactory {
|
|||||||
* for {@link SoftwareModule}s and {@link DistributionSet}s name,
|
* for {@link SoftwareModule}s and {@link DistributionSet}s name,
|
||||||
* vendor and description.
|
* vendor and description.
|
||||||
* @param tags
|
* @param tags
|
||||||
* {@link DistributionSet#getTags()}
|
* DistributionSet tags
|
||||||
*
|
*
|
||||||
* @return {@link DistributionSet} entity.
|
* @return {@link DistributionSet} entity.
|
||||||
*/
|
*/
|
||||||
@@ -397,7 +397,7 @@ public class TestdataFactory {
|
|||||||
* {@link SoftwareModule#getVersion()} extended by a random
|
* {@link SoftwareModule#getVersion()} extended by a random
|
||||||
* number.updat
|
* number.updat
|
||||||
* @param tags
|
* @param tags
|
||||||
* {@link DistributionSet#getTags()}
|
* DistributionSet tags
|
||||||
*
|
*
|
||||||
* @return {@link DistributionSet} entity.
|
* @return {@link DistributionSet} entity.
|
||||||
*/
|
*/
|
||||||
@@ -909,10 +909,13 @@ public class TestdataFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<Target> createTargets(final String prefix, final int number) {
|
public List<Target> createTargets(final String prefix, final int number) {
|
||||||
|
return createTargets(prefix, 0, number);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Target> createTargets(final String prefix, final int offset, final int number) {
|
||||||
final List<TargetCreate> targets = Lists.newArrayListWithExpectedSize(number);
|
final List<TargetCreate> targets = Lists.newArrayListWithExpectedSize(number);
|
||||||
for (int i = 0; i < number; i++) {
|
for (int i = 0; i < number; i++) {
|
||||||
targets.add(entityFactory.target().create().controllerId(prefix + i));
|
targets.add(entityFactory.target().create().controllerId(prefix + (offset + i)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return createTargets(targets);
|
return createTargets(targets);
|
||||||
@@ -1192,6 +1195,21 @@ public class TestdataFactory {
|
|||||||
successCondition, errorCondition, Action.ActionType.FORCED, null, confirmationRequired);
|
successCondition, errorCondition, Action.ActionType.FORCED, null, confirmationRequired);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Rollout createRolloutByVariables(final String rolloutName, final String rolloutDescription,
|
||||||
|
final int groupSize, final String filterQuery, final DistributionSet distributionSet,
|
||||||
|
final String successCondition, final String errorCondition, final boolean confirmationRequired,
|
||||||
|
final boolean dynamic) {
|
||||||
|
return createRolloutByVariables(rolloutName, rolloutDescription, groupSize, filterQuery, distributionSet,
|
||||||
|
successCondition, errorCondition, Action.ActionType.FORCED, null, confirmationRequired, dynamic);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rollout createRolloutByVariables(final String rolloutName, final String rolloutDescription,
|
||||||
|
final int groupSize, final String filterQuery, final DistributionSet distributionSet,
|
||||||
|
final String successCondition, final String errorCondition, final Action.ActionType actionType,
|
||||||
|
final Integer weight, final boolean confirmationRequired) {
|
||||||
|
return createRolloutByVariables(rolloutName, rolloutDescription, groupSize, filterQuery, distributionSet,
|
||||||
|
successCondition, errorCondition, actionType, weight, confirmationRequired, false);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Creates rollout based on given parameters.
|
* Creates rollout based on given parameters.
|
||||||
*
|
*
|
||||||
@@ -1216,12 +1234,13 @@ public class TestdataFactory {
|
|||||||
* @param confirmationRequired
|
* @param confirmationRequired
|
||||||
* if the confirmation is required (considered with confirmation flow
|
* if the confirmation is required (considered with confirmation flow
|
||||||
* active)
|
* active)
|
||||||
|
* @param dynamic is dynamic
|
||||||
* @return created {@link Rollout}
|
* @return created {@link Rollout}
|
||||||
*/
|
*/
|
||||||
public Rollout createRolloutByVariables(final String rolloutName, final String rolloutDescription,
|
public Rollout createRolloutByVariables(final String rolloutName, final String rolloutDescription,
|
||||||
final int groupSize, final String filterQuery, final DistributionSet distributionSet,
|
final int groupSize, final String filterQuery, final DistributionSet distributionSet,
|
||||||
final String successCondition, final String errorCondition, final Action.ActionType actionType,
|
final String successCondition, final String errorCondition, final Action.ActionType actionType,
|
||||||
final Integer weight, final boolean confirmationRequired) {
|
final Integer weight, final boolean confirmationRequired, final boolean dynamic) {
|
||||||
final RolloutGroupConditions conditions = new RolloutGroupConditionBuilder().withDefaults()
|
final RolloutGroupConditions conditions = new RolloutGroupConditionBuilder().withDefaults()
|
||||||
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, successCondition)
|
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, successCondition)
|
||||||
.errorCondition(RolloutGroupErrorCondition.THRESHOLD, errorCondition)
|
.errorCondition(RolloutGroupErrorCondition.THRESHOLD, errorCondition)
|
||||||
@@ -1229,7 +1248,8 @@ public class TestdataFactory {
|
|||||||
|
|
||||||
final Rollout rollout = rolloutManagement.create(
|
final Rollout rollout = rolloutManagement.create(
|
||||||
entityFactory.rollout().create().name(rolloutName).description(rolloutDescription)
|
entityFactory.rollout().create().name(rolloutName).description(rolloutDescription)
|
||||||
.targetFilterQuery(filterQuery).set(distributionSet).actionType(actionType).weight(weight),
|
.targetFilterQuery(filterQuery).set(distributionSet).actionType(actionType).weight(weight)
|
||||||
|
.dynamic(dynamic),
|
||||||
groupSize, confirmationRequired, conditions);
|
groupSize, confirmationRequired, conditions);
|
||||||
|
|
||||||
// Run here, because Scheduler is disabled during tests
|
// Run here, because Scheduler is disabled during tests
|
||||||
@@ -1300,7 +1320,7 @@ public class TestdataFactory {
|
|||||||
* the amount of targets used for the rollout
|
* the amount of targets used for the rollout
|
||||||
* @param amountOtherTargets
|
* @param amountOtherTargets
|
||||||
* amount of other targets not included in the rollout
|
* amount of other targets not included in the rollout
|
||||||
* @param groupSize
|
* @param amountGroups
|
||||||
* the size of the rollout group
|
* the size of the rollout group
|
||||||
* @param successCondition
|
* @param successCondition
|
||||||
* success condition
|
* success condition
|
||||||
|
|||||||
@@ -71,6 +71,10 @@ public class MgmtRolloutResponseBody extends MgmtNamedEntity {
|
|||||||
@Schema(example = "400")
|
@Schema(example = "400")
|
||||||
private Integer weight;
|
private Integer weight;
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
@Schema(example = "true")
|
||||||
|
private boolean dynamic;
|
||||||
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
@Schema(example = "Approved remark.")
|
@Schema(example = "Approved remark.")
|
||||||
private String approvalRemark;
|
private String approvalRemark;
|
||||||
@@ -171,6 +175,14 @@ public class MgmtRolloutResponseBody extends MgmtNamedEntity {
|
|||||||
return weight;
|
return weight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDynamic(final boolean dynamic) {
|
||||||
|
this.dynamic = dynamic;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDynamic() {
|
||||||
|
return dynamic;
|
||||||
|
}
|
||||||
|
|
||||||
public void setTotalGroups(final Integer totalGroups) {
|
public void setTotalGroups(final Integer totalGroups) {
|
||||||
this.totalGroups = totalGroups;
|
this.totalGroups = totalGroups;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ public class MgmtRolloutRestRequestBody extends AbstractMgmtRolloutConditionsEnt
|
|||||||
@Schema(example = "400")
|
@Schema(example = "400")
|
||||||
private Integer weight;
|
private Integer weight;
|
||||||
|
|
||||||
|
@JsonProperty(required = false)
|
||||||
|
@Schema(example = "true")
|
||||||
|
private boolean dynamic;
|
||||||
|
|
||||||
@JsonProperty(required = false)
|
@JsonProperty(required = false)
|
||||||
@Schema(example = "false")
|
@Schema(example = "false")
|
||||||
private Boolean confirmationRequired;
|
private Boolean confirmationRequired;
|
||||||
@@ -175,6 +179,21 @@ public class MgmtRolloutRestRequestBody extends AbstractMgmtRolloutConditionsEnt
|
|||||||
this.weight = weight;
|
this.weight = weight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return if the {@link Rollout} shall be dynamic
|
||||||
|
*/
|
||||||
|
public boolean isDynamic() {
|
||||||
|
return dynamic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param dynamic
|
||||||
|
* is the {@link Rollout} shall be dynamic
|
||||||
|
*/
|
||||||
|
public void setDynamic(final boolean dynamic) {
|
||||||
|
this.dynamic = dynamic;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Only considered if confirmation flow active
|
* Only considered if confirmation flow active
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ final class MgmtRolloutMapper {
|
|||||||
body.setLastModifiedBy(rollout.getLastModifiedBy());
|
body.setLastModifiedBy(rollout.getLastModifiedBy());
|
||||||
body.setName(rollout.getName());
|
body.setName(rollout.getName());
|
||||||
body.setRolloutId(rollout.getId());
|
body.setRolloutId(rollout.getId());
|
||||||
|
body.setDynamic(rollout.isDynamic());
|
||||||
body.setTargetFilterQuery(rollout.getTargetFilterQuery());
|
body.setTargetFilterQuery(rollout.getTargetFilterQuery());
|
||||||
body.setDistributionSetId(rollout.getDistributionSet().getId());
|
body.setDistributionSetId(rollout.getDistributionSet().getId());
|
||||||
body.setStatus(rollout.getStatus().toString().toLowerCase());
|
body.setStatus(rollout.getStatus().toString().toLowerCase());
|
||||||
@@ -126,6 +127,7 @@ final class MgmtRolloutMapper {
|
|||||||
final DistributionSet distributionSet) {
|
final DistributionSet distributionSet) {
|
||||||
|
|
||||||
return entityFactory.rollout().create().name(restRequest.getName()).description(restRequest.getDescription())
|
return entityFactory.rollout().create().name(restRequest.getName()).description(restRequest.getDescription())
|
||||||
|
.dynamic(restRequest.isDynamic())
|
||||||
.set(distributionSet).targetFilterQuery(restRequest.getTargetFilterQuery())
|
.set(distributionSet).targetFilterQuery(restRequest.getTargetFilterQuery())
|
||||||
.actionType(MgmtRestModelMapper.convertActionType(restRequest.getType()))
|
.actionType(MgmtRestModelMapper.convertActionType(restRequest.getType()))
|
||||||
.forcedTime(restRequest.getForcetime()).startAt(restRequest.getStartAt())
|
.forcedTime(restRequest.getForcetime()).startAt(restRequest.getStartAt())
|
||||||
|
|||||||
@@ -1516,8 +1516,7 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
|
|||||||
|
|
||||||
mvc.perform(post("/rest/v1/distributionsets/{ds}/assignedTargets", dsId).content(bodyValide.toString())
|
mvc.perform(post("/rest/v1/distributionsets/{ds}/assignedTargets", dsId).content(bodyValide.toString())
|
||||||
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
|
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
|
||||||
.andExpect(status().isBadRequest())
|
.andExpect(status().isOk());
|
||||||
.andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.multiassignmentNotEnabled")));
|
|
||||||
enableMultiAssignments();
|
enableMultiAssignments();
|
||||||
mvc.perform(post("/rest/v1/distributionsets/{ds}/assignedTargets", dsId).content(bodyInvalide.toString())
|
mvc.perform(post("/rest/v1/distributionsets/{ds}/assignedTargets", dsId).content(bodyInvalide.toString())
|
||||||
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
|
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
|
||||||
@@ -1528,7 +1527,7 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
|
|||||||
.andExpect(status().isOk());
|
.andExpect(status().isOk());
|
||||||
|
|
||||||
final List<Action> actions = deploymentManagement.findActionsAll(PAGE).get().collect(Collectors.toList());
|
final List<Action> actions = deploymentManagement.findActionsAll(PAGE).get().collect(Collectors.toList());
|
||||||
assertThat(actions).size().isEqualTo(1);
|
assertThat(actions).size().isEqualTo(2);
|
||||||
assertThat(actions.get(0).getWeight()).get().isEqualTo(weight);
|
assertThat(actions.get(0).getWeight()).get().isEqualTo(weight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1364,7 +1364,7 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Description("A rollout create request containing a weight is only accepted when weight is valid and multi assignment is on.")
|
@Description("A rollout create request containing a weight is always accepted when weight is valid.")
|
||||||
void weightValidation() throws Exception {
|
void weightValidation() throws Exception {
|
||||||
testdataFactory.createTargets(4, "rollout", "description");
|
testdataFactory.createTargets(4, "rollout", "description");
|
||||||
final Long dsId = testdataFactory.createDistributionSet().getId();
|
final Long dsId = testdataFactory.createDistributionSet().getId();
|
||||||
@@ -1375,22 +1375,23 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
|
|||||||
null, null);
|
null, null);
|
||||||
final String valideWeightRequest = JsonBuilder.rollout("withWeight", "d", 2, dsId, "id==rollout*",
|
final String valideWeightRequest = JsonBuilder.rollout("withWeight", "d", 2, dsId, "id==rollout*",
|
||||||
new RolloutGroupConditionBuilder().withDefaults().build(), null, null, weight, null, null, null);
|
new RolloutGroupConditionBuilder().withDefaults().build(), null, null, weight, null, null, null);
|
||||||
|
final String valideWeightRequestMultiAssignment = JsonBuilder.rollout("withWeightMultiAssignment", "d", 2, dsId, "id==rollout*",
|
||||||
|
new RolloutGroupConditionBuilder().withDefaults().build(), null, null, weight, null, null, null);
|
||||||
|
|
||||||
mvc.perform(post("/rest/v1/rollouts").content(valideWeightRequest).contentType(MediaType.APPLICATION_JSON)
|
mvc.perform(post("/rest/v1/rollouts").content(valideWeightRequest).contentType(MediaType.APPLICATION_JSON)
|
||||||
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
|
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
|
||||||
.andExpect(status().isBadRequest())
|
.andExpect(status().isCreated());
|
||||||
.andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.multiassignmentNotEnabled")));
|
|
||||||
enableMultiAssignments();
|
enableMultiAssignments();
|
||||||
mvc.perform(post("/rest/v1/rollouts").content(invalideWeightRequest).contentType(MediaType.APPLICATION_JSON)
|
mvc.perform(post("/rest/v1/rollouts").content(invalideWeightRequest).contentType(MediaType.APPLICATION_JSON)
|
||||||
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
|
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
|
||||||
.andExpect(status().isBadRequest())
|
.andExpect(status().isBadRequest())
|
||||||
.andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.repo.constraintViolation")));
|
.andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.repo.constraintViolation")));
|
||||||
mvc.perform(post("/rest/v1/rollouts").content(valideWeightRequest).contentType(MediaType.APPLICATION_JSON)
|
mvc.perform(post("/rest/v1/rollouts").content(valideWeightRequestMultiAssignment).contentType(MediaType.APPLICATION_JSON)
|
||||||
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
|
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
|
||||||
.andExpect(status().isCreated());
|
.andExpect(status().isCreated());
|
||||||
|
|
||||||
final List<Rollout> rollouts = rolloutManagement.findAll(PAGE, false).getContent();
|
final List<Rollout> rollouts = rolloutManagement.findAll(PAGE, false).getContent();
|
||||||
assertThat(rollouts).hasSize(1);
|
assertThat(rollouts).hasSize(2);
|
||||||
assertThat(rollouts.get(0).getWeight()).get().isEqualTo(weight);
|
assertThat(rollouts.get(0).getWeight()).get().isEqualTo(weight);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1542,7 +1543,7 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
|
|||||||
@Test
|
@Test
|
||||||
@Description("Trigger next rollout group if rollout is in wrong state")
|
@Description("Trigger next rollout group if rollout is in wrong state")
|
||||||
void triggeringNextGroupRolloutWrongState() throws Exception {
|
void triggeringNextGroupRolloutWrongState() throws Exception {
|
||||||
final int amountTargets = 2;
|
final int amountTargets = 3;
|
||||||
final List<Target> targets = testdataFactory.createTargets(amountTargets, "rollout");
|
final List<Target> targets = testdataFactory.createTargets(amountTargets, "rollout");
|
||||||
final DistributionSet dsA = testdataFactory.createDistributionSet("");
|
final DistributionSet dsA = testdataFactory.createDistributionSet("");
|
||||||
|
|
||||||
|
|||||||
@@ -720,8 +720,7 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInt
|
|||||||
|
|
||||||
mvc.perform(post(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/{targetFilterQueryId}/autoAssignDS",
|
mvc.perform(post(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/{targetFilterQueryId}/autoAssignDS",
|
||||||
filterId).content(valideWeightRequest).contentType(MediaType.APPLICATION_JSON)).andDo(print())
|
filterId).content(valideWeightRequest).contentType(MediaType.APPLICATION_JSON)).andDo(print())
|
||||||
.andExpect(status().isBadRequest())
|
.andExpect(status().isOk());
|
||||||
.andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.multiassignmentNotEnabled")));
|
|
||||||
enableMultiAssignments();
|
enableMultiAssignments();
|
||||||
mvc.perform(post(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/{targetFilterQueryId}/autoAssignDS",
|
mvc.perform(post(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/{targetFilterQueryId}/autoAssignDS",
|
||||||
filterId).content(invalideWeightRequest).contentType(MediaType.APPLICATION_JSON)).andDo(print())
|
filterId).content(invalideWeightRequest).contentType(MediaType.APPLICATION_JSON)).andDo(print())
|
||||||
|
|||||||
@@ -2167,8 +2167,7 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
|
|||||||
|
|
||||||
mvc.perform(post("/rest/v1/targets/{targetId}/assignedDS", targetId).content(bodyValid.toString())
|
mvc.perform(post("/rest/v1/targets/{targetId}/assignedDS", targetId).content(bodyValid.toString())
|
||||||
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
|
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
|
||||||
.andExpect(status().isBadRequest())
|
.andExpect(status().isOk());
|
||||||
.andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.multiassignmentNotEnabled")));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -2197,28 +2196,17 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
|
|||||||
final Long dsId = testdataFactory.createDistributionSet().getId();
|
final Long dsId = testdataFactory.createDistributionSet().getId();
|
||||||
final int customWeightHigh = 800;
|
final int customWeightHigh = 800;
|
||||||
final int customWeightLow = 300;
|
final int customWeightLow = 300;
|
||||||
assignDistributionSet(dsId, targetId);
|
assignDistributionSet(dsId, targetId); // default weight 1000
|
||||||
enableMultiAssignments();
|
enableMultiAssignments();
|
||||||
assignDistributionSet(dsId, targetId, customWeightHigh);
|
assignDistributionSet(dsId, targetId, customWeightHigh);
|
||||||
assignDistributionSet(dsId, targetId, customWeightLow);
|
assignDistributionSet(dsId, targetId, customWeightLow);
|
||||||
|
|
||||||
// POSTGRESQL sets null values at the end, not the beginning
|
mvc.perform(get("/rest/v1/targets/{targetId}/actions", targetId)
|
||||||
if (Database.POSTGRESQL.equals(jpaProperties.getDatabase())) {
|
.param(MgmtRestConstants.REQUEST_PARAMETER_SORTING, "WEIGHT:ASC"))
|
||||||
mvc.perform(get("/rest/v1/targets/{targetId}/actions", targetId)
|
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
|
||||||
.param(MgmtRestConstants.REQUEST_PARAMETER_SORTING, "WEIGHT:ASC"))
|
.andExpect(jsonPath("content.[0].weight", equalTo(customWeightLow)))
|
||||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
|
.andExpect(jsonPath("content.[1].weight", equalTo(customWeightHigh)))
|
||||||
.andExpect(jsonPath("content.[0].weight", equalTo(customWeightLow)))
|
.andExpect(jsonPath("content.[2].weight", equalTo(1000)));
|
||||||
.andExpect(jsonPath("content.[1].weight", equalTo(customWeightHigh)))
|
|
||||||
.andExpect(jsonPath("content.[2].weight").doesNotExist());
|
|
||||||
} else {
|
|
||||||
mvc.perform(get("/rest/v1/targets/{targetId}/actions", targetId)
|
|
||||||
.param(MgmtRestConstants.REQUEST_PARAMETER_SORTING, "WEIGHT:ASC"))
|
|
||||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
|
|
||||||
.andExpect(jsonPath("content.[0].weight").doesNotExist())
|
|
||||||
.andExpect(jsonPath("content.[1].weight", equalTo(customWeightLow)))
|
|
||||||
.andExpect(jsonPath("content.[2].weight", equalTo(customWeightHigh)));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
1703229421929:start
|
|
||||||
2
pom.xml
2
pom.xml
@@ -259,7 +259,7 @@
|
|||||||
<sonar.coverage.exclusions>
|
<sonar.coverage.exclusions>
|
||||||
**/src/main/java/org/eclipse/hawkbit/ui/**,**/target/generated-sources/apt/**,**/src/main/java/org/eclipse/hawkbit/repository/test/**,**/examples/**
|
**/src/main/java/org/eclipse/hawkbit/ui/**,**/target/generated-sources/apt/**,**/src/main/java/org/eclipse/hawkbit/repository/test/**,**/examples/**
|
||||||
</sonar.coverage.exclusions>
|
</sonar.coverage.exclusions>
|
||||||
<jacoco.version>0.8.8</jacoco.version>
|
<jacoco.version>0.8.11</jacoco.version>
|
||||||
<jacoco.outputDir>${project.build.directory}</jacoco.outputDir>
|
<jacoco.outputDir>${project.build.directory}</jacoco.outputDir>
|
||||||
<sonar.coverage.jacoco.xmlReportPaths>
|
<sonar.coverage.jacoco.xmlReportPaths>
|
||||||
${project.basedir}/../hawkbit-test-report/target/jacoco-aggregate/jacoco.xml,
|
${project.basedir}/../hawkbit-test-report/target/jacoco-aggregate/jacoco.xml,
|
||||||
|
|||||||
Reference in New Issue
Block a user