targetGroup = targetRepository.findByRolloutTargetGroupRolloutGroup(rolloutGroup);
+ // firstgroup can already be started
+ if (iGroup == 0) {
+ final List targetsWithActionType = targetGroup.stream()
+ .map(t -> new TargetWithActionType(t.getControllerId(), actionType, forceTime))
+ .collect(Collectors.toList());
+ deploymentManagement.assignDistributionSet(distributionSet.getId(), targetsWithActionType, rollout,
+ rolloutGroup);
+ rolloutGroup.setStatus(RolloutGroupStatus.RUNNING);
+ } else {
+ // create only not active actions with status scheduled so they
+ // can be activated later
+ deploymentManagement.createScheduledAction(targetGroup, distributionSet, actionType, forceTime, rollout,
+ rolloutGroup);
+ rolloutGroup.setStatus(RolloutGroupStatus.SCHEDULED);
+ }
+ rolloutGroupRepository.save(rolloutGroup);
+ }
+ rollout.setStatus(RolloutStatus.RUNNING);
+ return rolloutRepository.save(rollout);
+ }
+
+ /**
+ * Pauses a rollout which is currently running. The Rollout switches
+ * {@link RolloutStatus#PAUSED}. {@link RolloutGroup}s which are currently
+ * running will be untouched. {@link RolloutGroup}s which are
+ * {@link RolloutGroupStatus#SCHEDULED} will not be started and keep in
+ * {@link RolloutGroupStatus#SCHEDULED} state until the rollout is
+ * {@link RolloutManagement#resumeRollout(Rollout)}.
+ *
+ * Switching the rollout status to {@link RolloutStatus#PAUSED} is
+ * sufficient due the {@link #checkRunningRollouts(long)} will not check
+ * this rollout anymore.
+ *
+ * @param rollout
+ * the rollout to be paused.
+ *
+ * @throws RolloutIllegalStateException
+ * if given rollout is not in {@link RolloutStatus#RUNNING}.
+ * Only running rollouts can be paused.
+ */
+ @Transactional
+ @Modifying
+ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR
+ + SpringEvalExpressions.IS_SYSTEM_CODE)
+ public void pauseRollout(final Rollout rollout) {
+ final Rollout mergedRollout = entityManager.merge(rollout);
+ if (mergedRollout.getStatus() != RolloutStatus.RUNNING) {
+ throw new RolloutIllegalStateException("Rollout can only be paused in state running but current state is "
+ + rollout.getStatus().name().toLowerCase());
+ }
+ // setting the complete rollout only in paused state. This is sufficient
+ // due the currently running groups will be completed and new groups are
+ // not started until rollout goes back to running state again. The
+ // periodically check for running rollouts will skip rollouts in pause
+ // state.
+ mergedRollout.setStatus(RolloutStatus.PAUSED);
+ rolloutRepository.save(mergedRollout);
+ }
+
+ /**
+ * Resumes a paused rollout. The rollout switches back to
+ * {@link RolloutStatus#RUNNING} state which is then picked up again by the
+ * {@link #checkRunningRollouts(long)}.
+ *
+ * @param rollout
+ * the rollout to be resumed
+ * @throws RolloutIllegalStateException
+ * if given rollout is not in {@link RolloutStatus#PAUSED}. Only
+ * paused rollouts can be resumed.
+ */
+ @Transactional
+ @Modifying
+ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR
+ + SpringEvalExpressions.IS_SYSTEM_CODE)
+ public void resumeRollout(final Rollout rollout) {
+ final Rollout mergedRollout = entityManager.merge(rollout);
+ if (!(RolloutStatus.PAUSED.equals(mergedRollout.getStatus()))) {
+ throw new RolloutIllegalStateException("Rollout can only be resumed in state paused but current state is "
+ + rollout.getStatus().name().toLowerCase());
+ }
+ mergedRollout.setStatus(RolloutStatus.RUNNING);
+ rolloutRepository.save(mergedRollout);
+ }
+
+ /**
+ * Checking running rollouts. Rollouts which are checked updating the
+ * {@link Rollout#setLastCheck(long)} to indicate that the current instance
+ * is handling the specific rollout. This code should run as system-code.
+ *
+ *
+ * {@code
+ * SystemSecurityContext.runAsSystem(new Callable() {
+ * public Void call() throws Exception {
+ * //run system-code
+ * }
+ * });
+ * }
+ *
+ *
+ * This method is attend to be called by a scheduler.
+ * {@link RolloutScheduler}. And must be running in an transaction so it's
+ * splitted from the scheduler.
+ *
+ * Rollouts which are currently running are investigated, by means the
+ * error- and finish condition of running groups in this rollout are
+ * evaluated.
+ *
+ * @param delayBetweenChecks
+ * the time in milliseconds of the delay between the further and
+ * this check. This check is only applied if the last check is
+ * less than (lastcheck-delay).
+ */
+ @Transactional
+ @Modifying
+ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR
+ + SpringEvalExpressions.IS_SYSTEM_CODE)
+ public void checkRunningRollouts(final long delayBetweenChecks) {
+ verifyStuckedRollouts();
+ final long lastCheck = System.currentTimeMillis();
+ final int updated = rolloutRepository.updateLastCheck(lastCheck, delayBetweenChecks, RolloutStatus.RUNNING);
+
+ if (updated == 0) {
+ // nothing to check, maybe another instance already checked in
+ // between
+ LOGGER.info("No rolloutcheck necessary for current scheduled check {}, next check at {}", lastCheck,
+ lastCheck + delayBetweenChecks);
+ return;
+ }
+
+ final List rolloutsToCheck = rolloutRepository.findByLastCheckAndStatus(lastCheck,
+ RolloutStatus.RUNNING);
+ LOGGER.info("Found {} running rollouts to check", rolloutsToCheck.size());
+
+ for (final Rollout rollout : rolloutsToCheck) {
+ LOGGER.debug("Checking rollout {}", rollout);
+ final List rolloutGroups = rolloutGroupRepository.findByRolloutAndStatus(rollout,
+ RolloutGroupStatus.RUNNING);
+
+ if (rolloutGroups.isEmpty()) {
+ // no running rollouts, probably there was an error
+ // somewhere at the latest group. And the latest group has
+ // been switched from running into error state. So we need
+ // to find the latest group which
+ executeLatestRolloutGroup(rollout);
+ } else {
+ LOGGER.debug("Rollout {} has {} running groups", rollout.getId(), rolloutGroups.size());
+ executeRolloutGroups(rollout, rolloutGroups);
+ }
+
+ if (isRolloutComplete(rollout)) {
+ LOGGER.info("Rollout {} is finished, setting finished status", rollout);
+ rollout.setStatus(RolloutStatus.FINISHED);
+ rolloutRepository.save(rollout);
+ }
+ }
+ }
+
+ /**
+ * Verifies and handles stucked rollouts in asynchronous creation or
+ * starting state. If rollouts are created or started asynchronously it
+ * might be that they keep in state {@link RolloutStatus#CREATING} or
+ * {@link RolloutStatus#STARTING} due database or application interruption.
+ * In case this happens, set the rollout to error state.
+ */
+ private void verifyStuckedRollouts() {
+ final List rolloutsInCreatingState = rolloutRepository.findByStatus(RolloutStatus.CREATING);
+ rolloutsInCreatingState.stream().filter(rollout -> !creatingRollouts.contains(rollout.getName()))
+ .forEach(rollout -> {
+ LOGGER.warn(
+ "Determined error during rollout creation of rollout {}, stucking in creating state, setting to status",
+ rollout, RolloutStatus.ERROR_CREATING);
+ rollout.setStatus(RolloutStatus.ERROR_CREATING);
+ rolloutRepository.save(rollout);
+ });
+
+ final List rolloutsInStartingState = rolloutRepository.findByStatus(RolloutStatus.STARTING);
+ rolloutsInStartingState.stream().filter(rollout -> !startingRollouts.contains(rollout.getName()))
+ .forEach(rollout -> {
+ LOGGER.warn(
+ "Determined error during rollout starting of rollout {}, stucking in starting state, setting to status",
+ rollout, RolloutStatus.ERROR_STARTING);
+ rollout.setStatus(RolloutStatus.ERROR_STARTING);
+ rolloutRepository.save(rollout);
+ });
+
+ }
+
+ private void executeRolloutGroups(final Rollout rollout, final List rolloutGroups) {
+ for (final RolloutGroup rolloutGroup : rolloutGroups) {
+ // error state check, do we need to stop the whole
+ // rollout because of error?
+ final RolloutGroupErrorCondition errorCondition = rolloutGroup.getErrorCondition();
+ final boolean isError = checkErrorState(rollout, rolloutGroup, errorCondition);
+ if (isError) {
+ LOGGER.info("Rollout {} {} has error, calling error action", rollout.getName(), rollout.getId());
+ callErrorAction(rollout, rolloutGroup);
+ } else {
+ // not in error so check finished state, do we need to
+ // start the next group?
+ final RolloutGroupSuccessCondition finishedCondition = rolloutGroup.getSuccessCondition();
+ checkFinishCondition(rollout, rolloutGroup, finishedCondition);
+ if (isRolloutGroupComplete(rollout, rolloutGroup)) {
+ rolloutGroup.setStatus(RolloutGroupStatus.FINISHED);
+ rolloutGroupRepository.save(rolloutGroup);
+ }
+ }
+ }
+ }
+
+ private void executeLatestRolloutGroup(final Rollout rollout) {
+ final List latestRolloutGroup = rolloutGroupRepository
+ .findByRolloutAndStatusNotOrderByIdDesc(rollout, RolloutGroupStatus.SCHEDULED);
+ if (latestRolloutGroup.isEmpty()) {
+ return;
+ }
+ executeRolloutGroupSuccessAction(rollout, latestRolloutGroup.get(0));
+ }
+
+ private void callErrorAction(final Rollout rollout, final RolloutGroup rolloutGroup) {
+ try {
+ context.getBean(rolloutGroup.getErrorAction().getBeanName(), RolloutGroupActionEvaluator.class)
+ .eval(rollout, rolloutGroup, rolloutGroup.getErrorActionExp());
+ } catch (final BeansException e) {
+ LOGGER.error("Something bad happend when accessing the error action bean {}",
+ rolloutGroup.getErrorAction().getBeanName(), e);
+ }
+ }
+
+ private boolean isRolloutComplete(final Rollout rollout) {
+ final Long groupsActiveLeft = rolloutGroupRepository.countByRolloutAndStatusOrStatus(rollout,
+ RolloutGroupStatus.RUNNING, RolloutGroupStatus.SCHEDULED);
+ return groupsActiveLeft == 0;
+ }
+
+ private boolean isRolloutGroupComplete(final Rollout rollout, final RolloutGroup rolloutGroup) {
+ final Long actionsLeftForRollout = actionRepository
+ .countByRolloutAndRolloutGroupAndStatusNotAndStatusNotAndStatusNot(rollout, rolloutGroup,
+ Action.Status.ERROR, Action.Status.FINISHED, Action.Status.CANCELED);
+ return actionsLeftForRollout == 0;
+ }
+
+ private boolean checkErrorState(final Rollout rollout, final RolloutGroup rolloutGroup,
+ final RolloutGroupErrorCondition errorCondition) {
+ if (errorCondition == null) {
+ // there is no error condition, so return false, don't have error.
+ return false;
+ }
+ try {
+ return context.getBean(errorCondition.getBeanName(), RolloutGroupConditionEvaluator.class).eval(rollout,
+ rolloutGroup, rolloutGroup.getErrorConditionExp());
+ } catch (final BeansException e) {
+ LOGGER.error("Something bad happend when accessing the error condition bean {}",
+ errorCondition.getBeanName(), e);
+ return false;
+ }
+ }
+
+ private boolean checkFinishCondition(final Rollout rollout, final RolloutGroup rolloutGroup,
+ final RolloutGroupSuccessCondition finishCondition) {
+ LOGGER.trace("Checking finish condition {} on rolloutgroup {}", finishCondition, rolloutGroup);
+ try {
+ final boolean isFinished = context
+ .getBean(finishCondition.getBeanName(), RolloutGroupConditionEvaluator.class)
+ .eval(rollout, rolloutGroup, rolloutGroup.getSuccessConditionExp());
+ if (isFinished) {
+ LOGGER.info("Rolloutgroup {} is finished, starting next group", rolloutGroup);
+ executeRolloutGroupSuccessAction(rollout, rolloutGroup);
+ } else {
+ LOGGER.debug("Rolloutgroup {} is still running", rolloutGroup);
+ }
+ return isFinished;
+ } catch (final BeansException e) {
+ LOGGER.error("Something bad happend when accessing the finish condition bean {}",
+ finishCondition.getBeanName(), e);
+ return false;
+ }
+ }
+
+ private void executeRolloutGroupSuccessAction(final Rollout rollout, final RolloutGroup rolloutGroup) {
+ context.getBean(rolloutGroup.getSuccessAction().getBeanName(), RolloutGroupActionEvaluator.class).eval(rollout,
+ rolloutGroup, rolloutGroup.getSuccessActionExp());
+ }
+
+ /**
+ * Counts all {@link Target}s in the repository.
+ *
+ * @return number of targets
+ */
+ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
+ public Long countRolloutsAll() {
+ return rolloutRepository.count();
+ }
+
+ /**
+ * Count rollouts by specified filter text.
+ *
+ * @param searchText
+ * name or description
+ * @return total count rollouts for specified filter text.
+ */
+ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
+ public Long countRolloutsAllByFilters(final String searchText) {
+ return rolloutRepository.count(likeNameOrDescription(searchText));
+ }
+
+ private static Specification likeNameOrDescription(final String searchText) {
+ return (rolloutRoot, query, criteriaBuilder) -> {
+ final String searchTextToLower = searchText.toLowerCase();
+ return criteriaBuilder.or(
+ criteriaBuilder.like(criteriaBuilder.lower(rolloutRoot.get(Rollout_.name)), searchTextToLower),
+ criteriaBuilder.like(criteriaBuilder.lower(rolloutRoot.get(Rollout_.description)),
+ searchTextToLower));
+ };
+ }
+
+ /**
+ * * Retrieves a specific rollout by its ID.
+ *
+ * @param pageable
+ * the page request to sort and limit the result
+ * @param searchText
+ * search text which matches name or description of rollout
+ * @return the founded rollout or {@code null} if rollout with given ID does
+ * not exists
+ */
+ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
+ public Slice findRolloutByFilters(final Pageable pageable, @NotEmpty final String searchText) {
+ final Specification specs = likeNameOrDescription(searchText);
+ final Slice findAll = criteriaNoCountDao.findAll(specs, pageable, Rollout.class);
+ setRolloutStatusDetails(findAll);
+ return findAll;
+ }
+
+ /**
+ * Retrieves a specific rollout by its name.
+ *
+ * @param rolloutName
+ * the name of the rollout to retrieve
+ * @return the founded rollout or {@code null} if rollout with given name
+ * does not exists
+ */
+ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
+ public Rollout findRolloutByName(final String rolloutName) {
+ return rolloutRepository.findByName(rolloutName);
+ }
+
+ /**
+ * Update rollout details.
+ *
+ * @param rollout
+ * rollout to be updated
+ *
+ * @return Rollout updated rollout
+ */
+ @NotNull
+ @Transactional
+ @Modifying
+ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE)
+ public Rollout updateRollout(@NotNull final Rollout rollout) {
+ Assert.notNull(rollout.getId());
+ return rolloutRepository.save(rollout);
+ }
+
+ /**
+ * Get count of targets in different status in rollout.
+ *
+ * @param page
+ * the page request to sort and limit the result
+ * @return a list of rollouts with details of targets count for different
+ * statuses
+ *
+ */
+ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
+ public Page findAllRolloutsWithDetailedStatus(final Pageable page) {
+ final Page rollouts = findAll(page);
+ setRolloutStatusDetails(rollouts);
+ return rollouts;
+
+ }
+
+ /**
+ * Get count of targets in different status in rollout.
+ *
+ * @param rolloutId
+ * rollout id
+ * @return rollout details of targets count for different statuses
+ *
+ */
+ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
+ public Rollout findRolloutWithDetailedStatus(final Long rolloutId) {
+ final Rollout rollout = findRolloutById(rolloutId);
+ final List rolloutStatusCountItems = actionRepository
+ .getStatusCountByRolloutId(rolloutId);
+ final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus(rolloutStatusCountItems,
+ rollout.getTotalTargets());
+ rollout.setTotalTargetCountStatus(totalTargetCountStatus);
+ return rollout;
+ }
+
+ private Map> getStatusCountItemForRollout(final List rolloutIds) {
+ final List resultList = actionRepository.getStatusCountByRolloutId(rolloutIds);
+ return resultList.stream().collect(Collectors.groupingBy(TotalTargetCountActionStatus::getId));
+ }
+
+ private void setRolloutStatusDetails(final Slice rollouts) {
+ final List rolloutIds = rollouts.getContent().stream().map(rollout -> rollout.getId())
+ .collect(Collectors.toList());
+ final Map> allStatesForRollout = getStatusCountItemForRollout(
+ rolloutIds);
+
+ for (final Rollout rollout : rollouts) {
+ final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus(
+ allStatesForRollout.get(rollout.getId()), rollout.getTotalTargets());
+ rollout.setTotalTargetCountStatus(totalTargetCountStatus);
+ }
+ }
+
+ private void checkIfRolloutCanStarted(final Rollout rollout, final Rollout mergedRollout) {
+ if (!(RolloutStatus.READY.equals(mergedRollout.getStatus()))) {
+ throw new RolloutIllegalStateException("Rollout can only be started in state ready but current state is "
+ + rollout.getStatus().name().toLowerCase());
+ }
+ }
+
+ /***
+ * Get finished percentage details for a specified group which is in running
+ * state.
+ *
+ * @param rolloutId
+ * the ID of the {@link Rollout}
+ * @param rolloutGroup
+ * the ID of the {@link RolloutGroup}
+ * @return percentage finished
+ */
+ public float getFinishedPercentForRunningGroup(final Long rolloutId, final RolloutGroup rolloutGroup) {
+ final Long totalGroup = rolloutGroup.getTotalTargets();
+ final Long finished = actionRepository.countByRolloutIdAndRolloutGroupIdAndStatus(rolloutId,
+ rolloutGroup.getId(), Action.Status.FINISHED);
+ if (totalGroup == 0) {
+ // in case e.g. targets has been deleted we don't have any actions
+ // left for this group, so the group is finished
+ return 100;
+ }
+ // calculate threshold
+ return ((float) finished / (float) totalGroup) * 100;
+ }
+}
diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutRepository.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutRepository.java
new file mode 100644
index 000000000..9c4318a3f
--- /dev/null
+++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutRepository.java
@@ -0,0 +1,87 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.repository;
+
+import java.util.List;
+
+import org.eclipse.hawkbit.repository.model.Rollout;
+import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * The repository interface for the {@link Rollout} model.
+ */
+@Transactional(readOnly = true)
+public interface RolloutRepository extends BaseEntityRepository, JpaSpecificationExecutor {
+
+ /**
+ * Updates the {@code lastCheck} field of the {@link Rollout} for rollouts
+ * in a specific status and only if the {@code lastCheck} is overdue.
+ *
+ * @param lastCheck
+ * the time in milliseconds to set to the lastCheck column
+ * @param delay
+ * the delay between last checks
+ * @param status
+ * the status which the rollout should have to update the last
+ * check field
+ * @return the count of the updated rows. Zero if no row has been updated
+ */
+ @Modifying
+ @Transactional
+ @Query("UPDATE Rollout r SET r.lastCheck = :lastCheck WHERE r.lastCheck < (:lastCheck - :delay) AND r.status=:status")
+ int updateLastCheck(@Param("lastCheck") final long lastCheck, @Param("delay") final long delay,
+ @Param("status") final RolloutStatus status);
+
+ /**
+ * Retrieves all {@link Rollout} for a specific {@code lastCheck} time and
+ * for a specific status.
+ *
+ * @param lastCheck
+ * the lastCheck time to find the specific rollout.
+ * @param status
+ * the status of the rollout to find
+ * @return the list of {@link Rollout} for specific lastCheck time and
+ * status
+ */
+ List findByLastCheckAndStatus(long lastCheck, RolloutStatus status);
+
+ /**
+ * Retrieves all {@link Rollout} for a specific {@code name}
+ *
+ * @param name
+ * the rollout name
+ * @return {@link Rollout} for specific name
+ */
+ Page findByName(final Pageable pageable, String name);
+
+ /**
+ * Retrieves all {@link Rollout} for a specific {@code name}
+ *
+ * @param name
+ * the rollout name
+ * @return {@link Rollout} for specific name
+ */
+ Rollout findByName(String name);
+
+ /**
+ * Retrieves all {@link Rollout} for a specific status.
+ *
+ * @param status
+ * the status of the rollouts to retrieve
+ * @return a list of {@link Rollout} having the given status
+ */
+ List findByStatus(final RolloutStatus status);
+}
diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutScheduler.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutScheduler.java
new file mode 100644
index 000000000..b60d64cc5
--- /dev/null
+++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutScheduler.java
@@ -0,0 +1,91 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.repository;
+
+import java.util.List;
+
+import org.eclipse.hawkbit.security.SystemSecurityContext;
+import org.eclipse.hawkbit.tenancy.TenantAware;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.EnvironmentAware;
+import org.springframework.context.annotation.Profile;
+import org.springframework.core.env.Environment;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+/**
+ * Scheduler to schedule the
+ * {@link RolloutManagement#checkRunningRollouts(long)}. The delay between the
+ * checks be be configured using the property
+ * {@link #PROP_SCHEDULER_DELAY_PLACEHOLDER}.
+ */
+@Component
+// don't active the rollout scheduler in test, otherwise it is hard to test
+// rolloutmanagement and leads weird side-effects maybe.
+@Profile("!test")
+public class RolloutScheduler implements EnvironmentAware {
+
+ private static final Logger logger = LoggerFactory.getLogger(RolloutScheduler.class);
+
+ private static final String PROP_SCHEDULER_DELAY = "hawkbit.rollout.scheduler.fixedDelay";
+ private static final long DEFAULT_SCHEDULER_DELAY = 30000L;
+ private static final String PROP_SCHEDULER_DELAY_PLACEHOLDER = "${" + PROP_SCHEDULER_DELAY + ":"
+ + DEFAULT_SCHEDULER_DELAY + "}";
+
+ @Autowired
+ private TenantAware tenantAware;
+
+ @Autowired
+ private SystemManagement systemManagement;
+
+ @Autowired
+ private RolloutManagement rolloutManagement;
+
+ @Autowired
+ private SystemSecurityContext systemSecurityContext;
+
+ private long fixedDelay = DEFAULT_SCHEDULER_DELAY;
+
+ /**
+ * Scheduler method called by the spring-async mechanism. Retrieves all
+ * tenants from the {@link SystemManagement#findTenants()} and runs for each
+ * tenant the {@link RolloutManagement#checkRunningRollouts(long)} in the
+ * {@link SystemSecurityContext}.
+ */
+ @Scheduled(initialDelayString = PROP_SCHEDULER_DELAY_PLACEHOLDER, fixedDelayString = PROP_SCHEDULER_DELAY_PLACEHOLDER)
+ public void rolloutScheduler() {
+ logger.debug("rollout schedule checker has been triggered.");
+ // run this code in system code privileged to have the necessary
+ // permission to query and create entities.
+ systemSecurityContext.runAsSystem(() -> {
+ // workaround eclipselink that is currently not possible to
+ // execute a query without multitenancy if MultiTenant
+ // annotation is used.
+ // https://bugs.eclipse.org/bugs/show_bug.cgi?id=355458. So
+ // iterate through all tenants and execute the rollout check for
+ // each tenant seperately.
+ final List tenants = systemManagement.findTenants();
+ logger.info("Checking rollouts for {} tenants", tenants.size());
+ for (final String tenant : tenants) {
+ tenantAware.runAsTenant(tenant, () -> {
+ rolloutManagement.checkRunningRollouts(fixedDelay);
+ return null;
+ });
+ }
+ return null;
+ });
+ }
+
+ @Override
+ public void setEnvironment(final Environment environment) {
+ fixedDelay = environment.getProperty(PROP_SCHEDULER_DELAY, Long.class, DEFAULT_SCHEDULER_DELAY);
+ }
+}
diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutTargetGroupRepository.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutTargetGroupRepository.java
new file mode 100644
index 000000000..6564e2c1f
--- /dev/null
+++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutTargetGroupRepository.java
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.repository;
+
+import org.eclipse.hawkbit.repository.model.RolloutTargetGroup;
+import org.eclipse.hawkbit.repository.model.RolloutTargetGroupId;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.repository.CrudRepository;
+
+/**
+ *
+ *
+ */
+public interface RolloutTargetGroupRepository
+ extends CrudRepository, JpaSpecificationExecutor {
+}
diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/SystemManagement.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/SystemManagement.java
index 64865ac65..09b6025d3 100644
--- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/SystemManagement.java
+++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/SystemManagement.java
@@ -96,6 +96,12 @@ public class SystemManagement implements EnvironmentAware {
@Autowired
private TenantConfigurationRepository tenantConfigurationRepository;
+ @Autowired
+ private RolloutRepository rolloutRepository;
+
+ @Autowired
+ private RolloutGroupRepository rolloutGroupRepository;
+
@Autowired
private TenantAware tenantAware;
@@ -209,7 +215,8 @@ public class SystemManagement implements EnvironmentAware {
* @return list of all tenant names in the system.
*/
@NotNull
- @PreAuthorize(SpringEvalExpressions.HAS_AUTH_SYSTEM_ADMIN)
+ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_SYSTEM_ADMIN + SpringEvalExpressions.HAS_AUTH_OR
+ + SpringEvalExpressions.IS_SYSTEM_CODE)
// tenant independent
public List findTenants() {
return tenantMetaDataRepository.findAll().stream().map(md -> md.getTenant()).collect(Collectors.toList());
@@ -234,11 +241,13 @@ public class SystemManagement implements EnvironmentAware {
tenantMetaDataRepository.deleteByTenantIgnoreCase(tenant);
tenantConfigurationRepository.deleteByTenantIgnoreCase(tenant);
targetRepository.deleteByTenantIgnoreCase(tenant);
+ actionRepository.deleteByTenantIgnoreCase(tenant);
+ rolloutGroupRepository.deleteByTenantIgnoreCase(tenant);
+ rolloutRepository.deleteByTenantIgnoreCase(tenant);
artifactRepository.deleteByTenantIgnoreCase(tenant);
externalArtifactRepository.deleteByTenantIgnoreCase(tenant);
externalArtifactProviderRepository.deleteByTenantIgnoreCase(tenant);
targetTagRepository.deleteByTenantIgnoreCase(tenant);
- actionRepository.deleteByTenantIgnoreCase(tenant);
distributionSetTagRepository.deleteByTenantIgnoreCase(tenant);
distributionSetRepository.deleteByTenantIgnoreCase(tenant);
distributionSetTypeRepository.deleteByTenantIgnoreCase(tenant);
diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/TargetRepository.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/TargetRepository.java
index 6cb40cd09..35ba39660 100644
--- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/TargetRepository.java
+++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/TargetRepository.java
@@ -12,10 +12,12 @@ import java.util.Collection;
import java.util.List;
import org.eclipse.hawkbit.repository.model.DistributionSet;
+import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.Tag;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
+import org.eclipse.hawkbit.repository.model.TargetWithActionStatus;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
@@ -278,4 +280,43 @@ public interface TargetRepository extends BaseEntityRepository, Jp
@Query("UPDATE Target t SET t.assignedDistributionSet = :set, t.lastModifiedAt = :lastModifiedAt, t.lastModifiedBy = :lastModifiedBy WHERE t.id IN :targets")
void setAssignedDistributionSet(@Param("set") DistributionSet set, @Param("lastModifiedAt") Long modifiedAt,
@Param("lastModifiedBy") String modifiedBy, @Param("targets") Collection targets);
+
+ List findByRolloutTargetGroupRolloutGroup(final RolloutGroup rolloutGroup);
+
+ /**
+ *
+ * Finds all targets of a rollout group.
+ *
+ * @param rolloutGroupId
+ * the ID of the rollout group
+ * @param page
+ * the page request parameter
+ * @return a page of all targets related to a rollout group
+ */
+ Page findByRolloutTargetGroupRolloutGroupId(final Long rolloutGroupId, Pageable page);
+
+ /**
+ * Finds all targets related to a target rollout group stored for a specific
+ * rollout.
+ *
+ * @param rolloutGroup
+ * the rollout group the targets should belong to
+ * @param page
+ * the page request parameter
+ * @return a page of all targets related to a rollout group
+ */
+ Page findByActionsRolloutGroup(RolloutGroup rolloutGroup, Pageable page);
+
+ /**
+ * Find all targets with action status for a specific group.
+ *
+ * @param pageable
+ * the page request parameter
+ * @param rolloutGroupId
+ * the ID of the rollout group
+ * @return targets with action status
+ */
+ @Query("select DISTINCT NEW org.eclipse.hawkbit.repository.model.TargetWithActionStatus(a.target,a.status) from Action a inner join fetch a.target t where a.rolloutGroup.id = :rolloutGroupId")
+ Page findTargetsWithActionStatusByRolloutGroupId(final Pageable pageable,
+ @Param("rolloutGroupId") Long rolloutGroupId);
}
diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/exception/RolloutIllegalStateException.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/exception/RolloutIllegalStateException.java
new file mode 100644
index 000000000..f2920c18d
--- /dev/null
+++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/exception/RolloutIllegalStateException.java
@@ -0,0 +1,63 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.repository.exception;
+
+import org.eclipse.hawkbit.exception.SpServerError;
+import org.eclipse.hawkbit.exception.SpServerRtException;
+
+/**
+ * the {@link RolloutIllegalStateException} is thrown when a rollout is changing
+ * it's state which is not valid. E.g. trying to start a already running
+ * rollout, or trying to resume a already finished rollout.
+ *
+ */
+public class RolloutIllegalStateException extends SpServerRtException {
+
+ private static final long serialVersionUID = 1L;
+ private static final SpServerError THIS_ERROR = SpServerError.SP_ROLLOUT_ILLEGAL_STATE;
+
+ /**
+ * Default constructor.
+ */
+ public RolloutIllegalStateException() {
+ super(THIS_ERROR);
+ }
+
+ /**
+ * Parameterized constructor.
+ *
+ * @param cause
+ * of the exception
+ */
+ public RolloutIllegalStateException(final Throwable cause) {
+ super(THIS_ERROR, cause);
+ }
+
+ /**
+ * Parameterized constructor.
+ *
+ * @param message
+ * of the exception
+ * @param cause
+ * of the exception
+ */
+ public RolloutIllegalStateException(final String message, final Throwable cause) {
+ super(message, THIS_ERROR, cause);
+ }
+
+ /**
+ * Parameterized constructor.
+ *
+ * @param message
+ * of the exception
+ */
+ public RolloutIllegalStateException(final String message) {
+ super(message, THIS_ERROR);
+ }
+}
diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Action.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Action.java
index 80624054a..0554e6c58 100644
--- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Action.java
+++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Action.java
@@ -94,6 +94,14 @@ public class Action extends BaseEntity implements Comparable {
CascadeType.REMOVE })
private List actionStatus;
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "rolloutgroup", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_rolloutgroup") )
+ private RolloutGroup rolloutGroup;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "rollout", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_rollout") )
+ private Rollout rollout;
+
/**
* Note: filled only in {@link Status#DOWNLOAD}.
*/
@@ -221,6 +229,36 @@ public class Action extends BaseEntity implements Comparable {
this.forcedTime = forcedTime;
}
+ /**
+ * @return the rolloutGroup
+ */
+ public RolloutGroup getRolloutGroup() {
+ return rolloutGroup;
+ }
+
+ /**
+ * @param rolloutGroup
+ * the rolloutGroup to set
+ */
+ public void setRolloutGroup(final RolloutGroup rolloutGroup) {
+ this.rolloutGroup = rolloutGroup;
+ }
+
+ /**
+ * @return the rollout
+ */
+ public Rollout getRollout() {
+ return rollout;
+ }
+
+ /**
+ * @param rollout
+ * the rollout to set
+ */
+ public void setRollout(final Rollout rollout) {
+ this.rollout = rollout;
+ }
+
@Override
public int compareTo(final Action o) {
if (super.getId() == null || o == null || o.getId() == null) {
@@ -379,7 +417,13 @@ public class Action extends BaseEntity implements Comparable {
/**
* Action needs download by this target which has now started.
*/
- DOWNLOAD;
+ DOWNLOAD,
+
+ /**
+ * Action is in waiting state, e.g. the action is scheduled in a rollout
+ * but not yet activated.
+ */
+ SCHEDULED;
}
/**
diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/ActionWithStatusCount.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/ActionWithStatusCount.java
index abe2ee961..7d01b9dd2 100644
--- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/ActionWithStatusCount.java
+++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/ActionWithStatusCount.java
@@ -32,6 +32,7 @@ public class ActionWithStatusCount {
private final String dsName;
private final String dsVersion;
private final Action action;
+ private final String rolloutName;
/**
* JPA constructor, the parameter are the result set columns of the custom
@@ -59,10 +60,14 @@ public class ActionWithStatusCount {
* the version of the distributionset
* @param actionStatusCount
* the count of the action status for this action
+ * @param rolloutName
+ * the rollout name
*/
+
public ActionWithStatusCount(final Long actionId, final ActionType actionType, final boolean active,
final long forcedTime, final Status status, final Long actionCreatedAt, final Long actionLastModifiedAt,
- final Long dsId, final String dsName, final String dsVersion, final Long actionStatusCount) {
+ final Long dsId, final String dsName, final String dsVersion, final Long actionStatusCount,
+ final String rolloutName) {
this.actionId = actionId;
this.actionType = actionType;
this.actionActive = active;
@@ -74,6 +79,7 @@ public class ActionWithStatusCount {
this.dsName = dsName;
this.dsVersion = dsVersion;
this.actionStatusCount = actionStatusCount;
+ this.rolloutName = rolloutName;
this.action = new Action();
this.action.setActionType(actionType);
@@ -166,4 +172,12 @@ public class ActionWithStatusCount {
public Long getActionStatusCount() {
return actionStatusCount;
}
+
+ /**
+ * @return the rolloutName
+ */
+ public String getRolloutName() {
+ return rolloutName;
+ }
+
}
diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/BaseEntity.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/BaseEntity.java
index cd9873fe8..72d25be6c 100644
--- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/BaseEntity.java
+++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/BaseEntity.java
@@ -22,6 +22,7 @@ import javax.persistence.PrePersist;
import javax.persistence.Version;
import org.eclipse.hawkbit.eventbus.CacheFieldEntityListener;
+import org.eclipse.hawkbit.eventbus.EntityPropertyChangeListener;
import org.eclipse.hawkbit.repository.exception.TenantNotExistException;
import org.eclipse.hawkbit.repository.model.helper.SystemManagementHolder;
import org.eclipse.hawkbit.repository.model.helper.TenantAwareHolder;
@@ -45,7 +46,7 @@ import org.springframework.hateoas.Identifiable;
*/
@MappedSuperclass
@Access(AccessType.FIELD)
-@EntityListeners({ AuditingEntityListener.class, CacheFieldEntityListener.class })
+@EntityListeners({ AuditingEntityListener.class, CacheFieldEntityListener.class, EntityPropertyChangeListener.class })
@TenantDiscriminatorColumn(name = "tenant", length = 40)
@Multitenant(MultitenantType.SINGLE_TABLE)
public abstract class BaseEntity implements Serializable, Identifiable {
@@ -98,9 +99,9 @@ public abstract class BaseEntity implements Serializable, Identifiable {
// service
final String currentTenant = SystemManagementHolder.getInstance().currentTenant();
if (currentTenant == null) {
- throw new TenantNotExistException(
- "Tenant " + TenantAwareHolder.getInstance().getTenantAware().getCurrentTenant()
- + " does not exists, cannot create entity " + this.getClass() + " with id " + id);
+ throw new TenantNotExistException("Tenant "
+ + TenantAwareHolder.getInstance().getTenantAware().getCurrentTenant()
+ + " does not exists, cannot create entity " + this.getClass() + " with id " + id);
}
setTenant(currentTenant.toUpperCase());
}
diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Rollout.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Rollout.java
new file mode 100644
index 000000000..3b678ec48
--- /dev/null
+++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Rollout.java
@@ -0,0 +1,311 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.repository.model;
+
+import java.util.List;
+
+import javax.persistence.Column;
+import javax.persistence.ConstraintMode;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.FetchType;
+import javax.persistence.ForeignKey;
+import javax.persistence.Index;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.Transient;
+import javax.persistence.UniqueConstraint;
+
+import org.eclipse.hawkbit.cache.CacheField;
+import org.eclipse.hawkbit.cache.CacheKeys;
+import org.eclipse.hawkbit.repository.model.Action.ActionType;
+
+/**
+ * @author Michael Hirsch
+ *
+ */
+@Entity
+@Table(name = "sp_rollout", indexes = {
+ @Index(name = "sp_idx_rollout_01", columnList = "tenant,name") }, uniqueConstraints = @UniqueConstraint(columnNames = {
+ "name", "tenant" }, name = "uk_rollout") )
+public class Rollout extends NamedEntity {
+
+ private static final long serialVersionUID = 1L;
+
+ @OneToMany(targetEntity = RolloutGroup.class)
+ @JoinColumn(name = "rollout", insertable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rollout_rolloutgroup") )
+ private List rolloutGroups;
+
+ @Column(name = "target_filter", length = 1024, nullable = false)
+ private String targetFilterQuery;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "distribution_set", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rolltout_ds") )
+ private DistributionSet distributionSet;
+
+ @Column(name = "status")
+ private RolloutStatus status = RolloutStatus.CREATING;
+
+ @Column(name = "last_check")
+ private long lastCheck = 0L;
+
+ @Column(name = "action_type", nullable = false)
+ @Enumerated(EnumType.STRING)
+ private ActionType actionType = ActionType.FORCED;
+
+ @Column(name = "forced_time")
+ private long forcedTime;
+
+ @Column(name = "total_targets")
+ private long totalTargets;
+
+ @Transient
+ @CacheField(key = CacheKeys.ROLLOUT_GROUP_TOTAL)
+ private int rolloutGroupsTotal = 0;
+
+ @Transient
+ @CacheField(key = CacheKeys.ROLLOUT_GROUP_CREATED)
+ private int rolloutGroupsCreated = 0;
+
+ @Transient
+ private TotalTargetCountStatus totalTargetCountStatus;
+
+ /**
+ * @return the distributionSet
+ */
+ public DistributionSet getDistributionSet() {
+ return distributionSet;
+ }
+
+ /**
+ * @param distributionSet
+ * the distributionSet to set
+ */
+ public void setDistributionSet(final DistributionSet distributionSet) {
+ this.distributionSet = distributionSet;
+ }
+
+ /**
+ * @return the rolloutGroups
+ */
+ public List getRolloutGroups() {
+ return rolloutGroups;
+ }
+
+ /**
+ * @param rolloutGroups
+ * the rolloutGroups to set
+ */
+ public void setRolloutGroups(final List rolloutGroups) {
+ this.rolloutGroups = rolloutGroups;
+ }
+
+ /**
+ * @return the targetFilterQuery
+ */
+ public String getTargetFilterQuery() {
+ return targetFilterQuery;
+ }
+
+ /**
+ * @param targetFilterQuery
+ * the targetFilterQuery to set
+ */
+ public void setTargetFilterQuery(final String targetFilterQuery) {
+ this.targetFilterQuery = targetFilterQuery;
+ }
+
+ /**
+ * @return the status
+ */
+ public RolloutStatus getStatus() {
+ return status;
+ }
+
+ /**
+ * @param status
+ * the status to set
+ */
+ public void setStatus(final RolloutStatus status) {
+ this.status = status;
+ }
+
+ /**
+ * @return the lastCheck
+ */
+ public long getLastCheck() {
+ return lastCheck;
+ }
+
+ /**
+ * @param lastCheck
+ * the lastCheck to set
+ */
+ public void setLastCheck(final long lastCheck) {
+ this.lastCheck = lastCheck;
+ }
+
+ /**
+ * @return the actionType
+ */
+ public ActionType getActionType() {
+ return actionType;
+ }
+
+ /**
+ * @param actionType
+ * the actionType to set
+ */
+ public void setActionType(final ActionType actionType) {
+ this.actionType = actionType;
+ }
+
+ /**
+ * @return the forcedTime
+ */
+ public long getForcedTime() {
+ return forcedTime;
+ }
+
+ /**
+ * @param forcedTime
+ * the forcedTime to set
+ */
+ public void setForcedTime(final long forcedTime) {
+ this.forcedTime = forcedTime;
+ }
+
+ /**
+ * @return the totalTargets
+ */
+ public long getTotalTargets() {
+ return totalTargets;
+ }
+
+ /**
+ * @param totalTargets
+ * the totalTargets to set
+ */
+ public void setTotalTargets(final long totalTargets) {
+ this.totalTargets = totalTargets;
+ }
+
+ /**
+ * @return the rolloutGroupsTotal
+ */
+ public int getRolloutGroupsTotal() {
+ return rolloutGroupsTotal;
+ }
+
+ /**
+ * @param rolloutGroupsTotal
+ * the rolloutGroupsTotal to set
+ */
+ public void setRolloutGroupsTotal(final int rolloutGroupsTotal) {
+ this.rolloutGroupsTotal = rolloutGroupsTotal;
+ }
+
+ /**
+ * @return the rolloutGroupsCreated
+ */
+ public int getRolloutGroupsCreated() {
+ return rolloutGroupsCreated;
+ }
+
+ /**
+ * @param rolloutGroupsCreated
+ * the rolloutGroupsCreated to set
+ */
+ public void setRolloutGroupsCreated(final int rolloutGroupsCreated) {
+ this.rolloutGroupsCreated = rolloutGroupsCreated;
+ }
+
+ /**
+ * @return the totalTargetCountStatus
+ */
+ public TotalTargetCountStatus getTotalTargetCountStatus() {
+ if (totalTargetCountStatus == null) {
+ this.totalTargetCountStatus = new TotalTargetCountStatus(totalTargets);
+ }
+ return totalTargetCountStatus;
+ }
+
+ /**
+ * @param totalTargetCountStatus
+ * the totalTargetCountStatus to set
+ */
+ public void setTotalTargetCountStatus(final TotalTargetCountStatus totalTargetCountStatus) {
+ this.totalTargetCountStatus = totalTargetCountStatus;
+ }
+
+ @Override
+ public String toString() {
+ return "Rollout [rolloutGroups=" + rolloutGroups + ", targetFilterQuery=" + targetFilterQuery
+ + ", distributionSet=" + distributionSet + ", status=" + status + ", lastCheck=" + lastCheck
+ + ", getName()=" + getName() + ", getId()=" + getId() + "]";
+ }
+
+ /**
+ *
+ * @author Michael Hirsch
+ *
+ */
+ public static enum RolloutStatus {
+
+ /**
+ * Rollouts is beeing created.
+ */
+ CREATING,
+
+ /**
+ * Rollout is ready to start.
+ */
+ READY,
+
+ /**
+ * Rollout is paused.
+ */
+ PAUSED,
+
+ /**
+ * Rollout is starting.
+ */
+ STARTING,
+
+ /**
+ * Rollout is stopped.
+ */
+ STOPPED,
+
+ /**
+ * Rollout is running.
+ */
+ RUNNING,
+
+ /**
+ * Rollout is finished.
+ */
+ FINISHED,
+
+ /**
+ * Rollout could not created due errors, might be database problem due
+ * asynchronous creating.
+ */
+ ERROR_CREATING,
+
+ /**
+ * Rollout could not started due errors, might be database problem due
+ * asynchronous starting.
+ */
+ ERROR_STARTING;
+ }
+}
diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/RolloutGroup.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/RolloutGroup.java
new file mode 100644
index 000000000..ad7efdf1f
--- /dev/null
+++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/RolloutGroup.java
@@ -0,0 +1,481 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.repository.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.ConstraintMode;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.ForeignKey;
+import javax.persistence.Index;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.Transient;
+import javax.persistence.UniqueConstraint;
+
+/**
+ * JPA entity definition of persisting a group of an rollout.
+ *
+ * @author Michael Hirsch
+ *
+ */
+@Entity
+@Table(name = "sp_rolloutgroup", indexes = {
+ @Index(name = "sp_idx_rolloutgroup_01", columnList = "tenant,name") }, uniqueConstraints = @UniqueConstraint(columnNames = {
+ "name", "rollout", "tenant" }, name = "uk_rolloutgroup") )
+public class RolloutGroup extends NamedEntity {
+
+ private static final long serialVersionUID = 1L;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "rollout", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rolloutgroup_rollout") )
+ private Rollout rollout;
+
+ @Column(name = "status")
+ private RolloutGroupStatus status = RolloutGroupStatus.READY;
+
+ @OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST })
+ @JoinColumn(name = "rolloutGroup_Id", insertable = false, updatable = false)
+ private final List rolloutTargetGroup = new ArrayList<>();
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ private RolloutGroup parent;
+
+ @Column(name = "success_condition", nullable = false)
+ private RolloutGroupSuccessCondition successCondition = RolloutGroupSuccessCondition.THRESHOLD;
+
+ @Column(name = "success_condition_exp", length = 512, nullable = false)
+ private String successConditionExp = null;
+
+ @Column(name = "success_action", nullable = false)
+ private RolloutGroupSuccessAction successAction = RolloutGroupSuccessAction.NEXTGROUP;
+
+ @Column(name = "success_action_exp", length = 512, nullable = false)
+ private String successActionExp = null;
+
+ @Column(name = "error_condition")
+ private RolloutGroupErrorCondition errorCondition = null;
+
+ @Column(name = "error_condition_exp", length = 512)
+ private String errorConditionExp = null;
+
+ @Column(name = "error_action")
+ private RolloutGroupErrorAction errorAction = null;
+
+ @Column(name = "error_action_exp", length = 512)
+ private String errorActionExp = null;
+
+ @Column(name = "total_targets")
+ private long totalTargets;
+
+ @Transient
+ private transient TotalTargetCountStatus totalTargetCountStatus;
+
+ public Rollout getRollout() {
+ return rollout;
+ }
+
+ public void setRollout(final Rollout rollout) {
+ this.rollout = rollout;
+ }
+
+ public RolloutGroupStatus getStatus() {
+ return status;
+ }
+
+ public void setStatus(final RolloutGroupStatus status) {
+ this.status = status;
+ }
+
+ public List getRolloutTargetGroup() {
+ return rolloutTargetGroup;
+ }
+
+ public RolloutGroup getParent() {
+ return parent;
+ }
+
+ public void setParent(final RolloutGroup parent) {
+ this.parent = parent;
+ }
+
+ public RolloutGroupSuccessCondition getSuccessCondition() {
+ return successCondition;
+ }
+
+ public void setSuccessCondition(final RolloutGroupSuccessCondition finishCondition) {
+ this.successCondition = finishCondition;
+ }
+
+ public String getSuccessConditionExp() {
+ return successConditionExp;
+ }
+
+ public void setSuccessConditionExp(final String finishExp) {
+ this.successConditionExp = finishExp;
+ }
+
+ public RolloutGroupErrorCondition getErrorCondition() {
+ return errorCondition;
+ }
+
+ public void setErrorCondition(final RolloutGroupErrorCondition errorCondition) {
+ this.errorCondition = errorCondition;
+ }
+
+ public String getErrorConditionExp() {
+ return errorConditionExp;
+ }
+
+ public void setErrorConditionExp(final String errorExp) {
+ this.errorConditionExp = errorExp;
+ }
+
+ public RolloutGroupErrorAction getErrorAction() {
+ return errorAction;
+ }
+
+ public void setErrorAction(final RolloutGroupErrorAction errorAction) {
+ this.errorAction = errorAction;
+ }
+
+ public String getErrorActionExp() {
+ return errorActionExp;
+ }
+
+ public void setErrorActionExp(final String errorActionExp) {
+ this.errorActionExp = errorActionExp;
+ }
+
+ public RolloutGroupSuccessAction getSuccessAction() {
+ return successAction;
+ }
+
+ public String getSuccessActionExp() {
+ return successActionExp;
+ }
+
+ public long getTotalTargets() {
+ return totalTargets;
+ }
+
+ public void setTotalTargets(final long totalTargets) {
+ this.totalTargets = totalTargets;
+ }
+
+ public void setSuccessAction(final RolloutGroupSuccessAction successAction) {
+ this.successAction = successAction;
+ }
+
+ public void setSuccessActionExp(final String successActionExp) {
+ this.successActionExp = successActionExp;
+ }
+
+ /**
+ * @return the totalTargetCountStatus
+ */
+ public TotalTargetCountStatus getTotalTargetCountStatus() {
+ if (totalTargetCountStatus == null) {
+ this.totalTargetCountStatus = new TotalTargetCountStatus(totalTargets);
+ }
+ return totalTargetCountStatus;
+ }
+
+ /**
+ * @param totalTargetCountStatus
+ * the totalTargetCountStatus to set
+ */
+ public void setTotalTargetCountStatus(final TotalTargetCountStatus totalTargetCountStatus) {
+ this.totalTargetCountStatus = totalTargetCountStatus;
+ }
+
+ @Override
+ public String toString() {
+ return "RolloutGroup [rollout=" + rollout + ", status=" + status + ", rolloutTargetGroup=" + rolloutTargetGroup
+ + ", parent=" + parent + ", finishCondition=" + successCondition + ", finishExp=" + successConditionExp
+ + ", errorCondition=" + errorCondition + ", errorExp=" + errorConditionExp + ", getName()=" + getName()
+ + ", getId()=" + getId() + "]";
+ }
+
+ /**
+ *
+ * @author Michael Hirsch
+ *
+ */
+ public enum RolloutGroupStatus {
+
+ /**
+ * Ready to start the group.
+ */
+ READY,
+
+ /**
+ * Group is scheduled and started sometime, e.g. trigger of group
+ * before.
+ */
+ SCHEDULED,
+
+ /**
+ * Group is finished.
+ */
+ FINISHED,
+
+ /**
+ * Group is finished and has errors.
+ */
+ ERROR,
+
+ /**
+ * Group is running.
+ */
+ RUNNING;
+ }
+
+ /**
+ * The condition to evaluate if an group is success state.
+ */
+ public enum RolloutGroupSuccessCondition {
+ THRESHOLD("thresholdRolloutGroupSuccessCondition");
+
+ private final String beanName;
+
+ private RolloutGroupSuccessCondition(final String beanName) {
+ this.beanName = beanName;
+ }
+
+ /**
+ * @return the beanName
+ */
+ public String getBeanName() {
+ return beanName;
+ }
+ }
+
+ /**
+ * The condition to evaluate if an group is in error state.
+ */
+ public enum RolloutGroupErrorCondition {
+ THRESHOLD("thresholdRolloutGroupErrorCondition");
+
+ private final String beanName;
+
+ private RolloutGroupErrorCondition(final String beanName) {
+ this.beanName = beanName;
+ }
+
+ /**
+ * @return the beanName
+ */
+ public String getBeanName() {
+ return beanName;
+ }
+ }
+
+ /**
+ * The actions executed when the {@link RolloutGroup#errorCondition} is hit.
+ */
+ public enum RolloutGroupErrorAction {
+ PAUSE("pauseRolloutGroupAction");
+
+ private final String beanName;
+
+ private RolloutGroupErrorAction(final String beanName) {
+ this.beanName = beanName;
+ }
+
+ /**
+ * @return the beanName
+ */
+ public String getBeanName() {
+ return beanName;
+ }
+ }
+
+ /**
+ * The actions executed when the {@link RolloutGroup#successCondition} is
+ * hit.
+ */
+ public enum RolloutGroupSuccessAction {
+ NEXTGROUP("startNextRolloutGroupAction");
+
+ private final String beanName;
+
+ private RolloutGroupSuccessAction(final String beanName) {
+ this.beanName = beanName;
+ }
+
+ /**
+ * @return the beanName
+ */
+ public String getBeanName() {
+ return beanName;
+ }
+ }
+
+ /**
+ * Object which holds all {@link RolloutGroup} conditions together which can
+ * easily built.
+ */
+ public static class RolloutGroupConditions {
+ private RolloutGroupSuccessCondition successCondition = null;
+ private String successConditionExp = null;
+ private RolloutGroupSuccessAction successAction = null;
+ private String successActionExp = null;
+ private RolloutGroupErrorCondition errorCondition = null;
+ private String errorConditionExp = null;
+ private RolloutGroupErrorAction errorAction = null;
+ private String errorActionExp = null;
+
+ public RolloutGroupSuccessCondition getSuccessCondition() {
+ return successCondition;
+ }
+
+ public void setSuccessCondition(final RolloutGroupSuccessCondition finishCondition) {
+ this.successCondition = finishCondition;
+ }
+
+ public String getSuccessConditionExp() {
+ return successConditionExp;
+ }
+
+ public void setSuccessConditionExp(final String finishConditionExp) {
+ this.successConditionExp = finishConditionExp;
+ }
+
+ public RolloutGroupSuccessAction getSuccessAction() {
+ return successAction;
+ }
+
+ public void setSuccessAction(final RolloutGroupSuccessAction successAction) {
+ this.successAction = successAction;
+ }
+
+ public String getSuccessActionExp() {
+ return successActionExp;
+ }
+
+ public void setSuccessActionExp(final String successActionExp) {
+ this.successActionExp = successActionExp;
+ }
+
+ public RolloutGroupErrorCondition getErrorCondition() {
+ return errorCondition;
+ }
+
+ public void setErrorCondition(final RolloutGroupErrorCondition errorCondition) {
+ this.errorCondition = errorCondition;
+ }
+
+ public String getErrorConditionExp() {
+ return errorConditionExp;
+ }
+
+ public void setErrorConditionExp(final String errorConditionExp) {
+ this.errorConditionExp = errorConditionExp;
+ }
+
+ public RolloutGroupErrorAction getErrorAction() {
+ return errorAction;
+ }
+
+ public void setErrorAction(final RolloutGroupErrorAction errorAction) {
+ this.errorAction = errorAction;
+ }
+
+ public String getErrorActionExp() {
+ return errorActionExp;
+ }
+
+ public void setErrorActionExp(final String errorActionExp) {
+ this.errorActionExp = errorActionExp;
+ }
+ }
+
+ /**
+ * Builder to build easily the {@link RolloutGroupConditions}.
+ *
+ */
+ public static class RolloutGroupConditionBuilder {
+ private final RolloutGroupConditions conditions = new RolloutGroupConditions();
+
+ public RolloutGroupConditions build() {
+ return conditions;
+ }
+
+ /**
+ * Sets the finish condition and expression on the builder.
+ *
+ * @param condition
+ * the finish condition
+ * @param expression
+ * the finish expression
+ * @return the builder itself
+ */
+ public RolloutGroupConditionBuilder successCondition(final RolloutGroupSuccessCondition condition,
+ final String expression) {
+ conditions.setSuccessCondition(condition);
+ conditions.setSuccessConditionExp(expression);
+ return this;
+ }
+
+ /**
+ * Sets the success action and expression on the builder.
+ *
+ * @param action
+ * the success action
+ * @param expression
+ * the error expression
+ * @return the builder itself
+ */
+ public RolloutGroupConditionBuilder successAction(final RolloutGroupSuccessAction action,
+ final String expression) {
+ conditions.setSuccessAction(action);
+ conditions.setSuccessActionExp(expression);
+ return this;
+ }
+
+ /**
+ * Sets the error condition and expression on the builder.
+ *
+ * @param condition
+ * the error condition
+ * @param expression
+ * the error expression
+ * @return the builder itself
+ */
+ public RolloutGroupConditionBuilder errorCondition(final RolloutGroupErrorCondition condition,
+ final String expression) {
+ conditions.setErrorCondition(condition);
+ conditions.setErrorConditionExp(expression);
+ return this;
+ }
+
+ /**
+ * Sets the error action and expression on the builder.
+ *
+ * @param action
+ * the error action
+ * @param expression
+ * the error expression
+ * @return the builder itself
+ */
+ public RolloutGroupConditionBuilder errorAction(final RolloutGroupErrorAction action, final String expression) {
+ conditions.setErrorAction(action);
+ conditions.setErrorActionExp(expression);
+ return this;
+ }
+ }
+
+}
diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/RolloutTargetGroup.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/RolloutTargetGroup.java
new file mode 100644
index 000000000..a185f481f
--- /dev/null
+++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/RolloutTargetGroup.java
@@ -0,0 +1,65 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.repository.model;
+
+import java.util.List;
+
+import javax.persistence.CascadeType;
+import javax.persistence.ConstraintMode;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.ForeignKey;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.JoinColumn;
+import javax.persistence.JoinColumns;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+
+/**
+ * @author Michael Hirsch
+ *
+ */
+@IdClass(RolloutTargetGroupId.class)
+@Entity
+@Table(name = "sp_rollouttargetgroup")
+public class RolloutTargetGroup {
+
+ @Id
+ @ManyToOne(targetEntity = RolloutGroup.class, fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST })
+ @JoinColumn(name = "rolloutGroup_Id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rollouttargetgroup_group"))
+ private RolloutGroup rolloutGroup;
+
+ @Id
+ @ManyToOne(targetEntity = Target.class, fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST })
+ @JoinColumn(name = "target_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rollouttargetgroup_target"))
+ private Target target;
+
+ @OneToMany(targetEntity = Action.class, fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST })
+ @JoinColumns(value = { @JoinColumn(name = "rolloutgroup", referencedColumnName = "rolloutGroup_Id"),
+ @JoinColumn(name = "target", referencedColumnName = "target_id") })
+ private List actions;
+
+ /**
+ * default constructor for JPA.
+ */
+ public RolloutTargetGroup() {
+ // JPA constructor
+ }
+
+ public RolloutTargetGroup(final RolloutGroup rolloutGroup, final Target target) {
+ this.rolloutGroup = rolloutGroup;
+ this.target = target;
+ }
+
+ public RolloutTargetGroupId getId() {
+ return new RolloutTargetGroupId(rolloutGroup, target);
+ }
+}
diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/RolloutTargetGroupId.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/RolloutTargetGroupId.java
new file mode 100644
index 000000000..41850424b
--- /dev/null
+++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/RolloutTargetGroupId.java
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.repository.model;
+
+import java.io.Serializable;
+
+/**
+ * Combined unique key of the table {@link RolloutTargetGroup}.
+ *
+ * @author Michael Hirsch
+ *
+ */
+public class RolloutTargetGroupId implements Serializable {
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ private Long rolloutGroup;
+ private Long target;
+
+ /**
+ * default constructor necessary for JPA.
+ */
+ public RolloutTargetGroupId() {
+ // default constructor necessary for JPA, empty.
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param rolloutGroup
+ * the rollout group for this key
+ * @param target
+ * the target for this key
+ */
+ public RolloutTargetGroupId(final RolloutGroup rolloutGroup, final Target target) {
+ this.rolloutGroup = rolloutGroup.getId();
+ this.target = target.getId();
+ }
+
+ public Long getRolloutGroup() {
+ return rolloutGroup;
+ }
+
+ public Long getTarget() {
+ return target;
+ }
+}
\ No newline at end of file
diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/SoftwareModuleMetadata.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/SoftwareModuleMetadata.java
index d4ddba3f3..dfc1ecfa7 100644
--- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/SoftwareModuleMetadata.java
+++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/SoftwareModuleMetadata.java
@@ -47,7 +47,7 @@ public class SoftwareModuleMetadata implements Serializable {
@Id
@ManyToOne(targetEntity = SoftwareModule.class, fetch = FetchType.LAZY)
- @JoinColumn(name = "sw_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_metadata_sw") )
+ @JoinColumn(name = "sw_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_metadata_sw"))
private SoftwareModule softwareModule;
public SoftwareModuleMetadata() {
diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Target.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Target.java
index 6f37b2d8b..6de84ccaa 100644
--- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Target.java
+++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Target.java
@@ -106,6 +106,11 @@ public class Target extends NamedEntity implements Persistable {
@Column(name = "sec_token", insertable = true, updatable = true, nullable = false, length = 128)
private String securityToken = null;
+ @CascadeOnDelete
+ @OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.REMOVE, CascadeType.PERSIST })
+ @JoinColumn(name = "target_Id", insertable = false, updatable = false)
+ private final List rolloutTargetGroup = new ArrayList<>();
+
/**
* Constructor.
*
diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TargetWithActionStatus.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TargetWithActionStatus.java
new file mode 100644
index 000000000..a75141658
--- /dev/null
+++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TargetWithActionStatus.java
@@ -0,0 +1,63 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.repository.model;
+
+import org.eclipse.hawkbit.repository.model.Action.Status;
+
+/**
+ *
+ * Rollout - Target with action status.
+ *
+ */
+public class TargetWithActionStatus {
+
+ private Target target;
+
+ private Status status = null;;
+
+ public TargetWithActionStatus(final Target target) {
+ this.target = target;
+ }
+
+ public TargetWithActionStatus(final Target target, final Status status) {
+ this.status = status;
+ this.target = target;
+ }
+
+ /**
+ * @return the target
+ */
+ public Target getTarget() {
+ return target;
+ }
+
+ /**
+ * @return the status
+ */
+ public Status getStatus() {
+ return status;
+ }
+
+ /**
+ * @param target
+ * the target to set
+ */
+ public void setTarget(final Target target) {
+ this.target = target;
+ }
+
+ /**
+ * @param status
+ * the status to set
+ */
+ public void setStatus(final Status status) {
+ this.status = status;
+ }
+
+}
diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TotalTargetCountActionStatus.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TotalTargetCountActionStatus.java
new file mode 100644
index 000000000..9fabef663
--- /dev/null
+++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TotalTargetCountActionStatus.java
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.repository.model;
+
+/**
+ * Represents rollout or rollout group statuses and count of targets in each
+ * status.
+ *
+ */
+public class TotalTargetCountActionStatus {
+
+ private final Action.Status status;
+ private final Long count;
+ private Long id;
+
+ public TotalTargetCountActionStatus(final Long id, final Action.Status status, final Long count) {
+ this.status = status;
+ this.count = count;
+ this.id = id;
+ }
+
+ public TotalTargetCountActionStatus(final Action.Status status, final Long count) {
+ this.status = status;
+ this.count = count;
+ }
+
+ /**
+ * @return the id
+ */
+ public Long getId() {
+ return id;
+ }
+
+ /**
+ * @return the status
+ */
+ public Action.Status getStatus() {
+ return status;
+ }
+
+ /**
+ * @return the count
+ */
+ public Long getCount() {
+ return count;
+ }
+}
diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TotalTargetCountStatus.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TotalTargetCountStatus.java
new file mode 100644
index 000000000..f403ecc3d
--- /dev/null
+++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TotalTargetCountStatus.java
@@ -0,0 +1,125 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.repository.model;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ *
+ * Store all states with the target count of a rollout or rolloutgroup.
+ *
+ */
+public class TotalTargetCountStatus {
+
+ /**
+ * Status of the total target counts.
+ */
+ public enum Status {
+ SCHEDULED, RUNNING, ERROR, FINISHED, CANCELLED, NOTSTARTED
+ }
+
+ private final Map statusTotalCountMap = new HashMap<>();
+ private final Long totalTargetCount;
+
+ /**
+ * Create a new states map with the target count for each state.
+ *
+ * @param targetCountActionStatus
+ * the action state map
+ * @param totalTargets
+ * the total target count
+ */
+ public TotalTargetCountStatus(final List targetCountActionStatus,
+ final Long totalTargetCount) {
+ this.totalTargetCount = totalTargetCount;
+ mapActionStatusToTotalTargetCountStatus(targetCountActionStatus);
+ }
+
+ /**
+ * Create a new states map with the target count for each state.
+ *
+ * @param totalTargetCount
+ * the total target count
+ */
+ public TotalTargetCountStatus(final Long totalTargetCount) {
+ this(Collections.emptyList(), totalTargetCount);
+ }
+
+ /**
+ * The current state mape which the total target count
+ *
+ * @return the statusTotalCountMap the state map
+ */
+ public Map getStatusTotalCountMap() {
+ return statusTotalCountMap;
+ }
+
+ /**
+ * Gets the total target count from a state.
+ *
+ * @param status
+ * the state key
+ * @return the current target count cannot be
+ */
+ public Long getTotalTargetCountByStatus(final Status status) {
+ final Long count = statusTotalCountMap.get(status);
+ return count == null ? 0L : count;
+ }
+
+ /**
+ * Populate all target status to a the given map
+ *
+ * @param statusTotalCountMap
+ * the map
+ * @param rolloutStatusCountItems
+ * all target statut with total count
+ * @return some state is populated nothing is happend
+ */
+ private final void mapActionStatusToTotalTargetCountStatus(
+ final List targetCountActionStatus) {
+ if (targetCountActionStatus == null) {
+ statusTotalCountMap.put(TotalTargetCountStatus.Status.NOTSTARTED, totalTargetCount);
+ return;
+ }
+ statusTotalCountMap.put(Status.RUNNING, 0L);
+ Long notStartedTargetCount = totalTargetCount;
+ for (final TotalTargetCountActionStatus item : targetCountActionStatus) {
+ switch (item.getStatus()) {
+ case SCHEDULED:
+ statusTotalCountMap.put(Status.SCHEDULED, item.getCount());
+ break;
+ case ERROR:
+ statusTotalCountMap.put(Status.ERROR, item.getCount());
+ break;
+ case FINISHED:
+ statusTotalCountMap.put(Status.FINISHED, item.getCount());
+ break;
+ case RETRIEVED:
+ case RUNNING:
+ case WARNING:
+ case DOWNLOAD:
+ case CANCELING:
+ final Long runningItemsCount = statusTotalCountMap.get(Status.RUNNING) + item.getCount();
+ statusTotalCountMap.put(Status.RUNNING, runningItemsCount);
+ break;
+ case CANCELED:
+ statusTotalCountMap.put(Status.CANCELLED, item.getCount());
+ break;
+ default:
+ throw new IllegalArgumentException("State " + item.getStatus() + "is not valid");
+ }
+ notStartedTargetCount -= item.getCount();
+ }
+ statusTotalCountMap.put(TotalTargetCountStatus.Status.NOTSTARTED, notStartedTargetCount);
+ }
+
+}
diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/helper/AfterTransactionCommitExecutorHolder.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/helper/AfterTransactionCommitExecutorHolder.java
new file mode 100644
index 000000000..1282bc99d
--- /dev/null
+++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/helper/AfterTransactionCommitExecutorHolder.java
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.repository.model.helper;
+
+import org.eclipse.hawkbit.eventbus.EntityPropertyChangeListener;
+import org.eclipse.hawkbit.executor.AfterTransactionCommitExecutor;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * A singleton bean which holds the {@link AfterTransactionCommitExecutor} to
+ * have to the cache manager in beans not instantiated by spring e.g. JPA
+ * entities or {@link EntityPropertyChangeListener} which cannot be autowired.
+ *
+ */
+public final class AfterTransactionCommitExecutorHolder {
+
+ private static final AfterTransactionCommitExecutorHolder SINGLETON = new AfterTransactionCommitExecutorHolder();
+
+ @Autowired
+ private AfterTransactionCommitExecutor afterCommit;
+
+ private AfterTransactionCommitExecutorHolder() {
+
+ }
+
+ /**
+ * @return the cache manager holder singleton instance
+ */
+ public static AfterTransactionCommitExecutorHolder getInstance() {
+ return SINGLETON;
+ }
+
+ /**
+ * @return the afterCommit
+ */
+ public AfterTransactionCommitExecutor getAfterCommit() {
+ return afterCommit;
+ }
+
+ /**
+ * @param afterCommit
+ * the afterCommit to set
+ */
+ public void setAfterCommit(final AfterTransactionCommitExecutor afterCommit) {
+ this.afterCommit = afterCommit;
+ }
+
+}
diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/helper/EventBusHolder.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/helper/EventBusHolder.java
new file mode 100644
index 000000000..8111a1c28
--- /dev/null
+++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/helper/EventBusHolder.java
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.repository.model.helper;
+
+import org.eclipse.hawkbit.eventbus.CacheFieldEntityListener;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.google.common.eventbus.EventBus;
+
+/**
+ * A singleton bean which holds the {@link EventBus} to have to the cache
+ * manager in beans not instantiated by spring e.g. JPA entities or
+ * {@link CacheFieldEntityListener} which cannot be autowired.
+ *
+ */
+public final class EventBusHolder {
+
+ private static final EventBusHolder SINGLETON = new EventBusHolder();
+
+ @Autowired
+ private EventBus eventBus;
+
+ private EventBusHolder() {
+
+ }
+
+ /**
+ * @return the cache manager holder singleton instance
+ */
+ public static EventBusHolder getInstance() {
+ return SINGLETON;
+ }
+
+ /**
+ * @return the eventBus
+ */
+ public EventBus getEventBus() {
+ return eventBus;
+ }
+
+ /**
+ * @param eventBus
+ * the eventBus to set
+ */
+ public void setEventBus(final EventBus eventBus) {
+ this.eventBus = eventBus;
+ }
+
+}
diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/rsql/RSQLUtility.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/rsql/RSQLUtility.java
index 5dd4cfd1d..69ee58429 100644
--- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/rsql/RSQLUtility.java
+++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/rsql/RSQLUtility.java
@@ -104,6 +104,30 @@ public final class RSQLUtility {
return new RSQLSpecification<>(rsql, fieldNameProvider);
}
+ /**
+ * Validate the given rsql string regarding existence and correct syntax.
+ *
+ * @param rsql
+ * the rsql string to get validated
+ *
+ */
+ public static void isValid(final String rsql) {
+ parseRsql(rsql);
+ }
+
+ private static Node parseRsql(final String rsql) {
+ try {
+ LOGGER.debug("parsing rsql string {}", rsql);
+ final Set operators = RSQLOperators.defaultOperators();
+ operators.add(new ComparisonOperator("=li=", false));
+ return new RSQLParser(operators).parse(rsql);
+ } catch (final IllegalArgumentException e) {
+ throw new RSQLParameterSyntaxException("rsql filter must not be null", e);
+ } catch (final RSQLParserException e) {
+ throw new RSQLParameterSyntaxException(e);
+ }
+ }
+
private static final class RSQLSpecification & FieldNameProvider, T> implements Specification {
private final String rsql;
@@ -125,15 +149,7 @@ public final class RSQLUtility {
@Override
public Predicate toPredicate(final Root root, final CriteriaQuery> query, final CriteriaBuilder cb) {
- final Node rootNode;
- try {
- LOGGER.debug("parsing rsql string {}", rsql);
- final Set operators = RSQLOperators.defaultOperators();
- operators.add(new ComparisonOperator("=li=", false));
- rootNode = new RSQLParser(operators).parse(rsql);
- } catch (final RSQLParserException e) {
- throw new RSQLParameterSyntaxException(e);
- }
+ final Node rootNode = parseRsql(rsql);
final JpqQueryRSQLVisitor jpqQueryRSQLVisitor = new JpqQueryRSQLVisitor<>(root, cb, enumType);
final List accept = rootNode., String> accept(jpqQueryRSQLVisitor);
diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/PauseRolloutGroupAction.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/PauseRolloutGroupAction.java
new file mode 100644
index 000000000..af335cd0d
--- /dev/null
+++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/PauseRolloutGroupAction.java
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.rollout.condition;
+
+import java.util.concurrent.Callable;
+
+import org.eclipse.hawkbit.repository.RolloutGroupRepository;
+import org.eclipse.hawkbit.repository.RolloutManagement;
+import org.eclipse.hawkbit.repository.model.Rollout;
+import org.eclipse.hawkbit.repository.model.RolloutGroup;
+import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus;
+import org.eclipse.hawkbit.security.SystemSecurityContext;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * Error action evaluator which pauses the whole {@link Rollout} and sets the
+ * current {@link RolloutGroup} to error.
+ */
+@Component("pauseRolloutGroupAction")
+public class PauseRolloutGroupAction implements RolloutGroupActionEvaluator {
+
+ @Autowired
+ private RolloutManagement rolloutManagement;
+
+ @Autowired
+ private RolloutGroupRepository rolloutGroupRepository;
+
+ @Autowired
+ private SystemSecurityContext systemSecurityContext;
+
+ @Override
+ public boolean verifyExpression(final String expression) {
+ return true;
+ }
+
+ @Override
+ public void eval(final Rollout rollout, final RolloutGroup rolloutGroup, final String expression) {
+ systemSecurityContext.runAsSystem(new Callable() {
+ @Override
+ public Void call() throws Exception {
+ rolloutGroup.setStatus(RolloutGroupStatus.ERROR);
+ rolloutGroupRepository.save(rolloutGroup);
+ rolloutManagement.pauseRollout(rollout);
+ return null;
+ }
+ });
+ }
+}
diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/RolloutGroupActionEvaluator.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/RolloutGroupActionEvaluator.java
new file mode 100644
index 000000000..1e2c98707
--- /dev/null
+++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/RolloutGroupActionEvaluator.java
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.rollout.condition;
+
+import org.eclipse.hawkbit.repository.model.Rollout;
+import org.eclipse.hawkbit.repository.model.RolloutGroup;
+
+/**
+ *
+ */
+public interface RolloutGroupActionEvaluator {
+
+ boolean verifyExpression(final String expression);
+
+ void eval(Rollout rollout, RolloutGroup rolloutGroup, final String expression);
+}
diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/RolloutGroupConditionEvaluator.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/RolloutGroupConditionEvaluator.java
new file mode 100644
index 000000000..73580fd19
--- /dev/null
+++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/RolloutGroupConditionEvaluator.java
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.rollout.condition;
+
+import org.eclipse.hawkbit.repository.model.Rollout;
+import org.eclipse.hawkbit.repository.model.RolloutGroup;
+
+/**
+ *
+ */
+public interface RolloutGroupConditionEvaluator {
+
+ boolean verifyExpression(final String expression);
+
+ boolean eval(Rollout rollout, RolloutGroup rolloutGroup, final String expression);
+}
diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/StartNextGroupRolloutGroupSuccessAction.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/StartNextGroupRolloutGroupSuccessAction.java
new file mode 100644
index 000000000..21c499d31
--- /dev/null
+++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/StartNextGroupRolloutGroupSuccessAction.java
@@ -0,0 +1,90 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.rollout.condition;
+
+import java.util.List;
+
+import org.eclipse.hawkbit.repository.DeploymentManagement;
+import org.eclipse.hawkbit.repository.RolloutGroupRepository;
+import org.eclipse.hawkbit.repository.model.Action;
+import org.eclipse.hawkbit.repository.model.Rollout;
+import org.eclipse.hawkbit.repository.model.RolloutGroup;
+import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus;
+import org.eclipse.hawkbit.security.SystemSecurityContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * Success action which starts the next following {@link RolloutGroup}.
+ */
+@Component("startNextRolloutGroupAction")
+public class StartNextGroupRolloutGroupSuccessAction implements RolloutGroupActionEvaluator {
+
+ private static final Logger logger = LoggerFactory.getLogger(StartNextGroupRolloutGroupSuccessAction.class);
+
+ @Autowired
+ private RolloutGroupRepository rolloutGroupRepository;
+
+ @Autowired
+ private DeploymentManagement deploymentManagement;
+
+ @Autowired
+ private SystemSecurityContext systemSecurityContext;
+
+ @Override
+ public boolean verifyExpression(final String expression) {
+ return true;
+ }
+
+ @Override
+ public void eval(final Rollout rollout, final RolloutGroup rolloutGroup, final String expression) {
+ systemSecurityContext.runAsSystem(() -> {
+ startNextGroup(rollout, rolloutGroup);
+ return null;
+ });
+ }
+
+ private void startNextGroup(final Rollout rollout, final RolloutGroup rolloutGroup) {
+ // retrieve all actions accroding to the parent group of the finished
+ // rolloutGroup, so retrieve all child-group actions which need to be
+ // started.
+ final List rolloutGroupActions = deploymentManagement.findActionsByRolloutGroupParentAndStatus(rollout,
+ rolloutGroup, Action.Status.SCHEDULED);
+ logger.debug("{} Next actions to start for rollout {} and parent group {}", rolloutGroupActions.size(), rollout,
+ rolloutGroup);
+ rolloutGroupActions.forEach(action -> deploymentManagement.startScheduledAction(action));
+ if (!rolloutGroupActions.isEmpty()) {
+ // get all next scheduled groups based on the found actions and set
+ // them in state running
+ rolloutGroupActions.forEach(action -> {
+ final RolloutGroup nextGroup = action.getRolloutGroup();
+ logger.debug("Rolloutgroup {} is now running", nextGroup);
+ nextGroup.setStatus(RolloutGroupStatus.RUNNING);
+ rolloutGroupRepository.save(nextGroup);
+ });
+ } else {
+ logger.info("No actions to start for next rolloutgroup of parent {}", rolloutGroup);
+ // nothing for next group, just finish the group, this can happen
+ // e.g. if targets has been deleted after the group has been
+ // scheduled. If the group is empty now, we just finish the group if
+ // there are not actions available for this group.
+ final List findByRolloutGroupParent = rolloutGroupRepository
+ .findByParentAndStatus(rolloutGroup, RolloutGroupStatus.SCHEDULED);
+ findByRolloutGroupParent.forEach(nextGroup -> {
+ logger.debug("Rolloutgroup {} is finished, starting next group", nextGroup);
+ nextGroup.setStatus(RolloutGroupStatus.FINISHED);
+ rolloutGroupRepository.save(nextGroup);
+ // find the next group to set in running state
+ startNextGroup(rollout, nextGroup);
+ });
+ }
+ }
+}
diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/ThresholdRolloutGroupErrorCondition.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/ThresholdRolloutGroupErrorCondition.java
new file mode 100644
index 000000000..2c2bc361f
--- /dev/null
+++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/ThresholdRolloutGroupErrorCondition.java
@@ -0,0 +1,68 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.rollout.condition;
+
+import org.eclipse.hawkbit.repository.ActionRepository;
+import org.eclipse.hawkbit.repository.model.Action;
+import org.eclipse.hawkbit.repository.model.Rollout;
+import org.eclipse.hawkbit.repository.model.RolloutGroup;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ *
+ */
+@Component("thresholdRolloutGroupErrorCondition")
+public class ThresholdRolloutGroupErrorCondition implements RolloutGroupConditionEvaluator {
+
+ private static Logger logger = LoggerFactory.getLogger(ThresholdRolloutGroupErrorCondition.class);
+
+ @Autowired
+ private ActionRepository actionRepository;
+
+ @Override
+ public boolean verifyExpression(final String expression) {
+ // percentage value between 0 and 100
+ try {
+ final Integer value = Integer.valueOf(expression);
+ if (value >= 0 || value <= 100) {
+ return true;
+ }
+ return true;
+ } catch (final RuntimeException e) {
+
+ }
+ return false;
+ }
+
+ @Override
+ public boolean eval(final Rollout rollout, final RolloutGroup rolloutGroup, final String expression) {
+ final Long totalGroup = actionRepository.countByRolloutAndRolloutGroup(rollout, rolloutGroup);
+ final Long error = actionRepository.countByRolloutIdAndRolloutGroupIdAndStatus(rollout.getId(),
+ rolloutGroup.getId(), Action.Status.ERROR);
+ try {
+ final Integer threshold = Integer.valueOf(expression);
+
+ if (totalGroup == 0) {
+ // in case e.g. targets has been deleted we don't have any
+ // actions left for this group, so the group is finished
+ return false;
+ }
+
+ // calculate threshold
+ return ((float) error / (float) totalGroup) > ((float) threshold / 100F);
+ } catch (final NumberFormatException e) {
+ logger.error("Cannot evaluate condition expression " + expression, e);
+ return false;
+ }
+ }
+
+}
diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/ThresholdRolloutGroupSuccessCondition.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/ThresholdRolloutGroupSuccessCondition.java
new file mode 100644
index 000000000..81b5c7286
--- /dev/null
+++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/ThresholdRolloutGroupSuccessCondition.java
@@ -0,0 +1,58 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.rollout.condition;
+
+import org.eclipse.hawkbit.repository.ActionRepository;
+import org.eclipse.hawkbit.repository.model.Action;
+import org.eclipse.hawkbit.repository.model.Rollout;
+import org.eclipse.hawkbit.repository.model.RolloutGroup;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ *
+ */
+@Component("thresholdRolloutGroupSuccessCondition")
+public class ThresholdRolloutGroupSuccessCondition implements RolloutGroupConditionEvaluator {
+
+ @Autowired
+ private ActionRepository actionRepository;
+
+ @Override
+ public boolean verifyExpression(final String expression) {
+ // percentage value between 0 and 100
+ try {
+ final Integer value = Integer.valueOf(expression);
+ if (value >= 0 || value <= 100) {
+ return true;
+ }
+ return true;
+ } catch (final RuntimeException e) {
+
+ }
+ return false;
+ }
+
+ @Override
+ public boolean eval(final Rollout rollout, final RolloutGroup rolloutGroup, final String expression) {
+ final Long totalGroup = rolloutGroup.getTotalTargets();
+ final Long finished = actionRepository.countByRolloutIdAndRolloutGroupIdAndStatus(rollout.getId(),
+ rolloutGroup.getId(), Action.Status.FINISHED);
+ final Integer threshold = Integer.valueOf(expression);
+
+ if (totalGroup == 0) {
+ // in case e.g. targets has been deleted we don't have any actions
+ // left for this group, so the group is finished
+ return true;
+ }
+ // calculate threshold
+ return ((float) finished / (float) totalGroup) >= ((float) threshold / 100F);
+ }
+
+}
diff --git a/hawkbit-repository/src/main/resources/db/migration/H2/V1_6_0__rollout_management___H2.sql b/hawkbit-repository/src/main/resources/db/migration/H2/V1_6_0__rollout_management___H2.sql
new file mode 100644
index 000000000..490400161
--- /dev/null
+++ b/hawkbit-repository/src/main/resources/db/migration/H2/V1_6_0__rollout_management___H2.sql
@@ -0,0 +1,103 @@
+ create table sp_rolloutgroup (
+ id bigint generated by default as identity,
+ created_at bigint,
+ created_by varchar(40),
+ last_modified_at bigint,
+ last_modified_by varchar(40),
+ optlock_revision bigint,
+ tenant varchar(40) not null,
+ description varchar(512),
+ name varchar(64) not null,
+ error_condition integer,
+ error_condition_exp varchar(512),
+ error_action integer,
+ error_action_exp varchar(512),
+ success_condition integer not null,
+ success_condition_exp varchar(512) not null,
+ success_action integer not null,
+ success_action_exp varchar(512),
+ status integer,
+ parent_id bigint,
+ rollout bigint,
+ total_targets bigint,
+ primary key (id)
+ );
+
+ create table sp_rollout (
+ id bigint generated by default as identity,
+ created_at bigint,
+ created_by varchar(40),
+ last_modified_at bigint,
+ last_modified_by varchar(40),
+ optlock_revision bigint,
+ tenant varchar(40) not null,
+ description varchar(512),
+ name varchar(64) not null,
+ last_check bigint,
+ group_theshold float,
+ status integer,
+ distribution_set bigint,
+ target_filter varchar(1024),
+ action_type varchar(255) not null,
+ forced_time bigint,
+ total_targets bigint,
+ primary key (id)
+ );
+
+ create table sp_rollouttargetgroup (
+ target_Id bigint not null,
+ rolloutGroup_Id bigint not null,
+ primary key (rolloutGroup_Id, target_Id)
+ );
+
+ create index sp_idx_rollout_01 on sp_rollout (tenant, name);
+
+ create index sp_idx_rolloutgroup_01 on sp_rolloutgroup (tenant, name);
+
+ ALTER TABLE sp_action ADD COLUMN rollout bigint;
+ ALTER TABLE sp_action ADD COLUMN rolloutgroup bigint;
+
+ alter table sp_rollout
+ add constraint uk_rollout unique (name, tenant);
+
+ alter table sp_rolloutgroup
+ add constraint uk_rolloutgroup unique (name, rollout, tenant);
+
+ alter table sp_action
+ add constraint fk_action_rollout
+ foreign key (rollout)
+ references sp_rollout;
+
+ alter table sp_action
+ add constraint fk_action_rolloutgroup
+ foreign key (rolloutgroup)
+ references sp_rolloutgroup;
+
+ alter table sp_rollout
+ add constraint fk_rollout_ds
+ foreign key (distribution_set)
+ references sp_distribution_set;
+
+ alter table sp_rolloutgroup
+ add constraint fk_rolloutgroup_rollout
+ foreign key (rollout)
+ references sp_rollout
+ on delete cascade;
+
+ alter table sp_rolloutgroup
+ add constraint fk_rolloutgroup_rolloutgroup
+ foreign key (parent_id)
+ references sp_rolloutgroup
+ on delete cascade;
+
+ alter table sp_rollouttargetgroup
+ add constraint fk_rollouttargetgroup_target
+ foreign key (target_id)
+ references sp_target
+ on delete cascade;
+
+ alter table sp_rollouttargetgroup
+ add constraint fk_rollouttargetgroup_rolloutgroup
+ foreign key (rolloutgroup_id)
+ references sp_rolloutgroup
+ on delete cascade;
\ No newline at end of file
diff --git a/hawkbit-repository/src/main/resources/db/migration/MYSQL/V1_6_0__rollout_management___MYSQL.sql b/hawkbit-repository/src/main/resources/db/migration/MYSQL/V1_6_0__rollout_management___MYSQL.sql
new file mode 100644
index 000000000..4cd4c7ea4
--- /dev/null
+++ b/hawkbit-repository/src/main/resources/db/migration/MYSQL/V1_6_0__rollout_management___MYSQL.sql
@@ -0,0 +1,103 @@
+ create table sp_rolloutgroup (
+ id bigint not null auto_increment,
+ created_at bigint,
+ created_by varchar(40),
+ last_modified_at bigint,
+ last_modified_by varchar(40),
+ optlock_revision bigint,
+ tenant varchar(40) not null,
+ description varchar(512),
+ name varchar(64) not null,
+ error_condition integer,
+ error_condition_exp varchar(512),
+ error_action integer,
+ error_action_exp varchar(512),
+ success_condition integer not null,
+ success_condition_exp varchar(512) not null,
+ success_action integer not null,
+ success_action_exp varchar(512),
+ status integer,
+ parent_id bigint,
+ rollout bigint,
+ total_targets bigint,
+ primary key (id)
+ );
+
+ create table sp_rollout (
+ id bigint not null auto_increment,
+ created_at bigint,
+ created_by varchar(40),
+ last_modified_at bigint,
+ last_modified_by varchar(40),
+ optlock_revision bigint,
+ tenant varchar(40) not null,
+ description varchar(512),
+ name varchar(64) not null,
+ last_check bigint,
+ group_theshold float,
+ status integer,
+ distribution_set bigint,
+ target_filter varchar(1024),
+ action_type varchar(255) not null,
+ forced_time bigint,
+ total_targets bigint,
+ primary key (id)
+ );
+
+ create table sp_rollouttargetgroup (
+ target_Id bigint not null,
+ rolloutGroup_Id bigint not null,
+ primary key (rolloutGroup_Id, target_Id)
+ );
+
+ create index sp_idx_rollout_01 on sp_rollout (tenant, name);
+
+ create index sp_idx_rolloutgroup_01 on sp_rolloutgroup (tenant, name);
+
+ ALTER TABLE sp_action ADD COLUMN rollout bigint;
+ ALTER TABLE sp_action ADD COLUMN rolloutgroup bigint;
+
+ alter table sp_rollout
+ add constraint uk_rollout unique (name, tenant);
+
+ alter table sp_rolloutgroup
+ add constraint uk_rolloutgroup unique (name, rollout, tenant);
+
+ alter table sp_action
+ add constraint fk_action_rollout
+ foreign key (rollout)
+ references sp_rollout (id);
+
+ alter table sp_action
+ add constraint fk_action_rolloutgroup
+ foreign key (rolloutgroup)
+ references sp_rolloutgroup (id);
+
+ alter table sp_rollout
+ add constraint fk_rollout_ds
+ foreign key (distribution_set)
+ references sp_distribution_set (id);
+
+ alter table sp_rolloutgroup
+ add constraint fk_rolloutgroup_rollout
+ foreign key (rollout)
+ references sp_rollout (id)
+ on delete cascade;
+
+ alter table sp_rolloutgroup
+ add constraint fk_rolloutgroup_rolloutgroup
+ foreign key (parent_id)
+ references sp_rolloutgroup (id)
+ on delete cascade;
+
+ alter table sp_rollouttargetgroup
+ add constraint fk_rollouttargetgroup_target
+ foreign key (target_id)
+ references sp_target (id)
+ on delete cascade;
+
+ alter table sp_rollouttargetgroup
+ add constraint fk_rollouttargetgroup_rolloutgroup
+ foreign key (rolloutgroup_id)
+ references sp_rolloutgroup (id)
+ on delete cascade;
\ No newline at end of file
diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/AbstractIntegrationTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/AbstractIntegrationTest.java
index 94fa8beca..9d5fa2483 100644
--- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/AbstractIntegrationTest.java
+++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/AbstractIntegrationTest.java
@@ -28,6 +28,9 @@ import org.eclipse.hawkbit.repository.DistributionSetTagRepository;
import org.eclipse.hawkbit.repository.DistributionSetTypeRepository;
import org.eclipse.hawkbit.repository.ExternalArtifactRepository;
import org.eclipse.hawkbit.repository.LocalArtifactRepository;
+import org.eclipse.hawkbit.repository.RolloutGroupManagement;
+import org.eclipse.hawkbit.repository.RolloutGroupRepository;
+import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.SoftwareManagement;
import org.eclipse.hawkbit.repository.SoftwareModuleMetadataRepository;
import org.eclipse.hawkbit.repository.SoftwareModuleRepository;
@@ -178,6 +181,15 @@ public abstract class AbstractIntegrationTest implements EnvironmentAware {
@Autowired
protected TenantAwareCacheManager cacheManager;
+ @Autowired
+ protected RolloutManagement rolloutManagement;
+
+ @Autowired
+ protected RolloutGroupManagement rolloutGroupManagement;
+
+ @Autowired
+ protected RolloutGroupRepository rolloutGroupRepository;
+
protected MockMvc mvc;
@Autowired
diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/TestConfiguration.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/TestConfiguration.java
index 7cdca8efd..945e71c75 100644
--- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/TestConfiguration.java
+++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/TestConfiguration.java
@@ -9,10 +9,12 @@
package org.eclipse.hawkbit;
import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
import org.eclipse.hawkbit.cache.CacheConstants;
import org.eclipse.hawkbit.cache.TenancyCacheManager;
import org.eclipse.hawkbit.cache.TenantAwareCacheManager;
+import org.eclipse.hawkbit.repository.model.helper.EventBusHolder;
import org.eclipse.hawkbit.repository.utils.RepositoryDataGenerator;
import org.eclipse.hawkbit.repository.utils.RepositoryDataGenerator.DatabaseCleanupUtil;
import org.eclipse.hawkbit.security.SecurityContextTenantAware;
@@ -30,7 +32,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.data.domain.AuditorAware;
import org.springframework.scheduling.annotation.AsyncConfigurer;
-import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.security.concurrent.DelegatingSecurityContextExecutor;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import com.google.common.eventbus.AsyncEventBus;
@@ -91,9 +93,14 @@ public class TestConfiguration implements AsyncConfigurer {
return new AsyncEventBus(asyncExecutor());
}
+ @Bean
+ public EventBusHolder eventBusHolder() {
+ return EventBusHolder.getInstance();
+ }
+
@Bean
public Executor asyncExecutor() {
- return new ThreadPoolTaskExecutor();
+ return new DelegatingSecurityContextExecutor(Executors.newSingleThreadExecutor());
}
@Bean
diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/cache/CacheKeysTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/cache/CacheKeysTest.java
index b086875c3..6c9314d07 100644
--- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/cache/CacheKeysTest.java
+++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/cache/CacheKeysTest.java
@@ -21,5 +21,4 @@ public class CacheKeysTest {
final String entitySpecificCacheKey = CacheKeys.entitySpecificCacheKey(knownEntityId, knownCacheKey);
assertThat(entitySpecificCacheKey).isEqualTo(knownEntityId + "." + knownCacheKey);
}
-
}
diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/RolloutManagementTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/RolloutManagementTest.java
new file mode 100644
index 000000000..a405e7b7d
--- /dev/null
+++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/RolloutManagementTest.java
@@ -0,0 +1,1024 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.repository;
+
+import static org.fest.assertions.api.Assertions.assertThat;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.hawkbit.AbstractIntegrationTest;
+import org.eclipse.hawkbit.TestDataUtil;
+import org.eclipse.hawkbit.repository.model.Action;
+import org.eclipse.hawkbit.repository.model.Action.Status;
+import org.eclipse.hawkbit.repository.model.ActionStatus;
+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.RolloutGroupConditions;
+import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorAction;
+import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorCondition;
+import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus;
+import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition;
+import org.eclipse.hawkbit.repository.model.SoftwareModule;
+import org.eclipse.hawkbit.repository.model.Target;
+import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
+import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus;
+import org.eclipse.hawkbit.repository.rsql.RSQLUtility;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Description;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Slice;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.domain.Sort.Direction;
+import org.springframework.data.jpa.domain.Specification;
+
+import ru.yandex.qatools.allure.annotations.Features;
+import ru.yandex.qatools.allure.annotations.Stories;
+
+/**
+ * Junit tests for RolloutManagment.
+ *
+ * @author Michael Hirsch
+ *
+ */
+@Features("Component Tests - Repository")
+@Stories("Rollout Management")
+public class RolloutManagementTest extends AbstractIntegrationTest {
+
+ @Autowired
+ private RolloutManagement rolloutManagement;
+
+ @Autowired
+ private RolloutGroupManagement rolloutGroupManagement;
+
+ @Test
+ @Description("Verfiying that the rollout is created correctly, executing the filter and split up the targets in the correct group size.")
+ public void creatingRolloutIsCorrectPersisted() {
+ final int amountTargetsForRollout = 10;
+ final int amountOtherTargets = 15;
+ final int amountGroups = 5;
+ final String successCondition = "50";
+ final String errorCondition = "80";
+ final Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout,
+ amountOtherTargets, amountGroups, successCondition, errorCondition);
+
+ // verify the split of the target and targetGroup
+ final Page rolloutGroups = rolloutGroupManagement
+ .findRolloutGroupsByRolloutId(createdRollout.getId(), pageReq);
+ // we have total of #amountTargetsForRollout in rollouts splitted in
+ // group size #groupSize
+ assertThat(rolloutGroups).hasSize(amountGroups);
+ }
+
+ @Test
+ @Description("Verfiying that when the rollout is started the actions for all targets in the rollout is created and the state of the first group is running as well as the corresponding actions")
+ public void startRolloutSetFirstGroupAndActionsInRunningStateAndOthersInScheduleState() {
+ final int amountTargetsForRollout = 10;
+ final int amountOtherTargets = 15;
+ final int amountGroups = 5;
+ final String successCondition = "50";
+ final String errorCondition = "80";
+ final Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout,
+ amountOtherTargets, amountGroups, successCondition, errorCondition);
+
+ // start the rollout
+ rolloutManagement.startRollout(createdRollout);
+
+ // verify first group is running
+ final RolloutGroup firstGroup = rolloutGroupManagement.findRolloutGroupsByRolloutId(createdRollout.getId(),
+ new OffsetBasedPageRequest(0, 1, new Sort(Direction.ASC, "id"))).getContent().get(0);
+ assertThat(firstGroup.getStatus()).isEqualTo(RolloutGroupStatus.RUNNING);
+
+ // verify other groups are scheduled
+ final List scheduledGroups = rolloutGroupManagement.findRolloutGroupsByRolloutId(
+ createdRollout.getId(), new OffsetBasedPageRequest(1, 100, new Sort(Direction.ASC, "id"))).getContent();
+ scheduledGroups.forEach(group -> assertThat(group.getStatus()).isEqualTo(RolloutGroupStatus.SCHEDULED)
+ .as("group which should be in scheduled state is in " + group.getStatus() + " state"));
+ // verify that the first group actions has been started and are in state
+ // running
+ final List runningActions = deploymentManagement.findActionsByRolloutAndStatus(createdRollout,
+ Status.RUNNING);
+ assertThat(runningActions).hasSize(amountTargetsForRollout / amountGroups);
+ // the rest targets are only scheduled
+ assertThat(deploymentManagement.findActionsByRolloutAndStatus(createdRollout, Status.SCHEDULED))
+ .hasSize(amountTargetsForRollout - (amountTargetsForRollout / amountGroups));
+ }
+
+ @Test
+ @Description("Verfiying that a finish condition of a group is hit the next group of the rollout is also started")
+ public void checkRunningRolloutsDoesNotStartNextGroupIfFinishConditionIsNotHit() {
+ final int amountTargetsForRollout = 10;
+ final int amountOtherTargets = 15;
+ final int amountGroups = 5;
+ final String successCondition = "50";
+ final String errorCondition = "80";
+ final Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout,
+ amountOtherTargets, amountGroups, successCondition, errorCondition);
+
+ rolloutManagement.startRollout(createdRollout);
+ final List runningActions = deploymentManagement.findActionsByRolloutAndStatus(createdRollout,
+ Status.RUNNING);
+ // finish one action should be sufficient due the finish condition is at
+ // 50%
+ final Action action = runningActions.get(0);
+ action.setStatus(Status.FINISHED);
+ controllerManagament.addUpdateActionStatus(
+ new ActionStatus(action, Status.FINISHED, System.currentTimeMillis(), ""), action);
+
+ // check running rollouts again, now the finish condition should be hit
+ // and should start the next group
+ rolloutManagement.checkRunningRollouts(0);
+
+ // verify that now the first and the second group are in running state
+ final List runningRolloutGroups = rolloutGroupManagement.findRolloutGroupsByRolloutId(
+ createdRollout.getId(), new OffsetBasedPageRequest(0, 2, new Sort(Direction.ASC, "id"))).getContent();
+ runningRolloutGroups.forEach(group -> assertThat(group.getStatus()).isEqualTo(RolloutGroupStatus.RUNNING)
+ .as("group should be in running state because it should be started but it is in " + group.getStatus()
+ + " state"));
+
+ // verify that the other groups are still in schedule state
+ final List scheduledRolloutGroups = rolloutGroupManagement.findRolloutGroupsByRolloutId(
+ createdRollout.getId(), new OffsetBasedPageRequest(2, 10, new Sort(Direction.ASC, "id"))).getContent();
+ scheduledRolloutGroups.forEach(group -> assertThat(group.getStatus()).isEqualTo(RolloutGroupStatus.SCHEDULED)
+ .as("group should be in scheduled state because it should not be started but it is in "
+ + group.getStatus() + " state"));
+ }
+
+ @Test
+ @Description("Verfiying that the error handling action of a group is executed to pause the current rollout")
+ public void checkErrorHitOfGroupCallsErrorActionToPauseTheRollout() {
+ final int amountTargetsForRollout = 10;
+ final int amountOtherTargets = 15;
+ final int amountGroups = 5;
+ final String successCondition = "50";
+ final String errorCondition = "80";
+ final Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout,
+ amountOtherTargets, amountGroups, successCondition, errorCondition);
+
+ rolloutManagement.startRollout(createdRollout);
+ // set both actions in error state so error condition is hit and error
+ // action is executed
+ final List runningActions = deploymentManagement.findActionsByRolloutAndStatus(createdRollout,
+ Status.RUNNING);
+
+ // finish actions with error
+ for (final Action action : runningActions) {
+ action.setStatus(Status.ERROR);
+ controllerManagament.addUpdateActionStatus(
+ new ActionStatus(action, Status.ERROR, System.currentTimeMillis(), ""), action);
+ }
+
+ // check running rollouts again, now the error condition should be hit
+ // and should execute the error action
+ rolloutManagement.checkRunningRollouts(0);
+
+ final Rollout rollout = rolloutManagement.findRolloutById(createdRollout.getId());
+ // the rollout itself should be in paused based on the error action
+ assertThat(rollout.getStatus()).isEqualTo(RolloutStatus.PAUSED);
+
+ // the first rollout group should be in error state
+ final List errorGroup = rolloutGroupManagement.findRolloutGroupsByRolloutId(
+ createdRollout.getId(), new OffsetBasedPageRequest(0, 1, new Sort(Direction.ASC, "id"))).getContent();
+ assertThat(errorGroup).hasSize(1);
+ assertThat(errorGroup.get(0).getStatus()).isEqualTo(RolloutGroupStatus.ERROR);
+
+ // all other groups should still be in scheduled state
+ final List scheduleGroups = rolloutGroupManagement.findRolloutGroupsByRolloutId(
+ createdRollout.getId(), new OffsetBasedPageRequest(1, 100, new Sort(Direction.ASC, "id"))).getContent();
+ scheduleGroups.forEach(group -> assertThat(group.getStatus()).isEqualTo(RolloutGroupStatus.SCHEDULED));
+ }
+
+ @Test
+ @Description("Verfiying a paused rollout in case of error action hit can be resumed again")
+ public void errorActionPausesRolloutAndRolloutGetsResumedStartsNextScheduledGroup() {
+ final int amountTargetsForRollout = 10;
+ final int amountOtherTargets = 15;
+ final int amountGroups = 5;
+ final String successCondition = "50";
+ final String errorCondition = "80";
+ final Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout,
+ amountOtherTargets, amountGroups, successCondition, errorCondition);
+ rolloutManagement.startRollout(createdRollout);
+ // set both actions in error state so error condition is hit and error
+ // action is executed
+ final List runningActions = deploymentManagement.findActionsByRolloutAndStatus(createdRollout,
+ Status.RUNNING);
+ // finish actions with error
+ for (final Action action : runningActions) {
+ action.setStatus(Status.ERROR);
+ controllerManagament.addUpdateActionStatus(
+ new ActionStatus(action, Status.ERROR, System.currentTimeMillis(), ""), action);
+ }
+
+ // check running rollouts again, now the error condition should be hit
+ // and should execute the error action
+ rolloutManagement.checkRunningRollouts(0);
+
+ final Rollout rollout = rolloutManagement.findRolloutById(createdRollout.getId());
+ // the rollout itself should be in paused based on the error action
+ assertThat(rollout.getStatus()).isEqualTo(RolloutStatus.PAUSED);
+
+ // all other groups should still be in scheduled state
+ final List scheduleGroups = rolloutGroupManagement.findRolloutGroupsByRolloutId(
+ createdRollout.getId(), new OffsetBasedPageRequest(1, 100, new Sort(Direction.ASC, "id"))).getContent();
+ scheduleGroups.forEach(group -> assertThat(group.getStatus()).isEqualTo(RolloutGroupStatus.SCHEDULED));
+
+ // resume the rollout again after it gets paused by error action
+ rolloutManagement.resumeRollout(rolloutManagement.findRolloutById(createdRollout.getId()));
+
+ // the rollout should be running again
+ assertThat(rolloutManagement.findRolloutById(createdRollout.getId()).getStatus())
+ .isEqualTo(RolloutStatus.RUNNING);
+
+ // checking rollouts again
+ rolloutManagement.checkRunningRollouts(0);
+
+ // next group should be running again after resuming the rollout
+ final List resumedGroups = rolloutGroupManagement.findRolloutGroupsByRolloutId(
+ createdRollout.getId(), new OffsetBasedPageRequest(1, 1, new Sort(Direction.ASC, "id"))).getContent();
+ assertThat(resumedGroups).hasSize(1);
+ assertThat(resumedGroups.get(0).getStatus()).isEqualTo(RolloutGroupStatus.RUNNING);
+ }
+
+ @Test
+ @Description("Verfiying that the rollout is starting group after group and gets finished at the end")
+ public void rolloutStartsGroupAfterGroupAndGetsFinished() {
+ final int amountTargetsForRollout = 10;
+ final int amountOtherTargets = 15;
+ final int amountGroups = 5;
+ final String successCondition = "50";
+ final String errorCondition = "80";
+ final Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout,
+ amountOtherTargets, amountGroups, successCondition, errorCondition);
+ rolloutManagement.startRollout(createdRollout);
+ // finish running actions, 2 actions should be finished
+ assertThat(changeStatusForAllRunningActions(createdRollout, Status.FINISHED)).isEqualTo(2);
+
+ // calculate the rest of the groups and finish them
+ for (int groupsLeft = amountGroups - 1; groupsLeft >= 1; groupsLeft--) {
+ // next check and start next group
+ rolloutManagement.checkRunningRollouts(0);
+ // finish running actions, 2 actions should be finished
+ assertThat(changeStatusForAllRunningActions(createdRollout, Status.FINISHED)).isEqualTo(2);
+ assertThat(rolloutManagement.findRolloutById(createdRollout.getId()).getStatus())
+ .isEqualTo(RolloutStatus.RUNNING);
+
+ }
+ // check rollout to see that all actions and all groups are finished and
+ // so can go to FINISHED state of the rollout
+ rolloutManagement.checkRunningRollouts(0);
+
+ // verify all groups are in finished state
+ rolloutGroupManagement
+ .findRolloutGroupsByRolloutId(createdRollout.getId(),
+ new OffsetBasedPageRequest(0, 100, new Sort(Direction.ASC, "id")))
+ .forEach(group -> assertThat(group.getStatus()).isEqualTo(RolloutGroupStatus.FINISHED));
+
+ // verify that rollout itself is in finished state
+ final Rollout findRolloutById = rolloutManagement.findRolloutById(createdRollout.getId());
+ assertThat(findRolloutById.getStatus()).isEqualTo(RolloutStatus.FINISHED);
+ }
+
+ @Test
+ @Description("Verify that the targets have the right status during the rollout.")
+ public void countCorrectStatusForEachTargetDuringRollout() {
+
+ final int amountTargetsForRollout = 8;
+ final int amountOtherTargets = 15;
+ final int amountGroups = 4;
+ final String successCondition = "50";
+ final String errorCondition = "80";
+ final Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout,
+ amountOtherTargets, amountGroups, successCondition, errorCondition);
+
+ // targets have not started
+ Map validationMap = createInitStatusMap();
+ validationMap.put(TotalTargetCountStatus.Status.NOTSTARTED, 8L);
+ validateRolloutActionStatus(createdRollout.getId(), validationMap);
+
+ rolloutManagement.startRollout(createdRollout);
+ // 6 targets are ready and 2 are running
+ validationMap = createInitStatusMap();
+ validationMap.put(TotalTargetCountStatus.Status.SCHEDULED, 6L);
+ validationMap.put(TotalTargetCountStatus.Status.RUNNING, 2L);
+ validateRolloutActionStatus(createdRollout.getId(), validationMap);
+
+ changeStatusForAllRunningActions(createdRollout, Status.FINISHED);
+ rolloutManagement.checkRunningRollouts(0);
+ // 4 targets are ready, 2 are finished and 2 are running
+ validationMap = createInitStatusMap();
+ validationMap.put(TotalTargetCountStatus.Status.SCHEDULED, 4L);
+ validationMap.put(TotalTargetCountStatus.Status.FINISHED, 2L);
+ validationMap.put(TotalTargetCountStatus.Status.RUNNING, 2L);
+ validateRolloutActionStatus(createdRollout.getId(), validationMap);
+
+ changeStatusForAllRunningActions(createdRollout, Status.FINISHED);
+ rolloutManagement.checkRunningRollouts(0);
+ // 2 targets are ready, 4 are finished and 2 are running
+ validationMap = createInitStatusMap();
+ validationMap.put(TotalTargetCountStatus.Status.SCHEDULED, 2L);
+ validationMap.put(TotalTargetCountStatus.Status.FINISHED, 4L);
+ validationMap.put(TotalTargetCountStatus.Status.RUNNING, 2L);
+ validateRolloutActionStatus(createdRollout.getId(), validationMap);
+
+ changeStatusForAllRunningActions(createdRollout, Status.FINISHED);
+ rolloutManagement.checkRunningRollouts(0);
+ // 0 targets are ready, 6 are finished and 2 are running
+ validationMap = createInitStatusMap();
+ validationMap.put(TotalTargetCountStatus.Status.FINISHED, 6L);
+ validationMap.put(TotalTargetCountStatus.Status.RUNNING, 2L);
+ validateRolloutActionStatus(createdRollout.getId(), validationMap);
+
+ changeStatusForAllRunningActions(createdRollout, Status.FINISHED);
+ rolloutManagement.checkRunningRollouts(0);
+ // 0 targets are ready, 8 are finished and 0 are running
+ validationMap = createInitStatusMap();
+ validationMap.put(TotalTargetCountStatus.Status.FINISHED, 8L);
+ validateRolloutActionStatus(createdRollout.getId(), validationMap);
+
+ }
+
+ @Test
+ @Description("Verify that the targets have the right status during the rollout when an error emerges.")
+ public void countCorrectStatusForEachTargetDuringRolloutWithError() {
+
+ final int amountTargetsForRollout = 8;
+ final int amountOtherTargets = 15;
+ final int amountGroups = 4;
+ final String successCondition = "50";
+ final String errorCondition = "80";
+ final Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout,
+ amountOtherTargets, amountGroups, successCondition, errorCondition);
+
+ // 8 targets have not started
+ Map validationMap = createInitStatusMap();
+ validationMap.put(TotalTargetCountStatus.Status.NOTSTARTED, 8L);
+ validateRolloutActionStatus(createdRollout.getId(), validationMap);
+
+ rolloutManagement.startRollout(createdRollout);
+ // 6 targets are ready and 2 are running
+ validationMap = createInitStatusMap();
+ validationMap.put(TotalTargetCountStatus.Status.SCHEDULED, 6L);
+ validationMap.put(TotalTargetCountStatus.Status.RUNNING, 2L);
+ validateRolloutActionStatus(createdRollout.getId(), validationMap);
+
+ changeStatusForAllRunningActions(createdRollout, Status.ERROR);
+ rolloutManagement.checkRunningRollouts(0);
+ // 6 targets are ready and 2 are error
+ validationMap = createInitStatusMap();
+ validationMap.put(TotalTargetCountStatus.Status.SCHEDULED, 6L);
+ validationMap.put(TotalTargetCountStatus.Status.ERROR, 2L);
+ validateRolloutActionStatus(createdRollout.getId(), validationMap);
+ }
+
+ @Test
+ @Description("Verify that the targets have the right status during the rollout when receiving the status of rollout groups.")
+ public void countCorrectStatusForEachTargetGroupDuringRollout() {
+
+ final int amountTargetsForRollout = 9;
+ final int amountOtherTargets = 15;
+ final int amountGroups = 4;
+ final String successCondition = "50";
+ final String errorCondition = "80";
+ Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout,
+ amountOtherTargets, amountGroups, successCondition, errorCondition);
+
+ rolloutManagement.startRollout(createdRollout);
+ changeStatusForAllRunningActions(createdRollout, Status.FINISHED);
+ rolloutManagement.checkRunningRollouts(0);
+
+ // 3 targets finished (Group 1), 3 targets running (Group 3) and 3
+ // targets SCHEDULED (Group 3)
+ createdRollout = rolloutManagement.findRolloutById(createdRollout.getId());
+ final List rolloutGruops = createdRollout.getRolloutGroups();
+ Map expectedTargetCountStatus = createInitStatusMap();
+ expectedTargetCountStatus.put(TotalTargetCountStatus.Status.FINISHED, 3L);
+ validateRolloutGroupActionStatus(rolloutGruops.get(0), expectedTargetCountStatus);
+ expectedTargetCountStatus = createInitStatusMap();
+ expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 3L);
+ validateRolloutGroupActionStatus(rolloutGruops.get(1), expectedTargetCountStatus);
+ expectedTargetCountStatus = createInitStatusMap();
+ expectedTargetCountStatus.put(TotalTargetCountStatus.Status.SCHEDULED, 3L);
+ validateRolloutGroupActionStatus(rolloutGruops.get(2), expectedTargetCountStatus);
+
+ }
+
+ @Test
+ @Description("Verify that target actions of rollout get canceled when a manuel distribution sets assignment is done.")
+ public void targetsOfRolloutGetsManuelDsAssignment() {
+
+ final int amountTargetsForRollout = 10;
+ final int amountOtherTargets = 0;
+ final int amountGroups = 2;
+ final String successCondition = "50";
+ final String errorCondition = "80";
+ Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout,
+ amountOtherTargets, amountGroups, successCondition, errorCondition);
+ final DistributionSet ds = createdRollout.getDistributionSet();
+
+ rolloutManagement.startRollout(createdRollout);
+ createdRollout = rolloutManagement.findRolloutById(createdRollout.getId());
+ // 5 targets are running
+ final List runningActions = deploymentManagement.findActionsByRolloutAndStatus(createdRollout,
+ Status.RUNNING);
+ assertThat(runningActions.size()).isEqualTo(5);
+
+ // 5 targets are in the group and the DS has been assigned
+ final List rolloutGroups = createdRollout.getRolloutGroups();
+ final Page targets = rolloutGroupManagement.findRolloutGroupTargets(rolloutGroups.get(0),
+ new OffsetBasedPageRequest(0, 20, new Sort(Direction.ASC, "id")));
+ final List targetList = targets.getContent();
+ assertThat(targetList.size()).isEqualTo(5);
+ for (final Target t : targetList) {
+ final DistributionSet assignedDs = t.getAssignedDistributionSet();
+ assertThat(assignedDs.getId()).isEqualTo(ds.getId());
+ }
+
+ final List targetToCancel = new ArrayList();
+ targetToCancel.add(targetList.get(0));
+ targetToCancel.add(targetList.get(1));
+ targetToCancel.add(targetList.get(2));
+ final DistributionSet dsForCancelTest = TestDataUtil.generateDistributionSet("dsForTest", softwareManagement,
+ distributionSetManagement);
+ deploymentManagement.assignDistributionSet(dsForCancelTest, targetToCancel);
+ // 5 targets are canceling but still have the status running and 5 are
+ // still in SCHEDULED
+ final Map validationMap = createInitStatusMap();
+ validationMap.put(TotalTargetCountStatus.Status.RUNNING, 5L);
+ validationMap.put(TotalTargetCountStatus.Status.SCHEDULED, 5L);
+ validateRolloutActionStatus(createdRollout.getId(), validationMap);
+ }
+
+ @Test
+ @Description("Verify that target actions of a rollout get cancelled when another rollout with same targets gets started.")
+ public void targetsOfRolloutGetDistributionSetAssignmentByOtherRollout() {
+
+ final int amountTargetsForRollout = 15;
+ final int amountOtherTargets = 5;
+ final int amountGroups = 3;
+ final String successCondition = "50";
+ final String errorCondition = "80";
+ Rollout rolloutOne = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout,
+ amountOtherTargets, amountGroups, successCondition, errorCondition);
+ rolloutManagement.startRollout(rolloutOne);
+ rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId());
+
+ final DistributionSet dsForRolloutTwo = TestDataUtil.generateDistributionSet("dsForRolloutTwo",
+ softwareManagement, distributionSetManagement);
+
+ final Rollout rolloutTwo = createRolloutByVariables("rolloutTwo", "This is the description for rollout two", 1,
+ "controllerId==rollout-*", dsForRolloutTwo, "50", "80");
+ changeStatusForAllRunningActions(rolloutOne, Status.FINISHED);
+ rolloutManagement.checkRunningRollouts(0);
+ // Verify that 5 targets are finished, 5 are running and 5 are ready.
+ Map expectedTargetCountStatus = createInitStatusMap();
+ expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 5L);
+ expectedTargetCountStatus.put(TotalTargetCountStatus.Status.FINISHED, 5L);
+ expectedTargetCountStatus.put(TotalTargetCountStatus.Status.SCHEDULED, 5L);
+ validateRolloutActionStatus(rolloutOne.getId(), expectedTargetCountStatus);
+
+ rolloutManagement.startRollout(rolloutTwo);
+ // Verify that 5 targets are finished, 5 are still running and 5 are
+ // cancelled.
+ expectedTargetCountStatus = createInitStatusMap();
+ expectedTargetCountStatus.put(TotalTargetCountStatus.Status.FINISHED, 5L);
+ expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 5L);
+ expectedTargetCountStatus.put(TotalTargetCountStatus.Status.CANCELLED, 5L);
+ validateRolloutActionStatus(rolloutOne.getId(), expectedTargetCountStatus);
+ }
+
+ @Test
+ @Description("Verify that error status of DistributionSet installation during rollout can get rerun with second rollout so that all targets have some DistributionSet installed at the end.")
+ public void startSecondRolloutAfterFristRolloutEndenWithErrors() {
+
+ final int amountTargetsForRollout = 15;
+ final int amountOtherTargets = 0;
+ final int amountGroups = 3;
+ final String successCondition = "50";
+ final String errorCondition = "80";
+ Rollout rolloutOne = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout,
+ amountOtherTargets, amountGroups, successCondition, errorCondition);
+ final DistributionSet distributionSet = rolloutOne.getDistributionSet();
+ rolloutManagement.startRollout(rolloutOne);
+ rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId());
+ changeStatusForRunningActions(rolloutOne, Status.ERROR, 2);
+ changeStatusForRunningActions(rolloutOne, Status.FINISHED, 3);
+ rolloutManagement.checkRunningRollouts(0);
+ changeStatusForRunningActions(rolloutOne, Status.ERROR, 2);
+ changeStatusForRunningActions(rolloutOne, Status.FINISHED, 3);
+ rolloutManagement.checkRunningRollouts(0);
+ changeStatusForRunningActions(rolloutOne, Status.ERROR, 2);
+ changeStatusForRunningActions(rolloutOne, Status.FINISHED, 3);
+ rolloutManagement.checkRunningRollouts(0);
+
+ // 9 targets are finished and 6 have error
+ Map expectedTargetCountStatus = createInitStatusMap();
+ expectedTargetCountStatus.put(TotalTargetCountStatus.Status.FINISHED, 9L);
+ expectedTargetCountStatus.put(TotalTargetCountStatus.Status.ERROR, 6L);
+ validateRolloutActionStatus(rolloutOne.getId(), expectedTargetCountStatus);
+ // rollout is finished
+ rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId());
+ assertThat(rolloutOne.getStatus()).isEqualTo(RolloutStatus.FINISHED);
+
+ final int amountGroupsForRolloutTwo = 1;
+ Rollout rolloutTwo = createRolloutByVariables("rolloutTwo", "This is the description for rollout two",
+ amountGroupsForRolloutTwo, "controllerId==rollout-*", distributionSet, "50", "80");
+
+ rolloutManagement.startRollout(rolloutTwo);
+ rolloutTwo = rolloutManagement.findRolloutById(rolloutTwo.getId());
+ // 6 error targets are know running
+ expectedTargetCountStatus = createInitStatusMap();
+ expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 6L);
+ expectedTargetCountStatus.put(TotalTargetCountStatus.Status.NOTSTARTED, 9L);
+ validateRolloutActionStatus(rolloutTwo.getId(), expectedTargetCountStatus);
+ changeStatusForAllRunningActions(rolloutTwo, Status.FINISHED);
+ final Page targetPage = targetManagement.findTargetByUpdateStatus(pageReq, TargetUpdateStatus.IN_SYNC);
+ final List targetList = targetPage.getContent();
+ // 15 targets in finished/IN_SYNC status and same DS assigned
+ assertThat(targetList.size()).isEqualTo(amountTargetsForRollout);
+ for (final Target t : targetList) {
+ final DistributionSet ds = t.getAssignedDistributionSet();
+ assertThat(ds).isEqualTo(distributionSet);
+ }
+ }
+
+ @Test
+ @Description("Verify that the rollout moves to the next group when the success condition was achieved and the error condition was not exceeded.")
+ public void successConditionAchievedAndErrorConditionNotExceeded() {
+
+ final int amountTargetsForRollout = 10;
+ final int amountOtherTargets = 0;
+ final int amountGroups = 2;
+ final String successCondition = "50";
+ final String errorCondition = "80";
+ Rollout rolloutOne = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout,
+ amountOtherTargets, amountGroups, successCondition, errorCondition);
+
+ rolloutManagement.startRollout(rolloutOne);
+ rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId());
+ changeStatusForRunningActions(rolloutOne, Status.ERROR, 2);
+ changeStatusForRunningActions(rolloutOne, Status.FINISHED, 3);
+ rolloutManagement.checkRunningRollouts(0);
+ // verify: 40% error but 60% finished -> should move to next group
+ final List rolloutGruops = rolloutOne.getRolloutGroups();
+ final Map expectedTargetCountStatus = createInitStatusMap();
+ expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 5L);
+ validateRolloutGroupActionStatus(rolloutGruops.get(1), expectedTargetCountStatus);
+
+ }
+
+ @Test
+ @Description("Verify that the rollout does not move to the next group when the sucess condition was not achieved.")
+ public void successConditionNotAchieved() {
+
+ final int amountTargetsForRollout = 10;
+ final int amountOtherTargets = 0;
+ final int amountGroups = 2;
+ final String successCondition = "80";
+ final String errorCondition = "90";
+ Rollout rolloutOne = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout,
+ amountOtherTargets, amountGroups, successCondition, errorCondition);
+
+ rolloutManagement.startRollout(rolloutOne);
+ rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId());
+ changeStatusForRunningActions(rolloutOne, Status.ERROR, 2);
+ changeStatusForRunningActions(rolloutOne, Status.FINISHED, 3);
+ rolloutManagement.checkRunningRollouts(0);
+ // verify: 40% error and 60% finished -> should not move to next group
+ // because successCondition 80%
+ rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId());
+ final List rolloutGruops = rolloutOne.getRolloutGroups();
+ final Map expectedTargetCountStatus = createInitStatusMap();
+ expectedTargetCountStatus.put(TotalTargetCountStatus.Status.SCHEDULED, 5L);
+ validateRolloutGroupActionStatus(rolloutGruops.get(1), expectedTargetCountStatus);
+ }
+
+ @Test
+ @Description("Verify that the rollout pauses when the error condition was exceeded.")
+ public void errorConditionExceeded() {
+
+ final int amountTargetsForRollout = 10;
+ final int amountOtherTargets = 0;
+ final int amountGroups = 2;
+ final String successCondition = "50";
+ final String errorCondition = "20";
+ Rollout rolloutOne = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout,
+ amountOtherTargets, amountGroups, successCondition, errorCondition);
+
+ rolloutManagement.startRollout(rolloutOne);
+ rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId());
+ changeStatusForRunningActions(rolloutOne, Status.ERROR, 2);
+ changeStatusForRunningActions(rolloutOne, Status.FINISHED, 3);
+ rolloutManagement.checkRunningRollouts(0);
+ // verify: 40% error -> should pause because errorCondition is 20%
+ rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId());
+ assertThat(RolloutStatus.PAUSED).isEqualTo(rolloutOne.getStatus());
+ }
+
+ @Test
+ @Description("Verify that all rollouts are return with expected target statuses.")
+ public void findAllRolloutsWithDetailedStatus() {
+
+ final int amountTargetsForRollout = 12;
+ final int amountGroups = 2;
+ final String successCondition = "50";
+ final String errorCondition = "20";
+ final Rollout rolloutA = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, amountGroups,
+ successCondition, errorCondition, "RolloutA", "RolloutA");
+ rolloutManagement.startRollout(rolloutA);
+
+ final int amountTargetsForRollout2 = 10;
+ final int amountGroups2 = 2;
+ final Rollout rolloutB = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout2, amountGroups2,
+ successCondition, errorCondition, "RolloutB", "RolloutB");
+ rolloutManagement.startRollout(rolloutB);
+ changeStatusForAllRunningActions(rolloutB, Status.FINISHED);
+ rolloutManagement.checkRunningRollouts(0);
+
+ final int amountTargetsForRollout3 = 10;
+ final int amountGroups3 = 2;
+ final Rollout rolloutC = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout3, amountGroups3,
+ successCondition, errorCondition, "RolloutC", "RolloutC");
+ rolloutManagement.startRollout(rolloutC);
+ changeStatusForAllRunningActions(rolloutC, Status.ERROR);
+ rolloutManagement.checkRunningRollouts(0);
+
+ final int amountTargetsForRollout4 = 15;
+ final int amountGroups4 = 3;
+ final Rollout rolloutD = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout4, amountGroups4,
+ successCondition, errorCondition, "RolloutD", "RolloutD");
+ rolloutManagement.startRollout(rolloutD);
+ changeStatusForRunningActions(rolloutD, Status.ERROR, 1);
+ rolloutManagement.checkRunningRollouts(0);
+ changeStatusForAllRunningActions(rolloutD, Status.FINISHED);
+ rolloutManagement.checkRunningRollouts(0);
+
+ final Page rolloutPage = rolloutManagement
+ .findAllRolloutsWithDetailedStatus(new OffsetBasedPageRequest(0, 100, new Sort(Direction.ASC, "name")));
+ final List rolloutList = rolloutPage.getContent();
+
+ // validate rolloutA -> 6 running and 6 ready
+ Map expectedTargetCountStatus = createInitStatusMap();
+ expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 6L);
+ expectedTargetCountStatus.put(TotalTargetCountStatus.Status.SCHEDULED, 6L);
+ validateRolloutActionStatus(rolloutList.get(0).getId(), expectedTargetCountStatus);
+
+ // validate rolloutB -> 5 running and 5 finished
+ expectedTargetCountStatus = createInitStatusMap();
+ expectedTargetCountStatus.put(TotalTargetCountStatus.Status.FINISHED, 5L);
+ expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 5L);
+ validateRolloutActionStatus(rolloutList.get(1).getId(), expectedTargetCountStatus);
+
+ // validate rolloutC -> 5 running and 5 error
+ expectedTargetCountStatus = createInitStatusMap();
+ expectedTargetCountStatus.put(TotalTargetCountStatus.Status.ERROR, 5L);
+ expectedTargetCountStatus.put(TotalTargetCountStatus.Status.SCHEDULED, 5L);
+ validateRolloutActionStatus(rolloutList.get(2).getId(), expectedTargetCountStatus);
+
+ // validate rolloutD -> 1, error, 4 finished, 5 running and 5 ready
+ expectedTargetCountStatus = createInitStatusMap();
+ expectedTargetCountStatus.put(TotalTargetCountStatus.Status.ERROR, 1L);
+ expectedTargetCountStatus.put(TotalTargetCountStatus.Status.FINISHED, 4L);
+ expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 5L);
+ expectedTargetCountStatus.put(TotalTargetCountStatus.Status.SCHEDULED, 5L);
+ validateRolloutActionStatus(rolloutList.get(3).getId(), expectedTargetCountStatus);
+ }
+
+ @Test
+ @Description("Verify the count of existing rollouts.")
+ public void rightCountForAllRollouts() {
+
+ final int amountTargetsForRollout = 6;
+ ;
+ final int amountGroups = 2;
+ final String successCondition = "50";
+ final String errorCondition = "80";
+ for (int i = 1; i <= 10; i++) {
+ createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, amountGroups, successCondition,
+ errorCondition, "Rollout" + i, "Rollout" + i);
+ }
+ final Long count = rolloutManagement.countRolloutsAll();
+ assertThat(count).isEqualTo(10L);
+ }
+
+ @Test
+ @Description("Verify the count of filtered existing rollouts.")
+ public void countRolloutsAllByFilters() {
+
+ final int amountTargetsForRollout = 6;
+ final int amountGroups = 2;
+ final String successCondition = "50";
+ final String errorCondition = "80";
+ for (int i = 1; i <= 5; i++) {
+ createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, amountGroups, successCondition,
+ errorCondition, "Rollout" + i, "Rollout" + i);
+ }
+ for (int i = 1; i <= 5; i++) {
+ createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, amountGroups, successCondition,
+ errorCondition, "SomethingElse" + i, "SomethingElse" + i);
+ }
+
+ final Long count = rolloutManagement.countRolloutsAllByFilters("Rollout%");
+ assertThat(count).isEqualTo(5L);
+
+ }
+
+ @Test
+ @Description("Verify that the filtering and sorting ascending for rollout is working correctly.")
+ public void findRolloutByFilters() {
+
+ final int amountTargetsForRollout = 6;
+ final int amountGroups = 2;
+ final String successCondition = "50";
+ final String errorCondition = "80";
+ for (int i = 1; i <= 5; i++) {
+ createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, amountGroups, successCondition,
+ errorCondition, "Rollout" + i, "Rollout" + i);
+ }
+ for (int i = 1; i <= 8; i++) {
+ createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, amountGroups, successCondition,
+ errorCondition, "SomethingElse" + i, "SomethingElse" + i);
+ }
+
+ final Slice rollout = rolloutManagement
+ .findRolloutByFilters(new OffsetBasedPageRequest(0, 100, new Sort(Direction.ASC, "name")), "Rollout%");
+ final List rolloutList = rollout.getContent();
+ assertThat(rolloutList.size()).isEqualTo(5);
+ int i = 1;
+ for (final Rollout r : rolloutList) {
+ assertThat(r.getName()).isEqualTo("Rollout" + i);
+ i++;
+ }
+ }
+
+ @Test
+ @Description("Verify that the expected rollout is found by name.")
+ public void findRolloutByName() {
+
+ final int amountTargetsForRollout = 12;
+ final int amountGroups = 2;
+ final String successCondition = "50";
+ final String errorCondition = "80";
+ final String rolloutName = "Rollout137";
+ final Rollout rolloutCreated = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout,
+ amountGroups, successCondition, errorCondition, rolloutName, "RolloutA");
+
+ final Rollout rolloutFound = rolloutManagement.findRolloutByName(rolloutName);
+ assertThat(rolloutCreated).isEqualTo(rolloutFound);
+
+ }
+
+ @Test
+ @Description("Verify that the percent count is acting like aspected when targets move to the status finished or error.")
+ public void getFinishedPercentForRunningGroup() {
+
+ final int amountTargetsForRollout = 10;
+ final int amountGroups = 2;
+ final String successCondition = "50";
+ final String errorCondition = "80";
+ final String rolloutName = "MyRollout";
+ Rollout myRollout = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, amountGroups,
+ successCondition, errorCondition, rolloutName, rolloutName);
+ rolloutManagement.startRollout(myRollout);
+ changeStatusForRunningActions(myRollout, Status.FINISHED, 2);
+ rolloutManagement.checkRunningRollouts(0);
+ myRollout = rolloutManagement.findRolloutById(myRollout.getId());
+
+ float percent = rolloutManagement.getFinishedPercentForRunningGroup(myRollout.getId(),
+ myRollout.getRolloutGroups().get(0));
+ assertThat(percent).isEqualTo(40);
+
+ changeStatusForRunningActions(myRollout, Status.FINISHED, 3);
+ rolloutManagement.checkRunningRollouts(0);
+
+ percent = rolloutManagement.getFinishedPercentForRunningGroup(myRollout.getId(),
+ myRollout.getRolloutGroups().get(0));
+ assertThat(percent).isEqualTo(100);
+
+ changeStatusForRunningActions(myRollout, Status.FINISHED, 4);
+ changeStatusForAllRunningActions(myRollout, Status.ERROR);
+ rolloutManagement.checkRunningRollouts(0);
+
+ percent = rolloutManagement.getFinishedPercentForRunningGroup(myRollout.getId(),
+ myRollout.getRolloutGroups().get(1));
+ assertThat(percent).isEqualTo(80);
+ }
+
+ @Test
+ @Description("Verify that the expected targets in the expected order are returned for the rollout groups.")
+ public void findRolloutGroupTargetsWithRsqlParam() {
+
+ final int amountTargetsForRollout = 15;
+ final int amountGroups = 3;
+ final int amountOtherTargets = 15;
+ final String successCondition = "50";
+ final String errorCondition = "80";
+ final String rolloutName = "MyRollout";
+ Rollout myRollout = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, amountGroups,
+ successCondition, errorCondition, rolloutName, rolloutName);
+
+ targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountOtherTargets, "others-", "rollout"));
+
+ final String rsqlParam = "controllerId==*MyRoll*";
+ final Specification rsqlSpecification = RSQLUtility.parse(rsqlParam, TargetFields.class);
+
+ rolloutManagement.startRollout(myRollout);
+ myRollout = rolloutManagement.findRolloutById(myRollout.getId());
+ final List rolloutGroups = myRollout.getRolloutGroups();
+
+ Page targetPage = rolloutGroupManagement.findRolloutGroupTargets(rolloutGroups.get(0),
+ rsqlSpecification, new OffsetBasedPageRequest(0, 100, new Sort(Direction.ASC, "controllerId")));
+ final List targetlistGroup1 = targetPage.getContent();
+ assertThat(targetlistGroup1.size()).isEqualTo(5);
+ assertThat(targetlistGroup1.get(0).getControllerId()).isEqualTo("MyRollout--00000");
+
+ targetPage = rolloutGroupManagement.findRolloutGroupTargets(rolloutGroups.get(1), rsqlSpecification,
+ new OffsetBasedPageRequest(0, 100, new Sort(Direction.DESC, "controllerId")));
+ final List targetlistGroup2 = targetPage.getContent();
+ assertThat(targetlistGroup2.size()).isEqualTo(5);
+ assertThat(targetlistGroup2.get(0).getControllerId()).isEqualTo("MyRollout--00009");
+
+ targetPage = rolloutGroupManagement.findRolloutGroupTargets(rolloutGroups.get(2), rsqlSpecification,
+ new OffsetBasedPageRequest(0, 100, new Sort(Direction.ASC, "controllerId")));
+ final List targetlistGroup3 = targetPage.getContent();
+ assertThat(targetlistGroup3.size()).isEqualTo(5);
+ assertThat(targetlistGroup3.get(0).getControllerId()).isEqualTo("MyRollout--00010");
+
+ }
+
+ @Test
+ @Description("Verify the creation and the start of a rollout in asynchronous mode.")
+ public void createAndStartRolloutInAsync() {
+
+ final int amountTargetsForRollout = 500;
+ final int amountGroups = 5;
+ final String successCondition = "50";
+ final String errorCondition = "80";
+ final String rolloutName = "rolloutTest";
+ final String targetPrefixName = rolloutName;
+ final DistributionSet distributionSet = TestDataUtil.generateDistributionSet("dsFor" + rolloutName,
+ softwareManagement, distributionSetManagement);
+ targetManagement.createTargets(
+ TestDataUtil.buildTargetFixtures(amountTargetsForRollout, targetPrefixName + "-", targetPrefixName));
+ final RolloutGroupConditions conditions = new RolloutGroup.RolloutGroupConditionBuilder()
+ .successCondition(RolloutGroupSuccessCondition.THRESHOLD, successCondition)
+ .errorCondition(RolloutGroupErrorCondition.THRESHOLD, errorCondition)
+ .errorAction(RolloutGroupErrorAction.PAUSE, null).build();
+ Rollout myRollout = new Rollout();
+ myRollout.setName(rolloutName);
+ myRollout.setDescription("This is a test description for the rollout");
+ myRollout.setTargetFilterQuery("controllerId==" + targetPrefixName + "-*");
+ myRollout.setDistributionSet(distributionSet);
+
+ myRollout = rolloutManagement.createRolloutAsync(myRollout, amountGroups, conditions);
+
+ int counter = 1;
+ int counterMax = 10;
+ while (!isRolloutInGivenStatus(myRollout.getId(), RolloutStatus.READY) && (counter <= counterMax)) {
+ try {
+ Thread.sleep(500);
+ } catch (final InterruptedException e) {
+ e.printStackTrace();
+ }
+ counter++;
+ }
+
+ myRollout = rolloutManagement.findRolloutById(myRollout.getId());
+ assertThat(myRollout.getStatus()).isEqualTo(RolloutStatus.READY);
+ rolloutManagement.startRolloutAsync(myRollout);
+
+ counter = 1;
+ counterMax = 10;
+ while (!isRolloutInGivenStatus(myRollout.getId(), RolloutStatus.RUNNING) && counter <= counterMax) {
+ try {
+ Thread.sleep(500);
+ } catch (final InterruptedException e) {
+ e.printStackTrace();
+ }
+ counter++;
+ }
+
+ myRollout = rolloutManagement.findRolloutById(myRollout.getId());
+ assertThat(myRollout.getStatus()).isEqualTo(RolloutStatus.RUNNING);
+ final Map expectedTargetCountStatus = createInitStatusMap();
+ expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 100L);
+ expectedTargetCountStatus.put(TotalTargetCountStatus.Status.SCHEDULED, 400L);
+ validateRolloutActionStatus(myRollout.getId(), expectedTargetCountStatus);
+ }
+
+ private boolean isRolloutInGivenStatus(final Long rolloutID, final RolloutStatus status) {
+ final Rollout myRollout = rolloutManagement.findRolloutById(rolloutID);
+ if (myRollout.getStatus() == status) {
+ return true;
+ }
+ return false;
+ }
+
+ private void validateRolloutGroupActionStatus(final RolloutGroup rolloutGroup,
+ final Map expectedTargetCountStatus) {
+ final RolloutGroup rolloutGroupWithDetail = rolloutGroupManagement
+ .findRolloutGroupWithDetailedStatus(rolloutGroup.getId());
+ validateStatus(rolloutGroupWithDetail.getTotalTargetCountStatus(), expectedTargetCountStatus);
+ }
+
+ private void validateRolloutActionStatus(final Long rolloutId,
+ final Map expectedTargetCountStatus) {
+ final Rollout rolloutWithDetail = rolloutManagement.findRolloutWithDetailedStatus(rolloutId);
+ validateStatus(rolloutWithDetail.getTotalTargetCountStatus(), expectedTargetCountStatus);
+ }
+
+ private void validateStatus(final TotalTargetCountStatus totalTargetCountStatus,
+ final Map expectedTotalCountStates) {
+ for (final Map.Entry entry : expectedTotalCountStates.entrySet()) {
+ final Long countReady = totalTargetCountStatus.getTotalTargetCountByStatus(entry.getKey());
+ assertThat(countReady).isEqualTo(entry.getValue());
+ }
+ }
+
+ private Rollout createSimpleTestRolloutWithTargetsAndDistributionSet(final int amountTargetsForRollout,
+ final int amountOtherTargets, final int groupSize, final String successCondition,
+ final String errorCondition) {
+ final SoftwareModule ah = softwareManagement
+ .createSoftwareModule(new SoftwareModule(appType, "agent-hub", "1.0.1", null, ""));
+ final SoftwareModule jvm = softwareManagement
+ .createSoftwareModule(new SoftwareModule(runtimeType, "oracle-jre", "1.7.2", null, ""));
+ final SoftwareModule os = softwareManagement
+ .createSoftwareModule(new SoftwareModule(osType, "poky", "3.0.2", null, ""));
+ final DistributionSet rolloutDS = distributionSetManagement.createDistributionSet(
+ TestDataUtil.buildDistributionSet("rolloutDS", "0.0.0", standardDsType, os, jvm, ah));
+ targetManagement
+ .createTargets(TestDataUtil.buildTargetFixtures(amountTargetsForRollout, "rollout-", "rollout"));
+ targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountOtherTargets, "others-", "rollout"));
+ final String filterQuery = "controllerId==rollout-*";
+ return createRolloutByVariables("test-rollout-name-1", "test-rollout-description-1", groupSize, filterQuery,
+ rolloutDS, successCondition, errorCondition);
+ }
+
+ private Rollout createTestRolloutWithTargetsAndDistributionSet(final int amountTargetsForRollout,
+ final int groupSize, final String successCondition, final String errorCondition, final String rolloutName,
+ final String targetPrefixName) {
+ final DistributionSet dsForRolloutTwo = TestDataUtil.generateDistributionSet("dsFor" + rolloutName,
+ softwareManagement, distributionSetManagement);
+ targetManagement.createTargets(
+ TestDataUtil.buildTargetFixtures(amountTargetsForRollout, targetPrefixName + "-", targetPrefixName));
+ return createRolloutByVariables(rolloutName, rolloutName + "description", groupSize,
+ "controllerId==" + targetPrefixName + "-*", dsForRolloutTwo, successCondition, errorCondition);
+ }
+
+ private Rollout createRolloutByVariables(final String rolloutName, final String rolloutDescription,
+ final int groupSize, final String filterQuery, final DistributionSet distributionSet,
+ final String successCondition, final String errorCondition) {
+ final RolloutGroupConditions conditions = new RolloutGroup.RolloutGroupConditionBuilder()
+ .successCondition(RolloutGroupSuccessCondition.THRESHOLD, successCondition)
+ .errorCondition(RolloutGroupErrorCondition.THRESHOLD, errorCondition)
+ .errorAction(RolloutGroupErrorAction.PAUSE, null).build();
+ final Rollout rolloutToCreate = new Rollout();
+ rolloutToCreate.setName(rolloutName);
+ rolloutToCreate.setDescription(rolloutDescription);
+ rolloutToCreate.setTargetFilterQuery(filterQuery);
+ rolloutToCreate.setDistributionSet(distributionSet);
+ return rolloutManagement.createRollout(rolloutToCreate, groupSize, conditions);
+ }
+
+ private int changeStatusForAllRunningActions(final Rollout rollout, final Status status) {
+ final List runningActions = deploymentManagement.findActionsByRolloutAndStatus(rollout, Status.RUNNING);
+ for (final Action action : runningActions) {
+ action.setStatus(status);
+ controllerManagament.addUpdateActionStatus(new ActionStatus(action, status, System.currentTimeMillis(), ""),
+ action);
+ }
+ return runningActions.size();
+ }
+
+ private int changeStatusForRunningActions(final Rollout rollout, final Status status,
+ final int amountOfTargetsToGetChanged) {
+ final List runningActions = deploymentManagement.findActionsByRolloutAndStatus(rollout, Status.RUNNING);
+ assertThat(runningActions.size()).isGreaterThanOrEqualTo(amountOfTargetsToGetChanged);
+ for (int i = 0; i < amountOfTargetsToGetChanged; i++) {
+ controllerManagament.addUpdateActionStatus(
+ new ActionStatus(runningActions.get(i), status, System.currentTimeMillis(), ""),
+ runningActions.get(i));
+ }
+ return runningActions.size();
+ }
+
+ private Map createInitStatusMap() {
+ final Map map = new HashMap();
+ for (final TotalTargetCountStatus.Status status : TotalTargetCountStatus.Status.values()) {
+ map.put(status, 0L);
+ }
+ return map;
+ }
+
+}
diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLRolloutGroupFields.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLRolloutGroupFields.java
new file mode 100644
index 000000000..05e26293c
--- /dev/null
+++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLRolloutGroupFields.java
@@ -0,0 +1,94 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.repository.rsql;
+
+import static org.fest.assertions.api.Assertions.assertThat;
+
+import org.eclipse.hawkbit.AbstractIntegrationTest;
+import org.eclipse.hawkbit.TestDataUtil;
+import org.eclipse.hawkbit.repository.RolloutGroupFields;
+import org.eclipse.hawkbit.repository.model.DistributionSet;
+import org.eclipse.hawkbit.repository.model.Rollout;
+import org.eclipse.hawkbit.repository.model.RolloutGroup;
+import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupConditionBuilder;
+import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+
+import ru.yandex.qatools.allure.annotations.Description;
+import ru.yandex.qatools.allure.annotations.Features;
+import ru.yandex.qatools.allure.annotations.Stories;
+
+@Features("Component Tests - RSQL filtering")
+@Stories("RSQL filter rollout group")
+public class RSQLRolloutGroupFields extends AbstractIntegrationTest {
+
+ private Long rolloutGroupId;
+ private Rollout rollout;
+
+ @Before
+ public void seuptBeforeTest() {
+ final int amountTargets = 20;
+ targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout"));
+ final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement,
+ distributionSetManagement);
+ rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*");
+ rollout = rolloutManagement.findRolloutById(rollout.getId());
+ this.rolloutGroupId = rollout.getRolloutGroups().get(0).getId();
+ }
+
+ @Test
+ @Description("Test filter rollout group by id")
+ public void testFilterByParameterId() {
+ assertRSQLQuery(RolloutGroupFields.ID.name() + "==" + rolloutGroupId, 1);
+ assertRSQLQuery(RolloutGroupFields.ID.name() + "==noExist*", 0);
+ assertRSQLQuery(RolloutGroupFields.ID.name() + "=in=(" + rolloutGroupId + ")", 1);
+ assertRSQLQuery(RolloutGroupFields.ID.name() + "=out=(" + rolloutGroupId + ")", 3);
+ }
+
+ @Test
+ @Description("Test filter rollout group by name")
+ public void testFilterByParameterName() {
+ assertRSQLQuery(RolloutGroupFields.NAME.name() + "==group-1", 1);
+ assertRSQLQuery(RolloutGroupFields.NAME.name() + "==*", 4);
+ assertRSQLQuery(RolloutGroupFields.NAME.name() + "==noExist*", 0);
+ assertRSQLQuery(RolloutGroupFields.NAME.name() + "=in=(group-1,group-2)", 2);
+ assertRSQLQuery(RolloutGroupFields.NAME.name() + "=out=(group-1,group-2)", 2);
+ }
+
+ @Test
+ @Description("Test filter rollout group by description")
+ public void testFilterByParameterDescription() {
+ assertRSQLQuery(RolloutGroupFields.DESCRIPTION.name() + "==group-1", 1);
+ assertRSQLQuery(RolloutGroupFields.DESCRIPTION.name() + "==group*", 4);
+ assertRSQLQuery(RolloutGroupFields.DESCRIPTION.name() + "==noExist*", 0);
+ assertRSQLQuery(RolloutGroupFields.DESCRIPTION.name() + "=in=(group-1,notexist)", 1);
+ assertRSQLQuery(RolloutGroupFields.DESCRIPTION.name() + "=out=(group-1,notexist)", 3);
+ }
+
+ private void assertRSQLQuery(final String rsqlParam, final long expcetedTargets) {
+ final Page findTargetPage = rolloutGroupManagement.findRolloutGroupsByPredicate(rollout,
+ RSQLUtility.parse(rsqlParam, RolloutGroupFields.class), new PageRequest(0, 100));
+ final long countTargetsAll = findTargetPage.getTotalElements();
+ assertThat(findTargetPage).isNotNull();
+ assertThat(countTargetsAll).isEqualTo(expcetedTargets);
+ }
+
+ private Rollout createRollout(final String name, final int amountGroups, final long distributionSetId,
+ final String targetFilterQuery) {
+ final Rollout rollout = new Rollout();
+ rollout.setDistributionSet(distributionSetManagement.findDistributionSetById(distributionSetId));
+ rollout.setName(name);
+ rollout.setTargetFilterQuery(targetFilterQuery);
+ return rolloutManagement.createRollout(rollout, amountGroups, new RolloutGroupConditionBuilder()
+ .successCondition(RolloutGroupSuccessCondition.THRESHOLD, "100").build());
+ }
+}
diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/RestConstants.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/RestConstants.java
index 33b154844..706584a64 100644
--- a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/RestConstants.java
+++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/RestConstants.java
@@ -154,6 +154,16 @@ public final class RestConstants {
*/
public static final String DISTRIBUTIONSET_V1_REQUEST_MAPPING = BASE_V1_REQUEST_MAPPING + "/distributionsets";
+ /**
+ * The rollout URL mapping rest resource.
+ */
+ public static final String ROLLOUT_V1_REQUEST_MAPPING = BASE_V1_REQUEST_MAPPING + "/rollouts";
+
+ /**
+ * Request parameter for async
+ */
+ public static final String REQUEST_PARAMETER_ASYNC = "async";
+
/**
* The target URL mapping, href link for artifact download.
*/
diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/action/ActionPagedList.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/action/ActionPagedList.java
index f8908277f..27b36779a 100644
--- a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/action/ActionPagedList.java
+++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/action/ActionPagedList.java
@@ -14,7 +14,7 @@ import java.util.List;
import org.eclipse.hawkbit.rest.resource.model.PagedList;
/**
- * Paged list rest model for {@link Action} to RESTful API representation.
+ * Paged list rest model for {@link ErrorAction} to RESTful API representation.
*
*/
public class ActionPagedList extends PagedList {
diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutCondition.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutCondition.java
new file mode 100644
index 000000000..914a5d5c2
--- /dev/null
+++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutCondition.java
@@ -0,0 +1,69 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.rest.resource.model.rollout;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+
+/**
+ *
+ */
+@JsonInclude(Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class RolloutCondition {
+
+ private Condition condition = Condition.THRESHOLD;
+ private String expression = "100";
+
+ /**
+ *
+ */
+ public RolloutCondition() {
+ }
+
+ public RolloutCondition(final Condition condition, final String expression) {
+ this.condition = condition;
+ this.expression = expression;
+ }
+
+ /**
+ * @return the condition
+ */
+ public Condition getCondition() {
+ return condition;
+ }
+
+ /**
+ * @param condition
+ * the condition to set
+ */
+ public void setCondition(final Condition condition) {
+ this.condition = condition;
+ }
+
+ /**
+ * @return the expession
+ */
+ public String getExpression() {
+ return expression;
+ }
+
+ /**
+ * @param expession
+ * the expession to set
+ */
+ public void setExpression(final String expession) {
+ this.expression = expession;
+ }
+
+ public enum Condition {
+ THRESHOLD;
+ }
+}
diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutErrorAction.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutErrorAction.java
new file mode 100644
index 000000000..2ed6077e6
--- /dev/null
+++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutErrorAction.java
@@ -0,0 +1,58 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.rest.resource.model.rollout;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+
+/**
+ *
+ */
+@JsonInclude(Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class RolloutErrorAction {
+
+ private ErrorAction action = ErrorAction.PAUSE;
+ private String expression = null;
+
+ /**
+ * @return the action
+ */
+ public ErrorAction getAction() {
+ return action;
+ }
+
+ /**
+ * @param action
+ * the action to set
+ */
+ public void setAction(final ErrorAction action) {
+ this.action = action;
+ }
+
+ /**
+ * @return the expression
+ */
+ public String getExpression() {
+ return expression;
+ }
+
+ /**
+ * @param expression
+ * the expression to set
+ */
+ public void setExpression(final String expression) {
+ this.expression = expression;
+ }
+
+ public enum ErrorAction {
+ PAUSE;
+ }
+}
diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutPagedList.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutPagedList.java
new file mode 100644
index 000000000..ea1544991
--- /dev/null
+++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutPagedList.java
@@ -0,0 +1,36 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.rest.resource.model.rollout;
+
+import java.util.List;
+
+import org.eclipse.hawkbit.rest.resource.model.PagedList;
+
+/**
+ * Paged list for Rollout.
+ *
+ *
+ */
+public class RolloutPagedList extends PagedList {
+
+ private final List content;
+
+ public RolloutPagedList(final List content, final long total) {
+ super(content, total);
+ this.content = content;
+ }
+
+ /**
+ * @return the content of the paged list. Never {@code null}.
+ */
+ public List getContent() {
+ return content;
+ }
+
+}
diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutResponseBody.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutResponseBody.java
new file mode 100644
index 000000000..dd1296cfd
--- /dev/null
+++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutResponseBody.java
@@ -0,0 +1,124 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.rest.resource.model.rollout;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.hawkbit.rest.resource.model.NamedEntityRest;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ *
+ */
+@JsonInclude(Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class RolloutResponseBody extends NamedEntityRest {
+
+ private String targetFilterQuery;
+ private Long distributionSetId;
+
+ @JsonProperty(value = "id", required = true)
+ private Long rolloutId;
+
+ @JsonProperty(required = true)
+ private String status;
+
+ @JsonProperty(required = true)
+ private Long totalTargets;
+
+ @JsonProperty(required = true)
+ private final Map totalTargetsPerStatus = new HashMap<>();
+
+ /**
+ * @return the status
+ */
+ public String getStatus() {
+ return status;
+ }
+
+ /**
+ * @param status
+ * the status to set
+ */
+ public void setStatus(final String status) {
+ this.status = status;
+ }
+
+ /**
+ * @return the rolloutId
+ */
+ public Long getRolloutId() {
+ return rolloutId;
+ }
+
+ /**
+ * @param rolloutId
+ * the rolloutId to set
+ */
+ public void setRolloutId(final Long rolloutId) {
+ this.rolloutId = rolloutId;
+ }
+
+ /**
+ * @return the targetFilterQuery
+ */
+ public String getTargetFilterQuery() {
+ return targetFilterQuery;
+ }
+
+ /**
+ * @param targetFilterQuery
+ * the targetFilterQuery to set
+ */
+ public void setTargetFilterQuery(final String targetFilterQuery) {
+ this.targetFilterQuery = targetFilterQuery;
+ }
+
+ /**
+ * @return the distributionSetId
+ */
+ public Long getDistributionSetId() {
+ return distributionSetId;
+ }
+
+ /**
+ * @param distributionSetId
+ * the distributionSetId to set
+ */
+ public void setDistributionSetId(final Long distributionSetId) {
+ this.distributionSetId = distributionSetId;
+ }
+
+ /**
+ * @param totalTargets
+ * the totalTargets to set
+ */
+ public void setTotalTargets(final Long totalTargets) {
+ this.totalTargets = totalTargets;
+ }
+
+ /**
+ * @return the totalTargets
+ */
+ public Long getTotalTargets() {
+ return totalTargets;
+ }
+
+ /**
+ * @return the totalTargetsPerStatus
+ */
+ public Map getTotalTargetsPerStatus() {
+ return totalTargetsPerStatus;
+ }
+}
diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutRestRequestBody.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutRestRequestBody.java
new file mode 100644
index 000000000..0b83948ff
--- /dev/null
+++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutRestRequestBody.java
@@ -0,0 +1,175 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.rest.resource.model.rollout;
+
+import org.eclipse.hawkbit.rest.resource.model.NamedEntityRest;
+import org.eclipse.hawkbit.rest.resource.model.distributionset.ActionTypeRest;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+
+/**
+ * Model for request containing a rollout body e.g. in a POST request of
+ * creating a rollout via REST API.
+ */
+@JsonInclude(Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class RolloutRestRequestBody extends NamedEntityRest {
+
+ private String targetFilterQuery;
+ private long distributionSetId;
+
+ private int amountGroups = 1;
+
+ private RolloutCondition successCondition = new RolloutCondition();
+ private RolloutSuccessAction successAction = new RolloutSuccessAction();
+ private RolloutCondition errorCondition = null;
+ private RolloutErrorAction errorAction = null;
+
+ private Long forcetime;
+
+ private ActionTypeRest type;
+
+ /**
+ * @return the finishCondition
+ */
+ public RolloutCondition getSuccessCondition() {
+ return successCondition;
+ }
+
+ /**
+ * @param successCondition
+ * the finishCondition to set
+ */
+ public void setSuccessCondition(final RolloutCondition successCondition) {
+ this.successCondition = successCondition;
+ }
+
+ /**
+ * @return the successAction
+ */
+ public RolloutSuccessAction getSuccessAction() {
+ return successAction;
+ }
+
+ /**
+ * @param successAction
+ * the successAction to set
+ */
+ public void setSuccessAction(final RolloutSuccessAction successAction) {
+ this.successAction = successAction;
+ }
+
+ /**
+ * @return the errorCondition
+ */
+ public RolloutCondition getErrorCondition() {
+ return errorCondition;
+ }
+
+ /**
+ * @param errorCondition
+ * the errorCondition to set
+ */
+ public void setErrorCondition(final RolloutCondition errorCondition) {
+ this.errorCondition = errorCondition;
+ }
+
+ /**
+ * @return the targetFilterQuery
+ */
+ public String getTargetFilterQuery() {
+ return targetFilterQuery;
+ }
+
+ /**
+ * @param targetFilterQuery
+ * the targetFilterQuery to set
+ */
+ public void setTargetFilterQuery(final String targetFilterQuery) {
+ this.targetFilterQuery = targetFilterQuery;
+ }
+
+ /**
+ * @return the distributionSetId
+ */
+ public long getDistributionSetId() {
+ return distributionSetId;
+ }
+
+ /**
+ * @param distributionSetId
+ * the distributionSetId to set
+ */
+ public void setDistributionSetId(final long distributionSetId) {
+ this.distributionSetId = distributionSetId;
+ }
+
+ /**
+ * @return the groupSize
+ */
+ public int getAmountGroups() {
+ return amountGroups;
+ }
+
+ /**
+ * @param groupSize
+ * the groupSize to set
+ */
+ public void setAmountGroups(final int groupSize) {
+ this.amountGroups = groupSize;
+ }
+
+ /**
+ * @return the forcetime
+ */
+ public Long getForcetime() {
+ return forcetime;
+ }
+
+ /**
+ * @param forcetime
+ * the forcetime to set
+ */
+ public void setForcetime(final Long forcetime) {
+ this.forcetime = forcetime;
+ }
+
+ /**
+ * @return the type
+ */
+ public ActionTypeRest getType() {
+ return type;
+ }
+
+ /**
+ * @param type
+ * the type to set
+ */
+ public void setType(final ActionTypeRest type) {
+ this.type = type;
+ }
+
+ /**
+ * @return the errorAction
+ */
+ public RolloutErrorAction getErrorAction() {
+ return errorAction;
+ }
+
+ /**
+ * @param errorAction
+ * the errorAction to set
+ */
+ public void setErrorAction(final RolloutErrorAction errorAction) {
+ this.errorAction = errorAction;
+ }
+
+}
diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutSuccessAction.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutSuccessAction.java
new file mode 100644
index 000000000..f14e9a8bf
--- /dev/null
+++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutSuccessAction.java
@@ -0,0 +1,69 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.rest.resource.model.rollout;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+
+/**
+ *
+ */
+@JsonInclude(Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class RolloutSuccessAction {
+
+ private SuccessAction action = SuccessAction.NEXTGROUP;
+ private String expression = null;
+
+ /**
+ *
+ */
+ public RolloutSuccessAction() {
+ }
+
+ public RolloutSuccessAction(final SuccessAction action, final String expression) {
+ this.action = action;
+ this.expression = expression;
+ }
+
+ /**
+ * @return the action
+ */
+ public SuccessAction getAction() {
+ return action;
+ }
+
+ /**
+ * @param action
+ * the action to set
+ */
+ public void setAction(final SuccessAction action) {
+ this.action = action;
+ }
+
+ /**
+ * @return the expession
+ */
+ public String getExpression() {
+ return expression;
+ }
+
+ /**
+ * @param expession
+ * the expession to set
+ */
+ public void setExpression(final String expession) {
+ this.expression = expession;
+ }
+
+ public enum SuccessAction {
+ NEXTGROUP;
+ }
+}
diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rolloutgroup/RolloutGroupPagedList.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rolloutgroup/RolloutGroupPagedList.java
new file mode 100644
index 000000000..a341ac6ba
--- /dev/null
+++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rolloutgroup/RolloutGroupPagedList.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.rest.resource.model.rolloutgroup;
+
+import java.util.List;
+
+import org.eclipse.hawkbit.rest.resource.model.PagedList;
+
+/**
+ * Paged list for Rollout.
+ *
+ */
+public class RolloutGroupPagedList extends PagedList {
+
+ private final List content;
+
+ public RolloutGroupPagedList(final List content, final long total) {
+ super(content, total);
+ this.content = content;
+ }
+
+ /**
+ * @return the content of the paged list. Never {@code null}.
+ */
+ public List getContent() {
+ return content;
+ }
+
+}
diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rolloutgroup/RolloutGroupResponseBody.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rolloutgroup/RolloutGroupResponseBody.java
new file mode 100644
index 000000000..bb1b0116e
--- /dev/null
+++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rolloutgroup/RolloutGroupResponseBody.java
@@ -0,0 +1,61 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.rest.resource.model.rolloutgroup;
+
+import org.eclipse.hawkbit.rest.resource.model.NamedEntityRest;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Model for the rollout group annotated with json-annotations for easier
+ * serialization and de-serialization.
+ */
+@JsonInclude(Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class RolloutGroupResponseBody extends NamedEntityRest {
+
+ @JsonProperty(value = "id", required = true)
+ private Long rolloutGroupId;
+
+ @JsonProperty(required = true)
+ private String status;
+
+ /**
+ * @return the rolloutGroupId
+ */
+ public Long getRolloutGroupId() {
+ return rolloutGroupId;
+ }
+
+ /**
+ * @param rolloutGroupId
+ * the rolloutGroupId to set
+ */
+ public void setRolloutGroupId(final Long rolloutGroupId) {
+ this.rolloutGroupId = rolloutGroupId;
+ }
+
+ /**
+ * @return the status
+ */
+ public String getStatus() {
+ return status;
+ }
+
+ /**
+ * @param status
+ * the status to set
+ */
+ public void setStatus(final String status) {
+ this.status = status;
+ }
+}
diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/cache/CacheWriteNotify.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/cache/CacheWriteNotify.java
deleted file mode 100644
index 1bf5704e1..000000000
--- a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/cache/CacheWriteNotify.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/**
- * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
- *
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- */
-package org.eclipse.hawkbit.cache;
-
-import org.eclipse.hawkbit.eventbus.event.DownloadProgressEvent;
-import org.eclipse.hawkbit.repository.model.Action;
-import org.eclipse.hawkbit.repository.model.ActionStatus;
-import org.eclipse.hawkbit.tenancy.TenantAware;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.cache.Cache;
-import org.springframework.cache.CacheManager;
-import org.springframework.stereotype.Service;
-
-import com.google.common.eventbus.EventBus;
-
-/**
- * An service which combines the functionality for functional use cases to write
- * into the cache an notify the writing to the cache to the {@link EventBus}.
- *
- *
- *
- *
- */
-@Service
-public class CacheWriteNotify {
-
- /**
- *
- */
- private static final int DOWNLOAD_PROGRESS_MAX = 100;
-
- @Autowired
- private CacheManager cacheManager;
-
- @Autowired
- private EventBus eventBus;
-
- @Autowired
- private TenantAware tenantAware;
-
- /**
- * writes the download progress in percentage into the cache
- * {@link CacheKeys#DOWNLOAD_PROGRESS_PERCENT} and notifies the
- * {@link EventBus} with a {@link DownloadProgressEvent}.
- *
- * @param statusId
- * the ID of the {@link ActionStatus}
- * @param progressPercent
- * the progress in percentage which must be between 0-100
- */
- public void downloadProgressPercent(final long statusId, final int progressPercent) {
-
- final Cache cache = cacheManager.getCache(Action.class.getName());
- final String cacheKey = CacheKeys.entitySpecificCacheKey(String.valueOf(statusId),
- CacheKeys.DOWNLOAD_PROGRESS_PERCENT);
- if (progressPercent < DOWNLOAD_PROGRESS_MAX) {
- cache.put(cacheKey, progressPercent);
- } else {
- // in case we reached progress 100 delete the cache value again
- // because otherwise he will
- // keep there forever
- cache.evict(cacheKey);
- }
-
- eventBus.post(new DownloadProgressEvent(tenantAware.getCurrentTenant(), statusId, progressPercent));
- }
-
- /**
- * @param cacheManager
- * the cacheManager to set
- */
- void setCacheManager(final CacheManager cacheManager) {
- this.cacheManager = cacheManager;
- }
-
- /**
- * @param eventBus
- * the eventBus to set
- */
- void setEventBus(final EventBus eventBus) {
- this.eventBus = eventBus;
- }
-
- /**
- * @param tenantAware
- * the tenantAware to set
- */
- void setTenantAware(final TenantAware tenantAware) {
- this.tenantAware = tenantAware;
- }
-}
diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/PagingUtility.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/PagingUtility.java
index afefedb5c..1503b8bda 100644
--- a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/PagingUtility.java
+++ b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/PagingUtility.java
@@ -12,6 +12,8 @@ import org.eclipse.hawkbit.repository.ActionFields;
import org.eclipse.hawkbit.repository.ActionStatusFields;
import org.eclipse.hawkbit.repository.DistributionSetFields;
import org.eclipse.hawkbit.repository.DistributionSetMetadataFields;
+import org.eclipse.hawkbit.repository.RolloutFields;
+import org.eclipse.hawkbit.repository.RolloutGroupFields;
import org.eclipse.hawkbit.repository.SoftwareModuleFields;
import org.eclipse.hawkbit.repository.SoftwareModuleMetadataFields;
import org.eclipse.hawkbit.repository.TargetFields;
@@ -127,4 +129,25 @@ public final class PagingUtility {
return sorting;
}
+ static Sort sanitizeRolloutSortParam(final String sortParam) {
+ final Sort sorting;
+ if (sortParam != null) {
+ sorting = new Sort(SortUtility.parse(RolloutFields.class, sortParam));
+ } else {
+ // default sort
+ sorting = new Sort(Direction.ASC, RolloutFields.NAME.getFieldName());
+ }
+ return sorting;
+ }
+
+ static Sort sanitizeRolloutGroupSortParam(final String sortParam) {
+ final Sort sorting;
+ if (sortParam != null) {
+ sorting = new Sort(SortUtility.parse(RolloutGroupFields.class, sortParam));
+ } else {
+ // default sort
+ sorting = new Sort(Direction.ASC, RolloutGroupFields.NAME.getFieldName());
+ }
+ return sorting;
+ }
}
diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/ResponseExceptionHandler.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/ResponseExceptionHandler.java
index d222a96fd..f054e4bc5 100644
--- a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/ResponseExceptionHandler.java
+++ b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/ResponseExceptionHandler.java
@@ -64,7 +64,7 @@ public class ResponseExceptionHandler {
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_DS_TYPE_UNDEFINED, HttpStatus.BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_TENANT_NOT_EXISTS, HttpStatus.BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ENTITY_LOCKED, HttpStatus.LOCKED);
-
+ ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ROLLOUT_ILLEGAL_STATE, HttpStatus.BAD_REQUEST);
}
private static HttpStatus getStatusOrDefault(final SpServerError error) {
@@ -84,8 +84,7 @@ public class ResponseExceptionHandler {
* as entity.
*/
@ExceptionHandler(SpServerRtException.class)
- public ResponseEntity handleSpServerRtExceptions(final HttpServletRequest request,
- final Exception ex) {
+ public ResponseEntity handleSpServerRtExceptions(final HttpServletRequest request, final Exception ex) {
LOG.debug("Handling exception of request {}", request.getRequestURL());
final ExceptionInfo response = new ExceptionInfo();
final HttpStatus responseStatus;
diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/RolloutMapper.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/RolloutMapper.java
new file mode 100644
index 000000000..712595638
--- /dev/null
+++ b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/RolloutMapper.java
@@ -0,0 +1,162 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.rest.resource;
+
+import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
+import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.hawkbit.repository.model.Action.ActionType;
+import org.eclipse.hawkbit.repository.model.DistributionSet;
+import org.eclipse.hawkbit.repository.model.Rollout;
+import org.eclipse.hawkbit.repository.model.RolloutGroup;
+import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorAction;
+import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorCondition;
+import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessAction;
+import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition;
+import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus;
+import org.eclipse.hawkbit.rest.resource.helper.RestResourceConversionHelper;
+import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutCondition.Condition;
+import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutErrorAction.ErrorAction;
+import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutResponseBody;
+import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutRestRequestBody;
+import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutSuccessAction.SuccessAction;
+import org.eclipse.hawkbit.rest.resource.model.rolloutgroup.RolloutGroupResponseBody;
+
+/**
+ * A mapper which maps repository model to RESTful model representation and
+ * back.
+ *
+ *
+ */
+final class RolloutMapper {
+
+ private static final String NOT_SUPPORTED = " is not supported";
+
+ private RolloutMapper() {
+ // Utility class
+ }
+
+ static List toResponseRollout(final List rollouts) {
+ final List result = new ArrayList<>(rollouts.size());
+ rollouts.forEach(r -> result.add(toResponseRollout(r)));
+ return result;
+ }
+
+ static RolloutResponseBody toResponseRollout(final Rollout rollout) {
+ final RolloutResponseBody body = new RolloutResponseBody();
+ body.setCreatedAt(rollout.getCreatedAt());
+ body.setCreatedBy(rollout.getCreatedBy());
+ body.setDescription(rollout.getDescription());
+ body.setLastModifiedAt(rollout.getLastModifiedAt());
+ body.setLastModifiedBy(rollout.getLastModifiedBy());
+ body.setName(rollout.getName());
+ body.setRolloutId(rollout.getId());
+ body.setTargetFilterQuery(rollout.getTargetFilterQuery());
+ body.setDistributionSetId(rollout.getDistributionSet().getId());
+ body.setStatus(rollout.getStatus().toString().toLowerCase());
+ body.setTotalTargets(rollout.getTotalTargets());
+
+ for (final TotalTargetCountStatus.Status status : TotalTargetCountStatus.Status.values()) {
+ body.getTotalTargetsPerStatus().put(status.name().toLowerCase(),
+ rollout.getTotalTargetCountStatus().getTotalTargetCountByStatus(status));
+ }
+
+ body.add(linkTo(methodOn(RolloutResource.class).getRollout(rollout.getId())).withRel("self"));
+ body.add(linkTo(methodOn(RolloutResource.class).start(rollout.getId(), false)).withRel("start"));
+ body.add(linkTo(methodOn(RolloutResource.class).start(rollout.getId(), true)).withRel("startAsync"));
+ body.add(linkTo(methodOn(RolloutResource.class).pause(rollout.getId())).withRel("pause"));
+ body.add(linkTo(methodOn(RolloutResource.class).resume(rollout.getId())).withRel("resume"));
+ body.add(linkTo(methodOn(RolloutResource.class).getRolloutGroups(rollout.getId(),
+ Integer.parseInt(RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET),
+ Integer.parseInt(RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT), null, null)).withRel("groups"));
+ return body;
+ }
+
+ static Rollout fromRequest(final RolloutRestRequestBody restRequest, final DistributionSet distributionSet,
+ final String filterQuery) {
+ final Rollout rollout = new Rollout();
+ rollout.setName(restRequest.getName());
+ rollout.setDescription(restRequest.getDescription());
+ rollout.setDistributionSet(distributionSet);
+ rollout.setTargetFilterQuery(filterQuery);
+ final ActionType convertActionType = RestResourceConversionHelper.convertActionType(restRequest.getType());
+ if (convertActionType != null) {
+ rollout.setActionType(convertActionType);
+ }
+ if (restRequest.getForcetime() != null) {
+ rollout.setForcedTime(restRequest.getForcetime());
+
+ }
+ return rollout;
+ }
+
+ static List toResponseRolloutGroup(final List rollouts) {
+ final List