diff --git a/docs/src/main/resources/documentation/images/gettingStartedResult.png b/docs/src/main/resources/documentation/images/gettingStartedResult.png index fd1203def..82b546290 100644 Binary files a/docs/src/main/resources/documentation/images/gettingStartedResult.png and b/docs/src/main/resources/documentation/images/gettingStartedResult.png differ diff --git a/docs/src/main/resources/documentation/images/ui/artifact_mgmt.png b/docs/src/main/resources/documentation/images/ui/artifact_mgmt.png index a07459a6d..aec5b8655 100644 Binary files a/docs/src/main/resources/documentation/images/ui/artifact_mgmt.png and b/docs/src/main/resources/documentation/images/ui/artifact_mgmt.png differ diff --git a/docs/src/main/resources/documentation/images/ui/deployment_mgmt.png b/docs/src/main/resources/documentation/images/ui/deployment_mgmt.png index c2b7f3578..8546b07eb 100644 Binary files a/docs/src/main/resources/documentation/images/ui/deployment_mgmt.png and b/docs/src/main/resources/documentation/images/ui/deployment_mgmt.png differ diff --git a/docs/src/main/resources/documentation/images/ui/distribution_mgmt.png b/docs/src/main/resources/documentation/images/ui/distribution_mgmt.png index c3f716b0c..287301bc8 100644 Binary files a/docs/src/main/resources/documentation/images/ui/distribution_mgmt.png and b/docs/src/main/resources/documentation/images/ui/distribution_mgmt.png differ diff --git a/docs/src/main/resources/documentation/images/ui/rollout-mgmt.png b/docs/src/main/resources/documentation/images/ui/rollout-mgmt.png new file mode 100644 index 000000000..23f60ec0d Binary files /dev/null and b/docs/src/main/resources/documentation/images/ui/rollout-mgmt.png differ diff --git a/docs/src/main/resources/documentation/images/ui/rollout_groups.png b/docs/src/main/resources/documentation/images/ui/rollout_groups.png index e79d81e99..972b75bfb 100644 Binary files a/docs/src/main/resources/documentation/images/ui/rollout_groups.png and b/docs/src/main/resources/documentation/images/ui/rollout_groups.png differ diff --git a/docs/src/main/resources/documentation/images/ui/rollout_mgmt.png b/docs/src/main/resources/documentation/images/ui/rollout_mgmt.png deleted file mode 100644 index 0bf6501b8..000000000 Binary files a/docs/src/main/resources/documentation/images/ui/rollout_mgmt.png and /dev/null differ diff --git a/docs/src/main/resources/documentation/images/ui/target_filter.png b/docs/src/main/resources/documentation/images/ui/target_filter.png index 660b1210b..a6bd8a529 100644 Binary files a/docs/src/main/resources/documentation/images/ui/target_filter.png and b/docs/src/main/resources/documentation/images/ui/target_filter.png differ diff --git a/docs/src/main/resources/documentation/interfaces/management-api.md b/docs/src/main/resources/documentation/interfaces/management-api.md index f7c0dacfb..e314599fa 100644 --- a/docs/src/main/resources/documentation/interfaces/management-api.md +++ b/docs/src/main/resources/documentation/interfaces/management-api.md @@ -74,4 +74,4 @@ A _Distribution Set_ entity may have for example URIs to artifacts, _Software Mo "metadata": { "href": "http://localhost:8080/rest/v1/softwaremodules/83/metadata?offset=0&limit=50" } -{% endhighlight %} +{% endhighlight %} \ No newline at end of file diff --git a/docs/src/main/resources/documentation/interfaces/management-ui.md b/docs/src/main/resources/documentation/interfaces/management-ui.md index 2ecb75898..69f07e15d 100644 --- a/docs/src/main/resources/documentation/interfaces/management-ui.md +++ b/docs/src/main/resources/documentation/interfaces/management-ui.md @@ -83,8 +83,8 @@ Allows to: Software rollout in large scale, rollout status overview and rollout management. ## Features explained -- Create, update and start of rollouts. -- Pause and resume of rollouts. +- Create, update, copy and delete of rollouts. +- Start, pause and resume of rollouts. - Progress monitoring for the entire rollout and the individual groups. - Drill down to see the groups in a rollout and targets in each group. - Rollout attributes: diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java index 852fdcaf6..6b865d4e3 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java @@ -153,6 +153,7 @@ public class DeviceSimulatorUpdater { if (device.getProgress() <= 0 && modules != null) { device.setUpdateStatus(simulateDownloads(device.getTargetSecurityToken())); if (isErrorResponse(device.getUpdateStatus())) { + device.setProgress(1.0); callback.updateFinished(device, actionId); eventbus.post(new ProgressUpdate(device)); return; @@ -216,8 +217,11 @@ public class DeviceSimulatorUpdater { private static UpdateStatus downloadUrl(final String url, final String targetToken, final String sha1Hash, final long size) { - LOGGER.debug("Downloading {} with token {}, expected sha1 hash {} and size {}", url, - hideTokenDetails(targetToken), sha1Hash, size); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Downloading {} with token {}, expected sha1 hash {} and size {}", url, + hideTokenDetails(targetToken), sha1Hash, size); + } try { return readAndCheckDownloadUrl(url, targetToken, sha1Hash, size); diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/SpSenderService.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/SpSenderService.java index 16bc4f8d0..74e59e6f6 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/SpSenderService.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/SpSenderService.java @@ -232,7 +232,7 @@ public class SpSenderService extends SenderService { headers.put(MessageHeaderKey.TENANT, tenant); headers.put(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ACTION_STATUS.name()); headers.put(MessageHeaderKey.CONTENT_TYPE, MessageProperties.CONTENT_TYPE_JSON); - actionUpdateStatus.getMessage().addAll(updateResultMessages); + actionUpdateStatus.addMessage(updateResultMessages); actionUpdateStatus.setActionId(actionId); return convertMessage(actionUpdateStatus, messageProperties); diff --git a/examples/hawkbit-example-app/src/main/resources/application-mysql.properties b/examples/hawkbit-example-app/src/main/resources/application-mysql.properties index 202500245..352f128b8 100644 --- a/examples/hawkbit-example-app/src/main/resources/application-mysql.properties +++ b/examples/hawkbit-example-app/src/main/resources/application-mysql.properties @@ -17,16 +17,16 @@ spring.datasource.username=root spring.datasource.password= spring.datasource.driverClassName=org.mariadb.jdbc.Driver -spring.datasource.max-active=100 -spring.datasource.max-idle=10 -spring.datasource.min-idle=10 -spring.datasource.initial-size=10 -spring.datasource.validation-query=select 1 from dual -spring.datasource.validation-interval=30000 -spring.datasource.test-on-borrow=true -spring.datasource.test-on-return=false -spring.datasource.test-while-idle=true -spring.datasource.time-between-eviction-runs-millis=30000 -spring.datasource.min-evictable-idle-time-millis=60000 -spring.datasource.max-wait=10000 -spring.datasource.jmx-enabled=true +spring.datasource.tomcat.max-active=100 +spring.datasource.tomcat.max-idle=10 +spring.datasource.tomcat.min-idle=10 +spring.datasource.tomcat.initial-size=10 +spring.datasource.tomcat.validation-query=select 1 from dual +spring.datasource.tomcat.validation-interval=30000 +spring.datasource.tomcat.test-on-borrow=true +spring.datasource.tomcat.test-on-return=false +spring.datasource.tomcat.test-while-idle=true +spring.datasource.tomcat.time-between-eviction-runs-millis=30000 +spring.datasource.tomcat.min-evictable-idle-time-millis=60000 +spring.datasource.tomcat.max-wait=10000 +spring.datasource.tomcat.jmx-enabled=true diff --git a/examples/hawkbit-example-mgmt-simulator/src/main/java/org/eclipse/hawkbit/mgmt/client/scenarios/ConfigurableScenario.java b/examples/hawkbit-example-mgmt-simulator/src/main/java/org/eclipse/hawkbit/mgmt/client/scenarios/ConfigurableScenario.java index a87e9ece9..7b911e5b8 100644 --- a/examples/hawkbit-example-mgmt-simulator/src/main/java/org/eclipse/hawkbit/mgmt/client/scenarios/ConfigurableScenario.java +++ b/examples/hawkbit-example-mgmt-simulator/src/main/java/org/eclipse/hawkbit/mgmt/client/scenarios/ConfigurableScenario.java @@ -41,6 +41,7 @@ import org.eclipse.hawkbit.mgmt.json.model.tag.MgmtTag; import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; import com.google.common.collect.Lists; @@ -123,7 +124,6 @@ public class ConfigurableScenario { .createTargetTags(new TagBuilder().name(group).description("Group " + group).build()).getBody() .get(0).getTagId()) .collect(Collectors.toList()); - } private void cleanRepository() { @@ -136,11 +136,20 @@ public class ConfigurableScenario { } private void deleteRollouts() { - // TODO: complete this as soon as rollouts can be deleted + LOGGER.info("Delete Rollouts"); + PagedList rollouts; + + while ((rollouts = rolloutResource.getRollouts(0, PAGE_SIZE, null, null).getBody()).getTotal() > 0) { + rollouts.getContent().parallelStream().forEach(rollout -> { + rolloutResource.delete(rollout.getRolloutId()); + waitUntilRolloutNoLongerExists(rollout.getRolloutId()); + }); + } } private void deleteSoftwareModules() { + LOGGER.info("Delete SoftwareModules"); PagedList modules; do { modules = softwareModuleResource.getSoftwareModules(0, PAGE_SIZE, null, null).getBody(); @@ -150,6 +159,8 @@ public class ConfigurableScenario { } private void deleteDistributionSets() { + LOGGER.info("Delete DistributionSets"); + PagedList distributionSets; do { distributionSets = distributionSetResource.getDistributionSets(0, PAGE_SIZE, null, null).getBody(); @@ -170,6 +181,8 @@ public class ConfigurableScenario { } private void deleteTargets() { + LOGGER.info("Delete Targets"); + PagedList targets; do { targets = targetResource.getTargets(0, PAGE_SIZE, null, null).getBody(); @@ -203,8 +216,9 @@ public class ConfigurableScenario { LOGGER.info("Run semi automatic rollout for set {}", set.getDsId()); // create a Rollout final MgmtRolloutResponseBody rolloutResponseBody = rolloutResource.create(new RolloutBuilder() - .name("Rollout" + set.getName() + set.getVersion()).semiAutomaticGroups(createRolloutGroups(scenario)) - .targetFilterQuery("name==*").distributionSetId(set.getDsId()) + .name("SemiAutomaticRollout" + set.getName() + set.getVersion()) + .semiAutomaticGroups(createRolloutGroups(scenario)).targetFilterQuery("name==*") + .distributionSetId(set.getDsId()) .successThreshold(String.valueOf(scenario.getRolloutSuccessThreshold())).errorThreshold("5").build()) .getBody(); @@ -218,13 +232,15 @@ public class ConfigurableScenario { } private static List createRolloutGroups(final Scenario scenario) { - final List result = Lists.newArrayListWithCapacity(scenario.getDeviceGroups().size() * 2); + final List result = Lists + .newArrayListWithExpectedSize((scenario.getDeviceGroups().size() * 3) + 1); scenario.getDeviceGroups().forEach(groupname -> { result.add(createGroup(1, groupname, 10F)); result.add(createGroup(2, groupname, 50F)); result.add(createGroup(3, groupname, 100F)); }); + result.add(createFinalGroup()); return result; } @@ -238,6 +254,15 @@ public class ConfigurableScenario { return one; } + private static MgmtRolloutGroup createFinalGroup() { + final MgmtRolloutGroup one = new MgmtRolloutGroup(); + one.setName("final"); + one.setDescription("Group of non tagged devices"); + one.setTargetFilterQuery("name==*"); + one.setTargetPercentage(100F); + return one; + } + private void runRollout(final MgmtDistributionSet set, final Scenario scenario) { LOGGER.info("Run rollout for set {}", set.getDsId()); // create a Rollout @@ -256,6 +281,17 @@ public class ConfigurableScenario { LOGGER.info("Run rollout for set {} -> Done", set.getDsId()); } + private void waitUntilRolloutNoLongerExists(final Long id) { + do { + try { + TimeUnit.SECONDS.sleep(5); + } catch (final InterruptedException e) { + LOGGER.warn("Interrupted!"); + Thread.currentThread().interrupt(); + } + } while (rolloutResource.getRollout(id).getStatusCode() != HttpStatus.NOT_FOUND); + } + private void waitUntilRolloutIsComplete(final Long id) { do { try { diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/mgmt/ui/MgmtUiAutoConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/mgmt/ui/MgmtUiAutoConfiguration.java index 01ada60f2..06b141bd4 100644 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/mgmt/ui/MgmtUiAutoConfiguration.java +++ b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/mgmt/ui/MgmtUiAutoConfiguration.java @@ -67,7 +67,7 @@ public class MgmtUiAutoConfiguration { * UI has an own strategy. * * @param applicationContext - * the context to add the listener + * the context to add the listener to * @param executorService * the general scheduler service * @param eventBus diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/repository/JpaRepositoryAutoConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/repository/JpaRepositoryAutoConfiguration.java index 73c6935d5..0671a0d34 100644 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/repository/JpaRepositoryAutoConfiguration.java +++ b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/repository/JpaRepositoryAutoConfiguration.java @@ -16,6 +16,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.integration.support.locks.DefaultLockRegistry; +import org.springframework.integration.support.locks.LockRegistry; /** * Auto-Configuration for enabling JPA repository. @@ -36,4 +38,9 @@ public class JpaRepositoryAutoConfiguration { return new VirtualPropertyResolver(); } + @Bean + @ConditionalOnMissingBean + public LockRegistry lockRegistry() { + return new DefaultLockRegistry(); + } } diff --git a/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DataConversionHelper.java b/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DataConversionHelper.java index 8914f5f03..50a7629f7 100644 --- a/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DataConversionHelper.java +++ b/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DataConversionHelper.java @@ -13,7 +13,6 @@ import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; import java.io.IOException; import java.util.List; -import java.util.Optional; import java.util.stream.Collectors; import javax.servlet.http.HttpServletResponse; @@ -101,16 +100,16 @@ public final class DataConversionHelper { } - static DdiControllerBase fromTarget(final Target target, final Optional action, + static DdiControllerBase fromTarget(final Target target, final Action action, final String defaultControllerPollTime, final TenantAware tenantAware) { final DdiControllerBase result = new DdiControllerBase( new DdiConfig(new DdiPolling(defaultControllerPollTime))); - if (action.isPresent()) { - if (action.get().isCancelingOrCanceled()) { + if (action != null) { + if (action.isCancelingOrCanceled()) { result.add(linkTo( methodOn(DdiRootController.class, tenantAware.getCurrentTenant()).getControllerCancelAction( - tenantAware.getCurrentTenant(), target.getControllerId(), action.get().getId())) + tenantAware.getCurrentTenant(), target.getControllerId(), action.getId())) .withRel(DdiRestConstants.CANCEL_ACTION)); } else { // we need to add the hashcode here of the actionWithStatus @@ -120,7 +119,7 @@ public final class DataConversionHelper { // response because of eTags. result.add(linkTo(methodOn(DdiRootController.class, tenantAware.getCurrentTenant()) .getControllerBasedeploymentAction(tenantAware.getCurrentTenant(), target.getControllerId(), - action.get().getId(), calculateEtag(action.get()))) + action.getId(), calculateEtag(action))) .withRel(DdiRestConstants.DEPLOYMENT_BASE_ACTION)); } } diff --git a/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiArtifactStoreController.java b/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiArtifactStoreController.java index 8a9e57dcc..a32784f38 100644 --- a/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiArtifactStoreController.java +++ b/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiArtifactStoreController.java @@ -21,6 +21,7 @@ import org.eclipse.hawkbit.repository.ArtifactManagement; import org.eclipse.hawkbit.repository.ControllerManagement; import org.eclipse.hawkbit.repository.EntityFactory; import org.eclipse.hawkbit.repository.RepositoryConstants; +import org.eclipse.hawkbit.repository.exception.ArtifactBinaryNotFoundException; import org.eclipse.hawkbit.repository.exception.SoftwareModuleNotAssignedToTargetException; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.Status; @@ -88,7 +89,8 @@ public class DdiArtifactStoreController implements DdiDlArtifactStoreControllerR if (ifMatch != null && !RestResourceConversionHelper.matchesHttpHeader(ifMatch, artifact.getSha1Hash())) { result = new ResponseEntity<>(HttpStatus.PRECONDITION_FAILED); } else { - final DbArtifact file = artifactManagement.loadArtifactBinary(artifact.getSha1Hash()); + final DbArtifact file = artifactManagement.loadArtifactBinary(artifact.getSha1Hash()) + .orElseThrow(() -> new ArtifactBinaryNotFoundException(artifact.getSha1Hash())); // we set a download status only if we are aware of the // targetid, i.e. authenticated and not anonymous diff --git a/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootController.java b/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootController.java index 83baa2557..8e5a36a8b 100644 --- a/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootController.java +++ b/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootController.java @@ -36,6 +36,7 @@ import org.eclipse.hawkbit.repository.RepositoryConstants; import org.eclipse.hawkbit.repository.SoftwareManagement; import org.eclipse.hawkbit.repository.SystemManagement; import org.eclipse.hawkbit.repository.builder.ActionStatusCreate; +import org.eclipse.hawkbit.repository.exception.ArtifactBinaryNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.SoftwareModuleNotAssignedToTargetException; import org.eclipse.hawkbit.repository.model.Action; @@ -130,7 +131,7 @@ public class DdiRootController implements DdiRootControllerRestApi { final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotexist(controllerId, IpUtil .getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties)); return new ResponseEntity<>(DataConversionHelper.fromTarget(target, - controllerManagement.findOldestActiveActionByTarget(controllerId), + controllerManagement.findOldestActiveActionByTarget(controllerId).orElse(null), controllerManagement.getPollingTime(), tenantAware), HttpStatus.OK); } @@ -156,7 +157,8 @@ public class DdiRootController implements DdiRootControllerRestApi { @SuppressWarnings("squid:S3655") final Artifact artifact = module.getArtifactByFilename(fileName).get(); - final DbArtifact file = artifactManagement.loadArtifactBinary(artifact.getSha1Hash()); + final DbArtifact file = artifactManagement.loadArtifactBinary(artifact.getSha1Hash()) + .orElseThrow(() -> new ArtifactBinaryNotFoundException(artifact.getSha1Hash())); final String ifMatch = requestResponseContextHolder.getHttpServletRequest().getHeader("If-Match"); if (ifMatch != null && !RestResourceConversionHelper.matchesHttpHeader(ifMatch, artifact.getSha1Hash())) { diff --git a/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiConfigDataTest.java b/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiConfigDataTest.java index c459ec17d..fb6039440 100644 --- a/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiConfigDataTest.java +++ b/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiConfigDataTest.java @@ -65,7 +65,7 @@ public class DdiConfigDataTest extends AbstractDDiApiIntegrationTest { savedTarget.getTargetInfo().getControllerAttributes().put("dsafsdf", "sdsds"); - final Target updateControllerAttributes = controllerManagament.updateControllerAttributes( + final Target updateControllerAttributes = controllerManagement.updateControllerAttributes( savedTarget.getControllerId(), savedTarget.getTargetInfo().getControllerAttributes()); // request controller attributes need to be false because we don't want // to request the diff --git a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpAuthenticationMessageHandler.java b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpAuthenticationMessageHandler.java index edb493116..6e87996ad 100644 --- a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpAuthenticationMessageHandler.java +++ b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpAuthenticationMessageHandler.java @@ -201,10 +201,9 @@ public class AmqpAuthenticationMessageHandler extends BaseAmqpService { checkIfArtifactIsAssignedToTarget(secruityToken, sha1Hash); - final Artifact artifact = convertDbArtifact(artifactManagement.loadArtifactBinary(sha1Hash)); - if (artifact == null) { - throw new EntityNotFoundException(); - } + final Artifact artifact = convertDbArtifact(artifactManagement.loadArtifactBinary(sha1Hash).orElseThrow( + () -> new EntityNotFoundException(org.eclipse.hawkbit.repository.model.Artifact.class, sha1Hash))); + authentificationResponse.setArtifact(artifact); final String downloadId = UUID.randomUUID().toString(); // SHA1 key is set, download by SHA1 diff --git a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java index db35bb89f..31c0174a3 100644 --- a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java +++ b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java @@ -33,6 +33,8 @@ import org.eclipse.hawkbit.repository.event.remote.entity.CancelTargetAssignment import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.util.IpUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.amqp.core.Message; import org.springframework.amqp.core.MessageProperties; import org.springframework.amqp.rabbit.core.RabbitTemplate; @@ -50,6 +52,8 @@ import org.springframework.context.event.EventListener; */ public class AmqpMessageDispatcherService extends BaseAmqpService { + private static final Logger LOG = LoggerFactory.getLogger(AmqpMessageDispatcherService.class); + private final ArtifactUrlHandler artifactUrlHandler; private final AmqpSenderService amqpSenderService; private final SystemSecurityContext systemSecurityContext; @@ -102,6 +106,9 @@ public class AmqpMessageDispatcherService extends BaseAmqpService { return; } + LOG.debug("targetAssignDistributionSet retrieved for controller {}. I will forward it to DMF broker.", + assignedEvent.getControllerId()); + sendUpdateMessageToTarget(assignedEvent.getTenant(), targetManagement.findTargetByControllerID(assignedEvent.getControllerId()).get(), assignedEvent.getActionId(), assignedEvent.getModules()); diff --git a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthenticationTest.java b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthenticationTest.java index 659537fed..71f620dba 100644 --- a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthenticationTest.java +++ b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthenticationTest.java @@ -163,7 +163,7 @@ public class AmqpControllerAuthenticationTest { final DbArtifact artifact = new DbArtifact(); artifact.setSize(ARTIFACT_SIZE); artifact.setHashes(new DbArtifactHash(SHA1, "md5 test")); - when(artifactManagementMock.loadArtifactBinary(SHA1)).thenReturn(artifact); + when(artifactManagementMock.loadArtifactBinary(SHA1)).thenReturn(Optional.of(artifact)); amqpMessageHandlerService = new AmqpMessageHandlerService(rabbitTemplate, mock(AmqpMessageDispatcherService.class), controllerManagementMock, new JpaEntityFactory()); diff --git a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java index fc64d10c1..2c9925d0e 100644 --- a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java +++ b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java @@ -373,7 +373,7 @@ public class AmqpMessageHandlerServiceTest { when(artifactManagementMock.findFirstArtifactBySHA1(SHA1)).thenReturn(Optional.of(localArtifactMock)); when(controllerManagementMock.hasTargetArtifactAssigned(securityToken.getControllerId(), SHA1)) .thenReturn(true); - when(artifactManagementMock.loadArtifactBinary(anyString())).thenReturn(dbArtifactMock); + when(artifactManagementMock.loadArtifactBinary(anyString())).thenReturn(Optional.of(dbArtifactMock)); when(dbArtifactMock.getArtifactId()).thenReturn("artifactId"); when(dbArtifactMock.getSize()).thenReturn(1L); when(dbArtifactMock.getHashes()).thenReturn(new DbArtifactHash(SHA1, "md5")); diff --git a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtRolloutRestApi.java b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtRolloutRestApi.java index d6e8b1d58..cfdedae80 100644 --- a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtRolloutRestApi.java +++ b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtRolloutRestApi.java @@ -105,6 +105,18 @@ public interface MgmtRolloutRestApi { MediaType.APPLICATION_JSON_VALUE }) ResponseEntity pause(@PathVariable("rolloutId") final Long rolloutId); + /** + * Handles the DELETE request for deleting a rollout. + * + * @param rolloutId + * the ID of the rollout to be deleted. + * @return OK response (200) if rollout could be deleted. In case of any + * exception the corresponding errors occur. + */ + @RequestMapping(method = RequestMethod.DELETE, value = "/{rolloutId}", produces = { MediaTypes.HAL_JSON_VALUE, + MediaType.APPLICATION_JSON_VALUE }) + ResponseEntity delete(@PathVariable("rolloutId") final Long rolloutId); + /** * Handles the POST request for resuming a rollout. * diff --git a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDownloadArtifactResource.java b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDownloadArtifactResource.java index fef438cee..573d9b0ff 100644 --- a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDownloadArtifactResource.java +++ b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDownloadArtifactResource.java @@ -16,6 +16,7 @@ import org.eclipse.hawkbit.artifact.repository.model.DbArtifact; import org.eclipse.hawkbit.mgmt.rest.api.MgmtDownloadArtifactRestApi; import org.eclipse.hawkbit.repository.ArtifactManagement; import org.eclipse.hawkbit.repository.SoftwareManagement; +import org.eclipse.hawkbit.repository.exception.ArtifactBinaryNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.model.Artifact; import org.eclipse.hawkbit.repository.model.SoftwareModule; @@ -67,7 +68,8 @@ public class MgmtDownloadArtifactResource implements MgmtDownloadArtifactRestApi } final Artifact artifact = module.getArtifact(artifactId).get(); - final DbArtifact file = artifactManagement.loadArtifactBinary(artifact.getSha1Hash()); + final DbArtifact file = artifactManagement.loadArtifactBinary(artifact.getSha1Hash()) + .orElseThrow(() -> new ArtifactBinaryNotFoundException(artifact.getSha1Hash())); final HttpServletRequest request = requestResponseContextHolder.getHttpServletRequest(); final String ifMatch = request.getHeader("If-Match"); if (ifMatch != null && !RestResourceConversionHelper.matchesHttpHeader(ifMatch, artifact.getSha1Hash())) { diff --git a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtRolloutResource.java b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtRolloutResource.java index b8e1830db..95c8c8aa5 100644 --- a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtRolloutResource.java +++ b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtRolloutResource.java @@ -80,9 +80,9 @@ public class MgmtRolloutResource implements MgmtRolloutRestApi { final Page findModulesAll; if (rsqlParam != null) { - findModulesAll = this.rolloutManagement.findAllByPredicate(rsqlParam, pageable); + findModulesAll = this.rolloutManagement.findAllByPredicate(rsqlParam, pageable, false); } else { - findModulesAll = this.rolloutManagement.findAll(pageable); + findModulesAll = this.rolloutManagement.findAll(pageable, false); } final List rest = MgmtRolloutMapper.toResponseRollout(findModulesAll.getContent()); @@ -140,6 +140,12 @@ public class MgmtRolloutResource implements MgmtRolloutRestApi { return ResponseEntity.ok().build(); } + @Override + public ResponseEntity delete(@PathVariable("rolloutId") final Long rolloutId) { + this.rolloutManagement.deleteRollout(rolloutId); + return ResponseEntity.ok().build(); + } + @Override public ResponseEntity resume(@PathVariable("rolloutId") final Long rolloutId) { this.rolloutManagement.resumeRollout(rolloutId); diff --git a/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtRolloutResourceTest.java b/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtRolloutResourceTest.java index f2ca76af4..e3fdc35a0 100644 --- a/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtRolloutResourceTest.java +++ b/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtRolloutResourceTest.java @@ -16,6 +16,7 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.startsWith; import static org.hamcrest.Matchers.hasSize; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; @@ -238,7 +239,7 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes @Step private void retrieveAndVerifyRolloutInRunning(final Rollout rollout) throws Exception { - rolloutManagement.checkStartingRollouts(0); + rolloutManagement.handleRollouts(); mvc.perform(get("/rest/v1/rollouts/" + rollout.getId()).accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) @@ -272,7 +273,7 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes @Step private void retrieveAndVerifyRolloutInReady(final Rollout rollout) throws Exception { - rolloutManagement.checkCreatingRollouts(0); + rolloutManagement.handleRollouts(); mvc.perform(get("/rest/v1/rollouts/" + rollout.getId()).accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) @@ -330,7 +331,7 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes postRollout("rollout2", 5, dsA.getId(), "id==target-0001*", 10); // Run here, because Scheduler is disabled during tests - rolloutManagement.checkCreatingRollouts(0); + rolloutManagement.handleRollouts(); mvc.perform(get("/rest/v1/rollouts").accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) .andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) @@ -387,7 +388,7 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes postRollout("rollout2", 5, dsA.getId(), "id==target*", 20); // Run here, because Scheduler is disabled during tests - rolloutManagement.checkCreatingRollouts(0); + rolloutManagement.handleRollouts(); mvc.perform(get("/rest/v1/rollouts?limit=1").accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) @@ -441,7 +442,7 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes .andExpect(jsonPath("status", equalTo("starting"))); // Run here, because scheduler is disabled during tests - rolloutManagement.checkStartingRollouts(0); + rolloutManagement.handleRollouts(); // check rollout is in running state mvc.perform(get("/rest/v1/rollouts/{rolloutId}", rollout.getId()).accept(MediaType.APPLICATION_JSON)) @@ -467,7 +468,7 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes .andExpect(status().isOk()); // Run here, because scheduler is disabled during tests - rolloutManagement.checkStartingRollouts(0); + rolloutManagement.handleRollouts(); // pausing rollout mvc.perform(post("/rest/v1/rollouts/{rolloutId}/pause", rollout.getId())).andDo(MockMvcResultPrinter.print()) @@ -497,7 +498,7 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes .andExpect(status().isOk()); // Run here, because scheduler is disabled during tests - rolloutManagement.checkStartingRollouts(0); + rolloutManagement.handleRollouts(); // pausing rollout mvc.perform(post("/rest/v1/rollouts/{rolloutId}/pause", rollout.getId())).andDo(MockMvcResultPrinter.print()) @@ -531,7 +532,7 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes .andExpect(status().isOk()); // Run here, because scheduler is disabled during tests - rolloutManagement.checkStartingRollouts(0); + rolloutManagement.handleRollouts(); // starting rollout - already started should lead into bad request mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print()) @@ -572,7 +573,7 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes .andExpect(status().isOk()); // Run here, because scheduler is disabled during tests - rolloutManagement.checkStartingRollouts(0); + rolloutManagement.handleRollouts(); // retrieve rollout groups from created rollout - 2 groups exists // (amountTargets / groupSize = 2) @@ -615,7 +616,7 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes private void retrieveAndVerifyRolloutGroupInRunningAndScheduled(final Rollout rollout, final RolloutGroup firstGroup, final RolloutGroup secondGroup) throws Exception { rolloutManagement.startRollout(rollout.getId()); - rolloutManagement.checkStartingRollouts(0); + rolloutManagement.handleRollouts(); mvc.perform(get("/rest/v1/rollouts/{rolloutId}/deploygroups/{groupId}", rollout.getId(), firstGroup.getId()) .accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) @@ -642,7 +643,7 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes @Step private void retrieveAndVerifyRolloutGroupInReady(final Rollout rollout, final RolloutGroup firstGroup) throws Exception { - rolloutManagement.checkCreatingRollouts(0); + rolloutManagement.handleRollouts(); mvc.perform(get("/rest/v1/rollouts/{rolloutId}/deploygroups/{groupId}", rollout.getId(), firstGroup.getId()) .accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) @@ -748,7 +749,7 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes rolloutManagement.startRollout(rollout.getId()); // Run here, because scheduler is disabled during tests - rolloutManagement.checkStartingRollouts(0); + rolloutManagement.handleRollouts(); final RolloutGroup firstGroup = rolloutGroupManagement .findRolloutGroupsByRolloutId(rollout.getId(), new PageRequest(0, 1, Direction.ASC, "id")).getContent() @@ -779,12 +780,29 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes .andExpect(status().isOk()); // Run here, because scheduler is disabled during tests - rolloutManagement.checkStartingRollouts(0); + rolloutManagement.handleRollouts(); // check if running assertThat(doWithTimeout(() -> getRollout(rollout.getId()), this::success, 60_000, 100)).isNotNull(); } + @Test + @Description("Deletion of a rollout") + public void deleteRollout() throws Exception { + final int amountTargets = 10; + testdataFactory.createTargets(amountTargets, "rolloutDelete", "rolloutDelete"); + final DistributionSet dsA = testdataFactory.createDistributionSet(""); + + // create rollout including the created targets with prefix 'rollout' + final Rollout rollout = createRollout("rolloutDelete", 4, dsA.getId(), "controllerId==rolloutDelete*"); + + // delete rollout + mvc.perform(delete("/rest/v1/rollouts/{rolloutid}", rollout.getId())).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()); + + assertThat(getRollout(rollout.getId()).getStatus()).isEqualTo(RolloutStatus.DELETING); + } + @Test @Description("Testing that rollout paged list with rsql parameter") public void getRolloutWithRSQLParam() throws Exception { @@ -922,9 +940,7 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes .andExpect( jsonPath("$._links.resume.href", allOf(startsWith(HREF_ROLLOUT_PREFIX), endsWith("/resume")))) .andExpect(jsonPath("$._links.groups.href", - allOf(startsWith(HREF_ROLLOUT_PREFIX), containsString("/deploygroups")))) - - ; + allOf(startsWith(HREF_ROLLOUT_PREFIX), containsString("/deploygroups")))); } private Rollout createRollout(final String name, final int amountGroups, final long distributionSetId, @@ -935,16 +951,13 @@ public class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTes .successCondition(RolloutGroupSuccessCondition.THRESHOLD, "100").build()); // Run here, because Scheduler is disabled during tests - rolloutManagement.fillRolloutGroupsWithTargets(rollout.getId()); + rolloutManagement.handleRollouts(); return rolloutManagement.findRolloutById(rollout.getId()).get(); } protected boolean success(final Rollout result) { - if (null != result && result.getStatus() == RolloutStatus.RUNNING) { - return true; - } - return false; + return result != null && result.getStatus() == RolloutStatus.RUNNING; } public Rollout getRollout(final Long rolloutId) throws Exception { diff --git a/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtSoftwareModuleResourceTest.java b/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtSoftwareModuleResourceTest.java index 3d78332b6..909a4e982 100644 --- a/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtSoftwareModuleResourceTest.java +++ b/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtSoftwareModuleResourceTest.java @@ -162,7 +162,7 @@ public class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegra try (InputStream fileInputStream = artifactManagement .loadArtifactBinary( softwareManagement.findSoftwareModuleById(sm.getId()).get().getArtifacts().get(0).getSha1Hash()) - .getFileInputStream()) { + .get().getFileInputStream()) { assertTrue("Wrong artifact content", IOUtils.contentEquals(new ByteArrayInputStream(random), fileInputStream)); } diff --git a/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java b/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java index 437e750a9..16f8e08aa 100644 --- a/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java +++ b/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java @@ -106,7 +106,7 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest final int limitSize = 2; final String knownTargetId = "targetId"; final List actions = generateTargetWithTwoUpdatesWithOneOverride(knownTargetId); - controllerManagament.addUpdateActionStatus( + controllerManagement.addUpdateActionStatus( entityFactory.actionStatus().create(actions.get(0).getId()).status(Status.FINISHED).message("test")); final PageRequest pageRequest = new PageRequest(0, 1000, Direction.ASC, ActionFields.ID.getFieldName()); @@ -1205,7 +1205,7 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest knownControllerAttrs.put("a", "1"); knownControllerAttrs.put("b", "2"); testdataFactory.createTarget(knownTargetId); - controllerManagament.updateControllerAttributes(knownTargetId, knownControllerAttrs); + controllerManagement.updateControllerAttributes(knownTargetId, knownControllerAttrs); // test query target over rest resource mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownTargetId + "/attributes")) @@ -1246,7 +1246,7 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest private Target createSingleTarget(final String controllerId, final String name) { targetManagement.createTarget(entityFactory.target().create().controllerId(controllerId).name(name) .description(TARGET_DESCRIPTION_TEST)); - return controllerManagament.updateLastTargetQuery(controllerId, null); + return controllerManagement.updateLastTargetQuery(controllerId, null); } /** @@ -1261,7 +1261,7 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest for (int index = 0; index < amount; index++) { final String str = String.valueOf(character); targetManagement.createTarget(entityFactory.target().create().controllerId(str).name(str).description(str)); - controllerManagament.updateLastTargetQuery(str, null); + controllerManagement.updateLastTargetQuery(str, null); character++; } } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ArtifactManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ArtifactManagement.java index c1e56df73..b43a78875 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ArtifactManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ArtifactManagement.java @@ -15,7 +15,6 @@ import javax.validation.constraints.NotNull; import org.eclipse.hawkbit.artifact.repository.model.DbArtifact; import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; -import org.eclipse.hawkbit.repository.exception.ArtifactBinaryNotFoundException; import org.eclipse.hawkbit.repository.exception.ArtifactDeleteFailedException; import org.eclipse.hawkbit.repository.exception.ArtifactUploadFailedException; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; @@ -58,7 +57,10 @@ public interface ArtifactManagement { * * @return uploaded {@link Artifact} * - * @throw ArtifactUploadFailedException if upload fails + * @throws ArtifactUploadFailedException + * if upload fails + * @throws EntityNotFoundException + * if given software module does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) Artifact createArtifact(@NotNull InputStream inputStream, @NotNull Long moduleId, final String filename, @@ -101,16 +103,20 @@ public interface ArtifactManagement { String providedMd5Sum, String providedSha1Sum, boolean overrideExisting, String contentType); /** - * Garbage collects local artifact binary file if only referenced by given - * {@link Artifact} metadata object. + * Garbage collects artifact binaries if only referenced by given + * {@link SoftwareModule#getId()} or {@link SoftwareModules} that are marged + * as deleted. + * * - * @param artifactId - * the related local artifact + * @param artifactSha1Hash + * no longer needed + * @param moduleId + * the garbage colelction call is made for * * @return true if an binary was actually garbage collected */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_DELETE_REPOSITORY) - boolean clearArtifactBinary(@NotNull Long artifactId); + boolean clearArtifactBinary(@NotEmpty String artifactSha1Hash, @NotNull Long moduleId); /** * Deletes {@link Artifact} based on given id. @@ -144,6 +150,9 @@ public interface ArtifactManagement { * @param softwareModuleId * software module id. * @return found {@link Artifact} + * + * @throws EntityNotFoundException + * if software module with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY + SpringEvalExpressions.HAS_AUTH_OR + SpringEvalExpressions.IS_CONTROLLER) @@ -179,6 +188,9 @@ public interface ArtifactManagement { * @param swId * software module id * @return Page + * + * @throws EntityNotFoundException + * if software module with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) Page findArtifactBySoftwareModule(@NotNull Pageable pageReq, @NotNull Long swId); @@ -190,11 +202,9 @@ public interface ArtifactManagement { * to search for * @return loaded {@link DbArtifact} * - * @throws ArtifactBinaryNotFoundException - * if file could not be found in store */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_DOWNLOAD_ARTIFACT + SpringEvalExpressions.HAS_AUTH_OR + SpringEvalExpressions.HAS_CONTROLLER_DOWNLOAD) - DbArtifact loadArtifactBinary(@NotEmpty String sha1Hash); + Optional loadArtifactBinary(@NotEmpty String sha1Hash); } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/AutoAssignProperties.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/AutoAssignProperties.java deleted file mode 100644 index 862fc4e8c..000000000 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/AutoAssignProperties.java +++ /dev/null @@ -1,61 +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.repository; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * Rollout Management properties. - * - */ -@ConfigurationProperties("hawkbit.autoassign") -public class AutoAssignProperties { - /** - * Autoassign scheduler configuration. - */ - public static class Scheduler { - // used by @Scheduled annotation which needs constant - public static final String PROP_SCHEDULER_DELAY_PLACEHOLDER = "${hawkbit.autoassign.scheduler.fixedDelay:60000}"; - - /** - * Schedule where the autoassign scheduler looks necessary state changes - * in milliseconds. - */ - private long fixedDelay = 60000L; - - /** - * Set to true to run the autoassign scheduler. - */ - private boolean enabled = true; - - public long getFixedDelay() { - return fixedDelay; - } - - public void setFixedDelay(final long fixedDelay) { - this.fixedDelay = fixedDelay; - } - - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(final boolean enabled) { - this.enabled = enabled; - } - - } - - private final Scheduler scheduler = new Scheduler(); - - public Scheduler getScheduler() { - return scheduler; - } - -} diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java index c47abbeb4..2276ee005 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java @@ -120,6 +120,9 @@ public interface ControllerManagement { * @param controllerId * identifies the target to retrieve the actions from * @return a list of actions assigned to given target which are active + * + * @throws EntityNotFoundException + * if target with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) Optional findOldestActiveActionByTarget(@NotNull String controllerId); @@ -157,6 +160,9 @@ public interface ControllerManagement { * of the the {@link SoftwareModule} that should be assigned to * the target * @return last {@link Action} for given combination + * + * @throws EntityNotFoundException + * if target with given ID does not exist * */ @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) @@ -184,6 +190,9 @@ public interface ControllerManagement { * @return {@code true} if the given target has currently or had ever a * relation to the given artifact through the action history, * otherwise {@code false} + * + * @throws EntityNotFoundException + * if target with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) boolean hasTargetArtifactAssigned(@NotEmpty String controllerId, @NotEmpty String sha1Hash); @@ -203,6 +212,9 @@ public interface ControllerManagement { * @return {@code true} if the given target has currently or had ever a * relation to the given artifact through the action history, * otherwise {@code false} + * + * @throws EntityNotFoundException + * if target with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) boolean hasTargetArtifactAssigned(@NotNull Long targetId, @NotEmpty String sha1Hash); diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java index bb5a99055..8e0068cee 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java @@ -149,6 +149,8 @@ public interface DeploymentManagement { * given {@code fieldNameProvider} * @throws RSQLParameterSyntaxException * if the RSQL syntax is wrong + * @throws EntityNotFoundException + * if target with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) Long countActionsByTarget(@NotNull String rsqlParam, @NotEmpty String controllerId); @@ -171,6 +173,9 @@ public interface DeploymentManagement { * @param controllerId * the target associated to the actions to count * @return the count value of found actions associated to the target + * + * @throws EntityNotFoundException + * if target with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) Long countActionsByTarget(@NotEmpty String controllerId); @@ -309,6 +314,9 @@ public interface DeploymentManagement { * @param controllerId * the target associated with the actions * @return a list of actions associated with the given target + * + * @throws EntityNotFoundException + * if target with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) List findActiveActionsByTarget(@NotEmpty String controllerId); @@ -320,6 +328,9 @@ public interface DeploymentManagement { * @param controllerId * the target associated with the actions * @return a list of actions associated with the given target + * + * @throws EntityNotFoundException + * if target with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) List findInActiveActionsByTarget(@NotEmpty String controllerId); diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutGroupManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutGroupManagement.java index 41596f194..cb65bf270 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutGroupManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutGroupManagement.java @@ -21,7 +21,6 @@ import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetWithActionStatus; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.security.access.prepost.PreAuthorize; @@ -40,6 +39,9 @@ public interface RolloutGroupManagement { * @param pageable * the page request to sort and limit the result * @return a page of found {@link RolloutGroup}s + * + * @throws EntityNotFoundException + * of rollout with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) Page findAllRolloutGroupsWithDetailedStatus(@NotNull Long rolloutId, @NotNull Pageable pageable); @@ -52,14 +54,16 @@ public interface RolloutGroupManagement { * distribution set we do not create an action for it but the target is in * the result list of the rollout-group. * - * @param pageRequest + * @param pageable * the page request to sort and limit the result * @param rolloutGroupId * rollout group * @return {@link TargetWithActionStatus} target with action status + * @throws EntityNotFoundException + * if rollout group with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ_AND_TARGET_READ) - Page findAllTargetsWithActionStatus(@NotNull PageRequest pageRequest, + Page findAllTargetsWithActionStatus(@NotNull Pageable pageable, @NotNull Long rolloutGroupId); /** @@ -165,6 +169,9 @@ public interface RolloutGroupManagement { * @param rolloutGroupId * the rollout group id for the count * @return the target rollout group count + * + * @throws EntityNotFoundException + * if rollout group with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) Long countTargetsOfRolloutsGroup(@NotNull Long rolloutGroupId); diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java index 24c55b863..2afae7a25 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java @@ -29,6 +29,7 @@ import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus; import org.eclipse.hawkbit.repository.model.RolloutGroupConditions; import org.eclipse.hawkbit.repository.model.RolloutGroupsValidation; +import org.eclipse.hawkbit.repository.model.Target; import org.hibernate.validator.constraints.NotEmpty; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -44,72 +45,37 @@ import org.springframework.util.concurrent.ListenableFuture; public interface RolloutManagement { /** - * Checking running rollouts. Rollouts which are checked updating the - * lastCheck 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 intended to be called by a scheduler. 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). + * Process rollout based on its current {@link Rollout#getStatus()}. + * + * For {@link RolloutStatus#CREATING} that means creating the + * {@link RolloutGroup}s with {@link Target}s and when finished switch to + * {@link RolloutStatus#READY}. + * + * For {@link RolloutStatus#READY} that means switching to + * {@link RolloutStatus#STARTING} if the {@link Rollout#getStartAt()} is set + * and time of calling this method is beyond this point in time. This auto + * start mechanism is optional. Call {@link #startRollout(Long)} otherwise. + * + * For {@link RolloutStatus#STARTING} that means starting the first + * {@link RolloutGroup}s in line and when finished switch to + * {@link RolloutStatus#RUNNING}. + * + * For {@link RolloutStatus#RUNNING} that means checking to activate further + * groups based on the defined thresholds. Switched to + * {@link RolloutStatus#FINISHED} is all groups are finished. + * + * For {@link RolloutStatus#DELETING} that means either soft delete in case + * rollout was already {@link RolloutStatus#RUNNING} which results in status + * change {@link RolloutStatus#DELETED} or hard delete from the persistence + * otherwise. + * */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE) - void checkRunningRollouts(long delayBetweenChecks); + void handleRollouts(); /** - * Checking Rollouts that are currently being created with asynchronous - * assignment of targets to the Rollout Groups. - * - * @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). - */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE) - void checkCreatingRollouts(long delayBetweenChecks); - - /** - * Checking Rollouts that are currently being started with asynchronous - * creation of actions to the targets of a group. - * - * @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). - */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE) - void checkStartingRollouts(long delayBetweenChecks); - - /** - * Checking Rollouts that are currently ready for an auto start. - * - * @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). - */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE) - void checkReadyRollouts(long delayBetweenChecks); - - /** - * Counts all {@link Rollout}s in the repository. + * Counts all {@link Rollout}s in the repository that are not marked as + * deleted. * * @return number of roll outs */ @@ -212,44 +178,30 @@ public interface RolloutManagement { ListenableFuture validateTargetsInGroups(List groups, String targetFilter, Long createdAt); - /** - * Can be called on a Rollout in {@link RolloutStatus#CREATING} to - * automatically fill it with targets. - * - * Works through all Rollout groups in {@link RolloutGroupStatus#CREATING} - * and fills them with remaining targets until the supposed amount of - * targets for the group is reached. Targets are added to a group when they - * match the overall {@link Rollout#getTargetFilterQuery()} and the - * {@link RolloutGroup#getTargetFilterQuery()} and not more than - * {@link RolloutGroup#getTargetPercentage()} are assigned to the group. - * - * @param rollout - * the rollout - */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE) - void fillRolloutGroupsWithTargets(@NotNull Long rollout); - /** * Retrieves all rollouts. * * @param pageable * the page request to sort and limit the result + * @param deleted + * flag if deleted rollouts should be included * @return a page of found rollouts */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) - Page findAll(@NotNull Pageable pageable); + Page findAll(@NotNull Pageable pageable, boolean deleted); /** * Get count of targets in different status in rollout. * * @param pageable * the page request to sort and limit the result + * @param deleted + * flag if deleted rollouts should be included * @return a list of rollouts with details of targets count for different * statuses - * */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) - Page findAllRolloutsWithDetailedStatus(@NotNull Pageable pageable); + Page findAllRolloutsWithDetailedStatus(@NotNull Pageable pageable, boolean deleted); /** * Retrieves all rollouts found by the given specification. @@ -258,6 +210,8 @@ public interface RolloutManagement { * the specification to filter rollouts * @param pageable * the page request to sort and limit the result + * @param deleted + * flag if deleted rollouts should be included * @return a page of found rollouts * * @throws RSQLParameterUnsupportedFieldException @@ -267,7 +221,7 @@ public interface RolloutManagement { * if the RSQL syntax is wrong */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) - Page findAllByPredicate(@NotNull String rsqlParam, @NotNull Pageable pageable); + Page findAllByPredicate(@NotNull String rsqlParam, @NotNull Pageable pageable, boolean deleted); /** * Finds rollouts by given text in name or description. @@ -276,11 +230,14 @@ public interface RolloutManagement { * the page request to sort and limit the result * @param searchText * search text which matches name or description of rollout + * @param deleted + * flag if deleted rollouts should be included * @return the founded rollout or {@code null} if rollout with given ID does * not exists */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) - Slice findRolloutWithDetailedStatusByFilters(@NotNull Pageable pageable, @NotEmpty String searchText); + Slice findRolloutWithDetailedStatusByFilters(@NotNull Pageable pageable, @NotEmpty String searchText, + boolean deleted); /** * Retrieves a specific rollout by its ID. @@ -309,6 +266,8 @@ public interface RolloutManagement { * * @param rolloutId * rollout id + * @param deleted + * flag if deleted rollouts should be included * @return rollout details of targets count for different statuses * * @@ -417,4 +376,15 @@ public interface RolloutManagement { @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE) Rollout updateRollout(@NotNull RolloutUpdate update); + /** + * Deletes a rollout. A rollout might be deleted asynchronously by + * indicating the rollout by {@link RolloutStatus#DELETING} + * + * + * @param rolloutId + * the ID of the rollout to be deleted + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE) + void deleteRollout(long rolloutId); + } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutProperties.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutProperties.java deleted file mode 100644 index 1394cc271..000000000 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutProperties.java +++ /dev/null @@ -1,88 +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.repository; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * Rollout Management properties. - * - */ -@ConfigurationProperties("hawkbit.rollout") -public class RolloutProperties { - // used by @Scheduled annotation which needs constant - public static final String PROP_SCHEDULER_DELAY_PLACEHOLDER = "${hawkbit.rollout.scheduler.fixedDelay:30000}"; - - // used by @Scheduled annotation which needs constant - public static final String PROP_CREATING_SCHEDULER_DELAY_PLACEHOLDER = "${hawkbit.rollout.creatingScheduler.fixedDelay:2000}"; - - // used by @Scheduled annotation which needs constant - public static final String PROP_STARTING_SCHEDULER_DELAY_PLACEHOLDER = "${hawkbit.rollout.startingScheduler.fixedDelay:2000}"; - - // used by @Scheduled annotation which needs constant - public static final String PROP_READY_SCHEDULER_DELAY_PLACEHOLDER = "${hawkbit.rollout.readyScheduler.fixedDelay:30000}"; - - /** - * Rollout scheduler configuration. - */ - public static class Scheduler { - - /** - * Schedule where the rollout scheduler looks necessary state changes in - * milliseconds. - */ - private long fixedDelay; - - private boolean enabled = true; - - public Scheduler(final long fixedDelay) { - this.fixedDelay = fixedDelay; - } - - public long getFixedDelay() { - return fixedDelay; - } - - public void setFixedDelay(final long fixedDelay) { - this.fixedDelay = fixedDelay; - } - - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(final boolean enabled) { - this.enabled = enabled; - } - } - - private final Scheduler scheduler = new Scheduler(30000L); - - private final Scheduler creatingScheduler = new Scheduler(2000L); - - private final Scheduler startingScheduler = new Scheduler(2000L); - - private final Scheduler readyScheduler = new Scheduler(30000L); - - public Scheduler getScheduler() { - return scheduler; - } - - public Scheduler getCreatingScheduler() { - return creatingScheduler; - } - - public Scheduler getStartingScheduler() { - return startingScheduler; - } - - public Scheduler getReadyScheduler() { - return readyScheduler; - } -} diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/RolloutDeletedEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/RolloutDeletedEvent.java new file mode 100644 index 000000000..a34b20287 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/RolloutDeletedEvent.java @@ -0,0 +1,46 @@ +/** + * 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.event.remote; + +import org.eclipse.hawkbit.repository.event.remote.RemoteIdEvent; +import org.eclipse.hawkbit.repository.model.Rollout; + +/** + * + * Defines the remote event of deleting a {@link Rollout}. + */ +public class RolloutDeletedEvent extends RemoteIdEvent { + + private static final long serialVersionUID = 1L; + + /** + * Default constructor. + */ + public RolloutDeletedEvent() { + // for serialization libs like jackson + } + + /** + * Constructor for json serialization. + * + * @param tenant + * the tenant + * @param entityId + * the entity id + * @param entityClass + * the entity class + * @param applicationId + * the origin application id + */ + public RolloutDeletedEvent(final String tenant, final Long entityId, final String entityClass, + final String applicationId) { + super(entityId, tenant, entityClass, applicationId); + } + +} diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/AbstractActionEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/AbstractActionEvent.java new file mode 100644 index 000000000..386016337 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/AbstractActionEvent.java @@ -0,0 +1,56 @@ +/** + * 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.event.remote.entity; + +import org.eclipse.hawkbit.repository.model.Action; + +/** + * Defines the remote event of creating a new {@link Action}. + */ +public abstract class AbstractActionEvent extends RemoteEntityEvent { + private static final long serialVersionUID = 1L; + + private Long rolloutId; + private Long rolloutGroupId; + + /** + * Default constructor. + */ + public AbstractActionEvent() { + // for serialization libs like jackson + } + + /** + * Constructor + * + * @param action + * the created action + * @param rolloutId + * rollout identifier (optional) + * @param rolloutGroupId + * rollout group identifier (optional) + * @param applicationId + * the origin application id + */ + public AbstractActionEvent(final Action action, final Long rolloutId, final Long rolloutGroupId, + final String applicationId) { + super(action, applicationId); + this.rolloutId = rolloutId; + this.rolloutGroupId = rolloutGroupId; + } + + public Long getRolloutId() { + return rolloutId; + } + + public Long getRolloutGroupId() { + return rolloutGroupId; + } + +} diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/AbstractRolloutGroupEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/AbstractRolloutGroupEvent.java new file mode 100644 index 000000000..a1d2b4ef5 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/AbstractRolloutGroupEvent.java @@ -0,0 +1,40 @@ +/** + * 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.event.remote.entity; + +import org.eclipse.hawkbit.repository.model.RolloutGroup; + +/** + * TenantAwareEvent definition which is been published in case a rollout group + * has been created for a specific rollout or updated. + * + */ +public abstract class AbstractRolloutGroupEvent extends RemoteEntityEvent { + private static final long serialVersionUID = 1L; + + private Long rolloutId; + + /** + * Default constructor. + */ + public AbstractRolloutGroupEvent() { + // for serialization libs like jackson + } + + public AbstractRolloutGroupEvent(final RolloutGroup rolloutGroup, final Long rolloutId, + final String applicationId) { + super(rolloutGroup, applicationId); + this.rolloutId = rolloutId; + } + + public Long getRolloutId() { + return rolloutId; + } + +} diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/ActionCreatedEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/ActionCreatedEvent.java index aa62485e7..133c485e0 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/ActionCreatedEvent.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/ActionCreatedEvent.java @@ -13,8 +13,8 @@ import org.eclipse.hawkbit.repository.model.Action; /** * Defines the remote event of creating a new {@link Action}. */ -public class ActionCreatedEvent extends RemoteEntityEvent { - private static final long serialVersionUID = 1L; +public class ActionCreatedEvent extends AbstractActionEvent { + private static final long serialVersionUID = 2L; /** * Default constructor. @@ -28,11 +28,16 @@ public class ActionCreatedEvent extends RemoteEntityEvent { * * @param action * the created action + * @param rolloutId + * rollout identifier (optional) + * @param rolloutGroupId + * rollout group identifier (optional) * @param applicationId * the origin application id */ - public ActionCreatedEvent(final Action action, final String applicationId) { - super(action, applicationId); + public ActionCreatedEvent(final Action action, final Long rolloutId, final Long rolloutGroupId, + final String applicationId) { + super(action, rolloutId, rolloutGroupId, applicationId); } } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/ActionUpdatedEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/ActionUpdatedEvent.java index ff9783fe1..6906c0940 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/ActionUpdatedEvent.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/ActionUpdatedEvent.java @@ -13,8 +13,8 @@ import org.eclipse.hawkbit.repository.model.Action; /** * Defines the remote event of updated a {@link Action}. */ -public class ActionUpdatedEvent extends RemoteEntityEvent { - private static final long serialVersionUID = 1L; +public class ActionUpdatedEvent extends AbstractActionEvent { + private static final long serialVersionUID = 2L; /** * Default constructor. @@ -28,11 +28,16 @@ public class ActionUpdatedEvent extends RemoteEntityEvent { * * @param action * the updated action + * @param rolloutId + * rollout identifier (optional) + * @param rolloutGroupId + * rollout group identifier (optional) * @param applicationId * the origin application id */ - public ActionUpdatedEvent(final Action action, final String applicationId) { - super(action, applicationId); + public ActionUpdatedEvent(final Action action, final Long rolloutId, final Long rolloutGroupId, + final String applicationId) { + super(action, rolloutId, rolloutGroupId, applicationId); } } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/RolloutGroupCreatedEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/RolloutGroupCreatedEvent.java index 65519ea84..adac4baac 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/RolloutGroupCreatedEvent.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/RolloutGroupCreatedEvent.java @@ -15,12 +15,9 @@ import org.eclipse.hawkbit.repository.model.RolloutGroup; * has been created for a specific rollout. * */ -public class RolloutGroupCreatedEvent extends RemoteEntityEvent { - +public class RolloutGroupCreatedEvent extends AbstractRolloutGroupEvent { private static final long serialVersionUID = 1L; - private Long rolloutId; - /** * Default constructor. */ @@ -33,16 +30,12 @@ public class RolloutGroupCreatedEvent extends RemoteEntityEvent { * * @param rolloutGroup * the updated rolloutGroup + * @param rolloutId + * of the related rollout * @param applicationId * the origin application id */ - public RolloutGroupCreatedEvent(final RolloutGroup rolloutGroup, final String applicationId) { - super(rolloutGroup, applicationId); - this.rolloutId = rolloutGroup.getRollout().getId(); + public RolloutGroupCreatedEvent(final RolloutGroup rolloutGroup, final Long rolloutId, final String applicationId) { + super(rolloutGroup, rolloutId, applicationId); } - - public Long getRolloutId() { - return rolloutId; - } - } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/RolloutGroupUpdatedEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/RolloutGroupUpdatedEvent.java index 35089dd53..22c703270 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/RolloutGroupUpdatedEvent.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/RolloutGroupUpdatedEvent.java @@ -13,9 +13,9 @@ import org.eclipse.hawkbit.repository.model.RolloutGroup; /** * Defines the remote event of updated a {@link RolloutGroup}. */ -public class RolloutGroupUpdatedEvent extends RemoteEntityEvent { +public class RolloutGroupUpdatedEvent extends AbstractRolloutGroupEvent { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 2L; /** * Default constructor. @@ -29,11 +29,13 @@ public class RolloutGroupUpdatedEvent extends RemoteEntityEvent { * * @param rolloutGroup * the updated rolloutGroup + * @param rolloutId + * of the related rollout * @param applicationId * the origin application id */ - public RolloutGroupUpdatedEvent(final RolloutGroup rolloutGroup, final String applicationId) { - super(rolloutGroup, applicationId); + public RolloutGroupUpdatedEvent(final RolloutGroup rolloutGroup, final Long rolloutId, final String applicationId) { + super(rolloutGroup, rolloutId, applicationId); } } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/ArtifactUploadFailedException.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/ArtifactUploadFailedException.java index dbaf5cc75..268d31612 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/ArtifactUploadFailedException.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/ArtifactUploadFailedException.java @@ -18,9 +18,6 @@ import org.eclipse.hawkbit.exception.SpServerError; */ public final class ArtifactUploadFailedException extends AbstractServerRtException { - /** - * - */ private static final long serialVersionUID = 1L; /** diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/BaseEntity.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/BaseEntity.java index c6717d021..94cbc16cd 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/BaseEntity.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/BaseEntity.java @@ -19,6 +19,10 @@ import org.springframework.hateoas.Identifiable; */ public interface BaseEntity extends Serializable, Identifiable { + static Long getIdOrNull(final BaseEntity entity) { + return entity == null ? null : entity.getId(); + } + /** * @return time in {@link TimeUnit#MILLISECONDS} when the {@link BaseEntity} * was created. diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Rollout.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Rollout.java index f964d6442..277262dc1 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Rollout.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Rollout.java @@ -26,6 +26,12 @@ import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus.Status; */ public interface Rollout extends NamedEntity { + /** + * @return true if the rollout is deleted and only kept for + * history purposes. + */ + boolean isDeleted(); + /** * @return {@link DistributionSet} that is rolled out */ @@ -60,11 +66,11 @@ public interface Rollout extends NamedEntity { long getForcedTime(); /** - * @return Timestamp when the rollout should be started automatically. Can be null. + * @return Timestamp when the rollout should be started automatically. Can + * be null. */ Long getStartAt(); - /** * @return number of {@link Target}s in this rollout. */ @@ -123,9 +129,22 @@ public interface Rollout extends NamedEntity { */ FINISHED, + /** + * Rollout is under deletion. + */ + DELETING, + + /** + * Rollout has been deleted. This state is only set in case of a + * soft-deletion of the rollout which keeps references, in case of an + * hard-deletion of a rollout the rollout-entry itself is deleted. + */ + DELETED, + /** * Rollout could not be created due to errors, might be a database * problem during asynchronous creating. + * * @deprecated legacy status is not used anymore */ @Deprecated @@ -134,6 +153,7 @@ public interface Rollout extends NamedEntity { /** * Rollout could not be started due to errors, might be database problem * during asynchronous starting. + * * @deprecated legacy status is not used anymore */ @Deprecated diff --git a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/event/EventType.java b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/event/EventType.java index 23aa8081e..f12693a73 100644 --- a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/event/EventType.java +++ b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/event/EventType.java @@ -15,6 +15,7 @@ import java.util.Optional; import org.eclipse.hawkbit.repository.event.remote.DistributionSetDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.DistributionSetTagDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.DownloadProgressEvent; +import org.eclipse.hawkbit.repository.event.remote.RolloutDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.SoftwareModuleDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent; import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent; @@ -91,12 +92,13 @@ public class EventType { // download TYPES.put(20, DownloadProgressEvent.class); - + TYPES.put(21, SoftwareModuleCreatedEvent.class); TYPES.put(22, SoftwareModuleDeletedEvent.class); TYPES.put(23, SoftwareModuleUpdatedEvent.class); TYPES.put(24, TargetPollEvent.class); + TYPES.put(25, RolloutDeletedEvent.class); } private int value; diff --git a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/AbstractRolloutManagement.java b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/AbstractRolloutManagement.java new file mode 100644 index 000000000..12bdf630d --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/AbstractRolloutManagement.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.repository; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +import javax.validation.ConstraintDeclarationException; + +import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.eclipse.hawkbit.repository.model.RolloutGroupsValidation; +import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer; +import org.eclipse.hawkbit.tenancy.TenantAware; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.integration.support.locks.LockRegistry; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.AsyncResult; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.annotation.Isolation; +import org.springframework.transaction.support.DefaultTransactionDefinition; +import org.springframework.transaction.support.TransactionCallback; +import org.springframework.transaction.support.TransactionTemplate; +import org.springframework.util.concurrent.ListenableFuture; + +/** + * Core functionality for {@link RolloutManagement} implementations. + * + */ +public abstract class AbstractRolloutManagement implements RolloutManagement { + + protected final TargetManagement targetManagement; + + protected final DeploymentManagement deploymentManagement; + + protected final RolloutGroupManagement rolloutGroupManagement; + + protected final DistributionSetManagement distributionSetManagement; + + protected final ApplicationContext context; + + protected final ApplicationEventPublisher eventPublisher; + + protected final VirtualPropertyReplacer virtualPropertyReplacer; + + protected final PlatformTransactionManager txManager; + + protected final TenantAware tenantAware; + + protected final LockRegistry lockRegistry; + + protected AbstractRolloutManagement(final TargetManagement targetManagement, + final DeploymentManagement deploymentManagement, final RolloutGroupManagement rolloutGroupManagement, + final DistributionSetManagement distributionSetManagement, final ApplicationContext context, + final ApplicationEventPublisher eventPublisher, final VirtualPropertyReplacer virtualPropertyReplacer, + final PlatformTransactionManager txManager, final TenantAware tenantAware, + final LockRegistry lockRegistry) { + this.targetManagement = targetManagement; + this.deploymentManagement = deploymentManagement; + this.rolloutGroupManagement = rolloutGroupManagement; + this.distributionSetManagement = distributionSetManagement; + this.context = context; + this.eventPublisher = eventPublisher; + this.virtualPropertyReplacer = virtualPropertyReplacer; + this.txManager = txManager; + this.tenantAware = tenantAware; + this.lockRegistry = lockRegistry; + } + + protected int runInNewTransaction(final String transactionName, final TransactionCallback action) { + final DefaultTransactionDefinition def = new DefaultTransactionDefinition(); + def.setName(transactionName); + def.setReadOnly(false); + def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + def.setIsolationLevel(Isolation.READ_UNCOMMITTED.value()); + return new TransactionTemplate(txManager, def).execute(action); + } + + protected RolloutGroupsValidation validateTargetsInGroups(final List groups, final String baseFilter, + final long totalTargets) { + final List groupTargetCounts = new ArrayList<>(groups.size()); + final Map targetFilterCounts = groups.stream() + .map(group -> RolloutHelper.getGroupTargetFilter(baseFilter, group)).distinct() + .collect(Collectors.toMap(Function.identity(), targetManagement::countTargetByTargetFilterQuery)); + + long unusedTargetsCount = 0; + + for (int i = 0; i < groups.size(); i++) { + final RolloutGroup group = groups.get(i); + final String groupTargetFilter = RolloutHelper.getGroupTargetFilter(baseFilter, group); + RolloutHelper.verifyRolloutGroupTargetPercentage(group.getTargetPercentage()); + + final long targetsInGroupFilter = targetFilterCounts.get(groupTargetFilter); + final long overlappingTargets = countOverlappingTargetsWithPreviousGroups(baseFilter, groups, group, i, + targetFilterCounts); + + final long realTargetsInGroup; + // Assume that targets which were not used in the previous groups + // are used in this group + if (overlappingTargets > 0 && unusedTargetsCount > 0) { + realTargetsInGroup = targetsInGroupFilter - overlappingTargets + unusedTargetsCount; + unusedTargetsCount = 0; + } else { + realTargetsInGroup = targetsInGroupFilter - overlappingTargets; + } + + final long reducedTargetsInGroup = Math + .round(group.getTargetPercentage() / 100 * (double) realTargetsInGroup); + groupTargetCounts.add(reducedTargetsInGroup); + unusedTargetsCount += realTargetsInGroup - reducedTargetsInGroup; + + } + + return new RolloutGroupsValidation(totalTargets, groupTargetCounts); + } + + private long countOverlappingTargetsWithPreviousGroups(final String baseFilter, final List groups, + final RolloutGroup group, final int groupIndex, final Map targetFilterCounts) { + // there can't be overlapping targets in the first group + if (groupIndex == 0) { + return 0; + } + final List previousGroups = groups.subList(0, groupIndex); + final String overlappingTargetsFilter = RolloutHelper.getOverlappingWithGroupsTargetFilter(baseFilter, + previousGroups, group); + + if (targetFilterCounts.containsKey(overlappingTargetsFilter)) { + return targetFilterCounts.get(overlappingTargetsFilter); + } else { + final long overlappingTargets = targetManagement.countTargetByTargetFilterQuery(overlappingTargetsFilter); + targetFilterCounts.put(overlappingTargetsFilter, overlappingTargets); + return overlappingTargets; + } + } + + protected long calculateRemainingTargets(final List groups, final String targetFilter, + final Long createdAt) { + final String baseFilter = RolloutHelper.getTargetFilterQuery(targetFilter, createdAt); + final long totalTargets = targetManagement.countTargetByTargetFilterQuery(baseFilter); + if (totalTargets == 0) { + throw new ConstraintDeclarationException("Rollout target filter does not match any targets"); + } + + final RolloutGroupsValidation validation = validateTargetsInGroups(groups, baseFilter, totalTargets); + + return totalTargets - validation.getTargetsInGroups(); + } + + @Override + @Async + public ListenableFuture validateTargetsInGroups(final List groups, + final String targetFilter, final Long createdAt) { + + final String baseFilter = RolloutHelper.getTargetFilterQuery(targetFilter, createdAt); + final long totalTargets = targetManagement.countTargetByTargetFilterQuery(baseFilter); + if (totalTargets == 0) { + throw new ConstraintDeclarationException("Rollout target filter does not match any targets"); + } + + return new AsyncResult<>(validateTargetsInGroups( + groups.stream().map(RolloutGroupCreate::build).collect(Collectors.toList()), baseFilter, totalTargets)); + } + +} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RolloutHelper.java b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/RolloutHelper.java similarity index 66% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RolloutHelper.java rename to hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/RolloutHelper.java index 8ed0feb67..b66c54524 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RolloutHelper.java +++ b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/RolloutHelper.java @@ -6,34 +6,23 @@ * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; -import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate; import org.eclipse.hawkbit.repository.exception.ConstraintViolationException; import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException; -import org.eclipse.hawkbit.repository.jpa.builder.JpaRolloutGroupCreate; -import org.eclipse.hawkbit.repository.jpa.model.JpaRollout; -import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup; -import org.eclipse.hawkbit.repository.jpa.model.JpaRollout_; import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.repository.model.RolloutGroupConditions; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; -import org.springframework.data.jpa.domain.Specification; /** - * A collection of static helper methods for the {@link JpaRolloutManagement} + * A collection of static helper methods for the {@link RolloutManagement} */ -final class RolloutHelper { +public final class RolloutHelper { private RolloutHelper() { } @@ -43,7 +32,7 @@ final class RolloutHelper { * @param conditions * input conditions and actions */ - static void verifyRolloutGroupConditions(final RolloutGroupConditions conditions) { + public static void verifyRolloutGroupConditions(final RolloutGroupConditions conditions) { if (conditions.getSuccessCondition() == null) { throw new ConstraintViolationException("Rollout group is missing success condition"); } @@ -60,7 +49,7 @@ final class RolloutHelper { * the input group * @return the verified group */ - static RolloutGroup verifyRolloutGroupHasConditions(final RolloutGroup group) { + public static RolloutGroup verifyRolloutGroupHasConditions(final RolloutGroup group) { if (group.getTargetPercentage() < 1F || group.getTargetPercentage() > 100F) { throw new ConstraintViolationException("Target percentage has to be between 1 and 100"); } @@ -74,55 +63,13 @@ final class RolloutHelper { return group; } - /** - * In case the given group is missing conditions or actions, they will be - * set from the supplied default conditions. - * - * @param create - * group to check - * @param conditions - * default conditions and actions - */ - static JpaRolloutGroup prepareRolloutGroupWithDefaultConditions(final RolloutGroupCreate create, - final RolloutGroupConditions conditions) { - final JpaRolloutGroup group = ((JpaRolloutGroupCreate) create).build(); - - if (group.getSuccessCondition() == null) { - group.setSuccessCondition(conditions.getSuccessCondition()); - } - if (group.getSuccessConditionExp() == null) { - group.setSuccessConditionExp(conditions.getSuccessConditionExp()); - } - if (group.getSuccessAction() == null) { - group.setSuccessAction(conditions.getSuccessAction()); - } - if (group.getSuccessActionExp() == null) { - group.setSuccessActionExp(conditions.getSuccessActionExp()); - } - - if (group.getErrorCondition() == null) { - group.setErrorCondition(conditions.getErrorCondition()); - } - if (group.getErrorConditionExp() == null) { - group.setErrorConditionExp(conditions.getErrorConditionExp()); - } - if (group.getErrorAction() == null) { - group.setErrorAction(conditions.getErrorAction()); - } - if (group.getErrorActionExp() == null) { - group.setErrorActionExp(conditions.getErrorActionExp()); - } - - return group; - } - /** * Verify if the supplied amount of groups is in range * * @param amountGroup * amount of groups */ - static void verifyRolloutGroupParameter(final int amountGroup) { + public static void verifyRolloutGroupParameter(final int amountGroup) { if (amountGroup <= 0) { throw new ConstraintViolationException("the amountGroup must be greater than zero"); } else if (amountGroup > 500) { @@ -136,7 +83,7 @@ final class RolloutHelper { * @param percentage * the percentage */ - static void verifyRolloutGroupTargetPercentage(final float percentage) { + public static void verifyRolloutGroupTargetPercentage(final float percentage) { if (percentage <= 0) { throw new ConstraintViolationException("the percentage must be greater than zero"); } else if (percentage > 100) { @@ -152,7 +99,7 @@ final class RolloutHelper { * Rollout to derive the filter from * @return resulting target filter query */ - static String getTargetFilterQuery(final Rollout rollout) { + public static String getTargetFilterQuery(final Rollout rollout) { return getTargetFilterQuery(rollout.getTargetFilterQuery(), rollout.getCreatedAt()); } @@ -164,7 +111,7 @@ final class RolloutHelper { * @return a target filter query that only matches targets that were created * after the provided timestamp. */ - static String getTargetFilterQuery(final String targetFilter, final Long createdAt) { + public static String getTargetFilterQuery(final String targetFilter, final Long createdAt) { if (createdAt != null) { return targetFilter + ";createdat=le=" + createdAt.toString(); } @@ -179,7 +126,7 @@ final class RolloutHelper { * @param status * the Status */ - static void verifyRolloutInStatus(final Rollout rollout, final Rollout.RolloutStatus status) { + public static void verifyRolloutInStatus(final Rollout rollout, final Rollout.RolloutStatus status) { if (!rollout.getStatus().equals(status)) { throw new RolloutIllegalStateException("Rollout is not in status " + status.toString()); } @@ -197,7 +144,7 @@ final class RolloutHelper { * the group to add * @return list of groups */ - static List getGroupsByStatusIncludingGroup(final Rollout rollout, + public static List getGroupsByStatusIncludingGroup(final Rollout rollout, final RolloutGroup.RolloutGroupStatus status, final RolloutGroup group) { return rollout.getRolloutGroups().stream() .filter(innerGroup -> innerGroup.getStatus().equals(status) || innerGroup.equals(group)) @@ -211,7 +158,7 @@ final class RolloutHelper { * the rollout * @return ordered list of groups */ - static List getOrderedGroups(final Rollout rollout) { + public static List getOrderedGroups(final Rollout rollout) { return rollout.getRolloutGroups().stream().sorted((group1, group2) -> { if (group1.getId() < group2.getId()) { return -1; @@ -232,7 +179,7 @@ final class RolloutHelper { * @return RSQL string without base filter of the Rollout. Can be an empty * string. */ - static String getAllGroupsTargetFilter(final List groups) { + public static String getAllGroupsTargetFilter(final List groups) { if (groups.stream().anyMatch(group -> StringUtils.isEmpty(group.getTargetFilterQuery()))) { return ""; } @@ -254,7 +201,7 @@ final class RolloutHelper { * @return RSQL string without base filter of the Rollout. Can be an empty * string. */ - static String getOverlappingWithGroupsTargetFilter(final String baseFilter, final List groups, + public static String getOverlappingWithGroupsTargetFilter(final String baseFilter, final List groups, final RolloutGroup group) { final String groupFilter = group.getTargetFilterQuery(); // when any previous group has the same filter as the target group the @@ -283,7 +230,7 @@ final class RolloutHelper { && prevGroup.getTargetFilterQuery().equals(groupFilter)); } - private static String concatAndTargetFilters(String... filters) { + private static String concatAndTargetFilters(final String... filters) { return "(" + Arrays.stream(filters).collect(Collectors.joining(");(")) + ")"; } @@ -308,7 +255,7 @@ final class RolloutHelper { * @param targetCount * the count of left targets */ - static void verifyRemainingTargets(final long targetCount) { + public static void verifyRemainingTargets(final long targetCount) { if (targetCount > 0) { throw new ConstraintViolationException( "Rollout groups don't match all targets that are targeted by the rollout"); @@ -318,35 +265,11 @@ final class RolloutHelper { } } - /** - * @param searchText - * search string - * @return criteria specification with a query for name or description of a - * rollout - */ - static Specification likeNameOrDescription(final String searchText) { - return (rolloutRoot, query, criteriaBuilder) -> { - final String searchTextToLower = searchText.toLowerCase(); - return criteriaBuilder.or( - criteriaBuilder.like(criteriaBuilder.lower(rolloutRoot.get(JpaRollout_.name)), searchTextToLower), - criteriaBuilder.like(criteriaBuilder.lower(rolloutRoot.get(JpaRollout_.description)), - searchTextToLower)); - }; - } - - static void checkIfRolloutCanStarted(final Rollout rollout, final Rollout mergedRollout) { + public static void checkIfRolloutCanStarted(final Rollout rollout, final Rollout mergedRollout) { if (!(Rollout.RolloutStatus.READY.equals(mergedRollout.getStatus()))) { throw new RolloutIllegalStateException("Rollout can only be started in state ready but current state is " + rollout.getStatus().name().toLowerCase()); } } - static Page convertPage(final Page findAll, final Pageable pageable) { - return new PageImpl<>(Collections.unmodifiableList(findAll.getContent()), pageable, findAll.getTotalElements()); - } - - static Slice convertPage(final Slice findAll, final Pageable pageable) { - return new PageImpl<>(Collections.unmodifiableList(findAll.getContent()), pageable, 0); - } - } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/ActionRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/ActionRepository.java index b386b34f3..43fc46eb8 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/ActionRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/ActionRepository.java @@ -195,6 +195,7 @@ public interface ActionRepository extends BaseEntityRepository, * the status which the actions should not have * @return the found list of {@link Action}s */ + @EntityGraph(attributePaths = { "target" }, type = EntityGraphType.LOAD) @Query("SELECT a FROM JpaAction a WHERE a.active = true AND a.distributionSet.requiredMigrationStep = false AND a.target IN ?1 AND a.status != ?2") List findByActiveAndTargetIdInAndActionStatusNotEqualToAndDistributionSetRequiredMigrationStep( Collection targetIds, Action.Status notStatus); @@ -264,6 +265,44 @@ public interface ActionRepository extends BaseEntityRepository, */ Long countByRolloutIdAndRolloutGroupIdAndStatus(Long rolloutId, Long rolloutGroupId, Action.Status status); + /** + * Counts all actions referring to a given rollout and status. + * + * @param rolloutId + * the ID of the rollout the actions belong to + * @param status + * the status the actions should have + * @return the count of actions referring to a rollout and are in a given + * status + */ + Long countByRolloutIdAndStatus(Long rolloutId, Action.Status status); + + /** + * Returns {@code true} if actions for the given rollout exists, otherwise + * {@code false} + * + * @param rolloutId + * the ID of the rollout the actions belong to + * @return {@code true} if actions for the given rollout exists, otherwise + * {@code false} + */ + @Query("SELECT CASE WHEN COUNT(a)>0 THEN 'true' ELSE 'false' END FROM JpaAction a WHERE a.rollout.id=:rolloutId") + boolean existsByRolloutId(@Param("rolloutId") Long rolloutId); + + /** + * Returns {@code true} if actions for the given rollout exists, otherwise + * {@code false} + * + * @param rolloutId + * the ID of the rollout the actions belong to + * @param status + * the action is not to be in + * @return {@code true} if actions for the given rollout exists, otherwise + * {@code false} + */ + @Query("SELECT CASE WHEN COUNT(a)>0 THEN 'true' ELSE 'false' END FROM JpaAction a WHERE a.rollout.id=:rolloutId AND a.status != :status") + boolean existsByRolloutIdAndStatusNotIn(@Param("rolloutId") Long rolloutId, @Param("status") Status status); + /** * Retrieving all actions referring to a given rollout with a specific * action as parent reference and a specific status. @@ -281,6 +320,7 @@ public interface ActionRepository extends BaseEntityRepository, * @return the actions referring a specific rollout and a specific parent * rolloutgroup in a specific status */ + @EntityGraph(attributePaths = { "target" }, type = EntityGraphType.LOAD) Page findByRolloutAndRolloutGroupParentAndStatus(Pageable pageable, JpaRollout rollout, JpaRolloutGroup rolloutGroupParent, Status actionStatus); @@ -296,19 +336,22 @@ public interface ActionRepository extends BaseEntityRepository, * @return the actions referring a specific rollout and a specific parent * rolloutgroup in a specific status */ + @EntityGraph(attributePaths = { "target" }, type = EntityGraphType.LOAD) Page findByRolloutAndRolloutGroupParentIsNullAndStatus(Pageable pageable, JpaRollout rollout, Status actionStatus); /** * Retrieves all actions for a specific rollout and in a specific status. - * + * + * @param pageable + * page parameters * @param rolloutId * the rollout the actions beglong to * @param actionStatus * the status of the actions * @return the actions referring a specific rollout an in a specific status */ - List findByRolloutIdAndStatus(Long rolloutId, Status actionStatus); + Page findByRolloutIdAndStatus(Pageable pageable, Long rolloutId, Status actionStatus); /** * Get list of objects which has details of status and count of targets in @@ -354,4 +397,15 @@ public interface ActionRepository extends BaseEntityRepository, @Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus(a.rolloutGroup.id, a.status , COUNT(a.target)) FROM JpaAction a WHERE a.rolloutGroup.id IN ?1 GROUP BY a.rolloutGroup.id, a.status") List getStatusCountByRolloutGroupId(List rolloutGroupId); + /** + * Deletes all actions with the given IDs. + * + * @param actionIDs + * the IDs of the actions to be deleted. + */ + @Modifying + @Transactional(isolation = Isolation.READ_UNCOMMITTED) + // Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477 + @Query("DELETE FROM JpaAction a WHERE a.id IN ?1") + void deleteByIdIn(final Collection actionIDs); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/DistributionSetRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/DistributionSetRepository.java index 7aa56a512..8e9677df6 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/DistributionSetRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/DistributionSetRepository.java @@ -12,7 +12,6 @@ import java.util.Collection; import java.util.List; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; -import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetTag; @@ -72,11 +71,11 @@ public interface DistributionSetRepository * Finds {@link DistributionSet}s where given {@link SoftwareModule} is * assigned. * - * @param module + * @param moduleId * to search for * @return {@link List} of found {@link DistributionSet}s */ - Long countByModules(JpaSoftwareModule module); + Long countByModulesId(Long moduleId); /** * Finds {@link DistributionSet}s based on given ID that are assigned yet to diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaArtifactManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaArtifactManagement.java index 4e9ae1b4d..0c9d2c526 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaArtifactManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaArtifactManagement.java @@ -17,7 +17,6 @@ import org.eclipse.hawkbit.artifact.repository.HashNotMatchException; import org.eclipse.hawkbit.artifact.repository.model.DbArtifact; import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash; import org.eclipse.hawkbit.repository.ArtifactManagement; -import org.eclipse.hawkbit.repository.exception.ArtifactBinaryNotFoundException; import org.eclipse.hawkbit.repository.exception.ArtifactDeleteFailedException; import org.eclipse.hawkbit.repository.exception.ArtifactUploadFailedException; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; @@ -104,23 +103,16 @@ public class JpaArtifactManagement implements ArtifactManagement { @Override @Modifying @Transactional(isolation = Isolation.READ_UNCOMMITTED) - public boolean clearArtifactBinary(final Long artifactId) { - return clearArtifactBinary(localArtifactRepository.findById(artifactId) - .orElseThrow(() -> new EntityNotFoundException(Artifact.class, artifactId))); - } + public boolean clearArtifactBinary(final String sha1Hash, final Long moduleId) { - private boolean clearArtifactBinary(final JpaArtifact existing) { - - for (final Artifact lArtifact : localArtifactRepository.findBySha1Hash(existing.getSha1Hash())) { - if (!lArtifact.getSoftwareModule().isDeleted() - && Long.compare(lArtifact.getSoftwareModule().getId(), existing.getSoftwareModule().getId()) != 0) { - return false; - } + if (localArtifactRepository.existsWithSha1HashAndSoftwareModuleIdIsNot(sha1Hash, moduleId)) { + // there are still other artifacts that need the binary + return false; } try { - LOG.debug("deleting artifact from repository {}", existing.getSha1Hash()); - artifactRepository.deleteBySha1(existing.getSha1Hash()); + LOG.debug("deleting artifact from repository {}", sha1Hash); + artifactRepository.deleteBySha1(sha1Hash); return true; } catch (final ArtifactStoreException e) { throw new ArtifactDeleteFailedException(e); @@ -134,7 +126,7 @@ public class JpaArtifactManagement implements ArtifactManagement { final JpaArtifact existing = (JpaArtifact) findArtifact(id) .orElseThrow(() -> new EntityNotFoundException(Artifact.class, id)); - clearArtifactBinary(existing); + clearArtifactBinary(existing.getSha1Hash(), existing.getSoftwareModule().getId()); ((JpaSoftwareModule) existing.getSoftwareModule()).removeArtifact(existing); softwareModuleRepository.save((JpaSoftwareModule) existing.getSoftwareModule()); @@ -148,6 +140,8 @@ public class JpaArtifactManagement implements ArtifactManagement { @Override public Optional findByFilenameAndSoftwareModule(final String filename, final Long softwareModuleId) { + throwExceptionIfSoftwareModuleDoesNotExist(softwareModuleId); + return localArtifactRepository.findFirstByFilenameAndSoftwareModuleId(filename, softwareModuleId); } @@ -163,13 +157,20 @@ public class JpaArtifactManagement implements ArtifactManagement { @Override public Page findArtifactBySoftwareModule(final Pageable pageReq, final Long swId) { + throwExceptionIfSoftwareModuleDoesNotExist(swId); + return localArtifactRepository.findBySoftwareModuleId(pageReq, swId); } + private void throwExceptionIfSoftwareModuleDoesNotExist(final Long swId) { + if (!softwareModuleRepository.exists(swId)) { + throw new EntityNotFoundException(SoftwareModule.class, swId); + } + } + @Override - public DbArtifact loadArtifactBinary(final String sha1Hash) { - return Optional.ofNullable(artifactRepository.getArtifactBySha1(sha1Hash)) - .orElseThrow(() -> new ArtifactBinaryNotFoundException(sha1Hash)); + public Optional loadArtifactBinary(final String sha1Hash) { + return Optional.ofNullable(artifactRepository.getArtifactBySha1(sha1Hash)); } private Artifact storeArtifactMetadata(final SoftwareModule softwareModule, final String providedFilename, diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaControllerManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaControllerManagement.java index 8a476ed2a..3bd1c0053 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaControllerManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaControllerManagement.java @@ -46,6 +46,7 @@ import org.eclipse.hawkbit.repository.jpa.specifications.ActionSpecifications; 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.SoftwareModule; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetInfo; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; @@ -85,6 +86,9 @@ public class JpaControllerManagement implements ControllerManagement { @Autowired private TargetRepository targetRepository; + @Autowired + private SoftwareModuleRepository softwareModuleRepository; + @Autowired private TargetManagement targetManagement; @@ -140,6 +144,9 @@ public class JpaControllerManagement implements ControllerManagement { @Override public Optional getActionForDownloadByTargetAndSoftwareModule(final String controllerId, final Long moduleId) { + throwExceptionIfTargetDoesNotExist(controllerId); + throwExceptionIfSoftwareModuleDoesNotExist(moduleId); + final List action = actionRepository.findActionByTargetAndSoftwareModule(controllerId, moduleId); if (action.isEmpty() || action.get(0).isCancelingOrCanceled()) { @@ -149,26 +156,40 @@ public class JpaControllerManagement implements ControllerManagement { return Optional.ofNullable(action.get(0)); } + private void throwExceptionIfTargetDoesNotExist(final String controllerId) { + if (!targetRepository.existsByControllerId(controllerId)) { + throw new EntityNotFoundException(Target.class, controllerId); + } + } + + private void throwExceptionIfTargetDoesNotExist(final Long targetId) { + if (!targetRepository.exists(targetId)) { + throw new EntityNotFoundException(Target.class, targetId); + } + } + + private void throwExceptionIfSoftwareModuleDoesNotExist(final Long moduleId) { + if (!softwareModuleRepository.exists(moduleId)) { + throw new EntityNotFoundException(SoftwareModule.class, moduleId); + } + } + @Override public boolean hasTargetArtifactAssigned(final String controllerId, final String sha1Hash) { - final Optional target = targetRepository.findByControllerId(controllerId); - if (!target.isPresent()) { - return false; - } - return actionRepository.count(ActionSpecifications.hasTargetAssignedArtifact(target.get(), sha1Hash)) > 0; + throwExceptionIfTargetDoesNotExist(controllerId); + return actionRepository.count(ActionSpecifications.hasTargetAssignedArtifact(controllerId, sha1Hash)) > 0; } @Override public boolean hasTargetArtifactAssigned(final Long targetId, final String sha1Hash) { - final Target target = targetRepository.findOne(targetId); - if (target == null) { - return false; - } - return actionRepository.count(ActionSpecifications.hasTargetAssignedArtifact(target, sha1Hash)) > 0; + throwExceptionIfTargetDoesNotExist(targetId); + return actionRepository.count(ActionSpecifications.hasTargetAssignedArtifact(targetId, sha1Hash)) > 0; } @Override public Optional findOldestActiveActionByTarget(final String controllerId) { + throwExceptionIfTargetDoesNotExist(controllerId); + // used in favorite to findFirstByTargetAndActiveOrderByIdAsc due to // DATAJPA-841 issue. return actionRepository.findFirstByTargetControllerIdAndActive(new Sort(Direction.ASC, "id"), controllerId, diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java index 8ce179987..5c051c25a 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java @@ -225,7 +225,7 @@ public class JpaDeploymentManagement implements DeploymentManagement { final List targets = Lists.partition(controllerIDs, Constants.MAX_ENTRIES_IN_STATEMENT).stream() .map(ids -> targetRepository .findAll(TargetSpecifications.hasControllerIdAndAssignedDistributionSetIdNot(ids, set.getId()))) - .flatMap(t -> t.stream()).collect(Collectors.toList()); + .flatMap(List::stream).collect(Collectors.toList()); if (targets.isEmpty()) { // detaching as it is not necessary to persist the set itself @@ -456,6 +456,8 @@ public class JpaDeploymentManagement implements DeploymentManagement { final RolloutGroup rolloutGroupParent, final int limit) { final DefaultTransactionDefinition def = new DefaultTransactionDefinition(); def.setName("startScheduledActions"); + def.setReadOnly(false); + def.setIsolationLevel(Isolation.READ_UNCOMMITTED.value()); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); return new TransactionTemplate(txManager, def).execute(status -> { final Page rolloutGroupActions = findActionsByRolloutAndRolloutGroupParent(rollout, @@ -599,24 +601,38 @@ public class JpaDeploymentManagement implements DeploymentManagement { @Override public List findActiveActionsByTarget(final String controllerId) { + throwExceptionIfTargetFoesNotExist(controllerId); + return actionRepository.findByActiveAndTarget(controllerId, true); } @Override public List findInActiveActionsByTarget(final String controllerId) { + throwExceptionIfTargetFoesNotExist(controllerId); + return actionRepository.findByActiveAndTarget(controllerId, false); } @Override public Long countActionsByTarget(final String controllerId) { + throwExceptionIfTargetFoesNotExist(controllerId); + return actionRepository.countByTargetControllerId(controllerId); } @Override public Long countActionsByTarget(final String rsqlParam, final String controllerId) { + throwExceptionIfTargetFoesNotExist(controllerId); + return actionRepository.count(createSpecificationFor(controllerId, rsqlParam)); } + private void throwExceptionIfTargetFoesNotExist(final String controllerId) { + if (!targetRepository.existsByControllerId(controllerId)) { + throw new EntityNotFoundException(Target.class, controllerId); + } + } + @Override @Modifying @Transactional(isolation = Isolation.READ_UNCOMMITTED) diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutGroupManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutGroupManagement.java index 5c5c9762a..245b38c72 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutGroupManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutGroupManagement.java @@ -38,6 +38,7 @@ import org.eclipse.hawkbit.repository.jpa.model.RolloutTargetGroup; import org.eclipse.hawkbit.repository.jpa.model.RolloutTargetGroup_; import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility; import org.eclipse.hawkbit.repository.model.Action; +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.Target; @@ -48,7 +49,6 @@ import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.domain.Specification; import org.springframework.transaction.annotation.Isolation; @@ -65,6 +65,9 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement { @Autowired private RolloutGroupRepository rolloutGroupRepository; + @Autowired + private RolloutRepository rolloutRepository; + @Autowired private ActionRepository actionRepository; @@ -115,9 +118,19 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement { @Override public Page findAllRolloutGroupsWithDetailedStatus(final Long rolloutId, final Pageable pageable) { + if (!rolloutRepository.exists(rolloutId)) { + throw new EntityNotFoundException(Rollout.class, rolloutId); + } + final Page rolloutGroups = rolloutGroupRepository.findByRolloutId(rolloutId, pageable); - final List rolloutGroupIds = rolloutGroups.getContent().stream().map(rollout -> rollout.getId()) + final List rolloutGroupIds = rolloutGroups.getContent().stream().map(RolloutGroup::getId) .collect(Collectors.toList()); + + if (rolloutGroupIds.isEmpty()) { + // groups might already deleted, so return empty list. + return new PageImpl<>(Collections.emptyList()); + } + final Map> allStatesForRollout = getStatusCountItemForRolloutGroup( rolloutGroupIds); @@ -192,8 +205,9 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement { } @Override - public Page findAllTargetsWithActionStatus(final PageRequest pageRequest, + public Page findAllTargetsWithActionStatus(final Pageable pageRequest, final Long rolloutGroupId) { + throwExceptionIfRolloutGroupDoesNotExist(rolloutGroupId); final CriteriaBuilder cb = entityManager.getCriteriaBuilder(); final CriteriaQuery query = cb.createQuery(Object[].class); @@ -218,11 +232,14 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement { .setFirstResult(pageRequest.getOffset()).setMaxResults(pageRequest.getPageSize()).getResultList() .stream().map(o -> new TargetWithActionStatus((Target) o[0], (Action.Status) o[1])) .collect(Collectors.toList()); + return new PageImpl<>(targetWithActionStatus, pageRequest, totalCount); } @Override public Long countTargetsOfRolloutsGroup(@NotNull final Long rolloutGroupId) { + throwExceptionIfRolloutGroupDoesNotExist(rolloutGroupId); + final CriteriaBuilder cb = entityManager.getCriteriaBuilder(); final CriteriaQuery countQuery = cb.createQuery(Long.class); final Root countQueryFrom = countQuery.from(RolloutTargetGroup.class); @@ -231,4 +248,10 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement { return entityManager.createQuery(countQuery).getSingleResult(); } + private void throwExceptionIfRolloutGroupDoesNotExist(final Long rolloutGroupId) { + if (!rolloutGroupRepository.exists(rolloutGroupId)) { + throw new EntityNotFoundException(RolloutGroup.class, rolloutGroupId); + } + } + } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutHelper.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutHelper.java new file mode 100644 index 000000000..4439c3588 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutHelper.java @@ -0,0 +1,106 @@ +/** + * 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.jpa; + +import java.util.Collections; + +import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate; +import org.eclipse.hawkbit.repository.jpa.builder.JpaRolloutGroupCreate; +import org.eclipse.hawkbit.repository.jpa.model.JpaRollout; +import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup; +import org.eclipse.hawkbit.repository.jpa.model.JpaRollout_; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.RolloutGroupConditions; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.jpa.domain.Specification; + +/** + * A collection of static helper methods for the {@link JpaRolloutManagement} + */ +final class JpaRolloutHelper { + private JpaRolloutHelper() { + } + + /** + * In case the given group is missing conditions or actions, they will be + * set from the supplied default conditions. + * + * @param create + * group to check + * @param conditions + * default conditions and actions + */ + static JpaRolloutGroup prepareRolloutGroupWithDefaultConditions(final RolloutGroupCreate create, + final RolloutGroupConditions conditions) { + final JpaRolloutGroup group = ((JpaRolloutGroupCreate) create).build(); + + if (group.getSuccessCondition() == null) { + group.setSuccessCondition(conditions.getSuccessCondition()); + } + if (group.getSuccessConditionExp() == null) { + group.setSuccessConditionExp(conditions.getSuccessConditionExp()); + } + if (group.getSuccessAction() == null) { + group.setSuccessAction(conditions.getSuccessAction()); + } + if (group.getSuccessActionExp() == null) { + group.setSuccessActionExp(conditions.getSuccessActionExp()); + } + + if (group.getErrorCondition() == null) { + group.setErrorCondition(conditions.getErrorCondition()); + } + if (group.getErrorConditionExp() == null) { + group.setErrorConditionExp(conditions.getErrorConditionExp()); + } + if (group.getErrorAction() == null) { + group.setErrorAction(conditions.getErrorAction()); + } + if (group.getErrorActionExp() == null) { + group.setErrorActionExp(conditions.getErrorActionExp()); + } + + return group; + } + + /** + * Builds a {@link Specification} to search a rollout by name or + * description. + * + * @param searchText + * search string + * @param isDeleted + * true if deleted rollouts should be included in + * the search. Otherwise false + * @return criteria specification with a query for name or description of a + * rollout + */ + static Specification likeNameOrDescription(final String searchText, final boolean isDeleted) { + return (rolloutRoot, query, criteriaBuilder) -> { + final String searchTextToLower = searchText.toLowerCase(); + return criteriaBuilder.and(criteriaBuilder.or( + criteriaBuilder.like(criteriaBuilder.lower(rolloutRoot.get(JpaRollout_.name)), searchTextToLower), + criteriaBuilder.like(criteriaBuilder.lower(rolloutRoot.get(JpaRollout_.description)), + searchTextToLower)), + criteriaBuilder.equal(rolloutRoot.get(JpaRollout_.deleted), isDeleted)); + }; + } + + static Page convertPage(final Page findAll, final Pageable pageable) { + return new PageImpl<>(Collections.unmodifiableList(findAll.getContent()), pageable, findAll.getTotalElements()); + } + + static Slice convertPage(final Slice findAll, final Pageable pageable) { + return new PageImpl<>(Collections.unmodifiableList(findAll.getContent()), pageable, 0); + } + +} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutManagement.java index 120853eeb..18fa42cb8 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutManagement.java @@ -8,22 +8,23 @@ */ package org.eclipse.hawkbit.repository.jpa; -import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.function.Function; +import java.util.concurrent.locks.Lock; import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import javax.validation.ConstraintDeclarationException; import org.apache.commons.lang3.StringUtils; +import org.eclipse.hawkbit.repository.AbstractRolloutManagement; import org.eclipse.hawkbit.repository.DeploymentManagement; import org.eclipse.hawkbit.repository.DistributionSetManagement; import org.eclipse.hawkbit.repository.RolloutFields; import org.eclipse.hawkbit.repository.RolloutGroupManagement; +import org.eclipse.hawkbit.repository.RolloutHelper; import org.eclipse.hawkbit.repository.RolloutManagement; import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.builder.GenericRolloutUpdate; @@ -31,6 +32,7 @@ import org.eclipse.hawkbit.repository.builder.RolloutCreate; import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate; import org.eclipse.hawkbit.repository.builder.RolloutUpdate; import org.eclipse.hawkbit.repository.event.remote.entity.RolloutGroupCreatedEvent; +import org.eclipse.hawkbit.repository.event.remote.entity.RolloutUpdatedEvent; import org.eclipse.hawkbit.repository.exception.ConstraintViolationException; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; @@ -43,6 +45,8 @@ import org.eclipse.hawkbit.repository.jpa.model.RolloutTargetGroup; import org.eclipse.hawkbit.repository.jpa.rollout.condition.RolloutGroupActionEvaluator; import org.eclipse.hawkbit.repository.jpa.rollout.condition.RolloutGroupConditionEvaluator; import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility; +import org.eclipse.hawkbit.repository.jpa.specifications.RolloutSpecification; +import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.Action.Status; @@ -58,7 +62,9 @@ import org.eclipse.hawkbit.repository.model.RolloutGroupsValidation; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus; import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus; +import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder; import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer; +import org.eclipse.hawkbit.tenancy.TenantAware; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; @@ -71,87 +77,87 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.repository.Modifying; +import org.springframework.integration.support.locks.LockRegistry; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.AsyncResult; import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionException; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; -import org.springframework.transaction.support.DefaultTransactionDefinition; -import org.springframework.transaction.support.TransactionCallback; -import org.springframework.transaction.support.TransactionTemplate; import org.springframework.util.concurrent.ListenableFuture; import org.springframework.validation.annotation.Validated; +import com.google.common.collect.Lists; + /** * JPA implementation of {@link RolloutManagement}. */ @Validated @Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED) -public class JpaRolloutManagement implements RolloutManagement { - private static final Logger LOGGER = LoggerFactory.getLogger(RolloutManagement.class); +public class JpaRolloutManagement extends AbstractRolloutManagement { + private static final Logger LOGGER = LoggerFactory.getLogger(JpaRolloutManagement.class); /** * Max amount of targets that are handled in one transaction. */ - private static final int TRANSACTION_TARGETS = 1000; + private static final int TRANSACTION_TARGETS = 5_000; + + /** + * Maximum amount of actions that are deleted in one transaction. + */ + private static final int TRANSACTION_ACTIONS = 5_000; @Autowired private RolloutRepository rolloutRepository; - @Autowired - private TargetManagement targetManagement; - @Autowired private RolloutGroupRepository rolloutGroupRepository; - @Autowired - private DeploymentManagement deploymentManagement; - @Autowired private RolloutTargetGroupRepository rolloutTargetGroupRepository; - @Autowired - private RolloutGroupManagement rolloutGroupManagement; - - @Autowired - private DistributionSetManagement distributionSetManagement; - @Autowired private ActionRepository actionRepository; - @Autowired - private ApplicationContext context; - - @Autowired - private ApplicationEventPublisher eventPublisher; - - @Autowired - private NoCountPagingRepository criteriaNoCountDao; - - @Autowired - private PlatformTransactionManager txManager; - - @Autowired - private VirtualPropertyReplacer virtualPropertyReplacer; - @Autowired private AfterTransactionCommitExecutor afterCommit; - @Override - public Page findAll(final Pageable pageable) { - return RolloutHelper.convertPage(rolloutRepository.findAll(pageable), pageable); + JpaRolloutManagement(final TargetManagement targetManagement, final DeploymentManagement deploymentManagement, + final RolloutGroupManagement rolloutGroupManagement, + final DistributionSetManagement distributionSetManagement, final ApplicationContext context, + final ApplicationEventPublisher eventPublisher, final VirtualPropertyReplacer virtualPropertyReplacer, + final PlatformTransactionManager txManager, final TenantAware tenantAware, + final LockRegistry lockRegistry) { + super(targetManagement, deploymentManagement, rolloutGroupManagement, distributionSetManagement, context, + eventPublisher, virtualPropertyReplacer, txManager, tenantAware, lockRegistry); } @Override - public Page findAllByPredicate(final String rsqlParam, final Pageable pageable) { - final Specification specification = RSQLUtility.parse(rsqlParam, RolloutFields.class, - virtualPropertyReplacer); + public Page findAll(final Pageable pageable, final boolean deleted) { + final Specification spec = RolloutSpecification.isDeleted(deleted); + return JpaRolloutHelper.convertPage(rolloutRepository.findAll(spec, pageable), pageable); + } - final Page findAll = rolloutRepository.findAll(specification, pageable); - return RolloutHelper.convertPage(findAll, pageable); + @Override + public Page findAllByPredicate(final String rsqlParam, final Pageable pageable, final boolean deleted) { + final List> specList = Lists.newArrayListWithExpectedSize(2); + specList.add(RSQLUtility.parse(rsqlParam, RolloutFields.class, virtualPropertyReplacer)); + specList.add(RolloutSpecification.isDeleted(deleted)); + + return JpaRolloutHelper.convertPage(findByCriteriaAPI(pageable, specList), pageable); + } + + /** + * Executes findAll with the given {@link Rollout} {@link Specification}s. + */ + private Page findByCriteriaAPI(final Pageable pageable, + final List> specList) { + if (specList == null || specList.isEmpty()) { + return rolloutRepository.findAll(pageable); + } + + return rolloutRepository.findAll(SpecificationsBuilder.combineWithAnd(specList), pageable); } @Override @@ -225,8 +231,7 @@ public class JpaRolloutManagement implements RolloutManagement { group.setTargetPercentage(1.0F / (amountOfGroups - i) * 100); lastSavedGroup = rolloutGroupRepository.save(group); - publishRolloutGroupCreatedEventAfterCommit(lastSavedGroup); - + publishRolloutGroupCreatedEventAfterCommit(lastSavedGroup, rollout); } savedRollout.setRolloutGroupsCreated(amountOfGroups); @@ -240,7 +245,7 @@ public class JpaRolloutManagement implements RolloutManagement { // Preparing the groups final List groups = groupList.stream() - .map(group -> RolloutHelper.prepareRolloutGroupWithDefaultConditions(group, conditions)) + .map(group -> JpaRolloutHelper.prepareRolloutGroupWithDefaultConditions(group, conditions)) .collect(Collectors.toList()); groups.forEach(RolloutHelper::verifyRolloutGroupHasConditions); @@ -261,7 +266,7 @@ public class JpaRolloutManagement implements RolloutManagement { if (srcGroup.getTargetFilterQuery() != null) { group.setTargetFilterQuery(srcGroup.getTargetFilterQuery()); } else { - group.setTargetFilterQuery(""); + group.setTargetFilterQuery(StringUtils.EMPTY); } group.setSuccessCondition(srcGroup.getSuccessCondition()); @@ -277,25 +282,20 @@ public class JpaRolloutManagement implements RolloutManagement { group.setErrorActionExp(srcGroup.getErrorActionExp()); lastSavedGroup = rolloutGroupRepository.save(group); - publishRolloutGroupCreatedEventAfterCommit(lastSavedGroup); + publishRolloutGroupCreatedEventAfterCommit(lastSavedGroup, rollout); } savedRollout.setRolloutGroupsCreated(groups.size()); return rolloutRepository.save(savedRollout); } - private void publishRolloutGroupCreatedEventAfterCommit(final RolloutGroup group) { - afterCommit - .afterCommit(() -> eventPublisher.publishEvent(new RolloutGroupCreatedEvent(group, context.getId()))); + private void publishRolloutGroupCreatedEventAfterCommit(final RolloutGroup group, final Rollout rollout) { + afterCommit.afterCommit(() -> eventPublisher + .publishEvent(new RolloutGroupCreatedEvent(group, rollout.getId(), context.getId()))); } - @Override - @Transactional(isolation = Isolation.READ_UNCOMMITTED) - @Modifying - public void fillRolloutGroupsWithTargets(final Long rolloutId) { - final JpaRollout rollout = getRolloutAndThrowExceptionIfNotFound(rolloutId); - - RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.CREATING); + private void handleCreateRollout(final JpaRollout rollout) { + LOGGER.debug("handleCreateRollout called for rollout {}", rollout.getId()); final List rolloutGroups = RolloutHelper.getOrderedGroups(rollout); int readyGroups = 0; @@ -317,6 +317,7 @@ public class JpaRolloutManagement implements RolloutManagement { // When all groups are ready the rollout status can be changed to be // ready, too. if (readyGroups == rolloutGroups.size()) { + LOGGER.debug("rollout {} creatin done. Switch to READY.", rollout.getId()); rollout.setStatus(RolloutStatus.READY); rollout.setLastCheck(0); rollout.setTotalTargets(totalTargets); @@ -372,17 +373,10 @@ public class JpaRolloutManagement implements RolloutManagement { } } - private int runInNewCountingTransaction(final String transactionName, final TransactionCallback action) { - final DefaultTransactionDefinition def = new DefaultTransactionDefinition(); - def.setName(transactionName); - def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - return new TransactionTemplate(txManager, def).execute(action); - } - private Integer assignTargetsToGroupInNewTransaction(final Rollout rollout, final RolloutGroup group, final String targetFilter, final long limit) { - return runInNewCountingTransaction("assignTargetsToRolloutGroup", status -> { + return runInNewTransaction("assignTargetsToRolloutGroup", status -> { final PageRequest pageRequest = new PageRequest(0, Math.toIntExact(limit)); final List readyGroups = RolloutHelper.getGroupsByStatusIncludingGroup(rollout, RolloutGroupStatus.READY, group); @@ -399,19 +393,6 @@ public class JpaRolloutManagement implements RolloutManagement { targets.forEach(target -> rolloutTargetGroupRepository.save(new RolloutTargetGroup(group, target))); } - private long calculateRemainingTargets(final List groups, final String targetFilter, - final Long createdAt) { - final String baseFilter = RolloutHelper.getTargetFilterQuery(targetFilter, createdAt); - final long totalTargets = targetManagement.countTargetByTargetFilterQuery(baseFilter); - if (totalTargets == 0) { - throw new ConstraintDeclarationException("Rollout target filter does not match any targets"); - } - - final RolloutGroupsValidation validation = validateTargetsInGroups(groups, baseFilter, totalTargets); - - return totalTargets - validation.getTargetsInGroups(); - } - @Override @Async public ListenableFuture validateTargetsInGroups(final List groups, @@ -427,67 +408,12 @@ public class JpaRolloutManagement implements RolloutManagement { groups.stream().map(RolloutGroupCreate::build).collect(Collectors.toList()), baseFilter, totalTargets)); } - private RolloutGroupsValidation validateTargetsInGroups(final List groups, final String baseFilter, - final long totalTargets) { - final List groupTargetCounts = new ArrayList<>(groups.size()); - final Map targetFilterCounts = groups.stream() - .map(group -> RolloutHelper.getGroupTargetFilter(baseFilter, group)).distinct() - .collect(Collectors.toMap(Function.identity(), targetManagement::countTargetByTargetFilterQuery)); - - long unusedTargetsCount = 0; - - for (int i = 0; i < groups.size(); i++) { - final RolloutGroup group = groups.get(i); - final String groupTargetFilter = RolloutHelper.getGroupTargetFilter(baseFilter, group); - RolloutHelper.verifyRolloutGroupTargetPercentage(group.getTargetPercentage()); - - final long targetsInGroupFilter = targetFilterCounts.get(groupTargetFilter); - final long overlappingTargets = countOverlappingTargetsWithPreviousGroups(baseFilter, groups, group, i, - targetFilterCounts); - - final long realTargetsInGroup; - // Assume that targets which were not used in the previous groups - // are used in this group - if (overlappingTargets > 0 && unusedTargetsCount > 0) { - realTargetsInGroup = targetsInGroupFilter - overlappingTargets + unusedTargetsCount; - unusedTargetsCount = 0; - } else { - realTargetsInGroup = targetsInGroupFilter - overlappingTargets; - } - - final long reducedTargetsInGroup = Math - .round(group.getTargetPercentage() / 100 * (double) realTargetsInGroup); - groupTargetCounts.add(reducedTargetsInGroup); - unusedTargetsCount += realTargetsInGroup - reducedTargetsInGroup; - - } - - return new RolloutGroupsValidation(totalTargets, groupTargetCounts); - } - - private long countOverlappingTargetsWithPreviousGroups(final String baseFilter, final List groups, - final RolloutGroup group, final int groupIndex, final Map targetFilterCounts) { - // there can't be overlapping targets in the first group - if (groupIndex == 0) { - return 0; - } - final List previousGroups = groups.subList(0, groupIndex); - final String overlappingTargetsFilter = RolloutHelper.getOverlappingWithGroupsTargetFilter(baseFilter, - previousGroups, group); - - if (targetFilterCounts.containsKey(overlappingTargetsFilter)) { - return targetFilterCounts.get(overlappingTargetsFilter); - } else { - final long overlappingTargets = targetManagement.countTargetByTargetFilterQuery(overlappingTargetsFilter); - targetFilterCounts.put(overlappingTargetsFilter, overlappingTargets); - return overlappingTargets; - } - } - @Override @Transactional(isolation = Isolation.READ_UNCOMMITTED) @Modifying public Rollout startRollout(final Long rolloutId) { + LOGGER.debug("startRollout called for rollout {}", rolloutId); + final JpaRollout rollout = getRolloutAndThrowExceptionIfNotFound(rolloutId); RolloutHelper.checkIfRolloutCanStarted(rollout, rollout); rollout.setStatus(RolloutStatus.STARTING); @@ -496,6 +422,7 @@ public class JpaRolloutManagement implements RolloutManagement { } private void startFirstRolloutGroup(final Rollout rollout) { + LOGGER.debug("startFirstRolloutGroup called for rollout {}", rollout.getId()); RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.STARTING); final JpaRollout jpaRollout = (JpaRollout) rollout; @@ -516,7 +443,6 @@ public class JpaRolloutManagement implements RolloutManagement { } private boolean ensureAllGroupsAreScheduled(final Rollout rollout) { - RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.STARTING); final JpaRollout jpaRollout = (JpaRollout) rollout; final List groupsToBeScheduled = rolloutGroupRepository.findByRolloutAndStatus(rollout, @@ -566,7 +492,7 @@ public class JpaRolloutManagement implements RolloutManagement { } private Integer createActionsForTargetsInNewTransaction(final long rolloutId, final long groupId, final int limit) { - return runInNewCountingTransaction("createActionsForTargets", status -> { + return runInNewTransaction("createActionsForTargets", status -> { final PageRequest pageRequest = new PageRequest(0, limit); final Rollout rollout = rolloutRepository.findOne(rolloutId); final RolloutGroup group = rolloutGroupRepository.findOne(groupId); @@ -618,7 +544,7 @@ public class JpaRolloutManagement implements RolloutManagement { @Modifying public void pauseRollout(final Long rolloutId) { final JpaRollout rollout = getRolloutAndThrowExceptionIfNotFound(rolloutId); - if (rollout.getStatus() != RolloutStatus.RUNNING) { + if (!RolloutStatus.RUNNING.equals(rollout.getStatus())) { throw new RolloutIllegalStateException("Rollout can only be paused in state running but current state is " + rollout.getStatus().name().toLowerCase()); } @@ -644,39 +570,27 @@ public class JpaRolloutManagement implements RolloutManagement { rolloutRepository.save(rollout); } - @Override - @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED) - @Modifying - public void checkRunningRollouts(final long delayBetweenChecks) { - final List rolloutsToCheck = getRolloutsToCheckForStatus(delayBetweenChecks, RolloutStatus.RUNNING); - if (rolloutsToCheck.isEmpty()) { - return; + private void handleRunningRollout(final JpaRollout rollout) { + LOGGER.debug("handleRunningRollout called for rollout {}", rollout.getId()); + + final List rolloutGroupsRunning = rolloutGroupRepository.findByRolloutAndStatus(rollout, + RolloutGroupStatus.RUNNING); + + if (rolloutGroupsRunning.isEmpty()) { + // no running rollouts, probably there was an error + // somewhere at the latest group. And the latest group has + // been switched from running into error state. So we need + // to find the latest group which + executeLatestRolloutGroup(rollout); + } else { + LOGGER.debug("Rollout {} has {} running groups", rollout.getId(), rolloutGroupsRunning.size()); + executeRolloutGroups(rollout, rolloutGroupsRunning); } - LOGGER.info("Found {} running rollouts to check", rolloutsToCheck.size()); - - for (final JpaRollout rollout : rolloutsToCheck) { - LOGGER.debug("Checking rollout {}", rollout); - - final List rolloutGroupsRunning = rolloutGroupRepository.findByRolloutAndStatus(rollout, - RolloutGroupStatus.RUNNING); - - if (rolloutGroupsRunning.isEmpty()) { - // no running rollouts, probably there was an error - // somewhere at the latest group. And the latest group has - // been switched from running into error state. So we need - // to find the latest group which - executeLatestRolloutGroup(rollout); - } else { - LOGGER.debug("Rollout {} has {} running groups", rollout.getId(), rolloutGroupsRunning.size()); - executeRolloutGroups(rollout, rolloutGroupsRunning); - } - - if (isRolloutComplete(rollout)) { - LOGGER.info("Rollout {} is finished, setting finished status", rollout); - rollout.setStatus(RolloutStatus.FINISHED); - rolloutRepository.save(rollout); - } + if (isRolloutComplete(rollout)) { + LOGGER.info("Rollout {} is finished, setting FINISHED status", rollout); + rollout.setStatus(RolloutStatus.FINISHED); + rolloutRepository.save(rollout); } } @@ -741,7 +655,7 @@ public class JpaRolloutManagement implements RolloutManagement { } private boolean isRolloutComplete(final JpaRollout rollout) { - final Long groupsActiveLeft = rolloutGroupRepository.countByRolloutAndStatusOrStatus(rollout, + final Long groupsActiveLeft = rolloutGroupRepository.countByRolloutIdAndStatusOrStatus(rollout.getId(), RolloutGroupStatus.RUNNING, RolloutGroupStatus.SCHEDULED); return groupsActiveLeft == 0; } @@ -798,90 +712,170 @@ public class JpaRolloutManagement implements RolloutManagement { } @Override - @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED) - @Modifying - public void checkCreatingRollouts(final long delayBetweenChecks) { - final List rolloutsToCheck = getRolloutsToCheckForStatus(delayBetweenChecks, RolloutStatus.CREATING) - .stream().map(Rollout::getId).collect(Collectors.toList()); - if (rolloutsToCheck.isEmpty()) { + // No transaction, will be created per handled rollout + @Transactional(propagation = Propagation.NEVER) + public void handleRollouts() { + rolloutRepository + .findByStatusIn(Lists.newArrayList(RolloutStatus.CREATING, RolloutStatus.DELETING, + RolloutStatus.STARTING, RolloutStatus.READY, RolloutStatus.RUNNING)) + .forEach(this::handleRollout); + } + + private void handleRollout(final Long rolloutId) { + LOGGER.debug("handleRollout called for rollout {}", rolloutId); + + final String tenant = tenantAware.getCurrentTenant(); + + final String handlerId = tenant + "-rollout-" + rolloutId; + final Lock lock = lockRegistry.obtain(handlerId); + if (!lock.tryLock()) { return; } - LOGGER.info("Found {} creating rollouts to check", rolloutsToCheck.size()); + try { + runInNewTransaction(handlerId, status -> executeFittingHandler(rolloutId)); + } finally { + lock.unlock(); + } + } - rolloutsToCheck.forEach(this::fillRolloutGroupsWithTargets); + private int executeFittingHandler(final Long rolloutId) { + final JpaRollout rollout = rolloutRepository.findOne(rolloutId); + switch (rollout.getStatus()) { + case CREATING: + handleCreateRollout(rollout); + break; + case DELETING: + handleDeleteRollout(rollout); + break; + case READY: + handleReadyRollout(rollout); + break; + case STARTING: + handleStartingRollout(rollout); + break; + case RUNNING: + handleRunningRollout(rollout); + break; + default: + LOGGER.error("Rollout in status {} not supposed to be handled!", rollout.getStatus()); + break; + } + + return 0; + } + + private void handleStartingRollout(final Rollout rollout) { + LOGGER.debug("handleStartingRollout called for rollout {}", rollout.getId()); + + if (ensureAllGroupsAreScheduled(rollout)) { + startFirstRolloutGroup(rollout); + } + } + + private void handleReadyRollout(final Rollout rollout) { + if (rollout.getStartAt() != null && rollout.getStartAt() <= System.currentTimeMillis()) { + LOGGER.debug( + "handleReadyRollout called for rollout {} with autostart beyond define time. Switch to STARTING", + rollout.getId()); + startRollout(rollout.getId()); + } } @Override - @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED) + @Transactional(isolation = Isolation.READ_UNCOMMITTED) @Modifying - public void checkStartingRollouts(final long delayBetweenChecks) { - final List rolloutsToCheck = getRolloutsToCheckForStatus(delayBetweenChecks, - RolloutStatus.STARTING); - if (rolloutsToCheck.isEmpty()) { + public void deleteRollout(final long rolloutId) { + final JpaRollout jpaRollout = rolloutRepository.findOne(rolloutId); + if (RolloutStatus.DELETING.equals(jpaRollout.getStatus())) { return; } - LOGGER.info("Found {} starting rollouts to check", rolloutsToCheck.size()); - - rolloutsToCheck.forEach(rollout -> { - if (ensureAllGroupsAreScheduled(rollout)) { - startFirstRolloutGroup(rollout); - } - }); + jpaRollout.setStatus(RolloutStatus.DELETING); + rolloutRepository.save(jpaRollout); } - @Override - @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED) - @Modifying - public void checkReadyRollouts(final long delayBetweenChecks) { - final List rolloutsToCheck = getRolloutsToCheckForStatus(delayBetweenChecks, RolloutStatus.READY); - if (rolloutsToCheck.isEmpty()) { + private void handleDeleteRollout(final JpaRollout rollout) { + LOGGER.debug("handleDeleteRollout called for {}", rollout.getId()); + + // check if there are actions beyond schedule + boolean hardDeleteRolloutGroups = !actionRepository.existsByRolloutIdAndStatusNotIn(rollout.getId(), + Status.SCHEDULED); + if (hardDeleteRolloutGroups) { + LOGGER.debug("Rollout {} has no actions other than scheduled -> hard delete", rollout.getId()); + hardDeleteRollout(rollout); + return; + } + // clean up all scheduled actions + final Slice scheduledActions = findScheduledActionsByRollout(rollout); + deleteScheduledActions(rollout, scheduledActions); + + // avoid another scheduler round and re-check if all scheduled actions + // has been cleaned up + final boolean hasScheduledActionsLeft = findScheduledActionsByRollout(rollout).getNumberOfElements() > 0; + if (hasScheduledActionsLeft) { return; } - LOGGER.info("Found {} ready rollouts to check", rolloutsToCheck.size()); - - final long now = System.currentTimeMillis(); - - rolloutsToCheck.forEach(rollout -> { - if (rollout.getStartAt() != null && rollout.getStartAt() <= now) { - startRollout(rollout.getId()); - } - }); - } - - private List getRolloutsToCheckForStatus(final long delayBetweenChecks, final RolloutStatus status) { - final long lastCheck = System.currentTimeMillis(); - final int updated = rolloutRepository.updateLastCheck(lastCheck, delayBetweenChecks, status); - if (updated == 0) { - // nothing to check, maybe another instance already checked in - // between - LOGGER.debug("No rollouts starting check necessary for current scheduled check {}, next check at {}", - lastCheck, lastCheck + delayBetweenChecks); - return Collections.emptyList(); + // only hard delete the rollout if no actions are left for the rollout. + // In case actions are left, they are probably are running or were + // running before, so only soft delete. + hardDeleteRolloutGroups = !actionRepository.existsByRolloutId(rollout.getId()); + if (hardDeleteRolloutGroups) { + hardDeleteRollout(rollout); + return; } - return rolloutRepository.findByLastCheckAndStatus(lastCheck, status); + // set soft delete + rollout.setStatus(RolloutStatus.DELETED); + rollout.setDeleted(true); + rolloutRepository.save(rollout); + } + + private void hardDeleteRollout(final JpaRollout rollout) { + rolloutRepository.delete(rollout); + } + + private void deleteScheduledActions(final JpaRollout rollout, final Slice scheduledActions) { + final boolean hasScheduledActions = scheduledActions.getNumberOfElements() > 0; + + if (hasScheduledActions) { + try { + final Iterable iterable = scheduledActions::iterator; + final List actionIds = StreamSupport.stream(iterable.spliterator(), false).map(Action::getId) + .collect(Collectors.toList()); + actionRepository.deleteByIdIn(actionIds); + afterCommit.afterCommit(() -> eventPublisher.publishEvent( + new RolloutUpdatedEvent(rollout, EventPublisherHolder.getInstance().getApplicationId()))); + } catch (final RuntimeException e) { + LOGGER.error("Exception during deletion of actions of rollout {}", rollout, e); + } + } + } + + private Slice findScheduledActionsByRollout(final JpaRollout rollout) { + return actionRepository.findByRolloutIdAndStatus(new PageRequest(0, TRANSACTION_ACTIONS), rollout.getId(), + Status.SCHEDULED); } @Override public Long countRolloutsAll() { - return rolloutRepository.count(); + return rolloutRepository.count(RolloutSpecification.isDeleted(false)); } @Override public Long countRolloutsAllByFilters(final String searchText) { - return rolloutRepository.count(RolloutHelper.likeNameOrDescription(searchText)); + return rolloutRepository.count(JpaRolloutHelper.likeNameOrDescription(searchText, false)); } @Override - public Slice findRolloutWithDetailedStatusByFilters(final Pageable pageable, final String searchText) { - final Specification specs = RolloutHelper.likeNameOrDescription(searchText); - final Slice findAll = criteriaNoCountDao.findAll(specs, pageable, JpaRollout.class); + public Slice findRolloutWithDetailedStatusByFilters(final Pageable pageable, final String searchText, + final boolean deleted) { + final Slice findAll = findByCriteriaAPI(pageable, + Lists.newArrayList(JpaRolloutHelper.likeNameOrDescription(searchText, deleted))); setRolloutStatusDetails(findAll); - return RolloutHelper.convertPage(findAll, pageable); + return JpaRolloutHelper.convertPage(findAll, pageable); } @Override @@ -917,10 +911,12 @@ public class JpaRolloutManagement implements RolloutManagement { } @Override - public Page findAllRolloutsWithDetailedStatus(final Pageable pageable) { - final Page rollouts = rolloutRepository.findAll(pageable); + public Page findAllRolloutsWithDetailedStatus(final Pageable pageable, final boolean deleted) { + Page rollouts; + final Specification spec = RolloutSpecification.isDeleted(deleted); + rollouts = rolloutRepository.findAll(spec, pageable); setRolloutStatusDetails(rollouts); - return RolloutHelper.convertPage(rollouts, pageable); + return JpaRolloutHelper.convertPage(rollouts, pageable); } @Override @@ -940,8 +936,12 @@ public class JpaRolloutManagement implements RolloutManagement { } private Map> getStatusCountItemForRollout(final List rolloutIds) { - final List resultList = actionRepository.getStatusCountByRolloutId(rolloutIds); - return resultList.stream().collect(Collectors.groupingBy(TotalTargetCountActionStatus::getId)); + if (!rolloutIds.isEmpty()) { + final List resultList = actionRepository + .getStatusCountByRolloutId(rolloutIds); + return resultList.stream().collect(Collectors.groupingBy(TotalTargetCountActionStatus::getId)); + } + return null; } private void setRolloutStatusDetails(final Slice rollouts) { @@ -949,10 +949,12 @@ public class JpaRolloutManagement implements RolloutManagement { final Map> allStatesForRollout = getStatusCountItemForRollout( rolloutIds); - for (final Rollout rollout : rollouts) { - final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus( - allStatesForRollout.get(rollout.getId()), rollout.getTotalTargets()); - ((JpaRollout) rollout).setTotalTargetCountStatus(totalTargetCountStatus); + if (allStatesForRollout != null) { + rollouts.forEach(rollout -> { + final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus( + allStatesForRollout.get(rollout.getId()), rollout.getTotalTargets()); + rollout.setTotalTargetCountStatus(totalTargetCountStatus); + }); } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSoftwareManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSoftwareManagement.java index 8735324e1..5ffeb6f91 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSoftwareManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSoftwareManagement.java @@ -209,8 +209,8 @@ public class JpaSoftwareManagement implements SoftwareManagement { return softwareModuleRepository.findOneByNameAndVersionAndTypeId(name, version, typeId); } - private boolean isUnassigned(final JpaSoftwareModule bsmMerged) { - return distributionSetRepository.countByModules(bsmMerged) <= 0; + private boolean isUnassigned(final Long moduleId) { + return distributionSetRepository.countByModulesId(moduleId) <= 0; } private Slice findSwModuleByCriteriaAPI(final Pageable pageable, @@ -225,7 +225,7 @@ public class JpaSoftwareManagement implements SoftwareManagement { private void deleteGridFsArtifacts(final JpaSoftwareModule swModule) { for (final Artifact localArtifact : swModule.getArtifacts()) { - artifactManagement.clearArtifactBinary(localArtifact.getId()); + artifactManagement.clearArtifactBinary(localArtifact.getSha1Hash(), swModule.getId()); } } @@ -246,12 +246,9 @@ public class JpaSoftwareManagement implements SoftwareManagement { // delete binary data of artifacts deleteGridFsArtifacts(swModule); - if (isUnassigned(swModule)) { - - softwareModuleRepository.delete(swModule); - + if (isUnassigned(swModule.getId())) { + softwareModuleRepository.delete(swModule.getId()); } else { - assignedModuleIds.add(swModule.getId()); } }); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSystemManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSystemManagement.java index 868cfa078..4305130aa 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSystemManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSystemManagement.java @@ -218,8 +218,6 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst tenantConfigurationRepository.deleteByTenantIgnoreCase(tenant); targetRepository.deleteByTenantIgnoreCase(tenant); targetFilterQueryRepository.deleteByTenantIgnoreCase(tenant); - actionRepository.deleteByTenantIgnoreCase(tenant); - rolloutGroupRepository.deleteByTenantIgnoreCase(tenant); rolloutRepository.deleteByTenantIgnoreCase(tenant); artifactRepository.deleteByTenantIgnoreCase(tenant); targetTagRepository.deleteByTenantIgnoreCase(tenant); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java index fa3db9da3..9c0a49391 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java @@ -607,7 +607,7 @@ public class JpaTargetManagement implements TargetManagement { final JpaTarget target = create.build(); - if (targetRepository.findByControllerId(target.getControllerId()).isPresent()) { + if (targetRepository.existsByControllerId(target.getControllerId())) { throw new EntityAlreadyExistsException(); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/LocalArtifactRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/LocalArtifactRepository.java index 3d84492ce..ef8463c94 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/LocalArtifactRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/LocalArtifactRepository.java @@ -13,9 +13,11 @@ import java.util.Optional; import org.eclipse.hawkbit.repository.jpa.model.JpaArtifact; import org.eclipse.hawkbit.repository.model.Artifact; +import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; @@ -54,6 +56,21 @@ public interface LocalArtifactRepository extends BaseEntityRepository findBySha1Hash(String sha1Hash); + /** + * Verifies if an artifact exists that has given hash and is still related + * to a {@link SoftwareModule} other than a given one and not + * {@link SoftwareModule#isDeleted()}. + * + * @param sha1 + * to search for + * @param moduleId + * to ignore in relationship check + * + * @return true if such an artifact exists + */ + @Query("SELECT CASE WHEN COUNT(a)>0 THEN 'true' ELSE 'false' END FROM JpaArtifact a WHERE a.sha1Hash = :sha1 AND a.softwareModule.id != :moduleId AND a.softwareModule.deleted = 0") + boolean existsWithSha1HashAndSoftwareModuleIdIsNot(@Param("sha1") String sha1, @Param("moduleId") Long moduleId); + /** * Searches for a {@link Artifact} based on given gridFsFileName. * diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java index 8a08582e7..188d9db51 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java @@ -9,6 +9,7 @@ package org.eclipse.hawkbit.repository.jpa; import java.util.Map; +import java.util.concurrent.Executor; import javax.persistence.EntityManager; import javax.sql.DataSource; @@ -23,7 +24,6 @@ import org.eclipse.hawkbit.repository.ReportManagement; import org.eclipse.hawkbit.repository.RepositoryProperties; import org.eclipse.hawkbit.repository.RolloutGroupManagement; import org.eclipse.hawkbit.repository.RolloutManagement; -import org.eclipse.hawkbit.repository.RolloutProperties; import org.eclipse.hawkbit.repository.SoftwareManagement; import org.eclipse.hawkbit.repository.SystemManagement; import org.eclipse.hawkbit.repository.TagManagement; @@ -53,6 +53,7 @@ import org.eclipse.hawkbit.repository.jpa.model.helper.EntityInterceptorHolder; import org.eclipse.hawkbit.repository.jpa.model.helper.SecurityTokenGeneratorHolder; import org.eclipse.hawkbit.repository.jpa.model.helper.SystemSecurityContextHolder; import org.eclipse.hawkbit.repository.jpa.model.helper.TenantAwareHolder; +import org.eclipse.hawkbit.repository.jpa.rollout.RolloutScheduler; import org.eclipse.hawkbit.repository.jpa.rsql.RsqlParserValidationOracle; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetType; @@ -69,18 +70,23 @@ import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration; import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.orm.jpa.EntityScan; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.context.annotation.Profile; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.integration.support.locks.LockRegistry; import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter; import org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter; import org.springframework.scheduling.annotation.EnableScheduling; @@ -101,7 +107,7 @@ import com.google.common.collect.Maps; @EnableAspectJAutoProxy @Configuration @ComponentScan -@EnableConfigurationProperties({ RepositoryProperties.class, ControllerPollProperties.class, RolloutProperties.class, +@EnableConfigurationProperties({ RepositoryProperties.class, ControllerPollProperties.class, TenantConfigurationProperties.class }) @EnableScheduling @EntityScan("org.eclipse.hawkbit.repository.jpa.model") @@ -115,7 +121,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { @Bean @ConditionalOnMissingBean - public RsqlValidationOracle rsqlValidationOracle() { + RsqlValidationOracle rsqlValidationOracle() { return new RsqlParserValidationOracle(); } @@ -127,7 +133,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { * @return DistributionSetBuilder bean */ @Bean - public DistributionSetBuilder distributionSetBuilder(final DistributionSetManagement distributionSetManagement, + DistributionSetBuilder distributionSetBuilder(final DistributionSetManagement distributionSetManagement, final SoftwareManagement softwareManagement) { return new JpaDistributionSetBuilder(distributionSetManagement, softwareManagement); } @@ -140,7 +146,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { * @return DistributionSetTypeBuilder bean */ @Bean - public DistributionSetTypeBuilder distributionSetTypeBuilder(final SoftwareManagement softwareManagement) { + DistributionSetTypeBuilder distributionSetTypeBuilder(final SoftwareManagement softwareManagement) { return new JpaDistributionSetTypeBuilder(softwareManagement); } @@ -150,7 +156,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { * @return SoftwareModuleBuilder bean */ @Bean - public SoftwareModuleBuilder softwareModuleBuilder(final SoftwareManagement softwareManagement) { + SoftwareModuleBuilder softwareModuleBuilder(final SoftwareManagement softwareManagement) { return new JpaSoftwareModuleBuilder(softwareManagement); } @@ -160,7 +166,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { * @return RolloutBuilder bean */ @Bean - public RolloutBuilder rolloutBuilder(final DistributionSetManagement distributionSetManagement) { + RolloutBuilder rolloutBuilder(final DistributionSetManagement distributionSetManagement) { return new JpaRolloutBuilder(distributionSetManagement); } @@ -171,8 +177,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { * @return TargetFilterQueryBuilder bean */ @Bean - public TargetFilterQueryBuilder targetFilterQueryBuilder( - final DistributionSetManagement distributionSetManagement) { + TargetFilterQueryBuilder targetFilterQueryBuilder(final DistributionSetManagement distributionSetManagement) { return new JpaTargetFilterQueryBuilder(distributionSetManagement); } @@ -182,7 +187,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { * e.g. JPA entities. */ @Bean - public SystemSecurityContextHolder systemSecurityContextHolder() { + SystemSecurityContextHolder systemSecurityContextHolder() { return SystemSecurityContextHolder.getInstance(); } @@ -192,7 +197,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { * directly, e.g. JPA entities. */ @Bean - public TenantConfigurationManagementHolder tenantConfigurationManagementHolder() { + TenantConfigurationManagementHolder tenantConfigurationManagementHolder() { return TenantConfigurationManagementHolder.getInstance(); } @@ -203,7 +208,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { * entities. */ @Bean - public SystemManagementHolder systemManagementHolder() { + SystemManagementHolder systemManagementHolder() { return SystemManagementHolder.getInstance(); } @@ -214,7 +219,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { * entities. */ @Bean - public TenantAwareHolder tenantAwareHolder() { + TenantAwareHolder tenantAwareHolder() { return TenantAwareHolder.getInstance(); } @@ -225,7 +230,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { * injection */ @Bean - public SecurityTokenGeneratorHolder securityTokenGeneratorHolder() { + SecurityTokenGeneratorHolder securityTokenGeneratorHolder() { return SecurityTokenGeneratorHolder.getInstance(); } @@ -233,7 +238,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { * @return the singleton instance of the {@link EntityInterceptorHolder} */ @Bean - public EntityInterceptorHolder entityInterceptorHolder() { + EntityInterceptorHolder entityInterceptorHolder() { return EntityInterceptorHolder.getInstance(); } @@ -243,7 +248,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { * {@link AfterTransactionCommitExecutorHolder} */ @Bean - public AfterTransactionCommitExecutorHolder afterTransactionCommitExecutorHolder() { + AfterTransactionCommitExecutorHolder afterTransactionCommitExecutorHolder() { return AfterTransactionCommitExecutorHolder.getInstance(); } @@ -261,7 +266,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { * @return {@link ExceptionMappingAspectHandler} aspect bean */ @Bean - public ExceptionMappingAspectHandler createRepositoryExceptionHandlerAdvice() { + ExceptionMappingAspectHandler createRepositoryExceptionHandlerAdvice() { return new ExceptionMappingAspectHandler(); } @@ -305,7 +310,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { */ @Bean @ConditionalOnMissingBean - public SystemManagement systemManagement() { + SystemManagement systemManagement() { return new JpaSystemManagement(); } @@ -316,7 +321,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { */ @Bean @ConditionalOnMissingBean - public ReportManagement reportManagement() { + ReportManagement reportManagement() { return new JpaReportManagement(); } @@ -327,7 +332,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { */ @Bean @ConditionalOnMissingBean - public DistributionSetManagement distributionSetManagement() { + DistributionSetManagement distributionSetManagement() { return new JpaDistributionSetManagement(); } @@ -338,7 +343,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { */ @Bean @ConditionalOnMissingBean - public TenantStatsManagement tenantStatsManagement() { + TenantStatsManagement tenantStatsManagement() { return new JpaTenantStatsManagement(); } @@ -349,7 +354,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { */ @Bean @ConditionalOnMissingBean - public TenantConfigurationManagement tenantConfigurationManagement() { + TenantConfigurationManagement tenantConfigurationManagement() { return new JpaTenantConfigurationManagement(); } @@ -360,7 +365,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { */ @Bean @ConditionalOnMissingBean - public TargetManagement targetManagement() { + TargetManagement targetManagement() { return new JpaTargetManagement(); } @@ -378,7 +383,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { */ @Bean @ConditionalOnMissingBean - public TargetFilterQueryManagement targetFilterQueryManagement( + TargetFilterQueryManagement targetFilterQueryManagement( final TargetFilterQueryRepository targetFilterQueryRepository, final VirtualPropertyReplacer virtualPropertyReplacer, final DistributionSetManagement distributionSetManagement) { @@ -393,7 +398,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { */ @Bean @ConditionalOnMissingBean - public TagManagement tagManagement() { + TagManagement tagManagement() { return new JpaTagManagement(); } @@ -404,19 +409,21 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { */ @Bean @ConditionalOnMissingBean - public SoftwareManagement softwareManagement() { + SoftwareManagement softwareManagement() { return new JpaSoftwareManagement(); } - /** - * {@link JpaRolloutManagement} bean. - * - * @return a new {@link RolloutManagement} - */ @Bean @ConditionalOnMissingBean - public RolloutManagement rolloutManagement() { - return new JpaRolloutManagement(); + RolloutManagement rolloutManagement(final TargetManagement targetManagement, + final DeploymentManagement deploymentManagement, final RolloutGroupManagement rolloutGroupManagement, + final DistributionSetManagement distributionSetManagement, final ApplicationContext context, + final ApplicationEventPublisher eventPublisher, final VirtualPropertyReplacer virtualPropertyReplacer, + final PlatformTransactionManager txManager, final TenantAware tenantAware, + final LockRegistry lockRegistry) { + return new JpaRolloutManagement(targetManagement, deploymentManagement, rolloutGroupManagement, + distributionSetManagement, context, eventPublisher, virtualPropertyReplacer, txManager, tenantAware, + lockRegistry); } /** @@ -426,7 +433,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { */ @Bean @ConditionalOnMissingBean - public RolloutGroupManagement rolloutGroupManagement() { + RolloutGroupManagement rolloutGroupManagement() { return new JpaRolloutGroupManagement(); } @@ -437,7 +444,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { */ @Bean @ConditionalOnMissingBean - public DeploymentManagement deploymentManagement() { + DeploymentManagement deploymentManagement() { return new JpaDeploymentManagement(); } @@ -448,7 +455,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { */ @Bean @ConditionalOnMissingBean - public ControllerManagement controllerManagement() { + ControllerManagement controllerManagement() { return new JpaControllerManagement(); } @@ -460,7 +467,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { @Bean @ConditionalOnMissingBean - public ArtifactManagement artifactManagement() { + ArtifactManagement artifactManagement() { return new JpaArtifactManagement(); } @@ -482,7 +489,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { */ @Bean @ConditionalOnMissingBean - public EventEntityManagerHolder eventEntityManagerHolder() { + EventEntityManagerHolder eventEntityManagerHolder() { return EventEntityManagerHolder.getInstance(); } @@ -497,7 +504,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { */ @Bean @ConditionalOnMissingBean - public EventEntityManager eventEntityManager(final TenantAware aware, final EntityManager entityManager) { + EventEntityManager eventEntityManager(final TenantAware aware, final EntityManager entityManager) { return new JpaEventEntityManager(aware, entityManager); } @@ -516,7 +523,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { */ @Bean @ConditionalOnMissingBean - public AutoAssignChecker autoAssignChecker(final TargetFilterQueryManagement targetFilterQueryManagement, + AutoAssignChecker autoAssignChecker(final TargetFilterQueryManagement targetFilterQueryManagement, final TargetManagement targetManagement, final DeploymentManagement deploymentManagement, final PlatformTransactionManager transactionManager) { return new AutoAssignChecker(targetFilterQueryManagement, targetManagement, deploymentManagement, @@ -525,6 +532,9 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { /** * {@link AutoAssignScheduler} bean. + * + * Note: does not activate in test profile, otherwise it is hard to test the + * auto assign functionality. * * @param tenantAware * to run as specific tenant @@ -534,14 +544,49 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { * to run as system * @param autoAssignChecker * to run a check as tenant + * @param lockRegistry + * to lock the tenant for auto assigment * @return a new {@link AutoAssignChecker} */ @Bean @ConditionalOnMissingBean + // don't active the auto assign scheduler in test, otherwise it is hard to + // test + @Profile("!test") @ConditionalOnProperty(prefix = "hawkbit.autoassign.scheduler", name = "enabled", matchIfMissing = true) - public AutoAssignScheduler autoAssignScheduler(final TenantAware tenantAware, - final SystemManagement systemManagement, final SystemSecurityContext systemSecurityContext, - final AutoAssignChecker autoAssignChecker) { - return new AutoAssignScheduler(tenantAware, systemManagement, systemSecurityContext, autoAssignChecker); + AutoAssignScheduler autoAssignScheduler(final TenantAware tenantAware, final SystemManagement systemManagement, + final SystemSecurityContext systemSecurityContext, final AutoAssignChecker autoAssignChecker, + final LockRegistry lockRegistry) { + return new AutoAssignScheduler(tenantAware, systemManagement, systemSecurityContext, autoAssignChecker, + lockRegistry); + } + + /** + * {@link RolloutScheduler} bean. + * + * Note: does not activate in test profile, otherwise it is hard to test the + * rollout handling functionality. + * + * @param tenantAware + * to run as specific tenant + * @param systemManagement + * to find all tenants + * @param rolloutManagement + * to run the rollout handler + * @param systemSecurityContext + * to run as system + * @param threadPoolExecutor + * to execute the handlers in parallel + * @return a new {@link RolloutScheduler} bean. + */ + @Bean + @ConditionalOnMissingBean + @Profile("!test") + @ConditionalOnProperty(prefix = "hawkbit.rollout.scheduler", name = "enabled", matchIfMissing = true) + RolloutScheduler rolloutScheduler(final TenantAware tenantAware, final SystemManagement systemManagement, + final RolloutManagement rolloutManagement, final SystemSecurityContext systemSecurityContext, + @Qualifier("asyncExecutor") final Executor threadPoolExecutor) { + return new RolloutScheduler(tenantAware, systemManagement, rolloutManagement, systemSecurityContext, + threadPoolExecutor); } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RolloutGroupRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RolloutGroupRepository.java index 1aeb8343a..dcfc9d528 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RolloutGroupRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RolloutGroupRepository.java @@ -60,8 +60,8 @@ public interface RolloutGroupRepository * rollout-management to find out rolloutgroups which are in specific * states. * - * @param rollout - * the rollout the rolloutgroup belong to + * @param rolloutId + * the ID of the rollout the rolloutgroup belong to * @param rolloutGroupStatus1 * the status of the rollout groups * @param rolloutGroupStatus2 @@ -69,22 +69,45 @@ public interface RolloutGroupRepository * @return the count of rollout groups belonging to a rollout in specific * status */ - @Query("SELECT COUNT(r.id) FROM JpaRolloutGroup r WHERE r.rollout = :rollout and (r.status = :status1 or r.status = :status2)") - Long countByRolloutAndStatusOrStatus(@Param("rollout") JpaRollout rollout, + @Query("SELECT COUNT(r.id) FROM JpaRolloutGroup r WHERE r.rollout.id = :rolloutId and (r.status = :status1 or r.status = :status2)") + Long countByRolloutIdAndStatusOrStatus(@Param("rolloutId") long rolloutId, @Param("status1") RolloutGroupStatus rolloutGroupStatus1, @Param("status2") RolloutGroupStatus rolloutGroupStatus2); + /** + * + * Counts all rollout-groups refering to a given {@link Rollout} by its ID + * and groups which not having the given status. + * + * @param rolloutId + * the ID of the rollout refering the groups + * @param status1 + * the status which the groups should not have + * @param status2 + * the status which the groups should not have + * @param status2 + * the status which the groups should not have + * @return count of rollout-groups referning a rollout and not in the given + * states + */ + long countByRolloutIdAndStatusNotAndStatusNotAndStatusNot(@Param("rolloutId") long rolloutId, + @Param("status1") JpaRolloutGroup.RolloutGroupStatus status1, + @Param("status2") JpaRolloutGroup.RolloutGroupStatus status2, + @Param("status3") JpaRolloutGroup.RolloutGroupStatus status3); + /** * Retrieves all {@link RolloutGroup} for a specific parent in a specific * status. Retrieves the child rolloutgroup for a specific status. * - * @param rolloutGroup - * the parent rolloutgroup + * @param rolloutGroupId + * the rolloutgroupId to find the parents * @param status * the status of the rolloutgroups * @return The child {@link RolloutGroup}s in a specific status */ - List findByParentAndStatus(JpaRolloutGroup rolloutGroup, RolloutGroupStatus status); + @Query("SELECT g FROM JpaRolloutGroup g WHERE g.parent.id=:rolloutGroupId and g.status=:status") + List findByParentIdAndStatus(@Param("rolloutGroupId") long rolloutGroupId, + @Param("status") RolloutGroupStatus status); /** * Updates all {@link RolloutGroup#getStatus()} of children for given @@ -124,4 +147,8 @@ public interface RolloutGroupRepository */ Page findByRolloutId(final Long rolloutId, Pageable page); + @Modifying + @Query("DELETE FROM JpaRolloutGroup g where g.id in :rolloutGroupIds") + void deleteByIds(@Param("rolloutGroupIds") List rolloutGroups); + } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RolloutRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RolloutRepository.java index 753745b4f..46b7be916 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RolloutRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RolloutRepository.java @@ -8,6 +8,7 @@ */ package org.eclipse.hawkbit.repository.jpa; +import java.util.Collection; import java.util.List; import java.util.Optional; @@ -15,11 +16,8 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaRollout; import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus; 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.Isolation; -import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; /** @@ -30,36 +28,15 @@ 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. + * Retrieves all {@link Rollout} for given status. * - * @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 + * the status of the rollouts to find + * @return the list of {@link Rollout} for specific status */ - @Modifying - @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED) - @Query("UPDATE JpaRollout 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); + // Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477 + @Query("SELECT sm.id FROM JpaRollout sm WHERE sm.status IN ?1") + List findByStatusIn(Collection status); /** * Retrieves all {@link Rollout} for a specific {@code name} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetRepository.java index 15bef6fe4..d97b1e720 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetRepository.java @@ -47,6 +47,15 @@ public interface TargetRepository extends BaseEntityRepository, @EntityGraph(value = "Target.detail", type = EntityGraphType.LOAD) Optional findByControllerId(String controllerID); + /** + * Checks if target with given id exists. + * + * @param controllerId to check + * @return true if target with given id exists + */ + @Query("SELECT CASE WHEN COUNT(t)>0 THEN 'true' ELSE 'false' END FROM JpaTarget t WHERE t.controllerId=:controllerId") + boolean existsByControllerId(@Param("controllerId") String controllerId); + /** * Deletes the {@link Target}s with the given target IDs. * diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignChecker.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignChecker.java index e3e19d1f4..ae616e893 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignChecker.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignChecker.java @@ -97,6 +97,7 @@ public class AutoAssignChecker { */ @Transactional(propagation = Propagation.REQUIRES_NEW) public void check() { + LOGGER.debug("Auto assigned check call"); final PageRequest pageRequest = new PageRequest(0, PAGE_SIZE); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignScheduler.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignScheduler.java index 127adae7c..174a47359 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignScheduler.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignScheduler.java @@ -9,27 +9,24 @@ package org.eclipse.hawkbit.repository.jpa.autoassign; import java.util.List; +import java.util.concurrent.locks.Lock; -import org.eclipse.hawkbit.repository.AutoAssignProperties; import org.eclipse.hawkbit.repository.SystemManagement; import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.tenancy.TenantAware; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Profile; +import org.springframework.integration.support.locks.LockRegistry; import org.springframework.scheduling.annotation.Scheduled; /** * Scheduler to check target filters for auto assignment of distribution sets */ -// don't active the auto assign scheduler in test, otherwise it is hard to test -@Profile("!test") -@EnableConfigurationProperties(AutoAssignProperties.class) public class AutoAssignScheduler { - private static final Logger LOGGER = LoggerFactory.getLogger(AutoAssignScheduler.class); + private static final String PROP_SCHEDULER_DELAY_PLACEHOLDER = "${hawkbit.autoassign.scheduler.fixedDelay:2000}"; + private final TenantAware tenantAware; private final SystemManagement systemManagement; @@ -38,6 +35,8 @@ public class AutoAssignScheduler { private final AutoAssignChecker autoAssignChecker; + private final LockRegistry lockRegistry; + /** * Instantiates a new AutoAssignScheduler * @@ -49,13 +48,17 @@ public class AutoAssignScheduler { * to run as system * @param autoAssignChecker * to run a check as tenant + * @param lockRegistry + * to acquire a lock per tenant */ public AutoAssignScheduler(final TenantAware tenantAware, final SystemManagement systemManagement, - final SystemSecurityContext systemSecurityContext, final AutoAssignChecker autoAssignChecker) { + final SystemSecurityContext systemSecurityContext, final AutoAssignChecker autoAssignChecker, + final LockRegistry lockRegistry) { this.tenantAware = tenantAware; this.systemManagement = systemManagement; this.systemSecurityContext = systemSecurityContext; this.autoAssignChecker = autoAssignChecker; + this.lockRegistry = lockRegistry; } /** @@ -64,29 +67,38 @@ public class AutoAssignScheduler { * tenant the auto assignments defined in the target filter queries * {@link SystemSecurityContext}. */ - @Scheduled(initialDelayString = AutoAssignProperties.Scheduler.PROP_SCHEDULER_DELAY_PLACEHOLDER, fixedDelayString = AutoAssignProperties.Scheduler.PROP_SCHEDULER_DELAY_PLACEHOLDER) + @Scheduled(initialDelayString = PROP_SCHEDULER_DELAY_PLACEHOLDER, fixedDelayString = PROP_SCHEDULER_DELAY_PLACEHOLDER) public void autoAssignScheduler() { LOGGER.debug("auto assign 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 separately. - final List tenants = systemManagement.findTenants(); - LOGGER.info("Checking target filter queries for tenants: {}", tenants.size()); - for (final String tenant : tenants) { + systemSecurityContext.runAsSystem(() -> executeAutoAssign()); + } + + private Object executeAutoAssign() { + // 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 separately. + final List tenants = systemManagement.findTenants(); + LOGGER.info("Checking target filter queries for tenants: {}", tenants.size()); + for (final String tenant : tenants) { + + final Lock lock = lockRegistry.obtain(tenant + "-autoassign"); + if (!lock.tryLock()) { + return null; + } + try { tenantAware.runAsTenant(tenant, () -> { - autoAssignChecker.check(); - return null; }); + } finally { + lock.unlock(); } - return null; - }); + } + return null; } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/DistributionSetTypeElement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/DistributionSetTypeElement.java index c65de6e9b..8c080554c 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/DistributionSetTypeElement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/DistributionSetTypeElement.java @@ -43,12 +43,12 @@ public class DistributionSetTypeElement implements Serializable { @MapsId("dsType") @ManyToOne(optional = false, fetch = FetchType.LAZY) - @JoinColumn(name = "distribution_set_type", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_type_element_dstype")) + @JoinColumn(name = "distribution_set_type", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_type_element_element")) private JpaDistributionSetType dsType; @MapsId("smType") @ManyToOne(optional = false, fetch = FetchType.LAZY) - @JoinColumn(name = "software_module_type", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_type_element_smtype")) + @JoinColumn(name = "software_module_type", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_type_element_smtype")) private JpaSoftwareModuleType smType; public DistributionSetTypeElement() { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/DistributionSetTypeElementCompositeKey.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/DistributionSetTypeElementCompositeKey.java index 3915fc609..eb3dbca00 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/DistributionSetTypeElementCompositeKey.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/DistributionSetTypeElementCompositeKey.java @@ -20,10 +20,10 @@ import javax.persistence.Embeddable; public class DistributionSetTypeElementCompositeKey implements Serializable { private static final long serialVersionUID = 1L; - @Column(name = "distribution_set_type", nullable = false) + @Column(name = "distribution_set_type", nullable = false, updatable = false) private Long dsType; - @Column(name = "software_module_type", nullable = false) + @Column(name = "software_module_type", nullable = false, updatable = false) private Long smType; /** diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaAction.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaAction.java index 3d63ae719..56b35cac5 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaAction.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaAction.java @@ -11,7 +11,6 @@ package org.eclipse.hawkbit.repository.jpa.model; import java.util.Collections; import java.util.List; -import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.ConstraintMode; import javax.persistence.Entity; @@ -34,6 +33,7 @@ import org.eclipse.hawkbit.repository.event.remote.entity.ActionCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.ActionUpdatedEvent; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.ActionStatus; +import org.eclipse.hawkbit.repository.model.BaseEntity; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.RolloutGroup; @@ -58,13 +58,13 @@ import org.eclipse.persistence.descriptors.DescriptorEvent; public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Action, EventAwareEntity { private static final long serialVersionUID = 1L; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "distribution_set", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_ds")) + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "distribution_set", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_ds")) @NotNull private JpaDistributionSet distributionSet; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "target", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_target")) + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "target", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_target")) @NotNull private JpaTarget target; @@ -79,20 +79,20 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio @Column(name = "forced_time") private long forcedTime; - @Column(name = "status") + @Column(name = "status", nullable = false) + @NotNull private Status status; @CascadeOnDelete - @OneToMany(mappedBy = "action", targetEntity = JpaActionStatus.class, fetch = FetchType.LAZY, cascade = { - CascadeType.REMOVE }) - private List actionStatus; + @OneToMany(mappedBy = "action", targetEntity = JpaActionStatus.class, fetch = FetchType.LAZY) + private List actionStatus; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "rolloutgroup", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_rolloutgroup")) + @JoinColumn(name = "rolloutgroup", updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_rolloutgroup")) private JpaRolloutGroup rolloutGroup; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "rollout", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_rollout")) + @JoinColumn(name = "rollout", updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_rollout")) private JpaRollout rollout; @Override @@ -185,13 +185,15 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio @Override public void fireCreateEvent(final DescriptorEvent descriptorEvent) { EventPublisherHolder.getInstance().getEventPublisher() - .publishEvent(new ActionCreatedEvent(this, EventPublisherHolder.getInstance().getApplicationId())); + .publishEvent(new ActionCreatedEvent(this, BaseEntity.getIdOrNull(rollout), + BaseEntity.getIdOrNull(rolloutGroup), EventPublisherHolder.getInstance().getApplicationId())); } @Override public void fireUpdateEvent(final DescriptorEvent descriptorEvent) { EventPublisherHolder.getInstance().getEventPublisher() - .publishEvent(new ActionUpdatedEvent(this, EventPublisherHolder.getInstance().getApplicationId())); + .publishEvent(new ActionUpdatedEvent(this, BaseEntity.getIdOrNull(rollout), + BaseEntity.getIdOrNull(rolloutGroup), EventPublisherHolder.getInstance().getApplicationId())); } @Override diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaActionStatus.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaActionStatus.java index abae5398a..d6fd6862a 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaActionStatus.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaActionStatus.java @@ -54,11 +54,11 @@ public class JpaActionStatus extends AbstractJpaTenantAwareBaseEntity implements private Long occurredAt; @ManyToOne(fetch = FetchType.LAZY, optional = false) - @JoinColumn(name = "action", nullable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_act_stat_action")) + @JoinColumn(name = "action", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_act_stat_action")) @NotNull private JpaAction action; - @Column(name = "status") + @Column(name = "status", nullable = false, updatable = false) @NotNull private Status status; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaArtifact.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaArtifact.java index afb8395e6..47e663c83 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaArtifact.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaArtifact.java @@ -12,6 +12,7 @@ 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; @@ -49,7 +50,7 @@ public class JpaArtifact extends AbstractJpaTenantAwareBaseEntity implements Art @NotEmpty private String filename; - @ManyToOne(optional = false, cascade = { CascadeType.PERSIST }) + @ManyToOne(optional = false, cascade = { CascadeType.PERSIST }, fetch = FetchType.LAZY) @JoinColumn(name = "software_module", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_assigned_sm")) private JpaSoftwareModule softwareModule; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSet.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSet.java index ab161a9b2..4c11e4db6 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSet.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSet.java @@ -16,7 +16,6 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; -import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.ConstraintMode; import javax.persistence.Entity; @@ -45,9 +44,7 @@ import org.eclipse.hawkbit.repository.model.DistributionSetMetadata; import org.eclipse.hawkbit.repository.model.DistributionSetTag; import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.SoftwareModule; -import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetFilterQuery; -import org.eclipse.hawkbit.repository.model.TargetInfo; import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder; import org.eclipse.persistence.annotations.CascadeOnDelete; import org.eclipse.persistence.descriptors.DescriptorEvent; @@ -82,40 +79,38 @@ public class JpaDistributionSet extends AbstractJpaNamedVersionedEntity implemen @CascadeOnDelete @ManyToMany(targetEntity = JpaSoftwareModule.class, fetch = FetchType.LAZY) @JoinTable(name = "sp_ds_module", joinColumns = { - @JoinColumn(name = "ds_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_module_ds")) }, inverseJoinColumns = { - @JoinColumn(name = "module_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_module_module")) }) + @JoinColumn(name = "ds_id", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_module_ds")) }, inverseJoinColumns = { + @JoinColumn(name = "module_id", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_module_module")) }) private Set modules; @CascadeOnDelete @ManyToMany(targetEntity = JpaDistributionSetTag.class) @JoinTable(name = "sp_ds_dstag", joinColumns = { - @JoinColumn(name = "ds", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_dstag_ds")) }, inverseJoinColumns = { - @JoinColumn(name = "TAG", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_dstag_tag")) }) + @JoinColumn(name = "ds", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_dstag_ds")) }, inverseJoinColumns = { + @JoinColumn(name = "TAG", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_dstag_tag")) }) private Set tags; @Column(name = "deleted") private boolean deleted; @OneToMany(mappedBy = "assignedDistributionSet", targetEntity = JpaTarget.class, fetch = FetchType.LAZY) - private List assignedToTargets; + private List assignedToTargets; @OneToMany(mappedBy = "autoAssignDistributionSet", targetEntity = JpaTargetFilterQuery.class, fetch = FetchType.LAZY) private List autoAssignFilters; @OneToMany(mappedBy = "installedDistributionSet", targetEntity = JpaTargetInfo.class, fetch = FetchType.LAZY) - private List installedAtTargets; + private List installedAtTargets; @OneToMany(mappedBy = "distributionSet", targetEntity = JpaAction.class, fetch = FetchType.LAZY) - private List actions; + private List actions; @CascadeOnDelete - @OneToMany(fetch = FetchType.LAZY, targetEntity = JpaDistributionSetMetadata.class, cascade = { - CascadeType.REMOVE }) - @JoinColumn(name = "ds_id", insertable = false, updatable = false) + @OneToMany(mappedBy = "distributionSet", fetch = FetchType.LAZY, targetEntity = JpaDistributionSetMetadata.class) private List metadata; - @ManyToOne(fetch = FetchType.LAZY, targetEntity = JpaDistributionSetType.class) - @JoinColumn(name = "ds_id", nullable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_dstype_ds")) + @ManyToOne(fetch = FetchType.LAZY, optional = false, targetEntity = JpaDistributionSetType.class) + @JoinColumn(name = "ds_id", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_dstype_ds")) @NotNull private DistributionSetType type; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSetMetadata.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSetMetadata.java index db8dd7354..366236fcf 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSetMetadata.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSetMetadata.java @@ -32,8 +32,8 @@ public class JpaDistributionSetMetadata extends JpaMetaData implements Distribut private static final long serialVersionUID = 1L; @Id - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "ds_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_metadata_ds")) + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "ds_id", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_metadata_ds")) private JpaDistributionSet distributionSet; public JpaDistributionSetMetadata() { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSetType.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSetType.java index 05d39b056..b2acc5bcf 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSetType.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSetType.java @@ -19,7 +19,6 @@ import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.Index; -import javax.persistence.JoinColumn; import javax.persistence.OneToMany; import javax.persistence.Table; import javax.persistence.UniqueConstraint; @@ -29,6 +28,7 @@ import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModuleType; +import org.eclipse.persistence.annotations.CascadeOnDelete; import org.hibernate.validator.constraints.NotEmpty; import org.springframework.util.CollectionUtils; @@ -49,12 +49,12 @@ import org.springframework.util.CollectionUtils; public class JpaDistributionSetType extends AbstractJpaNamedEntity implements DistributionSetType { private static final long serialVersionUID = 1L; - @OneToMany(targetEntity = DistributionSetTypeElement.class, cascade = { - CascadeType.ALL }, fetch = FetchType.EAGER, orphanRemoval = true) - @JoinColumn(name = "distribution_set_type", insertable = false, updatable = false) + @CascadeOnDelete + @OneToMany(mappedBy = "dsType", targetEntity = DistributionSetTypeElement.class, cascade = { + CascadeType.PERSIST }, fetch = FetchType.EAGER, orphanRemoval = true) private Set elements; - @Column(name = "type_key", nullable = false, length = 64) + @Column(name = "type_key", nullable = false, updatable = false, length = 64) @Size(max = 64) @NotEmpty private String key; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaRollout.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaRollout.java index 3e52d1b09..576c8968e 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaRollout.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaRollout.java @@ -10,6 +10,7 @@ package org.eclipse.hawkbit.repository.jpa.model; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import javax.persistence.Column; import javax.persistence.ConstraintMode; @@ -28,6 +29,7 @@ import javax.persistence.UniqueConstraint; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; +import org.eclipse.hawkbit.repository.event.remote.RolloutDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.RolloutUpdatedEvent; import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.DistributionSet; @@ -35,7 +37,14 @@ import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus; import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder; +import org.eclipse.persistence.annotations.CascadeOnDelete; +import org.eclipse.persistence.annotations.ConversionValue; +import org.eclipse.persistence.annotations.Convert; +import org.eclipse.persistence.annotations.ObjectTypeConverter; import org.eclipse.persistence.descriptors.DescriptorEvent; +import org.eclipse.persistence.queries.UpdateObjectQuery; +import org.eclipse.persistence.sessions.changesets.DirectToFieldChangeRecord; +import org.eclipse.persistence.sessions.changesets.ObjectChangeSet; import org.hibernate.validator.constraints.NotEmpty; /** @@ -49,25 +58,41 @@ import org.hibernate.validator.constraints.NotEmpty; // exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for // sub entities @SuppressWarnings("squid:S2160") +@ObjectTypeConverter(name = "rolloutstatus", objectType = Rollout.RolloutStatus.class, dataType = Integer.class, conversionValues = { + @ConversionValue(objectValue = "CREATING", dataValue = "0"), + @ConversionValue(objectValue = "READY", dataValue = "1"), + @ConversionValue(objectValue = "PAUSED", dataValue = "2"), + @ConversionValue(objectValue = "STARTING", dataValue = "3"), + @ConversionValue(objectValue = "STOPPED", dataValue = "4"), + @ConversionValue(objectValue = "RUNNING", dataValue = "5"), + @ConversionValue(objectValue = "FINISHED", dataValue = "6"), + @ConversionValue(objectValue = "ERROR_CREATING", dataValue = "7"), + @ConversionValue(objectValue = "ERROR_STARTING", dataValue = "8"), + @ConversionValue(objectValue = "DELETING", dataValue = "9"), + @ConversionValue(objectValue = "DELETED", dataValue = "10") }) public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, EventAwareEntity { private static final long serialVersionUID = 1L; - @OneToMany(targetEntity = JpaRolloutGroup.class) - @JoinColumn(name = "rollout", insertable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rollout_rolloutgroup")) - private List rolloutGroups; + private static final String DELETED_PROPERTY = "deleted"; + + @CascadeOnDelete + @OneToMany(targetEntity = JpaRolloutGroup.class, fetch = FetchType.LAZY, mappedBy = "rollout") + private List rolloutGroups; @Column(name = "target_filter", length = 1024, nullable = false) @Size(max = 1024) @NotEmpty private String targetFilterQuery; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "distribution_set", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rolltout_ds")) + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "distribution_set", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rolltout_ds")) @NotNull private JpaDistributionSet distributionSet; - @Column(name = "status") + @Column(name = "status", nullable = false) + @Convert("rolloutstatus") + @NotNull private RolloutStatus status = RolloutStatus.CREATING; @Column(name = "last_check") @@ -87,6 +112,9 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event @Column(name = "rollout_groups_created") private int rolloutGroupsCreated; + @Column(name = "deleted") + private boolean deleted; + @Column(name = "start_at") private Long startAt; @@ -142,7 +170,7 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event return startAt; } - public void setStartAt(Long startAt) { + public void setStartAt(final Long startAt) { this.startAt = startAt; } @@ -196,9 +224,8 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event @Override public String toString() { - return "Rollout [rolloutGroups=" + rolloutGroups + ", targetFilterQuery=" + targetFilterQuery - + ", distributionSet=" + distributionSet + ", status=" + status + ", lastCheck=" + lastCheck - + ", getName()=" + getName() + ", getId()=" + getId() + "]"; + return "Rollout [ targetFilterQuery=" + targetFilterQuery + ", distributionSet=" + distributionSet + ", status=" + + status + ", lastCheck=" + lastCheck + ", getName()=" + getName() + ", getId()=" + getId() + "]"; } @Override @@ -211,11 +238,35 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event EventPublisherHolder.getInstance().getEventPublisher() .publishEvent(new RolloutUpdatedEvent(this, EventPublisherHolder.getInstance().getApplicationId())); + if (isSoftDeleted(descriptorEvent)) { + EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new RolloutDeletedEvent(getTenant(), + getId(), getClass().getName(), EventPublisherHolder.getInstance().getApplicationId())); + } + } + + private static boolean isSoftDeleted(final DescriptorEvent event) { + final ObjectChangeSet changeSet = ((UpdateObjectQuery) event.getQuery()).getObjectChangeSet(); + final List changes = changeSet.getChanges().stream() + .filter(record -> record instanceof DirectToFieldChangeRecord) + .map(record -> (DirectToFieldChangeRecord) record).collect(Collectors.toList()); + + return changes.stream().filter(record -> DELETED_PROPERTY.equals(record.getAttribute()) + && Boolean.parseBoolean(record.getNewValue().toString())).count() > 0; } @Override public void fireDeleteEvent(final DescriptorEvent descriptorEvent) { - // there is no rollout deletion event + EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new RolloutDeletedEvent(getTenant(), + getId(), getClass().getName(), EventPublisherHolder.getInstance().getApplicationId())); + } + + @Override + public boolean isDeleted() { + return deleted; + } + + public void setDeleted(final boolean deleted) { + this.deleted = deleted; } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaRolloutGroup.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaRolloutGroup.java index d15c92d50..29fdbde3c 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaRolloutGroup.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaRolloutGroup.java @@ -32,6 +32,7 @@ import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus; import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder; +import org.eclipse.persistence.annotations.CascadeOnDelete; import org.eclipse.persistence.annotations.ConversionValue; import org.eclipse.persistence.annotations.Convert; import org.eclipse.persistence.annotations.ObjectTypeConverter; @@ -60,18 +61,20 @@ public class JpaRolloutGroup extends AbstractJpaNamedEntity implements RolloutGr private static final long serialVersionUID = 1L; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "rollout", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rolloutgroup_rollout")) + @JoinColumn(name = "rollout", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rolloutgroup_rollout")) private JpaRollout rollout; - @Column(name = "status") + @Column(name = "status", nullable = false) @Convert("rolloutgroupstatus") private RolloutGroupStatus status = RolloutGroupStatus.CREATING; - @OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST }, targetEntity = RolloutTargetGroup.class) - @JoinColumn(name = "rolloutGroup_Id", insertable = false, updatable = false) + @CascadeOnDelete + @OneToMany(mappedBy = "rolloutGroup", fetch = FetchType.LAZY, cascade = { + CascadeType.PERSIST }, targetEntity = RolloutTargetGroup.class) private List rolloutTargetGroup; @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "parent_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rolloutgroup_rolloutgroup")) private JpaRolloutGroup parent; @Column(name = "success_condition", nullable = false) @@ -287,8 +290,7 @@ public class JpaRolloutGroup extends AbstractJpaNamedEntity implements RolloutGr @Override public void fireUpdateEvent(final DescriptorEvent descriptorEvent) { EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new RolloutGroupUpdatedEvent(this, - - EventPublisherHolder.getInstance().getApplicationId())); + this.getRollout().getId(), EventPublisherHolder.getInstance().getApplicationId())); } @Override diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaSoftwareModule.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaSoftwareModule.java index 1a1f4876e..858241c47 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaSoftwareModule.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaSoftwareModule.java @@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.jpa.model; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import javax.persistence.CascadeType; import javax.persistence.Column; @@ -36,11 +37,13 @@ import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleUpdatedE import org.eclipse.hawkbit.repository.model.Artifact; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.SoftwareModule; -import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata; import org.eclipse.hawkbit.repository.model.SoftwareModuleType; import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder; import org.eclipse.persistence.annotations.CascadeOnDelete; import org.eclipse.persistence.descriptors.DescriptorEvent; +import org.eclipse.persistence.queries.UpdateObjectQuery; +import org.eclipse.persistence.sessions.changesets.DirectToFieldChangeRecord; +import org.eclipse.persistence.sessions.changesets.ObjectChangeSet; /** * Base Software Module that is supported by OS level provisioning mechanism on @@ -60,8 +63,10 @@ import org.eclipse.persistence.descriptors.DescriptorEvent; public class JpaSoftwareModule extends AbstractJpaNamedVersionedEntity implements SoftwareModule, EventAwareEntity { private static final long serialVersionUID = 1L; + private static final String DELETED_PROPERTY = "deleted"; + @ManyToOne - @JoinColumn(name = "module_type", nullable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_module_type")) + @JoinColumn(name = "module_type", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_module_type")) @NotNull private JpaSoftwareModuleType type; @@ -77,13 +82,12 @@ public class JpaSoftwareModule extends AbstractJpaNamedVersionedEntity implement @CascadeOnDelete @OneToMany(fetch = FetchType.LAZY, mappedBy = "softwareModule", cascade = { - CascadeType.ALL }, targetEntity = JpaArtifact.class) - private List artifacts; + CascadeType.PERSIST }, targetEntity = JpaArtifact.class, orphanRemoval = true) + private List artifacts; @CascadeOnDelete - @OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.REMOVE }, targetEntity = JpaSoftwareModuleMetadata.class) - @JoinColumn(name = "sw_id", insertable = false, updatable = false) - private List metadata; + @OneToMany(mappedBy = "softwareModule", fetch = FetchType.LAZY, targetEntity = JpaSoftwareModuleMetadata.class) + private List metadata; /** * Default constructor. @@ -116,12 +120,12 @@ public class JpaSoftwareModule extends AbstractJpaNamedVersionedEntity implement public void addArtifact(final Artifact artifact) { if (null == artifacts) { artifacts = new ArrayList<>(4); - artifacts.add(artifact); + artifacts.add((JpaArtifact) artifact); return; } if (!artifacts.contains(artifact)) { - artifacts.add(artifact); + artifacts.add((JpaArtifact) artifact); } } @@ -206,6 +210,21 @@ public class JpaSoftwareModule extends AbstractJpaNamedVersionedEntity implement public void fireUpdateEvent(final DescriptorEvent descriptorEvent) { EventPublisherHolder.getInstance().getEventPublisher().publishEvent( new SoftwareModuleUpdatedEvent(this, EventPublisherHolder.getInstance().getApplicationId())); + + if (isSoftDeleted(descriptorEvent)) { + EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new SoftwareModuleDeletedEvent( + getTenant(), getId(), getClass().getName(), EventPublisherHolder.getInstance().getApplicationId())); + } + } + + private static boolean isSoftDeleted(final DescriptorEvent event) { + final ObjectChangeSet changeSet = ((UpdateObjectQuery) event.getQuery()).getObjectChangeSet(); + final List changes = changeSet.getChanges().stream() + .filter(record -> record instanceof DirectToFieldChangeRecord) + .map(record -> (DirectToFieldChangeRecord) record).collect(Collectors.toList()); + + return changes.stream().filter(record -> DELETED_PROPERTY.equals(record.getAttribute()) + && Boolean.parseBoolean(record.getNewValue().toString())).count() > 0; } @Override diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaSoftwareModuleMetadata.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaSoftwareModuleMetadata.java index 7038ee339..02b77457f 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaSoftwareModuleMetadata.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaSoftwareModuleMetadata.java @@ -32,8 +32,8 @@ public class JpaSoftwareModuleMetadata extends JpaMetaData implements SoftwareMo private static final long serialVersionUID = 1L; @Id - @ManyToOne(targetEntity = JpaSoftwareModule.class, fetch = FetchType.LAZY) - @JoinColumn(name = "sw_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_metadata_sw")) + @ManyToOne(optional = false, targetEntity = JpaSoftwareModule.class, fetch = FetchType.LAZY) + @JoinColumn(name = "sw_id", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_metadata_sw")) private SoftwareModule softwareModule; public JpaSoftwareModuleMetadata() { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTarget.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTarget.java index 8e7139558..121e73dda 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTarget.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTarget.java @@ -87,22 +87,21 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Persistable tags; @CascadeOnDelete - @OneToMany(fetch = FetchType.LAZY, orphanRemoval = true, cascade = { - CascadeType.REMOVE }, targetEntity = JpaAction.class) - @JoinColumn(name = "target", insertable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_act_hist_targ")) - private List actions; + @OneToMany(mappedBy = "target", fetch = FetchType.LAZY, targetEntity = JpaAction.class) + private List actions; @ManyToOne(optional = true, fetch = FetchType.LAZY, targetEntity = JpaDistributionSet.class) @JoinColumn(name = "assigned_distribution_set", nullable = true, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_target_assign_ds")) private JpaDistributionSet assignedDistributionSet; @CascadeOnDelete - @OneToOne(cascade = { CascadeType.ALL }, fetch = FetchType.LAZY, targetEntity = JpaTargetInfo.class) + @OneToOne(cascade = { CascadeType.PERSIST, + CascadeType.MERGE }, fetch = FetchType.LAZY, targetEntity = JpaTargetInfo.class) @PrimaryKeyJoinColumn private JpaTargetInfo targetInfo; @@ -110,14 +109,13 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Persistable rolloutTargetGroup; /** @@ -214,7 +212,7 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Persistable(); } - return actions.add(action); + return actions.add((JpaAction) action); } @Override diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTargetInfo.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTargetInfo.java index e4a07b78d..f34f7a0df 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTargetInfo.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTargetInfo.java @@ -79,8 +79,7 @@ public class JpaTargetInfo implements Persistable, TargetInfo { private boolean entityNew; @CascadeOnDelete - @OneToOne(cascade = { CascadeType.MERGE, - CascadeType.REMOVE }, fetch = FetchType.LAZY, targetEntity = JpaTarget.class) + @OneToOne(cascade = { CascadeType.MERGE }, fetch = FetchType.LAZY, targetEntity = JpaTarget.class) @JoinColumn(name = "target_id", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_stat_targ")) @MapsId private JpaTarget target; @@ -112,7 +111,7 @@ public class JpaTargetInfo implements Persistable, TargetInfo { @Column(name = "attribute_value", length = 128) @MapKeyColumn(name = "attribute_key", nullable = false, length = 32) @CollectionTable(name = "sp_target_attributes", joinColumns = { - @JoinColumn(name = "target_id") }, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_attrib_target")) + @JoinColumn(name = "target_id", nullable = false, updatable = false) }, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_attrib_target")) private final Map controllerAttributes = Collections.synchronizedMap(new HashMap()); // set default request controller attributes to true, because we want to diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTenantConfiguration.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTenantConfiguration.java index 836052c9e..3446610fc 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTenantConfiguration.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTenantConfiguration.java @@ -32,7 +32,7 @@ import org.hibernate.validator.constraints.NotEmpty; public class JpaTenantConfiguration extends AbstractJpaTenantAwareBaseEntity implements TenantConfiguration { private static final long serialVersionUID = 1L; - @Column(name = "conf_key", length = 128, nullable = false) + @Column(name = "conf_key", length = 128, nullable = false, updatable = false) @Size(max = 128) @NotEmpty private String key; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTenantMetaData.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTenantMetaData.java index 8bdd0632d..75223c06f 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTenantMetaData.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTenantMetaData.java @@ -43,7 +43,7 @@ import org.eclipse.hawkbit.repository.model.TenantMetaData; public class JpaTenantMetaData extends AbstractJpaBaseEntity implements TenantMetaData { private static final long serialVersionUID = 1L; - @Column(name = "tenant", nullable = false, length = 40) + @Column(name = "tenant", nullable = false, updatable = false, length = 40) @Size(max = 40) private String tenant; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/RolloutTargetGroup.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/RolloutTargetGroup.java index 0bb969c7f..86992d448 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/RolloutTargetGroup.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/RolloutTargetGroup.java @@ -24,7 +24,6 @@ import javax.persistence.ManyToOne; import javax.persistence.OneToMany; import javax.persistence.Table; -import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.persistence.annotations.ExistenceChecking; @@ -44,19 +43,22 @@ public class RolloutTargetGroup implements Serializable { private static final long serialVersionUID = 1L; @Id - @ManyToOne(targetEntity = JpaRolloutGroup.class, fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST }) - @JoinColumn(name = "rolloutGroup_Id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rollouttargetgroup_group")) - private RolloutGroup rolloutGroup; + @ManyToOne(optional = false, targetEntity = JpaRolloutGroup.class, fetch = FetchType.LAZY, cascade = { + CascadeType.PERSIST }) + @JoinColumn(name = "rolloutGroup_Id", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rollouttargetgroup_group")) + private JpaRolloutGroup rolloutGroup; @Id - @ManyToOne(targetEntity = JpaTarget.class, fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST }) - @JoinColumn(name = "target_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rollouttargetgroup_target")) + @ManyToOne(optional = false, targetEntity = JpaTarget.class, fetch = FetchType.LAZY, cascade = { + CascadeType.PERSIST }) + @JoinColumn(name = "target_id", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rollouttargetgroup_target")) private JpaTarget target; @OneToMany(targetEntity = JpaAction.class, fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST }) - @JoinColumns(value = { @JoinColumn(name = "rolloutgroup", referencedColumnName = "rolloutGroup_Id"), - @JoinColumn(name = "target", referencedColumnName = "target_id") }) - private List actions; + @JoinColumns(value = { + @JoinColumn(name = "rolloutgroup", nullable = false, updatable = false, referencedColumnName = "rolloutGroup_Id"), + @JoinColumn(name = "target", nullable = false, updatable = false, referencedColumnName = "target_id") }) + private List actions; /** * default constructor for JPA. @@ -66,7 +68,7 @@ public class RolloutTargetGroup implements Serializable { } public RolloutTargetGroup(final RolloutGroup rolloutGroup, final Target target) { - this.rolloutGroup = rolloutGroup; + this.rolloutGroup = (JpaRolloutGroup) rolloutGroup; this.target = (JpaTarget) target; } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/RolloutScheduler.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/RolloutScheduler.java index a4020cc30..37c9f86b1 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/RolloutScheduler.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/RolloutScheduler.java @@ -9,63 +9,78 @@ package org.eclipse.hawkbit.repository.jpa.rollout; import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorCompletionService; import org.eclipse.hawkbit.repository.RolloutManagement; -import org.eclipse.hawkbit.repository.RolloutProperties; import org.eclipse.hawkbit.repository.SystemManagement; 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.annotation.Profile; import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; + +import com.google.common.base.Throwables; /** - * Scheduler to schedule the - * {@link RolloutManagement#checkRunningRollouts(long)}. The delay between the - * checks be be configured using the properties from {@link RolloutProperties}. + * Scheduler to schedule the {@link RolloutManagement#handleRollouts()}. The + * delay between the checks be be configured using the property from + * {#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 { private static final Logger LOGGER = LoggerFactory.getLogger(RolloutScheduler.class); - @Autowired - private TenantAware tenantAware; + private static final String PROP_SCHEDULER_DELAY_PLACEHOLDER = "${hawkbit.rollout.scheduler.fixedDelay:2000}"; - @Autowired - private SystemManagement systemManagement; + private final TenantAware tenantAware; - @Autowired - private RolloutManagement rolloutManagement; + private final SystemManagement systemManagement; - @Autowired - private SystemSecurityContext systemSecurityContext; + private final RolloutManagement rolloutManagement; - @Autowired - private RolloutProperties rolloutProperties; + private final SystemSecurityContext systemSecurityContext; + + private final ExecutorCompletionService completionService; + + /** + * Constructor. + * + * @param tenantAware + * to run as specific tenant + * @param systemManagement + * to find all tenants + * @param rolloutManagement + * to run the rollout handler + * @param systemSecurityContext + * to run as system + * @param threadPoolExecutor + * to execute the handlers in parallel + */ + public RolloutScheduler(final TenantAware tenantAware, final SystemManagement systemManagement, + final RolloutManagement rolloutManagement, final SystemSecurityContext systemSecurityContext, + final Executor threadPoolExecutor) { + this.tenantAware = tenantAware; + this.systemManagement = systemManagement; + this.rolloutManagement = rolloutManagement; + this.systemSecurityContext = systemSecurityContext; + completionService = new ExecutorCompletionService<>(threadPoolExecutor); + } /** * 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 + * tenant the {@link RolloutManagement#handleRollouts()} in the * {@link SystemSecurityContext}. */ - @Scheduled(initialDelayString = RolloutProperties.PROP_SCHEDULER_DELAY_PLACEHOLDER, fixedDelayString = RolloutProperties.PROP_SCHEDULER_DELAY_PLACEHOLDER) + @Scheduled(initialDelayString = PROP_SCHEDULER_DELAY_PLACEHOLDER, fixedDelayString = PROP_SCHEDULER_DELAY_PLACEHOLDER) public void runningRolloutScheduler() { - if (!rolloutProperties.getScheduler().isEnabled()) { - return; - } - 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(() -> { + final int tasks = systemSecurityContext.runAsSystem(() -> { // workaround eclipselink that is currently not possible to // execute a query without multitenancy if MultiTenant // annotation is used. @@ -75,119 +90,25 @@ public class RolloutScheduler { final List tenants = systemManagement.findTenants(); LOGGER.info("Checking rollouts for {} tenants", tenants.size()); for (final String tenant : tenants) { - tenantAware.runAsTenant(tenant, () -> { - final long fixedDelay = rolloutProperties.getScheduler().getFixedDelay(); - rolloutManagement.checkRunningRollouts(fixedDelay); + completionService.submit(() -> tenantAware.runAsTenant(tenant, () -> { + rolloutManagement.handleRollouts(); return null; - }); + })); } - return null; + return tenants.size(); }); + + waitUntilHandlersAreComplete(tasks); } - /** - * Scheduler method called by the spring-async mechanism. Retrieves all - * tenants from the {@link SystemManagement#findTenants()} and runs for each - * tenant the {@link RolloutManagement#checkStartingRollouts(long)} in the - * {@link SystemSecurityContext}. - */ - @Scheduled(initialDelayString = RolloutProperties.PROP_STARTING_SCHEDULER_DELAY_PLACEHOLDER, fixedDelayString = RolloutProperties.PROP_STARTING_SCHEDULER_DELAY_PLACEHOLDER) - public void startingRolloutScheduler() { - if (!rolloutProperties.getStartingScheduler().isEnabled()) { - return; - } - - LOGGER.debug("rollout starting 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 starting rollouts for {} tenants", tenants.size()); - for (final String tenant : tenants) { - tenantAware.runAsTenant(tenant, () -> { - final long fixedDelay = rolloutProperties.getStartingScheduler().getFixedDelay(); - rolloutManagement.checkStartingRollouts(fixedDelay); - return null; - }); + private void waitUntilHandlersAreComplete(final int tasks) { + try { + for (int i = 0; i < tasks; i++) { + completionService.take().get(); } - return null; - }); - } - - /** - * Scheduler method called by the spring-async mechanism. Retrieves all - * tenants from the {@link SystemManagement#findTenants()} and runs for each - * tenant the {@link RolloutManagement#checkCreatingRollouts(long)} in the - * {@link SystemSecurityContext}. - */ - @Scheduled(initialDelayString = RolloutProperties.PROP_CREATING_SCHEDULER_DELAY_PLACEHOLDER, fixedDelayString = RolloutProperties.PROP_CREATING_SCHEDULER_DELAY_PLACEHOLDER) - public void creatingRolloutScheduler() { - if (!rolloutProperties.getCreatingScheduler().isEnabled()) { - return; + } catch (InterruptedException | ExecutionException e) { + Thread.currentThread().interrupt(); + throw Throwables.propagate(e); } - - LOGGER.debug("rollout creating 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 creating rollouts for {} tenants", tenants.size()); - for (final String tenant : tenants) { - tenantAware.runAsTenant(tenant, () -> { - final long fixedDelay = rolloutProperties.getCreatingScheduler().getFixedDelay(); - rolloutManagement.checkCreatingRollouts(fixedDelay); - return null; - }); - } - return null; - }); - } - - /** - * Scheduler method called by the spring-async mechanism. Retrieves all - * tenants from the {@link SystemManagement#findTenants()} and runs for each - * tenant the {@link RolloutManagement#checkReadyRollouts(long)} in the - * {@link SystemSecurityContext}. Used to auto start Rollouts as soon as - * their startAt time is reached. - */ - @Scheduled(initialDelayString = RolloutProperties.PROP_READY_SCHEDULER_DELAY_PLACEHOLDER, fixedDelayString = RolloutProperties.PROP_READY_SCHEDULER_DELAY_PLACEHOLDER) - public void readyRolloutScheduler() { - if (!rolloutProperties.getReadyScheduler().isEnabled()) { - return; - } - - LOGGER.debug("rollout ready 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 ready rollouts for {} tenants", tenants.size()); - for (final String tenant : tenants) { - tenantAware.runAsTenant(tenant, () -> { - final long fixedDelay = rolloutProperties.getReadyScheduler().getFixedDelay(); - rolloutManagement.checkReadyRollouts(fixedDelay); - return null; - }); - } - return null; - }); } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/condition/StartNextGroupRolloutGroupSuccessAction.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/condition/StartNextGroupRolloutGroupSuccessAction.java index e9da7af3c..dd02e12b4 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/condition/StartNextGroupRolloutGroupSuccessAction.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/condition/StartNextGroupRolloutGroupSuccessAction.java @@ -71,7 +71,7 @@ public class StartNextGroupRolloutGroupSuccessAction implements RolloutGroupActi // 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((JpaRolloutGroup) rolloutGroup, RolloutGroupStatus.SCHEDULED); + .findByParentIdAndStatus(rolloutGroup.getId(), RolloutGroupStatus.SCHEDULED); findByRolloutGroupParent.forEach(nextGroup -> { logger.debug("Rolloutgroup {} is finished, starting next group", nextGroup); nextGroup.setStatus(RolloutGroupStatus.FINISHED); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/ActionSpecifications.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/ActionSpecifications.java index 47a40b0f5..fc48eae39 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/ActionSpecifications.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/ActionSpecifications.java @@ -20,8 +20,8 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_; import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule; import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule_; +import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_; import org.eclipse.hawkbit.repository.model.Action; -import org.eclipse.hawkbit.repository.model.Target; import org.springframework.data.jpa.domain.Specification; /** @@ -37,25 +37,50 @@ public final class ActionSpecifications { /** * Specification which joins all necessary tables to retrieve the dependency - * between a target and a local file assignment through the assigen action + * between a target and a local file assignment through the assigned action * of the target. All actions are included, not only active actions. * - * @param target - * the target to verfiy if the given artifact is currently + * @param controllerId + * the target to verify if the given artifact is currently * assigned or had been assigned * @param sha1Hash * of the local artifact to check wherever the target had ever * been assigned * @return a specification to use with spring JPA */ - public static Specification hasTargetAssignedArtifact(final Target target, final String sha1Hash) { + public static Specification hasTargetAssignedArtifact(final String controllerId, final String sha1Hash) { return (actionRoot, query, criteriaBuilder) -> { final Join dsJoin = actionRoot.join(JpaAction_.distributionSet); final SetJoin modulesJoin = dsJoin.join(JpaDistributionSet_.modules); final ListJoin artifactsJoin = modulesJoin .join(JpaSoftwareModule_.artifacts); return criteriaBuilder.and(criteriaBuilder.equal(artifactsJoin.get(JpaArtifact_.sha1Hash), sha1Hash), - criteriaBuilder.equal(actionRoot.get(JpaAction_.target), target)); + criteriaBuilder.equal(actionRoot.get(JpaAction_.target).get(JpaTarget_.controllerId), + controllerId)); + }; + } + + /** + * Specification which joins all necessary tables to retrieve the dependency + * between a target and a local file assignment through the assigned action + * of the target. All actions are included, not only active actions. + * + * @param targetId + * the target to verify if the given artifact is currently + * assigned or had been assigned + * @param sha1Hash + * of the local artifact to check wherever the target had ever + * been assigned + * @return a specification to use with spring JPA + */ + public static Specification hasTargetAssignedArtifact(final Long targetId, final String sha1Hash) { + return (actionRoot, query, criteriaBuilder) -> { + final Join dsJoin = actionRoot.join(JpaAction_.distributionSet); + final SetJoin modulesJoin = dsJoin.join(JpaDistributionSet_.modules); + final ListJoin artifactsJoin = modulesJoin + .join(JpaSoftwareModule_.artifacts); + return criteriaBuilder.and(criteriaBuilder.equal(artifactsJoin.get(JpaArtifact_.sha1Hash), sha1Hash), + criteriaBuilder.equal(actionRoot.get(JpaAction_.target).get(JpaTarget_.id), targetId)); }; } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/RolloutSpecification.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/RolloutSpecification.java new file mode 100644 index 000000000..5a3eb30b1 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/RolloutSpecification.java @@ -0,0 +1,40 @@ +/** + * 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.jpa.specifications; + +import org.eclipse.hawkbit.repository.jpa.model.JpaRollout; +import org.eclipse.hawkbit.repository.jpa.model.JpaRollout_; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.springframework.data.jpa.domain.Specification; + +/** + * Specifications class for {@link Rollout}s. The class provides Spring Data + * JPQL Specifications. + * + */ +public final class RolloutSpecification { + private RolloutSpecification() { + // utility class + } + + /** + * {@link Specification} for retrieving {@link Rollout}s by its DELETED + * attribute. + * + * @param isDeleted + * TRUE/FALSE are compared to the attribute DELETED. If NULL the + * attribute is ignored + * @return the {@link Rollout} {@link Specification} + */ + public static Specification isDeleted(final Boolean isDeleted) { + return (root, query, cb) -> cb.equal(root. get(JpaRollout_.deleted), isDeleted); + + } + +} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/H2/V1_10_3__add_rollout_deleted_flag__H2.sql b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/H2/V1_10_3__add_rollout_deleted_flag__H2.sql new file mode 100644 index 000000000..d21679f3e --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/H2/V1_10_3__add_rollout_deleted_flag__H2.sql @@ -0,0 +1,26 @@ +ALTER TABLE sp_rollout ADD COLUMN deleted BOOLEAN; + +UPDATE sp_rollout SET deleted = 0; + +ALTER TABLE sp_action MODIFY target BIGINT NOT NULL; +ALTER TABLE sp_action MODIFY distribution_set BIGINT NOT NULL; +ALTER TABLE sp_action MODIFY status INTEGER NOT NULL; +ALTER TABLE sp_action_status MODIFY status INTEGER NOT NULL; +ALTER TABLE sp_rollout MODIFY status INTEGER NOT NULL; +ALTER TABLE sp_rollout MODIFY distribution_set BIGINT NOT NULL; +ALTER TABLE sp_rolloutgroup MODIFY rollout BIGINT NOT NULL; +ALTER TABLE sp_rolloutgroup MODIFY status INTEGER NOT NULL; + +ALTER TABLE sp_ds_type_element DROP CONSTRAINT fk_ds_type_element_element; +ALTER TABLE sp_ds_type_element + ADD CONSTRAINT fk_ds_type_element_element + FOREIGN KEY (distribution_set_type) + REFERENCES sp_distribution_set_type (id) + ON DELETE CASCADE; + +ALTER TABLE sp_ds_type_element DROP CONSTRAINT fk_ds_type_element_smtype; +ALTER TABLE sp_ds_type_element + ADD CONSTRAINT fk_ds_type_element_smtype + FOREIGN KEY (software_module_type) + REFERENCES sp_software_module_type (id) + ON DELETE CASCADE; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/MYSQL/V1_10_3__add_rollout_deleted_flag__MYSQL.sql b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/MYSQL/V1_10_3__add_rollout_deleted_flag__MYSQL.sql new file mode 100644 index 000000000..ed184162e --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/MYSQL/V1_10_3__add_rollout_deleted_flag__MYSQL.sql @@ -0,0 +1,26 @@ +ALTER TABLE sp_rollout ADD COLUMN deleted BOOLEAN; + +UPDATE sp_rollout SET deleted = 0; + +ALTER TABLE sp_action MODIFY target BIGINT NOT NULL; +ALTER TABLE sp_action MODIFY distribution_set BIGINT NOT NULL; +ALTER TABLE sp_action MODIFY status INTEGER NOT NULL; +ALTER TABLE sp_action_status MODIFY status INTEGER NOT NULL; +ALTER TABLE sp_rollout MODIFY status INTEGER NOT NULL; +ALTER TABLE sp_rollout MODIFY distribution_set BIGINT NOT NULL; +ALTER TABLE sp_rolloutgroup MODIFY rollout BIGINT NOT NULL; +ALTER TABLE sp_rolloutgroup MODIFY status INTEGER NOT NULL; + +ALTER TABLE sp_ds_type_element DROP FOREIGN KEY fk_ds_type_element_element; +ALTER TABLE sp_ds_type_element + ADD CONSTRAINT fk_ds_type_element_element + FOREIGN KEY (distribution_set_type) + REFERENCES sp_distribution_set_type (id) + ON DELETE CASCADE; + +ALTER TABLE sp_ds_type_element DROP FOREIGN KEY fk_ds_type_element_smtype; +ALTER TABLE sp_ds_type_element + ADD CONSTRAINT fk_ds_type_element_smtype + FOREIGN KEY (software_module_type) + REFERENCES sp_software_module_type (id) + ON DELETE CASCADE; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/RemoteIdEventTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/RemoteIdEventTest.java index f678421da..e202c747a 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/RemoteIdEventTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/RemoteIdEventTest.java @@ -65,6 +65,12 @@ public class RemoteIdEventTest extends AbstractRemoteEventTest { assertAndCreateRemoteEvent(SoftwareModuleDeletedEvent.class); } + @Test + @Description("Verifies that the rollout id is correct reloaded") + public void testRolloutDeletedEvent() { + assertAndCreateRemoteEvent(RolloutDeletedEvent.class); + } + protected void assertAndCreateRemoteEvent(final Class eventType) { final Constructor constructor = Arrays.stream(eventType.getDeclaredConstructors()) diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/RemoteTenantAwareEventTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/RemoteTenantAwareEventTest.java index 72f02fd9b..022f26d0f 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/RemoteTenantAwareEventTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/RemoteTenantAwareEventTest.java @@ -13,6 +13,7 @@ import static org.assertj.core.api.Assertions.assertThat; import org.eclipse.hawkbit.repository.jpa.model.JpaAction; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.ActionType; +import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.Target; import org.junit.Test; @@ -46,6 +47,7 @@ public class RemoteTenantAwareEventTest extends AbstractRemoteEventTest { final Target target = testdataFactory.createTarget("Test"); generateAction.setTarget(target); generateAction.setDistributionSet(dsA); + generateAction.setStatus(Status.RUNNING); final Action action = actionRepository.save(generateAction); final TargetAssignDistributionSetEvent assignmentEvent = new TargetAssignDistributionSetEvent(action, diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/entity/ActionEventTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/entity/ActionEventTest.java index d86035f11..796d93dab 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/entity/ActionEventTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/entity/ActionEventTest.java @@ -8,9 +8,16 @@ */ package org.eclipse.hawkbit.repository.event.remote.entity; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; + +import java.lang.reflect.Constructor; + import org.eclipse.hawkbit.repository.jpa.model.JpaAction; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.ActionType; +import org.eclipse.hawkbit.repository.model.Action.Status; +import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.Target; import org.junit.Test; @@ -35,7 +42,49 @@ public class ActionEventTest extends AbstractRemoteEntityEventTest { @Description("Verifies that the action entity reloading by remote updated works") public void testActionUpdatedEvent() { assertAndCreateRemoteEvent(ActionUpdatedEvent.class); + } + @Override + protected RemoteEntityEvent createRemoteEvent(final Action baseEntity, + final Class> eventType) { + + Constructor constructor = null; + for (final Constructor constructors : eventType.getDeclaredConstructors()) { + if (constructors.getParameterCount() == 4) { + constructor = constructors; + } + } + + if (constructor == null) { + throw new IllegalArgumentException("No suitable constructor foundes"); + } + + try { + return (RemoteEntityEvent) constructor.newInstance(baseEntity, 1L, 2L, "Node"); + } catch (final ReflectiveOperationException e) { + fail("Exception should not happen " + e.getMessage()); + } + return null; + } + + @Override + protected RemoteEntityEvent assertEntity(final Action baseEntity, final RemoteEntityEvent e) { + final AbstractActionEvent event = (AbstractActionEvent) e; + + assertThat(event.getEntity()).isSameAs(baseEntity); + assertThat(event.getRolloutId()).isEqualTo(1L); + + AbstractActionEvent underTestCreatedEvent = (AbstractActionEvent) createProtoStuffEvent(event); + assertThat(underTestCreatedEvent.getEntity()).isEqualTo(baseEntity); + assertThat(underTestCreatedEvent.getRolloutId()).isEqualTo(1L); + assertThat(underTestCreatedEvent.getRolloutGroupId()).isEqualTo(2L); + + underTestCreatedEvent = (AbstractActionEvent) createJacksonEvent(event); + assertThat(underTestCreatedEvent.getEntity()).isEqualTo(baseEntity); + assertThat(underTestCreatedEvent.getRolloutId()).isEqualTo(1L); + assertThat(underTestCreatedEvent.getRolloutGroupId()).isEqualTo(2L); + + return underTestCreatedEvent; } @Override @@ -43,8 +92,10 @@ public class ActionEventTest extends AbstractRemoteEntityEventTest { final JpaAction generateAction = new JpaAction(); generateAction.setActionType(ActionType.FORCED); final Target target = testdataFactory.createTarget("Test"); + final DistributionSet distributionSet = testdataFactory.createDistributionSet(); generateAction.setTarget(target); - generateAction.setDistributionSet(testdataFactory.createDistributionSet()); + generateAction.setDistributionSet(distributionSet); + generateAction.setStatus(Status.RUNNING); return actionRepository.save(generateAction); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/entity/RolloutGroupEventTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/entity/RolloutGroupEventTest.java index 974b73b8a..25bb5e84c 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/entity/RolloutGroupEventTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/entity/RolloutGroupEventTest.java @@ -8,8 +8,10 @@ */ package org.eclipse.hawkbit.repository.event.remote.entity; +import static org.junit.Assert.fail; import static org.assertj.core.api.Assertions.assertThat; +import java.lang.reflect.Constructor; import java.util.UUID; import org.eclipse.hawkbit.repository.model.DistributionSet; @@ -44,6 +46,47 @@ public class RolloutGroupEventTest extends AbstractRemoteEntityEventTest createRemoteEvent(final RolloutGroup baseEntity, + final Class> eventType) { + + Constructor constructor = null; + for (final Constructor constructors : eventType.getDeclaredConstructors()) { + if (constructors.getParameterCount() == 3) { + constructor = constructors; + } + } + + if (constructor == null) { + throw new IllegalArgumentException("No suitable constructor foundes"); + } + + try { + return (RemoteEntityEvent) constructor.newInstance(baseEntity, 1L, "Node"); + } catch (final ReflectiveOperationException e) { + fail("Exception should not happen " + e.getMessage()); + } + return null; + } + + @Override + protected RemoteEntityEvent assertEntity(final RolloutGroup baseEntity, final RemoteEntityEvent e) { + final AbstractRolloutGroupEvent event = (AbstractRolloutGroupEvent) e; + + assertThat(event.getEntity()).isSameAs(baseEntity); + assertThat(event.getRolloutId()).isEqualTo(1L); + + AbstractRolloutGroupEvent underTestCreatedEvent = (AbstractRolloutGroupEvent) createProtoStuffEvent(event); + assertThat(underTestCreatedEvent.getEntity()).isEqualTo(baseEntity); + assertThat(underTestCreatedEvent.getRolloutId()).isEqualTo(1L); + + underTestCreatedEvent = (AbstractRolloutGroupEvent) createJacksonEvent(event); + assertThat(underTestCreatedEvent.getEntity()).isEqualTo(baseEntity); + assertThat(underTestCreatedEvent.getRolloutId()).isEqualTo(1L); + + return underTestCreatedEvent; + } + @Override protected RolloutGroup createEntity() { testdataFactory.createTarget(UUID.randomUUID().toString()); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java index 477286050..d46787ee3 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java @@ -31,6 +31,8 @@ import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; +import com.google.common.collect.Lists; + @SpringApplicationConfiguration(classes = { org.eclipse.hawkbit.repository.jpa.RepositoryApplicationConfiguration.class }) public abstract class AbstractJpaIntegrationTest extends AbstractIntegrationTest { @@ -80,6 +82,9 @@ public abstract class AbstractJpaIntegrationTest extends AbstractIntegrationTest @Autowired protected RolloutGroupRepository rolloutGroupRepository; + @Autowired + protected RolloutTargetGroupRepository rolloutTargetGroupRepository; + @Autowired protected RolloutRepository rolloutRepository; @@ -91,7 +96,7 @@ public abstract class AbstractJpaIntegrationTest extends AbstractIntegrationTest @Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED) protected List findActionsByRolloutAndStatus(final Rollout rollout, final Action.Status actionStatus) { - return actionRepository.findByRolloutIdAndStatus(rollout.getId(), actionStatus); + return Lists.newArrayList(actionRepository.findByRolloutIdAndStatus(pageReq, rollout.getId(), actionStatus)); } protected TargetTagAssignmentResult toggleTagAssignment(final Collection targets, final TargetTag tag) { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ArtifactManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ArtifactManagementTest.java index 91531c9e2..c6e55af1a 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ArtifactManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ArtifactManagementTest.java @@ -9,23 +9,29 @@ package org.eclipse.hawkbit.repository.jpa; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.net.URISyntaxException; import java.security.NoSuchAlgorithmException; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.RandomStringUtils; import org.eclipse.hawkbit.im.authentication.SpPermission; import org.eclipse.hawkbit.repository.ArtifactManagement; +import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent; +import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException; import org.eclipse.hawkbit.repository.jpa.model.JpaArtifact; import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule; import org.eclipse.hawkbit.repository.model.Artifact; import org.eclipse.hawkbit.repository.model.SoftwareModule; +import org.eclipse.hawkbit.repository.test.matcher.Expect; +import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents; import org.eclipse.hawkbit.repository.test.util.HashGeneratorUtils; import org.eclipse.hawkbit.repository.test.util.WithUser; import org.junit.Test; @@ -35,24 +41,47 @@ import ru.yandex.qatools.allure.annotations.Features; import ru.yandex.qatools.allure.annotations.Stories; /** - * Test class for {@link ArtifactManagement} with running MongoDB instance.. - * - * - * - * + * Test class for {@link ArtifactManagement}. */ @Features("Component Tests - Repository") @Stories("Artifact Management") public class ArtifactManagementTest extends AbstractJpaIntegrationTest { - /** - * Test method for - * {@link org.eclipse.hawkbit.repository.ArtifactManagement#createArtifact(java.io.InputStream)} - * . - * - * @throws IOException - * @throws NoSuchAlgorithmException - */ + @Test + @Description("Verifies that management queries react as specfied on calls for non existing entities.") + @ExpectEvents({ @Expect(type = SoftwareModuleCreatedEvent.class, count = 1) }) + public void nonExistingEntityQueries() throws URISyntaxException { + final SoftwareModule module = testdataFactory.createSoftwareModuleOs(); + + assertThatThrownBy(() -> artifactManagement.createArtifact(IOUtils.toInputStream("test", "UTF-8"), 1234L, "xxx", + null, null, false, null)).isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234") + .hasMessageContaining("SoftwareModule"); + + assertThatThrownBy( + () -> artifactManagement.createArtifact(IOUtils.toInputStream("test", "UTF-8"), 1234L, "xxx", false)) + .isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234") + .hasMessageContaining("SoftwareModule"); + + assertThatThrownBy(() -> artifactManagement.deleteArtifact(1234L)).isInstanceOf(EntityNotFoundException.class) + .hasMessageContaining("1234").hasMessageContaining("Artifact"); + + assertThat(artifactManagement.findArtifact(1234L).isPresent()).isFalse(); + assertThatThrownBy(() -> artifactManagement.findArtifactBySoftwareModule(pageReq, 1234L)) + .isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234") + .hasMessageContaining("SoftwareModule"); + assertThat(artifactManagement.findArtifactByFilename("1234").isPresent()).isFalse(); + + assertThatThrownBy(() -> artifactManagement.findByFilenameAndSoftwareModule("xxx", 1234L)) + .isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234") + .hasMessageContaining("SoftwareModule"); + + assertThat(artifactManagement.findByFilenameAndSoftwareModule("1234", module.getId()).isPresent()).isFalse(); + + assertThat(artifactManagement.findFirstArtifactBySHA1("1234").isPresent()).isFalse(); + assertThat(artifactManagement.loadArtifactBinary("1234").isPresent()).isFalse(); + + } + @Test @Description("Test if a local artifact can be created by API including metadata.") public void createArtifact() throws NoSuchAlgorithmException, IOException { @@ -205,7 +234,7 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest { final Artifact result = artifactManagement.createArtifact(new ByteArrayInputStream(random), testdataFactory.createSoftwareModuleOs().getId(), "file1", false); - try (InputStream fileInputStream = artifactManagement.loadArtifactBinary(result.getSha1Hash()) + try (InputStream fileInputStream = artifactManagement.loadArtifactBinary(result.getSha1Hash()).get() .getFileInputStream()) { assertTrue("The stored binary matches the given binary", IOUtils.contentEquals(new ByteArrayInputStream(random), fileInputStream)); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ControllerManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ControllerManagementTest.java index 8158f9bee..7d361d765 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ControllerManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ControllerManagementTest.java @@ -9,9 +9,12 @@ package org.eclipse.hawkbit.repository.jpa; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.Assert.fail; import java.io.ByteArrayInputStream; +import java.net.URI; +import java.net.URISyntaxException; import java.util.List; import java.util.Map; @@ -29,10 +32,12 @@ import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleUpdatedE import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent; import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException; +import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.ActionStatus; import org.eclipse.hawkbit.repository.model.Artifact; import org.eclipse.hawkbit.repository.model.DistributionSet; +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.test.matcher.Expect; @@ -55,6 +60,75 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest { @Autowired private RepositoryProperties repositoryProperties; + @Test + @Description("Verifies that management queries react as specfied on calls for non existing entities.") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), + @Expect(type = SoftwareModuleCreatedEvent.class, count = 1) }) + public void nonExistingEntityQueries() throws URISyntaxException { + final Target target = testdataFactory.createTarget(); + final SoftwareModule module = testdataFactory.createSoftwareModuleOs(); + + assertThatThrownBy(() -> controllerManagement + .addCancelActionStatus(entityFactory.actionStatus().create(1234L).status(Action.Status.FINISHED))) + .isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234") + .hasMessageContaining("Action"); + + assertThatThrownBy(() -> controllerManagement + .addInformationalActionStatus(entityFactory.actionStatus().create(1234L).status(Action.Status.RUNNING))) + .isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234") + .hasMessageContaining("Action"); + + assertThatThrownBy(() -> controllerManagement + .addUpdateActionStatus(entityFactory.actionStatus().create(1234L).status(Action.Status.FINISHED))) + .isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234") + .hasMessageContaining("Action"); + + assertThat(controllerManagement.findActionWithDetails(1234L).isPresent()).isFalse(); + assertThat(controllerManagement.findByControllerId("1234").isPresent()).isFalse(); + assertThat(controllerManagement.findByTargetId(1234L).isPresent()).isFalse(); + + assertThatThrownBy(() -> controllerManagement.findOldestActiveActionByTarget("1234")) + .isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234") + .hasMessageContaining("Target"); + + assertThatThrownBy(() -> controllerManagement + .getActionForDownloadByTargetAndSoftwareModule(target.getControllerId(), 1234L)) + .isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234") + .hasMessageContaining("SoftwareModule"); + + assertThatThrownBy( + () -> controllerManagement.getActionForDownloadByTargetAndSoftwareModule("1234", module.getId())) + .isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234") + .hasMessageContaining("Target"); + + assertThat(controllerManagement + .getActionForDownloadByTargetAndSoftwareModule(target.getControllerId(), module.getId()).isPresent()) + .isFalse(); + + assertThatThrownBy(() -> controllerManagement.hasTargetArtifactAssigned(1234L, "XXX")) + .isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234") + .hasMessageContaining("Target"); + + assertThatThrownBy(() -> controllerManagement.hasTargetArtifactAssigned("1234", "XXX")) + .isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234") + .hasMessageContaining("Target"); + + assertThat(controllerManagement.hasTargetArtifactAssigned(target.getControllerId(), "XXX")).isFalse(); + assertThat(controllerManagement.hasTargetArtifactAssigned(target.getId(), "XXX")).isFalse(); + + assertThatThrownBy(() -> controllerManagement.registerRetrieved(1234L, "test message")) + .isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234") + .hasMessageContaining("Action"); + + assertThatThrownBy(() -> controllerManagement.updateControllerAttributes("1234", Maps.newHashMap())) + .isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234") + .hasMessageContaining("Target"); + + assertThatThrownBy(() -> controllerManagement.updateLastTargetQuery("1234", new URI("http://test.com"))) + .isInstanceOf(EntityNotFoundException.class).hasMessageContaining("1234") + .hasMessageContaining("Target"); + } + @Test @Description("Controller confirms successfull update with FINISHED status.") @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), @@ -68,7 +142,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest { simulateIntermediateStatusOnUpdate(actionId); - controllerManagament + controllerManagement .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED)); assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.IN_SYNC, Action.Status.FINISHED, Action.Status.FINISHED, false); @@ -91,7 +165,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest { final Long actionId = createTargetAndAssignDs(); deploymentManagement.cancelAction(actionId); - controllerManagament + controllerManagement .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED)); assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.IN_SYNC, Action.Status.FINISHED, Action.Status.FINISHED, false); @@ -111,7 +185,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest { final Long actionId = createTargetAndAssignDs(); try { - controllerManagament.addCancelActionStatus( + controllerManagement.addCancelActionStatus( entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED)); fail("Expected " + CancelActionNotAllowedException.class.getName()); } catch (final CancelActionNotAllowedException e) { @@ -144,7 +218,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest { simulateIntermediateStatusOnCancellation(actionId); - controllerManagament + controllerManagement .addCancelActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED)); assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.IN_SYNC, Action.Status.CANCELED, Action.Status.FINISHED, false); @@ -171,7 +245,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest { simulateIntermediateStatusOnCancellation(actionId); - controllerManagament + controllerManagement .addCancelActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.CANCELED)); assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.IN_SYNC, Action.Status.CANCELED, Action.Status.CANCELED, false); @@ -199,7 +273,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest { simulateIntermediateStatusOnCancellation(actionId); - controllerManagament.addCancelActionStatus( + controllerManagement.addCancelActionStatus( entityFactory.actionStatus().create(actionId).status(Action.Status.CANCEL_REJECTED)); assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING, Action.Status.RUNNING, Action.Status.CANCEL_REJECTED, true); @@ -227,7 +301,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest { simulateIntermediateStatusOnCancellation(actionId); - controllerManagament + controllerManagement .addCancelActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.ERROR)); assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING, Action.Status.RUNNING, Action.Status.ERROR, true); @@ -249,22 +323,22 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest { @Step private void simulateIntermediateStatusOnCancellation(final Long actionId) { - controllerManagament + controllerManagement .addCancelActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.RUNNING)); assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING, Action.Status.CANCELING, Action.Status.RUNNING, true); - controllerManagament + controllerManagement .addCancelActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.DOWNLOAD)); assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING, Action.Status.CANCELING, Action.Status.DOWNLOAD, true); - controllerManagament + controllerManagement .addCancelActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.RETRIEVED)); assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING, Action.Status.CANCELING, Action.Status.RETRIEVED, true); - controllerManagament + controllerManagement .addCancelActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.WARNING)); assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING, Action.Status.CANCELING, Action.Status.WARNING, true); @@ -272,22 +346,22 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest { @Step private void simulateIntermediateStatusOnUpdate(final Long actionId) { - controllerManagament + controllerManagement .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.RUNNING)); assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING, Action.Status.RUNNING, Action.Status.RUNNING, true); - controllerManagament + controllerManagement .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.DOWNLOAD)); assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING, Action.Status.RUNNING, Action.Status.DOWNLOAD, true); - controllerManagament + controllerManagement .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.RETRIEVED)); assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING, Action.Status.RUNNING, Action.Status.RETRIEVED, true); - controllerManagament + controllerManagement .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.WARNING)); assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING, Action.Status.RUNNING, Action.Status.WARNING, true); @@ -305,7 +379,10 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest { final List actionStatusList = deploymentManagement.findActionStatusByAction(pageReq, actionId) .getContent(); assertThat(actionStatusList.get(actionStatusList.size() - 1).getStatus()).isEqualTo(expectedActionStatus); - + if (actionActive) { + assertThat(controllerManagement.findOldestActiveActionByTarget(controllerId).get().getId()) + .isEqualTo(actionId); + } } @Test @@ -332,31 +409,31 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest { assertThat(artifact.getSha1Hash()).isEqualTo(artifact2.getSha1Hash()); assertThat( - controllerManagament.hasTargetArtifactAssigned(savedTarget.getControllerId(), artifact.getSha1Hash())) + controllerManagement.hasTargetArtifactAssigned(savedTarget.getControllerId(), artifact.getSha1Hash())) .isFalse(); savedTarget = assignDistributionSet(ds.getId(), savedTarget.getControllerId()).getAssignedEntity().iterator() .next(); assertThat( - controllerManagament.hasTargetArtifactAssigned(savedTarget.getControllerId(), artifact.getSha1Hash())) + controllerManagement.hasTargetArtifactAssigned(savedTarget.getControllerId(), artifact.getSha1Hash())) .isTrue(); assertThat( - controllerManagament.hasTargetArtifactAssigned(savedTarget.getControllerId(), artifact2.getSha1Hash())) + controllerManagement.hasTargetArtifactAssigned(savedTarget.getControllerId(), artifact2.getSha1Hash())) .isTrue(); } @Test @Description("Register a controller which does not exist") public void findOrRegisterTargetIfItDoesNotexist() { - final Target target = controllerManagament.findOrRegisterTargetIfItDoesNotexist("AA", null); + final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotexist("AA", null); assertThat(target).as("target should not be null").isNotNull(); - final Target sameTarget = controllerManagament.findOrRegisterTargetIfItDoesNotexist("AA", null); + final Target sameTarget = controllerManagement.findOrRegisterTargetIfItDoesNotexist("AA", null); assertThat(target).as("Target should be the equals").isEqualTo(sameTarget); assertThat(targetRepository.count()).as("Only 1 target should be registred").isEqualTo(1L); // throws exception try { - controllerManagament.findOrRegisterTargetIfItDoesNotexist("", null); + controllerManagement.findOrRegisterTargetIfItDoesNotexist("", null); fail("should fail as target does not exist"); } catch (final ConstraintViolationException e) { @@ -375,19 +452,19 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest { final Long actionId = createTargetAndAssignDs(); // test and verify - controllerManagament + controllerManagement .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.RUNNING)); assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING, Action.Status.RUNNING, Action.Status.RUNNING, true); - controllerManagament + controllerManagement .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.ERROR)); assertActionStatus(actionId, TestdataFactory.DEFAULT_CONTROLLER_ID, TargetUpdateStatus.ERROR, Action.Status.ERROR, Action.Status.ERROR, false); // try with disabled late feedback repositoryProperties.setRejectActionStatusForClosedAction(true); - controllerManagament + controllerManagement .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED)); // test @@ -397,7 +474,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest { // try with enabled late feedback - should not make a difference as it // only allows intermediate feedbacks and not multiple close repositoryProperties.setRejectActionStatusForClosedAction(false); - controllerManagament + controllerManagement .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED)); // test @@ -422,7 +499,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest { // try with disabled late feedback repositoryProperties.setRejectActionStatusForClosedAction(true); - controllerManagament + controllerManagement .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED)); // test @@ -432,7 +509,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest { // try with enabled late feedback - should not make a difference as it // only allows intermediate feedbacks and not multiple close repositoryProperties.setRejectActionStatusForClosedAction(false); - controllerManagament + controllerManagement .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED)); // test @@ -458,7 +535,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest { final Action action = prepareFinishedUpdate(); - controllerManagament.addUpdateActionStatus( + controllerManagement.addUpdateActionStatus( entityFactory.actionStatus().create(action.getId()).status(Action.Status.RUNNING)); // nothing changed as "feedback after close" is disabled @@ -483,7 +560,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest { repositoryProperties.setRejectActionStatusForClosedAction(false); Action action = prepareFinishedUpdate(); - action = controllerManagament.addUpdateActionStatus( + action = controllerManagement.addUpdateActionStatus( entityFactory.actionStatus().create(action.getId()).status(Action.Status.RUNNING)); // nothing changed as "feedback after close" is disabled @@ -512,7 +589,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest { private void addAttributeAndVerify(final String controllerId) { final Map testData = Maps.newHashMapWithExpectedSize(1); testData.put("test1", "testdata1"); - controllerManagament.updateControllerAttributes(controllerId, testData); + controllerManagement.updateControllerAttributes(controllerId, testData); final Target target = targetManagement.findTargetByControllerIDWithDetails(controllerId).get(); assertThat(target.getTargetInfo().getControllerAttributes()).as("Controller Attributes are wrong") @@ -523,7 +600,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest { private void addSecondAttributeAndVerify(final String controllerId) { final Map testData = Maps.newHashMapWithExpectedSize(2); testData.put("test2", "testdata20"); - controllerManagament.updateControllerAttributes(controllerId, testData); + controllerManagement.updateControllerAttributes(controllerId, testData); final Target target = targetManagement.findTargetByControllerIDWithDetails(controllerId).get(); testData.put("test1", "testdata1"); @@ -536,7 +613,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest { final Map testData = Maps.newHashMapWithExpectedSize(2); testData.put("test1", "testdata12"); - controllerManagament.updateControllerAttributes(controllerId, testData); + controllerManagement.updateControllerAttributes(controllerId, testData); final Target target = targetManagement.findTargetByControllerIDWithDetails(controllerId).get(); testData.put("test2", "testdata20"); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DeploymentManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DeploymentManagementTest.java index 06c877495..e60976353 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DeploymentManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DeploymentManagementTest.java @@ -752,7 +752,7 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest { .as("Installed distribution set of action should be null").isNotNull(); final Page updAct = actionRepository.findByDistributionSetId(pageReq, dsA.getId()); - controllerManagament.addUpdateActionStatus( + controllerManagement.addUpdateActionStatus( entityFactory.actionStatus().create(updAct.getContent().get(0).getId()).status(Status.FINISHED)); targ = targetManagement.findTargetByControllerID(targ.getControllerId()).get(); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ReportManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ReportManagementTest.java index 03745bb6b..ffd280070 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ReportManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ReportManagementTest.java @@ -139,7 +139,7 @@ public class ReportManagementTest extends AbstractJpaIntegrationTest { final Target createTarget = testdataFactory.createTarget("t" + month); final DistributionSetAssignmentResult result = assignDistributionSet(distributionSet, Lists.newArrayList(createTarget)); - controllerManagament.registerRetrieved(result.getActions().get(0), + controllerManagement.registerRetrieved(result.getActions().get(0), "Controller retrieved update action and should start now the download."); } DataReportSeries feedbackReceivedOverTime = reportManagement @@ -160,7 +160,7 @@ public class ReportManagementTest extends AbstractJpaIntegrationTest { final Target createTarget = testdataFactory.createTarget("t2" + month); final DistributionSetAssignmentResult result = assignDistributionSet(distributionSet, Lists.newArrayList(createTarget)); - controllerManagament.registerRetrieved(result.getActions().get(0), + controllerManagement.registerRetrieved(result.getActions().get(0), "Controller retrieved update action and should start now the download."); } feedbackReceivedOverTime = reportManagement.feedbackReceivedOverTime(DateTypes.perMonth(), from, to); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/RolloutManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/RolloutManagementTest.java index 794f9f8a4..3d36675f8 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/RolloutManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/RolloutManagementTest.java @@ -23,10 +23,23 @@ import org.eclipse.hawkbit.repository.RolloutGroupManagement; import org.eclipse.hawkbit.repository.RolloutManagement; import org.eclipse.hawkbit.repository.builder.RolloutCreate; import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate; +import org.eclipse.hawkbit.repository.event.remote.RolloutDeletedEvent; +import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent; +import org.eclipse.hawkbit.repository.event.remote.entity.ActionCreatedEvent; +import org.eclipse.hawkbit.repository.event.remote.entity.ActionUpdatedEvent; +import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent; +import org.eclipse.hawkbit.repository.event.remote.entity.RolloutGroupCreatedEvent; +import org.eclipse.hawkbit.repository.event.remote.entity.RolloutGroupUpdatedEvent; +import org.eclipse.hawkbit.repository.event.remote.entity.RolloutUpdatedEvent; +import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent; +import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent; +import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent; import org.eclipse.hawkbit.repository.exception.ConstraintViolationException; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; +import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException; import org.eclipse.hawkbit.repository.jpa.model.JpaAction; +import org.eclipse.hawkbit.repository.jpa.model.JpaRollout; import org.eclipse.hawkbit.repository.jpa.utils.MultipleInvokeHelper; import org.eclipse.hawkbit.repository.jpa.utils.SuccessCondition; import org.eclipse.hawkbit.repository.model.Action; @@ -45,6 +58,8 @@ 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.test.matcher.Expect; +import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents; import org.eclipse.hawkbit.repository.test.util.TestdataFactory; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -138,12 +153,12 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { // finish one action should be sufficient due the finish condition is at // 50% final JpaAction action = (JpaAction) runningActions.get(0); - controllerManagament + controllerManagement .addUpdateActionStatus(entityFactory.actionStatus().create(action.getId()).status(Status.FINISHED)); // check running rollouts again, now the finish condition should be hit // and should start the next group - rolloutManagement.checkRunningRollouts(0); + rolloutManagement.handleRollouts(); // verify that now the first and the second group are in running state final List runningRolloutGroups = rolloutGroupManagement.findRolloutGroupsByRolloutId( @@ -194,7 +209,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { rolloutManagement.startRollout(createdRollout.getId()); // Run here, because scheduler is disabled during tests - rolloutManagement.checkStartingRollouts(0); + rolloutManagement.handleRollouts(); return rolloutManagement.findRolloutById(createdRollout.getId()).get(); } @@ -202,8 +217,9 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { @Step("Finish three actions of the rollout group and delete two targets") private void finishActionAndDeleteTargetsOfFirstRunningGroup(final Rollout createdRollout) { // finish group one by finishing targets and deleting targets - final List runningActions = actionRepository.findByRolloutIdAndStatus(createdRollout.getId(), - Status.RUNNING); + final Slice runningActionsSlice = actionRepository.findByRolloutIdAndStatus(pageReq, + createdRollout.getId(), Status.RUNNING); + final List runningActions = Lists.newArrayList(runningActionsSlice.iterator()); finishAction(runningActions.get(0)); finishAction(runningActions.get(1)); finishAction(runningActions.get(2)); @@ -213,7 +229,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { @Step("Check the status of the rollout groups, second group should be in running status") private void checkSecondGroupStatusIsRunning(final Rollout createdRollout) { - rolloutManagement.checkRunningRollouts(0); + rolloutManagement.handleRollouts(); final List runningRolloutGroups = rolloutGroupManagement.findRolloutGroupsByRolloutId( createdRollout.getId(), new OffsetBasedPageRequest(0, 10, new Sort(Direction.ASC, "id"))).getContent(); assertThat(runningRolloutGroups.get(0).getStatus()).isEqualTo(RolloutGroupStatus.FINISHED); @@ -223,8 +239,9 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { @Step("Finish one action of the rollout group and delete four targets") private void finishActionAndDeleteTargetsOfSecondRunningGroup(final Rollout createdRollout) { - final List runningActions = actionRepository.findByRolloutIdAndStatus(createdRollout.getId(), - Status.RUNNING); + final Slice runningActionsSlice = actionRepository.findByRolloutIdAndStatus(pageReq, + createdRollout.getId(), Status.RUNNING); + final List runningActions = Lists.newArrayList(runningActionsSlice.iterator()); finishAction(runningActions.get(0)); targetManagement.deleteTargets( Lists.newArrayList(runningActions.get(1).getTarget().getId(), runningActions.get(2).getTarget().getId(), @@ -234,8 +251,9 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { @Step("Delete all targets of the rollout group") private void deleteAllTargetsFromThirdGroup(final Rollout createdRollout) { - final List runningActions = actionRepository.findByRolloutIdAndStatus(createdRollout.getId(), - Status.SCHEDULED); + final Slice runningActionsSlice = actionRepository.findByRolloutIdAndStatus(pageReq, + createdRollout.getId(), Status.SCHEDULED); + final List runningActions = Lists.newArrayList(runningActionsSlice.iterator()); targetManagement.deleteTargets(Lists.newArrayList(runningActions.get(0).getTarget().getId(), runningActions.get(1).getTarget().getId(), runningActions.get(2).getTarget().getId(), runningActions.get(3).getTarget().getId(), runningActions.get(4).getTarget().getId())); @@ -243,7 +261,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { @Step("Check the status of the rollout groups and the rollout") private void verifyRolloutAndAllGroupsAreFinished(final Rollout createdRollout) { - rolloutManagement.checkRunningRollouts(0); + rolloutManagement.handleRollouts(); final List runningRolloutGroups = rolloutGroupManagement.findRolloutGroupsByRolloutId( createdRollout.getId(), new OffsetBasedPageRequest(0, 10, new Sort(Direction.ASC, "id"))).getContent(); assertThat(runningRolloutGroups.get(0).getStatus()).isEqualTo(RolloutGroupStatus.FINISHED); @@ -255,7 +273,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { } private void finishAction(final Action action) { - controllerManagament + controllerManagement .addUpdateActionStatus(entityFactory.actionStatus().create(action.getId()).status(Status.FINISHED)); } @@ -276,13 +294,13 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { // finish actions with error for (final Action action : runningActions) { - controllerManagament + controllerManagement .addUpdateActionStatus(entityFactory.actionStatus().create(action.getId()).status(Status.ERROR)); } // check running rollouts again, now the error condition should be hit // and should execute the error action - rolloutManagement.checkRunningRollouts(0); + rolloutManagement.handleRollouts(); final Rollout rollout = rolloutManagement.findRolloutById(createdRollout.getId()).get(); // the rollout itself should be in paused based on the error action @@ -316,13 +334,13 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { final List runningActions = findActionsByRolloutAndStatus(createdRollout, Status.RUNNING); // finish actions with error for (final Action action : runningActions) { - controllerManagament + controllerManagement .addUpdateActionStatus(entityFactory.actionStatus().create(action.getId()).status(Status.ERROR)); } // check running rollouts again, now the error condition should be hit // and should execute the error action - rolloutManagement.checkRunningRollouts(0); + rolloutManagement.handleRollouts(); final Rollout rollout = rolloutManagement.findRolloutById(createdRollout.getId()).get(); // the rollout itself should be in paused based on the error action @@ -341,7 +359,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { .isEqualTo(RolloutStatus.RUNNING); // checking rollouts again - rolloutManagement.checkRunningRollouts(0); + rolloutManagement.handleRollouts(); // next group should be running again after resuming the rollout final List resumedGroups = rolloutGroupManagement.findRolloutGroupsByRolloutId( @@ -367,7 +385,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { // 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); + rolloutManagement.handleRollouts(); // finish running actions, 2 actions should be finished assertThat(changeStatusForAllRunningActions(createdRollout, Status.FINISHED)).isEqualTo(2); assertThat(rolloutManagement.findRolloutById(createdRollout.getId()).get().getStatus()) @@ -376,7 +394,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { } // 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); + rolloutManagement.handleRollouts(); // verify all groups are in finished state rolloutGroupManagement @@ -409,7 +427,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { rolloutManagement.startRollout(createdRollout.getId()); // Run here, because scheduler is disabled during tests - rolloutManagement.checkStartingRollouts(0); + rolloutManagement.handleRollouts(); // 6 targets are ready and 2 are running validationMap = createInitStatusMap(); @@ -418,7 +436,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { validateRolloutActionStatus(createdRollout.getId(), validationMap); changeStatusForAllRunningActions(createdRollout, Status.FINISHED); - rolloutManagement.checkRunningRollouts(0); + rolloutManagement.handleRollouts(); // 4 targets are ready, 2 are finished and 2 are running validationMap = createInitStatusMap(); validationMap.put(TotalTargetCountStatus.Status.SCHEDULED, 4L); @@ -427,7 +445,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { validateRolloutActionStatus(createdRollout.getId(), validationMap); changeStatusForAllRunningActions(createdRollout, Status.FINISHED); - rolloutManagement.checkRunningRollouts(0); + rolloutManagement.handleRollouts(); // 2 targets are ready, 4 are finished and 2 are running validationMap = createInitStatusMap(); validationMap.put(TotalTargetCountStatus.Status.SCHEDULED, 2L); @@ -436,7 +454,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { validateRolloutActionStatus(createdRollout.getId(), validationMap); changeStatusForAllRunningActions(createdRollout, Status.FINISHED); - rolloutManagement.checkRunningRollouts(0); + rolloutManagement.handleRollouts(); // 0 targets are ready, 6 are finished and 2 are running validationMap = createInitStatusMap(); validationMap.put(TotalTargetCountStatus.Status.FINISHED, 6L); @@ -444,7 +462,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { validateRolloutActionStatus(createdRollout.getId(), validationMap); changeStatusForAllRunningActions(createdRollout, Status.FINISHED); - rolloutManagement.checkRunningRollouts(0); + rolloutManagement.handleRollouts(); // 0 targets are ready, 8 are finished and 0 are running validationMap = createInitStatusMap(); validationMap.put(TotalTargetCountStatus.Status.FINISHED, 8L); @@ -472,7 +490,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { rolloutManagement.startRollout(createdRollout.getId()); // Run here, because scheduler is disabled during tests - rolloutManagement.checkStartingRollouts(0); + rolloutManagement.handleRollouts(); // 6 targets are ready and 2 are running validationMap = createInitStatusMap(); @@ -481,7 +499,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { validateRolloutActionStatus(createdRollout.getId(), validationMap); changeStatusForAllRunningActions(createdRollout, Status.ERROR); - rolloutManagement.checkRunningRollouts(0); + rolloutManagement.handleRollouts(); // 6 targets are ready and 2 are error validationMap = createInitStatusMap(); validationMap.put(TotalTargetCountStatus.Status.SCHEDULED, 6L); @@ -502,7 +520,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { successCondition, errorCondition); changeStatusForAllRunningActions(createdRollout, Status.FINISHED); - rolloutManagement.checkRunningRollouts(0); + rolloutManagement.handleRollouts(); // round(9/4)=2 targets finished (Group 1) // round(7/3)=2 targets running (Group 3) @@ -591,7 +609,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { 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); + rolloutManagement.handleRollouts(); // Verify that 5 targets are finished, 5 are running and 5 are ready. Map expectedTargetCountStatus = createInitStatusMap(); expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 5L); @@ -602,7 +620,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { rolloutManagement.startRollout(rolloutTwo.getId()); // Run here, because scheduler is disabled during tests - rolloutManagement.checkStartingRollouts(0); + rolloutManagement.handleRollouts(); // Verify that 5 targets are finished, 5 are still running and 5 are // cancelled. @@ -629,13 +647,13 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId()).get(); changeStatusForRunningActions(rolloutOne, Status.ERROR, 2); changeStatusForRunningActions(rolloutOne, Status.FINISHED, 3); - rolloutManagement.checkRunningRollouts(0); + rolloutManagement.handleRollouts(); changeStatusForRunningActions(rolloutOne, Status.ERROR, 2); changeStatusForRunningActions(rolloutOne, Status.FINISHED, 3); - rolloutManagement.checkRunningRollouts(0); + rolloutManagement.handleRollouts(); changeStatusForRunningActions(rolloutOne, Status.ERROR, 2); changeStatusForRunningActions(rolloutOne, Status.FINISHED, 3); - rolloutManagement.checkRunningRollouts(0); + rolloutManagement.handleRollouts(); // 9 targets are finished and 6 have error Map expectedTargetCountStatus = createInitStatusMap(); @@ -653,7 +671,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { rolloutManagement.startRollout(rolloutTwo.getId()); // Run here, because scheduler is disabled during tests - rolloutManagement.checkStartingRollouts(0); + rolloutManagement.handleRollouts(); rolloutTwo = rolloutManagement.findRolloutById(rolloutTwo.getId()).get(); // 6 error targets are know running @@ -687,7 +705,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId()).get(); changeStatusForRunningActions(rolloutOne, Status.ERROR, 2); changeStatusForRunningActions(rolloutOne, Status.FINISHED, 3); - rolloutManagement.checkRunningRollouts(0); + rolloutManagement.handleRollouts(); // verify: 40% error but 60% finished -> should move to next group final List rolloutGruops = rolloutOne.getRolloutGroups(); final Map expectedTargetCountStatus = createInitStatusMap(); @@ -711,7 +729,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId()).get(); changeStatusForRunningActions(rolloutOne, Status.ERROR, 2); changeStatusForRunningActions(rolloutOne, Status.FINISHED, 3); - rolloutManagement.checkRunningRollouts(0); + rolloutManagement.handleRollouts(); // verify: 40% error and 60% finished -> should not move to next group // because successCondition 80% rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId()).get(); @@ -736,7 +754,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId()).get(); changeStatusForRunningActions(rolloutOne, Status.ERROR, 2); changeStatusForRunningActions(rolloutOne, Status.FINISHED, 3); - rolloutManagement.checkRunningRollouts(0); + rolloutManagement.handleRollouts(); // verify: 40% error -> should pause because errorCondition is 20% rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId()).get(); assertThat(RolloutStatus.PAUSED).isEqualTo(rolloutOne.getStatus()); @@ -753,42 +771,42 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { final Rollout rolloutA = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, amountGroups, successCondition, errorCondition, "RolloutA", "RolloutA"); rolloutManagement.startRollout(rolloutA.getId()); - rolloutManagement.checkStartingRollouts(0); + rolloutManagement.handleRollouts(); final int amountTargetsForRollout2 = 10; final int amountGroups2 = 2; final Rollout rolloutB = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout2, amountGroups2, successCondition, errorCondition, "RolloutB", "RolloutB"); rolloutManagement.startRollout(rolloutB.getId()); - rolloutManagement.checkStartingRollouts(0); + rolloutManagement.handleRollouts(); changeStatusForAllRunningActions(rolloutB, Status.FINISHED); - rolloutManagement.checkRunningRollouts(0); + rolloutManagement.handleRollouts(); final int amountTargetsForRollout3 = 10; final int amountGroups3 = 2; final Rollout rolloutC = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout3, amountGroups3, successCondition, errorCondition, "RolloutC", "RolloutC"); rolloutManagement.startRollout(rolloutC.getId()); - rolloutManagement.checkStartingRollouts(0); + rolloutManagement.handleRollouts(); changeStatusForAllRunningActions(rolloutC, Status.ERROR); - rolloutManagement.checkRunningRollouts(0); + rolloutManagement.handleRollouts(); final int amountTargetsForRollout4 = 15; final int amountGroups4 = 3; final Rollout rolloutD = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout4, amountGroups4, successCondition, errorCondition, "RolloutD", "RolloutD"); rolloutManagement.startRollout(rolloutD.getId()); - rolloutManagement.checkStartingRollouts(0); + rolloutManagement.handleRollouts(); changeStatusForRunningActions(rolloutD, Status.ERROR, 1); - rolloutManagement.checkRunningRollouts(0); + rolloutManagement.handleRollouts(); changeStatusForAllRunningActions(rolloutD, Status.FINISHED); - rolloutManagement.checkRunningRollouts(0); + rolloutManagement.handleRollouts(); - final Page rolloutPage = rolloutManagement - .findAllRolloutsWithDetailedStatus(new OffsetBasedPageRequest(0, 100, new Sort(Direction.ASC, "name"))); + final Page rolloutPage = rolloutManagement.findAllRolloutsWithDetailedStatus( + new OffsetBasedPageRequest(0, 100, new Sort(Direction.ASC, "name")), false); final List rolloutList = rolloutPage.getContent(); // validate rolloutA -> 6 running and 6 ready @@ -874,7 +892,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { } final Slice rollout = rolloutManagement.findRolloutWithDetailedStatusByFilters( - new OffsetBasedPageRequest(0, 100, new Sort(Direction.ASC, "name")), "Rollout%"); + new OffsetBasedPageRequest(0, 100, new Sort(Direction.ASC, "name")), "Rollout%", false); final List rolloutList = rollout.getContent(); assertThat(rolloutList.size()).isEqualTo(5); int i = 1; @@ -915,10 +933,10 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { rolloutManagement.startRollout(myRollout.getId()); // Run here, because scheduler is disabled during tests - rolloutManagement.checkStartingRollouts(0); + rolloutManagement.handleRollouts(); changeStatusForRunningActions(myRollout, Status.FINISHED, 2); - rolloutManagement.checkRunningRollouts(0); + rolloutManagement.handleRollouts(); myRollout = rolloutManagement.findRolloutById(myRollout.getId()).get(); float percent = rolloutManagement.getFinishedPercentForRunningGroup(myRollout.getId(), @@ -926,7 +944,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { assertThat(percent).isEqualTo(40); changeStatusForRunningActions(myRollout, Status.FINISHED, 3); - rolloutManagement.checkRunningRollouts(0); + rolloutManagement.handleRollouts(); percent = rolloutManagement.getFinishedPercentForRunningGroup(myRollout.getId(), myRollout.getRolloutGroups().get(0).getId()); @@ -934,7 +952,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { changeStatusForRunningActions(myRollout, Status.FINISHED, 4); changeStatusForAllRunningActions(myRollout, Status.ERROR); - rolloutManagement.checkRunningRollouts(0); + rolloutManagement.handleRollouts(); percent = rolloutManagement.getFinishedPercentForRunningGroup(myRollout.getId(), myRollout.getRolloutGroups().get(1).getId()); @@ -961,7 +979,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { rolloutManagement.startRollout(myRollout.getId()); // Run here, because scheduler is disabled during tests - rolloutManagement.checkStartingRollouts(0); + rolloutManagement.handleRollouts(); myRollout = rolloutManagement.findRolloutById(myRollout.getId()).get(); final List rolloutGroups = myRollout.getRolloutGroups(); @@ -1063,7 +1081,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { rolloutManagement.startRollout(myRollout.getId()); // Run here, because scheduler is disabled during tests - rolloutManagement.checkStartingRollouts(0); + rolloutManagement.handleRollouts(); final SuccessConditionRolloutStatus conditionRolloutStatus = new SuccessConditionRolloutStatus( RolloutStatus.RUNNING); @@ -1097,7 +1115,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { assertThat(myRollout.getStatus()).isEqualTo(RolloutStatus.READY); - rolloutManagement.checkReadyRollouts(0); + rolloutManagement.handleRollouts(); myRollout = rolloutManagement.findRolloutById(myRollout.getId()).get(); assertThat(myRollout.getStatus()).isEqualTo(RolloutStatus.READY); @@ -1105,7 +1123,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { rolloutManagement.startRollout(myRollout.getId()); // Run here, because scheduler is disabled during tests - rolloutManagement.checkStartingRollouts(0); + rolloutManagement.handleRollouts(); final SuccessConditionRolloutStatus conditionRolloutTargetCount = new SuccessConditionRolloutStatus( RolloutStatus.RUNNING); @@ -1141,7 +1159,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { // schedule rollout auto start into the future rolloutManagement.updateRollout( entityFactory.rollout().update(myRollout.getId()).startAt(System.currentTimeMillis() + 60000)); - rolloutManagement.checkReadyRollouts(0); + rolloutManagement.handleRollouts(); // rollout should not have been started myRollout = rolloutManagement.findRolloutById(myRollout.getId()).get(); @@ -1150,13 +1168,13 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { // schedule to now rolloutManagement .updateRollout(entityFactory.rollout().update(myRollout.getId()).startAt(System.currentTimeMillis())); - rolloutManagement.checkReadyRollouts(0); + rolloutManagement.handleRollouts(); myRollout = rolloutManagement.findRolloutById(myRollout.getId()).get(); assertThat(myRollout.getStatus()).isEqualTo(RolloutStatus.STARTING); // Run here, because scheduler is disabled during tests - rolloutManagement.checkStartingRollouts(0); + rolloutManagement.handleRollouts(); final SuccessConditionRolloutStatus conditionRolloutTargetCount = new SuccessConditionRolloutStatus( RolloutStatus.RUNNING); @@ -1212,7 +1230,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { TimeUnit.SECONDS.sleep(1); testdataFactory.createTargets(10, rolloutName + "-notIn-", rolloutName); - rolloutManagement.fillRolloutGroupsWithTargets(myRollout.getId()); + rolloutManagement.handleRollouts(); myRollout = rolloutManagement.findRolloutById(myRollout.getId()).get(); assertThat(myRollout.getStatus()).isEqualTo(RolloutStatus.READY); @@ -1297,6 +1315,50 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { } + @Test + @Description("Verify Exception when a Rollout groups are queried for rollout that does not exist.") + public void findRolloutGroupsForRolloutThatDoesNotExist() throws Exception { + try { + rolloutGroupManagement.findAllRolloutGroupsWithDetailedStatus(1234L, pageReq); + fail("Was able to get Rollout group for rollout that does not exist."); + } catch (final EntityNotFoundException e) { + // OK + } + } + + @Test + @Description("Verify Exception when targets are queried for rollout group that does not exist.") + public void findRolloutGroupTargetsForGroupThatDoesNotExist() throws Exception { + try { + rolloutGroupManagement.findRolloutGroupTargets(1234L, pageReq); + fail("Was able to get Rollout group targets for rollout group that does not exist."); + } catch (final EntityNotFoundException e) { + // OK + } + } + + @Test + @Description("Verify Exception when targets are queried for rollout group that does not exist.") + public void findRolloutGroupTargetWithActionsForGroupThatDoesNotExist() throws Exception { + try { + rolloutGroupManagement.findAllTargetsWithActionStatus(pageReq, 1234L); + fail("Was able to get Rollout group targets for rollout group that does not exist."); + } catch (final EntityNotFoundException e) { + // OK + } + } + + @Test + @Description("Verify Exception when targets are counted for rollout group that does not exist.") + public void countRolloutGroupTargetWithActionsForGroupThatDoesNotExist() throws Exception { + try { + rolloutGroupManagement.countTargetsOfRolloutsGroup(1234L); + fail("Was able to count Rollout group targets for rollout group that does not exist."); + } catch (final EntityNotFoundException e) { + // OK + } + } + @Test @Description("Verify the start of a Rollout does not work during creation phase.") public void createAndStartRolloutDuringCreationFails() throws Exception { @@ -1331,6 +1393,83 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { } + @Test + @ExpectEvents({ @Expect(type = RolloutDeletedEvent.class, count = 1), + @Expect(type = DistributionSetCreatedEvent.class, count = 1), + @Expect(type = TargetCreatedEvent.class, count = 25), @Expect(type = RolloutUpdatedEvent.class, count = 2), + @Expect(type = RolloutGroupCreatedEvent.class, count = 5), + @Expect(type = SoftwareModuleCreatedEvent.class, count = 3), + @Expect(type = RolloutGroupUpdatedEvent.class, count = 10) }) + public void deleteRolloutWhichHasNeverStartedIsHardDeleted() { + 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); + + // test + rolloutManagement.deleteRollout(createdRollout.getId()); + rolloutManagement.handleRollouts(); + + // verify + final JpaRollout deletedRollout = rolloutRepository.findOne(createdRollout.getId()); + assertThat(deletedRollout).isNull(); + assertThat(rolloutGroupRepository.count()).isZero(); + assertThat(rolloutTargetGroupRepository.count()).isZero(); + } + + @Test + @ExpectEvents({ @Expect(type = SoftwareModuleCreatedEvent.class, count = 3), + @Expect(type = RolloutGroupUpdatedEvent.class, count = 16), + @Expect(type = RolloutUpdatedEvent.class, count = 6), + @Expect(type = DistributionSetCreatedEvent.class, count = 1), + @Expect(type = TargetCreatedEvent.class, count = 25), @Expect(type = TargetUpdatedEvent.class, count = 4), + @Expect(type = TargetAssignDistributionSetEvent.class, count = 2), + @Expect(type = RolloutGroupCreatedEvent.class, count = 5), + @Expect(type = ActionCreatedEvent.class, count = 10), @Expect(type = ActionUpdatedEvent.class, count = 2), + @Expect(type = RolloutDeletedEvent.class, count = 1) }) + public void deleteRolloutWhichHasBeenStartedBeforeIsSoftDeleted() { + 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, so it has active running actions and a group which + // has been started + rolloutManagement.startRollout(createdRollout.getId()); + rolloutManagement.handleRollouts(); + + // verify we have running actions + assertThat(actionRepository.findByRolloutIdAndStatus(pageReq, createdRollout.getId(), Status.RUNNING) + .getNumberOfElements()).isEqualTo(2); + + // test + rolloutManagement.deleteRollout(createdRollout.getId()); + rolloutManagement.handleRollouts(); + + // verify + final JpaRollout deletedRollout = rolloutRepository.findOne(createdRollout.getId()); + assertThat(deletedRollout).isNotNull(); + assertThat(deletedRollout.getStatus()).isEqualTo(RolloutStatus.DELETED); + assertThat(rolloutManagement.findAll(pageReq, true).getContent()).hasSize(1); + assertThat(rolloutManagement.findAll(pageReq, false).getContent()).hasSize(0); + assertThat(rolloutGroupManagement.findAllRolloutGroupsWithDetailedStatus(createdRollout.getId(), pageReq) + .getContent()).hasSize(amountGroups); + + // verify that all scheduled actions are deleted + assertThat(actionRepository.findByRolloutIdAndStatus(pageReq, deletedRollout.getId(), Status.SCHEDULED) + .getNumberOfElements()).isEqualTo(0); + // verify that all running actions keep running + assertThat(actionRepository.findByRolloutIdAndStatus(pageReq, deletedRollout.getId(), Status.RUNNING) + .getNumberOfElements()).isEqualTo(2); + + } + private RolloutGroupCreate generateRolloutGroup(final int index, final Integer percentage, final String targetFilter) { return entityFactory.rolloutGroup().create().name("Group" + index).description("Group" + index + "desc") @@ -1397,7 +1536,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { private int changeStatusForAllRunningActions(final Rollout rollout, final Status status) { final List runningActions = findActionsByRolloutAndStatus(rollout, Status.RUNNING); for (final Action action : runningActions) { - controllerManagament + controllerManagement .addUpdateActionStatus(entityFactory.actionStatus().create(action.getId()).status(status)); } return runningActions.size(); @@ -1408,7 +1547,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest { final List runningActions = findActionsByRolloutAndStatus(rollout, Status.RUNNING); assertThat(runningActions.size()).isGreaterThanOrEqualTo(amountOfTargetsToGetChanged); for (int i = 0; i < amountOfTargetsToGetChanged; i++) { - controllerManagament.addUpdateActionStatus( + controllerManagement.addUpdateActionStatus( entityFactory.actionStatus().create(runningActions.get(i).getId()).status(status)); } return runningActions.size(); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementSearchTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementSearchTest.java index c1f72e6a6..662d3cf5b 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementSearchTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementSearchTest.java @@ -96,7 +96,7 @@ public class TargetManagementSearchTest extends AbstractJpaIntegrationTest { final Long actionId = assignDistributionSet(installedSet.getId(), assignedC).getActions().get(0); // set one installed DS also - controllerManagament.addUpdateActionStatus( + controllerManagement.addUpdateActionStatus( entityFactory.actionStatus().create(actionId).status(Status.FINISHED).message("message")); assignDistributionSet(setA.getId(), installedC); @@ -663,7 +663,7 @@ public class TargetManagementSearchTest extends AbstractJpaIntegrationTest { List installedtargets = testdataFactory.createTargets(10, "assigned", "assigned"); // set on installed and assign another one - assignDistributionSet(installedSet, installedtargets).getActions().forEach(actionId -> controllerManagament + assignDistributionSet(installedSet, installedtargets).getActions().forEach(actionId -> controllerManagement .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.FINISHED))); assignDistributionSet(assignedSet, installedtargets); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java index 9eeb2c6ac..1354915b7 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java @@ -248,7 +248,7 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest { testData.put("test1", "testdata1"); targetManagement.createTarget(entityFactory.target().create().controllerId(controllerId)); - controllerManagament.updateControllerAttributes(controllerId, testData); + controllerManagement.updateControllerAttributes(controllerId, testData); final Target target = targetManagement.findTargetByControllerIDWithDetails(controllerId).get(); assertThat(target.getTargetInfo().getControllerAttributes()).as("Controller Attributes are wrong") @@ -279,11 +279,11 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest { Target target = createTargetWithAttributes("4711"); final long current = System.currentTimeMillis(); - controllerManagament.updateLastTargetQuery("4711", null); + controllerManagement.updateLastTargetQuery("4711", null); final DistributionSetAssignmentResult result = assignDistributionSet(set.getId(), "4711"); - controllerManagament.addUpdateActionStatus( + controllerManagement.addUpdateActionStatus( entityFactory.actionStatus().create(result.getActions().get(0)).status(Status.FINISHED)); assignDistributionSet(set2.getId(), "4711"); @@ -694,7 +694,7 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest { @Expect(type = TargetCreatedEvent.class, count = 0) public void targetCanBeReadWithOnlyReadTargetPermission() throws Exception { final String knownTargetControllerId = "readTarget"; - controllerManagament.findOrRegisterTargetIfItDoesNotexist(knownTargetControllerId, new URI("http://127.0.0.1")); + controllerManagement.findOrRegisterTargetIfItDoesNotexist(knownTargetControllerId, new URI("http://127.0.0.1")); securityRule.runAs(WithSpringAuthorityRule.withUser("bumlux", "READ_TARGET"), () -> { final Target findTargetByControllerID = targetManagement.findTargetByControllerID(knownTargetControllerId) diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/event/RepositoryEntityEventTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/event/RepositoryEntityEventTest.java index a305bb286..a975322a2 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/event/RepositoryEntityEventTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/event/RepositoryEntityEventTest.java @@ -17,6 +17,7 @@ import java.util.concurrent.TimeUnit; import org.assertj.core.api.Assertions; import org.eclipse.hawkbit.repository.event.TenantAwareEvent; import org.eclipse.hawkbit.repository.event.remote.DistributionSetDeletedEvent; +import org.eclipse.hawkbit.repository.event.remote.RolloutDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.SoftwareModuleDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent; @@ -27,6 +28,7 @@ import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent; import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.jpa.event.RepositoryEntityEventTest.RepositoryTestConfiguration; import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.Target; import org.junit.Before; @@ -90,6 +92,30 @@ public class RepositoryEntityEventTest extends AbstractJpaIntegrationTest { assertThat(targetDeletedEvent.getEntityId()).isEqualTo(createdTarget.getId()); } + @Test + @Description("Verifies that the rollout deleted event is published when a rollout has been deleted") + public void rolloutDeletedEventIsPublished() throws InterruptedException { + 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 = testdataFactory.createDistributionSet("dsFor" + rolloutName); + testdataFactory.createTargets(amountTargetsForRollout, targetPrefixName + "-", targetPrefixName); + + final Rollout createdRollout = createRolloutByVariables(rolloutName, "desc", amountGroups, + "controllerId==" + targetPrefixName + "-*", distributionSet, successCondition, errorCondition); + + rolloutManagement.deleteRollout(createdRollout.getId()); + rolloutManagement.handleRollouts(); + + final RolloutDeletedEvent rolloutDeletedEvent = eventListener.waitForEvent(RolloutDeletedEvent.class, 1, + TimeUnit.SECONDS); + assertThat(rolloutDeletedEvent).isNotNull(); + assertThat(rolloutDeletedEvent.getEntityId()).isEqualTo(createdRollout.getId()); + } + @Test @Description("Verifies that the distribution set created event is published when a distribution set has been created") public void distributionSetCreatedEventIsPublished() throws InterruptedException { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLActionFieldsTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLActionFieldsTest.java index 9d5a7ba6a..0b32a854a 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLActionFieldsTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLActionFieldsTest.java @@ -18,6 +18,7 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaAction; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.ActionType; +import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.junit.Before; import org.junit.Test; @@ -43,14 +44,17 @@ public class RSQLActionFieldsTest extends AbstractJpaIntegrationTest { action = new JpaAction(); action.setActionType(ActionType.SOFT); action.setDistributionSet(dsA); - target.addAction(action); action.setTarget(target); + action.setStatus(Status.RUNNING); + target.addAction(action); + actionRepository.save(action); for (int i = 0; i < 10; i++) { final JpaAction newAction = new JpaAction(); newAction.setActionType(ActionType.SOFT); newAction.setDistributionSet(dsA); newAction.setActive(i % 2 == 0); + newAction.setStatus(Status.RUNNING); newAction.setTarget(target); actionRepository.save(newAction); target.addAction(newAction); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLTargetFieldTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLTargetFieldTest.java index 26d92a652..21788a6bc 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLTargetFieldTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLTargetFieldTest.java @@ -48,13 +48,13 @@ public class RSQLTargetFieldTest extends AbstractJpaIntegrationTest { target = targetManagement.createTarget(entityFactory.target().create().controllerId("targetId123") .name("targetName123").description("targetDesc123")); attributes.put("revision", "1.1"); - target = controllerManagament.updateControllerAttributes(target.getControllerId(), attributes); + target = controllerManagement.updateControllerAttributes(target.getControllerId(), attributes); target2 = targetManagement .createTarget(entityFactory.target().create().controllerId("targetId1234").description("targetId1234")); attributes.put("revision", "1.2"); Thread.sleep(1); - target2 = controllerManagament.updateControllerAttributes(target2.getControllerId(), attributes); + target2 = controllerManagement.updateControllerAttributes(target2.getControllerId(), attributes); testdataFactory.createTarget("targetId1235"); testdataFactory.createTarget("targetId1236"); diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/TestConfiguration.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/TestConfiguration.java index 5b455152c..0a0671a08 100644 --- a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/TestConfiguration.java +++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/TestConfiguration.java @@ -55,6 +55,8 @@ import org.springframework.context.annotation.Profile; import org.springframework.context.event.SimpleApplicationEventMulticaster; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.data.domain.AuditorAware; +import org.springframework.integration.support.locks.DefaultLockRegistry; +import org.springframework.integration.support.locks.LockRegistry; import org.springframework.messaging.converter.MessageConverter; import org.springframework.scheduling.annotation.AsyncConfigurer; import org.springframework.security.concurrent.DelegatingSecurityContextExecutorService; @@ -73,6 +75,11 @@ import org.springframework.util.AntPathMatcher; @EnableAutoConfiguration public class TestConfiguration implements AsyncConfigurer { + @Bean + public LockRegistry lockRegistry() { + return new DefaultLockRegistry(); + } + @Bean public SecurityTokenGenerator securityTokenGenerator() { return new SecurityTokenGenerator(); diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/AbstractIntegrationTest.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/AbstractIntegrationTest.java index 54d1ab57c..768e5fd32 100644 --- a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/AbstractIntegrationTest.java +++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/AbstractIntegrationTest.java @@ -152,9 +152,6 @@ public abstract class AbstractIntegrationTest implements EnvironmentAware { @Autowired protected WebApplicationContext context; - @Autowired - protected ControllerManagement controllerManagament; - @Autowired protected AuditingHandler auditingHandler; @@ -269,10 +266,10 @@ public abstract class AbstractIntegrationTest implements EnvironmentAware { .next(); Action savedAction = deploymentManagement.findActiveActionsByTarget(savedTarget.getControllerId()).get(0); - savedAction = controllerManagament.addUpdateActionStatus( + savedAction = controllerManagement.addUpdateActionStatus( entityFactory.actionStatus().create(savedAction.getId()).status(Action.Status.RUNNING)); - return controllerManagament.addUpdateActionStatus( + return controllerManagement.addUpdateActionStatus( entityFactory.actionStatus().create(savedAction.getId()).status(Action.Status.FINISHED)); } @@ -289,7 +286,7 @@ public abstract class AbstractIntegrationTest implements EnvironmentAware { conditions); // Run here, because Scheduler is disabled during tests - rolloutManagement.fillRolloutGroupsWithTargets(rollout.getId()); + rolloutManagement.handleRollouts(); return rolloutManagement.findRolloutById(rollout.getId()).get(); } diff --git a/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/exception/ResponseExceptionHandler.java b/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/exception/ResponseExceptionHandler.java index c0ebb4e59..d34c0631d 100644 --- a/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/exception/ResponseExceptionHandler.java +++ b/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/exception/ResponseExceptionHandler.java @@ -71,6 +71,7 @@ public class ResponseExceptionHandler { ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_INVALID_TARGET_ADDRESS, HttpStatus.BAD_REQUEST); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_CONSTRAINT_VIOLATION, HttpStatus.BAD_REQUEST); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_OPERATION_NOT_SUPPORTED, HttpStatus.GONE); + ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_CONCURRENT_MODIFICATION, HttpStatus.CONFLICT); } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/details/ArtifactDetailsLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/details/ArtifactDetailsLayout.java index 21f806b84..2ff15d8ae 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/details/ArtifactDetailsLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/details/ArtifactDetailsLayout.java @@ -240,7 +240,7 @@ public class ArtifactDetailsLayout extends VerticalLayout { .getItemProperty(PROVIDED_FILE_NAME).getValue(); final Button deleteIcon = SPUIComponentProvider.getButton( fileName + "-" + UIComponentIdProvider.UPLOAD_FILE_DELETE_ICON, "", - SPUILabelDefinitions.DISCARD, ValoTheme.BUTTON_TINY + " " + "redicon", true, + SPUILabelDefinitions.DISCARD, ValoTheme.BUTTON_TINY + " " + "blueicon", true, FontAwesome.TRASH_O, SPUIButtonStyleSmallNoBorder.class); deleteIcon.setData(itemId); deleteIcon.addClickListener(event -> confirmAndDeleteArtifact((Long) itemId, fileName)); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/footer/UploadViewConfirmationWindowLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/footer/UploadViewConfirmationWindowLayout.java index 52fac3e20..88df6a335 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/footer/UploadViewConfirmationWindowLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/footer/UploadViewConfirmationWindowLayout.java @@ -14,6 +14,7 @@ import java.util.Map; import java.util.Set; import org.eclipse.hawkbit.repository.SoftwareManagement; +import org.eclipse.hawkbit.repository.model.SoftwareModuleType; import org.eclipse.hawkbit.ui.artifacts.event.UploadArtifactUIEvent; import org.eclipse.hawkbit.ui.artifacts.state.ArtifactUploadState; import org.eclipse.hawkbit.ui.artifacts.state.CustomFile; @@ -238,9 +239,8 @@ public class UploadViewConfirmationWindowLayout extends AbstractConfirmationWind private void deleteSMtypeAll(final ConfirmationTab tab) { final int deleteSWModuleTypeCount = artifactUploadState.getSelectedDeleteSWModuleTypes().size(); for (final String swModuleTypeName : artifactUploadState.getSelectedDeleteSWModuleTypes()) { - - softwareManagement.deleteSoftwareModuleType( - softwareManagement.findSoftwareModuleTypeByName(swModuleTypeName).get().getId()); + softwareManagement.findSoftwareModuleTypeByName(swModuleTypeName).map(SoftwareModuleType::getId) + .ifPresent(softwareManagement::deleteSoftwareModuleType); } addToConsolitatedMsg(FontAwesome.TASKS.getHtml() + SPUILabelDefinitions.HTML_SPACE + i18n.get("message.sw.module.type.delete", new Object[] { deleteSWModuleTypeCount })); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleAddUpdateWindow.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleAddUpdateWindow.java index 06112648b..a8c43840b 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleAddUpdateWindow.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleAddUpdateWindow.java @@ -8,11 +8,14 @@ */ package org.eclipse.hawkbit.ui.artifacts.smtable; +import java.util.Optional; + import org.apache.commons.lang3.StringUtils; import org.eclipse.hawkbit.repository.EntityFactory; import org.eclipse.hawkbit.repository.SoftwareManagement; import org.eclipse.hawkbit.repository.builder.SoftwareModuleCreate; import org.eclipse.hawkbit.repository.model.SoftwareModule; +import org.eclipse.hawkbit.repository.model.SoftwareModuleType; import org.eclipse.hawkbit.ui.artifacts.event.SoftwareModuleEvent; import org.eclipse.hawkbit.ui.common.CommonDialogWindow; import org.eclipse.hawkbit.ui.common.CommonDialogWindow.SaveDialogCloseListener; @@ -26,7 +29,6 @@ import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil; import org.eclipse.hawkbit.ui.utils.I18N; import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; import org.eclipse.hawkbit.ui.utils.SPUILabelDefinitions; -import org.eclipse.hawkbit.ui.utils.SpringContextHelper; import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; import org.eclipse.hawkbit.ui.utils.UINotification; import org.vaadin.addons.lazyquerycontainer.BeanQueryFactory; @@ -116,6 +118,56 @@ public class SoftwareModuleAddUpdateWindow extends CustomComponent { public boolean canWindowSaveOrUpdate() { return editSwModule || !isDuplicate(); } + + private void addNewBaseSoftware() { + final String name = HawkbitCommonUtil.trimAndNullIfEmpty(nameTextField.getValue()); + final String version = HawkbitCommonUtil.trimAndNullIfEmpty(versionTextField.getValue()); + final String vendor = HawkbitCommonUtil.trimAndNullIfEmpty(vendorTextField.getValue()); + final String description = HawkbitCommonUtil.trimAndNullIfEmpty(descTextArea.getValue()); + final String type = typeComboBox.getValue() != null ? typeComboBox.getValue().toString() : null; + + final SoftwareModuleCreate softwareModule = entityFactory.softwareModule().create() + .type(softwareManagement.findSoftwareModuleTypeByName(type).get()).name(name).version(version) + .description(description).vendor(vendor); + + final SoftwareModule newSoftwareModule = softwareManagement.createSoftwareModule(softwareModule); + + if (newSoftwareModule != null) { + eventBus.publish(this, new SoftwareModuleEvent(BaseEntityEventType.ADD_ENTITY, newSoftwareModule)); + uiNotifcation.displaySuccess(i18n.get("message.save.success", + new Object[] { newSoftwareModule.getName() + ":" + newSoftwareModule.getVersion() })); + } + } + + private boolean isDuplicate() { + final String name = nameTextField.getValue(); + final String version = versionTextField.getValue(); + final String type = typeComboBox.getValue() != null ? typeComboBox.getValue().toString() : null; + + final Optional moduleType = softwareManagement.findSoftwareModuleTypeByName(type) + .map(SoftwareModuleType::getId); + if (moduleType.isPresent() && softwareManagement + .findSoftwareModuleByNameAndVersion(name, version, moduleType.get()).isPresent()) { + uiNotifcation.displayValidationError( + i18n.get("message.duplicate.softwaremodule", new Object[] { name, version })); + return true; + } + return false; + } + + /** + * updates a softwareModule + */ + private void updateSwModule() { + final SoftwareModule newSWModule = softwareManagement.updateSoftwareModule(entityFactory.softwareModule() + .update(baseSwModuleId).description(descTextArea.getValue()).vendor(vendorTextField.getValue())); + if (newSWModule != null) { + uiNotifcation.displaySuccess(i18n.get("message.save.success", + new Object[] { newSWModule.getName() + ":" + newSWModule.getVersion() })); + + eventBus.publish(this, new SoftwareModuleEvent(BaseEntityEventType.UPDATED_ENTITY, newSWModule)); + } + } } /** @@ -217,56 +269,6 @@ public class SoftwareModuleAddUpdateWindow extends CustomComponent { return window; } - private void addNewBaseSoftware() { - final String name = HawkbitCommonUtil.trimAndNullIfEmpty(nameTextField.getValue()); - final String version = HawkbitCommonUtil.trimAndNullIfEmpty(versionTextField.getValue()); - final String vendor = HawkbitCommonUtil.trimAndNullIfEmpty(vendorTextField.getValue()); - final String description = HawkbitCommonUtil.trimAndNullIfEmpty(descTextArea.getValue()); - final String type = typeComboBox.getValue() != null ? typeComboBox.getValue().toString() : null; - - final SoftwareModuleCreate softwareModule = entityFactory.softwareModule().create() - .type(softwareManagement.findSoftwareModuleTypeByName(type).get()).name(name).version(version) - .description(description).vendor(vendor); - - final SoftwareModule newSoftwareModule = softwareManagement.createSoftwareModule(softwareModule); - - if (newSoftwareModule != null) { - eventBus.publish(this, new SoftwareModuleEvent(BaseEntityEventType.ADD_ENTITY, newSoftwareModule)); - uiNotifcation.displaySuccess(i18n.get("message.save.success", - new Object[] { newSoftwareModule.getName() + ":" + newSoftwareModule.getVersion() })); - } - } - - private boolean isDuplicate() { - final String name = nameTextField.getValue(); - final String version = versionTextField.getValue(); - final String type = typeComboBox.getValue() != null ? typeComboBox.getValue().toString() : null; - - final SoftwareManagement swMgmtService = SpringContextHelper.getBean(SoftwareManagement.class); - - if (swMgmtService.findSoftwareModuleByNameAndVersion(name, version, - swMgmtService.findSoftwareModuleTypeByName(type).get().getId()).isPresent()) { - uiNotifcation.displayValidationError( - i18n.get("message.duplicate.softwaremodule", new Object[] { name, version })); - return true; - } - return false; - } - - /** - * updates a softwareModule - */ - private void updateSwModule() { - final SoftwareModule newSWModule = softwareManagement.updateSoftwareModule(entityFactory.softwareModule() - .update(baseSwModuleId).description(descTextArea.getValue()).vendor(vendorTextField.getValue())); - if (newSWModule != null) { - uiNotifcation.displaySuccess(i18n.get("message.save.success", - new Object[] { newSWModule.getName() + ":" + newSWModule.getVersion() })); - - eventBus.publish(this, new SoftwareModuleEvent(BaseEntityEventType.UPDATED_ENTITY, newSWModule)); - } - } - /** * fill the data of a softwareModule in the content of the window */ @@ -275,16 +277,17 @@ public class SoftwareModuleAddUpdateWindow extends CustomComponent { return; } editSwModule = Boolean.TRUE; - final SoftwareModule swModule = softwareManagement.findSoftwareModuleById(baseSwModuleId).get(); - nameTextField.setValue(swModule.getName()); - versionTextField.setValue(swModule.getVersion()); - vendorTextField.setValue(HawkbitCommonUtil.trimAndNullIfEmpty(swModule.getVendor())); - descTextArea.setValue(HawkbitCommonUtil.trimAndNullIfEmpty(swModule.getDescription())); + softwareManagement.findSoftwareModuleById(baseSwModuleId).ifPresent(swModule -> { + nameTextField.setValue(swModule.getName()); + versionTextField.setValue(swModule.getVersion()); + vendorTextField.setValue(HawkbitCommonUtil.trimAndNullIfEmpty(swModule.getVendor())); + descTextArea.setValue(HawkbitCommonUtil.trimAndNullIfEmpty(swModule.getDescription())); - if (swModule.getType().isDeleted()) { - typeComboBox.addItem(swModule.getType().getName()); - } - typeComboBox.setValue(swModule.getType().getName()); + if (swModule.getType().isDeleted()) { + typeComboBox.addItem(swModule.getType().getName()); + } + typeComboBox.setValue(swModule.getType().getName()); + }); } public FormLayout getFormLayout() { diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleDetails.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleDetails.java index 674c1f01f..3ca243f25 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleDetails.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleDetails.java @@ -201,9 +201,8 @@ public class SoftwareModuleDetails extends AbstractNamedVersionedEntityTableDeta @Override protected void showMetadata(final ClickEvent event) { - final SoftwareModule swmodule = softwareManagement.findSoftwareModuleById(getSelectedBaseEntityId()).get(); - /* display the window */ - UI.getCurrent().addWindow(swMetadataPopupLayout.getWindow(swmodule, null)); + softwareManagement.findSoftwareModuleById(getSelectedBaseEntityId()) + .ifPresent(swmodule -> UI.getCurrent().addWindow(swMetadataPopupLayout.getWindow(swmodule, null))); } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleTable.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleTable.java index 5fc3f4f47..feb93a2f5 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleTable.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleTable.java @@ -260,9 +260,8 @@ public class SoftwareModuleTable extends AbstractNamedVersionTable UI.getCurrent().addWindow(swMetadataPopupLayout.getWindow(swmodule, null))); } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtype/SMTypeFilterButtonClick.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtype/SMTypeFilterButtonClick.java index 6b0c25e00..582e6bd07 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtype/SMTypeFilterButtonClick.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtype/SMTypeFilterButtonClick.java @@ -11,7 +11,6 @@ package org.eclipse.hawkbit.ui.artifacts.smtype; import java.io.Serializable; import org.eclipse.hawkbit.repository.SoftwareManagement; -import org.eclipse.hawkbit.repository.model.SoftwareModuleType; import org.eclipse.hawkbit.ui.artifacts.event.SMFilterEvent; import org.eclipse.hawkbit.ui.artifacts.state.ArtifactUploadState; import org.eclipse.hawkbit.ui.common.filterlayout.AbstractFilterSingleButtonClick; @@ -48,10 +47,11 @@ public class SMTypeFilterButtonClick extends AbstractFilterSingleButtonClick imp @Override protected void filterClicked(final Button clickedButton) { - final SoftwareModuleType softwareModuleType = softwareManagement - .findSoftwareModuleTypeByName(clickedButton.getData().toString()).get(); - artifactUploadState.getSoftwareModuleFilters().setSoftwareModuleType(softwareModuleType); - eventBus.publish(this, SMFilterEvent.FILTER_BY_TYPE); + softwareManagement.findSoftwareModuleTypeByName(clickedButton.getData().toString()) + .ifPresent(softwareModuleType -> { + artifactUploadState.getSoftwareModuleFilters().setSoftwareModuleType(softwareModuleType); + eventBus.publish(this, SMFilterEvent.FILTER_BY_TYPE); + }); } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadConfirmationWindow.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadConfirmationWindow.java index 151c2a3b3..edd357923 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadConfirmationWindow.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadConfirmationWindow.java @@ -248,7 +248,7 @@ public class UploadConfirmationWindow implements Button.ClickListener { newItem.getItemProperty(SIZE).setValue(customFile.getFileSize()); final Button deleteIcon = SPUIComponentProvider.getButton( UIComponentIdProvider.UPLOAD_DELETE_ICON + "-" + itemId, "", SPUILabelDefinitions.DISCARD, - ValoTheme.BUTTON_TINY + " " + "redicon", true, FontAwesome.TRASH_O, + ValoTheme.BUTTON_TINY + " " + "blueicon", true, FontAwesome.TRASH_O, SPUIButtonStyleSmallNoBorder.class); deleteIcon.addClickListener(this); deleteIcon.setData(itemId); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadStatusInfoWindow.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadStatusInfoWindow.java index 0d3e9e989..d4c141049 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadStatusInfoWindow.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadStatusInfoWindow.java @@ -143,10 +143,8 @@ public class UploadStatusInfoWindow extends Window { case RECEIVE_UPLOAD: uploadRecevied(uploadStatus.getFileName(), uploadStatus.getSoftwareModule()); break; - case UPLOAD_FINISHED: - case ABORT_UPLOAD: - case UPLOAD_FAILED: default: + break; } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/CommonDialogWindow.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/CommonDialogWindow.java index 93a6d2e9e..29c60e57b 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/CommonDialogWindow.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/CommonDialogWindow.java @@ -57,6 +57,7 @@ import com.vaadin.ui.GridLayout; import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.Label; import com.vaadin.ui.Link; +import com.vaadin.ui.TabSheet; import com.vaadin.ui.Table; import com.vaadin.ui.VerticalLayout; import com.vaadin.ui.Window; @@ -90,11 +91,11 @@ public class CommonDialogWindow extends Window { private final ClickListener cancelButtonClickListener; - private final ClickListener closeClickListener = event -> onCloseEvent(event); + private final ClickListener closeClickListener = this::onCloseEvent; private final transient Map orginalValues; - private final List> allComponents; + private List> allComponents; private final I18N i18n; @@ -207,7 +208,7 @@ public class CommonDialogWindow extends Window { addStyleName("marginTop"); } - if (null != content) { + if (content != null) { mainLayout.addComponent(content); mainLayout.setExpandRatio(content, 1.0F); } @@ -391,6 +392,16 @@ public class CommonDialogWindow extends Window { if (c instanceof FlexibleOptionGroupItemComponent) { components.add(((FlexibleOptionGroupItemComponent) c).getOwner()); } + + if (c instanceof TabSheet) { + final TabSheet tabSheet = (TabSheet) c; + for (final Iterator i = tabSheet.iterator(); i.hasNext();) { + final Component component = i.next(); + if (component instanceof AbstractLayout) { + components.addAll(getAllComponents((AbstractLayout) component)); + } + } + } } return components; } @@ -560,4 +571,15 @@ public class CommonDialogWindow extends Window { void saveOrUpdate(); } + /** + * Updates the field allComponents. All components existing on the given + * layout are added to the list of allComponents + * + * @param layout + * AbstractLayout + */ + public void updateAllComponents(final AbstractLayout layout) { + allComponents = getAllComponents(layout); + } + } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/ConfirmationDialog.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/ConfirmationDialog.java index 652a1e526..cb4bb6d51 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/ConfirmationDialog.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/ConfirmationDialog.java @@ -31,7 +31,6 @@ import com.vaadin.ui.themes.ValoTheme; * */ public class ConfirmationDialog implements Button.ClickListener { - /** Serial version UID. */ private static final long serialVersionUID = 1L; /** The confirmation callback. */ private transient ConfirmationDialogCallback callback; diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/detailslayout/SoftwareModuleMetadatadetailslayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/detailslayout/SoftwareModuleMetadatadetailslayout.java index 9c9cf4b13..7bfb1920e 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/detailslayout/SoftwareModuleMetadatadetailslayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/detailslayout/SoftwareModuleMetadatadetailslayout.java @@ -32,11 +32,9 @@ import com.vaadin.ui.UI; import com.vaadin.ui.themes.ValoTheme; /** - * * SoftwareModule Metadata details layout. * */ - @SpringComponent @UIScope public class SoftwareModuleMetadatadetailslayout extends Table { @@ -174,10 +172,8 @@ public class SoftwareModuleMetadatadetailslayout extends Table { } private void showMetadataDetails(final Long selectedSWModuleId, final String metadataKey) { - final SoftwareModule swmodule = softwareManagement.findSoftwareModuleById(selectedSWModuleId).get(); - /* display the window */ - UI.getCurrent() - .addWindow(swMetadataPopupLayout.getWindow(swmodule, entityFactory.generateMetadata(metadataKey, ""))); + softwareManagement.findSoftwareModuleById(selectedSWModuleId).ifPresent(swmodule -> UI.getCurrent() + .addWindow(swMetadataPopupLayout.getWindow(swmodule, entityFactory.generateMetadata(metadataKey, "")))); } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/AbstractGridHeader.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/AbstractGridHeader.java index 67a3257ab..4db6143d7 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/AbstractGridHeader.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/AbstractGridHeader.java @@ -155,15 +155,18 @@ public abstract class AbstractGridHeader extends VerticalLayout { searchResetIcon.togleIcon(FontAwesome.TIMES); searchResetIcon.setData(Boolean.TRUE); searchField.removeStyleName(SPUIDefinitions.FILTER_BOX_HIDE); + searchField.setVisible(true); searchField.focus(); } private void closeSearchTextField() { searchField.setValue(""); searchField.addStyleName(SPUIDefinitions.FILTER_BOX_HIDE); + searchField.setVisible(false); searchResetIcon.removeStyleName(SPUIDefinitions.FILTER_RESET_ICON); searchResetIcon.togleIcon(FontAwesome.SEARCH); searchResetIcon.setData(Boolean.FALSE); + resetSearchText(); } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/AbstractGridLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/AbstractGridLayout.java index c3fda17fe..711f9712c 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/AbstractGridLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/AbstractGridLayout.java @@ -88,4 +88,9 @@ public abstract class AbstractGridLayout extends VerticalLayout { * @return count message */ protected abstract Label getCountMessageLabel(); + + public AbstractGrid getGrid() { + return grid; + } + } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/dd/client/criteria/ViewComponentClientCriterion.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/dd/client/criteria/ViewComponentClientCriterion.java index 27d9f8718..e83ff5eec 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/dd/client/criteria/ViewComponentClientCriterion.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/dd/client/criteria/ViewComponentClientCriterion.java @@ -51,7 +51,7 @@ public final class ViewComponentClientCriterion extends VAcceptCriterion { public static final String HINT_AREA_STYLE = "show-drop-hint"; @Override - protected boolean accept(VDragEvent drag, UIDL configuration) { + protected boolean accept(final VDragEvent drag, final UIDL configuration) { // 1. check if this component is responsible for the drag source: if (!isValidDragSource(drag, configuration)) { return false; @@ -81,14 +81,14 @@ public final class ViewComponentClientCriterion extends VAcceptCriterion { // Exception squid:S2221 - This code is trans-coded to JavaScript, hence // Exception semantics changes @SuppressWarnings({ "squid:S1166", "squid:S2221" }) - boolean isValidDragSource(VDragEvent drag, UIDL configuration) { + boolean isValidDragSource(final VDragEvent drag, final UIDL configuration) { try { - String dragSource = drag.getTransferable().getDragSource().getWidget().getElement().getId(); - String dragSourcePrefix = configuration.getStringAttribute(DRAG_SOURCE); + final String dragSource = drag.getTransferable().getDragSource().getWidget().getElement().getId(); + final String dragSourcePrefix = configuration.getStringAttribute(DRAG_SOURCE); if (dragSource.startsWith(dragSourcePrefix)) { return true; } - } catch (Exception e) { + } catch (final Exception e) { // log and continue LOGGER.log(Level.SEVERE, "Error verifying drag source: " + e.getLocalizedMessage()); } @@ -106,19 +106,20 @@ public final class ViewComponentClientCriterion extends VAcceptCriterion { // Exception squid:S1166 - Hide origin exception // Exception squid:S2221 - This code is trans-coded to JavaScript, hence // Exception semantics changes - @SuppressWarnings({ "squid:S1166", "squid:S2221" }) - void showDropTargetHints(UIDL configuration) { - int dropAreaCount = configuration.getIntAttribute(DROP_AREA_COUNT); + // Exception squid:S2629 - not supported by GWT + @SuppressWarnings({ "squid:S1166", "squid:S2221", "squid:S2629" }) + void showDropTargetHints(final UIDL configuration) { + final int dropAreaCount = configuration.getIntAttribute(DROP_AREA_COUNT); for (int dropAreaIndex = 0; dropAreaIndex < dropAreaCount; dropAreaIndex++) { try { - String dropArea = configuration.getStringAttribute(DROP_AREA + dropAreaIndex); + final String dropArea = configuration.getStringAttribute(DROP_AREA + dropAreaIndex); LOGGER.log(Level.FINE, "Hint Area: " + dropArea); - Element showHintFor = Document.get().getElementById(dropArea); + final Element showHintFor = Document.get().getElementById(dropArea); if (showHintFor != null) { showHintFor.addClassName(HINT_AREA_STYLE); } - } catch (Exception e) { + } catch (final Exception e) { // log and continue LOGGER.log(Level.SEVERE, "Error highlighting drop targets: " + e.getLocalizedMessage()); } @@ -140,20 +141,21 @@ public final class ViewComponentClientCriterion extends VAcceptCriterion { // Exception squid:S1166 - Hide origin exception // Exception squid:S2221 - This code is trans-coded to JavaScript, hence // Exception semantics changes - @SuppressWarnings({ "squid:S1166", "squid:S2221" }) - boolean isValidDropTarget(UIDL configuration) { + // Exception squid:S2629 - not supported by GWT + @SuppressWarnings({ "squid:S1166", "squid:S2221", "squid:S2629" }) + boolean isValidDropTarget(final UIDL configuration) { try { - String dropTarget = VDragAndDropManager.get().getCurrentDropHandler().getConnector().getWidget() + final String dropTarget = VDragAndDropManager.get().getCurrentDropHandler().getConnector().getWidget() .getElement().getId(); - int dropTargetCount = configuration.getIntAttribute(DROP_TARGET_COUNT); + final int dropTargetCount = configuration.getIntAttribute(DROP_TARGET_COUNT); for (int dropTargetIndex = 0; dropTargetIndex < dropTargetCount; dropTargetIndex++) { - String dropTargetPrefix = configuration.getStringAttribute(DROP_TARGET + dropTargetIndex); + final String dropTargetPrefix = configuration.getStringAttribute(DROP_TARGET + dropTargetIndex); LOGGER.log(Level.FINE, "Drop Target: " + dropTargetPrefix); if (dropTarget.startsWith(dropTargetPrefix)) { return true; } } - } catch (Exception e) { + } catch (final Exception e) { // log and continue LOGGER.log(Level.SEVERE, "Error verifying drop target: " + e.getLocalizedMessage()); } @@ -161,7 +163,7 @@ public final class ViewComponentClientCriterion extends VAcceptCriterion { } @Override - public boolean needsServerSideCheck(VDragEvent drag, UIDL criterioUIDL) { + public boolean needsServerSideCheck(final VDragEvent drag, final UIDL criterioUIDL) { // client-side only return false; } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/dstable/DistributionSetDetails.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/dstable/DistributionSetDetails.java index 750e9e906..0194c609a 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/dstable/DistributionSetDetails.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/dstable/DistributionSetDetails.java @@ -373,9 +373,11 @@ public class DistributionSetDetails extends AbstractNamedVersionedEntityTableDet if (assignedSWModule != null) { assignedSWModule.clear(); } - setSelectedBaseEntity( - distributionSetManagement.findDistributionSetByIdWithDetails(getSelectedBaseEntityId()).get()); - UI.getCurrent().access(this::populateModule); + + distributionSetManagement.findDistributionSetByIdWithDetails(getSelectedBaseEntityId()).ifPresent(set -> { + setSelectedBaseEntity(set); + UI.getCurrent().access(this::populateModule); + }); } } @@ -408,8 +410,7 @@ public class DistributionSetDetails extends AbstractNamedVersionedEntityTableDet @Override protected void showMetadata(final ClickEvent event) { - final DistributionSet ds = distributionSetManagement - .findDistributionSetByIdWithDetails(getSelectedBaseEntityId()).get(); - UI.getCurrent().addWindow(dsMetadataPopupLayout.getWindow(ds, null)); + distributionSetManagement.findDistributionSetByIdWithDetails(getSelectedBaseEntityId()) + .ifPresent(ds -> UI.getCurrent().addWindow(dsMetadataPopupLayout.getWindow(ds, null))); } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/dstable/DistributionSetTable.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/dstable/DistributionSetTable.java index cc4673da8..30ecc9bb9 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/dstable/DistributionSetTable.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/dstable/DistributionSetTable.java @@ -266,13 +266,14 @@ public class DistributionSetTable extends AbstractNamedVersionTable softwareModule = softwareManagement.findSoftwareModuleById(softwareModuleId); + + if (softwareModule.isPresent() && validSoftwareModule((Long) distId, softwareModule.get())) { final SoftwareModuleIdName softwareModuleIdName = new SoftwareModuleIdName(softwareModuleId, name.concat(":" + swVersion)); - publishAssignEvent((Long) distId, softwareModule); - handleSoftwareCase(map, softwareModule, softwareModuleIdName); - handleFirmwareCase(map, softwareModule, softwareModuleIdName); + publishAssignEvent((Long) distId, softwareModule.get()); + handleSoftwareCase(map, softwareModule.get(), softwareModuleIdName); + handleFirmwareCase(map, softwareModule.get(), softwareModuleIdName); } else { return; } @@ -328,14 +329,14 @@ public class DistributionSetTable extends AbstractNamedVersionTable ds = distributionSetManagement.findDistributionSetByIdWithDetails(distId); + if (!ds.isPresent() || !validateSoftwareModule(sm, ds.get())) { return false; } - if (distributionSetManagement.isDistributionSetInUse(ds.getId())) { - notification.displayValidationError( - i18n.get("message.error.notification.ds.target.assigned", ds.getName(), ds.getVersion())); + if (distributionSetManagement.isDistributionSetInUse(ds.get().getId())) { + notification.displayValidationError(i18n.get("message.error.notification.ds.target.assigned", + ds.get().getName(), ds.get().getVersion())); return false; } return true; @@ -492,8 +493,8 @@ public class DistributionSetTable extends AbstractNamedVersionTable UI.getCurrent().addWindow(dsMetadataPopupLayout.getWindow(ds, null))); } private String getNameAndVerion(final Object itemId) { diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/footer/DistributionsConfirmationWindowLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/footer/DistributionsConfirmationWindowLayout.java index b9df7b862..1fdba6373 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/footer/DistributionsConfirmationWindowLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/footer/DistributionsConfirmationWindowLayout.java @@ -17,6 +17,7 @@ import java.util.stream.Collectors; import org.eclipse.hawkbit.repository.DistributionSetManagement; import org.eclipse.hawkbit.repository.SoftwareManagement; +import org.eclipse.hawkbit.repository.model.SoftwareModuleType; import org.eclipse.hawkbit.ui.artifacts.event.SoftwareModuleEvent; import org.eclipse.hawkbit.ui.common.confirmwindow.layout.AbstractConfirmationWindowLayout; import org.eclipse.hawkbit.ui.common.confirmwindow.layout.ConfirmationTab; @@ -276,8 +277,8 @@ public class DistributionsConfirmationWindowLayout extends AbstractConfirmationW final int deleteSWModuleTypeCount = manageDistUIState.getSelectedDeleteSWModuleTypes().size(); for (final String swModuleTypeName : manageDistUIState.getSelectedDeleteSWModuleTypes()) { - softwareManagement.deleteSoftwareModuleType( - softwareManagement.findSoftwareModuleTypeByName(swModuleTypeName).get().getId()); + softwareManagement.findSoftwareModuleTypeByName(swModuleTypeName).map(SoftwareModuleType::getId) + .ifPresent(softwareManagement::deleteSoftwareModuleType); } addToConsolitatedMsg(FontAwesome.TASKS.getHtml() + SPUILabelDefinitions.HTML_SPACE + i18n.get("message.sw.module.type.delete", new Object[] { deleteSWModuleTypeCount })); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/smtable/SwModuleDetails.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/smtable/SwModuleDetails.java index 760f625a9..4928b3be5 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/smtable/SwModuleDetails.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/smtable/SwModuleDetails.java @@ -215,7 +215,7 @@ public class SwModuleDetails extends AbstractNamedVersionedEntityTableDetailsLay @Override protected void showMetadata(final ClickEvent event) { - final SoftwareModule swmodule = softwareManagement.findSoftwareModuleById(getSelectedBaseEntityId()).get(); - UI.getCurrent().addWindow(swMetadataPopupLayout.getWindow(swmodule, null)); + softwareManagement.findSoftwareModuleById(getSelectedBaseEntityId()) + .ifPresent(swmodule -> UI.getCurrent().addWindow(swMetadataPopupLayout.getWindow(swmodule, null))); } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/smtable/SwModuleTable.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/smtable/SwModuleTable.java index 8b9ebc1df..171e78306 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/smtable/SwModuleTable.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/smtable/SwModuleTable.java @@ -421,8 +421,8 @@ public class SwModuleTable extends AbstractNamedVersionTable UI.getCurrent().addWindow(swMetadataPopupLayout.getWindow(swmodule, null))); } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/smtype/DistSMTypeFilterButtonClick.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/smtype/DistSMTypeFilterButtonClick.java index 72aeec5f2..7c08e9a63 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/smtype/DistSMTypeFilterButtonClick.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/smtype/DistSMTypeFilterButtonClick.java @@ -11,7 +11,6 @@ package org.eclipse.hawkbit.ui.distributions.smtype; import java.io.Serializable; import org.eclipse.hawkbit.repository.SoftwareManagement; -import org.eclipse.hawkbit.repository.model.SoftwareModuleType; import org.eclipse.hawkbit.ui.artifacts.event.SMFilterEvent; import org.eclipse.hawkbit.ui.common.filterlayout.AbstractFilterSingleButtonClick; import org.eclipse.hawkbit.ui.distributions.state.ManageDistUIState; @@ -48,10 +47,10 @@ public class DistSMTypeFilterButtonClick extends AbstractFilterSingleButtonClick @Override protected void filterClicked(final Button clickedButton) { - final SoftwareModuleType smType = softwareManagement - .findSoftwareModuleTypeByName(clickedButton.getData().toString()).get(); - manageDistUIState.getSoftwareModuleFilters().setSoftwareModuleType(smType); - eventBus.publish(this, SMFilterEvent.FILTER_BY_TYPE); + softwareManagement.findSoftwareModuleTypeByName(clickedButton.getData().toString()).ifPresent(smType -> { + manageDistUIState.getSoftwareModuleFilters().setSoftwareModuleType(smType); + eventBus.publish(this, SMFilterEvent.FILTER_BY_TYPE); + }); } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/CreateOrUpdateFilterTable.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/CreateOrUpdateFilterTable.java index b1b5d4d58..402896307 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/CreateOrUpdateFilterTable.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/CreateOrUpdateFilterTable.java @@ -22,6 +22,7 @@ import org.eclipse.hawkbit.ui.utils.AssignInstalledDSTooltipGenerator; import org.eclipse.hawkbit.ui.utils.I18N; import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; import org.eclipse.hawkbit.ui.utils.SPUILabelDefinitions; +import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions; import org.eclipse.hawkbit.ui.utils.TableColumn; import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; import org.vaadin.addons.lazyquerycontainer.BeanQueryFactory; @@ -199,22 +200,22 @@ public class CreateOrUpdateFilterTable extends Table { label.setContentMode(ContentMode.HTML); if (targetStatus == TargetUpdateStatus.PENDING) { label.setDescription("Pending"); - label.setStyleName("statusIconYellow"); + label.setStyleName(SPUIStyleDefinitions.STATUS_ICON_YELLOW); label.setValue(FontAwesome.ADJUST.getHtml()); } else if (targetStatus == TargetUpdateStatus.REGISTERED) { label.setDescription("Registered"); - label.setStyleName("statusIconLightBlue"); + label.setStyleName(SPUIStyleDefinitions.STATUS_ICON_LIGHT_BLUE); label.setValue(FontAwesome.DOT_CIRCLE_O.getHtml()); } else if (targetStatus == TargetUpdateStatus.ERROR) { label.setDescription(i18n.get("label.error")); - label.setStyleName("statusIconRed"); + label.setStyleName(SPUIStyleDefinitions.STATUS_ICON_RED); label.setValue(FontAwesome.EXCLAMATION_CIRCLE.getHtml()); } else if (targetStatus == TargetUpdateStatus.IN_SYNC) { - label.setStyleName("statusIconGreen"); + label.setStyleName(SPUIStyleDefinitions.STATUS_ICON_GREEN); label.setDescription("In-Synch"); label.setValue(FontAwesome.CHECK_CIRCLE.getHtml()); } else if (targetStatus == TargetUpdateStatus.UNKNOWN) { - label.setStyleName("statusIconBlue"); + label.setStyleName(SPUIStyleDefinitions.STATUS_ICON_BLUE); label.setDescription(i18n.get("label.unknown")); label.setValue(FontAwesome.QUESTION_CIRCLE.getHtml()); } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterTable.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterTable.java index 68c4fce6e..2ba398b01 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterTable.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterTable.java @@ -15,7 +15,6 @@ import java.util.Map; import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; import org.eclipse.hawkbit.repository.TargetManagement; -import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.eclipse.hawkbit.ui.common.ConfirmationDialog; import org.eclipse.hawkbit.ui.components.ProxyDistribution; import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; @@ -162,7 +161,7 @@ public class TargetFilterTable extends Table { final Item row = getItem(itemId); final String tfName = (String) row.getItemProperty(SPUILabelDefinitions.NAME).getValue(); final Button deleteIcon = SPUIComponentProvider.getButton(getDeleteIconId(tfName), "", - SPUILabelDefinitions.DELETE_CUSTOM_FILTER, ValoTheme.BUTTON_TINY + " " + "redicon", true, + SPUILabelDefinitions.DELETE_CUSTOM_FILTER, ValoTheme.BUTTON_TINY + " " + "blueicon", true, FontAwesome.TRASH_O, SPUIButtonStyleSmallNoBorder.class); deleteIcon.setData(itemId); deleteIcon.addClickListener(this::onDelete); @@ -253,12 +252,12 @@ public class TargetFilterTable extends Table { private void onClickOfDetailButton(final ClickEvent event) { final String targetFilterName = (String) ((Button) event.getComponent()).getData(); - final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement - .findTargetFilterQueryByName(targetFilterName).get(); - filterManagementUIState.setFilterQueryValue(targetFilterQuery.getQuery()); - filterManagementUIState.setTfQuery(targetFilterQuery); - filterManagementUIState.setEditViewDisplayed(true); - eventBus.publish(this, CustomFilterUIEvent.TARGET_FILTER_DETAIL_VIEW); + targetFilterQueryManagement.findTargetFilterQueryByName(targetFilterName).ifPresent(targetFilterQuery -> { + filterManagementUIState.setFilterQueryValue(targetFilterQuery.getQuery()); + filterManagementUIState.setTfQuery(targetFilterQuery); + filterManagementUIState.setEditViewDisplayed(true); + eventBus.publish(this, CustomFilterUIEvent.TARGET_FILTER_DETAIL_VIEW); + }); } private void populateTableData() { diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/layouts/AbstractCreateUpdateTagLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/layouts/AbstractCreateUpdateTagLayout.java index 798d893b8..383d215c9 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/layouts/AbstractCreateUpdateTagLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/layouts/AbstractCreateUpdateTagLayout.java @@ -8,6 +8,7 @@ */ package org.eclipse.hawkbit.ui.layouts; +import java.util.Objects; import java.util.Optional; import org.apache.commons.lang3.StringUtils; @@ -299,15 +300,15 @@ public abstract class AbstractCreateUpdateTagLayout exten protected Color getColorForColorPicker() { final Optional targetTagSelected = tagManagement .findTargetTag(tagNameComboBox.getValue().toString()); - if (!targetTagSelected.isPresent()) { - final DistributionSetTag distTag = tagManagement - .findDistributionSetTag(tagNameComboBox.getValue().toString()).get(); - return distTag.getColour() != null ? ColorPickerHelper.rgbToColorConverter(distTag.getColour()) - : ColorPickerHelper.rgbToColorConverter(ColorPickerConstants.DEFAULT_COLOR); + if (targetTagSelected.isPresent()) { + return ColorPickerHelper.rgbToColorConverter(targetTagSelected.map(TargetTag::getColour) + .filter(Objects::nonNull).orElse(ColorPickerConstants.DEFAULT_COLOR)); } - return targetTagSelected.get().getColour() != null - ? ColorPickerHelper.rgbToColorConverter(targetTagSelected.get().getColour()) - : ColorPickerHelper.rgbToColorConverter(ColorPickerConstants.DEFAULT_COLOR); + + return ColorPickerHelper.rgbToColorConverter(tagManagement + .findDistributionSetTag(tagNameComboBox.getValue().toString()).map(DistributionSetTag::getColour) + .filter(Objects::nonNull).orElse(ColorPickerConstants.DEFAULT_COLOR)); + } private void tagNameChosen(final ValueChangeEvent event) { diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionHistoryTable.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionHistoryTable.java index 9c1fd3b8c..2b993dd69 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionHistoryTable.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/actionhistory/ActionHistoryTable.java @@ -11,6 +11,7 @@ package org.eclipse.hawkbit.ui.management.actionhistory; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Optional; import java.util.StringJoiner; import org.eclipse.hawkbit.repository.ActionStatusFields; @@ -104,10 +105,10 @@ public class ActionHistoryTable extends TreeTable { @EventBusListenerMethod(scope = EventScope.UI) void onEvent(final ManagementUIEvent mgmtUIEvent) { if (mgmtUIEvent == ManagementUIEvent.MAX_ACTION_HISTORY) { - UI.getCurrent().access(() -> createTableContentForMax()); + UI.getCurrent().access(this::createTableContentForMax); } if (mgmtUIEvent == ManagementUIEvent.MIN_ACTION_HISTORY) { - UI.getCurrent().access(() -> normalActionHistoryTable()); + UI.getCurrent().access(this::normalActionHistoryTable); } } @@ -431,8 +432,12 @@ public class ActionHistoryTable extends TreeTable { final Long actionId = (Long) item.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_ACTION_ID_HIDDEN) .getValue(); - final org.eclipse.hawkbit.repository.model.Action action = deploymentManagement - .findActionWithDetails(actionId).get(); + final Optional action = deploymentManagement.findActionWithDetails(actionId); + + if (!action.isPresent()) { + return; + } + final Pageable pageReq = new PageRequest(0, 1000, new Sort(Direction.DESC, ActionStatusFields.ID.getFieldName())); final Page actionStatusList; @@ -458,8 +463,9 @@ public class ActionHistoryTable extends TreeTable { */ childItem.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_ACTIVE_HIDDEN).setValue(""); - childItem.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_DIST).setValue( - action.getDistributionSet().getName() + ":" + action.getDistributionSet().getVersion()); + childItem.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_DIST) + .setValue(action.get().getDistributionSet().getName() + ":" + + action.get().getDistributionSet().getVersion()); childItem.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_DATETIME) .setValue(SPDateTimeUtil.getFormattedDate(actionStatus.getCreatedAt())); @@ -517,7 +523,6 @@ public class ActionHistoryTable extends TreeTable { label.setDescription(i18n.get(mapping.getDescriptionI18N())); label.setStyleName(mapping.getStyleName()); label.setValue(mapping.getIcon().getHtml()); - return label; } @@ -568,7 +573,7 @@ public class ActionHistoryTable extends TreeTable { label.setStyleName("statusIconActive"); } else if (SPUIDefinitions.IN_ACTIVE.equals(activeValue)) { if (endedWithError) { - label.setStyleName("statusIconRed"); + label.setStyleName(SPUIStyleDefinitions.STATUS_ICON_RED); } else { label.setStyleName("statusIconNeutral"); } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/dstable/DistributionAddUpdateWindowLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/dstable/DistributionAddUpdateWindowLayout.java index a1d58e9c7..cf0279bd2 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/dstable/DistributionAddUpdateWindowLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/dstable/DistributionAddUpdateWindowLayout.java @@ -128,6 +128,77 @@ public class DistributionAddUpdateWindowLayout extends CustomComponent { return !isDuplicate(); } + private void updateDistribution() { + + if (isDuplicate()) { + return; + } + final boolean isMigStepReq = reqMigStepCheckbox.getValue(); + final Long distSetTypeId = (Long) distsetTypeNameComboBox.getValue(); + + distributionSetManagement.findDistributionSetTypeById(distSetTypeId).ifPresent(type -> { + final DistributionSet currentDS = distributionSetManagement.updateDistributionSet( + entityFactory.distributionSet().update(editDistId).name(distNameTextField.getValue()) + .description(descTextArea.getValue()).version(distVersionTextField.getValue()) + .requiredMigrationStep(isMigStepReq).type(type)); + notificationMessage.displaySuccess(i18n.get("message.new.dist.save.success", + new Object[] { currentDS.getName(), currentDS.getVersion() })); + // update table row+details layout + eventBus.publish(this, new DistributionTableEvent(BaseEntityEventType.UPDATED_ENTITY, currentDS)); + }); + } + + /** + * Add new Distribution set. + */ + private void addNewDistribution() { + editDistribution = Boolean.FALSE; + + final String name = HawkbitCommonUtil.trimAndNullIfEmpty(distNameTextField.getValue()); + final String version = HawkbitCommonUtil.trimAndNullIfEmpty(distVersionTextField.getValue()); + final Long distSetTypeId = (Long) distsetTypeNameComboBox.getValue(); + final String desc = HawkbitCommonUtil.trimAndNullIfEmpty(descTextArea.getValue()); + final boolean isMigStepReq = reqMigStepCheckbox.getValue(); + + final DistributionSet newDist = distributionSetManagement.createDistributionSet( + entityFactory.distributionSet().create().name(name).version(version).description(desc) + .type(distributionSetManagement.findDistributionSetTypeById(distSetTypeId).get()) + .requiredMigrationStep(isMigStepReq)); + + eventBus.publish(this, new DistributionTableEvent(BaseEntityEventType.ADD_ENTITY, newDist)); + + notificationMessage.displaySuccess(i18n.get("message.new.dist.save.success", + new Object[] { newDist.getName(), newDist.getVersion() })); + + distributionSetTable.setValue(Sets.newHashSet(newDist.getId())); + } + + private boolean isDuplicate() { + final String name = distNameTextField.getValue(); + final String version = distVersionTextField.getValue(); + + final Optional existingDs = distributionSetManagement + .findDistributionSetByNameAndVersion(name, version); + /* + * Distribution should not exists with the same name & version. + * Display error message, when the "existingDs" is not null and it + * is add window (or) when the "existingDs" is not null and it is + * edit window and the distribution Id of the edit window is + * different then the "existingDs" + */ + if (existingDs.isPresent() && !existingDs.get().getId().equals(editDistId)) { + distNameTextField.addStyleName(SPUIStyleDefinitions.SP_TEXTFIELD_LAYOUT_ERROR_HIGHTLIGHT); + distVersionTextField.addStyleName(SPUIStyleDefinitions.SP_TEXTFIELD_LAYOUT_ERROR_HIGHTLIGHT); + notificationMessage.displayValidationError(i18n.get("message.duplicate.dist", + new Object[] { existingDs.get().getName(), existingDs.get().getVersion() })); + + return true; + } + + return false; + + } + } private void buildLayout() { @@ -199,79 +270,6 @@ public class DistributionAddUpdateWindowLayout extends CustomComponent { return tenantMetaData.getDefaultDsType(); } - /** - * Update Distribution. - */ - private void updateDistribution() { - - if (isDuplicate()) { - return; - } - final boolean isMigStepReq = reqMigStepCheckbox.getValue(); - final Long distSetTypeId = (Long) distsetTypeNameComboBox.getValue(); - - final DistributionSet currentDS = distributionSetManagement - .updateDistributionSet(entityFactory.distributionSet().update(editDistId) - .name(distNameTextField.getValue()).description(descTextArea.getValue()) - .version(distVersionTextField.getValue()).requiredMigrationStep(isMigStepReq) - .type(distributionSetManagement.findDistributionSetTypeById(distSetTypeId).get())); - notificationMessage.displaySuccess(i18n.get("message.new.dist.save.success", - new Object[] { currentDS.getName(), currentDS.getVersion() })); - // update table row+details layout - eventBus.publish(this, new DistributionTableEvent(BaseEntityEventType.UPDATED_ENTITY, currentDS)); - - } - - /** - * Add new Distribution set. - */ - private void addNewDistribution() { - editDistribution = Boolean.FALSE; - - final String name = HawkbitCommonUtil.trimAndNullIfEmpty(distNameTextField.getValue()); - final String version = HawkbitCommonUtil.trimAndNullIfEmpty(distVersionTextField.getValue()); - final Long distSetTypeId = (Long) distsetTypeNameComboBox.getValue(); - final String desc = HawkbitCommonUtil.trimAndNullIfEmpty(descTextArea.getValue()); - final boolean isMigStepReq = reqMigStepCheckbox.getValue(); - - final DistributionSet newDist = distributionSetManagement.createDistributionSet( - entityFactory.distributionSet().create().name(name).version(version).description(desc) - .type(distributionSetManagement.findDistributionSetTypeById(distSetTypeId).get()) - .requiredMigrationStep(isMigStepReq)); - - eventBus.publish(this, new DistributionTableEvent(BaseEntityEventType.ADD_ENTITY, newDist)); - - notificationMessage.displaySuccess( - i18n.get("message.new.dist.save.success", new Object[] { newDist.getName(), newDist.getVersion() })); - - distributionSetTable.setValue(Sets.newHashSet(newDist.getId())); - } - - private boolean isDuplicate() { - final String name = distNameTextField.getValue(); - final String version = distVersionTextField.getValue(); - - final Optional existingDs = distributionSetManagement.findDistributionSetByNameAndVersion(name, - version); - /* - * Distribution should not exists with the same name & version. Display - * error message, when the "existingDs" is not null and it is add window - * (or) when the "existingDs" is not null and it is edit window and the - * distribution Id of the edit window is different then the "existingDs" - */ - if (existingDs.isPresent() && !existingDs.get().getId().equals(editDistId)) { - distNameTextField.addStyleName(SPUIStyleDefinitions.SP_TEXTFIELD_LAYOUT_ERROR_HIGHTLIGHT); - distVersionTextField.addStyleName(SPUIStyleDefinitions.SP_TEXTFIELD_LAYOUT_ERROR_HIGHTLIGHT); - notificationMessage.displayValidationError(i18n.get("message.duplicate.dist", - new Object[] { existingDs.get().getName(), existingDs.get().getVersion() })); - - return true; - } - - return false; - - } - /** * clear all the fields. */ diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetBeanQuery.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetBeanQuery.java index 3203f803f..b9374247d 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetBeanQuery.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetBeanQuery.java @@ -23,7 +23,6 @@ import java.util.Map; import org.eclipse.hawkbit.repository.FilterParams; import org.eclipse.hawkbit.repository.OffsetBasedPageRequest; import org.eclipse.hawkbit.repository.TargetManagement; -import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; @@ -150,12 +149,10 @@ public class TargetBeanQuery extends AbstractBeanQuery { prxyTarget.setInstalledDistributionSet(null); prxyTarget.setAssignedDistributionSet(null); } else { - final Target target = getTargetManagement().findTargetByControllerIDWithDetails(targ.getControllerId()) - .get(); - final DistributionSet installedDistributionSet = target.getTargetInfo().getInstalledDistributionSet(); - prxyTarget.setInstalledDistributionSet(installedDistributionSet); - final DistributionSet assignedDistributionSet = target.getAssignedDistributionSet(); - prxyTarget.setAssignedDistributionSet(assignedDistributionSet); + getTargetManagement().findTargetByControllerIDWithDetails(targ.getControllerId()).ifPresent(target -> { + prxyTarget.setInstalledDistributionSet(target.getTargetInfo().getInstalledDistributionSet()); + prxyTarget.setAssignedDistributionSet(target.getAssignedDistributionSet()); + }); } prxyTarget.setUpdateStatus(targ.getTargetInfo().getUpdateStatus()); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetBulkTokenTags.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetBulkTokenTags.java index b8cbbc1a9..e9b1cf75c 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetBulkTokenTags.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetBulkTokenTags.java @@ -68,7 +68,7 @@ public class TargetBulkTokenTags extends AbstractTargetTagToken { protected void addAlreadySelectedTags() { for (final String tagName : managementUIState.getTargetTableFilters().getBulkUpload().getAssignedTagNames()) { - addNewToken(tagManagement.findTargetTag(tagName).get().getId()); + tagManagement.findTargetTag(tagName).map(TargetTag::getId).ifPresent(this::addNewToken); } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetTable.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetTable.java index 8abfb4044..579ae1f9a 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetTable.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetTable.java @@ -22,6 +22,7 @@ import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -153,7 +154,8 @@ public class TargetTable extends AbstractTable { if (isFilterEnabled()) { refreshTargets(); } else { - eventContainer.getEvents().stream().filter(event -> visibleItemIds.contains(event.getEntityId())) + eventContainer.getEvents().stream().filter(event -> visibleItemIds.contains(event.getEntityId())).filter( + event -> !Objects.isNull(event.getEntity()) && !Objects.isNull(event.getEntity().getTargetInfo())) .forEach(event -> updateVisibleItemOnEvent(event.getEntity().getTargetInfo())); } publishTargetSelectedEntityForRefresh(eventContainer.getEvents().stream()); @@ -468,7 +470,9 @@ public class TargetTable extends AbstractTable { private void resetPinStyle(final Button pinBtn) { pinBtn.removeStyleName(TARGET_PINNED); pinBtn.addStyleName(SPUIStyleDefinitions.TARGET_STATUS_PIN_TOGGLE); - HawkbitCommonUtil.applyStatusLblStyle(this, pinBtn, pinBtn.getData()); + final TargetIdName targetIdname = (TargetIdName) pinBtn.getData(); + + HawkbitCommonUtil.applyStatusLblStyle(this, pinBtn, targetIdname.getTargetId()); } /** diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettag/CustomTargetTagFilterButtonClick.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettag/CustomTargetTagFilterButtonClick.java index 8cd112ce3..f6559671d 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettag/CustomTargetTagFilterButtonClick.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettag/CustomTargetTagFilterButtonClick.java @@ -11,7 +11,6 @@ package org.eclipse.hawkbit.ui.management.targettag; import java.io.Serializable; import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; -import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.eclipse.hawkbit.ui.common.filterlayout.AbstractFilterSingleButtonClick; import org.eclipse.hawkbit.ui.management.event.TargetFilterEvent; import org.eclipse.hawkbit.ui.management.state.ManagementUIState; @@ -51,10 +50,11 @@ public class CustomTargetTagFilterButtonClick extends AbstractFilterSingleButton @Override protected void filterClicked(final Button clickedButton) { - final TargetFilterQuery targetFilterQuery = this.targetFilterQueryManagement - .findTargetFilterQueryById((Long) clickedButton.getData()).get(); - this.managementUIState.getTargetTableFilters().setTargetFilterQuery(targetFilterQuery.getId()); - this.eventBus.publish(this, TargetFilterEvent.FILTER_BY_TARGET_FILTER_QUERY); + targetFilterQueryManagement.findTargetFilterQueryById((Long) clickedButton.getData()) + .ifPresent(targetFilterQuery -> { + this.managementUIState.getTargetTableFilters().setTargetFilterQuery(targetFilterQuery.getId()); + this.eventBus.publish(this, TargetFilterEvent.FILTER_BY_TARGET_FILTER_QUERY); + }); } protected void processButtonClick(final ClickEvent event) { diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/push/DelayedEventBusPushStrategy.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/push/DelayedEventBusPushStrategy.java index 7c998d5f4..58b20788a 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/push/DelayedEventBusPushStrategy.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/push/DelayedEventBusPushStrategy.java @@ -34,7 +34,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; -import org.springframework.scheduling.annotation.Async; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; @@ -68,14 +67,14 @@ public class DelayedEventBusPushStrategy implements EventPushStrategy, Applicati private static final Logger LOG = LoggerFactory.getLogger(DelayedEventBusPushStrategy.class); private static final int BLOCK_SIZE = 10_000; - private final BlockingDeque queue = new LinkedBlockingDeque<>( - BLOCK_SIZE); + private final BlockingDeque queue = new LinkedBlockingDeque<>(BLOCK_SIZE); private final ScheduledExecutorService executorService; private final EventBus.UIEventBus eventBus; private final UIEventProvider eventProvider; - private ScheduledFuture jobHandle; private final long delay; + + private ScheduledFuture jobHandle; private UI vaadinUI; /** @@ -248,7 +247,6 @@ public class DelayedEventBusPushStrategy implements EventPushStrategy, Applicati * the entity event which has been published from the repository */ @Override - @Async public void onApplicationEvent(final ApplicationEvent applicationEvent) { if (!(applicationEvent instanceof org.eclipse.hawkbit.repository.event.TenantAwareEvent)) { return; @@ -274,7 +272,7 @@ public class DelayedEventBusPushStrategy implements EventPushStrategy, Applicati private void offerEvent(final org.eclipse.hawkbit.repository.event.TenantAwareEvent event) { if (!queue.offer(event)) { - LOG.warn("Deque limit is reached, cannot add more events!!! Dropped event is {}", event); + LOG.trace("Deque limit is reached, cannot add more events!!! Dropped event is {}", event); } } @@ -282,25 +280,23 @@ public class DelayedEventBusPushStrategy implements EventPushStrategy, Applicati Long rolloutId = null; Long rolloutGroupId = null; if (event instanceof ActionCreatedEvent) { - rolloutId = getRolloutId(((ActionCreatedEvent) event).getEntity().getRollout()); - rolloutGroupId = getRolloutGroupId(((ActionCreatedEvent) event).getEntity().getRolloutGroup()); + rolloutId = ((ActionCreatedEvent) event).getRolloutId(); + rolloutGroupId = ((ActionCreatedEvent) event).getRolloutGroupId(); } else if (event instanceof ActionUpdatedEvent) { - rolloutId = getRolloutId(((ActionUpdatedEvent) event).getEntity().getRollout()); - rolloutGroupId = getRolloutGroupId(((ActionUpdatedEvent) event).getEntity().getRolloutGroup()); + rolloutId = ((ActionUpdatedEvent) event).getRolloutId(); + rolloutGroupId = ((ActionUpdatedEvent) event).getRolloutGroupId(); } else if (event instanceof RolloutUpdatedEvent) { rolloutId = ((RolloutUpdatedEvent) event).getEntityId(); } else if (event instanceof RolloutGroupCreatedEvent) { rolloutId = ((RolloutGroupCreatedEvent) event).getRolloutId(); rolloutGroupId = ((RolloutGroupCreatedEvent) event).getEntityId(); } else if (event instanceof RolloutGroupUpdatedEvent) { - final RolloutGroup rolloutGroup = ((RolloutGroupUpdatedEvent) event).getEntity(); - rolloutId = rolloutGroup.getRollout().getId(); - rolloutGroupId = rolloutGroup.getId(); - } - - if (rolloutId == null) { + rolloutId = ((RolloutGroupUpdatedEvent) event).getRolloutId(); + rolloutGroupId = ((RolloutGroupUpdatedEvent) event).getEntityId(); + } else { return; } + offerEventIfNotContains(new RolloutChangeEvent(event.getTenant(), rolloutId)); if (rolloutGroupId != null) { diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/push/HawkbitEventProvider.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/push/HawkbitEventProvider.java index b2da83a5f..10c543c78 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/push/HawkbitEventProvider.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/push/HawkbitEventProvider.java @@ -13,6 +13,7 @@ import java.util.Map; import org.eclipse.hawkbit.repository.event.TenantAwareEvent; import org.eclipse.hawkbit.repository.event.remote.DistributionSetDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.DistributionSetTagDeletedEvent; +import org.eclipse.hawkbit.repository.event.remote.RolloutDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.SoftwareModuleDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.TargetTagDeletedEvent; @@ -37,7 +38,7 @@ import com.google.common.collect.Maps; */ public class HawkbitEventProvider implements UIEventProvider { - private static final Map, Class> EVENTS = Maps.newHashMapWithExpectedSize(15); + private static final Map, Class> EVENTS = Maps.newHashMapWithExpectedSize(19); static { @@ -60,6 +61,7 @@ public class HawkbitEventProvider implements UIEventProvider { EVENTS.put(RolloutGroupChangeEvent.class, RolloutGroupChangeEventContainer.class); EVENTS.put(RolloutChangeEvent.class, RolloutChangeEventContainer.class); + EVENTS.put(RolloutDeletedEvent.class, RolloutDeletedEventContainer.class); EVENTS.put(SoftwareModuleCreatedEvent.class, SoftwareModuleCreatedEventContainer.class); EVENTS.put(SoftwareModuleDeletedEvent.class, SoftwareModuleDeletedEventContainer.class); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/push/RolloutDeletedEventContainer.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/push/RolloutDeletedEventContainer.java new file mode 100644 index 000000000..69be961b3 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/push/RolloutDeletedEventContainer.java @@ -0,0 +1,31 @@ +/** + * 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.ui.push; + +import java.util.List; + +import org.eclipse.hawkbit.repository.event.remote.RolloutDeletedEvent; + +/** + * EventHolder for {@link RolloutDeletedEvent}s. + * + */ +public class RolloutDeletedEventContainer implements EventContainer { + private final List events; + + RolloutDeletedEventContainer(final List events) { + this.events = events; + } + + @Override + public List getEvents() { + return events; + } + +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutView.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutView.java index 72d251b2b..fcf41d8ca 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutView.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutView.java @@ -8,6 +8,8 @@ */ package org.eclipse.hawkbit.ui.rollout; +import java.util.Optional; + import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -16,6 +18,7 @@ import org.eclipse.hawkbit.repository.RolloutGroupManagement; import org.eclipse.hawkbit.repository.RolloutManagement; import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; import org.eclipse.hawkbit.repository.TargetManagement; +import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.ui.HawkbitUI; import org.eclipse.hawkbit.ui.SpPermissionChecker; import org.eclipse.hawkbit.ui.UiProperties; @@ -59,6 +62,8 @@ public class RolloutView extends VerticalLayout implements View { private final RolloutUIState rolloutUIState; + private final transient RolloutManagement rolloutManagement; + private final transient EventBus.UIEventBus eventBus; @Autowired @@ -68,6 +73,7 @@ public class RolloutView extends VerticalLayout implements View { final UINotification uiNotification, final UiProperties uiProperties, final EntityFactory entityFactory, final I18N i18n, final TargetFilterQueryManagement targetFilterQueryManagement) { this.permChecker = permissionChecker; + this.rolloutManagement = rolloutManagement; this.rolloutListView = new RolloutListView(permissionChecker, rolloutUIState, eventBus, rolloutManagement, targetManagement, uiNotification, uiProperties, entityFactory, i18n, targetFilterQueryManagement); this.rolloutGroupsListView = new RolloutGroupsListView(i18n, eventBus, rolloutGroupManagement, rolloutUIState, @@ -124,6 +130,11 @@ public class RolloutView extends VerticalLayout implements View { } private void showRolloutGroupTargetsListView() { + if (isRolloutDeleted()) { + showRolloutListView(); + return; + } + rolloutGroupTargetsListView.setVisible(true); if (rolloutListView.isVisible()) { rolloutListView.setVisible(false); @@ -132,10 +143,15 @@ public class RolloutView extends VerticalLayout implements View { rolloutGroupsListView.setVisible(false); } addComponent(rolloutGroupTargetsListView); - setExpandRatio(rolloutGroupTargetsListView, 1.0f); + setExpandRatio(rolloutGroupTargetsListView, 1.0F); } private void showRolloutGroupListView() { + if (isRolloutDeleted()) { + showRolloutListView(); + return; + } + rolloutGroupsListView.setVisible(true); if (rolloutListView.isVisible()) { rolloutListView.setVisible(false); @@ -144,7 +160,16 @@ public class RolloutView extends VerticalLayout implements View { rolloutGroupTargetsListView.setVisible(false); } addComponent(rolloutGroupsListView); - setExpandRatio(rolloutGroupsListView, 1.0f); + setExpandRatio(rolloutGroupsListView, 1.0F); + } + + private boolean isRolloutDeleted() { + if (!rolloutUIState.getRolloutId().isPresent()) { + return true; + } + + final Optional rollout = rolloutManagement.findRolloutById(rolloutUIState.getRolloutId().get()); + return !rollout.isPresent() || rollout.get().isDeleted(); } private void showRolloutListView() { @@ -156,7 +181,7 @@ public class RolloutView extends VerticalLayout implements View { rolloutGroupTargetsListView.setVisible(false); } addComponent(rolloutListView); - setExpandRatio(rolloutListView, 1.0f); + setExpandRatio(rolloutListView, 1.0F); } @Override diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/AddUpdateRolloutWindowLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/AddUpdateRolloutWindowLayout.java index feef858fe..4d065f153 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/AddUpdateRolloutWindowLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/AddUpdateRolloutWindowLayout.java @@ -13,6 +13,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; @@ -23,6 +24,7 @@ import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.builder.RolloutCreate; import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate; import org.eclipse.hawkbit.repository.builder.RolloutUpdate; +import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.RepositoryModelConstants; import org.eclipse.hawkbit.repository.model.Rollout; @@ -56,6 +58,8 @@ import org.eclipse.hawkbit.ui.utils.SPUILabelDefinitions; import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions; import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; import org.eclipse.hawkbit.ui.utils.UINotification; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.vaadin.addons.lazyquerycontainer.BeanQueryFactory; @@ -89,6 +93,8 @@ public class AddUpdateRolloutWindowLayout extends GridLayout { private static final long serialVersionUID = 2999293468801479916L; + private static final Logger LOGGER = LoggerFactory.getLogger(AddUpdateRolloutWindowLayout.class); + private static final String MESSAGE_ROLLOUT_FIELD_VALUE_RANGE = "message.rollout.field.value.range"; private static final String MESSAGE_ENTER_NUMBER = "message.enter.number"; @@ -201,6 +207,131 @@ public class AddUpdateRolloutWindowLayout extends GridLayout { } return duplicateCheck(); } + + private void createRollout() { + final Rollout rolloutToCreate = saveRollout(); + uiNotification.displaySuccess(i18n.get("message.save.success", rolloutToCreate.getName())); + } + + private boolean duplicateCheck() { + if (rolloutManagement.findRolloutByName(getRolloutName()).isPresent()) { + uiNotification.displayValidationError(i18n.get("message.rollout.duplicate.check", getRolloutName())); + return false; + } + return true; + } + + private void editRollout() { + if (rollout == null) { + return; + } + + final Long distributionSetId = (Long) distributionSet.getValue(); + + final RolloutUpdate rolloutUpdate = entityFactory.rollout().update(rollout.getId()) + .name(rolloutName.getValue()).description(description.getValue()).set(distributionSetId) + .actionType(getActionType()).forcedTime(getForcedTimeStamp()); + + if (AutoStartOptionGroupLayout.AutoStartOption.AUTO_START.equals(getAutoStartOption())) { + rolloutUpdate.startAt(System.currentTimeMillis()); + } + if (AutoStartOptionGroupLayout.AutoStartOption.SCHEDULED.equals(getAutoStartOption())) { + rolloutUpdate.startAt(getScheduledStartTime()); + } + + Rollout updatedRollout; + try { + updatedRollout = rolloutManagement.updateRollout(rolloutUpdate); + } catch (final EntityNotFoundException e) { + LOGGER.warn("Rollout was deleted. Redirect to Rollouts overview.", e); + uiNotification.displayWarning( + "Rollout with name " + rolloutName.getValue() + " was deleted. Update is not poosible"); + eventBus.publish(this, RolloutEvent.SHOW_ROLLOUTS); + return; + } + + uiNotification.displaySuccess(i18n.get("message.update.success", updatedRollout.getName())); + eventBus.publish(this, RolloutEvent.UPDATE_ROLLOUT); + } + + private boolean duplicateCheckForEdit() { + final String rolloutNameVal = getRolloutName(); + if (!rollout.getName().equals(rolloutNameVal) + && rolloutManagement.findRolloutByName(rolloutNameVal).isPresent()) { + uiNotification.displayValidationError(i18n.get("message.rollout.duplicate.check", rolloutNameVal)); + return false; + } + return true; + } + + private String getRolloutName() { + return HawkbitCommonUtil.trimAndNullIfEmpty(rolloutName.getValue()); + } + + private Rollout saveRollout() { + + final Long distributionId = (Long) distributionSet.getValue(); + + final int amountGroup = Integer.parseInt(noOfGroups.getValue()); + final int errorThresholdPercent = getErrorThresholdPercentage(amountGroup); + final RolloutGroupConditions conditions = new RolloutGroupConditionBuilder() + .successAction(RolloutGroupSuccessAction.NEXTGROUP, null) + .successCondition(RolloutGroupSuccessCondition.THRESHOLD, triggerThreshold.getValue()) + .errorCondition(RolloutGroupErrorCondition.THRESHOLD, String.valueOf(errorThresholdPercent)) + .errorAction(RolloutGroupErrorAction.PAUSE, null).build(); + + final RolloutCreate rolloutCreate = entityFactory.rollout().create().name(rolloutName.getValue()) + .description(description.getValue()).set(distributionId).targetFilterQuery(getTargetFilterQuery()) + .actionType(getActionType()).forcedTime(getForcedTimeStamp()); + + if (AutoStartOptionGroupLayout.AutoStartOption.AUTO_START.equals(getAutoStartOption())) { + rolloutCreate.startAt(System.currentTimeMillis()); + } + if (AutoStartOptionGroupLayout.AutoStartOption.SCHEDULED.equals(getAutoStartOption())) { + rolloutCreate.startAt(getScheduledStartTime()); + } + + if (isNumberOfGroups()) { + return rolloutManagement.createRollout(rolloutCreate, amountGroup, conditions); + } else if (isGroupsDefinition()) { + final List groups = defineGroupsLayout.getSavedRolloutGroups(); + return rolloutManagement.createRollout(rolloutCreate, groups, conditions); + } + + throw new IllegalStateException("Either of the Tabs must be selected"); + } + + private long getForcedTimeStamp() { + return ActionTypeOption.AUTO_FORCED + .equals(actionTypeOptionGroupLayout.getActionTypeOptionGroup().getValue()) + ? actionTypeOptionGroupLayout.getForcedTimeDateField().getValue().getTime() + : RepositoryModelConstants.NO_FORCE_TIME; + } + + private Long getScheduledStartTime() { + return AutoStartOptionGroupLayout.AutoStartOption.SCHEDULED.equals(getAutoStartOption()) + ? autoStartOptionGroupLayout.getStartAtDateField().getValue().getTime() : null; + } + + private int getErrorThresholdPercentage(final int amountGroup) { + int errorThresoldPercent = Integer.parseInt(errorThreshold.getValue()); + if (errorThresholdOptionGroup.getValue().equals(ERRORTHRESOLDOPTIONS.COUNT.getValue())) { + final int groupSize = (int) Math.ceil((double) totalTargetsCount / (double) amountGroup); + final int erroThresoldCount = Integer.parseInt(errorThreshold.getValue()); + errorThresoldPercent = (int) Math.ceil(((float) erroThresoldCount / (float) groupSize) * 100); + } + return errorThresoldPercent; + } + + private ActionType getActionType() { + return ((ActionTypeOptionGroupLayout.ActionTypeOption) actionTypeOptionGroupLayout + .getActionTypeOptionGroup().getValue()).getActionType(); + } + + private AutoStartOptionGroupLayout.AutoStartOption getAutoStartOption() { + return (AutoStartOptionGroupLayout.AutoStartOption) autoStartOptionGroupLayout.getAutoStartOptionGroup() + .getValue(); + } } CommonDialogWindow getWindow(final Long rolloutId, final boolean copy) { @@ -244,6 +375,9 @@ public class AddUpdateRolloutWindowLayout extends GridLayout { noOfGroups.setVisible(true); removeComponent(1, 2); addComponent(targetFilterQueryCombo, 1, 2); + addGroupsLegendLayout(); + addGroupsDefinitionTabs(); + actionTypeOptionGroupLayout.selectDefaultOption(); autoStartOptionGroupLayout.selectDefaultOption(); totalTargetsCount = 0L; @@ -252,6 +386,18 @@ public class AddUpdateRolloutWindowLayout extends GridLayout { groupsDefinitionTabs.setSelectedTab(0); } + private void addGroupsDefinitionTabs() { + if (getComponent(0, 6) == null) { + addComponent(groupsDefinitionTabs, 0, 6, 3, 6); + } + } + + private void addGroupsLegendLayout() { + if (getComponent(3, 0) == null) { + addComponent(groupsLegendLayout, 3, 0, 3, 3); + } + } + private void resetFields() { rolloutName.removeStyleName(SPUIStyleDefinitions.SP_TEXTFIELD_ERROR); noOfGroups.clear(); @@ -497,7 +643,6 @@ public class AddUpdateRolloutWindowLayout extends GridLayout { } else { updateGroupsChart(0); } - } } @@ -544,7 +689,6 @@ public class AddUpdateRolloutWindowLayout extends GridLayout { groupsPieChart.setChartState(groups, totalTargetsCount); groupsLegendLayout.populateGroupsLegendByTargetCounts(groups); } - } private ComboBox createTargetFilterQueryCombo() { @@ -583,7 +727,6 @@ public class AddUpdateRolloutWindowLayout extends GridLayout { final TargetFilterQuery filterQuery = filterQueries.getContent().get(0); targetFilterQueryCombo.setValue(filterQuery.getName()); } - } private static Container createTargetFilterComboContainer() { @@ -594,99 +737,6 @@ public class AddUpdateRolloutWindowLayout extends GridLayout { targetFilterQF); } - private void editRollout() { - if (rollout == null) { - return; - } - - final Long distributionSetId = (Long) distributionSet.getValue(); - - final RolloutUpdate rolloutUpdate = entityFactory.rollout().update(rollout.getId()).name(rolloutName.getValue()) - .description(description.getValue()).set(distributionSetId).actionType(getActionType()) - .forcedTime(getForcedTimeStamp()); - - if (AutoStartOptionGroupLayout.AutoStartOption.AUTO_START.equals(getAutoStartOption())) { - rolloutUpdate.startAt(System.currentTimeMillis()); - } - if (AutoStartOptionGroupLayout.AutoStartOption.SCHEDULED.equals(getAutoStartOption())) { - rolloutUpdate.startAt(getScheduledStartTime()); - } - - final Rollout updatedRollout = rolloutManagement.updateRollout(rolloutUpdate); - - uiNotification.displaySuccess(i18n.get("message.update.success", updatedRollout.getName())); - eventBus.publish(this, RolloutEvent.UPDATE_ROLLOUT); - } - - private boolean duplicateCheckForEdit() { - final String rolloutNameVal = getRolloutName(); - if (!rollout.getName().equals(rolloutNameVal) - && rolloutManagement.findRolloutByName(rolloutNameVal).isPresent()) { - uiNotification.displayValidationError(i18n.get("message.rollout.duplicate.check", rolloutNameVal)); - return false; - } - return true; - } - - private long getForcedTimeStamp() { - return ActionTypeOption.AUTO_FORCED.equals(actionTypeOptionGroupLayout.getActionTypeOptionGroup().getValue()) - ? actionTypeOptionGroupLayout.getForcedTimeDateField().getValue().getTime() - : RepositoryModelConstants.NO_FORCE_TIME; - } - - private Long getScheduledStartTime() { - return AutoStartOptionGroupLayout.AutoStartOption.SCHEDULED.equals(getAutoStartOption()) - ? autoStartOptionGroupLayout.getStartAtDateField().getValue().getTime() : null; - } - - private AutoStartOptionGroupLayout.AutoStartOption getAutoStartOption() { - return (AutoStartOptionGroupLayout.AutoStartOption) autoStartOptionGroupLayout.getAutoStartOptionGroup() - .getValue(); - } - - private ActionType getActionType() { - return ((ActionTypeOptionGroupLayout.ActionTypeOption) actionTypeOptionGroupLayout.getActionTypeOptionGroup() - .getValue()).getActionType(); - } - - private void createRollout() { - final Rollout rolloutToCreate = saveRollout(); - uiNotification.displaySuccess(i18n.get("message.save.success", rolloutToCreate.getName())); - } - - private Rollout saveRollout() { - - final Long distributionId = (Long) distributionSet.getValue(); - - final int amountGroup = Integer.parseInt(noOfGroups.getValue()); - final int errorThresholdPercent = getErrorThresholdPercentage(amountGroup); - final RolloutGroupConditions conditions = new RolloutGroupConditionBuilder() - .successAction(RolloutGroupSuccessAction.NEXTGROUP, null) - .successCondition(RolloutGroupSuccessCondition.THRESHOLD, triggerThreshold.getValue()) - .errorCondition(RolloutGroupErrorCondition.THRESHOLD, String.valueOf(errorThresholdPercent)) - .errorAction(RolloutGroupErrorAction.PAUSE, null).build(); - - final RolloutCreate rolloutCreate = entityFactory.rollout().create().name(rolloutName.getValue()) - .description(description.getValue()).set(distributionId).targetFilterQuery(getTargetFilterQuery()) - .actionType(getActionType()).forcedTime(getForcedTimeStamp()); - - if (AutoStartOptionGroupLayout.AutoStartOption.AUTO_START.equals(getAutoStartOption())) { - rolloutCreate.startAt(System.currentTimeMillis()); - } - if (AutoStartOptionGroupLayout.AutoStartOption.SCHEDULED.equals(getAutoStartOption())) { - rolloutCreate.startAt(getScheduledStartTime()); - } - - if (isNumberOfGroups()) { - return rolloutManagement.createRollout(rolloutCreate, amountGroup, conditions); - } else if (isGroupsDefinition()) { - final List groups = defineGroupsLayout.getSavedRolloutGroups(); - return rolloutManagement.createRollout(rolloutCreate, groups, conditions); - } - - throw new IllegalStateException("Either of the Tabs must be selected"); - } - private String getTargetFilterQuery() { if (null != targetFilterQueryCombo.getValue() && HawkbitCommonUtil.trimAndNullIfEmpty((String) targetFilterQueryCombo.getValue()) != null) { @@ -697,24 +747,6 @@ public class AddUpdateRolloutWindowLayout extends GridLayout { return null; } - private int getErrorThresholdPercentage(final int amountGroup) { - int errorThresoldPercent = Integer.parseInt(errorThreshold.getValue()); - if (errorThresholdOptionGroup.getValue().equals(ERRORTHRESOLDOPTIONS.COUNT.getValue())) { - final int groupSize = (int) Math.ceil((double) totalTargetsCount / (double) amountGroup); - final int erroThresoldCount = Integer.parseInt(errorThreshold.getValue()); - errorThresoldPercent = (int) Math.ceil(((float) erroThresoldCount / (float) groupSize) * 100); - } - return errorThresoldPercent; - } - - private boolean duplicateCheck() { - if (rolloutManagement.findRolloutByName(getRolloutName()).isPresent()) { - uiNotification.displayValidationError(i18n.get("message.rollout.duplicate.check", getRolloutName())); - return false; - } - return true; - } - private void setDefaultSaveStartGroupOption() { errorThresholdOptionGroup.setValue(ERRORTHRESOLDOPTIONS.PERCENT.getValue()); } @@ -795,10 +827,6 @@ public class AddUpdateRolloutWindowLayout extends GridLayout { return rolloutNameField; } - private String getRolloutName() { - return HawkbitCommonUtil.trimAndNullIfEmpty(rolloutName.getValue()); - } - class ErrorThresoldOptionValidator implements Validator { private static final long serialVersionUID = 9049939751976326550L; @@ -855,7 +883,12 @@ public class AddUpdateRolloutWindowLayout extends GridLayout { return; } - rollout = rolloutManagement.findRolloutById(rolloutId).get(); + final Optional rolloutFound = rolloutManagement.findRolloutById(rolloutId); + if (!rolloutFound.isPresent()) { + return; + } + + rollout = rolloutFound.get(); description.setValue(rollout.getDescription()); distributionSet.setValue(rollout.getDistributionSet().getId()); setActionType(rollout); @@ -869,7 +902,6 @@ public class AddUpdateRolloutWindowLayout extends GridLayout { groupsDefinitionTabs.setSelectedTab(1); window.clearOriginalValues(); - } else { editRolloutEnabled = true; if (rollout.getStatus() != Rollout.RolloutStatus.READY) { @@ -884,6 +916,10 @@ public class AddUpdateRolloutWindowLayout extends GridLayout { addComponent(targetFilterQuery, 1, 2); targetFilterQuery.addValidator(nullValidator); + removeComponent(defineGroupsLayout); + removeComponent(groupsDefinitionTabs); + + window.updateAllComponents(this); window.setOrginaleValues(); updateGroupsChart(rollout.getRolloutGroups(), rollout.getTotalTargets()); @@ -955,12 +991,8 @@ public class AddUpdateRolloutWindowLayout extends GridLayout { value = val; } - /** - * @return the value - */ - public String getValue() { + private String getValue() { return value; } } - } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/ProxyRollout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/ProxyRollout.java index 88e7204aa..1b045000c 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/ProxyRollout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/ProxyRollout.java @@ -24,15 +24,13 @@ import com.vaadin.server.FontAwesome; */ public class ProxyRollout { - private static final long serialVersionUID = 4539849939617681918L; - private String distributionSetNameVersion; private String createdDate; private String modifiedDate; - private Long numberOfGroups; + private Integer numberOfGroups; private Boolean isActionRecieved = Boolean.FALSE; @@ -151,7 +149,7 @@ public class ProxyRollout { /** * @return the numberOfGroups */ - public Long getNumberOfGroups() { + public Integer getNumberOfGroups() { return numberOfGroups; } @@ -159,7 +157,7 @@ public class ProxyRollout { * @param numberOfGroups * the numberOfGroups to set */ - public void setNumberOfGroups(final Long numberOfGroups) { + public void setNumberOfGroups(final Integer numberOfGroups) { this.numberOfGroups = numberOfGroups; } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/RolloutBeanQuery.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/RolloutBeanQuery.java index 3b3cf17f5..ec21ba46f 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/RolloutBeanQuery.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/RolloutBeanQuery.java @@ -15,7 +15,6 @@ import java.util.List; import java.util.Map; import org.eclipse.hawkbit.repository.RolloutManagement; -import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus; @@ -51,8 +50,6 @@ public class RolloutBeanQuery extends AbstractBeanQuery { private transient RolloutManagement rolloutManagement; - private transient TargetFilterQueryManagement filterQueryManagement; - private transient RolloutUIState rolloutUIState; /** @@ -95,18 +92,18 @@ public class RolloutBeanQuery extends AbstractBeanQuery { @Override protected List loadBeans(final int startIndex, final int count) { final Slice rolloutBeans; + final PageRequest pageRequest = new PageRequest(startIndex / SPUIDefinitions.PAGE_SIZE, + SPUIDefinitions.PAGE_SIZE, sort); if (Strings.isNullOrEmpty(searchText)) { - rolloutBeans = getRolloutManagement().findAllRolloutsWithDetailedStatus( - new PageRequest(startIndex / SPUIDefinitions.PAGE_SIZE, SPUIDefinitions.PAGE_SIZE, sort)); + rolloutBeans = getRolloutManagement().findAllRolloutsWithDetailedStatus(pageRequest, false); } else { - rolloutBeans = getRolloutManagement().findRolloutWithDetailedStatusByFilters( - new PageRequest(startIndex / SPUIDefinitions.PAGE_SIZE, SPUIDefinitions.PAGE_SIZE, sort), - searchText); + rolloutBeans = getRolloutManagement().findRolloutWithDetailedStatusByFilters(pageRequest, searchText, + false); } return getProxyRolloutList(rolloutBeans); } - private List getProxyRolloutList(final Slice rolloutBeans) { + private static List getProxyRolloutList(final Slice rolloutBeans) { final List proxyRolloutList = new ArrayList<>(); for (final Rollout rollout : rolloutBeans) { final ProxyRollout proxyRollout = new ProxyRollout(); @@ -116,7 +113,7 @@ public class RolloutBeanQuery extends AbstractBeanQuery { proxyRollout.setDistributionSetNameVersion( HawkbitCommonUtil.getFormattedNameVersion(distributionSet.getName(), distributionSet.getVersion())); proxyRollout.setDistributionSet(distributionSet); - proxyRollout.setNumberOfGroups(Long.valueOf(rollout.getRolloutGroups().size())); + proxyRollout.setNumberOfGroups(Integer.valueOf(rollout.getRolloutGroups().size())); proxyRollout.setCreatedDate(SPDateTimeUtil.getFormattedDate(rollout.getCreatedAt())); proxyRollout.setModifiedDate(SPDateTimeUtil.getFormattedDate(rollout.getLastModifiedAt())); proxyRollout.setCreatedBy(UserDetailsFormatter.loadAndFormatCreatedBy(rollout)); @@ -139,13 +136,6 @@ public class RolloutBeanQuery extends AbstractBeanQuery { return proxyRolloutList; } - /* - * (non-Javadoc) - * - * @see - * org.vaadin.addons.lazyquerycontainer.AbstractBeanQuery#saveBeans(java - * .util.List, java.util.List, java.util.List) - */ @Override protected void saveBeans(final List arg0, final List arg1, final List arg2) { @@ -163,30 +153,14 @@ public class RolloutBeanQuery extends AbstractBeanQuery { return size; } - /** - * @return the rolloutManagement - */ - public RolloutManagement getRolloutManagement() { + private RolloutManagement getRolloutManagement() { if (null == rolloutManagement) { rolloutManagement = SpringContextHelper.getBean(RolloutManagement.class); } return rolloutManagement; } - /** - * @return the filterQueryManagement - */ - public TargetFilterQueryManagement getFilterQueryManagement() { - if (null == filterQueryManagement) { - filterQueryManagement = SpringContextHelper.getBean(TargetFilterQueryManagement.class); - } - return filterQueryManagement; - } - - /** - * @return the rolloutUIState - */ - public RolloutUIState getRolloutUIState() { + private RolloutUIState getRolloutUIState() { if (null == rolloutUIState) { rolloutUIState = SpringContextHelper.getBean(RolloutUIState.class); } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/RolloutListGrid.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/RolloutListGrid.java index 3b9063133..60da3e35e 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/RolloutListGrid.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rollout/RolloutListGrid.java @@ -33,8 +33,10 @@ import java.util.EnumMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.Set; +import org.apache.commons.lang3.StringUtils; import org.eclipse.hawkbit.repository.EntityFactory; import org.eclipse.hawkbit.repository.RolloutManagement; import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; @@ -43,15 +45,18 @@ import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus; +import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus.Status; import org.eclipse.hawkbit.ui.SpPermissionChecker; import org.eclipse.hawkbit.ui.UiProperties; import org.eclipse.hawkbit.ui.common.CommonDialogWindow; +import org.eclipse.hawkbit.ui.common.ConfirmationDialog; import org.eclipse.hawkbit.ui.common.grid.AbstractGrid; import org.eclipse.hawkbit.ui.customrenderers.client.renderers.RolloutRendererData; import org.eclipse.hawkbit.ui.customrenderers.renderers.HtmlButtonRenderer; import org.eclipse.hawkbit.ui.customrenderers.renderers.HtmlLabelRenderer; import org.eclipse.hawkbit.ui.customrenderers.renderers.RolloutRenderer; import org.eclipse.hawkbit.ui.push.RolloutChangeEventContainer; +import org.eclipse.hawkbit.ui.push.RolloutDeletedEventContainer; import org.eclipse.hawkbit.ui.push.event.RolloutChangeEvent; import org.eclipse.hawkbit.ui.rollout.DistributionBarHelper; import org.eclipse.hawkbit.ui.rollout.StatusFontIcon; @@ -70,7 +75,6 @@ import org.vaadin.spring.events.EventBus.UIEventBus; import org.vaadin.spring.events.EventScope; import org.vaadin.spring.events.annotation.EventBusListenerMethod; -import com.google.common.collect.Maps; import com.vaadin.data.Container; import com.vaadin.data.Item; import com.vaadin.data.util.converter.Converter; @@ -84,7 +88,7 @@ import com.vaadin.ui.renderers.HtmlRenderer; */ public class RolloutListGrid extends AbstractGrid { - private static final long serialVersionUID = 4060904914954370524L; + private static final long serialVersionUID = 1L; private static final String UPDATE_OPTION = "Update"; @@ -94,6 +98,8 @@ public class RolloutListGrid extends AbstractGrid { private static final String RUN_OPTION = "Run"; + private static final String DELETE_OPTION = "Delete"; + private static final String DS_TYPE = "type"; private static final String SW_MODULES = "swModules"; @@ -128,6 +134,7 @@ public class RolloutListGrid extends AbstractGrid { new StatusFontIcon(FontAwesome.EXCLAMATION_CIRCLE, SPUIStyleDefinitions.STATUS_ICON_RED)); statusIconMap.put(RolloutStatus.ERROR_STARTING, new StatusFontIcon(FontAwesome.EXCLAMATION_CIRCLE, SPUIStyleDefinitions.STATUS_ICON_RED)); + statusIconMap.put(RolloutStatus.DELETING, new StatusFontIcon(null, SPUIStyleDefinitions.STATUS_SPINNER_RED)); } RolloutListGrid(final I18N i18n, final UIEventBus eventBus, final RolloutManagement rolloutManagement, @@ -161,39 +168,58 @@ public class RolloutListGrid extends AbstractGrid { } } + /** + * Handles the RolloutDeletedEvent to refresh the grid. + * + * @param eventContainer + * container which holds the rollout delete event + */ + @EventBusListenerMethod(scope = EventScope.UI) + public void onRolloutDeletedEvent(final RolloutDeletedEventContainer eventContainer) { + refreshContainer(); + } + /** * Handles the RolloutChangeEvent to refresh the item in the grid. * * @param eventContainer * container which holds the rollout change event */ - @SuppressWarnings("unchecked") @EventBusListenerMethod(scope = EventScope.UI) public void onRolloutChangeEvent(final RolloutChangeEventContainer eventContainer) { eventContainer.getEvents().forEach(this::handleEvent); } private void handleEvent(final RolloutChangeEvent rolloutChangeEvent) { - if (!rolloutUIState.isShowRollOuts()) { + if (!rolloutUIState.isShowRollOuts() || rolloutChangeEvent.getRolloutId() == null) { return; } - final Rollout rollout = rolloutManagement.findRolloutWithDetailedStatus(rolloutChangeEvent.getRolloutId()) - .get(); - final TotalTargetCountStatus totalTargetCountStatus = rollout.getTotalTargetCountStatus(); + final Optional rollout = rolloutManagement + .findRolloutWithDetailedStatus(rolloutChangeEvent.getRolloutId()); + + if (!rollout.isPresent()) { + return; + } + final LazyQueryContainer rolloutContainer = (LazyQueryContainer) getContainerDataSource(); final Item item = rolloutContainer.getItem(rolloutChangeEvent.getRolloutId()); if (item == null) { refreshContainer(); return; } + updateItem(rollout.get(), item); + } + + private void updateItem(final Rollout rollout, final Item item) { + final TotalTargetCountStatus totalTargetCountStatus = rollout.getTotalTargetCountStatus(); item.getItemProperty(VAR_STATUS).setValue(rollout.getStatus()); item.getItemProperty(VAR_TOTAL_TARGETS_COUNT_STATUS).setValue(totalTargetCountStatus); - final Long groupCount = (Long) item.getItemProperty(VAR_NUMBER_OF_GROUPS).getValue(); + final Integer groupCount = (Integer) item.getItemProperty(VAR_NUMBER_OF_GROUPS).getValue(); final int groupsCreated = rollout.getRolloutGroupsCreated(); if (groupsCreated != 0) { - item.getItemProperty(VAR_NUMBER_OF_GROUPS).setValue(Long.valueOf(groupsCreated)); - } else if (rollout.getRolloutGroups() != null && groupCount != rollout.getRolloutGroups().size()) { - item.getItemProperty(VAR_NUMBER_OF_GROUPS).setValue(Long.valueOf(rollout.getRolloutGroups().size())); + item.getItemProperty(VAR_NUMBER_OF_GROUPS).setValue(Integer.valueOf(groupsCreated)); + } else if (rollout.getRolloutGroups() != null && !groupCount.equals(rollout.getRolloutGroups().size())) { + item.getItemProperty(VAR_NUMBER_OF_GROUPS).setValue(Integer.valueOf(rollout.getRolloutGroups().size())); } item.getItemProperty(ROLLOUT_RENDERER_DATA) .setValue(new RolloutRendererData(rollout.getName(), rollout.getStatus().toString())); @@ -221,13 +247,12 @@ public class RolloutListGrid extends AbstractGrid { rolloutGridContainer.addContainerProperty(VAR_MODIFIED_DATE, String.class, null, false, false); rolloutGridContainer.addContainerProperty(VAR_CREATED_USER, String.class, null, false, false); rolloutGridContainer.addContainerProperty(VAR_MODIFIED_BY, String.class, null, false, false); - rolloutGridContainer.addContainerProperty(VAR_NUMBER_OF_GROUPS, Long.class, 0, false, false); + rolloutGridContainer.addContainerProperty(VAR_NUMBER_OF_GROUPS, Integer.class, 0, false, false); rolloutGridContainer.addContainerProperty(VAR_TOTAL_TARGETS, String.class, "0", false, false); rolloutGridContainer.addContainerProperty(VAR_TOTAL_TARGETS_COUNT_STATUS, TotalTargetCountStatus.class, null, false, false); rolloutGridContainer.addContainerProperty(RUN_OPTION, String.class, FontAwesome.PLAY.getHtml(), false, false); - rolloutGridContainer.addContainerProperty(PAUSE_OPTION, String.class, FontAwesome.PAUSE.getHtml(), false, false); @@ -239,6 +264,8 @@ public class RolloutListGrid extends AbstractGrid { rolloutGridContainer.addContainerProperty(COPY_OPTION, String.class, FontAwesome.COPY.getHtml(), false, false); } + rolloutGridContainer.addContainerProperty(DELETE_OPTION, String.class, FontAwesome.TRASH_O.getHtml(), false, + false); } @Override @@ -267,15 +294,17 @@ public class RolloutListGrid extends AbstractGrid { if (permissionChecker.hasRolloutUpdatePermission()) { getColumn(UPDATE_OPTION).setMinimumWidth(25); - getColumn(UPDATE_OPTION).setMaximumWidth(40); - } else { - getColumn(PAUSE_OPTION).setMaximumWidth(60); + getColumn(UPDATE_OPTION).setMaximumWidth(25); } + if (permissionChecker.hasRolloutCreatePermission()) { getColumn(COPY_OPTION).setMinimumWidth(25); getColumn(COPY_OPTION).setMaximumWidth(25); } + getColumn(DELETE_OPTION).setMinimumWidth(25); + getColumn(DELETE_OPTION).setMaximumWidth(40); + getColumn(VAR_TOTAL_TARGETS_COUNT_STATUS).setMinimumWidth(280); } @@ -302,17 +331,27 @@ public class RolloutListGrid extends AbstractGrid { if (permissionChecker.hasRolloutUpdatePermission()) { getColumn(UPDATE_OPTION).setHeaderCaption(i18n.get("header.action.update")); } + if (permissionChecker.hasRolloutCreatePermission()) { getColumn(COPY_OPTION).setHeaderCaption(i18n.get("header.action.copy")); } - HeaderCell join; - if (permissionChecker.hasRolloutUpdatePermission()) { - join = getDefaultHeaderRow().join(RUN_OPTION, PAUSE_OPTION, UPDATE_OPTION, COPY_OPTION); - } else { - join = getDefaultHeaderRow().join(RUN_OPTION, PAUSE_OPTION); + getColumn(DELETE_OPTION).setHeaderCaption(i18n.get("header.action.delete")); + + joinColumns().setText(i18n.get("header.action")); + } + + private HeaderCell joinColumns() { + if (permissionChecker.hasRolloutUpdatePermission() && permissionChecker.hasRolloutCreatePermission()) { + return getDefaultHeaderRow().join(RUN_OPTION, PAUSE_OPTION, UPDATE_OPTION, COPY_OPTION, DELETE_OPTION); } - join.setText(i18n.get("header.action")); + if (permissionChecker.hasRolloutUpdatePermission()) { + return getDefaultHeaderRow().join(RUN_OPTION, PAUSE_OPTION, UPDATE_OPTION, DELETE_OPTION); + } + if (permissionChecker.hasRolloutCreatePermission()) { + return getDefaultHeaderRow().join(RUN_OPTION, PAUSE_OPTION, COPY_OPTION, DELETE_OPTION); + } + return getDefaultHeaderRow().join(RUN_OPTION, PAUSE_OPTION); } @Override @@ -342,6 +381,7 @@ public class RolloutListGrid extends AbstractGrid { if (permissionChecker.hasRolloutCreatePermission()) { columnList.add(COPY_OPTION); } + columnList.add(DELETE_OPTION); columnList.add(VAR_CREATED_DATE); columnList.add(VAR_CREATED_USER); @@ -401,6 +441,9 @@ public class RolloutListGrid extends AbstractGrid { .setRenderer(new HtmlButtonRenderer(clickEvent -> copyRollout((Long) clickEvent.getItemId()))); } + getColumn(DELETE_OPTION) + .setRenderer(new HtmlButtonRenderer(clickEvent -> deleteRollout((Long) clickEvent.getItemId()))); + } private void alignColumns() { @@ -466,6 +509,44 @@ public class RolloutListGrid extends AbstractGrid { addTargetWindow.setVisible(Boolean.TRUE); } + private void deleteRollout(final Long rolloutId) { + final Optional rollout = rolloutManagement.findRolloutWithDetailedStatus(rolloutId); + + if (!rollout.isPresent()) { + return; + } + + final String formattedConfirmationQuestion = getConfirmationQuestion(rollout.get()); + final ConfirmationDialog confirmationDialog = new ConfirmationDialog(i18n.get("caption.confirm.delete.rollout"), + formattedConfirmationQuestion, i18n.get("button.ok"), i18n.get("button.cancel"), ok -> { + if (!ok) { + return; + } + final Item row = getContainerDataSource().getItem(rolloutId); + final String rolloutName = (String) row.getItemProperty(VAR_NAME).getValue(); + rolloutManagement.deleteRollout(rolloutId); + uiNotification.displaySuccess(i18n.get("message.rollout.deleted", rolloutName)); + }); + UI.getCurrent().addWindow(confirmationDialog.getWindow()); + confirmationDialog.getWindow().bringToFront(); + } + + private String getConfirmationQuestion(final Rollout rollout) { + + final Map statusTotalCount = rollout.getTotalTargetCountStatus().getStatusTotalCountMap(); + Long scheduledActions = statusTotalCount.get(Status.SCHEDULED); + if (scheduledActions == null) { + scheduledActions = 0L; + } + final Long runningActions = statusTotalCount.get(Status.RUNNING); + String rolloutDetailsMessage = StringUtils.EMPTY; + if ((scheduledActions > 0) || (runningActions > 0)) { + rolloutDetailsMessage = i18n.get("message.delete.rollout.details", runningActions, scheduledActions); + } + + return i18n.get("message.delete.rollout", rollout.getName(), rolloutDetailsMessage); + } + private String getDescription(final CellReference cell) { String description = null; @@ -488,6 +569,7 @@ public class RolloutListGrid extends AbstractGrid { private static String getDSDetails(final Item rolloutItem) { final StringBuilder swModuleNames = new StringBuilder(); final StringBuilder swModuleVendors = new StringBuilder(); + @SuppressWarnings("unchecked") final Set swModules = (Set) rolloutItem.getItemProperty(SW_MODULES).getValue(); swModules.forEach(swModule -> { swModuleNames.append(swModule.getName()); @@ -524,19 +606,22 @@ public class RolloutListGrid extends AbstractGrid { private static class RollouStatusCellStyleGenerator implements CellStyleGenerator { - private static final long serialVersionUID = 1L; - /** - * Contains all expected rollout status per column to enable or disable - * the button. - */ - private static final Map EXPECTED_ROLLOUT_STATUS_ENABLE_BUTTON = Maps - .newHashMapWithExpectedSize(2); - private final Container.Indexed containerDataSource; + private static final List DELETE_COPY_BUTTON_ENABLED = Arrays.asList(RolloutStatus.CREATING, + RolloutStatus.ERROR_CREATING, RolloutStatus.ERROR_STARTING, RolloutStatus.PAUSED, RolloutStatus.READY, + RolloutStatus.RUNNING, RolloutStatus.STARTING, RolloutStatus.STOPPED, RolloutStatus.FINISHED); - static { - EXPECTED_ROLLOUT_STATUS_ENABLE_BUTTON.put(RUN_OPTION, RolloutStatus.READY); - EXPECTED_ROLLOUT_STATUS_ENABLE_BUTTON.put(PAUSE_OPTION, RolloutStatus.RUNNING); - } + private static final List UPDATE_BUTTON_ENABLED = Arrays.asList(RolloutStatus.CREATING, + RolloutStatus.ERROR_CREATING, RolloutStatus.ERROR_STARTING, RolloutStatus.PAUSED, RolloutStatus.READY, + RolloutStatus.RUNNING, RolloutStatus.STARTING, RolloutStatus.STOPPED); + + private static final List PAUSE_BUTTON_ENABLED = Arrays.asList(RolloutStatus.RUNNING); + + private static final List RUN_BUTTON_ENABLED = Arrays.asList(RolloutStatus.READY, + RolloutStatus.PAUSED); + + private static final long serialVersionUID = 1L; + + private final Container.Indexed containerDataSource; /** * Constructor @@ -557,27 +642,31 @@ public class RolloutListGrid extends AbstractGrid { } private String convertRolloutStatusToString(final CellReference cellReference) { - final Object propertyId = cellReference.getPropertyId(); - final RolloutStatus expectedRolloutStatus = EXPECTED_ROLLOUT_STATUS_ENABLE_BUTTON.get(propertyId); - if (expectedRolloutStatus == null) { - return null; + final String propertyId = (String) cellReference.getPropertyId(); + + if (RUN_OPTION.equals(propertyId)) { + return getStatus(cellReference, RUN_BUTTON_ENABLED); } - if (RUN_OPTION.equals(cellReference.getPropertyId())) { - return getStatus(cellReference, RolloutStatus.READY, RolloutStatus.PAUSED); + if (PAUSE_OPTION.equals(propertyId)) { + return getStatus(cellReference, PAUSE_BUTTON_ENABLED); } - if (PAUSE_OPTION.equals(cellReference.getPropertyId())) { - return getStatus(cellReference, RolloutStatus.RUNNING); + if (UPDATE_OPTION.equals(propertyId)) { + return getStatus(cellReference, UPDATE_BUTTON_ENABLED); + } + + if (DELETE_OPTION.equals(propertyId) || COPY_OPTION.equals(propertyId)) { + return getStatus(cellReference, DELETE_COPY_BUTTON_ENABLED); } return null; } - private String getStatus(final CellReference cellReference, final RolloutStatus... expectedRolloutStatus) { + private String getStatus(final CellReference cellReference, final List expectedRolloutStatus) { final RolloutStatus currentRolloutStatus = getRolloutStatus(cellReference.getItemId()); - if (Arrays.asList(expectedRolloutStatus).contains(currentRolloutStatus)) { + if (expectedRolloutStatus.contains(currentRolloutStatus)) { return null; } @@ -622,7 +711,10 @@ public class RolloutListGrid extends AbstractGrid { } private String convertRolloutStatusToString(final RolloutStatus value) { - final StatusFontIcon statusFontIcon = statusIconMap.get(value); + StatusFontIcon statusFontIcon = statusIconMap.get(value); + if (statusFontIcon == null) { + statusFontIcon = new StatusFontIcon(FontAwesome.QUESTION_CIRCLE, SPUIStyleDefinitions.STATUS_ICON_BLUE); + } final String codePoint = HawkbitCommonUtil.getCodePoint(statusFontIcon); return HawkbitCommonUtil.getStatusLabelDetailsInString(codePoint, statusFontIcon.getStyle(), UIComponentIdProvider.ROLLOUT_STATUS_LABEL_ID); @@ -636,7 +728,7 @@ public class RolloutListGrid extends AbstractGrid { */ class TotalTargetCountStatusConverter implements Converter { - private static final long serialVersionUID = -5794528427855153924L; + private static final long serialVersionUID = 1L; @Override public TotalTargetCountStatus convertToModel(final String value, @@ -665,17 +757,18 @@ public class RolloutListGrid extends AbstractGrid { * Converter to convert 0 to empty, if total target groups is zero. * */ - class TotalTargetGroupsConverter implements Converter { + class TotalTargetGroupsConverter implements Converter { - private static final long serialVersionUID = 6589305227035220369L; + private static final long serialVersionUID = 1L; @Override - public Long convertToModel(final String value, final Class targetType, final Locale locale) { + public Integer convertToModel(final String value, final Class targetType, + final Locale locale) { return null; } @Override - public String convertToPresentation(final Long value, final Class targetType, + public String convertToPresentation(final Integer value, final Class targetType, final Locale locale) { if (value == 0) { return ""; @@ -684,8 +777,8 @@ public class RolloutListGrid extends AbstractGrid { } @Override - public Class getModelType() { - return Long.class; + public Class getModelType() { + return Integer.class; } @Override diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgroup/RolloutGroupBeanQuery.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgroup/RolloutGroupBeanQuery.java index 5e5ab0508..567004a78 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgroup/RolloutGroupBeanQuery.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgroup/RolloutGroupBeanQuery.java @@ -18,6 +18,7 @@ import java.util.Map; import org.eclipse.hawkbit.repository.RolloutGroupManagement; import org.eclipse.hawkbit.repository.RolloutManagement; +import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.ui.common.UserDetailsFormatter; import org.eclipse.hawkbit.ui.customrenderers.client.renderers.RolloutRendererData; @@ -26,6 +27,8 @@ import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil; import org.eclipse.hawkbit.ui.utils.SPDateTimeUtil; import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; import org.eclipse.hawkbit.ui.utils.SpringContextHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; @@ -42,9 +45,11 @@ public class RolloutGroupBeanQuery extends AbstractBeanQuery private static final long serialVersionUID = 5342450502894318589L; + private static final Logger LOG = LoggerFactory.getLogger(RolloutGroupBeanQuery.class); + private Sort sort = new Sort(Direction.ASC, "id"); - private transient Page firstPageRolloutGroupSets = null; + private transient Page firstPageRolloutGroupSets; private transient RolloutManagement rolloutManagement; @@ -95,12 +100,14 @@ public class RolloutGroupBeanQuery extends AbstractBeanQuery @Override protected List loadBeans(final int startIndex, final int count) { List proxyRolloutGroupsList = new ArrayList<>(); - if (startIndex == 0 && firstPageRolloutGroupSets != null) { - proxyRolloutGroupsList = firstPageRolloutGroupSets.getContent(); - } else if (null != rolloutId) { - proxyRolloutGroupsList = getRolloutGroupManagement() - .findAllRolloutGroupsWithDetailedStatus(rolloutId, new PageRequest(startIndex / count, count)) - .getContent(); + if (rolloutId != null) { + if (startIndex == 0 && firstPageRolloutGroupSets != null) { + proxyRolloutGroupsList = firstPageRolloutGroupSets.getContent(); + } else { + proxyRolloutGroupsList = getRolloutGroupManagement() + .findAllRolloutGroupsWithDetailedStatus(rolloutId, new PageRequest(startIndex / count, count)) + .getContent(); + } } return getProxyRolloutGroupList(proxyRolloutGroupsList); } @@ -151,10 +158,17 @@ public class RolloutGroupBeanQuery extends AbstractBeanQuery @Override public int size() { long size = 0; - if (null != rolloutId) { - firstPageRolloutGroupSets = getRolloutGroupManagement().findAllRolloutGroupsWithDetailedStatus(rolloutId, - new PageRequest(0, SPUIDefinitions.PAGE_SIZE, sort)); - size = firstPageRolloutGroupSets.getTotalElements(); + if (rolloutId != null) { + try { + firstPageRolloutGroupSets = getRolloutGroupManagement().findAllRolloutGroupsWithDetailedStatus( + rolloutId, new PageRequest(0, SPUIDefinitions.PAGE_SIZE, sort)); + size = firstPageRolloutGroupSets.getTotalElements(); + } catch (final EntityNotFoundException e) { + LOG.error("Rollout does not exists. Redirect to Rollouts overview", e); + rolloutUIState.setShowRolloutGroups(false); + rolloutUIState.setShowRollOuts(true); + return 0; + } } if (size > Integer.MAX_VALUE) { return Integer.MAX_VALUE; @@ -164,21 +178,21 @@ public class RolloutGroupBeanQuery extends AbstractBeanQuery } public RolloutManagement getRolloutManagement() { - if (null == rolloutManagement) { + if (rolloutManagement == null) { rolloutManagement = SpringContextHelper.getBean(RolloutManagement.class); } return rolloutManagement; } public RolloutGroupManagement getRolloutGroupManagement() { - if (null == rolloutGroupManagement) { + if (rolloutGroupManagement == null) { rolloutGroupManagement = SpringContextHelper.getBean(RolloutGroupManagement.class); } return rolloutGroupManagement; } public RolloutUIState getRolloutUIState() { - if (null == rolloutUIState) { + if (rolloutUIState == null) { rolloutUIState = SpringContextHelper.getBean(RolloutUIState.class); } return rolloutUIState; diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgroup/RolloutGroupListGrid.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgroup/RolloutGroupListGrid.java index 9670a77e5..f5d27a47a 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgroup/RolloutGroupListGrid.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgroup/RolloutGroupListGrid.java @@ -77,6 +77,20 @@ public class RolloutGroupListGrid extends AbstractGrid { new StatusFontIcon(FontAwesome.EXCLAMATION_CIRCLE, SPUIStyleDefinitions.STATUS_ICON_RED)); } + /** + * Constructor for RolloutGroupListGrid (Header with breadcrumbs) + * + * @param i18n + * I18N + * @param eventBus + * UIEventBus + * @param rolloutGroupManagement + * RolloutGroupManagement + * @param rolloutUIState + * RolloutUIState + * @param permissionChecker + * SpPermissionChecker + */ public RolloutGroupListGrid(final I18N i18n, final UIEventBus eventBus, final RolloutGroupManagement rolloutGroupManagement, final RolloutUIState rolloutUIState, final SpPermissionChecker permissionChecker) { @@ -87,6 +101,11 @@ public class RolloutGroupListGrid extends AbstractGrid { @EventBusListenerMethod(scope = EventScope.UI) void onEvent(final RolloutEvent event) { + if (RolloutEvent.SHOW_ROLLOUTS == event) { + rolloutUIState.setShowRollOuts(true); + rolloutUIState.setShowRolloutGroups(false); + rolloutUIState.setShowRolloutGroupTargets(false); + } if (RolloutEvent.SHOW_ROLLOUT_GROUPS != event) { return; } @@ -107,7 +126,6 @@ public class RolloutGroupListGrid extends AbstractGrid { if (!rolloutUIState.isShowRolloutGroups()) { return; } - ((LazyQueryContainer) getContainerDataSource()).refresh(); } @@ -149,7 +167,6 @@ public class RolloutGroupListGrid extends AbstractGrid { false); rolloutGroupGridContainer.addContainerProperty(SPUILabelDefinitions.VAR_TOTAL_TARGETS_COUNT_STATUS, TotalTargetCountStatus.class, null, false, false); - } @Override @@ -286,9 +303,10 @@ public class RolloutGroupListGrid extends AbstractGrid { @Override public void click(final RendererClickEvent event) { - rolloutUIState.setRolloutGroup( - rolloutGroupManagement.findRolloutGroupWithDetailedStatus((Long) event.getItemId()).get()); - eventBus.publish(this, RolloutEvent.SHOW_ROLLOUT_GROUP_TARGETS); + rolloutGroupManagement.findRolloutGroupWithDetailedStatus((Long) event.getItemId()).ifPresent(group -> { + rolloutUIState.setRolloutGroup(group); + eventBus.publish(this, RolloutEvent.SHOW_ROLLOUT_GROUP_TARGETS); + }); } } @@ -359,8 +377,8 @@ public class RolloutGroupListGrid extends AbstractGrid { final String codePoint = HawkbitCommonUtil.getCodePoint(statusFontIcon); return HawkbitCommonUtil.getStatusLabelDetailsInString(codePoint, statusFontIcon.getStyle(), UIComponentIdProvider.ROLLOUT_GROUP_STATUS_LABEL_ID); - } } + } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgroup/RolloutGroupsListHeader.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgroup/RolloutGroupsListHeader.java index 198177c58..f122d6a60 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgroup/RolloutGroupsListHeader.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgroup/RolloutGroupsListHeader.java @@ -38,6 +38,16 @@ public class RolloutGroupsListHeader extends AbstractGridHeader { private Label headerCaption; + /** + * Constructor for RolloutGroupsListHeader + * + * @param eventBus + * UIEventBus + * @param rolloutUiState + * RolloutUIState + * @param i18n + * I18N + */ public RolloutGroupsListHeader(final UIEventBus eventBus, final RolloutUIState rolloutUiState, final I18N i18n) { super(null, rolloutUiState, i18n); this.eventBus = eventBus; diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgroup/RolloutGroupsListView.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgroup/RolloutGroupsListView.java index 11df1c981..0ecd91595 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgroup/RolloutGroupsListView.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgroup/RolloutGroupsListView.java @@ -24,6 +24,20 @@ public class RolloutGroupsListView extends AbstractGridLayout { private static final long serialVersionUID = 7252345838154270259L; + /** + * Constructor for RolloutGroupsListView + * + * @param i18n + * I18N + * @param eventBus + * UIEventBus + * @param rolloutGroupManagement + * RolloutGroupManagement + * @param rolloutUIState + * RolloutUIState + * @param permissionChecker + * SpPermissionChecker + */ public RolloutGroupsListView(final I18N i18n, final UIEventBus eventBus, final RolloutGroupManagement rolloutGroupManagement, final RolloutUIState rolloutUIState, final SpPermissionChecker permissionChecker) { diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgrouptargets/RolloutGroupTargetsBeanQuery.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgrouptargets/RolloutGroupTargetsBeanQuery.java index 622ecaa9e..b6547b220 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgrouptargets/RolloutGroupTargetsBeanQuery.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgrouptargets/RolloutGroupTargetsBeanQuery.java @@ -16,6 +16,7 @@ import java.util.stream.Collectors; import org.eclipse.hawkbit.repository.RolloutGroupManagement; import org.eclipse.hawkbit.repository.RolloutManagement; +import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetWithActionStatus; @@ -26,6 +27,8 @@ import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil; import org.eclipse.hawkbit.ui.utils.SPDateTimeUtil; import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; import org.eclipse.hawkbit.ui.utils.SpringContextHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; @@ -41,6 +44,8 @@ public class RolloutGroupTargetsBeanQuery extends AbstractBeanQuery private static final long serialVersionUID = -8841076207255485907L; + private static final Logger LOG = LoggerFactory.getLogger(RolloutGroupTargetsBeanQuery.class); + private static final Sort sort = new Sort(Direction.ASC, "id"); private transient Page firstPageTargetSets; @@ -134,9 +139,16 @@ public class RolloutGroupTargetsBeanQuery extends AbstractBeanQuery long size = 0; if (rolloutGroup.isPresent()) { - firstPageTargetSets = getRolloutGroupManagement().findAllTargetsWithActionStatus( - new PageRequest(0, SPUIDefinitions.PAGE_SIZE, sort), rolloutGroup.get().getId()); - size = firstPageTargetSets.getTotalElements(); + try { + firstPageTargetSets = getRolloutGroupManagement().findAllTargetsWithActionStatus( + new PageRequest(0, SPUIDefinitions.PAGE_SIZE, sort), rolloutGroup.get().getId()); + size = firstPageTargetSets.getTotalElements(); + } catch (final EntityNotFoundException e) { + LOG.error("Rollout does not exists. Redirect to Rollouts overview", e); + rolloutUIState.setShowRolloutGroupTargets(false); + rolloutUIState.setShowRollOuts(true); + return 0; + } } getRolloutUIState().setRolloutGroupTargetsTotalCount(size); if (size > SPUIDefinitions.MAX_TABLE_ENTRIES) { diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgrouptargets/RolloutGroupTargetsListGrid.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgrouptargets/RolloutGroupTargetsListGrid.java index 45e32ded8..eba7eee85 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgrouptargets/RolloutGroupTargetsListGrid.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgrouptargets/RolloutGroupTargetsListGrid.java @@ -71,6 +71,16 @@ public class RolloutGroupTargetsListGrid extends AbstractGrid { new StatusFontIcon(FontAwesome.EXCLAMATION_CIRCLE, SPUIStyleDefinitions.STATUS_ICON_RED)); } + /** + * Constructor for RolloutGroupTargetsListGrid + * + * @param i18n + * I18N + * @param eventBus + * UIEventBus + * @param rolloutUIState + * RolloutUIState + */ public RolloutGroupTargetsListGrid(final I18N i18n, final UIEventBus eventBus, final RolloutUIState rolloutUIState) { super(i18n, eventBus, null); @@ -273,4 +283,5 @@ public class RolloutGroupTargetsListGrid extends AbstractGrid { } return "unknown"; } + } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgrouptargets/RolloutGroupTargetsListHeader.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgrouptargets/RolloutGroupTargetsListHeader.java index 49b56f1f3..b7a9297c4 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgrouptargets/RolloutGroupTargetsListHeader.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgrouptargets/RolloutGroupTargetsListHeader.java @@ -37,7 +37,7 @@ public class RolloutGroupTargetsListHeader extends AbstractGridHeader { private final transient EventBus.UIEventBus eventBus; - private Button rolloutsGroupViewLink; + private Button rolloutNameLink; private Label headerCaption; public RolloutGroupTargetsListHeader(final UIEventBus eventBus, final I18N i18n, @@ -57,7 +57,7 @@ public class RolloutGroupTargetsListHeader extends AbstractGridHeader { private void setCaptionDetails() { rolloutUIState.getRolloutGroup().map(RolloutGroup::getName).ifPresent(headerCaption::setCaption); - rolloutsGroupViewLink.setCaption(rolloutUIState.getRolloutGroup().map(RolloutGroup::getName).orElse("")); + rolloutNameLink.setCaption(rolloutUIState.getRolloutName().orElse("")); } @Override @@ -149,18 +149,17 @@ public class RolloutGroupTargetsListHeader extends AbstractGridHeader { rolloutsListViewLink.setCaption(i18n.get("message.rollouts")); rolloutsListViewLink.addClickListener(value -> showRolloutListView()); - rolloutsGroupViewLink = SPUIComponentProvider.getButton(null, "", "", null, false, null, + rolloutNameLink = SPUIComponentProvider.getButton(null, "", "", null, false, null, SPUIButtonStyleSmallNoBorder.class); - rolloutsGroupViewLink - .setStyleName(ValoTheme.LINK_SMALL + " " + "on-focus-no-border link rollout-caption-links"); - rolloutsGroupViewLink.setDescription("Rollouts Group"); - rolloutsGroupViewLink.addClickListener(value -> showRolloutGroupListView()); + rolloutNameLink.setStyleName(ValoTheme.LINK_SMALL + " " + "on-focus-no-border link rollout-caption-links"); + rolloutNameLink.setDescription("Rollout"); + rolloutNameLink.addClickListener(value -> showRolloutGroupListView()); final HorizontalLayout headerCaptionLayout = new HorizontalLayout(); headerCaptionLayout.addComponent(rolloutsListViewLink); headerCaptionLayout.addComponent(new Label(">")); - headerCaptionLayout.addComponent(rolloutsGroupViewLink); - headerCaptionLayout.addComponent(new Label(">")); + headerCaptionLayout.addComponent(rolloutNameLink); + headerCaptionLayout.addComponent(new Label("> ")); headerCaptionLayout.addComponent(headerCaption); return headerCaptionLayout; diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/HawkbitCommonUtil.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/HawkbitCommonUtil.java index ea8e59cd6..e433a6dbd 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/HawkbitCommonUtil.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/HawkbitCommonUtil.java @@ -23,7 +23,6 @@ import org.vaadin.addons.lazyquerycontainer.AbstractBeanQuery; import org.vaadin.addons.lazyquerycontainer.BeanQueryFactory; import org.vaadin.addons.lazyquerycontainer.LazyQueryContainer; import org.vaadin.addons.lazyquerycontainer.LazyQueryDefinition; -import org.vaadin.alump.distributionbar.DistributionBar; import com.google.common.base.Strings; import com.vaadin.data.Container; @@ -454,26 +453,19 @@ public final class HawkbitCommonUtil { .getItemProperty(SPUILabelDefinitions.VAR_TARGET_STATUS).getValue(); pinBtn.removeStyleName("statusIconRed statusIconBlue statusIconGreen statusIconYellow statusIconLightBlue"); if (updateStatus == TargetUpdateStatus.ERROR) { - pinBtn.addStyleName("statusIconRed"); + pinBtn.addStyleName(SPUIStyleDefinitions.STATUS_ICON_RED); } else if (updateStatus == TargetUpdateStatus.UNKNOWN) { - pinBtn.addStyleName("statusIconBlue"); + pinBtn.addStyleName(SPUIStyleDefinitions.STATUS_ICON_BLUE); } else if (updateStatus == TargetUpdateStatus.IN_SYNC) { - pinBtn.addStyleName("statusIconGreen"); + pinBtn.addStyleName(SPUIStyleDefinitions.STATUS_ICON_GREEN); } else if (updateStatus == TargetUpdateStatus.PENDING) { - pinBtn.addStyleName("statusIconYellow"); + pinBtn.addStyleName(SPUIStyleDefinitions.STATUS_ICON_YELLOW); } else if (updateStatus == TargetUpdateStatus.REGISTERED) { - pinBtn.addStyleName("statusIconLightBlue"); + pinBtn.addStyleName(SPUIStyleDefinitions.STATUS_ICON_LIGHT_BLUE); } } } - private static void setBarPartSize(final DistributionBar bar, final String statusName, final int count, - final int index) { - bar.setPartSize(index, count); - bar.setPartTooltip(index, statusName); - bar.setPartStyleName(index, "status-bar-part-" + statusName); - } - /** * Formats the finished percentage of a rollout group into a string with one * digit after comma. @@ -504,10 +496,6 @@ public final class HawkbitCommonUtil { return String.format("%.1f", tmpFinishedPercentage); } - private static Long getStatusCount(final String propertName, final Item item) { - return (Long) item.getItemProperty(propertName).getValue(); - } - /** * Returns a formatted string as needed by label custom render .This string * holds the properties of a status label. diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIStyleDefinitions.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIStyleDefinitions.java index 57791cfdd..c105a2b7f 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIStyleDefinitions.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIStyleDefinitions.java @@ -178,6 +178,11 @@ public final class SPUIStyleDefinitions { */ public static final String TARGET_FILTER_SEARCH_PROGRESS_INDICATOR_STYLE = "target-filter-spinner"; + /** + * Rollout deleting spinner for status column + */ + public static final String ROLLOUT_DELETING_SPINNER = "rollout-deleting-spinner"; + /** * Status icon style - red color. */ @@ -213,6 +218,11 @@ public final class SPUIStyleDefinitions { */ public static final String STATUS_SPINNER_GREY = "greySpinner"; + /** + * Status icon spinner style - red color. + */ + public static final String STATUS_SPINNER_RED = "redSpinner"; + /** * Status icon spinner style - blue color. */ diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIComponentIdProvider.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIComponentIdProvider.java index 426db45c1..ce2afad5c 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIComponentIdProvider.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIComponentIdProvider.java @@ -802,6 +802,11 @@ public final class UIComponentIdProvider { */ public static final String ROLLOUT_COPY_BUTTON_ID = ROLLOUT_ACTION_ID + ".12"; + /** + * Rollout delete button id. + */ + public static final String ROLLOUT_DELETE_BUTTON_ID = ROLLOUT_ACTION_ID + ".13"; + /** * Rollout status label id. */ diff --git a/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/others.scss b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/others.scss index ac6402910..88dac5700 100644 --- a/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/others.scss +++ b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/others.scss @@ -209,6 +209,11 @@ color: $hawkbit-primary-color; } + + .redSpinner{ + @include valo-spinner($size: $v-font-size--small,$color: $red-color); + pointer-events: auto !important; + } .greySpinner{ @include valo-spinner($size: $v-font-size--small,$color: $grey-color); diff --git a/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/table-header-common.scss b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/table-header-common.scss index 8e543aa31..5b1ad6a87 100644 --- a/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/table-header-common.scss +++ b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/table-header-common.scss @@ -12,8 +12,8 @@ //Table header - filter text field style .filter-box { height: 25px !important; - transition: width .5s ease-in-out; float: right; + visibility: visible; border-radius: $v-border-radius; } @@ -23,9 +23,7 @@ } .filter-box-hide { - width: 0 !important; - padding: 0 !important; - border-width: 0 !important; + visibility: hidden; } //Search rest icon color diff --git a/hawkbit-ui/src/main/resources/messages.properties b/hawkbit-ui/src/main/resources/messages.properties index 82c6e642e..3346d5851 100644 --- a/hawkbit-ui/src/main/resources/messages.properties +++ b/hawkbit-ui/src/main/resources/messages.properties @@ -61,6 +61,7 @@ header.action.run=Run header.action.pause=Pause header.action.update=Edit header.action.copy=Copy +header.action.delete=Delete header.status=Status # event container @@ -117,6 +118,7 @@ caption.forcequit.action.confirmbox = Confirm force quit action caption.forced.datefield = Force update at time caption.force.action.confirmbox = Confirm Force Active Action caption.confirm.abort.action = Confirm Abort Action +caption.confirm.delete.rollout = Confirm Rollout deletion caption.filter.delete.confirmbox = Confirm Filter Delete Action caption.metadata.popup = Metadata of @@ -536,12 +538,17 @@ message.rollout.field.value.range = Value should be in range {0} to {1} message.rollout.started = Rollout {0} started successfully message.rollout.paused = Rollout {0} paused successfully message.rollout.resumed = Rollout {0} resumed successfully +message.rollout.deleted = Rollout {0} deleted successfully message.rollout.noofgroups.or.targetfilter.missing = Please enter number of groups and select target filter message.rollouts = Rollouts label.target.per.group = Targets per group : message.dist.already.assigned = Distribution {0} is already assigned to target message.error.creating.rollout = Server error. Error creating Rollout. Please contact the administrator message.error.starting.rollout = Server error. Error starting Rollout. Please contact the administrator + +message.delete.rollout = You are about to delete the rollout "{0}".\n{1}Are you sure? +message.delete.rollout.details = There are {0} running updates that will continue and {1} scheduled updates that will terminate.\n + caption.rollout.group.definition.desc = Define which groups the Rollout should have. header.target.percentage = Target percentage textfield.target.percentage = Target percentage