From f18825ce34e287fb379696a7732bf786d7fa4713 Mon Sep 17 00:00:00 2001 From: Michael Hirsch Date: Thu, 4 Feb 2016 15:10:44 +0100 Subject: [PATCH] Initial Contribution of the rollout-management feature - Repository functionality for rollout, rolloutgroup entities - Rollout scheduler to watch and handle running rollouts and start next group of rollout - Vaadin view to administrate rollouts and reflect the current rollout status - REST resources to cover rollout creation, updating, starting, pausing and resuming Signed-off-by: Michael Hirsch --- .../eventbus/EventBusAutoConfiguration.java | 9 + .../main/resources/hawkbitdefaults.properties | 2 +- hawkbit-core/pom.xml | 1 - .../hawkbit/eventbus/event/AbstractEvent.java | 45 + .../event/CancelTargetAssignmentEvent.java | 10 +- .../hawkbit/exception/SpServerError.java | 8 +- .../hawkbit/repository/RolloutFields.java | 40 + .../repository/RolloutGroupFields.java | 40 + .../amqp/AmqpMessageDispatcherService.java | 17 +- .../amqp/AmqpMessageHandlerService.java | 36 +- .../AmqpMessageDispatcherServiceTest.java | 8 +- hawkbit-repository/README.md | 37 +- .../MultiTenantJpaTransactionManager.java | 4 +- .../RepositoryApplicationConfiguration.java | 11 + .../org/eclipse/hawkbit/cache/CacheKeys.java | 2 + .../hawkbit/cache/CacheWriteNotify.java | 39 + .../eventbus/EntityChangeEventListener.java | 1 + .../EntityPropertyChangeListener.java | 107 ++ .../event/AbstractPropertyChangeEvent.java | 83 ++ .../eventbus/event/ActionCreatedEvent.java | 26 + .../event/ActionPropertyChangeEvent.java | 30 + .../hawkbit/eventbus/event/EventMerger.java | 185 +++ .../eventbus/event/RolloutChangeEvent.java | 40 + .../event/RolloutGroupChangeEvent.java | 47 + .../event/RolloutGroupCreatedEvent.java | 65 ++ .../RolloutGroupPropertyChangeEvent.java | 32 + .../event/RolloutPropertyChangeEvent.java | 31 + .../TargetAssignDistributionSetEvent.java | 11 +- .../eventbus/event/TargetCreatedEvent.java | 2 - .../hawkbit/repository/ActionRepository.java | 183 ++- .../repository/DeploymentManagement.java | 256 ++++- .../repository/OffsetBasedPageRequest.java | 69 ++ .../repository/RolloutGroupManagement.java | 262 +++++ .../repository/RolloutGroupRepository.java | 131 +++ .../hawkbit/repository/RolloutManagement.java | 870 ++++++++++++++ .../hawkbit/repository/RolloutRepository.java | 87 ++ .../hawkbit/repository/RolloutScheduler.java | 91 ++ .../RolloutTargetGroupRepository.java | 22 + .../hawkbit/repository/SystemManagement.java | 13 +- .../hawkbit/repository/TargetRepository.java | 41 + .../RolloutIllegalStateException.java | 63 + .../hawkbit/repository/model/Action.java | 46 +- .../model/ActionWithStatusCount.java | 16 +- .../hawkbit/repository/model/BaseEntity.java | 9 +- .../hawkbit/repository/model/Rollout.java | 311 +++++ .../repository/model/RolloutGroup.java | 481 ++++++++ .../repository/model/RolloutTargetGroup.java | 65 ++ .../model/RolloutTargetGroupId.java | 55 + .../model/SoftwareModuleMetadata.java | 2 +- .../hawkbit/repository/model/Target.java | 5 + .../model/TargetWithActionStatus.java | 63 + .../model/TotalTargetCountActionStatus.java | 53 + .../model/TotalTargetCountStatus.java | 125 ++ .../AfterTransactionCommitExecutorHolder.java | 54 + .../model/helper/EventBusHolder.java | 55 + .../hawkbit/repository/rsql/RSQLUtility.java | 34 +- .../condition/PauseRolloutGroupAction.java | 55 + .../RolloutGroupActionEvaluator.java | 22 + .../RolloutGroupConditionEvaluator.java | 22 + ...artNextGroupRolloutGroupSuccessAction.java | 90 ++ .../ThresholdRolloutGroupErrorCondition.java | 68 ++ ...ThresholdRolloutGroupSuccessCondition.java | 58 + .../H2/V1_6_0__rollout_management___H2.sql | 103 ++ .../V1_6_0__rollout_management___MYSQL.sql | 103 ++ .../hawkbit/AbstractIntegrationTest.java | 12 + .../eclipse/hawkbit/TestConfiguration.java | 11 +- .../eclipse/hawkbit/cache/CacheKeysTest.java | 1 - .../repository/RolloutManagementTest.java | 1024 +++++++++++++++++ .../rsql/RSQLRolloutGroupFields.java | 94 ++ .../hawkbit/rest/resource/RestConstants.java | 10 + .../model/action/ActionPagedList.java | 2 +- .../model/rollout/RolloutCondition.java | 69 ++ .../model/rollout/RolloutErrorAction.java | 58 + .../model/rollout/RolloutPagedList.java | 36 + .../model/rollout/RolloutResponseBody.java | 124 ++ .../model/rollout/RolloutRestRequestBody.java | 175 +++ .../model/rollout/RolloutSuccessAction.java | 69 ++ .../rolloutgroup/RolloutGroupPagedList.java | 35 + .../RolloutGroupResponseBody.java | 61 + .../hawkbit/cache/CacheWriteNotify.java | 97 -- .../hawkbit/rest/resource/PagingUtility.java | 23 + .../resource/ResponseExceptionHandler.java | 5 +- .../hawkbit/rest/resource/RolloutMapper.java | 162 +++ .../rest/resource/RolloutResource.java | 402 +++++++ .../hawkbit/rest/resource/JsonBuilder.java | 68 +- .../rest/resource/RolloutResourceTest.java | 598 ++++++++++ .../rest/resource/SuccessCondition.java | 21 + .../im/authentication/SpPermission.java | 40 +- .../security/SecurityContextTenantAware.java | 15 +- .../security/SystemSecurityContext.java | 112 ++ hawkbit-ui/pom.xml | 11 + .../repository/SpPermissionChecker.java | 38 + .../eclipse/hawkbit/ui/AppWidgetSet.gwt.xml | 6 + .../artifacts/UploadArtifactViewMenuItem.java | 2 +- .../hawkbit/ui/components/ProxyTarget.java | 18 + .../DistributionsViewMenuItem.java | 2 +- .../documentation/DocumentationPageLink.java | 5 +- .../CreateOrUpdateFilterTable.java | 2 +- .../FilterManagementViewMenuItem.java | 2 +- .../FilterQueryValidation.java | 8 +- .../TargetFilterBeanQuery.java | 1 + .../actionhistory/ActionHistoryTable.java | 119 +- .../event/DistributionTagDropEvent.java | 12 +- .../footer/ActionTypeOptionGroupLayout.java | 6 +- .../management/targettable/TargetTable.java | 25 +- .../ui/rollout/AbstractSimpleTable.java | 116 ++ .../ui/rollout/AbstractSimpleTableHeader.java | 273 +++++ .../ui/rollout/AbstractSimpleTableLayout.java | 92 ++ .../rollout/AddUpdateRolloutWindowLayout.java | 848 ++++++++++++++ .../hawkbit/ui/rollout/DistBeanQuery.java | 168 +++ .../hawkbit/ui/rollout/ProxyRollout.java | 258 +++++ .../hawkbit/ui/rollout/ProxyRolloutGroup.java | 242 ++++ .../hawkbit/ui/rollout/RolloutBeanQuery.java | 214 ++++ .../ui/rollout/RolloutGroupBeanQuery.java | 205 ++++ .../ui/rollout/RolloutGroupListTable.java | 361 ++++++ .../rollout/RolloutGroupTargetsBeanQuery.java | 179 +++ .../RolloutGroupTargetsCountLabelMessage.java | 121 ++ .../RolloutGroupTargetsListHeader.java | 218 ++++ .../rollout/RolloutGroupTargetsListTable.java | 229 ++++ .../rollout/RolloutGroupTargetsListView.java | 57 + .../ui/rollout/RolloutGroupsListHeader.java | 202 ++++ .../ui/rollout/RolloutGroupsListView.java | 52 + .../hawkbit/ui/rollout/RolloutListHeader.java | 169 +++ .../hawkbit/ui/rollout/RolloutListTable.java | 552 +++++++++ .../hawkbit/ui/rollout/RolloutListView.java | 55 + .../hawkbit/ui/rollout/RolloutView.java | 148 +++ .../ui/rollout/RolloutViewMenuItem.java | 58 + .../ui/rollout/event/RolloutEvent.java | 17 + .../ui/rollout/state/RolloutUIState.java | 181 +++ .../hawkbit/ui/utils/HawkbitCommonUtil.java | 116 ++ .../ui/utils/SPUIComponetIdProvider.java | 141 +++ .../hawkbit/ui/utils/SPUIDefinitions.java | 81 +- .../ui/utils/SPUILabelDefinitions.java | 76 ++ .../ui/utils/SPUIStyleDefinitions.java | 13 + .../customstyles/hawkbitvariables.scss | 8 + .../themes/hawkbit/customstyles/others.scss | 37 +- .../themes/hawkbit/customstyles/rollout.scss | 60 + .../customstyles/statusprogressbar.scss | 64 ++ .../customstyles/table-header-common.scss | 2 +- .../VAADIN/themes/hawkbit/hawkbittheme.scss | 9 +- .../VAADIN/themes/hawkbit/styles.scss | 2 + .../src/main/resources/messages.properties | 49 +- .../src/main/resources/messages_de.properties | 52 +- .../src/main/resources/messages_en.properties | 80 +- pom.xml | 10 + 145 files changed, 13995 insertions(+), 353 deletions(-) create mode 100644 hawkbit-core/src/main/java/org/eclipse/hawkbit/eventbus/event/AbstractEvent.java create mode 100644 hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/RolloutFields.java create mode 100644 hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/RolloutGroupFields.java create mode 100644 hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/EntityPropertyChangeListener.java create mode 100644 hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/AbstractPropertyChangeEvent.java create mode 100644 hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/ActionCreatedEvent.java create mode 100644 hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/ActionPropertyChangeEvent.java create mode 100644 hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/EventMerger.java create mode 100644 hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/RolloutChangeEvent.java create mode 100644 hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/RolloutGroupChangeEvent.java create mode 100644 hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/RolloutGroupCreatedEvent.java create mode 100644 hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/RolloutGroupPropertyChangeEvent.java create mode 100644 hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/RolloutPropertyChangeEvent.java create mode 100644 hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/OffsetBasedPageRequest.java create mode 100644 hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutGroupManagement.java create mode 100644 hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutGroupRepository.java create mode 100644 hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java create mode 100644 hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutRepository.java create mode 100644 hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutScheduler.java create mode 100644 hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutTargetGroupRepository.java create mode 100644 hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/exception/RolloutIllegalStateException.java create mode 100644 hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Rollout.java create mode 100644 hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/RolloutGroup.java create mode 100644 hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/RolloutTargetGroup.java create mode 100644 hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/RolloutTargetGroupId.java create mode 100644 hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TargetWithActionStatus.java create mode 100644 hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TotalTargetCountActionStatus.java create mode 100644 hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TotalTargetCountStatus.java create mode 100644 hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/helper/AfterTransactionCommitExecutorHolder.java create mode 100644 hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/helper/EventBusHolder.java create mode 100644 hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/PauseRolloutGroupAction.java create mode 100644 hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/RolloutGroupActionEvaluator.java create mode 100644 hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/RolloutGroupConditionEvaluator.java create mode 100644 hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/StartNextGroupRolloutGroupSuccessAction.java create mode 100644 hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/ThresholdRolloutGroupErrorCondition.java create mode 100644 hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/ThresholdRolloutGroupSuccessCondition.java create mode 100644 hawkbit-repository/src/main/resources/db/migration/H2/V1_6_0__rollout_management___H2.sql create mode 100644 hawkbit-repository/src/main/resources/db/migration/MYSQL/V1_6_0__rollout_management___MYSQL.sql create mode 100644 hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/RolloutManagementTest.java create mode 100644 hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLRolloutGroupFields.java create mode 100644 hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutCondition.java create mode 100644 hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutErrorAction.java create mode 100644 hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutPagedList.java create mode 100644 hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutResponseBody.java create mode 100644 hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutRestRequestBody.java create mode 100644 hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutSuccessAction.java create mode 100644 hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rolloutgroup/RolloutGroupPagedList.java create mode 100644 hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rolloutgroup/RolloutGroupResponseBody.java delete mode 100644 hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/cache/CacheWriteNotify.java create mode 100644 hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/RolloutMapper.java create mode 100644 hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/RolloutResource.java create mode 100644 hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/RolloutResourceTest.java create mode 100644 hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/SuccessCondition.java create mode 100644 hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SystemSecurityContext.java create mode 100644 hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/AbstractSimpleTable.java create mode 100644 hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/AbstractSimpleTableHeader.java create mode 100644 hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/AbstractSimpleTableLayout.java create mode 100644 hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/AddUpdateRolloutWindowLayout.java create mode 100644 hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/DistBeanQuery.java create mode 100644 hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/ProxyRollout.java create mode 100644 hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/ProxyRolloutGroup.java create mode 100644 hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutBeanQuery.java create mode 100644 hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupBeanQuery.java create mode 100644 hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupListTable.java create mode 100644 hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupTargetsBeanQuery.java create mode 100644 hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupTargetsCountLabelMessage.java create mode 100644 hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupTargetsListHeader.java create mode 100644 hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupTargetsListTable.java create mode 100644 hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupTargetsListView.java create mode 100644 hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupsListHeader.java create mode 100644 hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupsListView.java create mode 100644 hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutListHeader.java create mode 100644 hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutListTable.java create mode 100644 hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutListView.java create mode 100644 hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutView.java create mode 100644 hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutViewMenuItem.java create mode 100644 hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/event/RolloutEvent.java create mode 100644 hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/state/RolloutUIState.java create mode 100644 hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/rollout.scss create mode 100644 hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/statusprogressbar.scss diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/eventbus/EventBusAutoConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/eventbus/EventBusAutoConfiguration.java index e09c0a68e..37e72880c 100644 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/eventbus/EventBusAutoConfiguration.java +++ b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/eventbus/EventBusAutoConfiguration.java @@ -12,6 +12,7 @@ import java.util.concurrent.Executor; import org.eclipse.hawkbit.eventbus.EventBusSubscriberProcessor; import org.eclipse.hawkbit.eventbus.EventSubscriber; +import org.eclipse.hawkbit.repository.model.helper.EventBusHolder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -54,4 +55,12 @@ public class EventBusAutoConfiguration { return new EventBusSubscriberProcessor(); } + /** + * @return the singleton instance of the {@link EventBusHolder} + */ + @Bean + public EventBusHolder eventBusHolder() { + return EventBusHolder.getInstance(); + } + } diff --git a/hawkbit-autoconfigure/src/main/resources/hawkbitdefaults.properties b/hawkbit-autoconfigure/src/main/resources/hawkbitdefaults.properties index 69c9a47d2..4020d0cdd 100644 --- a/hawkbit-autoconfigure/src/main/resources/hawkbitdefaults.properties +++ b/hawkbit-autoconfigure/src/main/resources/hawkbitdefaults.properties @@ -45,7 +45,7 @@ spring.mvc.favicon.enabled=false hawkbit.threadpool.corethreads=5 hawkbit.threadpool.maxthreads=20 hawkbit.threadpool.idletimeout=10000 -hawkbit.threadpool.queuesize=250 +hawkbit.threadpool.queuesize=20000 # Defines the polling time for the controllers in HH:MM:SS notation hawkbit.controller.pollingTime=00:05:00 diff --git a/hawkbit-core/pom.xml b/hawkbit-core/pom.xml index e5d0f65fd..b56d30075 100644 --- a/hawkbit-core/pom.xml +++ b/hawkbit-core/pom.xml @@ -32,7 +32,6 @@ com.google.guava guava - org.springframework.boot diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/eventbus/event/AbstractEvent.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/eventbus/event/AbstractEvent.java new file mode 100644 index 000000000..498418169 --- /dev/null +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/eventbus/event/AbstractEvent.java @@ -0,0 +1,45 @@ +/** + * 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.eventbus.event; + +/** + * Abstract event definition class which holds the necessary revsion and tenant + * information which every event needs. + * + * @author Michael Hirsch + * @see AbstractDistributedEvent for events which should be distributed to other + * cluster nodes + */ +public class AbstractEvent implements Event { + + private final long revision; + private final String tenant; + + /** + * @param revision + * the revision number of the event + * @param tenant + * the tenant of the event + */ + protected AbstractEvent(final long revision, final String tenant) { + this.revision = revision; + this.tenant = tenant; + } + + @Override + public long getRevision() { + return revision; + } + + @Override + public String getTenant() { + return tenant; + } + +} diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/eventbus/event/CancelTargetAssignmentEvent.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/eventbus/event/CancelTargetAssignmentEvent.java index ad421b266..da93cc1a3 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/eventbus/event/CancelTargetAssignmentEvent.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/eventbus/event/CancelTargetAssignmentEvent.java @@ -17,7 +17,7 @@ import java.net.URI; * * */ -public class CancelTargetAssignmentEvent { +public class CancelTargetAssignmentEvent extends AbstractEvent { private final String controllerId; private final Long actionId; @@ -26,6 +26,10 @@ public class CancelTargetAssignmentEvent { /** * Creates a new {@link CancelTargetAssignmentEvent}. * + * @param revision + * the revision for this event + * @param tenant + * the tenant for this event * @param controllerId * the ID of the controller * @param actionId @@ -33,7 +37,9 @@ public class CancelTargetAssignmentEvent { * @param targetAdress * the targetAdress of the target */ - public CancelTargetAssignmentEvent(final String controllerId, final Long actionId, final URI targetAdress) { + public CancelTargetAssignmentEvent(final long revision, final String tenant, final String controllerId, + final Long actionId, final URI targetAdress) { + super(revision, tenant); this.controllerId = controllerId; this.actionId = actionId; this.targetAdress = targetAdress; diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerError.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerError.java index 91559cc88..cdf91f879 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerError.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerError.java @@ -174,7 +174,13 @@ public enum SpServerError { * */ SP_REPO_ENTITY_READ_ONLY("hawkbit.server.error.entityreadonly", - "The given entity is read only and the change cannot be completed."); + "The given entity is read only and the change cannot be completed."), + + /** + * + */ + SP_ROLLOUT_ILLEGAL_STATE("hawkbit.server.error.rollout.illegalstate", + "The rollout is currently in the wrong state for the current operation"); private final String key; private final String message; diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/RolloutFields.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/RolloutFields.java new file mode 100644 index 000000000..2c7f7b971 --- /dev/null +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/RolloutFields.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; + +/** + * Describing the fields of the Rollout model which can be used in the REST API + * e.g. for sorting etc. + * + */ +public enum RolloutFields implements FieldNameProvider { + /** + * The name field. + */ + NAME("name"), + /** + * The description field. + */ + DESCRIPTION("description"), + /** + * The id field. + */ + ID("id"); + + private final String fieldName; + + private RolloutFields(final String fieldName) { + this.fieldName = fieldName; + } + + @Override + public String getFieldName() { + return fieldName; + } +} diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/RolloutGroupFields.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/RolloutGroupFields.java new file mode 100644 index 000000000..2c8ebf8ac --- /dev/null +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/RolloutGroupFields.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; + +/** + * Describing the fields of the RolloutGroup model which can be used in the REST + * API e.g. for sorting etc. + * + */ +public enum RolloutGroupFields implements FieldNameProvider { + /** + * The name field. + */ + NAME("name"), + /** + * The description field. + */ + DESCRIPTION("description"), + /** + * The id field. + */ + ID("id"); + + private final String fieldName; + + private RolloutGroupFields(final String fieldName) { + this.fieldName = fieldName; + } + + @Override + public String getFieldName() { + return fieldName; + } +} 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 7469e6768..3708f942b 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 @@ -79,8 +79,10 @@ public class AmqpMessageDispatcherService { downloadAndUpdateRequest.addSoftwareModule(amqpSoftwareModule); } - final Message message = rabbitTemplate.getMessageConverter().toMessage(downloadAndUpdateRequest, - createConnectorMessageProperties(controllerId, EventTopic.DOWNLOAD_AND_INSTALL)); + final Message message = rabbitTemplate.getMessageConverter().toMessage( + downloadAndUpdateRequest, + createConnectorMessageProperties(targetAssignDistributionSetEvent.getTenant(), controllerId, + EventTopic.DOWNLOAD_AND_INSTALL)); sendMessage(targetAdress.getHost(), message); } @@ -96,8 +98,10 @@ public class AmqpMessageDispatcherService { final CancelTargetAssignmentEvent cancelTargetAssignmentDistributionSetEvent) { final String controllerId = cancelTargetAssignmentDistributionSetEvent.getControllerId(); final Long actionId = cancelTargetAssignmentDistributionSetEvent.getActionId(); - final Message message = rabbitTemplate.getMessageConverter().toMessage(actionId, - createConnectorMessageProperties(controllerId, EventTopic.CANCEL_DOWNLOAD)); + final Message message = rabbitTemplate.getMessageConverter().toMessage( + actionId, + createConnectorMessageProperties(cancelTargetAssignmentDistributionSetEvent.getTenant(), controllerId, + EventTopic.CANCEL_DOWNLOAD)); sendMessage(cancelTargetAssignmentDistributionSetEvent.getTargetAdress().getHost(), message); @@ -117,11 +121,12 @@ public class AmqpMessageDispatcherService { rabbitTemplate.send(message); } - private MessageProperties createConnectorMessageProperties(final String controllerId, final EventTopic topic) { + private MessageProperties createConnectorMessageProperties(final String tenant, final String controllerId, + final EventTopic topic) { final MessageProperties messageProperties = createMessageProperties(); messageProperties.setHeader(MessageHeaderKey.TOPIC, topic); messageProperties.setHeader(MessageHeaderKey.THING_ID, controllerId); - messageProperties.setHeader(MessageHeaderKey.TENANT, tenantAware.getCurrentTenant()); + messageProperties.setHeader(MessageHeaderKey.TENANT, tenant); messageProperties.setHeader(MessageHeaderKey.TYPE, MessageType.EVENT); return messageProperties; } diff --git a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java index 640c0e270..8921e368c 100644 --- a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java +++ b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java @@ -153,8 +153,8 @@ public class AmqpMessageHandlerService { final String sha1 = secruityToken.getSha1(); try { SecurityContextHolder.getContext().setAuthentication(authenticationManager.doAuthenticate(secruityToken)); - final LocalArtifact localArtifact = artifactManagement - .findFirstLocalArtifactsBySHA1(secruityToken.getSha1()); + final LocalArtifact localArtifact = artifactManagement.findFirstLocalArtifactsBySHA1(secruityToken + .getSha1()); if (localArtifact == null) { throw new EntityNotFoundException(); } @@ -177,9 +177,9 @@ public class AmqpMessageHandlerService { final String downloadId = UUID.randomUUID().toString(); final DownloadArtifactCache downloadCache = new DownloadArtifactCache(DownloadType.BY_SHA1, sha1); cache.put(downloadId, downloadCache); - authentificationResponse - .setDownloadUrl(UriComponentsBuilder.fromUri(hostnameResolver.resolveHostname().toURI()) - .path("/api/v1/downloadserver/downloadId/").path(downloadId).build().toUriString()); + authentificationResponse.setDownloadUrl(UriComponentsBuilder + .fromUri(hostnameResolver.resolveHostname().toURI()).path("/api/v1/downloadserver/downloadId/") + .path(downloadId).build().toUriString()); authentificationResponse.setResponseCode(HttpStatus.OK.value()); } catch (final BadCredentialsException | AuthenticationServiceException | CredentialsExpiredException e) { LOG.error("Login failed", e); @@ -219,9 +219,9 @@ public class AmqpMessageHandlerService { } private static void setTenantSecurityContext(final String tenantId) { - final AnonymousAuthenticationToken authenticationToken = new AnonymousAuthenticationToken( - UUID.randomUUID().toString(), "AMQP-Controller", - Collections.singletonList(new SimpleGrantedAuthority(SpringEvalExpressions.CONTROLLER_ROLE_ANONYMOUS))); + final AnonymousAuthenticationToken authenticationToken = new AnonymousAuthenticationToken(UUID.randomUUID() + .toString(), "AMQP-Controller", Collections.singletonList(new SimpleGrantedAuthority( + SpringEvalExpressions.CONTROLLER_ROLE_ANONYMOUS))); authenticationToken.setDetails(new TenantAwareAuthenticationDetails(tenantId, true)); setSecurityContext(authenticationToken); } @@ -267,8 +267,8 @@ public class AmqpMessageHandlerService { final DistributionSet distributionSet = action.getDistributionSet(); final List softwareModuleList = controllerManagement .findSoftwareModulesByDistributionSet(distributionSet); - eventBus.post(new TargetAssignDistributionSetEvent(target.getControllerId(), action.getId(), softwareModuleList, - target.getTargetInfo().getAddress())); + eventBus.post(new TargetAssignDistributionSetEvent(target.getOptLockRevision(), target.getTenant(), target + .getControllerId(), action.getId(), softwareModuleList, target.getTargetInfo().getAddress())); } @@ -299,8 +299,8 @@ public class AmqpMessageHandlerService { private void updateActionStatus(final Message message) { final ActionUpdateStatus actionUpdateStatus = convertMessage(message, ActionUpdateStatus.class); final Long actionId = actionUpdateStatus.getActionId(); - LOG.debug("Target notifies intermediate about action {} with status {}.", actionId, - actionUpdateStatus.getActionStatus().name()); + LOG.debug("Target notifies intermediate about action {} with status {}.", actionId, actionUpdateStatus + .getActionStatus().name()); if (actionId == null) { logAndThrowMessageError(message, "Invalid message no action id"); @@ -309,8 +309,8 @@ public class AmqpMessageHandlerService { final Action action = controllerManagement.findActionWithDetails(actionId); if (action == null) { - logAndThrowMessageError(message, - "Got intermediate notification about action " + actionId + " but action does not exist"); + logAndThrowMessageError(message, "Got intermediate notification about action " + actionId + + " but action does not exist"); } final ActionStatus actionStatus = new ActionStatus(); @@ -371,8 +371,8 @@ public class AmqpMessageHandlerService { // back to running action status } else { - logAndThrowMessageError(message, - "Cancel Recjected message is not allowed, if action is on state: " + action.getStatus()); + logAndThrowMessageError(message, "Cancel Recjected message is not allowed, if action is on state: " + + action.getStatus()); } } @@ -387,8 +387,8 @@ public class AmqpMessageHandlerService { */ @SuppressWarnings("unchecked") private T convertMessage(final Message message, final Class clazz) { - message.getMessageProperties().getHeaders().put(AbstractJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME, - clazz.getTypeName()); + message.getMessageProperties().getHeaders() + .put(AbstractJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME, clazz.getTypeName()); return (T) rabbitTemplate.getMessageConverter().fromMessage(message); } diff --git a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java index c423b76bf..348e8dea4 100644 --- a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java +++ b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java @@ -87,7 +87,7 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTestWit @Description("Verfies that download and install event with no software modul works") public void testSendDownloadRequesWithEmptySoftwareModules() { final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = new TargetAssignDistributionSetEvent( - CONTROLLER_ID, 1l, new ArrayList(), IpUtil.createAmqpUri("mytest")); + 1L, "default", CONTROLLER_ID, 1l, new ArrayList(), IpUtil.createAmqpUri("mytest")); amqpMessageDispatcherService.targetAssignDistributionSet(targetAssignDistributionSetEvent); final Message sendMessage = createArgumentCapture(targetAssignDistributionSetEvent.getTargetAdress().getHost()); final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage); @@ -100,7 +100,7 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTestWit final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, distributionSetManagement); final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = new TargetAssignDistributionSetEvent( - CONTROLLER_ID, 1l, dsA.getModules(), IpUtil.createAmqpUri("mytest")); + 1L, "default", CONTROLLER_ID, 1l, dsA.getModules(), IpUtil.createAmqpUri("mytest")); amqpMessageDispatcherService.targetAssignDistributionSet(targetAssignDistributionSetEvent); final Message sendMessage = createArgumentCapture(targetAssignDistributionSetEvent.getTargetAdress().getHost()); final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage); @@ -134,7 +134,7 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTestWit Mockito.when(rabbitTemplate.convertSendAndReceive(any())).thenReturn(receivedList); final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = new TargetAssignDistributionSetEvent( - CONTROLLER_ID, 1l, dsA.getModules(), IpUtil.createAmqpUri("mytest")); + 1L, "default", CONTROLLER_ID, 1l, dsA.getModules(), IpUtil.createAmqpUri("mytest")); amqpMessageDispatcherService.targetAssignDistributionSet(targetAssignDistributionSetEvent); final Message sendMessage = createArgumentCapture(targetAssignDistributionSetEvent.getTargetAdress().getHost()); final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage); @@ -152,7 +152,7 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTestWit @Description("Verfies that send cancel event works") public void testSendCancelRequest() { final CancelTargetAssignmentEvent cancelTargetAssignmentDistributionSetEvent = new CancelTargetAssignmentEvent( - CONTROLLER_ID, 1l, IpUtil.createAmqpUri("mytest")); + 1L, "default", CONTROLLER_ID, 1l, IpUtil.createAmqpUri("mytest")); amqpMessageDispatcherService .targetCancelAssignmentToDistributionSet(cancelTargetAssignmentDistributionSetEvent); final Message sendMessage = createArgumentCapture( diff --git a/hawkbit-repository/README.md b/hawkbit-repository/README.md index 6c1e35c5d..2d68a4e73 100644 --- a/hawkbit-repository/README.md +++ b/hawkbit-repository/README.md @@ -9,4 +9,39 @@ The repository is in charge for managing the meta data of the update server, e.g $ mvn clean install ---- -Note, in order to build correctly in your IDE, you have to add ./target/generated-sources/apt to your build path. \ No newline at end of file +Note, in order to build correctly in your IDE, you have to add ./target/generated-sources/apt to your build path. + +#Concepts + +## Rollout +A rollout consists of the distribution set and a target-query-filter which defines the targets to be updated in this rollout. The targets within this filter are split up in configured amount of _Deployment Groups_ at creation time. + +When starting a rollout, for all targets within this rollout deployment actions will be created. The deployment actions of the first group will be started immediately all other deployment actions will be scheduled. + +> Due rollouts might include a large number of targets and deployment group, creation as well as starting a rollout might take some time and therefore the creation and starting of an rollout is executed asynchronously. The creation and starting progress is reflected by the rollout's status attribute + +### Rollout Creation +The targets reflected by the target-query-filter is interpreted at creation time. Only targets which have been created at that time are included in the rollout. Targets which are created after the rollout creation will not be included. At rollout creation time all necessary deployment groups containing the targets will be created. Dynamically adding targets to a created or running rollout is currently not supported. + +### Rollout Starting +The rollout is using the same concept based on _deployment acionts_ per target. All necessary _deployment acionts_ will be created at starting point but not all _deployment acionts_ will be set to running. Only the _deployment acionts_ of the first _deployment group_ is set to running, all other actions are created in _scheduled_ state. +If targets having not finished _deployment actions_ at rollout starting time, these action are tried to cancel (state: canceling). Scheduled actions from another rollout are canceled directly on server side because they were never running before and can be safely canceled. + +>Multiple rollouts can be running at the same time, but if the rollouts effect the same targets they will interfer the targets _deployment actions_ e.g. canceling/cancel already running/scheduled _deployment actions_. + +### Deployment Group Condition/Action +#### Success Condition/Action +A _success condition_ defines when a deployment group is interpreted as success and triggers the _success action_. E.g. a threshold of successfully updated targets within the group. + +The _success action_ is executed if the _success_condition_ is fullfilled. Currently only the _success action_ starting the next following deployment group is available. + +#### Error Condition/Action +A _error condition_ defines when a deployment group is interpreted as failure and triggers the _error action_. E.g. a threshold of update failures of targets within the group. + +The _error action_ is executed if the _error condition_ is fullfilled. Currently only the _error action_ pausing the whole rollout is available, a paused rollout can be resumed by a user. + +### Rollout Scheduler +The rollout is managed by a scheduler task which is checking for rollouts in _running_ state. This is done atomic by updating the row in the database, so only the rollouts are checked and processed for the current instance to avoid that multiple instances are processing a rollout. + +>Due the scheduler, it might take some time until a rollouts is processed and switching is state or starting the next deployment group. + diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/MultiTenantJpaTransactionManager.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/MultiTenantJpaTransactionManager.java index 2675510c4..2ddbfe870 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/MultiTenantJpaTransactionManager.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/MultiTenantJpaTransactionManager.java @@ -11,6 +11,7 @@ package org.eclipse.hawkbit; import javax.persistence.EntityManager; import javax.transaction.Transaction; +import org.eclipse.hawkbit.repository.RolloutManagement; import org.eclipse.hawkbit.repository.SystemManagement; import org.eclipse.hawkbit.repository.exception.TenantNotExistException; import org.eclipse.hawkbit.tenancy.TenantAware; @@ -46,7 +47,8 @@ public class MultiTenantJpaTransactionManager extends JpaTransactionManager { if (!definition.getName().startsWith(SystemManagement.class.getCanonicalName() + ".findTenants") && !definition.getName().startsWith(SystemManagement.class.getCanonicalName() + ".deleteTenant") && !definition.getName() - .startsWith(SystemManagement.class.getCanonicalName() + ".currentTenantKeyGenerator")) { + .startsWith(SystemManagement.class.getCanonicalName() + ".currentTenantKeyGenerator") + && !definition.getName().startsWith(RolloutManagement.class.getCanonicalName() + ".rolloutScheduler")) { final String currentTenant = tenantAware.getCurrentTenant(); if (currentTenant == null) { diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/RepositoryApplicationConfiguration.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/RepositoryApplicationConfiguration.java index a621e94fc..6acf212ee 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/RepositoryApplicationConfiguration.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/RepositoryApplicationConfiguration.java @@ -13,6 +13,7 @@ import java.util.Map; import org.eclipse.hawkbit.aspects.ExceptionMappingAspectHandler; import org.eclipse.hawkbit.repository.SystemManagement; +import org.eclipse.hawkbit.repository.model.helper.AfterTransactionCommitExecutorHolder; import org.eclipse.hawkbit.repository.model.helper.CacheManagerHolder; import org.eclipse.hawkbit.repository.model.helper.PollConfigurationHelper; import org.eclipse.hawkbit.repository.model.helper.SecurityTokenGeneratorHolder; @@ -112,6 +113,16 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { return CacheManagerHolder.getInstance(); } + /** + * + * @return the singleton instance of the + * {@link AfterTransactionCommitExecutorHolder} + */ + @Bean + public AfterTransactionCommitExecutorHolder afterTransactionCommitExecutorHolder() { + return AfterTransactionCommitExecutorHolder.getInstance(); + } + /** * Defines the validation processor bean. * diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/cache/CacheKeys.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/cache/CacheKeys.java index 8eca2744a..9cb53a317 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/cache/CacheKeys.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/cache/CacheKeys.java @@ -28,6 +28,8 @@ public final class CacheKeys { * event. */ public static final String DOWNLOAD_PROGRESS_PERCENT = "download.progress.percent"; + public static final String ROLLOUT_GROUP_CREATED = "rollout.group.created"; + public static final String ROLLOUT_GROUP_TOTAL = "rollout.group.total"; /** * utility class only private constructor. diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/cache/CacheWriteNotify.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/cache/CacheWriteNotify.java index 812f4ca85..bf690e07e 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/cache/CacheWriteNotify.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/cache/CacheWriteNotify.java @@ -9,8 +9,10 @@ package org.eclipse.hawkbit.cache; import org.eclipse.hawkbit.eventbus.event.DownloadProgressEvent; +import org.eclipse.hawkbit.eventbus.event.RolloutGroupCreatedEvent; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.ActionStatus; +import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.tenancy.TenantAware; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.Cache; @@ -70,6 +72,43 @@ public class CacheWriteNotify { eventBus.post(new DownloadProgressEvent(tenantAware.getCurrentTenant(), statusId, progressPercent)); } + /** + * Writes the {@link CacheKeys#ROLLOUT_GROUP_CREATED} and + * {@link CacheKeys#ROLLOUT_GROUP_TOTAL} into the cache and notfies the + * {@link EventBus} with a {@link RolloutGroupCreatedEvent}. + * + * @param revision + * the revision of the event + * @param rolloutId + * the ID of the rollout the group has been created + * @param rolloutGroupId + * the ID of the rollout group which has been created + * @param totalRolloutGroup + * the total number of rollout groups for this rollout + * @param createdRolloutGroup + * the number of already created groups of the rollout + */ + public void rolloutGroupCreated(final long revision, final Long rolloutId, final Long rolloutGroupId, + final int totalRolloutGroup, final int createdRolloutGroup) { + + final Cache cache = cacheManager.getCache(Rollout.class.getName()); + final String cacheKeyGroupTotal = CacheKeys.entitySpecificCacheKey(String.valueOf(rolloutId), + CacheKeys.ROLLOUT_GROUP_TOTAL); + final String cacheKeyGroupCreated = CacheKeys.entitySpecificCacheKey(String.valueOf(rolloutId), + CacheKeys.ROLLOUT_GROUP_CREATED); + if (createdRolloutGroup < totalRolloutGroup) { + cache.put(cacheKeyGroupTotal, totalRolloutGroup); + cache.put(cacheKeyGroupCreated, createdRolloutGroup); + } else { + // in case we reached progress 100 delete the cache value again + // because otherwise he will keep there forever + cache.evict(cacheKeyGroupTotal); + cache.evict(cacheKeyGroupCreated); + } + eventBus.post(new RolloutGroupCreatedEvent(tenantAware.getCurrentTenant(), revision, rolloutId, rolloutGroupId, + totalRolloutGroup, createdRolloutGroup)); + } + /** * @param cacheManager * the cacheManager to set diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/EntityChangeEventListener.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/EntityChangeEventListener.java index e1ff377e5..b25a72fbc 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/EntityChangeEventListener.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/EntityChangeEventListener.java @@ -144,4 +144,5 @@ public class EntityChangeEventListener { private boolean isTargetInfoNew(final Object targetInfo) { return ((TargetInfo) targetInfo).isNew(); } + } diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/EntityPropertyChangeListener.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/EntityPropertyChangeListener.java new file mode 100644 index 000000000..8b632ee6a --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/EntityPropertyChangeListener.java @@ -0,0 +1,107 @@ +/** + * 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.eventbus; + +import java.util.Map; +import java.util.stream.Collectors; + +import org.eclipse.hawkbit.eventbus.event.AbstractPropertyChangeEvent; +import org.eclipse.hawkbit.eventbus.event.ActionCreatedEvent; +import org.eclipse.hawkbit.eventbus.event.ActionPropertyChangeEvent; +import org.eclipse.hawkbit.eventbus.event.RolloutGroupPropertyChangeEvent; +import org.eclipse.hawkbit.eventbus.event.RolloutPropertyChangeEvent; +import org.eclipse.hawkbit.executor.AfterTransactionCommitExecutor; +import org.eclipse.hawkbit.repository.model.Action; +import org.eclipse.hawkbit.repository.model.BaseEntity; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.eclipse.hawkbit.repository.model.helper.AfterTransactionCommitExecutorHolder; +import org.eclipse.hawkbit.repository.model.helper.EventBusHolder; +import org.eclipse.persistence.descriptors.DescriptorEvent; +import org.eclipse.persistence.descriptors.DescriptorEventAdapter; +import org.eclipse.persistence.internal.sessions.ObjectChangeSet; +import org.eclipse.persistence.queries.UpdateObjectQuery; +import org.eclipse.persistence.sessions.changesets.DirectToFieldChangeRecord; + +import com.google.common.eventbus.EventBus; + +/** + * Listens to change in property values of an entity. + * + */ +public class EntityPropertyChangeListener extends DescriptorEventAdapter { + /* + * (non-Javadoc) + * + * @see + * org.eclipse.persistence.descriptors.DescriptorEventAdapter#postInsert + * (org.eclipse.persistence.descriptors.DescriptorEvent) + */ + @Override + public void postInsert(final DescriptorEvent event) { + if (event.getObject().getClass().equals(Action.class)) { + final Action action = (Action) event.getObject(); + if (action.getRollout() != null) { + final EventBus eventBus = getEventBus(); + final AfterTransactionCommitExecutor afterCommit = getAfterTransactionCommmitExecutor(); + afterCommit.afterCommit(() -> eventBus.post(new ActionCreatedEvent(action))); + } + } + + } + + /* + * (non-Javadoc) + * + * @see + * org.eclipse.persistence.descriptors.DescriptorEventAdapter#postUpdate + * (org.eclipse.persistence.descriptors.DescriptorEvent) + */ + @Override + public void postUpdate(final DescriptorEvent event) { + if (event.getObject().getClass().equals(Action.class)) { + getAfterTransactionCommmitExecutor().afterCommit( + () -> getEventBus() + .post(new ActionPropertyChangeEvent((Action) event.getObject(), getChangeSet(Action.class, + event)))); + } else if (event.getObject().getClass().equals(Rollout.class)) { + getAfterTransactionCommmitExecutor().afterCommit( + () -> getEventBus().post( + new RolloutPropertyChangeEvent((Rollout) event.getObject(), getChangeSet(Rollout.class, + event)))); + } else if (event.getObject().getClass().equals(RolloutGroup.class)) { + getAfterTransactionCommmitExecutor().afterCommit( + () -> getEventBus().post( + new RolloutGroupPropertyChangeEvent((RolloutGroup) event.getObject(), getChangeSet( + RolloutGroup.class, event)))); + } + } + + private Map.Values> getChangeSet( + final Class clazz, final DescriptorEvent event) { + final T rolloutGroup = clazz.cast(event.getObject()); + final ObjectChangeSet changeSet = ((UpdateObjectQuery) event.getQuery()).getObjectChangeSet(); + return changeSet + .getChanges() + .stream() + .filter(record -> record instanceof DirectToFieldChangeRecord) + .map(record -> (DirectToFieldChangeRecord) record) + .collect( + Collectors.toMap(record -> record.getAttribute(), record -> new AbstractPropertyChangeEvent( + rolloutGroup, null).new Values(record.getOldValue(), record.getNewValue()))); + } + + private AfterTransactionCommitExecutor getAfterTransactionCommmitExecutor() { + return AfterTransactionCommitExecutorHolder.getInstance().getAfterCommit(); + } + + private EventBus getEventBus() { + return EventBusHolder.getInstance().getEventBus(); + } +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/AbstractPropertyChangeEvent.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/AbstractPropertyChangeEvent.java new file mode 100644 index 000000000..8a596eeb5 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/AbstractPropertyChangeEvent.java @@ -0,0 +1,83 @@ +/** + * 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.eventbus.event; + +import java.util.Map; + +import org.eclipse.hawkbit.repository.model.BaseEntity; + +/** + * Property change event. + * + * @param + */ +public class AbstractPropertyChangeEvent extends AbstractBaseEntityEvent { + + private static final long serialVersionUID = -3671601415138242311L; + private final transient Map changeSet; + + /** + * Initialize base entity and property changed with old and new value. + * + * @param baseEntity + * entity changed + * @param changeSetValues + * details of properties changed and old value and new value of + * the changed properties + */ + public AbstractPropertyChangeEvent(final E baseEntity, final Map changeSetValues) { + super(baseEntity); + this.changeSet = changeSetValues; + } + + /** + * @return the changeSet + */ + public Map getChangeSet() { + return changeSet; + } + + /** + * Carries old value and new value of a property . + * + */ + public class Values { + private final Object oldValue; + private final Object newValue; + + /** + * Initialize old value and new changes value of property. + * + * @param oldValue + * old value before change + * @param newValue + * new value after change + */ + public Values(final Object oldValue, final Object newValue) { + super(); + this.oldValue = oldValue; + this.newValue = newValue; + } + + /** + * @return the oldValue + */ + public Object getOldValue() { + return oldValue; + } + + /** + * @return the newValue + */ + public Object getNewValue() { + return newValue; + } + + } +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/ActionCreatedEvent.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/ActionCreatedEvent.java new file mode 100644 index 000000000..5efdca2a9 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/ActionCreatedEvent.java @@ -0,0 +1,26 @@ +/** + * 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.eventbus.event; + +import org.eclipse.hawkbit.repository.model.Action; + +/** + * Defines the {@link AbstractBaseEntityEvent} of creating a new {@link Action}. + */ +public class ActionCreatedEvent extends AbstractBaseEntityEvent { + private static final long serialVersionUID = 181780358321768629L; + + /** + * @param action + */ + public ActionCreatedEvent(final Action action) { + super(action); + } + +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/ActionPropertyChangeEvent.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/ActionPropertyChangeEvent.java new file mode 100644 index 000000000..822f9bbeb --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/ActionPropertyChangeEvent.java @@ -0,0 +1,30 @@ +/** + * 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.eventbus.event; + +import java.util.Map; + +import org.eclipse.hawkbit.repository.model.Action; + +/** + * Defines the {@link AbstractPropertyChangeEvent} of {@link Action}. + */ +public class ActionPropertyChangeEvent extends AbstractPropertyChangeEvent { + private static final long serialVersionUID = 181780358321768629L; + + /** + * @param action + * @param changeSetValues + */ + public ActionPropertyChangeEvent(final Action action, + final Map.Values> changeSetValues) { + super(action, changeSetValues); + } + +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/EventMerger.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/EventMerger.java new file mode 100644 index 000000000..fe4cf4b34 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/EventMerger.java @@ -0,0 +1,185 @@ +/** + * 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.eventbus.event; + +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.eclipse.hawkbit.eventbus.EventSubscriber; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; + +import com.google.common.eventbus.AllowConcurrentEvents; +import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; + +/** + * Collects and merges fine grained events to more generic events and push them + * in a fixed delay on the events bus. This helps for code which are not + * interested in all fine grained events, e.g. UI code. The UI code is not + * interested in handling the flood of events, so collecting the events and + * merge them to one event together and post them in a fixed interval is easier + * to consume e.g. for push notifcations on UI. + * + * @author Michael Hirsch + * + */ +@EventSubscriber +public class EventMerger { + + private static final Set rolloutEvents = ConcurrentHashMap.newKeySet(); + private static final Set rolloutGroupEvents = ConcurrentHashMap.newKeySet(); + + @Autowired + private EventBus eventBus; + + /** + * Checks if there are events to publish in the fixed interval. + */ + @Scheduled(initialDelay = 10000, fixedDelay = 2000) + public void rolloutEventScheduler() { + final Iterator rolloutIterator = rolloutEvents.iterator(); + while (rolloutIterator.hasNext()) { + final RolloutEventKey eventKey = rolloutIterator.next(); + eventBus.post(new RolloutChangeEvent(1, eventKey.tenant, eventKey.rolloutId)); + rolloutIterator.remove(); + } + + final Iterator rolloutGroupIterator = rolloutGroupEvents.iterator(); + while (rolloutGroupIterator.hasNext()) { + final RolloutEventKey eventKey = rolloutGroupIterator.next(); + eventBus.post(new RolloutGroupChangeEvent(1, eventKey.tenant, eventKey.rolloutId, eventKey.rolloutGroupId)); + rolloutGroupIterator.remove(); + } + } + + /** + * Called by the event bus to retrieve all necessary events to collect and + * merge. + * + * @param event + * the event on the event bus + */ + @Subscribe + @AllowConcurrentEvents + public void onEvent(final Event event) { + Long rolloutId = null; + Long rolloutGroupId = null; + if (event instanceof ActionCreatedEvent) { + rolloutId = getRolloutId(((ActionCreatedEvent) event).getEntity().getRollout()); + rolloutGroupId = getRolloutGroupId(((ActionCreatedEvent) event).getEntity().getRolloutGroup()); + } else if (event instanceof ActionPropertyChangeEvent) { + rolloutId = getRolloutId(((ActionPropertyChangeEvent) event).getEntity().getRollout()); + rolloutGroupId = getRolloutGroupId(((ActionPropertyChangeEvent) event).getEntity().getRolloutGroup()); + } else if (event instanceof RolloutPropertyChangeEvent) { + rolloutId = ((RolloutPropertyChangeEvent) event).getEntity().getId(); + } else if (event instanceof RolloutGroupCreatedEvent) { + rolloutId = ((RolloutGroupCreatedEvent) event).getRolloutId(); + rolloutGroupId = ((RolloutGroupCreatedEvent) event).getRolloutGroupId(); + } else if (event instanceof RolloutGroupPropertyChangeEvent) { + final RolloutGroup rolloutGroup = ((RolloutGroupPropertyChangeEvent) event).getEntity(); + rolloutId = rolloutGroup.getRollout().getId(); + rolloutGroupId = rolloutGroup.getId(); + } + + if (rolloutId != null) { + rolloutEvents.add(new RolloutEventKey(rolloutId, event.getTenant())); + if (rolloutGroupId != null) { + rolloutGroupEvents.add(new RolloutEventKey(rolloutId, rolloutGroupId, event.getTenant())); + } + } + } + + private Long getRolloutGroupId(final RolloutGroup rolloutGroup) { + if (rolloutGroup != null) { + return rolloutGroup.getId(); + } + return null; + } + + private Long getRolloutId(final Rollout rollout) { + if (rollout != null) { + return rollout.getId(); + } + return null; + } + + /** + * The rollout key in the concurrent set to be hold. + * + * @author Michael Hirsch + * + */ + private static final class RolloutEventKey { + private final Long rolloutId; + private final String tenant; + private final Long rolloutGroupId; + + private RolloutEventKey(final Long rolloutId, final Long rolloutGroupId, final String tenant) { + this.rolloutGroupId = rolloutGroupId; + this.rolloutId = rolloutId; + this.tenant = tenant; + } + + private RolloutEventKey(final Long rolloutId, final String tenant) { + this(rolloutId, null, tenant); + } + + @Override + public int hashCode() {// NOSONAR - as this is generated + final int prime = 31; + int result = 1; + result = prime * result + ((rolloutGroupId == null) ? 0 : rolloutGroupId.hashCode()); + result = prime * result + ((rolloutId == null) ? 0 : rolloutId.hashCode()); + result = prime * result + ((tenant == null) ? 0 : tenant.hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) {// NOSONAR - as this is + // generated + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final RolloutEventKey other = (RolloutEventKey) obj; + if (rolloutGroupId == null) { + if (other.rolloutGroupId != null) { + return false; + } + } else if (!rolloutGroupId.equals(other.rolloutGroupId)) { + return false; + } + if (rolloutId == null) { + if (other.rolloutId != null) { + return false; + } + } else if (!rolloutId.equals(other.rolloutId)) { + return false; + } + if (tenant == null) { + if (other.tenant != null) { + return false; + } + } else if (!tenant.equals(other.tenant)) { + return false; + } + return true; + } + + } +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/RolloutChangeEvent.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/RolloutChangeEvent.java new file mode 100644 index 000000000..8f15602b5 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/RolloutChangeEvent.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.eventbus.event; + +import org.eclipse.hawkbit.eventbus.event.AbstractEvent; + +/** + * Event declaration for the UI to notify the UI that a rollout has been + * changed. + * + * @author Michael Hirsch + * + */ +public class RolloutChangeEvent extends AbstractEvent { + + private final Long rolloutId; + + /** + * @param revision + * the revision of the event + * @param tenant + * the tenant of the event + * @param rolloutId + * the ID of the rollout which has been changed + */ + public RolloutChangeEvent(final long revision, final String tenant, final Long rolloutId) { + super(revision, tenant); + this.rolloutId = rolloutId; + } + + public Long getRolloutId() { + return rolloutId; + } +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/RolloutGroupChangeEvent.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/RolloutGroupChangeEvent.java new file mode 100644 index 000000000..cbda44ef5 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/RolloutGroupChangeEvent.java @@ -0,0 +1,47 @@ +/** + * 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.eventbus.event; + +/** + * Event declaration for the UI to notify the UI that a rollout has been + * changed. + * + * @author Michael Hirsch + * + */ +public class RolloutGroupChangeEvent extends AbstractEvent { + + private final Long rolloutId; + private final Long rolloutGroupId; + + /** + * @param revision + * the revision of the event + * @param tenant + * the tenant of the event + * @param rolloutId + * the ID of the rollout which has been changed + * @param rolloutGroupId + * the ID of the rollout group which has been changed + */ + public RolloutGroupChangeEvent(final long revision, final String tenant, final Long rolloutId, + final Long rolloutGroupId) { + super(revision, tenant); + this.rolloutId = rolloutId; + this.rolloutGroupId = rolloutGroupId; + } + + public Long getRolloutId() { + return rolloutId; + } + + public Long getRolloutGroupId() { + return rolloutGroupId; + } +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/RolloutGroupCreatedEvent.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/RolloutGroupCreatedEvent.java new file mode 100644 index 000000000..465ff6088 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/RolloutGroupCreatedEvent.java @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.eventbus.event; + +/** + * Event definition which is been published in case a rollout group has been + * created for a specific rollout. + * + * @author Michael Hirsch + * + */ +public class RolloutGroupCreatedEvent extends AbstractDistributedEvent { + + private static final long serialVersionUID = 1L; + private final Long rolloutId; + private final Long rolloutGroupId; + private final int totalRolloutGroup; + private final int createdRolloutGroup; + + /** + * Creating a new rollout group created event for a specific rollout. + * + * @param tenant + * the tenant of this event + * @param revision + * the revision of the event + * @param rolloutId + * the ID of the rollout the group has been created + * @param totalRolloutGroup + * the total number of rollout groups for this rollout + * @param createdRolloutGroup + * the number of already created groups of the rollout + */ + public RolloutGroupCreatedEvent(final String tenant, final long revision, final Long rolloutId, + final Long rolloutGroupId, final int totalRolloutGroup, final int createdRolloutGroup) { + super(revision, tenant); + this.rolloutId = rolloutId; + this.rolloutGroupId = rolloutGroupId; + this.totalRolloutGroup = totalRolloutGroup; + this.createdRolloutGroup = createdRolloutGroup; + + } + + public Long getRolloutId() { + return rolloutId; + } + + public int getTotalRolloutGroup() { + return totalRolloutGroup; + } + + public int getCreatedRolloutGroup() { + return createdRolloutGroup; + } + + public Long getRolloutGroupId() { + return rolloutGroupId; + } +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/RolloutGroupPropertyChangeEvent.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/RolloutGroupPropertyChangeEvent.java new file mode 100644 index 000000000..6caa46547 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/RolloutGroupPropertyChangeEvent.java @@ -0,0 +1,32 @@ +/** + * 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.eventbus.event; + +import java.util.Map; + +import org.eclipse.hawkbit.repository.model.RolloutGroup; + +/** + * Defines the {@link AbstractPropertyChangeEvent} of {@link RolloutGroup}. + */ +public class RolloutGroupPropertyChangeEvent extends AbstractPropertyChangeEvent { + + private static final long serialVersionUID = 4026477044419472686L; + + /** + * + * @param rolloutGroup + * @param changeSetValues + */ + public RolloutGroupPropertyChangeEvent(final RolloutGroup rolloutGroup, + final Map.Values> changeSetValues) { + super(rolloutGroup, changeSetValues); + } + +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/RolloutPropertyChangeEvent.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/RolloutPropertyChangeEvent.java new file mode 100644 index 000000000..5f72307df --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/RolloutPropertyChangeEvent.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.eventbus.event; + +import java.util.Map; + +import org.eclipse.hawkbit.repository.model.Rollout; + +/** + * Defines the {@link AbstractPropertyChangeEvent} of {@link Rollout}. + */ +public class RolloutPropertyChangeEvent extends AbstractPropertyChangeEvent { + private static final long serialVersionUID = 1056221355466373514L; + + /** + * + * @param rollout + * @param changeSetValues + */ + public RolloutPropertyChangeEvent(final Rollout rollout, + final Map.Values> changeSetValues) { + super(rollout, changeSetValues); + } + +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/TargetAssignDistributionSetEvent.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/TargetAssignDistributionSetEvent.java index 8c13800c9..b286ac6ea 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/TargetAssignDistributionSetEvent.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/TargetAssignDistributionSetEvent.java @@ -19,7 +19,7 @@ import org.eclipse.hawkbit.repository.model.SoftwareModule; * * */ -public class TargetAssignDistributionSetEvent { +public class TargetAssignDistributionSetEvent extends AbstractEvent { private final Collection softwareModules; private final String controllerId; @@ -29,6 +29,10 @@ public class TargetAssignDistributionSetEvent { /** * Creates a new {@link TargetAssignDistributionSetEvent}. * + * @param revision + * the revision of the event + * @param tenant + * the tenant of the event * @param controllerId * the ID of the controller * @param actionId @@ -38,8 +42,9 @@ public class TargetAssignDistributionSetEvent { * @param targetAdress * the targetAdress of the target */ - public TargetAssignDistributionSetEvent(final String controllerId, final Long actionId, - final Collection softwareModules, final URI targetAdress) { + public TargetAssignDistributionSetEvent(final long revision, final String tenant, final String controllerId, + final Long actionId, final Collection softwareModules, final URI targetAdress) { + super(revision, tenant); this.controllerId = controllerId; this.actionId = actionId; this.softwareModules = softwareModules; diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/TargetCreatedEvent.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/TargetCreatedEvent.java index 3e80e45a5..7e89565b1 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/TargetCreatedEvent.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/TargetCreatedEvent.java @@ -14,8 +14,6 @@ import org.eclipse.hawkbit.repository.model.Target; * Defines the {@link AbstractBaseEntityEvent} of creating a new {@link Target}. * * - * - * */ public class TargetCreatedEvent extends AbstractBaseEntityEvent { diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/ActionRepository.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/ActionRepository.java index a97799ab4..331fdad5b 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/ActionRepository.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/ActionRepository.java @@ -12,9 +12,13 @@ import java.util.Collection; import java.util.List; import org.eclipse.hawkbit.repository.model.Action; +import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus; import org.springframework.cache.annotation.CacheEvict; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -30,10 +34,6 @@ import org.springframework.transaction.annotation.Transactional; /** * {@link Action} repository. * - * - * - * - * */ @Transactional(readOnly = true) public interface ActionRepository extends BaseEntityRepository, JpaSpecificationExecutor { @@ -187,6 +187,47 @@ public interface ActionRepository extends BaseEntityRepository, Jp void setToInactive(@Param("keySet") List keySet, @Param("targetsIds") List targetsIds); /** + * Switches the status of actions from one specific status into another, + * only if the actions are in a specific status. This should be a atomar + * operation. + * + * @param statusToSet + * the new status the actions should get + * @param targetIds + * the IDs of the targets of the actions which are affected + * @param active + * the active flag of the actions which should be affected + * @param currentStatus + * the current status of the actions which are affected + */ + @Modifying + @Transactional + @Query("UPDATE Action a SET a.status = :statusToSet WHERE a.target IN :targetsIds AND a.active = :active AND a.status = :currentStatus AND a.distributionSet.requiredMigrationStep = false") + void switchStatus(@Param("statusToSet") Action.Status statusToSet, @Param("targetsIds") List targetIds, + @Param("active") boolean active, @Param("currentStatus") Action.Status currentStatus); + + /** + * Switches the status of actions from one specific status into another, + * only if the actions are in a specific status. This should be a atomar + * operation. + * + * @param statusToSet + * the new status the actions should get + * @param rollout + * the rollout of the actions which are affected + * @param active + * the active flag of the actions which should be affected + * @param currentStatus + * the current status of the actions which are affected + */ + @Modifying + @Transactional + @Query("UPDATE Action a SET a.status = :statusToSet WHERE a.rollout = :rollout AND a.active = :active AND a.status = :currentStatus") + void switchStatus(@Param("statusToSet") Action.Status statusToSet, @Param("rollout") Rollout rollout, + @Param("active") boolean active, @Param("currentStatus") Action.Status currentStatus); + + /** + * * Retrieves all {@link Action}s which are active and referring to the given * target Ids and distribution set required migration step. * @@ -234,7 +275,139 @@ public interface ActionRepository extends BaseEntityRepository, Jp * * @param distributionSet * DistributionSet to count the {@link Action}s from - * @return the count of actions referring to the given target + * @return the count of actions referring to the given distributionSet */ Long countByDistributionSet(DistributionSet distributionSet); + + /** + * Counts all {@link Action}s referring to the given rollout. + * + * @param rollout + * the rollout to count the {@link Action}s from + * @return the count of actions referring to the given rollout + */ + Long countByRollout(Rollout rollout); + + /** + * Counts all actions referring to a given rollout and rolloutgroup which + * are currently not in the given status. An in-clause statement does not + * work with the spring-data, so this is specific usecase regarding to the + * rollout-management to find out actions which are not in specific states. + * + * @param rollout + * the rollout the actions are belong to + * @param rolloutGroup + * the rolloutgroup the actions are belong to + * @param notStatus1 + * the status the action should not have + * @param notStatus2 + * the status the action should not have + * @param notStatus3 + * the status the action should not have + * @return the count of actions referring the rollout and rolloutgroup and + * are not in given states + */ + Long countByRolloutAndRolloutGroupAndStatusNotAndStatusNotAndStatusNot(Rollout rollout, RolloutGroup rolloutGroup, + Status notStatus1, Status notStatus2, Status notStatus3); + + /** + * Counts all actions referring to a given rollout and rolloutgroup. + * + * @param rollout + * the rollout the actions belong to + * @param rolloutGroup + * the rolloutgroup the actions belong to + * @return the count of actions referring to a rollout and rolloutgroup + */ + Long countByRolloutAndRolloutGroup(Rollout rollout, RolloutGroup rolloutGroup); + + /** + * Counts all actions referring to a given rollout, rolloutgroup and status. + * + * @param rolloutId + * the ID of rollout the actions belong to + * @param rolloutGroupId + * the ID rolloutgroup the actions belong to + * @param status + * the status the actions should have + * @return the count of actions referring to a rollout, rolloutgroup and are + * in a given status + */ + Long countByRolloutIdAndRolloutGroupIdAndStatus(Long rolloutId, Long rolloutGroupId, Action.Status status); + + /** + * Retrieving all actions referring to a given rollout with a specific + * action as parent reference and a specific status. + * + * Finding all actions of a specific rolloutgroup parent relation. + * + * @param rollout + * the rollout the actions belong to + * @param rolloutGroupParent + * the parent rolloutgroup the actions should reference + * @param actionStatus + * the status the actions have + * @return the actions referring a specific rollout and a specific parent + * rolloutgroup in a specific status + */ + List findByRolloutAndRolloutGroupParentAndStatus(Rollout rollout, RolloutGroup rolloutGroupParent, + Status actionStatus); + + /** + * Retrieves all actions for a specific rollout and in a specific status. + * + * @param rollout + * 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 findByRolloutAndStatus(Rollout rollout, Status actionStatus); + + /** + * Get list of objects which has details of status and count of targets in + * each status in specified rollout. + * + * @param rolloutId + * id of {@link Rollout} + * @return list of objects with status and target count + */ + @Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus( a.rollout.id, a.status , COUNT(a.target)) FROM Action a WHERE a.rollout.id IN ?1 GROUP BY a.rollout.id,a.status") + List getStatusCountByRolloutId(List rolloutId); + + /** + * Get list of objects which has details of status and count of targets in + * each status in specified rollout. + * + * @param rolloutId + * id of {@link Rollout} + * @return list of objects with status and target count + */ + @Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus( a.rollout.id, a.status , COUNT(a.target)) FROM Action a WHERE a.rollout.id = ?1 GROUP BY a.rollout.id,a.status") + List getStatusCountByRolloutId(Long rolloutId); + + /** + * Get list of objects which has details of status and count of targets in + * each status in specified rollout group. + * + * @param rolloutGroupId + * id of {@link RolloutGroup} + * @return list of objects with status and target count + */ + @Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus(a.rolloutGroup.id, a.status , COUNT(a.target)) FROM Action a WHERE a.rolloutGroup.id = ?1 GROUP BY a.rolloutGroup.id, a.status") + List getStatusCountByRolloutGroupId(Long rolloutGroupId); + + /** + * Get list of objects which has details of status and count of targets in + * each status in specified rollout group. + * + * @param rolloutGroupId + * list of id of {@link RolloutGroup} + * @return list of objects with status and target count + */ + @Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus(a.rolloutGroup.id, a.status , COUNT(a.target)) FROM Action a WHERE a.rolloutGroup.id IN ?1 GROUP BY a.rolloutGroup.id, a.status") + List getStatusCountByRolloutGroupId(List rolloutGroupId); + + // Asha-ends here + } diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java index b8b8610cd..172f1c3e1 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java @@ -23,7 +23,6 @@ import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Join; import javax.persistence.criteria.JoinType; import javax.persistence.criteria.ListJoin; -import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import javax.validation.constraints.NotNull; @@ -46,6 +45,9 @@ import org.eclipse.hawkbit.repository.model.Action_; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.DistributionSet_; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.eclipse.hawkbit.repository.model.Rollout_; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModuleType; import org.eclipse.hawkbit.repository.model.Target; @@ -228,7 +230,7 @@ public class DeploymentManagement { String.format("no %s with id %d found", DistributionSet.class.getSimpleName(), dsID)); } - return assignDistributionSetToTargets(set, targets); + return assignDistributionSetToTargets(set, targets, null, null); } /** @@ -237,8 +239,45 @@ public class DeploymentManagement { * * @param dsID * the ID of the distribution set to assign - * @param targetsWithActionType + * @param targets * a list of all targets and their action type + * @param rollout + * the rollout for this assignment + * @param rolloutgroup + * the rolloutgroup for this assignment + * @return the assignment result + * + * @throw IncompleteDistributionSetException if mandatory + * {@link SoftwareModuleType} are not assigned as define by the + * {@link DistributionSetType}. + */ + @Modifying + @Transactional + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET) + @CacheEvict(value = { "distributionUsageAssigned" }, allEntries = true) + public DistributionSetAssignmentResult assignDistributionSet(@NotNull final Long dsID, + final List targets, final Rollout rollout, final RolloutGroup rolloutGroup) { + final DistributionSet set = distributoinSetRepository.findOne(dsID); + if (set == null) { + throw new EntityNotFoundException( + String.format("no %s with id %d found", DistributionSet.class.getSimpleName(), dsID)); + } + + return assignDistributionSetToTargets(set, targets, rollout, rolloutGroup); + } + + /** + * method assigns the {@link DistributionSet} to all {@link Target}s by + * their IDs with a specific {@link ActionType} and {@code forcetime}. + * + * @param dsID + * the ID of the distribution set to assign + * @param targets + * a list of all targets and their action type + * @param rollout + * the rollout for this assignment + * @param rolloutgroup + * the rolloutgroup for this assignment * @return the assignment result * * @throw IncompleteDistributionSetException if mandatory @@ -246,7 +285,8 @@ public class DeploymentManagement { * {@link DistributionSetType}. */ private DistributionSetAssignmentResult assignDistributionSetToTargets(@NotNull final DistributionSet set, - final List targetsWithActionType) { + final List targetsWithActionType, final Rollout rollout, + final RolloutGroup rolloutGroup) { if (!set.isComplete()) { throw new IncompleteDistributionSetException( @@ -293,8 +333,10 @@ public class DeploymentManagement { final Set targetIdsCancellList = new HashSet(); targetIds.forEach(ids -> targetIdsCancellList.addAll(overrideObsoleteUpdateActions(ids))); - // check for old assigments that needs cancelation - // final List canncelledTargetIds = + // cancel all scheduled actions which are in-active, these actions were + // not active before and the manual assignment which has been done + // cancels the + targetIds.forEach(tIds -> actionRepository.switchStatus(Status.CANCELED, tIds, false, Status.SCHEDULED)); // set assigned distribution set and TargetUpdateStatus final String currentUser; @@ -317,6 +359,8 @@ public class DeploymentManagement { tAction.setStatus(Status.RUNNING); tAction.setTarget(t); tAction.setDistributionSet(set); + tAction.setRollout(rollout); + tAction.setRolloutGroup(rolloutGroup); return tAction; }).collect(Collectors.toList())).stream() .collect(Collectors.toMap(a -> a.getTarget().getControllerId(), Function.identity())); @@ -359,7 +403,7 @@ public class DeploymentManagement { /** * Sends the {@link TargetAssignDistributionSetEvent} for a specific target * to the {@link EventBus}. - * + * * @param target * the Target which has been assigned to a distribution set * @param actionId @@ -372,14 +416,14 @@ public class DeploymentManagement { target.getTargetInfo().setUpdateStatus(TargetUpdateStatus.PENDING); afterCommit.afterCommit(() -> { eventBus.post(new TargetInfoUpdateEvent(target.getTargetInfo())); - eventBus.post(new TargetAssignDistributionSetEvent(target.getControllerId(), actionId, softwareModules, - target.getTargetInfo().getAddress())); + eventBus.post(new TargetAssignDistributionSetEvent(target.getOptLockRevision(), target.getTenant(), + target.getControllerId(), actionId, softwareModules, target.getTargetInfo().getAddress())); }); } /** * Removes {@link UpdateAction}s that are no longer necessary and sends - * cancellations to the controller. + * cancelations to the controller. * * @param myTarget * to override {@link UpdateAction}s @@ -390,13 +434,14 @@ public class DeploymentManagement { // Figure out if there are potential target/action combinations that // need to be considered - // for cancellation + // for cancelation final List activeActions = actionRepository .findByActiveAndTargetIdInAndActionStatusNotEqualToAndDistributionSetRequiredMigrationStep(targetsIds, Action.Status.CANCELING); activeActions.forEach(action -> { action.setStatus(Status.CANCELING); // document that the status has been retrieved + actionStatusRepository.save(new ActionStatus(action, Status.CANCELING, System.currentTimeMillis(), "manual cancelation requested")); @@ -404,15 +449,20 @@ public class DeploymentManagement { cancelledTargetIds.add(action.getTarget().getId()); }); + actionRepository.save(activeActions); return cancelledTargetIds; + } private DistributionSetAssignmentResult assignDistributionSetByTargetId(@NotNull final DistributionSet set, @NotEmpty final List tIDs, final ActionType actionType, final long forcedTime) { + return assignDistributionSetToTargets(set, tIDs.stream() - .map(t -> new TargetWithActionType(t, actionType, forcedTime)).collect(Collectors.toList())); + .map(t -> new TargetWithActionType(t, actionType, forcedTime)).collect(Collectors.toList()), null, + null); + } /** @@ -475,7 +525,6 @@ public class DeploymentManagement { actionStatusRepository.save(new ActionStatus(myAction, Status.CANCELING, System.currentTimeMillis(), "manual cancelation requested")); final Action saveAction = actionRepository.save(myAction); - cancelAssignDistributionSetEvent(target, myAction.getId()); return saveAction; @@ -488,15 +537,15 @@ public class DeploymentManagement { /** * Sends the {@link CancelTargetAssignmentEvent} for a specific target to * the {@link EventBus}. - * + * * @param target * the Target which has been assigned to a distribution set * @param actionId * the action id of the assignment */ private void cancelAssignDistributionSetEvent(final Target target, final Long actionId) { - afterCommit.afterCommit(() -> eventBus.post(new CancelTargetAssignmentEvent(target.getControllerId(), actionId, - target.getTargetInfo().getAddress()))); + afterCommit.afterCommit(() -> eventBus.post(new CancelTargetAssignmentEvent(target.getOptLockRevision(), + target.getTenant(), target.getControllerId(), actionId, target.getTargetInfo().getAddress()))); } /** @@ -542,6 +591,111 @@ public class DeploymentManagement { return actionRepository.save(mergedAction); } + /** + * Creates an action entry into the action repository. In case of existing + * scheduled actions the scheduled actions gets canceled. A scheduled action + * is created in-active. + * + * @param targets + * the targets to create scheduled actions for + * @param distributionSet + * the distribution set for the actions + * @param actionType + * the action type for the action + * @param forcedTime + * the forcedTime of the action + * @param rollout + * the rollout for this action + * @param rolloutGroup + * the rolloutgroup for this action + */ + @Modifying + @Transactional + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET) + public void createScheduledAction(final List targets, final DistributionSet distributionSet, + final ActionType actionType, final long forcedTime, final Rollout rollout, + final RolloutGroup rolloutGroup) { + // cancel all current scheduled actions for this target. E.g. an action + // is already scheduled and a next action is created then cancel the + // current scheduled action to cancel. E.g. a new scheduled action is + // created. + final List targetIds = targets.stream().map(t -> t.getId()).collect(Collectors.toList()); + actionRepository.switchStatus(Action.Status.CANCELED, targetIds, false, Action.Status.SCHEDULED); + targets.forEach(target -> { + final Action action = new Action(); + action.setTarget(target); + action.setActive(false); + action.setDistributionSet(distributionSet); + action.setActionType(actionType); + action.setForcedTime(forcedTime); + action.setStatus(Status.SCHEDULED); + action.setRollout(rollout); + action.setRolloutGroup(rolloutGroup); + actionRepository.save(action); + }); + } + + /** + * Starting an action which is scheduled, e.g. in case of rollout a + * scheduled action must be started now. + * + * @param action + * the action to start now. + * @return the action which has been started + */ + @Modifying + @Transactional + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET + SpringEvalExpressions.HAS_AUTH_OR + + SpringEvalExpressions.IS_SYSTEM_CODE) + public Action startScheduledAction(@NotNull final Action action) { + + final Action mergedAction = entityManager.merge(action); + final Target mergedTarget = entityManager.merge(action.getTarget()); + + // check if we need to override running update actions + final Set overrideObsoleteUpdateActions = overrideObsoleteUpdateActions( + Collections.singletonList(action.getTarget().getId())); + + final boolean hasDistributionSetAlreadyAssigned = targetRepository + .count(TargetSpecifications.hasControllerIdAndAssignedDistributionSetIdNot( + Collections.singletonList(mergedTarget.getControllerId()), + action.getDistributionSet().getId())) == 0; + if (hasDistributionSetAlreadyAssigned) { + // the target has already the distribution set assigned, we don't + // need to start the scheduled action, just finished it. + mergedAction.setStatus(Status.FINISHED); + mergedAction.setActive(false); + return actionRepository.save(mergedAction); + } + + mergedAction.setActive(true); + mergedAction.setStatus(Status.RUNNING); + final Action savedAction = actionRepository.save(mergedAction); + + final ActionStatus actionStatus = new ActionStatus(); + actionStatus.setAction(action); + actionStatus.setOccurredAt(action.getCreatedAt()); + actionStatus.setStatus(Status.RUNNING); + actionStatusRepository.save(actionStatus); + + mergedTarget.setAssignedDistributionSet(action.getDistributionSet()); + final TargetInfo targetInfo = mergedTarget.getTargetInfo(); + targetInfo.setUpdateStatus(TargetUpdateStatus.PENDING); + targetRepository.save(mergedTarget); + targetInfoRepository.save(targetInfo); + + // in case we canceled an action before for this target, then don't fire + // assignment event + if (!overrideObsoleteUpdateActions.contains(savedAction.getId())) { + final List softwareModules = softwareModuleRepository + .findByAssignedTo(action.getDistributionSet()); + // send distribution set assignment event + + assignDistributionSetEvent(mergedAction.getTarget(), mergedAction.getId(), softwareModules); + } + return savedAction; + } + /** * Get the {@link Action} entity for given actionId. * @@ -609,13 +763,14 @@ public class DeploymentManagement { final Root actionRoot = query.from(Action.class); final ListJoin actionStatusJoin = actionRoot.join(Action_.actionStatus, JoinType.LEFT); final Join actionDsJoin = actionRoot.join(Action_.distributionSet); + final Join actionRolloutJoin = actionRoot.join(Action_.rollout, JoinType.LEFT); final CriteriaQuery multiselect = query.distinct(true).multiselect( actionRoot.get(Action_.id), actionRoot.get(Action_.actionType), actionRoot.get(Action_.active), actionRoot.get(Action_.forcedTime), actionRoot.get(Action_.status), actionRoot.get(Action_.createdAt), actionRoot.get(Action_.lastModifiedAt), actionDsJoin.get(DistributionSet_.id), actionDsJoin.get(DistributionSet_.name), actionDsJoin.get(DistributionSet_.version), - cb.count(actionStatusJoin)); + cb.count(actionStatusJoin), actionRolloutJoin.get(Rollout_.name)); multiselect.where(cb.equal(actionRoot.get(Action_.target), target)); multiselect.orderBy(cb.desc(actionRoot.get(Action_.id))); multiselect.groupBy(actionRoot.get(Action_.id)); @@ -640,25 +795,19 @@ public class DeploymentManagement { public Slice findActionsByTarget(final Specification specifiction, final Target target, final Pageable pageable) { - return actionRepository.findAll(new Specification() { - - @Override - public Predicate toPredicate(final Root root, final CriteriaQuery query, - final CriteriaBuilder cb) { - return cb.and(specifiction.toPredicate(root, query, cb), cb.equal(root.get(Action_.target), target)); - } - }, pageable); + return actionRepository.findAll((Specification) (root, query, cb) -> cb + .and(specifiction.toPredicate(root, query, cb), cb.equal(root.get(Action_.target), target)), pageable); } /** * Retrieves all {@link Action}s which are referring the given * {@link Target}. - * - * @param pageable - * page parameters + * * @param foundTarget - * the target to find assigned actions - * @return the found {@link Action}s + * the target to find actions for + * @param pageable + * the pageable request to limit, sort the actions + * @return a slice of actions found for a specific target */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) public Slice findActionsByTarget(final Target foundTarget, final Pageable pageable) { @@ -744,13 +893,8 @@ public class DeploymentManagement { */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) public Long countActionsByTarget(@NotNull final Specification spec, @NotNull final Target target) { - return actionRepository.count(new Specification() { - @Override - public Predicate toPredicate(final Root root, final CriteriaQuery query, - final CriteriaBuilder cb) { - return cb.and(spec.toPredicate(root, query, cb), cb.equal(root.get(Action_.target), target)); - } - }); + return actionRepository.count((root, query, cb) -> cb.and(spec.toPredicate(root, query, cb), + cb.equal(root.get(Action_.target), target))); } /** @@ -802,7 +946,7 @@ public class DeploymentManagement { * This method is called, when cancellation has been successful. It sets the * action to canceled, resets the meta data of the target and in case there * is a new action this action is triggered. - * + * * @param action * the action which is set to canceled */ @@ -824,4 +968,40 @@ public class DeploymentManagement { } targetManagement.updateTarget(target); } + + /** + * Retrieving all actions referring to a given rollout with a specific + * action as parent reference and a specific status. + * + * Finding all actions of a specific rolloutgroup parent relation. + * + * @param rollout + * the rollout the actions belong to + * @param rolloutGroupParent + * the parent rolloutgroup the actions should reference + * @param actionStatus + * the status the actions have + * @return the actions referring a specific rollout and a specific parent + * rolloutgroup in a specific status + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET + SpringEvalExpressions.HAS_AUTH_OR + + SpringEvalExpressions.IS_SYSTEM_CODE) + public List findActionsByRolloutGroupParentAndStatus(final Rollout rollout, + final RolloutGroup rolloutGroupParent, final Action.Status actionStatus) { + return actionRepository.findByRolloutAndRolloutGroupParentAndStatus(rollout, rolloutGroupParent, actionStatus); + } + + /** + * Retrieves all actions for a specific rollout and in a specific status. + * + * @param rollout + * 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 + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) + public List findActionsByRolloutAndStatus(final Rollout rollout, final Action.Status actionStatus) { + return actionRepository.findByRolloutAndStatus(rollout, actionStatus); + } } diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/OffsetBasedPageRequest.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/OffsetBasedPageRequest.java new file mode 100644 index 000000000..753ec6963 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/OffsetBasedPageRequest.java @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository; + +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; + +/** + * An implementation of the {@link PageRequest} which is offset based by means + * the offset is given and not the page number as in the original + * {@link PageRequest} implemntation where the offset is generated. Due that the + * REST-API is working with {@code offset} and {@code limit} parameter we need + * an offset based page request for JPA. + * + * @author Michael Hirsch + * @since 0.2.2 + * + */ +public final class OffsetBasedPageRequest extends PageRequest { + + private static final long serialVersionUID = 1L; + private final int offset; + + /** + * Creates a new {@link OffsetBasedPageRequest}. Offsets are zero indexed, + * thus providing 0 for {@code offset} will return the first entry. + * + * @param offset + * zero-based offset index. + * @param limit + * the limit of the page to be returned. + */ + public OffsetBasedPageRequest(final int offset, final int limit) { + this(offset, limit, null); + } + + /** + * Creates a new {@link OffsetBasedPageRequest}. Offsets are zero indexed, + * thus providing 0 for {@code offset} will return the first entry. + * + * @param offset + * zero-based offset index. + * @param limit + * the limit of the page to be returned. + * @param sort + * sort can be {@literal null}. + */ + public OffsetBasedPageRequest(final int offset, final int limit, final Sort sort) { + super(0, limit, sort); + this.offset = offset; + } + + @Override + public int getOffset() { + return offset; + } + + @Override + public String toString() { + return "OffsetBasedPageRequest [offset=" + offset + ", getPageSize()=" + getPageSize() + ", getPageNumber()=" + + getPageNumber() + "]"; + } +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutGroupManagement.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutGroupManagement.java new file mode 100644 index 000000000..e98439b57 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutGroupManagement.java @@ -0,0 +1,262 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import javax.persistence.EntityManager; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Join; +import javax.persistence.criteria.JoinType; +import javax.persistence.criteria.ListJoin; +import javax.persistence.criteria.Root; +import javax.validation.constraints.NotNull; + +import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; +import org.eclipse.hawkbit.repository.model.Action; +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.RolloutGroup_; +import org.eclipse.hawkbit.repository.model.RolloutTargetGroup; +import org.eclipse.hawkbit.repository.model.RolloutTargetGroup_; +import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.model.TargetWithActionStatus; +import org.eclipse.hawkbit.repository.model.Target_; +import org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus; +import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus; +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.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +/** + * RolloutGroupManagement to control rollout groups. This service secures all + * the functionality based on the {@link PreAuthorize} annotation on methods. + */ +@Validated +@Service +@Transactional(readOnly = true) +public class RolloutGroupManagement { + + @Autowired + private RolloutGroupRepository rolloutGroupRepository; + + @Autowired + private ActionRepository actionRepository; + + @Autowired + private TargetRepository targetRepository; + + @Autowired + private EntityManager entityManager; + + /** + * Retrieves a single {@link RolloutGroup} by its ID. + * + * @param rolloutGroupId + * the ID of the rollout group to find + * @return the found {@link RolloutGroup} by its ID or {@code null} if it + * does not exists + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) + public RolloutGroup findRolloutGroupById(final Long rolloutGroupId) { + return rolloutGroupRepository.findOne(rolloutGroupId); + } + + /** + * Retrieves a page of {@link RolloutGroup}s filtered by a given + * {@link Rollout}. + * + * @param rolloutId + * the ID of the rollout to filter the {@link RolloutGroup}s + * @param page + * the page request to sort and limit the result + * @return a page of found {@link RolloutGroup}s + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) + public Page findRolloutGroupsByRolloutId(final Long rolloutId, final Pageable page) { + return rolloutGroupRepository.findByRolloutId(rolloutId, page); + } + + /** + * Retrieves a page of {@link RolloutGroup}s filtered by a given + * {@link Rollout} and the given {@link Specification}. + * + * @param rolloutId + * the ID of the rollout to filter the {@link RolloutGroup}s + * @param specification + * the specification to filter the result set based on attributes + * of the {@link RolloutGroup} + * @param page + * the page request to sort and limit the result + * @return a page of found {@link RolloutGroup}s + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) + public Page findRolloutGroupsByPredicate(final Rollout rollout, + final Specification specification, final Pageable page) { + return rolloutGroupRepository.findAll((root, query, criteriaBuilder) -> criteriaBuilder.and( + criteriaBuilder.equal(root.get(RolloutGroup_.rollout), rollout), + specification.toPredicate(root, query, criteriaBuilder)), page); + } + + /** + * Retrieves a page of {@link RolloutGroup}s filtered by a given + * {@link Rollout} with the detailed status. + * + * @param rolloutId + * the ID of the rollout to filter the {@link RolloutGroup}s + * @param page + * the page request to sort and limit the result + * @return a page of found {@link RolloutGroup}s + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) + public Page findAllRolloutGroupsWithDetailedStatus(final Long rolloutId, final Pageable page) { + final Page rolloutGroups = rolloutGroupRepository.findByRolloutId(rolloutId, page); + final List rolloutGroupIds = rolloutGroups.getContent().stream().map(rollout -> rollout.getId()) + .collect(Collectors.toList()); + final Map> allStatesForRollout = getStatusCountItemForRolloutGroup( + rolloutGroupIds); + + for (final RolloutGroup rolloutGroup : rolloutGroups) { + final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus( + allStatesForRollout.get(rolloutGroup.getId()), rolloutGroup.getTotalTargets()); + rolloutGroup.setTotalTargetCountStatus(totalTargetCountStatus); + } + + return rolloutGroups; + } + + /** + * Get count of targets in different status in rollout group. + * + * @param rolloutGroupId + * rollout group id + * @return rolloutGroup with details of targets count for different statuses + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) + public RolloutGroup findRolloutGroupWithDetailedStatus(final Long rolloutGroupId) { + final RolloutGroup rolloutGroup = findRolloutGroupById(rolloutGroupId); + final List rolloutStatusCountItems = actionRepository + .getStatusCountByRolloutGroupId(rolloutGroupId); + + final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus(rolloutStatusCountItems, + rolloutGroup.getTotalTargets()); + rolloutGroup.setTotalTargetCountStatus(totalTargetCountStatus); + return rolloutGroup; + + } + + private Map> getStatusCountItemForRolloutGroup( + final List rolloutGroupIds) { + final List resultList = actionRepository + .getStatusCountByRolloutGroupId(rolloutGroupIds); + return resultList.stream().collect(Collectors.groupingBy(TotalTargetCountActionStatus::getId)); + } + + /** + * Get targets of specified rollout group. + * + * @param rolloutGroup + * rollout group + * @param specification + * the specification for filtering the targets of a rollout group + * @param page + * the page request to sort and limit the result + * + * @return Page list of targets of a rollout group + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) + public Page findRolloutGroupTargets(final RolloutGroup rolloutGroup, + final Specification specification, final Pageable page) { + return targetRepository.findAll((root, query, criteriaBuilder) -> { + final ListJoin rolloutTargetJoin = root.join(Target_.rolloutTargetGroup); + return criteriaBuilder.and(specification.toPredicate(root, query, criteriaBuilder), + criteriaBuilder.equal(rolloutTargetJoin.get(RolloutTargetGroup_.rolloutGroup), rolloutGroup)); + } , page); + } + + /** + * Get targets of specified rollout group. + * + * @param rolloutGroup + * rollout group + * @param page + * the page request to sort and limit the result + * + * @return Page list of targets of a rollout group + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) + public Page findRolloutGroupTargets(@NotNull final RolloutGroup rolloutGroup, final Pageable page) { + if (isRolloutStatusReady(rolloutGroup)) { + // in case of status ready the action has not been created yet and + // the relation information between target and rollout-group is + // stored in the #TargetRolloutGroup. + return targetRepository.findByRolloutTargetGroupRolloutGroupId(rolloutGroup.getId(), page); + } + return targetRepository.findByActionsRolloutGroup(rolloutGroup, page); + } + + private boolean isRolloutStatusReady(final RolloutGroup rolloutGroup) { + return rolloutGroup != null && RolloutStatus.READY.equals(rolloutGroup.getRollout().getStatus()); + } + + /** + * + * Find all targets with action status by rollout group id. The action + * status might be {@code null} if for the target within the rollout no + * actions as been created, e.g. the target already had assigned the same + * distribution set we do not create an action for it but the target is in + * the result list of the rollout-group. + * + * @param pageRequest + * the page request to sort and limit the result + * @param rolloutGroup + * rollout group + * @return {@link TargetWithActionStatus} target with action status + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) + public Page findAllTargetsWithActionStatus(final PageRequest pageRequest, + @NotNull final RolloutGroup rolloutGroup) { + + final CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + final CriteriaQuery query = cb.createQuery(Object[].class); + final CriteriaQuery countQuery = cb.createQuery(Long.class); + + final Root targetRoot = query.distinct(true).from(RolloutTargetGroup.class); + final Join targetJoin = targetRoot.join(RolloutTargetGroup_.target); + final ListJoin actionJoin = targetRoot.join(RolloutTargetGroup_.actions, + JoinType.LEFT); + + final Root countQueryFrom = countQuery.distinct(true).from(RolloutTargetGroup.class); + countQuery + .select(cb.count(countQueryFrom.join(RolloutTargetGroup_.target).join(Target_.actions, JoinType.LEFT))) + .where(cb.equal(countQueryFrom.get(RolloutTargetGroup_.rolloutGroup), rolloutGroup)); + final Long totalCount = entityManager.createQuery(countQuery).getSingleResult(); + + final CriteriaQuery multiselect = query.multiselect(targetJoin, actionJoin.get(Action_.status)) + .where(cb.equal(targetRoot.get(RolloutTargetGroup_.rolloutGroup), rolloutGroup)); + final List targetWithActionStatus = entityManager.createQuery(multiselect) + .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); + } + +} \ No newline at end of file diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutGroupRepository.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutGroupRepository.java new file mode 100644 index 000000000..cf3f3d729 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutGroupRepository.java @@ -0,0 +1,131 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository; + +import java.util.List; + +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.transaction.annotation.Transactional; + +/** + * The repository interface for the {@link RolloutGroup} model. + */ +@Transactional(readOnly = true) +public interface RolloutGroupRepository + extends BaseEntityRepository, JpaSpecificationExecutor { + + /** + * Retrieves all {@link RolloutGroup} referring a specific rollout in the + * order of creating them. ID ASC. + * + * @param rollout + * the rollout the rolloutgroups belong to + * @return the rollout groups belonging to a rollout ordered by ID ASC. + */ + List findByRolloutOrderByIdAsc(final Rollout rollout); + + /** + * Retrieves all {@link RolloutGroup} referring a specific rollout in a + * specific {@link RolloutGroupStatus}. + * + * @param rollout + * the rollout the rolloutgroup belong to + * @param status + * the status of the rollout groups + * @return the rollout groups belonging to a rollout in a specific status + */ + List findByRolloutAndStatus(final Rollout rollout, final RolloutGroupStatus status); + + /** + * Counts all {@link RolloutGroup} referring a specific rollout. + * + * @param rollout + * the rollout the rolloutgroup belong to + * @return the count of the rollout groups for a specific rollout + */ + Long countByRollout(final Rollout rollout); + + /** + * Counts all {@link RolloutGroup} referring a specific rollout in a + * specific {@link RolloutGroupStatus}. + * + * @param rollout + * the rollout the rolloutgroup belong to + * @param rolloutGroupStatus + * the status of the rollout groups + * @return the count of rollout groups belonging to a rollout in a specific + * status + */ + Long countByRolloutAndStatus(Rollout rollout, RolloutGroupStatus rolloutGroupStatus); + + /** + * Counts all {@link RolloutGroup} referring a specific rollout in specific + * {@link RolloutGroupStatus}s. An in-clause statement does not work with + * the spring-data, so this is specific usecase regarding to the + * rollout-management to find out rolloutgroups which are in specific + * states. + * + * @param rollout + * the rollout the rolloutgroup belong to + * @param rolloutGroupStatus1 + * the status of the rollout groups + * @param rolloutGroupStatus2 + * the status of the rollout groups + * @return the count of rollout groups belonging to a rollout in specific + * status + */ + @Query("SELECT COUNT(r.id) FROM RolloutGroup r WHERE r.rollout = :rollout and (r.status = :status1 or r.status = :status2)") + Long countByRolloutAndStatusOrStatus(@Param("rollout") Rollout rollout, + @Param("status1") RolloutGroupStatus rolloutGroupStatus1, + @Param("status2") RolloutGroupStatus rolloutGroupStatus2); + + /** + * 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 status + * the status of the rolloutgroups + * @return The child {@link RolloutGroup}s in a specific status + */ + List findByParentAndStatus(RolloutGroup rolloutGroup, RolloutGroupStatus status); + + /** + * Retrieves all {@link RolloutGroup} for a specific rollout and status not + * having ordered by ID DESC, latest top. + * + * @param rollout + * the rollout the rolloutgroup belong to + * @param notStatus + * the status which the rolloutgroup should not have + * @return rolloutgroup referring to a rollout and not having a specific + * status ordered by ID DESC. + */ + List findByRolloutAndStatusNotOrderByIdDesc(Rollout rollout, RolloutGroupStatus notStatus); + + /** + * Retrieves all {@link RolloutGroup} for a specific rollout. + * + * @param rolloutId + * the ID of the rollout to find the rollout groups + * @param page + * the page request to sort, limit the result + * @return a page of found {@link RolloutGroup} or {@code empty}. + */ + Page findByRolloutId(final Long rolloutId, Pageable page); + +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java new file mode 100644 index 000000000..50dc9f2b0 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java @@ -0,0 +1,870 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; +import java.util.stream.Collectors; + +import javax.persistence.EntityManager; +import javax.validation.constraints.NotNull; + +import org.eclipse.hawkbit.cache.CacheWriteNotify; +import org.eclipse.hawkbit.eventbus.event.RolloutGroupCreatedEvent; +import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; +import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException; +import org.eclipse.hawkbit.repository.model.Action; +import org.eclipse.hawkbit.repository.model.Action.ActionType; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupConditions; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorCondition; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition; +import org.eclipse.hawkbit.repository.model.RolloutTargetGroup; +import org.eclipse.hawkbit.repository.model.Rollout_; +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.rollout.condition.RolloutGroupActionEvaluator; +import org.eclipse.hawkbit.rollout.condition.RolloutGroupConditionEvaluator; +import org.hibernate.validator.constraints.NotEmpty; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.ApplicationContext; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Service; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.DefaultTransactionDefinition; +import org.springframework.transaction.support.TransactionTemplate; +import org.springframework.util.Assert; +import org.springframework.validation.annotation.Validated; + +/** + * RolloutManagement to control rollouts e.g. like creating, starting, resuming + * and pausing rollouts. This service secures all the functionality based on the + * {@link PreAuthorize} annotation on methods. + */ +@Validated +@Service +@EnableScheduling +@Transactional(readOnly = true) +public class RolloutManagement { + private static final Logger LOGGER = LoggerFactory.getLogger(RolloutManagement.class); + + @Autowired + private EntityManager entityManager; + + @Autowired + private RolloutRepository rolloutRepository; + + @Autowired + private TargetManagement targetManagement; + + @Autowired + private TargetRepository targetRepository; + + @Autowired + private RolloutGroupRepository rolloutGroupRepository; + + @Autowired + private DeploymentManagement deploymentManagement; + + @Autowired + private RolloutTargetGroupRepository rolloutTargetGroupRepository; + + @Autowired + private ActionRepository actionRepository; + + @Autowired + private ApplicationContext context; + + @Autowired + private NoCountPagingRepository criteriaNoCountDao; + + @Autowired + private PlatformTransactionManager txManager; + + @Autowired + private CacheWriteNotify cacheWriteNotify; + + @Autowired + @Qualifier("asyncExecutor") + private Executor executor; + + /* + * set which stores the rollouts which are asynchronously creating. This is + * necessary to verify rollouts which maybe stuck during creationg e.g. + * because of database interruption, failures or even application crash. + * !This is not cluster aware! + */ + private static final Set creatingRollouts = ConcurrentHashMap.newKeySet(); + + /* + * set which stores the rollouts which are asynchronously starting. This is + * necessary to verify rollouts which maybe stuck during starting e.g. + * because of database interruption, failures or even application crash. + * !This is not cluster aware! + */ + private static final Set startingRollouts = ConcurrentHashMap.newKeySet(); + + /** + * Retrieves all rollouts. + * + * @param page + * the page request to sort and limit the result + * @return a page of found rollouts + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) + public Page findAll(final Pageable page) { + return rolloutRepository.findAll(page); + } + + /** + * Retrieves all rollouts found by the given specification. + * + * @param specification + * the specification to filter rollouts + * @param page + * the page request to sort and limit the result + * @return a page of found rollouts + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) + public Page findAllWithDetailedStatusByPredicate(final Specification specification, + final Pageable page) { + final Page findAll = rolloutRepository.findAll(specification, page); + setRolloutStatusDetails(findAll); + return findAll; + } + + /** + * Retrieves a specific rollout by its ID. + * + * @param rolloutId + * the ID of the rollout to retrieve + * @return the founded rollout or {@code null} if rollout with given ID does + * not exists + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) + public Rollout findRolloutById(final Long rolloutId) { + return rolloutRepository.findOne(rolloutId); + } + + /** + * Persists a new rollout entity. The filter within the + * {@link Rollout#getTargetFilterQuery()} is used to retrieve the targets + * which are effected by this rollout to create. The targets will then be + * split up into groups. The size of the groups can be defined in the + * {@code groupSize} parameter. + * + * The rollout is not started. Only the preparation of the rollout is done, + * persisting and creating all the necessary groups. The Rollout and the + * groups are persisted in {@link RolloutStatus#READY} and + * {@link RolloutGroupStatus#READY} so they can be started + * {@link #startRollout(Rollout)}. + * + * @param rollout + * the rollout entity to create + * @param amountGroup + * the amount of groups to split the rollout into + * @param conditions + * the rolloutgroup conditions and actions which should be + * applied for each {@link RolloutGroup} + * @return the persisted rollout. + * + * @throws IllegalArgumentException + * in case the given groupSize is zero or lower. + */ + @Transactional + @Modifying + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE) + public Rollout createRollout(final Rollout rollout, final int amountGroup, + final RolloutGroupConditions conditions) { + final Rollout savedRollout = createRollout(rollout, amountGroup); + return createRolloutGroups(amountGroup, conditions, savedRollout); + } + + /** + * Persists a new rollout entity. The filter within the + * {@link Rollout#getTargetFilterQuery()} is used to retrieve the targets + * which are effected by this rollout to create. The creation of the rollout + * will be done synchronously and will be returned. The targets will then be + * split up into groups. The size of the groups can be defined in the + * {@code groupSize} parameter. + * + * The creation of the rollout groups is executed asynchronously due it + * might take some time to split up the targets into groups. The creation of + * the {@link RolloutGroup} is published as event + * {@link RolloutGroupCreatedEvent}. + * + * The rollout is in status {@link RolloutStatus#CREATING} until all rollout + * groups has been created and the targets are split up, then the rollout + * will change the status to {@link RolloutStatus#READY}. + * + * The rollout is not started. Only the preparation of the rollout is done, + * persisting and creating all the necessary groups. The Rollout and the + * groups are persisted in {@link RolloutStatus#READY} and + * {@link RolloutGroupStatus#READY} so they can be started + * {@link #startRollout(Rollout)}. + * + * @param rollout + * the rollout to be created + * @param amountGroup + * the number of groups should be created for the rollout and + * split up the targets + * @param conditions + * the rolloutgroup conditions and actions which should be + * applied for each {@link RolloutGroup} + * @return the created rollout entity in state + * {@link RolloutStatus#CREATING} + */ + @Transactional + @Modifying + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE) + public Rollout createRolloutAsync(final Rollout rollout, final int amountGroup, + final RolloutGroupConditions conditions) { + final Rollout savedRollout = createRollout(rollout, amountGroup); + creatingRollouts.add(savedRollout.getName()); + executor.execute(() -> { + try { + final DefaultTransactionDefinition def = new DefaultTransactionDefinition(); + def.setName("creatingRollout"); + def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + new TransactionTemplate(txManager, def).execute(status -> { + createRolloutGroups(amountGroup, conditions, savedRollout); + return null; + }); + } finally { + creatingRollouts.remove(savedRollout.getName()); + } + }); + return savedRollout; + } + + private Rollout createRollout(final Rollout rollout, final int amountGroup) { + verifyRolloutGroupParameter(amountGroup); + final Long totalTargets = targetManagement.countTargetByTargetFilterQuery(rollout.getTargetFilterQuery()); + rollout.setTotalTargets(totalTargets.longValue()); + return rolloutRepository.save(rollout); + } + + private void verifyRolloutGroupParameter(final int amountGroup) { + if (amountGroup <= 0) { + throw new IllegalArgumentException("the amountGroup must be greater than zero"); + } else if (amountGroup > 500) { + throw new IllegalArgumentException("the amountGroup must not be greater than 500"); + } + } + + /** + * Method for creating rollout groups and calculating group sizes. Group + * sizes are calculated by dividing the total count of targets through the + * amount of given groups. In same cases this will lead to less rollout + * groups than given by client. + * + * @param amountGroup + * the amount of groups + * @param conditions + * the rollout group conditions + * @param savedRollout + * the rollout + * @return the rollout with created groups + */ + private Rollout createRolloutGroups(final int amountGroup, final RolloutGroupConditions conditions, + final Rollout savedRollout) { + int pageIndex = 0; + int groupIndex = 0; + final Long totalCount = savedRollout.getTotalTargets(); + final int groupSize = (int) Math.ceil((double) totalCount / (double) amountGroup); + // validate if the amount of groups that will be created are the amount + // of groups that the client what's to have created. + int amountGroupValidated = amountGroup; + final int amountGroupCreation = (int) (Math.ceil((double) totalCount / (double) groupSize)); + if (amountGroupCreation == (amountGroup - 1)) { + amountGroupValidated--; + } + RolloutGroup lastSavedGroup = null; + while (pageIndex < totalCount) { + groupIndex++; + final String nameAndDesc = "group-" + groupIndex; + final RolloutGroup group = new RolloutGroup(); + group.setName(nameAndDesc); + group.setDescription(nameAndDesc); + group.setRollout(savedRollout); + group.setParent(lastSavedGroup); + group.setSuccessCondition(conditions.getSuccessCondition()); + group.setSuccessConditionExp(conditions.getSuccessConditionExp()); + group.setErrorCondition(conditions.getErrorCondition()); + group.setErrorConditionExp(conditions.getErrorConditionExp()); + group.setErrorAction(conditions.getErrorAction()); + group.setErrorActionExp(conditions.getErrorActionExp()); + + final RolloutGroup savedGroup = rolloutGroupRepository.save(group); + + final Slice targetGroup = targetManagement.findTargetsAll(savedRollout.getTargetFilterQuery(), + new OffsetBasedPageRequest(pageIndex, groupSize, new Sort(Direction.ASC, "id"))); + savedGroup.setTotalTargets(targetGroup.getContent().size()); + + lastSavedGroup = savedGroup; + + targetGroup + .forEach(target -> rolloutTargetGroupRepository.save(new RolloutTargetGroup(savedGroup, target))); + cacheWriteNotify.rolloutGroupCreated(groupIndex, savedRollout.getId(), savedGroup.getId(), + amountGroupValidated, groupIndex); + pageIndex += groupSize; + + } + + savedRollout.setStatus(RolloutStatus.READY); + return rolloutRepository.save(savedRollout); + } + + /** + * Starts a rollout which has been created. The rollout must be in + * {@link RolloutStatus#READY} state. The according actions will be created + * for each affected target in the rollout. The actions of the first group + * will be started immediately {@link RolloutGroupStatus#RUNNING} as the + * other groups will be {@link RolloutGroupStatus#SCHEDULED} state. + * + * The rollout itself will be then also in {@link RolloutStatus#RUNNING}. + * + * @param rollout + * the rollout to be started + * + * @throws RolloutIllegalStateException + * if given rollout is not in {@link RolloutStatus#READY}. Only + * ready rollouts can be started. + */ + @Transactional + @Modifying + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR + + SpringEvalExpressions.IS_SYSTEM_CODE) + public Rollout startRollout(final Rollout rollout) { + final Rollout mergedRollout = entityManager.merge(rollout); + checkIfRolloutCanStarted(rollout, mergedRollout); + return doStartRollout(mergedRollout); + } + + /** + * Starts a rollout asynchronously which has been created. The rollout must + * be in {@link RolloutStatus#READY} state. The according actions will be + * created asynchronously for each affected target in the rollout. The + * actions of the first group will be started immediately + * {@link RolloutGroupStatus#RUNNING} as the other groups will be + * {@link RolloutGroupStatus#SCHEDULED} state. + * + * The rollout itself will be then also in {@link RolloutStatus#RUNNING}. + * + * @param rollout + * the rollout to be started + * + * @throws RolloutIllegalStateException + * if given rollout is not in {@link RolloutStatus#READY}. Only + * ready rollouts can be started. + */ + @Transactional + @Modifying + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR + + SpringEvalExpressions.IS_SYSTEM_CODE) + public Rollout startRolloutAsync(final Rollout rollout) { + final Rollout mergedRollout = entityManager.merge(rollout); + checkIfRolloutCanStarted(rollout, mergedRollout); + mergedRollout.setStatus(RolloutStatus.STARTING); + final Rollout updatedRollout = rolloutRepository.save(mergedRollout); + startingRollouts.add(updatedRollout.getName()); + executor.execute(() -> { + try { + final DefaultTransactionDefinition def = new DefaultTransactionDefinition(); + def.setName("startingRollout"); + def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + new TransactionTemplate(txManager, def).execute(status -> { + doStartRollout(updatedRollout); + return null; + }); + } finally { + startingRollouts.remove(updatedRollout.getName()); + } + }); + return updatedRollout; + + } + + private Rollout doStartRollout(final Rollout rollout) { + final DistributionSet distributionSet = rollout.getDistributionSet(); + final ActionType actionType = rollout.getActionType(); + final long forceTime = rollout.getForcedTime(); + final List rolloutGroups = rolloutGroupRepository.findByRolloutOrderByIdAsc(rollout); + for (int iGroup = 0; iGroup < rolloutGroups.size(); iGroup++) { + final RolloutGroup rolloutGroup = rolloutGroups.get(iGroup); + final List targetGroup = targetRepository.findByRolloutTargetGroupRolloutGroup(rolloutGroup); + // firstgroup can already be started + if (iGroup == 0) { + final List targetsWithActionType = targetGroup.stream() + .map(t -> new TargetWithActionType(t.getControllerId(), actionType, forceTime)) + .collect(Collectors.toList()); + deploymentManagement.assignDistributionSet(distributionSet.getId(), targetsWithActionType, rollout, + rolloutGroup); + rolloutGroup.setStatus(RolloutGroupStatus.RUNNING); + } else { + // create only not active actions with status scheduled so they + // can be activated later + deploymentManagement.createScheduledAction(targetGroup, distributionSet, actionType, forceTime, rollout, + rolloutGroup); + rolloutGroup.setStatus(RolloutGroupStatus.SCHEDULED); + } + rolloutGroupRepository.save(rolloutGroup); + } + rollout.setStatus(RolloutStatus.RUNNING); + return rolloutRepository.save(rollout); + } + + /** + * Pauses a rollout which is currently running. The Rollout switches + * {@link RolloutStatus#PAUSED}. {@link RolloutGroup}s which are currently + * running will be untouched. {@link RolloutGroup}s which are + * {@link RolloutGroupStatus#SCHEDULED} will not be started and keep in + * {@link RolloutGroupStatus#SCHEDULED} state until the rollout is + * {@link RolloutManagement#resumeRollout(Rollout)}. + * + * Switching the rollout status to {@link RolloutStatus#PAUSED} is + * sufficient due the {@link #checkRunningRollouts(long)} will not check + * this rollout anymore. + * + * @param rollout + * the rollout to be paused. + * + * @throws RolloutIllegalStateException + * if given rollout is not in {@link RolloutStatus#RUNNING}. + * Only running rollouts can be paused. + */ + @Transactional + @Modifying + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR + + SpringEvalExpressions.IS_SYSTEM_CODE) + public void pauseRollout(final Rollout rollout) { + final Rollout mergedRollout = entityManager.merge(rollout); + if (mergedRollout.getStatus() != RolloutStatus.RUNNING) { + throw new RolloutIllegalStateException("Rollout can only be paused in state running but current state is " + + rollout.getStatus().name().toLowerCase()); + } + // setting the complete rollout only in paused state. This is sufficient + // due the currently running groups will be completed and new groups are + // not started until rollout goes back to running state again. The + // periodically check for running rollouts will skip rollouts in pause + // state. + mergedRollout.setStatus(RolloutStatus.PAUSED); + rolloutRepository.save(mergedRollout); + } + + /** + * Resumes a paused rollout. The rollout switches back to + * {@link RolloutStatus#RUNNING} state which is then picked up again by the + * {@link #checkRunningRollouts(long)}. + * + * @param rollout + * the rollout to be resumed + * @throws RolloutIllegalStateException + * if given rollout is not in {@link RolloutStatus#PAUSED}. Only + * paused rollouts can be resumed. + */ + @Transactional + @Modifying + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR + + SpringEvalExpressions.IS_SYSTEM_CODE) + public void resumeRollout(final Rollout rollout) { + final Rollout mergedRollout = entityManager.merge(rollout); + if (!(RolloutStatus.PAUSED.equals(mergedRollout.getStatus()))) { + throw new RolloutIllegalStateException("Rollout can only be resumed in state paused but current state is " + + rollout.getStatus().name().toLowerCase()); + } + mergedRollout.setStatus(RolloutStatus.RUNNING); + rolloutRepository.save(mergedRollout); + } + + /** + * Checking running rollouts. Rollouts which are checked updating the + * {@link Rollout#setLastCheck(long)} to indicate that the current instance + * is handling the specific rollout. This code should run as system-code. + * + *
+     * {@code
+     *  SystemSecurityContext.runAsSystem(new Callable() {
+     *     public Void call() throws Exception {
+     *        //run system-code
+     *     }
+     * });
+     *  }
+     * 
+ * + * This method is attend to be called by a scheduler. + * {@link RolloutScheduler}. And must be running in an transaction so it's + * splitted from the scheduler. + * + * Rollouts which are currently running are investigated, by means the + * error- and finish condition of running groups in this rollout are + * evaluated. + * + * @param delayBetweenChecks + * the time in milliseconds of the delay between the further and + * this check. This check is only applied if the last check is + * less than (lastcheck-delay). + */ + @Transactional + @Modifying + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR + + SpringEvalExpressions.IS_SYSTEM_CODE) + public void checkRunningRollouts(final long delayBetweenChecks) { + verifyStuckedRollouts(); + final long lastCheck = System.currentTimeMillis(); + final int updated = rolloutRepository.updateLastCheck(lastCheck, delayBetweenChecks, RolloutStatus.RUNNING); + + if (updated == 0) { + // nothing to check, maybe another instance already checked in + // between + LOGGER.info("No rolloutcheck necessary for current scheduled check {}, next check at {}", lastCheck, + lastCheck + delayBetweenChecks); + return; + } + + final List rolloutsToCheck = rolloutRepository.findByLastCheckAndStatus(lastCheck, + RolloutStatus.RUNNING); + LOGGER.info("Found {} running rollouts to check", rolloutsToCheck.size()); + + for (final Rollout rollout : rolloutsToCheck) { + LOGGER.debug("Checking rollout {}", rollout); + final List rolloutGroups = rolloutGroupRepository.findByRolloutAndStatus(rollout, + RolloutGroupStatus.RUNNING); + + if (rolloutGroups.isEmpty()) { + // no running rollouts, probably there was an error + // somewhere at the latest group. And the latest group has + // been switched from running into error state. So we need + // to find the latest group which + executeLatestRolloutGroup(rollout); + } else { + LOGGER.debug("Rollout {} has {} running groups", rollout.getId(), rolloutGroups.size()); + executeRolloutGroups(rollout, rolloutGroups); + } + + if (isRolloutComplete(rollout)) { + LOGGER.info("Rollout {} is finished, setting finished status", rollout); + rollout.setStatus(RolloutStatus.FINISHED); + rolloutRepository.save(rollout); + } + } + } + + /** + * Verifies and handles stucked rollouts in asynchronous creation or + * starting state. If rollouts are created or started asynchronously it + * might be that they keep in state {@link RolloutStatus#CREATING} or + * {@link RolloutStatus#STARTING} due database or application interruption. + * In case this happens, set the rollout to error state. + */ + private void verifyStuckedRollouts() { + final List rolloutsInCreatingState = rolloutRepository.findByStatus(RolloutStatus.CREATING); + rolloutsInCreatingState.stream().filter(rollout -> !creatingRollouts.contains(rollout.getName())) + .forEach(rollout -> { + LOGGER.warn( + "Determined error during rollout creation of rollout {}, stucking in creating state, setting to status", + rollout, RolloutStatus.ERROR_CREATING); + rollout.setStatus(RolloutStatus.ERROR_CREATING); + rolloutRepository.save(rollout); + }); + + final List rolloutsInStartingState = rolloutRepository.findByStatus(RolloutStatus.STARTING); + rolloutsInStartingState.stream().filter(rollout -> !startingRollouts.contains(rollout.getName())) + .forEach(rollout -> { + LOGGER.warn( + "Determined error during rollout starting of rollout {}, stucking in starting state, setting to status", + rollout, RolloutStatus.ERROR_STARTING); + rollout.setStatus(RolloutStatus.ERROR_STARTING); + rolloutRepository.save(rollout); + }); + + } + + private void executeRolloutGroups(final Rollout rollout, final List rolloutGroups) { + for (final RolloutGroup rolloutGroup : rolloutGroups) { + // error state check, do we need to stop the whole + // rollout because of error? + final RolloutGroupErrorCondition errorCondition = rolloutGroup.getErrorCondition(); + final boolean isError = checkErrorState(rollout, rolloutGroup, errorCondition); + if (isError) { + LOGGER.info("Rollout {} {} has error, calling error action", rollout.getName(), rollout.getId()); + callErrorAction(rollout, rolloutGroup); + } else { + // not in error so check finished state, do we need to + // start the next group? + final RolloutGroupSuccessCondition finishedCondition = rolloutGroup.getSuccessCondition(); + checkFinishCondition(rollout, rolloutGroup, finishedCondition); + if (isRolloutGroupComplete(rollout, rolloutGroup)) { + rolloutGroup.setStatus(RolloutGroupStatus.FINISHED); + rolloutGroupRepository.save(rolloutGroup); + } + } + } + } + + private void executeLatestRolloutGroup(final Rollout rollout) { + final List latestRolloutGroup = rolloutGroupRepository + .findByRolloutAndStatusNotOrderByIdDesc(rollout, RolloutGroupStatus.SCHEDULED); + if (latestRolloutGroup.isEmpty()) { + return; + } + executeRolloutGroupSuccessAction(rollout, latestRolloutGroup.get(0)); + } + + private void callErrorAction(final Rollout rollout, final RolloutGroup rolloutGroup) { + try { + context.getBean(rolloutGroup.getErrorAction().getBeanName(), RolloutGroupActionEvaluator.class) + .eval(rollout, rolloutGroup, rolloutGroup.getErrorActionExp()); + } catch (final BeansException e) { + LOGGER.error("Something bad happend when accessing the error action bean {}", + rolloutGroup.getErrorAction().getBeanName(), e); + } + } + + private boolean isRolloutComplete(final Rollout rollout) { + final Long groupsActiveLeft = rolloutGroupRepository.countByRolloutAndStatusOrStatus(rollout, + RolloutGroupStatus.RUNNING, RolloutGroupStatus.SCHEDULED); + return groupsActiveLeft == 0; + } + + private boolean isRolloutGroupComplete(final Rollout rollout, final RolloutGroup rolloutGroup) { + final Long actionsLeftForRollout = actionRepository + .countByRolloutAndRolloutGroupAndStatusNotAndStatusNotAndStatusNot(rollout, rolloutGroup, + Action.Status.ERROR, Action.Status.FINISHED, Action.Status.CANCELED); + return actionsLeftForRollout == 0; + } + + private boolean checkErrorState(final Rollout rollout, final RolloutGroup rolloutGroup, + final RolloutGroupErrorCondition errorCondition) { + if (errorCondition == null) { + // there is no error condition, so return false, don't have error. + return false; + } + try { + return context.getBean(errorCondition.getBeanName(), RolloutGroupConditionEvaluator.class).eval(rollout, + rolloutGroup, rolloutGroup.getErrorConditionExp()); + } catch (final BeansException e) { + LOGGER.error("Something bad happend when accessing the error condition bean {}", + errorCondition.getBeanName(), e); + return false; + } + } + + private boolean checkFinishCondition(final Rollout rollout, final RolloutGroup rolloutGroup, + final RolloutGroupSuccessCondition finishCondition) { + LOGGER.trace("Checking finish condition {} on rolloutgroup {}", finishCondition, rolloutGroup); + try { + final boolean isFinished = context + .getBean(finishCondition.getBeanName(), RolloutGroupConditionEvaluator.class) + .eval(rollout, rolloutGroup, rolloutGroup.getSuccessConditionExp()); + if (isFinished) { + LOGGER.info("Rolloutgroup {} is finished, starting next group", rolloutGroup); + executeRolloutGroupSuccessAction(rollout, rolloutGroup); + } else { + LOGGER.debug("Rolloutgroup {} is still running", rolloutGroup); + } + return isFinished; + } catch (final BeansException e) { + LOGGER.error("Something bad happend when accessing the finish condition bean {}", + finishCondition.getBeanName(), e); + return false; + } + } + + private void executeRolloutGroupSuccessAction(final Rollout rollout, final RolloutGroup rolloutGroup) { + context.getBean(rolloutGroup.getSuccessAction().getBeanName(), RolloutGroupActionEvaluator.class).eval(rollout, + rolloutGroup, rolloutGroup.getSuccessActionExp()); + } + + /** + * Counts all {@link Target}s in the repository. + * + * @return number of targets + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) + public Long countRolloutsAll() { + return rolloutRepository.count(); + } + + /** + * Count rollouts by specified filter text. + * + * @param searchText + * name or description + * @return total count rollouts for specified filter text. + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) + public Long countRolloutsAllByFilters(final String searchText) { + return rolloutRepository.count(likeNameOrDescription(searchText)); + } + + private static Specification likeNameOrDescription(final String searchText) { + return (rolloutRoot, query, criteriaBuilder) -> { + final String searchTextToLower = searchText.toLowerCase(); + return criteriaBuilder.or( + criteriaBuilder.like(criteriaBuilder.lower(rolloutRoot.get(Rollout_.name)), searchTextToLower), + criteriaBuilder.like(criteriaBuilder.lower(rolloutRoot.get(Rollout_.description)), + searchTextToLower)); + }; + } + + /** + * * Retrieves a specific rollout by its ID. + * + * @param pageable + * the page request to sort and limit the result + * @param searchText + * search text which matches name or description of rollout + * @return the founded rollout or {@code null} if rollout with given ID does + * not exists + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) + public Slice findRolloutByFilters(final Pageable pageable, @NotEmpty final String searchText) { + final Specification specs = likeNameOrDescription(searchText); + final Slice findAll = criteriaNoCountDao.findAll(specs, pageable, Rollout.class); + setRolloutStatusDetails(findAll); + return findAll; + } + + /** + * Retrieves a specific rollout by its name. + * + * @param rolloutName + * the name of the rollout to retrieve + * @return the founded rollout or {@code null} if rollout with given name + * does not exists + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) + public Rollout findRolloutByName(final String rolloutName) { + return rolloutRepository.findByName(rolloutName); + } + + /** + * Update rollout details. + * + * @param rollout + * rollout to be updated + * + * @return Rollout updated rollout + */ + @NotNull + @Transactional + @Modifying + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE) + public Rollout updateRollout(@NotNull final Rollout rollout) { + Assert.notNull(rollout.getId()); + return rolloutRepository.save(rollout); + } + + /** + * Get count of targets in different status in rollout. + * + * @param page + * the page request to sort and limit the result + * @return a list of rollouts with details of targets count for different + * statuses + * + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) + public Page findAllRolloutsWithDetailedStatus(final Pageable page) { + final Page rollouts = findAll(page); + setRolloutStatusDetails(rollouts); + return rollouts; + + } + + /** + * Get count of targets in different status in rollout. + * + * @param rolloutId + * rollout id + * @return rollout details of targets count for different statuses + * + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) + public Rollout findRolloutWithDetailedStatus(final Long rolloutId) { + final Rollout rollout = findRolloutById(rolloutId); + final List rolloutStatusCountItems = actionRepository + .getStatusCountByRolloutId(rolloutId); + final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus(rolloutStatusCountItems, + rollout.getTotalTargets()); + rollout.setTotalTargetCountStatus(totalTargetCountStatus); + return rollout; + } + + private Map> getStatusCountItemForRollout(final List rolloutIds) { + final List resultList = actionRepository.getStatusCountByRolloutId(rolloutIds); + return resultList.stream().collect(Collectors.groupingBy(TotalTargetCountActionStatus::getId)); + } + + private void setRolloutStatusDetails(final Slice rollouts) { + final List rolloutIds = rollouts.getContent().stream().map(rollout -> rollout.getId()) + .collect(Collectors.toList()); + final Map> allStatesForRollout = getStatusCountItemForRollout( + rolloutIds); + + for (final Rollout rollout : rollouts) { + final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus( + allStatesForRollout.get(rollout.getId()), rollout.getTotalTargets()); + rollout.setTotalTargetCountStatus(totalTargetCountStatus); + } + } + + private void checkIfRolloutCanStarted(final Rollout rollout, final Rollout mergedRollout) { + if (!(RolloutStatus.READY.equals(mergedRollout.getStatus()))) { + throw new RolloutIllegalStateException("Rollout can only be started in state ready but current state is " + + rollout.getStatus().name().toLowerCase()); + } + } + + /*** + * Get finished percentage details for a specified group which is in running + * state. + * + * @param rolloutId + * the ID of the {@link Rollout} + * @param rolloutGroup + * the ID of the {@link RolloutGroup} + * @return percentage finished + */ + public float getFinishedPercentForRunningGroup(final Long rolloutId, final RolloutGroup rolloutGroup) { + final Long totalGroup = rolloutGroup.getTotalTargets(); + final Long finished = actionRepository.countByRolloutIdAndRolloutGroupIdAndStatus(rolloutId, + rolloutGroup.getId(), Action.Status.FINISHED); + if (totalGroup == 0) { + // in case e.g. targets has been deleted we don't have any actions + // left for this group, so the group is finished + return 100; + } + // calculate threshold + return ((float) finished / (float) totalGroup) * 100; + } +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutRepository.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutRepository.java new file mode 100644 index 000000000..9c4318a3f --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutRepository.java @@ -0,0 +1,87 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository; + +import java.util.List; + +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.transaction.annotation.Transactional; + +/** + * The repository interface for the {@link Rollout} model. + */ +@Transactional(readOnly = true) +public interface RolloutRepository extends BaseEntityRepository, JpaSpecificationExecutor { + + /** + * Updates the {@code lastCheck} field of the {@link Rollout} for rollouts + * in a specific status and only if the {@code lastCheck} is overdue. + * + * @param lastCheck + * the time in milliseconds to set to the lastCheck column + * @param delay + * the delay between last checks + * @param status + * the status which the rollout should have to update the last + * check field + * @return the count of the updated rows. Zero if no row has been updated + */ + @Modifying + @Transactional + @Query("UPDATE Rollout r SET r.lastCheck = :lastCheck WHERE r.lastCheck < (:lastCheck - :delay) AND r.status=:status") + int updateLastCheck(@Param("lastCheck") final long lastCheck, @Param("delay") final long delay, + @Param("status") final RolloutStatus status); + + /** + * Retrieves all {@link Rollout} for a specific {@code lastCheck} time and + * for a specific status. + * + * @param lastCheck + * the lastCheck time to find the specific rollout. + * @param status + * the status of the rollout to find + * @return the list of {@link Rollout} for specific lastCheck time and + * status + */ + List findByLastCheckAndStatus(long lastCheck, RolloutStatus status); + + /** + * Retrieves all {@link Rollout} for a specific {@code name} + * + * @param name + * the rollout name + * @return {@link Rollout} for specific name + */ + Page findByName(final Pageable pageable, String name); + + /** + * Retrieves all {@link Rollout} for a specific {@code name} + * + * @param name + * the rollout name + * @return {@link Rollout} for specific name + */ + Rollout findByName(String name); + + /** + * Retrieves all {@link Rollout} for a specific status. + * + * @param status + * the status of the rollouts to retrieve + * @return a list of {@link Rollout} having the given status + */ + List findByStatus(final RolloutStatus status); +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutScheduler.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutScheduler.java new file mode 100644 index 000000000..b60d64cc5 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutScheduler.java @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository; + +import java.util.List; + +import org.eclipse.hawkbit.security.SystemSecurityContext; +import org.eclipse.hawkbit.tenancy.TenantAware; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.EnvironmentAware; +import org.springframework.context.annotation.Profile; +import org.springframework.core.env.Environment; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +/** + * Scheduler to schedule the + * {@link RolloutManagement#checkRunningRollouts(long)}. The delay between the + * checks be be configured using the property + * {@link #PROP_SCHEDULER_DELAY_PLACEHOLDER}. + */ +@Component +// don't active the rollout scheduler in test, otherwise it is hard to test +// rolloutmanagement and leads weird side-effects maybe. +@Profile("!test") +public class RolloutScheduler implements EnvironmentAware { + + private static final Logger logger = LoggerFactory.getLogger(RolloutScheduler.class); + + private static final String PROP_SCHEDULER_DELAY = "hawkbit.rollout.scheduler.fixedDelay"; + private static final long DEFAULT_SCHEDULER_DELAY = 30000L; + private static final String PROP_SCHEDULER_DELAY_PLACEHOLDER = "${" + PROP_SCHEDULER_DELAY + ":" + + DEFAULT_SCHEDULER_DELAY + "}"; + + @Autowired + private TenantAware tenantAware; + + @Autowired + private SystemManagement systemManagement; + + @Autowired + private RolloutManagement rolloutManagement; + + @Autowired + private SystemSecurityContext systemSecurityContext; + + private long fixedDelay = DEFAULT_SCHEDULER_DELAY; + + /** + * Scheduler method called by the spring-async mechanism. Retrieves all + * tenants from the {@link SystemManagement#findTenants()} and runs for each + * tenant the {@link RolloutManagement#checkRunningRollouts(long)} in the + * {@link SystemSecurityContext}. + */ + @Scheduled(initialDelayString = PROP_SCHEDULER_DELAY_PLACEHOLDER, fixedDelayString = PROP_SCHEDULER_DELAY_PLACEHOLDER) + public void rolloutScheduler() { + logger.debug("rollout schedule checker has been triggered."); + // run this code in system code privileged to have the necessary + // permission to query and create entities. + systemSecurityContext.runAsSystem(() -> { + // workaround eclipselink that is currently not possible to + // execute a query without multitenancy if MultiTenant + // annotation is used. + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=355458. So + // iterate through all tenants and execute the rollout check for + // each tenant seperately. + final List tenants = systemManagement.findTenants(); + logger.info("Checking rollouts for {} tenants", tenants.size()); + for (final String tenant : tenants) { + tenantAware.runAsTenant(tenant, () -> { + rolloutManagement.checkRunningRollouts(fixedDelay); + return null; + }); + } + return null; + }); + } + + @Override + public void setEnvironment(final Environment environment) { + fixedDelay = environment.getProperty(PROP_SCHEDULER_DELAY, Long.class, DEFAULT_SCHEDULER_DELAY); + } +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutTargetGroupRepository.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutTargetGroupRepository.java new file mode 100644 index 000000000..6564e2c1f --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutTargetGroupRepository.java @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository; + +import org.eclipse.hawkbit.repository.model.RolloutTargetGroup; +import org.eclipse.hawkbit.repository.model.RolloutTargetGroupId; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.repository.CrudRepository; + +/** + * + * + */ +public interface RolloutTargetGroupRepository + extends CrudRepository, JpaSpecificationExecutor { +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/SystemManagement.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/SystemManagement.java index 64865ac65..09b6025d3 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/SystemManagement.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/SystemManagement.java @@ -96,6 +96,12 @@ public class SystemManagement implements EnvironmentAware { @Autowired private TenantConfigurationRepository tenantConfigurationRepository; + @Autowired + private RolloutRepository rolloutRepository; + + @Autowired + private RolloutGroupRepository rolloutGroupRepository; + @Autowired private TenantAware tenantAware; @@ -209,7 +215,8 @@ public class SystemManagement implements EnvironmentAware { * @return list of all tenant names in the system. */ @NotNull - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_SYSTEM_ADMIN) + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_SYSTEM_ADMIN + SpringEvalExpressions.HAS_AUTH_OR + + SpringEvalExpressions.IS_SYSTEM_CODE) // tenant independent public List findTenants() { return tenantMetaDataRepository.findAll().stream().map(md -> md.getTenant()).collect(Collectors.toList()); @@ -234,11 +241,13 @@ public class SystemManagement implements EnvironmentAware { tenantMetaDataRepository.deleteByTenantIgnoreCase(tenant); tenantConfigurationRepository.deleteByTenantIgnoreCase(tenant); targetRepository.deleteByTenantIgnoreCase(tenant); + actionRepository.deleteByTenantIgnoreCase(tenant); + rolloutGroupRepository.deleteByTenantIgnoreCase(tenant); + rolloutRepository.deleteByTenantIgnoreCase(tenant); artifactRepository.deleteByTenantIgnoreCase(tenant); externalArtifactRepository.deleteByTenantIgnoreCase(tenant); externalArtifactProviderRepository.deleteByTenantIgnoreCase(tenant); targetTagRepository.deleteByTenantIgnoreCase(tenant); - actionRepository.deleteByTenantIgnoreCase(tenant); distributionSetTagRepository.deleteByTenantIgnoreCase(tenant); distributionSetRepository.deleteByTenantIgnoreCase(tenant); distributionSetTypeRepository.deleteByTenantIgnoreCase(tenant); diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/TargetRepository.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/TargetRepository.java index 6cb40cd09..35ba39660 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/TargetRepository.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/TargetRepository.java @@ -12,10 +12,12 @@ import java.util.Collection; import java.util.List; import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.repository.model.Tag; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetTag; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; +import org.eclipse.hawkbit.repository.model.TargetWithActionStatus; import org.springframework.cache.annotation.CacheEvict; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -278,4 +280,43 @@ public interface TargetRepository extends BaseEntityRepository, Jp @Query("UPDATE Target t SET t.assignedDistributionSet = :set, t.lastModifiedAt = :lastModifiedAt, t.lastModifiedBy = :lastModifiedBy WHERE t.id IN :targets") void setAssignedDistributionSet(@Param("set") DistributionSet set, @Param("lastModifiedAt") Long modifiedAt, @Param("lastModifiedBy") String modifiedBy, @Param("targets") Collection targets); + + List findByRolloutTargetGroupRolloutGroup(final RolloutGroup rolloutGroup); + + /** + * + * Finds all targets of a rollout group. + * + * @param rolloutGroupId + * the ID of the rollout group + * @param page + * the page request parameter + * @return a page of all targets related to a rollout group + */ + Page findByRolloutTargetGroupRolloutGroupId(final Long rolloutGroupId, Pageable page); + + /** + * Finds all targets related to a target rollout group stored for a specific + * rollout. + * + * @param rolloutGroup + * the rollout group the targets should belong to + * @param page + * the page request parameter + * @return a page of all targets related to a rollout group + */ + Page findByActionsRolloutGroup(RolloutGroup rolloutGroup, Pageable page); + + /** + * Find all targets with action status for a specific group. + * + * @param pageable + * the page request parameter + * @param rolloutGroupId + * the ID of the rollout group + * @return targets with action status + */ + @Query("select DISTINCT NEW org.eclipse.hawkbit.repository.model.TargetWithActionStatus(a.target,a.status) from Action a inner join fetch a.target t where a.rolloutGroup.id = :rolloutGroupId") + Page findTargetsWithActionStatusByRolloutGroupId(final Pageable pageable, + @Param("rolloutGroupId") Long rolloutGroupId); } diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/exception/RolloutIllegalStateException.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/exception/RolloutIllegalStateException.java new file mode 100644 index 000000000..f2920c18d --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/exception/RolloutIllegalStateException.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.exception; + +import org.eclipse.hawkbit.exception.SpServerError; +import org.eclipse.hawkbit.exception.SpServerRtException; + +/** + * the {@link RolloutIllegalStateException} is thrown when a rollout is changing + * it's state which is not valid. E.g. trying to start a already running + * rollout, or trying to resume a already finished rollout. + * + */ +public class RolloutIllegalStateException extends SpServerRtException { + + private static final long serialVersionUID = 1L; + private static final SpServerError THIS_ERROR = SpServerError.SP_ROLLOUT_ILLEGAL_STATE; + + /** + * Default constructor. + */ + public RolloutIllegalStateException() { + super(THIS_ERROR); + } + + /** + * Parameterized constructor. + * + * @param cause + * of the exception + */ + public RolloutIllegalStateException(final Throwable cause) { + super(THIS_ERROR, cause); + } + + /** + * Parameterized constructor. + * + * @param message + * of the exception + * @param cause + * of the exception + */ + public RolloutIllegalStateException(final String message, final Throwable cause) { + super(message, THIS_ERROR, cause); + } + + /** + * Parameterized constructor. + * + * @param message + * of the exception + */ + public RolloutIllegalStateException(final String message) { + super(message, THIS_ERROR); + } +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Action.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Action.java index 80624054a..0554e6c58 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Action.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Action.java @@ -94,6 +94,14 @@ public class Action extends BaseEntity implements Comparable { CascadeType.REMOVE }) private List actionStatus; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "rolloutgroup", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_rolloutgroup") ) + private RolloutGroup rolloutGroup; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "rollout", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_rollout") ) + private Rollout rollout; + /** * Note: filled only in {@link Status#DOWNLOAD}. */ @@ -221,6 +229,36 @@ public class Action extends BaseEntity implements Comparable { this.forcedTime = forcedTime; } + /** + * @return the rolloutGroup + */ + public RolloutGroup getRolloutGroup() { + return rolloutGroup; + } + + /** + * @param rolloutGroup + * the rolloutGroup to set + */ + public void setRolloutGroup(final RolloutGroup rolloutGroup) { + this.rolloutGroup = rolloutGroup; + } + + /** + * @return the rollout + */ + public Rollout getRollout() { + return rollout; + } + + /** + * @param rollout + * the rollout to set + */ + public void setRollout(final Rollout rollout) { + this.rollout = rollout; + } + @Override public int compareTo(final Action o) { if (super.getId() == null || o == null || o.getId() == null) { @@ -379,7 +417,13 @@ public class Action extends BaseEntity implements Comparable { /** * Action needs download by this target which has now started. */ - DOWNLOAD; + DOWNLOAD, + + /** + * Action is in waiting state, e.g. the action is scheduled in a rollout + * but not yet activated. + */ + SCHEDULED; } /** diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/ActionWithStatusCount.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/ActionWithStatusCount.java index abe2ee961..7d01b9dd2 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/ActionWithStatusCount.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/ActionWithStatusCount.java @@ -32,6 +32,7 @@ public class ActionWithStatusCount { private final String dsName; private final String dsVersion; private final Action action; + private final String rolloutName; /** * JPA constructor, the parameter are the result set columns of the custom @@ -59,10 +60,14 @@ public class ActionWithStatusCount { * the version of the distributionset * @param actionStatusCount * the count of the action status for this action + * @param rolloutName + * the rollout name */ + public ActionWithStatusCount(final Long actionId, final ActionType actionType, final boolean active, final long forcedTime, final Status status, final Long actionCreatedAt, final Long actionLastModifiedAt, - final Long dsId, final String dsName, final String dsVersion, final Long actionStatusCount) { + final Long dsId, final String dsName, final String dsVersion, final Long actionStatusCount, + final String rolloutName) { this.actionId = actionId; this.actionType = actionType; this.actionActive = active; @@ -74,6 +79,7 @@ public class ActionWithStatusCount { this.dsName = dsName; this.dsVersion = dsVersion; this.actionStatusCount = actionStatusCount; + this.rolloutName = rolloutName; this.action = new Action(); this.action.setActionType(actionType); @@ -166,4 +172,12 @@ public class ActionWithStatusCount { public Long getActionStatusCount() { return actionStatusCount; } + + /** + * @return the rolloutName + */ + public String getRolloutName() { + return rolloutName; + } + } diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/BaseEntity.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/BaseEntity.java index cd9873fe8..72d25be6c 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/BaseEntity.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/BaseEntity.java @@ -22,6 +22,7 @@ import javax.persistence.PrePersist; import javax.persistence.Version; import org.eclipse.hawkbit.eventbus.CacheFieldEntityListener; +import org.eclipse.hawkbit.eventbus.EntityPropertyChangeListener; import org.eclipse.hawkbit.repository.exception.TenantNotExistException; import org.eclipse.hawkbit.repository.model.helper.SystemManagementHolder; import org.eclipse.hawkbit.repository.model.helper.TenantAwareHolder; @@ -45,7 +46,7 @@ import org.springframework.hateoas.Identifiable; */ @MappedSuperclass @Access(AccessType.FIELD) -@EntityListeners({ AuditingEntityListener.class, CacheFieldEntityListener.class }) +@EntityListeners({ AuditingEntityListener.class, CacheFieldEntityListener.class, EntityPropertyChangeListener.class }) @TenantDiscriminatorColumn(name = "tenant", length = 40) @Multitenant(MultitenantType.SINGLE_TABLE) public abstract class BaseEntity implements Serializable, Identifiable { @@ -98,9 +99,9 @@ public abstract class BaseEntity implements Serializable, Identifiable { // service final String currentTenant = SystemManagementHolder.getInstance().currentTenant(); if (currentTenant == null) { - throw new TenantNotExistException( - "Tenant " + TenantAwareHolder.getInstance().getTenantAware().getCurrentTenant() - + " does not exists, cannot create entity " + this.getClass() + " with id " + id); + throw new TenantNotExistException("Tenant " + + TenantAwareHolder.getInstance().getTenantAware().getCurrentTenant() + + " does not exists, cannot create entity " + this.getClass() + " with id " + id); } setTenant(currentTenant.toUpperCase()); } diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Rollout.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Rollout.java new file mode 100644 index 000000000..3b678ec48 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Rollout.java @@ -0,0 +1,311 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.model; + +import java.util.List; + +import javax.persistence.Column; +import javax.persistence.ConstraintMode; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.FetchType; +import javax.persistence.ForeignKey; +import javax.persistence.Index; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import javax.persistence.Transient; +import javax.persistence.UniqueConstraint; + +import org.eclipse.hawkbit.cache.CacheField; +import org.eclipse.hawkbit.cache.CacheKeys; +import org.eclipse.hawkbit.repository.model.Action.ActionType; + +/** + * @author Michael Hirsch + * + */ +@Entity +@Table(name = "sp_rollout", indexes = { + @Index(name = "sp_idx_rollout_01", columnList = "tenant,name") }, uniqueConstraints = @UniqueConstraint(columnNames = { + "name", "tenant" }, name = "uk_rollout") ) +public class Rollout extends NamedEntity { + + private static final long serialVersionUID = 1L; + + @OneToMany(targetEntity = RolloutGroup.class) + @JoinColumn(name = "rollout", insertable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rollout_rolloutgroup") ) + private List rolloutGroups; + + @Column(name = "target_filter", length = 1024, nullable = false) + private String targetFilterQuery; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "distribution_set", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rolltout_ds") ) + private DistributionSet distributionSet; + + @Column(name = "status") + private RolloutStatus status = RolloutStatus.CREATING; + + @Column(name = "last_check") + private long lastCheck = 0L; + + @Column(name = "action_type", nullable = false) + @Enumerated(EnumType.STRING) + private ActionType actionType = ActionType.FORCED; + + @Column(name = "forced_time") + private long forcedTime; + + @Column(name = "total_targets") + private long totalTargets; + + @Transient + @CacheField(key = CacheKeys.ROLLOUT_GROUP_TOTAL) + private int rolloutGroupsTotal = 0; + + @Transient + @CacheField(key = CacheKeys.ROLLOUT_GROUP_CREATED) + private int rolloutGroupsCreated = 0; + + @Transient + private TotalTargetCountStatus totalTargetCountStatus; + + /** + * @return the distributionSet + */ + public DistributionSet getDistributionSet() { + return distributionSet; + } + + /** + * @param distributionSet + * the distributionSet to set + */ + public void setDistributionSet(final DistributionSet distributionSet) { + this.distributionSet = distributionSet; + } + + /** + * @return the rolloutGroups + */ + public List getRolloutGroups() { + return rolloutGroups; + } + + /** + * @param rolloutGroups + * the rolloutGroups to set + */ + public void setRolloutGroups(final List rolloutGroups) { + this.rolloutGroups = rolloutGroups; + } + + /** + * @return the targetFilterQuery + */ + public String getTargetFilterQuery() { + return targetFilterQuery; + } + + /** + * @param targetFilterQuery + * the targetFilterQuery to set + */ + public void setTargetFilterQuery(final String targetFilterQuery) { + this.targetFilterQuery = targetFilterQuery; + } + + /** + * @return the status + */ + public RolloutStatus getStatus() { + return status; + } + + /** + * @param status + * the status to set + */ + public void setStatus(final RolloutStatus status) { + this.status = status; + } + + /** + * @return the lastCheck + */ + public long getLastCheck() { + return lastCheck; + } + + /** + * @param lastCheck + * the lastCheck to set + */ + public void setLastCheck(final long lastCheck) { + this.lastCheck = lastCheck; + } + + /** + * @return the actionType + */ + public ActionType getActionType() { + return actionType; + } + + /** + * @param actionType + * the actionType to set + */ + public void setActionType(final ActionType actionType) { + this.actionType = actionType; + } + + /** + * @return the forcedTime + */ + public long getForcedTime() { + return forcedTime; + } + + /** + * @param forcedTime + * the forcedTime to set + */ + public void setForcedTime(final long forcedTime) { + this.forcedTime = forcedTime; + } + + /** + * @return the totalTargets + */ + public long getTotalTargets() { + return totalTargets; + } + + /** + * @param totalTargets + * the totalTargets to set + */ + public void setTotalTargets(final long totalTargets) { + this.totalTargets = totalTargets; + } + + /** + * @return the rolloutGroupsTotal + */ + public int getRolloutGroupsTotal() { + return rolloutGroupsTotal; + } + + /** + * @param rolloutGroupsTotal + * the rolloutGroupsTotal to set + */ + public void setRolloutGroupsTotal(final int rolloutGroupsTotal) { + this.rolloutGroupsTotal = rolloutGroupsTotal; + } + + /** + * @return the rolloutGroupsCreated + */ + public int getRolloutGroupsCreated() { + return rolloutGroupsCreated; + } + + /** + * @param rolloutGroupsCreated + * the rolloutGroupsCreated to set + */ + public void setRolloutGroupsCreated(final int rolloutGroupsCreated) { + this.rolloutGroupsCreated = rolloutGroupsCreated; + } + + /** + * @return the totalTargetCountStatus + */ + public TotalTargetCountStatus getTotalTargetCountStatus() { + if (totalTargetCountStatus == null) { + this.totalTargetCountStatus = new TotalTargetCountStatus(totalTargets); + } + return totalTargetCountStatus; + } + + /** + * @param totalTargetCountStatus + * the totalTargetCountStatus to set + */ + public void setTotalTargetCountStatus(final TotalTargetCountStatus totalTargetCountStatus) { + this.totalTargetCountStatus = totalTargetCountStatus; + } + + @Override + public String toString() { + return "Rollout [rolloutGroups=" + rolloutGroups + ", targetFilterQuery=" + targetFilterQuery + + ", distributionSet=" + distributionSet + ", status=" + status + ", lastCheck=" + lastCheck + + ", getName()=" + getName() + ", getId()=" + getId() + "]"; + } + + /** + * + * @author Michael Hirsch + * + */ + public static enum RolloutStatus { + + /** + * Rollouts is beeing created. + */ + CREATING, + + /** + * Rollout is ready to start. + */ + READY, + + /** + * Rollout is paused. + */ + PAUSED, + + /** + * Rollout is starting. + */ + STARTING, + + /** + * Rollout is stopped. + */ + STOPPED, + + /** + * Rollout is running. + */ + RUNNING, + + /** + * Rollout is finished. + */ + FINISHED, + + /** + * Rollout could not created due errors, might be database problem due + * asynchronous creating. + */ + ERROR_CREATING, + + /** + * Rollout could not started due errors, might be database problem due + * asynchronous starting. + */ + ERROR_STARTING; + } +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/RolloutGroup.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/RolloutGroup.java new file mode 100644 index 000000000..ad7efdf1f --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/RolloutGroup.java @@ -0,0 +1,481 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.model; + +import java.util.ArrayList; +import java.util.List; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.ConstraintMode; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.ForeignKey; +import javax.persistence.Index; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import javax.persistence.Transient; +import javax.persistence.UniqueConstraint; + +/** + * JPA entity definition of persisting a group of an rollout. + * + * @author Michael Hirsch + * + */ +@Entity +@Table(name = "sp_rolloutgroup", indexes = { + @Index(name = "sp_idx_rolloutgroup_01", columnList = "tenant,name") }, uniqueConstraints = @UniqueConstraint(columnNames = { + "name", "rollout", "tenant" }, name = "uk_rolloutgroup") ) +public class RolloutGroup extends NamedEntity { + + private static final long serialVersionUID = 1L; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "rollout", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rolloutgroup_rollout") ) + private Rollout rollout; + + @Column(name = "status") + private RolloutGroupStatus status = RolloutGroupStatus.READY; + + @OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST }) + @JoinColumn(name = "rolloutGroup_Id", insertable = false, updatable = false) + private final List rolloutTargetGroup = new ArrayList<>(); + + @ManyToOne(fetch = FetchType.LAZY) + private RolloutGroup parent; + + @Column(name = "success_condition", nullable = false) + private RolloutGroupSuccessCondition successCondition = RolloutGroupSuccessCondition.THRESHOLD; + + @Column(name = "success_condition_exp", length = 512, nullable = false) + private String successConditionExp = null; + + @Column(name = "success_action", nullable = false) + private RolloutGroupSuccessAction successAction = RolloutGroupSuccessAction.NEXTGROUP; + + @Column(name = "success_action_exp", length = 512, nullable = false) + private String successActionExp = null; + + @Column(name = "error_condition") + private RolloutGroupErrorCondition errorCondition = null; + + @Column(name = "error_condition_exp", length = 512) + private String errorConditionExp = null; + + @Column(name = "error_action") + private RolloutGroupErrorAction errorAction = null; + + @Column(name = "error_action_exp", length = 512) + private String errorActionExp = null; + + @Column(name = "total_targets") + private long totalTargets; + + @Transient + private transient TotalTargetCountStatus totalTargetCountStatus; + + public Rollout getRollout() { + return rollout; + } + + public void setRollout(final Rollout rollout) { + this.rollout = rollout; + } + + public RolloutGroupStatus getStatus() { + return status; + } + + public void setStatus(final RolloutGroupStatus status) { + this.status = status; + } + + public List getRolloutTargetGroup() { + return rolloutTargetGroup; + } + + public RolloutGroup getParent() { + return parent; + } + + public void setParent(final RolloutGroup parent) { + this.parent = parent; + } + + public RolloutGroupSuccessCondition getSuccessCondition() { + return successCondition; + } + + public void setSuccessCondition(final RolloutGroupSuccessCondition finishCondition) { + this.successCondition = finishCondition; + } + + public String getSuccessConditionExp() { + return successConditionExp; + } + + public void setSuccessConditionExp(final String finishExp) { + this.successConditionExp = finishExp; + } + + public RolloutGroupErrorCondition getErrorCondition() { + return errorCondition; + } + + public void setErrorCondition(final RolloutGroupErrorCondition errorCondition) { + this.errorCondition = errorCondition; + } + + public String getErrorConditionExp() { + return errorConditionExp; + } + + public void setErrorConditionExp(final String errorExp) { + this.errorConditionExp = errorExp; + } + + public RolloutGroupErrorAction getErrorAction() { + return errorAction; + } + + public void setErrorAction(final RolloutGroupErrorAction errorAction) { + this.errorAction = errorAction; + } + + public String getErrorActionExp() { + return errorActionExp; + } + + public void setErrorActionExp(final String errorActionExp) { + this.errorActionExp = errorActionExp; + } + + public RolloutGroupSuccessAction getSuccessAction() { + return successAction; + } + + public String getSuccessActionExp() { + return successActionExp; + } + + public long getTotalTargets() { + return totalTargets; + } + + public void setTotalTargets(final long totalTargets) { + this.totalTargets = totalTargets; + } + + public void setSuccessAction(final RolloutGroupSuccessAction successAction) { + this.successAction = successAction; + } + + public void setSuccessActionExp(final String successActionExp) { + this.successActionExp = successActionExp; + } + + /** + * @return the totalTargetCountStatus + */ + public TotalTargetCountStatus getTotalTargetCountStatus() { + if (totalTargetCountStatus == null) { + this.totalTargetCountStatus = new TotalTargetCountStatus(totalTargets); + } + return totalTargetCountStatus; + } + + /** + * @param totalTargetCountStatus + * the totalTargetCountStatus to set + */ + public void setTotalTargetCountStatus(final TotalTargetCountStatus totalTargetCountStatus) { + this.totalTargetCountStatus = totalTargetCountStatus; + } + + @Override + public String toString() { + return "RolloutGroup [rollout=" + rollout + ", status=" + status + ", rolloutTargetGroup=" + rolloutTargetGroup + + ", parent=" + parent + ", finishCondition=" + successCondition + ", finishExp=" + successConditionExp + + ", errorCondition=" + errorCondition + ", errorExp=" + errorConditionExp + ", getName()=" + getName() + + ", getId()=" + getId() + "]"; + } + + /** + * + * @author Michael Hirsch + * + */ + public enum RolloutGroupStatus { + + /** + * Ready to start the group. + */ + READY, + + /** + * Group is scheduled and started sometime, e.g. trigger of group + * before. + */ + SCHEDULED, + + /** + * Group is finished. + */ + FINISHED, + + /** + * Group is finished and has errors. + */ + ERROR, + + /** + * Group is running. + */ + RUNNING; + } + + /** + * The condition to evaluate if an group is success state. + */ + public enum RolloutGroupSuccessCondition { + THRESHOLD("thresholdRolloutGroupSuccessCondition"); + + private final String beanName; + + private RolloutGroupSuccessCondition(final String beanName) { + this.beanName = beanName; + } + + /** + * @return the beanName + */ + public String getBeanName() { + return beanName; + } + } + + /** + * The condition to evaluate if an group is in error state. + */ + public enum RolloutGroupErrorCondition { + THRESHOLD("thresholdRolloutGroupErrorCondition"); + + private final String beanName; + + private RolloutGroupErrorCondition(final String beanName) { + this.beanName = beanName; + } + + /** + * @return the beanName + */ + public String getBeanName() { + return beanName; + } + } + + /** + * The actions executed when the {@link RolloutGroup#errorCondition} is hit. + */ + public enum RolloutGroupErrorAction { + PAUSE("pauseRolloutGroupAction"); + + private final String beanName; + + private RolloutGroupErrorAction(final String beanName) { + this.beanName = beanName; + } + + /** + * @return the beanName + */ + public String getBeanName() { + return beanName; + } + } + + /** + * The actions executed when the {@link RolloutGroup#successCondition} is + * hit. + */ + public enum RolloutGroupSuccessAction { + NEXTGROUP("startNextRolloutGroupAction"); + + private final String beanName; + + private RolloutGroupSuccessAction(final String beanName) { + this.beanName = beanName; + } + + /** + * @return the beanName + */ + public String getBeanName() { + return beanName; + } + } + + /** + * Object which holds all {@link RolloutGroup} conditions together which can + * easily built. + */ + public static class RolloutGroupConditions { + private RolloutGroupSuccessCondition successCondition = null; + private String successConditionExp = null; + private RolloutGroupSuccessAction successAction = null; + private String successActionExp = null; + private RolloutGroupErrorCondition errorCondition = null; + private String errorConditionExp = null; + private RolloutGroupErrorAction errorAction = null; + private String errorActionExp = null; + + public RolloutGroupSuccessCondition getSuccessCondition() { + return successCondition; + } + + public void setSuccessCondition(final RolloutGroupSuccessCondition finishCondition) { + this.successCondition = finishCondition; + } + + public String getSuccessConditionExp() { + return successConditionExp; + } + + public void setSuccessConditionExp(final String finishConditionExp) { + this.successConditionExp = finishConditionExp; + } + + public RolloutGroupSuccessAction getSuccessAction() { + return successAction; + } + + public void setSuccessAction(final RolloutGroupSuccessAction successAction) { + this.successAction = successAction; + } + + public String getSuccessActionExp() { + return successActionExp; + } + + public void setSuccessActionExp(final String successActionExp) { + this.successActionExp = successActionExp; + } + + public RolloutGroupErrorCondition getErrorCondition() { + return errorCondition; + } + + public void setErrorCondition(final RolloutGroupErrorCondition errorCondition) { + this.errorCondition = errorCondition; + } + + public String getErrorConditionExp() { + return errorConditionExp; + } + + public void setErrorConditionExp(final String errorConditionExp) { + this.errorConditionExp = errorConditionExp; + } + + public RolloutGroupErrorAction getErrorAction() { + return errorAction; + } + + public void setErrorAction(final RolloutGroupErrorAction errorAction) { + this.errorAction = errorAction; + } + + public String getErrorActionExp() { + return errorActionExp; + } + + public void setErrorActionExp(final String errorActionExp) { + this.errorActionExp = errorActionExp; + } + } + + /** + * Builder to build easily the {@link RolloutGroupConditions}. + * + */ + public static class RolloutGroupConditionBuilder { + private final RolloutGroupConditions conditions = new RolloutGroupConditions(); + + public RolloutGroupConditions build() { + return conditions; + } + + /** + * Sets the finish condition and expression on the builder. + * + * @param condition + * the finish condition + * @param expression + * the finish expression + * @return the builder itself + */ + public RolloutGroupConditionBuilder successCondition(final RolloutGroupSuccessCondition condition, + final String expression) { + conditions.setSuccessCondition(condition); + conditions.setSuccessConditionExp(expression); + return this; + } + + /** + * Sets the success action and expression on the builder. + * + * @param action + * the success action + * @param expression + * the error expression + * @return the builder itself + */ + public RolloutGroupConditionBuilder successAction(final RolloutGroupSuccessAction action, + final String expression) { + conditions.setSuccessAction(action); + conditions.setSuccessActionExp(expression); + return this; + } + + /** + * Sets the error condition and expression on the builder. + * + * @param condition + * the error condition + * @param expression + * the error expression + * @return the builder itself + */ + public RolloutGroupConditionBuilder errorCondition(final RolloutGroupErrorCondition condition, + final String expression) { + conditions.setErrorCondition(condition); + conditions.setErrorConditionExp(expression); + return this; + } + + /** + * Sets the error action and expression on the builder. + * + * @param action + * the error action + * @param expression + * the error expression + * @return the builder itself + */ + public RolloutGroupConditionBuilder errorAction(final RolloutGroupErrorAction action, final String expression) { + conditions.setErrorAction(action); + conditions.setErrorActionExp(expression); + return this; + } + } + +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/RolloutTargetGroup.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/RolloutTargetGroup.java new file mode 100644 index 000000000..a185f481f --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/RolloutTargetGroup.java @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.model; + +import java.util.List; + +import javax.persistence.CascadeType; +import javax.persistence.ConstraintMode; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.ForeignKey; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.JoinColumn; +import javax.persistence.JoinColumns; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +/** + * @author Michael Hirsch + * + */ +@IdClass(RolloutTargetGroupId.class) +@Entity +@Table(name = "sp_rollouttargetgroup") +public class RolloutTargetGroup { + + @Id + @ManyToOne(targetEntity = RolloutGroup.class, fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST }) + @JoinColumn(name = "rolloutGroup_Id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rollouttargetgroup_group")) + private RolloutGroup rolloutGroup; + + @Id + @ManyToOne(targetEntity = Target.class, fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST }) + @JoinColumn(name = "target_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rollouttargetgroup_target")) + private Target target; + + @OneToMany(targetEntity = Action.class, fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST }) + @JoinColumns(value = { @JoinColumn(name = "rolloutgroup", referencedColumnName = "rolloutGroup_Id"), + @JoinColumn(name = "target", referencedColumnName = "target_id") }) + private List actions; + + /** + * default constructor for JPA. + */ + public RolloutTargetGroup() { + // JPA constructor + } + + public RolloutTargetGroup(final RolloutGroup rolloutGroup, final Target target) { + this.rolloutGroup = rolloutGroup; + this.target = target; + } + + public RolloutTargetGroupId getId() { + return new RolloutTargetGroupId(rolloutGroup, target); + } +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/RolloutTargetGroupId.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/RolloutTargetGroupId.java new file mode 100644 index 000000000..41850424b --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/RolloutTargetGroupId.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.model; + +import java.io.Serializable; + +/** + * Combined unique key of the table {@link RolloutTargetGroup}. + * + * @author Michael Hirsch + * + */ +public class RolloutTargetGroupId implements Serializable { + /** + * + */ + private static final long serialVersionUID = 1L; + + private Long rolloutGroup; + private Long target; + + /** + * default constructor necessary for JPA. + */ + public RolloutTargetGroupId() { + // default constructor necessary for JPA, empty. + } + + /** + * Constructor. + * + * @param rolloutGroup + * the rollout group for this key + * @param target + * the target for this key + */ + public RolloutTargetGroupId(final RolloutGroup rolloutGroup, final Target target) { + this.rolloutGroup = rolloutGroup.getId(); + this.target = target.getId(); + } + + public Long getRolloutGroup() { + return rolloutGroup; + } + + public Long getTarget() { + return target; + } +} \ No newline at end of file diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/SoftwareModuleMetadata.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/SoftwareModuleMetadata.java index d4ddba3f3..dfc1ecfa7 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/SoftwareModuleMetadata.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/SoftwareModuleMetadata.java @@ -47,7 +47,7 @@ public class SoftwareModuleMetadata implements Serializable { @Id @ManyToOne(targetEntity = SoftwareModule.class, fetch = FetchType.LAZY) - @JoinColumn(name = "sw_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_metadata_sw") ) + @JoinColumn(name = "sw_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_metadata_sw")) private SoftwareModule softwareModule; public SoftwareModuleMetadata() { diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Target.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Target.java index 6f37b2d8b..6de84ccaa 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Target.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Target.java @@ -106,6 +106,11 @@ public class Target extends NamedEntity implements Persistable { @Column(name = "sec_token", insertable = true, updatable = true, nullable = false, length = 128) private String securityToken = null; + @CascadeOnDelete + @OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.REMOVE, CascadeType.PERSIST }) + @JoinColumn(name = "target_Id", insertable = false, updatable = false) + private final List rolloutTargetGroup = new ArrayList<>(); + /** * Constructor. * diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TargetWithActionStatus.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TargetWithActionStatus.java new file mode 100644 index 000000000..a75141658 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TargetWithActionStatus.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.model; + +import org.eclipse.hawkbit.repository.model.Action.Status; + +/** + * + * Rollout - Target with action status. + * + */ +public class TargetWithActionStatus { + + private Target target; + + private Status status = null;; + + public TargetWithActionStatus(final Target target) { + this.target = target; + } + + public TargetWithActionStatus(final Target target, final Status status) { + this.status = status; + this.target = target; + } + + /** + * @return the target + */ + public Target getTarget() { + return target; + } + + /** + * @return the status + */ + public Status getStatus() { + return status; + } + + /** + * @param target + * the target to set + */ + public void setTarget(final Target target) { + this.target = target; + } + + /** + * @param status + * the status to set + */ + public void setStatus(final Status status) { + this.status = status; + } + +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TotalTargetCountActionStatus.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TotalTargetCountActionStatus.java new file mode 100644 index 000000000..9fabef663 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TotalTargetCountActionStatus.java @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.model; + +/** + * Represents rollout or rollout group statuses and count of targets in each + * status. + * + */ +public class TotalTargetCountActionStatus { + + private final Action.Status status; + private final Long count; + private Long id; + + public TotalTargetCountActionStatus(final Long id, final Action.Status status, final Long count) { + this.status = status; + this.count = count; + this.id = id; + } + + public TotalTargetCountActionStatus(final Action.Status status, final Long count) { + this.status = status; + this.count = count; + } + + /** + * @return the id + */ + public Long getId() { + return id; + } + + /** + * @return the status + */ + public Action.Status getStatus() { + return status; + } + + /** + * @return the count + */ + public Long getCount() { + return count; + } +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TotalTargetCountStatus.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TotalTargetCountStatus.java new file mode 100644 index 000000000..f403ecc3d --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TotalTargetCountStatus.java @@ -0,0 +1,125 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.model; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * + * Store all states with the target count of a rollout or rolloutgroup. + * + */ +public class TotalTargetCountStatus { + + /** + * Status of the total target counts. + */ + public enum Status { + SCHEDULED, RUNNING, ERROR, FINISHED, CANCELLED, NOTSTARTED + } + + private final Map statusTotalCountMap = new HashMap<>(); + private final Long totalTargetCount; + + /** + * Create a new states map with the target count for each state. + * + * @param targetCountActionStatus + * the action state map + * @param totalTargets + * the total target count + */ + public TotalTargetCountStatus(final List targetCountActionStatus, + final Long totalTargetCount) { + this.totalTargetCount = totalTargetCount; + mapActionStatusToTotalTargetCountStatus(targetCountActionStatus); + } + + /** + * Create a new states map with the target count for each state. + * + * @param totalTargetCount + * the total target count + */ + public TotalTargetCountStatus(final Long totalTargetCount) { + this(Collections.emptyList(), totalTargetCount); + } + + /** + * The current state mape which the total target count + * + * @return the statusTotalCountMap the state map + */ + public Map getStatusTotalCountMap() { + return statusTotalCountMap; + } + + /** + * Gets the total target count from a state. + * + * @param status + * the state key + * @return the current target count cannot be + */ + public Long getTotalTargetCountByStatus(final Status status) { + final Long count = statusTotalCountMap.get(status); + return count == null ? 0L : count; + } + + /** + * Populate all target status to a the given map + * + * @param statusTotalCountMap + * the map + * @param rolloutStatusCountItems + * all target statut with total count + * @return some state is populated nothing is happend + */ + private final void mapActionStatusToTotalTargetCountStatus( + final List targetCountActionStatus) { + if (targetCountActionStatus == null) { + statusTotalCountMap.put(TotalTargetCountStatus.Status.NOTSTARTED, totalTargetCount); + return; + } + statusTotalCountMap.put(Status.RUNNING, 0L); + Long notStartedTargetCount = totalTargetCount; + for (final TotalTargetCountActionStatus item : targetCountActionStatus) { + switch (item.getStatus()) { + case SCHEDULED: + statusTotalCountMap.put(Status.SCHEDULED, item.getCount()); + break; + case ERROR: + statusTotalCountMap.put(Status.ERROR, item.getCount()); + break; + case FINISHED: + statusTotalCountMap.put(Status.FINISHED, item.getCount()); + break; + case RETRIEVED: + case RUNNING: + case WARNING: + case DOWNLOAD: + case CANCELING: + final Long runningItemsCount = statusTotalCountMap.get(Status.RUNNING) + item.getCount(); + statusTotalCountMap.put(Status.RUNNING, runningItemsCount); + break; + case CANCELED: + statusTotalCountMap.put(Status.CANCELLED, item.getCount()); + break; + default: + throw new IllegalArgumentException("State " + item.getStatus() + "is not valid"); + } + notStartedTargetCount -= item.getCount(); + } + statusTotalCountMap.put(TotalTargetCountStatus.Status.NOTSTARTED, notStartedTargetCount); + } + +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/helper/AfterTransactionCommitExecutorHolder.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/helper/AfterTransactionCommitExecutorHolder.java new file mode 100644 index 000000000..1282bc99d --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/helper/AfterTransactionCommitExecutorHolder.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.model.helper; + +import org.eclipse.hawkbit.eventbus.EntityPropertyChangeListener; +import org.eclipse.hawkbit.executor.AfterTransactionCommitExecutor; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * A singleton bean which holds the {@link AfterTransactionCommitExecutor} to + * have to the cache manager in beans not instantiated by spring e.g. JPA + * entities or {@link EntityPropertyChangeListener} which cannot be autowired. + * + */ +public final class AfterTransactionCommitExecutorHolder { + + private static final AfterTransactionCommitExecutorHolder SINGLETON = new AfterTransactionCommitExecutorHolder(); + + @Autowired + private AfterTransactionCommitExecutor afterCommit; + + private AfterTransactionCommitExecutorHolder() { + + } + + /** + * @return the cache manager holder singleton instance + */ + public static AfterTransactionCommitExecutorHolder getInstance() { + return SINGLETON; + } + + /** + * @return the afterCommit + */ + public AfterTransactionCommitExecutor getAfterCommit() { + return afterCommit; + } + + /** + * @param afterCommit + * the afterCommit to set + */ + public void setAfterCommit(final AfterTransactionCommitExecutor afterCommit) { + this.afterCommit = afterCommit; + } + +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/helper/EventBusHolder.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/helper/EventBusHolder.java new file mode 100644 index 000000000..8111a1c28 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/helper/EventBusHolder.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.model.helper; + +import org.eclipse.hawkbit.eventbus.CacheFieldEntityListener; +import org.springframework.beans.factory.annotation.Autowired; + +import com.google.common.eventbus.EventBus; + +/** + * A singleton bean which holds the {@link EventBus} to have to the cache + * manager in beans not instantiated by spring e.g. JPA entities or + * {@link CacheFieldEntityListener} which cannot be autowired. + * + */ +public final class EventBusHolder { + + private static final EventBusHolder SINGLETON = new EventBusHolder(); + + @Autowired + private EventBus eventBus; + + private EventBusHolder() { + + } + + /** + * @return the cache manager holder singleton instance + */ + public static EventBusHolder getInstance() { + return SINGLETON; + } + + /** + * @return the eventBus + */ + public EventBus getEventBus() { + return eventBus; + } + + /** + * @param eventBus + * the eventBus to set + */ + public void setEventBus(final EventBus eventBus) { + this.eventBus = eventBus; + } + +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/rsql/RSQLUtility.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/rsql/RSQLUtility.java index 5dd4cfd1d..69ee58429 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/rsql/RSQLUtility.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/rsql/RSQLUtility.java @@ -104,6 +104,30 @@ public final class RSQLUtility { return new RSQLSpecification<>(rsql, fieldNameProvider); } + /** + * Validate the given rsql string regarding existence and correct syntax. + * + * @param rsql + * the rsql string to get validated + * + */ + public static void isValid(final String rsql) { + parseRsql(rsql); + } + + private static Node parseRsql(final String rsql) { + try { + LOGGER.debug("parsing rsql string {}", rsql); + final Set operators = RSQLOperators.defaultOperators(); + operators.add(new ComparisonOperator("=li=", false)); + return new RSQLParser(operators).parse(rsql); + } catch (final IllegalArgumentException e) { + throw new RSQLParameterSyntaxException("rsql filter must not be null", e); + } catch (final RSQLParserException e) { + throw new RSQLParameterSyntaxException(e); + } + } + private static final class RSQLSpecification & FieldNameProvider, T> implements Specification { private final String rsql; @@ -125,15 +149,7 @@ public final class RSQLUtility { @Override public Predicate toPredicate(final Root root, final CriteriaQuery query, final CriteriaBuilder cb) { - final Node rootNode; - try { - LOGGER.debug("parsing rsql string {}", rsql); - final Set operators = RSQLOperators.defaultOperators(); - operators.add(new ComparisonOperator("=li=", false)); - rootNode = new RSQLParser(operators).parse(rsql); - } catch (final RSQLParserException e) { - throw new RSQLParameterSyntaxException(e); - } + final Node rootNode = parseRsql(rsql); final JpqQueryRSQLVisitor jpqQueryRSQLVisitor = new JpqQueryRSQLVisitor<>(root, cb, enumType); final List accept = rootNode., String> accept(jpqQueryRSQLVisitor); diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/PauseRolloutGroupAction.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/PauseRolloutGroupAction.java new file mode 100644 index 000000000..af335cd0d --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/PauseRolloutGroupAction.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rollout.condition; + +import java.util.concurrent.Callable; + +import org.eclipse.hawkbit.repository.RolloutGroupRepository; +import org.eclipse.hawkbit.repository.RolloutManagement; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus; +import org.eclipse.hawkbit.security.SystemSecurityContext; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Error action evaluator which pauses the whole {@link Rollout} and sets the + * current {@link RolloutGroup} to error. + */ +@Component("pauseRolloutGroupAction") +public class PauseRolloutGroupAction implements RolloutGroupActionEvaluator { + + @Autowired + private RolloutManagement rolloutManagement; + + @Autowired + private RolloutGroupRepository rolloutGroupRepository; + + @Autowired + private SystemSecurityContext systemSecurityContext; + + @Override + public boolean verifyExpression(final String expression) { + return true; + } + + @Override + public void eval(final Rollout rollout, final RolloutGroup rolloutGroup, final String expression) { + systemSecurityContext.runAsSystem(new Callable() { + @Override + public Void call() throws Exception { + rolloutGroup.setStatus(RolloutGroupStatus.ERROR); + rolloutGroupRepository.save(rolloutGroup); + rolloutManagement.pauseRollout(rollout); + return null; + } + }); + } +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/RolloutGroupActionEvaluator.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/RolloutGroupActionEvaluator.java new file mode 100644 index 000000000..1e2c98707 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/RolloutGroupActionEvaluator.java @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rollout.condition; + +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.RolloutGroup; + +/** + * + */ +public interface RolloutGroupActionEvaluator { + + boolean verifyExpression(final String expression); + + void eval(Rollout rollout, RolloutGroup rolloutGroup, final String expression); +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/RolloutGroupConditionEvaluator.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/RolloutGroupConditionEvaluator.java new file mode 100644 index 000000000..73580fd19 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/RolloutGroupConditionEvaluator.java @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rollout.condition; + +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.RolloutGroup; + +/** + * + */ +public interface RolloutGroupConditionEvaluator { + + boolean verifyExpression(final String expression); + + boolean eval(Rollout rollout, RolloutGroup rolloutGroup, final String expression); +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/StartNextGroupRolloutGroupSuccessAction.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/StartNextGroupRolloutGroupSuccessAction.java new file mode 100644 index 000000000..21c499d31 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/StartNextGroupRolloutGroupSuccessAction.java @@ -0,0 +1,90 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rollout.condition; + +import java.util.List; + +import org.eclipse.hawkbit.repository.DeploymentManagement; +import org.eclipse.hawkbit.repository.RolloutGroupRepository; +import org.eclipse.hawkbit.repository.model.Action; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus; +import org.eclipse.hawkbit.security.SystemSecurityContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Success action which starts the next following {@link RolloutGroup}. + */ +@Component("startNextRolloutGroupAction") +public class StartNextGroupRolloutGroupSuccessAction implements RolloutGroupActionEvaluator { + + private static final Logger logger = LoggerFactory.getLogger(StartNextGroupRolloutGroupSuccessAction.class); + + @Autowired + private RolloutGroupRepository rolloutGroupRepository; + + @Autowired + private DeploymentManagement deploymentManagement; + + @Autowired + private SystemSecurityContext systemSecurityContext; + + @Override + public boolean verifyExpression(final String expression) { + return true; + } + + @Override + public void eval(final Rollout rollout, final RolloutGroup rolloutGroup, final String expression) { + systemSecurityContext.runAsSystem(() -> { + startNextGroup(rollout, rolloutGroup); + return null; + }); + } + + private void startNextGroup(final Rollout rollout, final RolloutGroup rolloutGroup) { + // retrieve all actions accroding to the parent group of the finished + // rolloutGroup, so retrieve all child-group actions which need to be + // started. + final List rolloutGroupActions = deploymentManagement.findActionsByRolloutGroupParentAndStatus(rollout, + rolloutGroup, Action.Status.SCHEDULED); + logger.debug("{} Next actions to start for rollout {} and parent group {}", rolloutGroupActions.size(), rollout, + rolloutGroup); + rolloutGroupActions.forEach(action -> deploymentManagement.startScheduledAction(action)); + if (!rolloutGroupActions.isEmpty()) { + // get all next scheduled groups based on the found actions and set + // them in state running + rolloutGroupActions.forEach(action -> { + final RolloutGroup nextGroup = action.getRolloutGroup(); + logger.debug("Rolloutgroup {} is now running", nextGroup); + nextGroup.setStatus(RolloutGroupStatus.RUNNING); + rolloutGroupRepository.save(nextGroup); + }); + } else { + logger.info("No actions to start for next rolloutgroup of parent {}", rolloutGroup); + // nothing for next group, just finish the group, this can happen + // e.g. if targets has been deleted after the group has been + // scheduled. If the group is empty now, we just finish the group if + // there are not actions available for this group. + final List findByRolloutGroupParent = rolloutGroupRepository + .findByParentAndStatus(rolloutGroup, RolloutGroupStatus.SCHEDULED); + findByRolloutGroupParent.forEach(nextGroup -> { + logger.debug("Rolloutgroup {} is finished, starting next group", nextGroup); + nextGroup.setStatus(RolloutGroupStatus.FINISHED); + rolloutGroupRepository.save(nextGroup); + // find the next group to set in running state + startNextGroup(rollout, nextGroup); + }); + } + } +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/ThresholdRolloutGroupErrorCondition.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/ThresholdRolloutGroupErrorCondition.java new file mode 100644 index 000000000..2c2bc361f --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/ThresholdRolloutGroupErrorCondition.java @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rollout.condition; + +import org.eclipse.hawkbit.repository.ActionRepository; +import org.eclipse.hawkbit.repository.model.Action; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * + */ +@Component("thresholdRolloutGroupErrorCondition") +public class ThresholdRolloutGroupErrorCondition implements RolloutGroupConditionEvaluator { + + private static Logger logger = LoggerFactory.getLogger(ThresholdRolloutGroupErrorCondition.class); + + @Autowired + private ActionRepository actionRepository; + + @Override + public boolean verifyExpression(final String expression) { + // percentage value between 0 and 100 + try { + final Integer value = Integer.valueOf(expression); + if (value >= 0 || value <= 100) { + return true; + } + return true; + } catch (final RuntimeException e) { + + } + return false; + } + + @Override + public boolean eval(final Rollout rollout, final RolloutGroup rolloutGroup, final String expression) { + final Long totalGroup = actionRepository.countByRolloutAndRolloutGroup(rollout, rolloutGroup); + final Long error = actionRepository.countByRolloutIdAndRolloutGroupIdAndStatus(rollout.getId(), + rolloutGroup.getId(), Action.Status.ERROR); + try { + final Integer threshold = Integer.valueOf(expression); + + if (totalGroup == 0) { + // in case e.g. targets has been deleted we don't have any + // actions left for this group, so the group is finished + return false; + } + + // calculate threshold + return ((float) error / (float) totalGroup) > ((float) threshold / 100F); + } catch (final NumberFormatException e) { + logger.error("Cannot evaluate condition expression " + expression, e); + return false; + } + } + +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/ThresholdRolloutGroupSuccessCondition.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/ThresholdRolloutGroupSuccessCondition.java new file mode 100644 index 000000000..81b5c7286 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/ThresholdRolloutGroupSuccessCondition.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rollout.condition; + +import org.eclipse.hawkbit.repository.ActionRepository; +import org.eclipse.hawkbit.repository.model.Action; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * + */ +@Component("thresholdRolloutGroupSuccessCondition") +public class ThresholdRolloutGroupSuccessCondition implements RolloutGroupConditionEvaluator { + + @Autowired + private ActionRepository actionRepository; + + @Override + public boolean verifyExpression(final String expression) { + // percentage value between 0 and 100 + try { + final Integer value = Integer.valueOf(expression); + if (value >= 0 || value <= 100) { + return true; + } + return true; + } catch (final RuntimeException e) { + + } + return false; + } + + @Override + public boolean eval(final Rollout rollout, final RolloutGroup rolloutGroup, final String expression) { + final Long totalGroup = rolloutGroup.getTotalTargets(); + final Long finished = actionRepository.countByRolloutIdAndRolloutGroupIdAndStatus(rollout.getId(), + rolloutGroup.getId(), Action.Status.FINISHED); + final Integer threshold = Integer.valueOf(expression); + + if (totalGroup == 0) { + // in case e.g. targets has been deleted we don't have any actions + // left for this group, so the group is finished + return true; + } + // calculate threshold + return ((float) finished / (float) totalGroup) >= ((float) threshold / 100F); + } + +} diff --git a/hawkbit-repository/src/main/resources/db/migration/H2/V1_6_0__rollout_management___H2.sql b/hawkbit-repository/src/main/resources/db/migration/H2/V1_6_0__rollout_management___H2.sql new file mode 100644 index 000000000..490400161 --- /dev/null +++ b/hawkbit-repository/src/main/resources/db/migration/H2/V1_6_0__rollout_management___H2.sql @@ -0,0 +1,103 @@ + create table sp_rolloutgroup ( + id bigint generated by default as identity, + created_at bigint, + created_by varchar(40), + last_modified_at bigint, + last_modified_by varchar(40), + optlock_revision bigint, + tenant varchar(40) not null, + description varchar(512), + name varchar(64) not null, + error_condition integer, + error_condition_exp varchar(512), + error_action integer, + error_action_exp varchar(512), + success_condition integer not null, + success_condition_exp varchar(512) not null, + success_action integer not null, + success_action_exp varchar(512), + status integer, + parent_id bigint, + rollout bigint, + total_targets bigint, + primary key (id) + ); + + create table sp_rollout ( + id bigint generated by default as identity, + created_at bigint, + created_by varchar(40), + last_modified_at bigint, + last_modified_by varchar(40), + optlock_revision bigint, + tenant varchar(40) not null, + description varchar(512), + name varchar(64) not null, + last_check bigint, + group_theshold float, + status integer, + distribution_set bigint, + target_filter varchar(1024), + action_type varchar(255) not null, + forced_time bigint, + total_targets bigint, + primary key (id) + ); + + create table sp_rollouttargetgroup ( + target_Id bigint not null, + rolloutGroup_Id bigint not null, + primary key (rolloutGroup_Id, target_Id) + ); + + create index sp_idx_rollout_01 on sp_rollout (tenant, name); + + create index sp_idx_rolloutgroup_01 on sp_rolloutgroup (tenant, name); + + ALTER TABLE sp_action ADD COLUMN rollout bigint; + ALTER TABLE sp_action ADD COLUMN rolloutgroup bigint; + + alter table sp_rollout + add constraint uk_rollout unique (name, tenant); + + alter table sp_rolloutgroup + add constraint uk_rolloutgroup unique (name, rollout, tenant); + + alter table sp_action + add constraint fk_action_rollout + foreign key (rollout) + references sp_rollout; + + alter table sp_action + add constraint fk_action_rolloutgroup + foreign key (rolloutgroup) + references sp_rolloutgroup; + + alter table sp_rollout + add constraint fk_rollout_ds + foreign key (distribution_set) + references sp_distribution_set; + + alter table sp_rolloutgroup + add constraint fk_rolloutgroup_rollout + foreign key (rollout) + references sp_rollout + on delete cascade; + + alter table sp_rolloutgroup + add constraint fk_rolloutgroup_rolloutgroup + foreign key (parent_id) + references sp_rolloutgroup + on delete cascade; + + alter table sp_rollouttargetgroup + add constraint fk_rollouttargetgroup_target + foreign key (target_id) + references sp_target + on delete cascade; + + alter table sp_rollouttargetgroup + add constraint fk_rollouttargetgroup_rolloutgroup + foreign key (rolloutgroup_id) + references sp_rolloutgroup + on delete cascade; \ No newline at end of file diff --git a/hawkbit-repository/src/main/resources/db/migration/MYSQL/V1_6_0__rollout_management___MYSQL.sql b/hawkbit-repository/src/main/resources/db/migration/MYSQL/V1_6_0__rollout_management___MYSQL.sql new file mode 100644 index 000000000..4cd4c7ea4 --- /dev/null +++ b/hawkbit-repository/src/main/resources/db/migration/MYSQL/V1_6_0__rollout_management___MYSQL.sql @@ -0,0 +1,103 @@ + create table sp_rolloutgroup ( + id bigint not null auto_increment, + created_at bigint, + created_by varchar(40), + last_modified_at bigint, + last_modified_by varchar(40), + optlock_revision bigint, + tenant varchar(40) not null, + description varchar(512), + name varchar(64) not null, + error_condition integer, + error_condition_exp varchar(512), + error_action integer, + error_action_exp varchar(512), + success_condition integer not null, + success_condition_exp varchar(512) not null, + success_action integer not null, + success_action_exp varchar(512), + status integer, + parent_id bigint, + rollout bigint, + total_targets bigint, + primary key (id) + ); + + create table sp_rollout ( + id bigint not null auto_increment, + created_at bigint, + created_by varchar(40), + last_modified_at bigint, + last_modified_by varchar(40), + optlock_revision bigint, + tenant varchar(40) not null, + description varchar(512), + name varchar(64) not null, + last_check bigint, + group_theshold float, + status integer, + distribution_set bigint, + target_filter varchar(1024), + action_type varchar(255) not null, + forced_time bigint, + total_targets bigint, + primary key (id) + ); + + create table sp_rollouttargetgroup ( + target_Id bigint not null, + rolloutGroup_Id bigint not null, + primary key (rolloutGroup_Id, target_Id) + ); + + create index sp_idx_rollout_01 on sp_rollout (tenant, name); + + create index sp_idx_rolloutgroup_01 on sp_rolloutgroup (tenant, name); + + ALTER TABLE sp_action ADD COLUMN rollout bigint; + ALTER TABLE sp_action ADD COLUMN rolloutgroup bigint; + + alter table sp_rollout + add constraint uk_rollout unique (name, tenant); + + alter table sp_rolloutgroup + add constraint uk_rolloutgroup unique (name, rollout, tenant); + + alter table sp_action + add constraint fk_action_rollout + foreign key (rollout) + references sp_rollout (id); + + alter table sp_action + add constraint fk_action_rolloutgroup + foreign key (rolloutgroup) + references sp_rolloutgroup (id); + + alter table sp_rollout + add constraint fk_rollout_ds + foreign key (distribution_set) + references sp_distribution_set (id); + + alter table sp_rolloutgroup + add constraint fk_rolloutgroup_rollout + foreign key (rollout) + references sp_rollout (id) + on delete cascade; + + alter table sp_rolloutgroup + add constraint fk_rolloutgroup_rolloutgroup + foreign key (parent_id) + references sp_rolloutgroup (id) + on delete cascade; + + alter table sp_rollouttargetgroup + add constraint fk_rollouttargetgroup_target + foreign key (target_id) + references sp_target (id) + on delete cascade; + + alter table sp_rollouttargetgroup + add constraint fk_rollouttargetgroup_rolloutgroup + foreign key (rolloutgroup_id) + references sp_rolloutgroup (id) + on delete cascade; \ No newline at end of file diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/AbstractIntegrationTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/AbstractIntegrationTest.java index 94fa8beca..9d5fa2483 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/AbstractIntegrationTest.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/AbstractIntegrationTest.java @@ -28,6 +28,9 @@ import org.eclipse.hawkbit.repository.DistributionSetTagRepository; import org.eclipse.hawkbit.repository.DistributionSetTypeRepository; import org.eclipse.hawkbit.repository.ExternalArtifactRepository; import org.eclipse.hawkbit.repository.LocalArtifactRepository; +import org.eclipse.hawkbit.repository.RolloutGroupManagement; +import org.eclipse.hawkbit.repository.RolloutGroupRepository; +import org.eclipse.hawkbit.repository.RolloutManagement; import org.eclipse.hawkbit.repository.SoftwareManagement; import org.eclipse.hawkbit.repository.SoftwareModuleMetadataRepository; import org.eclipse.hawkbit.repository.SoftwareModuleRepository; @@ -178,6 +181,15 @@ public abstract class AbstractIntegrationTest implements EnvironmentAware { @Autowired protected TenantAwareCacheManager cacheManager; + @Autowired + protected RolloutManagement rolloutManagement; + + @Autowired + protected RolloutGroupManagement rolloutGroupManagement; + + @Autowired + protected RolloutGroupRepository rolloutGroupRepository; + protected MockMvc mvc; @Autowired diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/TestConfiguration.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/TestConfiguration.java index 7cdca8efd..945e71c75 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/TestConfiguration.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/TestConfiguration.java @@ -9,10 +9,12 @@ package org.eclipse.hawkbit; import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import org.eclipse.hawkbit.cache.CacheConstants; import org.eclipse.hawkbit.cache.TenancyCacheManager; import org.eclipse.hawkbit.cache.TenantAwareCacheManager; +import org.eclipse.hawkbit.repository.model.helper.EventBusHolder; import org.eclipse.hawkbit.repository.utils.RepositoryDataGenerator; import org.eclipse.hawkbit.repository.utils.RepositoryDataGenerator.DatabaseCleanupUtil; import org.eclipse.hawkbit.security.SecurityContextTenantAware; @@ -30,7 +32,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; import org.springframework.data.domain.AuditorAware; import org.springframework.scheduling.annotation.AsyncConfigurer; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.security.concurrent.DelegatingSecurityContextExecutor; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import com.google.common.eventbus.AsyncEventBus; @@ -91,9 +93,14 @@ public class TestConfiguration implements AsyncConfigurer { return new AsyncEventBus(asyncExecutor()); } + @Bean + public EventBusHolder eventBusHolder() { + return EventBusHolder.getInstance(); + } + @Bean public Executor asyncExecutor() { - return new ThreadPoolTaskExecutor(); + return new DelegatingSecurityContextExecutor(Executors.newSingleThreadExecutor()); } @Bean diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/cache/CacheKeysTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/cache/CacheKeysTest.java index b086875c3..6c9314d07 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/cache/CacheKeysTest.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/cache/CacheKeysTest.java @@ -21,5 +21,4 @@ public class CacheKeysTest { final String entitySpecificCacheKey = CacheKeys.entitySpecificCacheKey(knownEntityId, knownCacheKey); assertThat(entitySpecificCacheKey).isEqualTo(knownEntityId + "." + knownCacheKey); } - } diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/RolloutManagementTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/RolloutManagementTest.java new file mode 100644 index 000000000..a405e7b7d --- /dev/null +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/RolloutManagementTest.java @@ -0,0 +1,1024 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository; + +import static org.fest.assertions.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.hawkbit.AbstractIntegrationTest; +import org.eclipse.hawkbit.TestDataUtil; +import org.eclipse.hawkbit.repository.model.Action; +import org.eclipse.hawkbit.repository.model.Action.Status; +import org.eclipse.hawkbit.repository.model.ActionStatus; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupConditions; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorAction; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorCondition; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition; +import org.eclipse.hawkbit.repository.model.SoftwareModule; +import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; +import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus; +import org.eclipse.hawkbit.repository.rsql.RSQLUtility; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Description; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.jpa.domain.Specification; + +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +/** + * Junit tests for RolloutManagment. + * + * @author Michael Hirsch + * + */ +@Features("Component Tests - Repository") +@Stories("Rollout Management") +public class RolloutManagementTest extends AbstractIntegrationTest { + + @Autowired + private RolloutManagement rolloutManagement; + + @Autowired + private RolloutGroupManagement rolloutGroupManagement; + + @Test + @Description("Verfiying that the rollout is created correctly, executing the filter and split up the targets in the correct group size.") + public void creatingRolloutIsCorrectPersisted() { + final int amountTargetsForRollout = 10; + final int amountOtherTargets = 15; + final int amountGroups = 5; + final String successCondition = "50"; + final String errorCondition = "80"; + final Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, + amountOtherTargets, amountGroups, successCondition, errorCondition); + + // verify the split of the target and targetGroup + final Page rolloutGroups = rolloutGroupManagement + .findRolloutGroupsByRolloutId(createdRollout.getId(), pageReq); + // we have total of #amountTargetsForRollout in rollouts splitted in + // group size #groupSize + assertThat(rolloutGroups).hasSize(amountGroups); + } + + @Test + @Description("Verfiying that when the rollout is started the actions for all targets in the rollout is created and the state of the first group is running as well as the corresponding actions") + public void startRolloutSetFirstGroupAndActionsInRunningStateAndOthersInScheduleState() { + final int amountTargetsForRollout = 10; + final int amountOtherTargets = 15; + final int amountGroups = 5; + final String successCondition = "50"; + final String errorCondition = "80"; + final Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, + amountOtherTargets, amountGroups, successCondition, errorCondition); + + // start the rollout + rolloutManagement.startRollout(createdRollout); + + // verify first group is running + final RolloutGroup firstGroup = rolloutGroupManagement.findRolloutGroupsByRolloutId(createdRollout.getId(), + new OffsetBasedPageRequest(0, 1, new Sort(Direction.ASC, "id"))).getContent().get(0); + assertThat(firstGroup.getStatus()).isEqualTo(RolloutGroupStatus.RUNNING); + + // verify other groups are scheduled + final List scheduledGroups = rolloutGroupManagement.findRolloutGroupsByRolloutId( + createdRollout.getId(), new OffsetBasedPageRequest(1, 100, new Sort(Direction.ASC, "id"))).getContent(); + scheduledGroups.forEach(group -> assertThat(group.getStatus()).isEqualTo(RolloutGroupStatus.SCHEDULED) + .as("group which should be in scheduled state is in " + group.getStatus() + " state")); + // verify that the first group actions has been started and are in state + // running + final List runningActions = deploymentManagement.findActionsByRolloutAndStatus(createdRollout, + Status.RUNNING); + assertThat(runningActions).hasSize(amountTargetsForRollout / amountGroups); + // the rest targets are only scheduled + assertThat(deploymentManagement.findActionsByRolloutAndStatus(createdRollout, Status.SCHEDULED)) + .hasSize(amountTargetsForRollout - (amountTargetsForRollout / amountGroups)); + } + + @Test + @Description("Verfiying that a finish condition of a group is hit the next group of the rollout is also started") + public void checkRunningRolloutsDoesNotStartNextGroupIfFinishConditionIsNotHit() { + final int amountTargetsForRollout = 10; + final int amountOtherTargets = 15; + final int amountGroups = 5; + final String successCondition = "50"; + final String errorCondition = "80"; + final Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, + amountOtherTargets, amountGroups, successCondition, errorCondition); + + rolloutManagement.startRollout(createdRollout); + final List runningActions = deploymentManagement.findActionsByRolloutAndStatus(createdRollout, + Status.RUNNING); + // finish one action should be sufficient due the finish condition is at + // 50% + final Action action = runningActions.get(0); + action.setStatus(Status.FINISHED); + controllerManagament.addUpdateActionStatus( + new ActionStatus(action, Status.FINISHED, System.currentTimeMillis(), ""), action); + + // check running rollouts again, now the finish condition should be hit + // and should start the next group + rolloutManagement.checkRunningRollouts(0); + + // verify that now the first and the second group are in running state + final List runningRolloutGroups = rolloutGroupManagement.findRolloutGroupsByRolloutId( + createdRollout.getId(), new OffsetBasedPageRequest(0, 2, new Sort(Direction.ASC, "id"))).getContent(); + runningRolloutGroups.forEach(group -> assertThat(group.getStatus()).isEqualTo(RolloutGroupStatus.RUNNING) + .as("group should be in running state because it should be started but it is in " + group.getStatus() + + " state")); + + // verify that the other groups are still in schedule state + final List scheduledRolloutGroups = rolloutGroupManagement.findRolloutGroupsByRolloutId( + createdRollout.getId(), new OffsetBasedPageRequest(2, 10, new Sort(Direction.ASC, "id"))).getContent(); + scheduledRolloutGroups.forEach(group -> assertThat(group.getStatus()).isEqualTo(RolloutGroupStatus.SCHEDULED) + .as("group should be in scheduled state because it should not be started but it is in " + + group.getStatus() + " state")); + } + + @Test + @Description("Verfiying that the error handling action of a group is executed to pause the current rollout") + public void checkErrorHitOfGroupCallsErrorActionToPauseTheRollout() { + final int amountTargetsForRollout = 10; + final int amountOtherTargets = 15; + final int amountGroups = 5; + final String successCondition = "50"; + final String errorCondition = "80"; + final Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, + amountOtherTargets, amountGroups, successCondition, errorCondition); + + rolloutManagement.startRollout(createdRollout); + // set both actions in error state so error condition is hit and error + // action is executed + final List runningActions = deploymentManagement.findActionsByRolloutAndStatus(createdRollout, + Status.RUNNING); + + // finish actions with error + for (final Action action : runningActions) { + action.setStatus(Status.ERROR); + controllerManagament.addUpdateActionStatus( + new ActionStatus(action, Status.ERROR, System.currentTimeMillis(), ""), action); + } + + // check running rollouts again, now the error condition should be hit + // and should execute the error action + rolloutManagement.checkRunningRollouts(0); + + final Rollout rollout = rolloutManagement.findRolloutById(createdRollout.getId()); + // the rollout itself should be in paused based on the error action + assertThat(rollout.getStatus()).isEqualTo(RolloutStatus.PAUSED); + + // the first rollout group should be in error state + final List errorGroup = rolloutGroupManagement.findRolloutGroupsByRolloutId( + createdRollout.getId(), new OffsetBasedPageRequest(0, 1, new Sort(Direction.ASC, "id"))).getContent(); + assertThat(errorGroup).hasSize(1); + assertThat(errorGroup.get(0).getStatus()).isEqualTo(RolloutGroupStatus.ERROR); + + // all other groups should still be in scheduled state + final List scheduleGroups = rolloutGroupManagement.findRolloutGroupsByRolloutId( + createdRollout.getId(), new OffsetBasedPageRequest(1, 100, new Sort(Direction.ASC, "id"))).getContent(); + scheduleGroups.forEach(group -> assertThat(group.getStatus()).isEqualTo(RolloutGroupStatus.SCHEDULED)); + } + + @Test + @Description("Verfiying a paused rollout in case of error action hit can be resumed again") + public void errorActionPausesRolloutAndRolloutGetsResumedStartsNextScheduledGroup() { + final int amountTargetsForRollout = 10; + final int amountOtherTargets = 15; + final int amountGroups = 5; + final String successCondition = "50"; + final String errorCondition = "80"; + final Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, + amountOtherTargets, amountGroups, successCondition, errorCondition); + rolloutManagement.startRollout(createdRollout); + // set both actions in error state so error condition is hit and error + // action is executed + final List runningActions = deploymentManagement.findActionsByRolloutAndStatus(createdRollout, + Status.RUNNING); + // finish actions with error + for (final Action action : runningActions) { + action.setStatus(Status.ERROR); + controllerManagament.addUpdateActionStatus( + new ActionStatus(action, Status.ERROR, System.currentTimeMillis(), ""), action); + } + + // check running rollouts again, now the error condition should be hit + // and should execute the error action + rolloutManagement.checkRunningRollouts(0); + + final Rollout rollout = rolloutManagement.findRolloutById(createdRollout.getId()); + // the rollout itself should be in paused based on the error action + assertThat(rollout.getStatus()).isEqualTo(RolloutStatus.PAUSED); + + // all other groups should still be in scheduled state + final List scheduleGroups = rolloutGroupManagement.findRolloutGroupsByRolloutId( + createdRollout.getId(), new OffsetBasedPageRequest(1, 100, new Sort(Direction.ASC, "id"))).getContent(); + scheduleGroups.forEach(group -> assertThat(group.getStatus()).isEqualTo(RolloutGroupStatus.SCHEDULED)); + + // resume the rollout again after it gets paused by error action + rolloutManagement.resumeRollout(rolloutManagement.findRolloutById(createdRollout.getId())); + + // the rollout should be running again + assertThat(rolloutManagement.findRolloutById(createdRollout.getId()).getStatus()) + .isEqualTo(RolloutStatus.RUNNING); + + // checking rollouts again + rolloutManagement.checkRunningRollouts(0); + + // next group should be running again after resuming the rollout + final List resumedGroups = rolloutGroupManagement.findRolloutGroupsByRolloutId( + createdRollout.getId(), new OffsetBasedPageRequest(1, 1, new Sort(Direction.ASC, "id"))).getContent(); + assertThat(resumedGroups).hasSize(1); + assertThat(resumedGroups.get(0).getStatus()).isEqualTo(RolloutGroupStatus.RUNNING); + } + + @Test + @Description("Verfiying that the rollout is starting group after group and gets finished at the end") + public void rolloutStartsGroupAfterGroupAndGetsFinished() { + final int amountTargetsForRollout = 10; + final int amountOtherTargets = 15; + final int amountGroups = 5; + final String successCondition = "50"; + final String errorCondition = "80"; + final Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, + amountOtherTargets, amountGroups, successCondition, errorCondition); + rolloutManagement.startRollout(createdRollout); + // finish running actions, 2 actions should be finished + assertThat(changeStatusForAllRunningActions(createdRollout, Status.FINISHED)).isEqualTo(2); + + // calculate the rest of the groups and finish them + for (int groupsLeft = amountGroups - 1; groupsLeft >= 1; groupsLeft--) { + // next check and start next group + rolloutManagement.checkRunningRollouts(0); + // finish running actions, 2 actions should be finished + assertThat(changeStatusForAllRunningActions(createdRollout, Status.FINISHED)).isEqualTo(2); + assertThat(rolloutManagement.findRolloutById(createdRollout.getId()).getStatus()) + .isEqualTo(RolloutStatus.RUNNING); + + } + // check rollout to see that all actions and all groups are finished and + // so can go to FINISHED state of the rollout + rolloutManagement.checkRunningRollouts(0); + + // verify all groups are in finished state + rolloutGroupManagement + .findRolloutGroupsByRolloutId(createdRollout.getId(), + new OffsetBasedPageRequest(0, 100, new Sort(Direction.ASC, "id"))) + .forEach(group -> assertThat(group.getStatus()).isEqualTo(RolloutGroupStatus.FINISHED)); + + // verify that rollout itself is in finished state + final Rollout findRolloutById = rolloutManagement.findRolloutById(createdRollout.getId()); + assertThat(findRolloutById.getStatus()).isEqualTo(RolloutStatus.FINISHED); + } + + @Test + @Description("Verify that the targets have the right status during the rollout.") + public void countCorrectStatusForEachTargetDuringRollout() { + + final int amountTargetsForRollout = 8; + final int amountOtherTargets = 15; + final int amountGroups = 4; + final String successCondition = "50"; + final String errorCondition = "80"; + final Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, + amountOtherTargets, amountGroups, successCondition, errorCondition); + + // targets have not started + Map validationMap = createInitStatusMap(); + validationMap.put(TotalTargetCountStatus.Status.NOTSTARTED, 8L); + validateRolloutActionStatus(createdRollout.getId(), validationMap); + + rolloutManagement.startRollout(createdRollout); + // 6 targets are ready and 2 are running + validationMap = createInitStatusMap(); + validationMap.put(TotalTargetCountStatus.Status.SCHEDULED, 6L); + validationMap.put(TotalTargetCountStatus.Status.RUNNING, 2L); + validateRolloutActionStatus(createdRollout.getId(), validationMap); + + changeStatusForAllRunningActions(createdRollout, Status.FINISHED); + rolloutManagement.checkRunningRollouts(0); + // 4 targets are ready, 2 are finished and 2 are running + validationMap = createInitStatusMap(); + validationMap.put(TotalTargetCountStatus.Status.SCHEDULED, 4L); + validationMap.put(TotalTargetCountStatus.Status.FINISHED, 2L); + validationMap.put(TotalTargetCountStatus.Status.RUNNING, 2L); + validateRolloutActionStatus(createdRollout.getId(), validationMap); + + changeStatusForAllRunningActions(createdRollout, Status.FINISHED); + rolloutManagement.checkRunningRollouts(0); + // 2 targets are ready, 4 are finished and 2 are running + validationMap = createInitStatusMap(); + validationMap.put(TotalTargetCountStatus.Status.SCHEDULED, 2L); + validationMap.put(TotalTargetCountStatus.Status.FINISHED, 4L); + validationMap.put(TotalTargetCountStatus.Status.RUNNING, 2L); + validateRolloutActionStatus(createdRollout.getId(), validationMap); + + changeStatusForAllRunningActions(createdRollout, Status.FINISHED); + rolloutManagement.checkRunningRollouts(0); + // 0 targets are ready, 6 are finished and 2 are running + validationMap = createInitStatusMap(); + validationMap.put(TotalTargetCountStatus.Status.FINISHED, 6L); + validationMap.put(TotalTargetCountStatus.Status.RUNNING, 2L); + validateRolloutActionStatus(createdRollout.getId(), validationMap); + + changeStatusForAllRunningActions(createdRollout, Status.FINISHED); + rolloutManagement.checkRunningRollouts(0); + // 0 targets are ready, 8 are finished and 0 are running + validationMap = createInitStatusMap(); + validationMap.put(TotalTargetCountStatus.Status.FINISHED, 8L); + validateRolloutActionStatus(createdRollout.getId(), validationMap); + + } + + @Test + @Description("Verify that the targets have the right status during the rollout when an error emerges.") + public void countCorrectStatusForEachTargetDuringRolloutWithError() { + + final int amountTargetsForRollout = 8; + final int amountOtherTargets = 15; + final int amountGroups = 4; + final String successCondition = "50"; + final String errorCondition = "80"; + final Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, + amountOtherTargets, amountGroups, successCondition, errorCondition); + + // 8 targets have not started + Map validationMap = createInitStatusMap(); + validationMap.put(TotalTargetCountStatus.Status.NOTSTARTED, 8L); + validateRolloutActionStatus(createdRollout.getId(), validationMap); + + rolloutManagement.startRollout(createdRollout); + // 6 targets are ready and 2 are running + validationMap = createInitStatusMap(); + validationMap.put(TotalTargetCountStatus.Status.SCHEDULED, 6L); + validationMap.put(TotalTargetCountStatus.Status.RUNNING, 2L); + validateRolloutActionStatus(createdRollout.getId(), validationMap); + + changeStatusForAllRunningActions(createdRollout, Status.ERROR); + rolloutManagement.checkRunningRollouts(0); + // 6 targets are ready and 2 are error + validationMap = createInitStatusMap(); + validationMap.put(TotalTargetCountStatus.Status.SCHEDULED, 6L); + validationMap.put(TotalTargetCountStatus.Status.ERROR, 2L); + validateRolloutActionStatus(createdRollout.getId(), validationMap); + } + + @Test + @Description("Verify that the targets have the right status during the rollout when receiving the status of rollout groups.") + public void countCorrectStatusForEachTargetGroupDuringRollout() { + + final int amountTargetsForRollout = 9; + final int amountOtherTargets = 15; + final int amountGroups = 4; + final String successCondition = "50"; + final String errorCondition = "80"; + Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, + amountOtherTargets, amountGroups, successCondition, errorCondition); + + rolloutManagement.startRollout(createdRollout); + changeStatusForAllRunningActions(createdRollout, Status.FINISHED); + rolloutManagement.checkRunningRollouts(0); + + // 3 targets finished (Group 1), 3 targets running (Group 3) and 3 + // targets SCHEDULED (Group 3) + createdRollout = rolloutManagement.findRolloutById(createdRollout.getId()); + final List rolloutGruops = createdRollout.getRolloutGroups(); + Map expectedTargetCountStatus = createInitStatusMap(); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.FINISHED, 3L); + validateRolloutGroupActionStatus(rolloutGruops.get(0), expectedTargetCountStatus); + expectedTargetCountStatus = createInitStatusMap(); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 3L); + validateRolloutGroupActionStatus(rolloutGruops.get(1), expectedTargetCountStatus); + expectedTargetCountStatus = createInitStatusMap(); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.SCHEDULED, 3L); + validateRolloutGroupActionStatus(rolloutGruops.get(2), expectedTargetCountStatus); + + } + + @Test + @Description("Verify that target actions of rollout get canceled when a manuel distribution sets assignment is done.") + public void targetsOfRolloutGetsManuelDsAssignment() { + + final int amountTargetsForRollout = 10; + final int amountOtherTargets = 0; + final int amountGroups = 2; + final String successCondition = "50"; + final String errorCondition = "80"; + Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, + amountOtherTargets, amountGroups, successCondition, errorCondition); + final DistributionSet ds = createdRollout.getDistributionSet(); + + rolloutManagement.startRollout(createdRollout); + createdRollout = rolloutManagement.findRolloutById(createdRollout.getId()); + // 5 targets are running + final List runningActions = deploymentManagement.findActionsByRolloutAndStatus(createdRollout, + Status.RUNNING); + assertThat(runningActions.size()).isEqualTo(5); + + // 5 targets are in the group and the DS has been assigned + final List rolloutGroups = createdRollout.getRolloutGroups(); + final Page targets = rolloutGroupManagement.findRolloutGroupTargets(rolloutGroups.get(0), + new OffsetBasedPageRequest(0, 20, new Sort(Direction.ASC, "id"))); + final List targetList = targets.getContent(); + assertThat(targetList.size()).isEqualTo(5); + for (final Target t : targetList) { + final DistributionSet assignedDs = t.getAssignedDistributionSet(); + assertThat(assignedDs.getId()).isEqualTo(ds.getId()); + } + + final List targetToCancel = new ArrayList(); + targetToCancel.add(targetList.get(0)); + targetToCancel.add(targetList.get(1)); + targetToCancel.add(targetList.get(2)); + final DistributionSet dsForCancelTest = TestDataUtil.generateDistributionSet("dsForTest", softwareManagement, + distributionSetManagement); + deploymentManagement.assignDistributionSet(dsForCancelTest, targetToCancel); + // 5 targets are canceling but still have the status running and 5 are + // still in SCHEDULED + final Map validationMap = createInitStatusMap(); + validationMap.put(TotalTargetCountStatus.Status.RUNNING, 5L); + validationMap.put(TotalTargetCountStatus.Status.SCHEDULED, 5L); + validateRolloutActionStatus(createdRollout.getId(), validationMap); + } + + @Test + @Description("Verify that target actions of a rollout get cancelled when another rollout with same targets gets started.") + public void targetsOfRolloutGetDistributionSetAssignmentByOtherRollout() { + + final int amountTargetsForRollout = 15; + final int amountOtherTargets = 5; + final int amountGroups = 3; + final String successCondition = "50"; + final String errorCondition = "80"; + Rollout rolloutOne = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, + amountOtherTargets, amountGroups, successCondition, errorCondition); + rolloutManagement.startRollout(rolloutOne); + rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId()); + + final DistributionSet dsForRolloutTwo = TestDataUtil.generateDistributionSet("dsForRolloutTwo", + softwareManagement, distributionSetManagement); + + final Rollout rolloutTwo = createRolloutByVariables("rolloutTwo", "This is the description for rollout two", 1, + "controllerId==rollout-*", dsForRolloutTwo, "50", "80"); + changeStatusForAllRunningActions(rolloutOne, Status.FINISHED); + rolloutManagement.checkRunningRollouts(0); + // Verify that 5 targets are finished, 5 are running and 5 are ready. + Map expectedTargetCountStatus = createInitStatusMap(); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 5L); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.FINISHED, 5L); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.SCHEDULED, 5L); + validateRolloutActionStatus(rolloutOne.getId(), expectedTargetCountStatus); + + rolloutManagement.startRollout(rolloutTwo); + // Verify that 5 targets are finished, 5 are still running and 5 are + // cancelled. + expectedTargetCountStatus = createInitStatusMap(); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.FINISHED, 5L); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 5L); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.CANCELLED, 5L); + validateRolloutActionStatus(rolloutOne.getId(), expectedTargetCountStatus); + } + + @Test + @Description("Verify that error status of DistributionSet installation during rollout can get rerun with second rollout so that all targets have some DistributionSet installed at the end.") + public void startSecondRolloutAfterFristRolloutEndenWithErrors() { + + final int amountTargetsForRollout = 15; + final int amountOtherTargets = 0; + final int amountGroups = 3; + final String successCondition = "50"; + final String errorCondition = "80"; + Rollout rolloutOne = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, + amountOtherTargets, amountGroups, successCondition, errorCondition); + final DistributionSet distributionSet = rolloutOne.getDistributionSet(); + rolloutManagement.startRollout(rolloutOne); + rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId()); + changeStatusForRunningActions(rolloutOne, Status.ERROR, 2); + changeStatusForRunningActions(rolloutOne, Status.FINISHED, 3); + rolloutManagement.checkRunningRollouts(0); + changeStatusForRunningActions(rolloutOne, Status.ERROR, 2); + changeStatusForRunningActions(rolloutOne, Status.FINISHED, 3); + rolloutManagement.checkRunningRollouts(0); + changeStatusForRunningActions(rolloutOne, Status.ERROR, 2); + changeStatusForRunningActions(rolloutOne, Status.FINISHED, 3); + rolloutManagement.checkRunningRollouts(0); + + // 9 targets are finished and 6 have error + Map expectedTargetCountStatus = createInitStatusMap(); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.FINISHED, 9L); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.ERROR, 6L); + validateRolloutActionStatus(rolloutOne.getId(), expectedTargetCountStatus); + // rollout is finished + rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId()); + assertThat(rolloutOne.getStatus()).isEqualTo(RolloutStatus.FINISHED); + + final int amountGroupsForRolloutTwo = 1; + Rollout rolloutTwo = createRolloutByVariables("rolloutTwo", "This is the description for rollout two", + amountGroupsForRolloutTwo, "controllerId==rollout-*", distributionSet, "50", "80"); + + rolloutManagement.startRollout(rolloutTwo); + rolloutTwo = rolloutManagement.findRolloutById(rolloutTwo.getId()); + // 6 error targets are know running + expectedTargetCountStatus = createInitStatusMap(); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 6L); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.NOTSTARTED, 9L); + validateRolloutActionStatus(rolloutTwo.getId(), expectedTargetCountStatus); + changeStatusForAllRunningActions(rolloutTwo, Status.FINISHED); + final Page targetPage = targetManagement.findTargetByUpdateStatus(pageReq, TargetUpdateStatus.IN_SYNC); + final List targetList = targetPage.getContent(); + // 15 targets in finished/IN_SYNC status and same DS assigned + assertThat(targetList.size()).isEqualTo(amountTargetsForRollout); + for (final Target t : targetList) { + final DistributionSet ds = t.getAssignedDistributionSet(); + assertThat(ds).isEqualTo(distributionSet); + } + } + + @Test + @Description("Verify that the rollout moves to the next group when the success condition was achieved and the error condition was not exceeded.") + public void successConditionAchievedAndErrorConditionNotExceeded() { + + final int amountTargetsForRollout = 10; + final int amountOtherTargets = 0; + final int amountGroups = 2; + final String successCondition = "50"; + final String errorCondition = "80"; + Rollout rolloutOne = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, + amountOtherTargets, amountGroups, successCondition, errorCondition); + + rolloutManagement.startRollout(rolloutOne); + rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId()); + changeStatusForRunningActions(rolloutOne, Status.ERROR, 2); + changeStatusForRunningActions(rolloutOne, Status.FINISHED, 3); + rolloutManagement.checkRunningRollouts(0); + // verify: 40% error but 60% finished -> should move to next group + final List rolloutGruops = rolloutOne.getRolloutGroups(); + final Map expectedTargetCountStatus = createInitStatusMap(); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 5L); + validateRolloutGroupActionStatus(rolloutGruops.get(1), expectedTargetCountStatus); + + } + + @Test + @Description("Verify that the rollout does not move to the next group when the sucess condition was not achieved.") + public void successConditionNotAchieved() { + + final int amountTargetsForRollout = 10; + final int amountOtherTargets = 0; + final int amountGroups = 2; + final String successCondition = "80"; + final String errorCondition = "90"; + Rollout rolloutOne = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, + amountOtherTargets, amountGroups, successCondition, errorCondition); + + rolloutManagement.startRollout(rolloutOne); + rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId()); + changeStatusForRunningActions(rolloutOne, Status.ERROR, 2); + changeStatusForRunningActions(rolloutOne, Status.FINISHED, 3); + rolloutManagement.checkRunningRollouts(0); + // verify: 40% error and 60% finished -> should not move to next group + // because successCondition 80% + rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId()); + final List rolloutGruops = rolloutOne.getRolloutGroups(); + final Map expectedTargetCountStatus = createInitStatusMap(); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.SCHEDULED, 5L); + validateRolloutGroupActionStatus(rolloutGruops.get(1), expectedTargetCountStatus); + } + + @Test + @Description("Verify that the rollout pauses when the error condition was exceeded.") + public void errorConditionExceeded() { + + final int amountTargetsForRollout = 10; + final int amountOtherTargets = 0; + final int amountGroups = 2; + final String successCondition = "50"; + final String errorCondition = "20"; + Rollout rolloutOne = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, + amountOtherTargets, amountGroups, successCondition, errorCondition); + + rolloutManagement.startRollout(rolloutOne); + rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId()); + changeStatusForRunningActions(rolloutOne, Status.ERROR, 2); + changeStatusForRunningActions(rolloutOne, Status.FINISHED, 3); + rolloutManagement.checkRunningRollouts(0); + // verify: 40% error -> should pause because errorCondition is 20% + rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId()); + assertThat(RolloutStatus.PAUSED).isEqualTo(rolloutOne.getStatus()); + } + + @Test + @Description("Verify that all rollouts are return with expected target statuses.") + public void findAllRolloutsWithDetailedStatus() { + + final int amountTargetsForRollout = 12; + final int amountGroups = 2; + final String successCondition = "50"; + final String errorCondition = "20"; + final Rollout rolloutA = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, amountGroups, + successCondition, errorCondition, "RolloutA", "RolloutA"); + rolloutManagement.startRollout(rolloutA); + + final int amountTargetsForRollout2 = 10; + final int amountGroups2 = 2; + final Rollout rolloutB = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout2, amountGroups2, + successCondition, errorCondition, "RolloutB", "RolloutB"); + rolloutManagement.startRollout(rolloutB); + changeStatusForAllRunningActions(rolloutB, Status.FINISHED); + rolloutManagement.checkRunningRollouts(0); + + final int amountTargetsForRollout3 = 10; + final int amountGroups3 = 2; + final Rollout rolloutC = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout3, amountGroups3, + successCondition, errorCondition, "RolloutC", "RolloutC"); + rolloutManagement.startRollout(rolloutC); + changeStatusForAllRunningActions(rolloutC, Status.ERROR); + rolloutManagement.checkRunningRollouts(0); + + final int amountTargetsForRollout4 = 15; + final int amountGroups4 = 3; + final Rollout rolloutD = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout4, amountGroups4, + successCondition, errorCondition, "RolloutD", "RolloutD"); + rolloutManagement.startRollout(rolloutD); + changeStatusForRunningActions(rolloutD, Status.ERROR, 1); + rolloutManagement.checkRunningRollouts(0); + changeStatusForAllRunningActions(rolloutD, Status.FINISHED); + rolloutManagement.checkRunningRollouts(0); + + final Page rolloutPage = rolloutManagement + .findAllRolloutsWithDetailedStatus(new OffsetBasedPageRequest(0, 100, new Sort(Direction.ASC, "name"))); + final List rolloutList = rolloutPage.getContent(); + + // validate rolloutA -> 6 running and 6 ready + Map expectedTargetCountStatus = createInitStatusMap(); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 6L); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.SCHEDULED, 6L); + validateRolloutActionStatus(rolloutList.get(0).getId(), expectedTargetCountStatus); + + // validate rolloutB -> 5 running and 5 finished + expectedTargetCountStatus = createInitStatusMap(); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.FINISHED, 5L); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 5L); + validateRolloutActionStatus(rolloutList.get(1).getId(), expectedTargetCountStatus); + + // validate rolloutC -> 5 running and 5 error + expectedTargetCountStatus = createInitStatusMap(); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.ERROR, 5L); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.SCHEDULED, 5L); + validateRolloutActionStatus(rolloutList.get(2).getId(), expectedTargetCountStatus); + + // validate rolloutD -> 1, error, 4 finished, 5 running and 5 ready + expectedTargetCountStatus = createInitStatusMap(); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.ERROR, 1L); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.FINISHED, 4L); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 5L); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.SCHEDULED, 5L); + validateRolloutActionStatus(rolloutList.get(3).getId(), expectedTargetCountStatus); + } + + @Test + @Description("Verify the count of existing rollouts.") + public void rightCountForAllRollouts() { + + final int amountTargetsForRollout = 6; + ; + final int amountGroups = 2; + final String successCondition = "50"; + final String errorCondition = "80"; + for (int i = 1; i <= 10; i++) { + createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, amountGroups, successCondition, + errorCondition, "Rollout" + i, "Rollout" + i); + } + final Long count = rolloutManagement.countRolloutsAll(); + assertThat(count).isEqualTo(10L); + } + + @Test + @Description("Verify the count of filtered existing rollouts.") + public void countRolloutsAllByFilters() { + + final int amountTargetsForRollout = 6; + final int amountGroups = 2; + final String successCondition = "50"; + final String errorCondition = "80"; + for (int i = 1; i <= 5; i++) { + createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, amountGroups, successCondition, + errorCondition, "Rollout" + i, "Rollout" + i); + } + for (int i = 1; i <= 5; i++) { + createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, amountGroups, successCondition, + errorCondition, "SomethingElse" + i, "SomethingElse" + i); + } + + final Long count = rolloutManagement.countRolloutsAllByFilters("Rollout%"); + assertThat(count).isEqualTo(5L); + + } + + @Test + @Description("Verify that the filtering and sorting ascending for rollout is working correctly.") + public void findRolloutByFilters() { + + final int amountTargetsForRollout = 6; + final int amountGroups = 2; + final String successCondition = "50"; + final String errorCondition = "80"; + for (int i = 1; i <= 5; i++) { + createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, amountGroups, successCondition, + errorCondition, "Rollout" + i, "Rollout" + i); + } + for (int i = 1; i <= 8; i++) { + createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, amountGroups, successCondition, + errorCondition, "SomethingElse" + i, "SomethingElse" + i); + } + + final Slice rollout = rolloutManagement + .findRolloutByFilters(new OffsetBasedPageRequest(0, 100, new Sort(Direction.ASC, "name")), "Rollout%"); + final List rolloutList = rollout.getContent(); + assertThat(rolloutList.size()).isEqualTo(5); + int i = 1; + for (final Rollout r : rolloutList) { + assertThat(r.getName()).isEqualTo("Rollout" + i); + i++; + } + } + + @Test + @Description("Verify that the expected rollout is found by name.") + public void findRolloutByName() { + + final int amountTargetsForRollout = 12; + final int amountGroups = 2; + final String successCondition = "50"; + final String errorCondition = "80"; + final String rolloutName = "Rollout137"; + final Rollout rolloutCreated = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, + amountGroups, successCondition, errorCondition, rolloutName, "RolloutA"); + + final Rollout rolloutFound = rolloutManagement.findRolloutByName(rolloutName); + assertThat(rolloutCreated).isEqualTo(rolloutFound); + + } + + @Test + @Description("Verify that the percent count is acting like aspected when targets move to the status finished or error.") + public void getFinishedPercentForRunningGroup() { + + final int amountTargetsForRollout = 10; + final int amountGroups = 2; + final String successCondition = "50"; + final String errorCondition = "80"; + final String rolloutName = "MyRollout"; + Rollout myRollout = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, amountGroups, + successCondition, errorCondition, rolloutName, rolloutName); + rolloutManagement.startRollout(myRollout); + changeStatusForRunningActions(myRollout, Status.FINISHED, 2); + rolloutManagement.checkRunningRollouts(0); + myRollout = rolloutManagement.findRolloutById(myRollout.getId()); + + float percent = rolloutManagement.getFinishedPercentForRunningGroup(myRollout.getId(), + myRollout.getRolloutGroups().get(0)); + assertThat(percent).isEqualTo(40); + + changeStatusForRunningActions(myRollout, Status.FINISHED, 3); + rolloutManagement.checkRunningRollouts(0); + + percent = rolloutManagement.getFinishedPercentForRunningGroup(myRollout.getId(), + myRollout.getRolloutGroups().get(0)); + assertThat(percent).isEqualTo(100); + + changeStatusForRunningActions(myRollout, Status.FINISHED, 4); + changeStatusForAllRunningActions(myRollout, Status.ERROR); + rolloutManagement.checkRunningRollouts(0); + + percent = rolloutManagement.getFinishedPercentForRunningGroup(myRollout.getId(), + myRollout.getRolloutGroups().get(1)); + assertThat(percent).isEqualTo(80); + } + + @Test + @Description("Verify that the expected targets in the expected order are returned for the rollout groups.") + public void findRolloutGroupTargetsWithRsqlParam() { + + final int amountTargetsForRollout = 15; + final int amountGroups = 3; + final int amountOtherTargets = 15; + final String successCondition = "50"; + final String errorCondition = "80"; + final String rolloutName = "MyRollout"; + Rollout myRollout = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, amountGroups, + successCondition, errorCondition, rolloutName, rolloutName); + + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountOtherTargets, "others-", "rollout")); + + final String rsqlParam = "controllerId==*MyRoll*"; + final Specification rsqlSpecification = RSQLUtility.parse(rsqlParam, TargetFields.class); + + rolloutManagement.startRollout(myRollout); + myRollout = rolloutManagement.findRolloutById(myRollout.getId()); + final List rolloutGroups = myRollout.getRolloutGroups(); + + Page targetPage = rolloutGroupManagement.findRolloutGroupTargets(rolloutGroups.get(0), + rsqlSpecification, new OffsetBasedPageRequest(0, 100, new Sort(Direction.ASC, "controllerId"))); + final List targetlistGroup1 = targetPage.getContent(); + assertThat(targetlistGroup1.size()).isEqualTo(5); + assertThat(targetlistGroup1.get(0).getControllerId()).isEqualTo("MyRollout--00000"); + + targetPage = rolloutGroupManagement.findRolloutGroupTargets(rolloutGroups.get(1), rsqlSpecification, + new OffsetBasedPageRequest(0, 100, new Sort(Direction.DESC, "controllerId"))); + final List targetlistGroup2 = targetPage.getContent(); + assertThat(targetlistGroup2.size()).isEqualTo(5); + assertThat(targetlistGroup2.get(0).getControllerId()).isEqualTo("MyRollout--00009"); + + targetPage = rolloutGroupManagement.findRolloutGroupTargets(rolloutGroups.get(2), rsqlSpecification, + new OffsetBasedPageRequest(0, 100, new Sort(Direction.ASC, "controllerId"))); + final List targetlistGroup3 = targetPage.getContent(); + assertThat(targetlistGroup3.size()).isEqualTo(5); + assertThat(targetlistGroup3.get(0).getControllerId()).isEqualTo("MyRollout--00010"); + + } + + @Test + @Description("Verify the creation and the start of a rollout in asynchronous mode.") + public void createAndStartRolloutInAsync() { + + final int amountTargetsForRollout = 500; + final int amountGroups = 5; + final String successCondition = "50"; + final String errorCondition = "80"; + final String rolloutName = "rolloutTest"; + final String targetPrefixName = rolloutName; + final DistributionSet distributionSet = TestDataUtil.generateDistributionSet("dsFor" + rolloutName, + softwareManagement, distributionSetManagement); + targetManagement.createTargets( + TestDataUtil.buildTargetFixtures(amountTargetsForRollout, targetPrefixName + "-", targetPrefixName)); + final RolloutGroupConditions conditions = new RolloutGroup.RolloutGroupConditionBuilder() + .successCondition(RolloutGroupSuccessCondition.THRESHOLD, successCondition) + .errorCondition(RolloutGroupErrorCondition.THRESHOLD, errorCondition) + .errorAction(RolloutGroupErrorAction.PAUSE, null).build(); + Rollout myRollout = new Rollout(); + myRollout.setName(rolloutName); + myRollout.setDescription("This is a test description for the rollout"); + myRollout.setTargetFilterQuery("controllerId==" + targetPrefixName + "-*"); + myRollout.setDistributionSet(distributionSet); + + myRollout = rolloutManagement.createRolloutAsync(myRollout, amountGroups, conditions); + + int counter = 1; + int counterMax = 10; + while (!isRolloutInGivenStatus(myRollout.getId(), RolloutStatus.READY) && (counter <= counterMax)) { + try { + Thread.sleep(500); + } catch (final InterruptedException e) { + e.printStackTrace(); + } + counter++; + } + + myRollout = rolloutManagement.findRolloutById(myRollout.getId()); + assertThat(myRollout.getStatus()).isEqualTo(RolloutStatus.READY); + rolloutManagement.startRolloutAsync(myRollout); + + counter = 1; + counterMax = 10; + while (!isRolloutInGivenStatus(myRollout.getId(), RolloutStatus.RUNNING) && counter <= counterMax) { + try { + Thread.sleep(500); + } catch (final InterruptedException e) { + e.printStackTrace(); + } + counter++; + } + + myRollout = rolloutManagement.findRolloutById(myRollout.getId()); + assertThat(myRollout.getStatus()).isEqualTo(RolloutStatus.RUNNING); + final Map expectedTargetCountStatus = createInitStatusMap(); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 100L); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.SCHEDULED, 400L); + validateRolloutActionStatus(myRollout.getId(), expectedTargetCountStatus); + } + + private boolean isRolloutInGivenStatus(final Long rolloutID, final RolloutStatus status) { + final Rollout myRollout = rolloutManagement.findRolloutById(rolloutID); + if (myRollout.getStatus() == status) { + return true; + } + return false; + } + + private void validateRolloutGroupActionStatus(final RolloutGroup rolloutGroup, + final Map expectedTargetCountStatus) { + final RolloutGroup rolloutGroupWithDetail = rolloutGroupManagement + .findRolloutGroupWithDetailedStatus(rolloutGroup.getId()); + validateStatus(rolloutGroupWithDetail.getTotalTargetCountStatus(), expectedTargetCountStatus); + } + + private void validateRolloutActionStatus(final Long rolloutId, + final Map expectedTargetCountStatus) { + final Rollout rolloutWithDetail = rolloutManagement.findRolloutWithDetailedStatus(rolloutId); + validateStatus(rolloutWithDetail.getTotalTargetCountStatus(), expectedTargetCountStatus); + } + + private void validateStatus(final TotalTargetCountStatus totalTargetCountStatus, + final Map expectedTotalCountStates) { + for (final Map.Entry entry : expectedTotalCountStates.entrySet()) { + final Long countReady = totalTargetCountStatus.getTotalTargetCountByStatus(entry.getKey()); + assertThat(countReady).isEqualTo(entry.getValue()); + } + } + + private Rollout createSimpleTestRolloutWithTargetsAndDistributionSet(final int amountTargetsForRollout, + final int amountOtherTargets, final int groupSize, final String successCondition, + final String errorCondition) { + final SoftwareModule ah = softwareManagement + .createSoftwareModule(new SoftwareModule(appType, "agent-hub", "1.0.1", null, "")); + final SoftwareModule jvm = softwareManagement + .createSoftwareModule(new SoftwareModule(runtimeType, "oracle-jre", "1.7.2", null, "")); + final SoftwareModule os = softwareManagement + .createSoftwareModule(new SoftwareModule(osType, "poky", "3.0.2", null, "")); + final DistributionSet rolloutDS = distributionSetManagement.createDistributionSet( + TestDataUtil.buildDistributionSet("rolloutDS", "0.0.0", standardDsType, os, jvm, ah)); + targetManagement + .createTargets(TestDataUtil.buildTargetFixtures(amountTargetsForRollout, "rollout-", "rollout")); + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountOtherTargets, "others-", "rollout")); + final String filterQuery = "controllerId==rollout-*"; + return createRolloutByVariables("test-rollout-name-1", "test-rollout-description-1", groupSize, filterQuery, + rolloutDS, successCondition, errorCondition); + } + + private Rollout createTestRolloutWithTargetsAndDistributionSet(final int amountTargetsForRollout, + final int groupSize, final String successCondition, final String errorCondition, final String rolloutName, + final String targetPrefixName) { + final DistributionSet dsForRolloutTwo = TestDataUtil.generateDistributionSet("dsFor" + rolloutName, + softwareManagement, distributionSetManagement); + targetManagement.createTargets( + TestDataUtil.buildTargetFixtures(amountTargetsForRollout, targetPrefixName + "-", targetPrefixName)); + return createRolloutByVariables(rolloutName, rolloutName + "description", groupSize, + "controllerId==" + targetPrefixName + "-*", dsForRolloutTwo, successCondition, errorCondition); + } + + private Rollout createRolloutByVariables(final String rolloutName, final String rolloutDescription, + final int groupSize, final String filterQuery, final DistributionSet distributionSet, + final String successCondition, final String errorCondition) { + final RolloutGroupConditions conditions = new RolloutGroup.RolloutGroupConditionBuilder() + .successCondition(RolloutGroupSuccessCondition.THRESHOLD, successCondition) + .errorCondition(RolloutGroupErrorCondition.THRESHOLD, errorCondition) + .errorAction(RolloutGroupErrorAction.PAUSE, null).build(); + final Rollout rolloutToCreate = new Rollout(); + rolloutToCreate.setName(rolloutName); + rolloutToCreate.setDescription(rolloutDescription); + rolloutToCreate.setTargetFilterQuery(filterQuery); + rolloutToCreate.setDistributionSet(distributionSet); + return rolloutManagement.createRollout(rolloutToCreate, groupSize, conditions); + } + + private int changeStatusForAllRunningActions(final Rollout rollout, final Status status) { + final List runningActions = deploymentManagement.findActionsByRolloutAndStatus(rollout, Status.RUNNING); + for (final Action action : runningActions) { + action.setStatus(status); + controllerManagament.addUpdateActionStatus(new ActionStatus(action, status, System.currentTimeMillis(), ""), + action); + } + return runningActions.size(); + } + + private int changeStatusForRunningActions(final Rollout rollout, final Status status, + final int amountOfTargetsToGetChanged) { + final List runningActions = deploymentManagement.findActionsByRolloutAndStatus(rollout, Status.RUNNING); + assertThat(runningActions.size()).isGreaterThanOrEqualTo(amountOfTargetsToGetChanged); + for (int i = 0; i < amountOfTargetsToGetChanged; i++) { + controllerManagament.addUpdateActionStatus( + new ActionStatus(runningActions.get(i), status, System.currentTimeMillis(), ""), + runningActions.get(i)); + } + return runningActions.size(); + } + + private Map createInitStatusMap() { + final Map map = new HashMap(); + for (final TotalTargetCountStatus.Status status : TotalTargetCountStatus.Status.values()) { + map.put(status, 0L); + } + return map; + } + +} diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLRolloutGroupFields.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLRolloutGroupFields.java new file mode 100644 index 000000000..05e26293c --- /dev/null +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLRolloutGroupFields.java @@ -0,0 +1,94 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.rsql; + +import static org.fest.assertions.api.Assertions.assertThat; + +import org.eclipse.hawkbit.AbstractIntegrationTest; +import org.eclipse.hawkbit.TestDataUtil; +import org.eclipse.hawkbit.repository.RolloutGroupFields; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupConditionBuilder; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition; +import org.junit.Before; +import org.junit.Test; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; + +import ru.yandex.qatools.allure.annotations.Description; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +@Features("Component Tests - RSQL filtering") +@Stories("RSQL filter rollout group") +public class RSQLRolloutGroupFields extends AbstractIntegrationTest { + + private Long rolloutGroupId; + private Rollout rollout; + + @Before + public void seuptBeforeTest() { + final int amountTargets = 20; + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout")); + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*"); + rollout = rolloutManagement.findRolloutById(rollout.getId()); + this.rolloutGroupId = rollout.getRolloutGroups().get(0).getId(); + } + + @Test + @Description("Test filter rollout group by id") + public void testFilterByParameterId() { + assertRSQLQuery(RolloutGroupFields.ID.name() + "==" + rolloutGroupId, 1); + assertRSQLQuery(RolloutGroupFields.ID.name() + "==noExist*", 0); + assertRSQLQuery(RolloutGroupFields.ID.name() + "=in=(" + rolloutGroupId + ")", 1); + assertRSQLQuery(RolloutGroupFields.ID.name() + "=out=(" + rolloutGroupId + ")", 3); + } + + @Test + @Description("Test filter rollout group by name") + public void testFilterByParameterName() { + assertRSQLQuery(RolloutGroupFields.NAME.name() + "==group-1", 1); + assertRSQLQuery(RolloutGroupFields.NAME.name() + "==*", 4); + assertRSQLQuery(RolloutGroupFields.NAME.name() + "==noExist*", 0); + assertRSQLQuery(RolloutGroupFields.NAME.name() + "=in=(group-1,group-2)", 2); + assertRSQLQuery(RolloutGroupFields.NAME.name() + "=out=(group-1,group-2)", 2); + } + + @Test + @Description("Test filter rollout group by description") + public void testFilterByParameterDescription() { + assertRSQLQuery(RolloutGroupFields.DESCRIPTION.name() + "==group-1", 1); + assertRSQLQuery(RolloutGroupFields.DESCRIPTION.name() + "==group*", 4); + assertRSQLQuery(RolloutGroupFields.DESCRIPTION.name() + "==noExist*", 0); + assertRSQLQuery(RolloutGroupFields.DESCRIPTION.name() + "=in=(group-1,notexist)", 1); + assertRSQLQuery(RolloutGroupFields.DESCRIPTION.name() + "=out=(group-1,notexist)", 3); + } + + private void assertRSQLQuery(final String rsqlParam, final long expcetedTargets) { + final Page findTargetPage = rolloutGroupManagement.findRolloutGroupsByPredicate(rollout, + RSQLUtility.parse(rsqlParam, RolloutGroupFields.class), new PageRequest(0, 100)); + final long countTargetsAll = findTargetPage.getTotalElements(); + assertThat(findTargetPage).isNotNull(); + assertThat(countTargetsAll).isEqualTo(expcetedTargets); + } + + private Rollout createRollout(final String name, final int amountGroups, final long distributionSetId, + final String targetFilterQuery) { + final Rollout rollout = new Rollout(); + rollout.setDistributionSet(distributionSetManagement.findDistributionSetById(distributionSetId)); + rollout.setName(name); + rollout.setTargetFilterQuery(targetFilterQuery); + return rolloutManagement.createRollout(rollout, amountGroups, new RolloutGroupConditionBuilder() + .successCondition(RolloutGroupSuccessCondition.THRESHOLD, "100").build()); + } +} diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/RestConstants.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/RestConstants.java index 33b154844..706584a64 100644 --- a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/RestConstants.java +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/RestConstants.java @@ -154,6 +154,16 @@ public final class RestConstants { */ public static final String DISTRIBUTIONSET_V1_REQUEST_MAPPING = BASE_V1_REQUEST_MAPPING + "/distributionsets"; + /** + * The rollout URL mapping rest resource. + */ + public static final String ROLLOUT_V1_REQUEST_MAPPING = BASE_V1_REQUEST_MAPPING + "/rollouts"; + + /** + * Request parameter for async + */ + public static final String REQUEST_PARAMETER_ASYNC = "async"; + /** * The target URL mapping, href link for artifact download. */ diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/action/ActionPagedList.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/action/ActionPagedList.java index f8908277f..27b36779a 100644 --- a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/action/ActionPagedList.java +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/action/ActionPagedList.java @@ -14,7 +14,7 @@ import java.util.List; import org.eclipse.hawkbit.rest.resource.model.PagedList; /** - * Paged list rest model for {@link Action} to RESTful API representation. + * Paged list rest model for {@link ErrorAction} to RESTful API representation. * */ public class ActionPagedList extends PagedList { diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutCondition.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutCondition.java new file mode 100644 index 000000000..914a5d5c2 --- /dev/null +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutCondition.java @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource.model.rollout; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +/** + * + */ +@JsonInclude(Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class RolloutCondition { + + private Condition condition = Condition.THRESHOLD; + private String expression = "100"; + + /** + * + */ + public RolloutCondition() { + } + + public RolloutCondition(final Condition condition, final String expression) { + this.condition = condition; + this.expression = expression; + } + + /** + * @return the condition + */ + public Condition getCondition() { + return condition; + } + + /** + * @param condition + * the condition to set + */ + public void setCondition(final Condition condition) { + this.condition = condition; + } + + /** + * @return the expession + */ + public String getExpression() { + return expression; + } + + /** + * @param expession + * the expession to set + */ + public void setExpression(final String expession) { + this.expression = expession; + } + + public enum Condition { + THRESHOLD; + } +} diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutErrorAction.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutErrorAction.java new file mode 100644 index 000000000..2ed6077e6 --- /dev/null +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutErrorAction.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource.model.rollout; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +/** + * + */ +@JsonInclude(Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class RolloutErrorAction { + + private ErrorAction action = ErrorAction.PAUSE; + private String expression = null; + + /** + * @return the action + */ + public ErrorAction getAction() { + return action; + } + + /** + * @param action + * the action to set + */ + public void setAction(final ErrorAction action) { + this.action = action; + } + + /** + * @return the expression + */ + public String getExpression() { + return expression; + } + + /** + * @param expression + * the expression to set + */ + public void setExpression(final String expression) { + this.expression = expression; + } + + public enum ErrorAction { + PAUSE; + } +} diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutPagedList.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutPagedList.java new file mode 100644 index 000000000..ea1544991 --- /dev/null +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutPagedList.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource.model.rollout; + +import java.util.List; + +import org.eclipse.hawkbit.rest.resource.model.PagedList; + +/** + * Paged list for Rollout. + * + * + */ +public class RolloutPagedList extends PagedList { + + private final List content; + + public RolloutPagedList(final List content, final long total) { + super(content, total); + this.content = content; + } + + /** + * @return the content of the paged list. Never {@code null}. + */ + public List getContent() { + return content; + } + +} diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutResponseBody.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutResponseBody.java new file mode 100644 index 000000000..dd1296cfd --- /dev/null +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutResponseBody.java @@ -0,0 +1,124 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource.model.rollout; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.hawkbit.rest.resource.model.NamedEntityRest; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * + */ +@JsonInclude(Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class RolloutResponseBody extends NamedEntityRest { + + private String targetFilterQuery; + private Long distributionSetId; + + @JsonProperty(value = "id", required = true) + private Long rolloutId; + + @JsonProperty(required = true) + private String status; + + @JsonProperty(required = true) + private Long totalTargets; + + @JsonProperty(required = true) + private final Map totalTargetsPerStatus = new HashMap<>(); + + /** + * @return the status + */ + public String getStatus() { + return status; + } + + /** + * @param status + * the status to set + */ + public void setStatus(final String status) { + this.status = status; + } + + /** + * @return the rolloutId + */ + public Long getRolloutId() { + return rolloutId; + } + + /** + * @param rolloutId + * the rolloutId to set + */ + public void setRolloutId(final Long rolloutId) { + this.rolloutId = rolloutId; + } + + /** + * @return the targetFilterQuery + */ + public String getTargetFilterQuery() { + return targetFilterQuery; + } + + /** + * @param targetFilterQuery + * the targetFilterQuery to set + */ + public void setTargetFilterQuery(final String targetFilterQuery) { + this.targetFilterQuery = targetFilterQuery; + } + + /** + * @return the distributionSetId + */ + public Long getDistributionSetId() { + return distributionSetId; + } + + /** + * @param distributionSetId + * the distributionSetId to set + */ + public void setDistributionSetId(final Long distributionSetId) { + this.distributionSetId = distributionSetId; + } + + /** + * @param totalTargets + * the totalTargets to set + */ + public void setTotalTargets(final Long totalTargets) { + this.totalTargets = totalTargets; + } + + /** + * @return the totalTargets + */ + public Long getTotalTargets() { + return totalTargets; + } + + /** + * @return the totalTargetsPerStatus + */ + public Map getTotalTargetsPerStatus() { + return totalTargetsPerStatus; + } +} diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutRestRequestBody.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutRestRequestBody.java new file mode 100644 index 000000000..0b83948ff --- /dev/null +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutRestRequestBody.java @@ -0,0 +1,175 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource.model.rollout; + +import org.eclipse.hawkbit.rest.resource.model.NamedEntityRest; +import org.eclipse.hawkbit.rest.resource.model.distributionset.ActionTypeRest; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +/** + * Model for request containing a rollout body e.g. in a POST request of + * creating a rollout via REST API. + */ +@JsonInclude(Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class RolloutRestRequestBody extends NamedEntityRest { + + private String targetFilterQuery; + private long distributionSetId; + + private int amountGroups = 1; + + private RolloutCondition successCondition = new RolloutCondition(); + private RolloutSuccessAction successAction = new RolloutSuccessAction(); + private RolloutCondition errorCondition = null; + private RolloutErrorAction errorAction = null; + + private Long forcetime; + + private ActionTypeRest type; + + /** + * @return the finishCondition + */ + public RolloutCondition getSuccessCondition() { + return successCondition; + } + + /** + * @param successCondition + * the finishCondition to set + */ + public void setSuccessCondition(final RolloutCondition successCondition) { + this.successCondition = successCondition; + } + + /** + * @return the successAction + */ + public RolloutSuccessAction getSuccessAction() { + return successAction; + } + + /** + * @param successAction + * the successAction to set + */ + public void setSuccessAction(final RolloutSuccessAction successAction) { + this.successAction = successAction; + } + + /** + * @return the errorCondition + */ + public RolloutCondition getErrorCondition() { + return errorCondition; + } + + /** + * @param errorCondition + * the errorCondition to set + */ + public void setErrorCondition(final RolloutCondition errorCondition) { + this.errorCondition = errorCondition; + } + + /** + * @return the targetFilterQuery + */ + public String getTargetFilterQuery() { + return targetFilterQuery; + } + + /** + * @param targetFilterQuery + * the targetFilterQuery to set + */ + public void setTargetFilterQuery(final String targetFilterQuery) { + this.targetFilterQuery = targetFilterQuery; + } + + /** + * @return the distributionSetId + */ + public long getDistributionSetId() { + return distributionSetId; + } + + /** + * @param distributionSetId + * the distributionSetId to set + */ + public void setDistributionSetId(final long distributionSetId) { + this.distributionSetId = distributionSetId; + } + + /** + * @return the groupSize + */ + public int getAmountGroups() { + return amountGroups; + } + + /** + * @param groupSize + * the groupSize to set + */ + public void setAmountGroups(final int groupSize) { + this.amountGroups = groupSize; + } + + /** + * @return the forcetime + */ + public Long getForcetime() { + return forcetime; + } + + /** + * @param forcetime + * the forcetime to set + */ + public void setForcetime(final Long forcetime) { + this.forcetime = forcetime; + } + + /** + * @return the type + */ + public ActionTypeRest getType() { + return type; + } + + /** + * @param type + * the type to set + */ + public void setType(final ActionTypeRest type) { + this.type = type; + } + + /** + * @return the errorAction + */ + public RolloutErrorAction getErrorAction() { + return errorAction; + } + + /** + * @param errorAction + * the errorAction to set + */ + public void setErrorAction(final RolloutErrorAction errorAction) { + this.errorAction = errorAction; + } + +} diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutSuccessAction.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutSuccessAction.java new file mode 100644 index 000000000..f14e9a8bf --- /dev/null +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutSuccessAction.java @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource.model.rollout; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +/** + * + */ +@JsonInclude(Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class RolloutSuccessAction { + + private SuccessAction action = SuccessAction.NEXTGROUP; + private String expression = null; + + /** + * + */ + public RolloutSuccessAction() { + } + + public RolloutSuccessAction(final SuccessAction action, final String expression) { + this.action = action; + this.expression = expression; + } + + /** + * @return the action + */ + public SuccessAction getAction() { + return action; + } + + /** + * @param action + * the action to set + */ + public void setAction(final SuccessAction action) { + this.action = action; + } + + /** + * @return the expession + */ + public String getExpression() { + return expression; + } + + /** + * @param expession + * the expession to set + */ + public void setExpression(final String expession) { + this.expression = expession; + } + + public enum SuccessAction { + NEXTGROUP; + } +} diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rolloutgroup/RolloutGroupPagedList.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rolloutgroup/RolloutGroupPagedList.java new file mode 100644 index 000000000..a341ac6ba --- /dev/null +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rolloutgroup/RolloutGroupPagedList.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource.model.rolloutgroup; + +import java.util.List; + +import org.eclipse.hawkbit.rest.resource.model.PagedList; + +/** + * Paged list for Rollout. + * + */ +public class RolloutGroupPagedList extends PagedList { + + private final List content; + + public RolloutGroupPagedList(final List content, final long total) { + super(content, total); + this.content = content; + } + + /** + * @return the content of the paged list. Never {@code null}. + */ + public List getContent() { + return content; + } + +} diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rolloutgroup/RolloutGroupResponseBody.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rolloutgroup/RolloutGroupResponseBody.java new file mode 100644 index 000000000..bb1b0116e --- /dev/null +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rolloutgroup/RolloutGroupResponseBody.java @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource.model.rolloutgroup; + +import org.eclipse.hawkbit.rest.resource.model.NamedEntityRest; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Model for the rollout group annotated with json-annotations for easier + * serialization and de-serialization. + */ +@JsonInclude(Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class RolloutGroupResponseBody extends NamedEntityRest { + + @JsonProperty(value = "id", required = true) + private Long rolloutGroupId; + + @JsonProperty(required = true) + private String status; + + /** + * @return the rolloutGroupId + */ + public Long getRolloutGroupId() { + return rolloutGroupId; + } + + /** + * @param rolloutGroupId + * the rolloutGroupId to set + */ + public void setRolloutGroupId(final Long rolloutGroupId) { + this.rolloutGroupId = rolloutGroupId; + } + + /** + * @return the status + */ + public String getStatus() { + return status; + } + + /** + * @param status + * the status to set + */ + public void setStatus(final String status) { + this.status = status; + } +} diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/cache/CacheWriteNotify.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/cache/CacheWriteNotify.java deleted file mode 100644 index 1bf5704e1..000000000 --- a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/cache/CacheWriteNotify.java +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.eclipse.hawkbit.cache; - -import org.eclipse.hawkbit.eventbus.event.DownloadProgressEvent; -import org.eclipse.hawkbit.repository.model.Action; -import org.eclipse.hawkbit.repository.model.ActionStatus; -import org.eclipse.hawkbit.tenancy.TenantAware; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cache.Cache; -import org.springframework.cache.CacheManager; -import org.springframework.stereotype.Service; - -import com.google.common.eventbus.EventBus; - -/** - * An service which combines the functionality for functional use cases to write - * into the cache an notify the writing to the cache to the {@link EventBus}. - * - * - * - * - */ -@Service -public class CacheWriteNotify { - - /** - * - */ - private static final int DOWNLOAD_PROGRESS_MAX = 100; - - @Autowired - private CacheManager cacheManager; - - @Autowired - private EventBus eventBus; - - @Autowired - private TenantAware tenantAware; - - /** - * writes the download progress in percentage into the cache - * {@link CacheKeys#DOWNLOAD_PROGRESS_PERCENT} and notifies the - * {@link EventBus} with a {@link DownloadProgressEvent}. - * - * @param statusId - * the ID of the {@link ActionStatus} - * @param progressPercent - * the progress in percentage which must be between 0-100 - */ - public void downloadProgressPercent(final long statusId, final int progressPercent) { - - final Cache cache = cacheManager.getCache(Action.class.getName()); - final String cacheKey = CacheKeys.entitySpecificCacheKey(String.valueOf(statusId), - CacheKeys.DOWNLOAD_PROGRESS_PERCENT); - if (progressPercent < DOWNLOAD_PROGRESS_MAX) { - cache.put(cacheKey, progressPercent); - } else { - // in case we reached progress 100 delete the cache value again - // because otherwise he will - // keep there forever - cache.evict(cacheKey); - } - - eventBus.post(new DownloadProgressEvent(tenantAware.getCurrentTenant(), statusId, progressPercent)); - } - - /** - * @param cacheManager - * the cacheManager to set - */ - void setCacheManager(final CacheManager cacheManager) { - this.cacheManager = cacheManager; - } - - /** - * @param eventBus - * the eventBus to set - */ - void setEventBus(final EventBus eventBus) { - this.eventBus = eventBus; - } - - /** - * @param tenantAware - * the tenantAware to set - */ - void setTenantAware(final TenantAware tenantAware) { - this.tenantAware = tenantAware; - } -} diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/PagingUtility.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/PagingUtility.java index afefedb5c..1503b8bda 100644 --- a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/PagingUtility.java +++ b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/PagingUtility.java @@ -12,6 +12,8 @@ import org.eclipse.hawkbit.repository.ActionFields; import org.eclipse.hawkbit.repository.ActionStatusFields; import org.eclipse.hawkbit.repository.DistributionSetFields; import org.eclipse.hawkbit.repository.DistributionSetMetadataFields; +import org.eclipse.hawkbit.repository.RolloutFields; +import org.eclipse.hawkbit.repository.RolloutGroupFields; import org.eclipse.hawkbit.repository.SoftwareModuleFields; import org.eclipse.hawkbit.repository.SoftwareModuleMetadataFields; import org.eclipse.hawkbit.repository.TargetFields; @@ -127,4 +129,25 @@ public final class PagingUtility { return sorting; } + static Sort sanitizeRolloutSortParam(final String sortParam) { + final Sort sorting; + if (sortParam != null) { + sorting = new Sort(SortUtility.parse(RolloutFields.class, sortParam)); + } else { + // default sort + sorting = new Sort(Direction.ASC, RolloutFields.NAME.getFieldName()); + } + return sorting; + } + + static Sort sanitizeRolloutGroupSortParam(final String sortParam) { + final Sort sorting; + if (sortParam != null) { + sorting = new Sort(SortUtility.parse(RolloutGroupFields.class, sortParam)); + } else { + // default sort + sorting = new Sort(Direction.ASC, RolloutGroupFields.NAME.getFieldName()); + } + return sorting; + } } diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/ResponseExceptionHandler.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/ResponseExceptionHandler.java index d222a96fd..f054e4bc5 100644 --- a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/ResponseExceptionHandler.java +++ b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/ResponseExceptionHandler.java @@ -64,7 +64,7 @@ public class ResponseExceptionHandler { ERROR_TO_HTTP_STATUS.put(SpServerError.SP_DS_TYPE_UNDEFINED, HttpStatus.BAD_REQUEST); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_TENANT_NOT_EXISTS, HttpStatus.BAD_REQUEST); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ENTITY_LOCKED, HttpStatus.LOCKED); - + ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ROLLOUT_ILLEGAL_STATE, HttpStatus.BAD_REQUEST); } private static HttpStatus getStatusOrDefault(final SpServerError error) { @@ -84,8 +84,7 @@ public class ResponseExceptionHandler { * as entity. */ @ExceptionHandler(SpServerRtException.class) - public ResponseEntity handleSpServerRtExceptions(final HttpServletRequest request, - final Exception ex) { + public ResponseEntity handleSpServerRtExceptions(final HttpServletRequest request, final Exception ex) { LOG.debug("Handling exception of request {}", request.getRequestURL()); final ExceptionInfo response = new ExceptionInfo(); final HttpStatus responseStatus; diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/RolloutMapper.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/RolloutMapper.java new file mode 100644 index 000000000..712595638 --- /dev/null +++ b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/RolloutMapper.java @@ -0,0 +1,162 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource; + +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.hawkbit.repository.model.Action.ActionType; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorAction; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorCondition; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessAction; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition; +import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus; +import org.eclipse.hawkbit.rest.resource.helper.RestResourceConversionHelper; +import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutCondition.Condition; +import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutErrorAction.ErrorAction; +import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutResponseBody; +import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutRestRequestBody; +import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutSuccessAction.SuccessAction; +import org.eclipse.hawkbit.rest.resource.model.rolloutgroup.RolloutGroupResponseBody; + +/** + * A mapper which maps repository model to RESTful model representation and + * back. + * + * + */ +final class RolloutMapper { + + private static final String NOT_SUPPORTED = " is not supported"; + + private RolloutMapper() { + // Utility class + } + + static List toResponseRollout(final List rollouts) { + final List result = new ArrayList<>(rollouts.size()); + rollouts.forEach(r -> result.add(toResponseRollout(r))); + return result; + } + + static RolloutResponseBody toResponseRollout(final Rollout rollout) { + final RolloutResponseBody body = new RolloutResponseBody(); + body.setCreatedAt(rollout.getCreatedAt()); + body.setCreatedBy(rollout.getCreatedBy()); + body.setDescription(rollout.getDescription()); + body.setLastModifiedAt(rollout.getLastModifiedAt()); + body.setLastModifiedBy(rollout.getLastModifiedBy()); + body.setName(rollout.getName()); + body.setRolloutId(rollout.getId()); + body.setTargetFilterQuery(rollout.getTargetFilterQuery()); + body.setDistributionSetId(rollout.getDistributionSet().getId()); + body.setStatus(rollout.getStatus().toString().toLowerCase()); + body.setTotalTargets(rollout.getTotalTargets()); + + for (final TotalTargetCountStatus.Status status : TotalTargetCountStatus.Status.values()) { + body.getTotalTargetsPerStatus().put(status.name().toLowerCase(), + rollout.getTotalTargetCountStatus().getTotalTargetCountByStatus(status)); + } + + body.add(linkTo(methodOn(RolloutResource.class).getRollout(rollout.getId())).withRel("self")); + body.add(linkTo(methodOn(RolloutResource.class).start(rollout.getId(), false)).withRel("start")); + body.add(linkTo(methodOn(RolloutResource.class).start(rollout.getId(), true)).withRel("startAsync")); + body.add(linkTo(methodOn(RolloutResource.class).pause(rollout.getId())).withRel("pause")); + body.add(linkTo(methodOn(RolloutResource.class).resume(rollout.getId())).withRel("resume")); + body.add(linkTo(methodOn(RolloutResource.class).getRolloutGroups(rollout.getId(), + Integer.parseInt(RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET), + Integer.parseInt(RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT), null, null)).withRel("groups")); + return body; + } + + static Rollout fromRequest(final RolloutRestRequestBody restRequest, final DistributionSet distributionSet, + final String filterQuery) { + final Rollout rollout = new Rollout(); + rollout.setName(restRequest.getName()); + rollout.setDescription(restRequest.getDescription()); + rollout.setDistributionSet(distributionSet); + rollout.setTargetFilterQuery(filterQuery); + final ActionType convertActionType = RestResourceConversionHelper.convertActionType(restRequest.getType()); + if (convertActionType != null) { + rollout.setActionType(convertActionType); + } + if (restRequest.getForcetime() != null) { + rollout.setForcedTime(restRequest.getForcetime()); + + } + return rollout; + } + + static List toResponseRolloutGroup(final List rollouts) { + final List result = new ArrayList<>(rollouts.size()); + rollouts.forEach(r -> result.add(toResponseRolloutGroup(r))); + return result; + } + + static RolloutGroupResponseBody toResponseRolloutGroup(final RolloutGroup rolloutGroup) { + final RolloutGroupResponseBody body = new RolloutGroupResponseBody(); + body.setCreatedAt(rolloutGroup.getCreatedAt()); + body.setCreatedBy(rolloutGroup.getCreatedBy()); + body.setDescription(rolloutGroup.getDescription()); + body.setLastModifiedAt(rolloutGroup.getLastModifiedAt()); + body.setLastModifiedBy(rolloutGroup.getLastModifiedBy()); + body.setName(rolloutGroup.getName()); + body.setRolloutGroupId(rolloutGroup.getId()); + body.setStatus(rolloutGroup.getStatus().toString().toLowerCase()); + body.add(linkTo(methodOn(RolloutResource.class).getRolloutGroup(rolloutGroup.getRollout().getId(), + rolloutGroup.getId())).withRel("self")); + return body; + } + + static RolloutGroupErrorCondition mapErrorCondition(final Condition condition) { + if (Condition.THRESHOLD.equals(condition)) { + return RolloutGroupErrorCondition.THRESHOLD; + } + throw new IllegalArgumentException(createIllegalArgumentLiteral(condition)); + } + + static RolloutGroupSuccessCondition mapFinishCondition(final Condition condition) { + if (Condition.THRESHOLD.equals(condition)) { + return RolloutGroupSuccessCondition.THRESHOLD; + } + throw new IllegalArgumentException(createIllegalArgumentLiteral(condition)); + } + + static Condition map(final RolloutGroupSuccessCondition rolloutCondition) { + if (RolloutGroupSuccessCondition.THRESHOLD.equals(rolloutCondition)) { + return Condition.THRESHOLD; + } + throw new IllegalArgumentException("Rollout group condition " + rolloutCondition + NOT_SUPPORTED); + } + + static RolloutGroupErrorAction map(final ErrorAction action) { + if (ErrorAction.PAUSE.equals(action)) { + return RolloutGroupErrorAction.PAUSE; + } + throw new IllegalArgumentException("Error Action " + action + NOT_SUPPORTED); + } + + static RolloutGroupSuccessAction map(final SuccessAction action) { + if (SuccessAction.NEXTGROUP.equals(action)) { + return RolloutGroupSuccessAction.NEXTGROUP; + } + throw new IllegalArgumentException("Success Action " + action + NOT_SUPPORTED); + } + + private static String createIllegalArgumentLiteral(final Condition condition) { + return "Condition " + condition + NOT_SUPPORTED; + } + +} diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/RolloutResource.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/RolloutResource.java new file mode 100644 index 000000000..d72db72f9 --- /dev/null +++ b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/RolloutResource.java @@ -0,0 +1,402 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource; + +import java.util.List; + +import org.eclipse.hawkbit.repository.DistributionSetManagement; +import org.eclipse.hawkbit.repository.RolloutFields; +import org.eclipse.hawkbit.repository.RolloutGroupFields; +import org.eclipse.hawkbit.repository.RolloutGroupManagement; +import org.eclipse.hawkbit.repository.RolloutManagement; +import org.eclipse.hawkbit.repository.TargetFields; +import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupConditions; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorAction; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorCondition; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessAction; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition; +import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.rsql.RSQLUtility; +import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutPagedList; +import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutResponseBody; +import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutRestRequestBody; +import org.eclipse.hawkbit.rest.resource.model.rolloutgroup.RolloutGroupPagedList; +import org.eclipse.hawkbit.rest.resource.model.rolloutgroup.RolloutGroupResponseBody; +import org.eclipse.hawkbit.rest.resource.model.target.TargetPagedList; +import org.eclipse.hawkbit.rest.resource.model.target.TargetRest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * REST Resource handling rollout CRUD operations. + * + */ +@RestController +@RequestMapping(RestConstants.ROLLOUT_V1_REQUEST_MAPPING) +public class RolloutResource { + + @Autowired + private RolloutManagement rolloutManagement; + + @Autowired + private RolloutGroupManagement rolloutGroupManagement; + + @Autowired + private DistributionSetManagement distributionSetManagement; + + /** + * Handles the GET request of retrieving all rollouts. + * + * @param pagingOffsetParam + * the offset of list of rollouts for pagination, might not be + * present in the rest request then default value will be applied + * @param pagingLimitParam + * the limit of the paged request, might not be present in the + * rest request then default value will be applied + * @param sortParam + * the sorting parameter in the request URL, syntax + * {@code field:direction, field:direction} + * @param rsqlParam + * the search parameter in the request URL, syntax + * {@code q=name==abc} + * @return a list of all rollouts for a defined or default page request with + * status OK. The response is always paged. In any failure the + * JsonResponseExceptionHandler is handling the response. + */ + @RequestMapping(method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE, "application/hal+json" }) + public ResponseEntity getRollouts( + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam) { + + final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam); + final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam); + final Sort sorting = PagingUtility.sanitizeRolloutSortParam(sortParam); + + final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting); + + final Page findModulesAll; + if (rsqlParam != null) { + findModulesAll = rolloutManagement + .findAllWithDetailedStatusByPredicate(RSQLUtility.parse(rsqlParam, RolloutFields.class), pageable); + } else { + findModulesAll = rolloutManagement.findAll(pageable); + } + + final List rest = RolloutMapper.toResponseRollout(findModulesAll.getContent()); + return new ResponseEntity<>(new RolloutPagedList(rest, findModulesAll.getTotalElements()), HttpStatus.OK); + } + + /** + * Handles the GET request of retrieving a single rollout. + * + * @param rolloutId + * the ID of the rollout to retrieve + * @return a single rollout with status OK. + * @throws EntityNotFoundException + * in case no rollout with the given {@code rolloutId} exists. + */ + @RequestMapping(value = "/{rolloutId}", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE, + "application/hal+json" }) + public ResponseEntity getRollout(@PathVariable("rolloutId") final Long rolloutId) { + final Rollout findRolloutById = findRolloutOrThrowException(rolloutId); + return new ResponseEntity<>(RolloutMapper.toResponseRollout(findRolloutById), HttpStatus.OK); + } + + /** + * Handles the POST request for creating rollout. + * + * @param rollout + * the rollout body to be created. + * @return In case rollout could successful created the ResponseEntity with + * status code 201 with the successfully created rollout. In any + * failure the JsonResponseExceptionHandler is handling the + * response. + * @throws EntityNotFoundException + */ + @RequestMapping(method = RequestMethod.POST, consumes = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity create(@RequestBody final RolloutRestRequestBody rolloutRequestBody) { + + // first check the given RSQL query if it's well formed, otherwise and + // exception is thrown + RSQLUtility.isValid(rolloutRequestBody.getTargetFilterQuery()); + + final DistributionSet distributionSet = findDistributionSetOrThrowException(rolloutRequestBody); + + // success condition + RolloutGroupSuccessCondition successCondition = null; + String successConditionExpr = null; + // success action + RolloutGroupSuccessAction successAction = null; + String successActionExpr = null; + // error condition + RolloutGroupErrorCondition errorCondition = null; + // error action + String errorConditionExpr = null; + RolloutGroupErrorAction errorAction = null; + String errorActionExpr = null; + if (rolloutRequestBody.getSuccessCondition() != null) { + successCondition = RolloutMapper + .mapFinishCondition(rolloutRequestBody.getSuccessCondition().getCondition()); + successConditionExpr = rolloutRequestBody.getSuccessCondition().getExpression(); + } + if (rolloutRequestBody.getSuccessAction() != null) { + successAction = RolloutMapper.map(rolloutRequestBody.getSuccessAction().getAction()); + successActionExpr = rolloutRequestBody.getSuccessAction().getExpression(); + } + if (rolloutRequestBody.getErrorCondition() != null) { + errorCondition = RolloutMapper.mapErrorCondition(rolloutRequestBody.getErrorCondition().getCondition()); + errorConditionExpr = rolloutRequestBody.getErrorCondition().getExpression(); + } + if (rolloutRequestBody.getErrorAction() != null) { + errorAction = RolloutMapper.map(rolloutRequestBody.getErrorAction().getAction()); + errorActionExpr = rolloutRequestBody.getErrorAction().getExpression(); + } + + final RolloutGroupConditions rolloutGroupConditions = new RolloutGroup.RolloutGroupConditionBuilder() + .successCondition(successCondition, successConditionExpr) + .successAction(successAction, successActionExpr).errorCondition(errorCondition, errorConditionExpr) + .errorAction(errorAction, errorActionExpr).build(); + final Rollout rollout = rolloutManagement.createRollout( + RolloutMapper.fromRequest(rolloutRequestBody, distributionSet, + rolloutRequestBody.getTargetFilterQuery()), + rolloutRequestBody.getAmountGroups(), rolloutGroupConditions); + + return ResponseEntity.status(HttpStatus.CREATED).body(RolloutMapper.toResponseRollout(rollout)); + } + + /** + * Handles the POST request for starting a rollout. + * + * @param rolloutId + * the ID of the rollout to be started. + * @return OK response (200) if rollout could be started. In case of any + * exception the corresponding errors occur. + * @throws EntityNotFoundException + * @see RolloutManagement#startRollout(Rollout) + * @see ResponseExceptionHandler + */ + @RequestMapping(method = RequestMethod.POST, value = "/{rolloutId}/start", produces = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity start(@PathVariable("rolloutId") final Long rolloutId, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_ASYNC, defaultValue = "false") final boolean startAsync) { + final Rollout rollout = findRolloutOrThrowException(rolloutId); + if (startAsync) { + rolloutManagement.startRolloutAsync(rollout); + } else { + rolloutManagement.startRollout(rollout); + } + return ResponseEntity.ok().build(); + } + + /** + * Handles the POST request for pausing a rollout. + * + * @param rolloutId + * the ID of the rollout to be paused. + * @return OK response (200) if rollout could be paused. In case of any + * exception the corresponding errors occur. + * @throws EntityNotFoundException + * @see RolloutManagement#pauseRollout(Rollout) + * @see ResponseExceptionHandler + */ + @RequestMapping(method = RequestMethod.POST, value = "/{rolloutId}/pause", produces = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity pause(@PathVariable("rolloutId") final Long rolloutId) { + final Rollout rollout = findRolloutOrThrowException(rolloutId); + rolloutManagement.pauseRollout(rollout); + return ResponseEntity.ok().build(); + } + + /** + * Handles the POST request for resuming a rollout. + * + * @param rolloutId + * the ID of the rollout to be resumed. + * @return OK response (200) if rollout could be resumed. In case of any + * exception the corresponding errors occur. + * @throws EntityNotFoundException + * @see RolloutManagement#resumeRollout(Rollout) + * @see ResponseExceptionHandler + */ + @RequestMapping(method = RequestMethod.POST, value = "/{rolloutId}/resume", produces = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity resume(@PathVariable("rolloutId") final Long rolloutId) { + final Rollout rollout = findRolloutOrThrowException(rolloutId); + rolloutManagement.resumeRollout(rollout); + return ResponseEntity.ok().build(); + } + + /** + * Handles the GET request of retrieving all rollout groups referred to a + * rollout. + * + * @param pagingOffsetParam + * the offset of list of rollout groups for pagination, might not + * be present in the rest request then default value will be + * applied + * @param pagingLimitParam + * the limit of the paged request, might not be present in the + * rest request then default value will be applied + * @param sortParam + * the sorting parameter in the request URL, syntax + * {@code field:direction, field:direction} + * @param rsqlParam + * the search parameter in the request URL, syntax + * {@code q=name==abc} + * @return a list of all rollout groups referred to a rollout for a defined + * or default page request with status OK. The response is always + * paged. In any failure the JsonResponseExceptionHandler is + * handling the response. + */ + @RequestMapping(method = RequestMethod.GET, value = "/{rolloutId}/deploygroups", produces = { + MediaType.APPLICATION_JSON_VALUE, "application/hal+json" }) + public ResponseEntity getRolloutGroups(@PathVariable("rolloutId") final Long rolloutId, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam) { + final Rollout rollout = findRolloutOrThrowException(rolloutId); + + final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam); + final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam); + final Sort sorting = PagingUtility.sanitizeRolloutGroupSortParam(sortParam); + + final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting); + + final Page findRolloutGroupsAll; + if (rsqlParam != null) { + findRolloutGroupsAll = rolloutGroupManagement.findRolloutGroupsByPredicate(rollout, + RSQLUtility.parse(rsqlParam, RolloutGroupFields.class), pageable); + } else { + findRolloutGroupsAll = rolloutGroupManagement.findRolloutGroupsByRolloutId(rolloutId, pageable); + } + + final List rest = RolloutMapper + .toResponseRolloutGroup(findRolloutGroupsAll.getContent()); + return new ResponseEntity<>(new RolloutGroupPagedList(rest, findRolloutGroupsAll.getTotalElements()), + HttpStatus.OK); + } + + /** + * Handles the GET request for retrieving a single rollout group. + * + * @param rolloutId + * the rolloutId to retrieve the group from + * @param groupId + * the groupId to retrieve the rollout group + * @return the OK response containing the {@link RolloutGroupResponseBody} + * @throws EntityNotFoundException + */ + @RequestMapping(method = RequestMethod.GET, value = "/{rolloutId}/deploygroups/{groupId}", produces = { + MediaType.APPLICATION_JSON_VALUE, "application/hal+json" }) + public ResponseEntity getRolloutGroup(@PathVariable("rolloutId") final Long rolloutId, + @PathVariable("groupId") final Long groupId) { + findRolloutOrThrowException(rolloutId); + final RolloutGroup rolloutGroup = findRolloutGroupOrThrowException(groupId); + return ResponseEntity.ok(RolloutMapper.toResponseRolloutGroup(rolloutGroup)); + } + + /** + * Retrieves all targets related to a specific rollout group. + * + * @param rolloutId + * the ID of the rollout + * @param groupId + * the ID of the rollout group + * @param pagingOffsetParam + * the offset of list of rollout groups for pagination, might not + * be present in the rest request then default value will be + * applied + * @param pagingLimitParam + * the limit of the paged request, might not be present in the + * rest request then default value will be applied + * @param sortParam + * the sorting parameter in the request URL, syntax + * {@code field:direction, field:direction} + * @param rsqlParam + * the search parameter in the request URL, syntax + * {@code q=name==abc} + * @return a paged list of targets related to a specific rollout and rollout + * group. + */ + @RequestMapping(method = RequestMethod.GET, value = "/{rolloutId}/deploygroups/{groupId}/targets", produces = { + MediaType.APPLICATION_JSON_VALUE, "application/hal+json" }) + public ResponseEntity getRolloutGroupTargets(@PathVariable("rolloutId") final Long rolloutId, + @PathVariable("groupId") final Long groupId, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam) { + findRolloutOrThrowException(rolloutId); + final RolloutGroup rolloutGroup = findRolloutGroupOrThrowException(groupId); + + final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam); + final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam); + final Sort sorting = PagingUtility.sanitizeTargetSortParam(sortParam); + + final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting); + + final Page rolloutGroupTargets; + if (rsqlParam != null) { + final Specification rsqlSpecification = RSQLUtility.parse(rsqlParam, TargetFields.class); + rolloutGroupTargets = rolloutGroupManagement.findRolloutGroupTargets(rolloutGroup, rsqlSpecification, + pageable); + } else { + final Page pageTargets = rolloutGroupManagement.findRolloutGroupTargets(rolloutGroup, pageable); + rolloutGroupTargets = pageTargets; + } + final List rest = TargetMapper.toResponse(rolloutGroupTargets.getContent()); + return new ResponseEntity<>(new TargetPagedList(rest, rolloutGroupTargets.getTotalElements()), HttpStatus.OK); + } + + private Rollout findRolloutOrThrowException(final Long rolloutId) { + final Rollout rollout = rolloutManagement.findRolloutWithDetailedStatus(rolloutId); + if (rollout == null) { + throw new EntityNotFoundException("Rollout with Id {" + rolloutId + "} does not exist"); + } + return rollout; + } + + private RolloutGroup findRolloutGroupOrThrowException(final Long rolloutGroupId) { + final RolloutGroup rolloutGroup = rolloutGroupManagement.findRolloutGroupById(rolloutGroupId); + if (rolloutGroup == null) { + throw new EntityNotFoundException("Group with Id {" + rolloutGroupId + "} does not exist"); + } + return rolloutGroup; + } + + private DistributionSet findDistributionSetOrThrowException(final RolloutRestRequestBody rolloutRequestBody) { + final DistributionSet ds = distributionSetManagement + .findDistributionSetById(rolloutRequestBody.getDistributionSetId()); + if (ds == null) { + throw new EntityNotFoundException( + "DistributionSet with Id {" + rolloutRequestBody.getDistributionSetId() + "} does not exist"); + } + return ds; + } +} diff --git a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/JsonBuilder.java b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/JsonBuilder.java index bbed30c38..dec0bb070 100644 --- a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/JsonBuilder.java +++ b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/JsonBuilder.java @@ -16,6 +16,7 @@ import java.util.stream.Collectors; import org.apache.commons.lang3.RandomStringUtils; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetType; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupConditions; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModuleType; import org.eclipse.hawkbit.repository.model.Target; @@ -40,9 +41,9 @@ public abstract class JsonBuilder { try { builder.append(new JSONObject().put("name", module.getName()) .put("description", module.getDescription()).put("type", module.getType().getKey()) - .put("id", Long.MAX_VALUE).put("vendor", module.getVendor()).put("version", module.getVersion()) - .put("createdAt", "0").put("updatedAt", "0").put("createdBy", "fghdfkjghdfkjh") - .put("updatedBy", "fghdfkjghdfkjh").toString()); + .put("id", Long.MAX_VALUE).put("vendor", module.getVendor()) + .put("version", module.getVersion()).put("createdAt", "0").put("updatedAt", "0") + .put("createdBy", "fghdfkjghdfkjh").put("updatedBy", "fghdfkjghdfkjh").toString()); } catch (final Exception e) { e.printStackTrace(); } @@ -184,13 +185,15 @@ public abstract class JsonBuilder { final List messages = new ArrayList(); messages.add(message); - return new JSONObject().put("id", id).put("time", "20140511T121314") + return new JSONObject() + .put("id", id) + .put("time", "20140511T121314") .put("status", - new JSONObject().put("execution", execution) + new JSONObject() + .put("execution", execution) .put("result", new JSONObject().put("finished", finished).put("progress", - new JSONObject().put("cnt", 2).put("of", 5))) - .put("details", messages)) + new JSONObject().put("cnt", 2).put("of", 5))).put("details", messages)) .toString(); } @@ -378,9 +381,9 @@ public abstract class JsonBuilder { for (final Target target : targets) { try { builder.append(new JSONObject().put("controllerId", target.getControllerId()) - .put("description", target.getDescription()).put("name", target.getName()).put("createdAt", "0") - .put("updatedAt", "0").put("createdBy", "fghdfkjghdfkjh").put("updatedBy", "fghdfkjghdfkjh") - .toString()); + .put("description", target.getDescription()).put("name", target.getName()) + .put("createdAt", "0").put("updatedAt", "0").put("createdBy", "fghdfkjghdfkjh") + .put("updatedBy", "fghdfkjghdfkjh").toString()); } catch (final Exception e) { e.printStackTrace(); } @@ -395,6 +398,40 @@ public abstract class JsonBuilder { return builder.toString(); } + public static String rollout(final String name, final String description, final int groupSize, + final long distributionSetId, final String targetFilterQuery, final RolloutGroupConditions conditions) { + final JSONObject json = new JSONObject(); + json.put("name", name); + json.put("description", description); + json.put("amountGroups", groupSize); + json.put("distributionSetId", distributionSetId); + json.put("targetFilterQuery", targetFilterQuery); + + if (conditions != null) { + final JSONObject successCondition = new JSONObject(); + json.put("successCondition", successCondition); + successCondition.put("condition", conditions.getSuccessCondition().toString()); + successCondition.put("expression", conditions.getSuccessConditionExp().toString()); + + final JSONObject successAction = new JSONObject(); + json.put("successAction", successAction); + successAction.put("action", conditions.getSuccessAction().toString()); + successAction.put("expression", conditions.getSuccessActionExp().toString()); + + final JSONObject errorCondition = new JSONObject(); + json.put("errorCondition", errorCondition); + errorCondition.put("condition", conditions.getErrorCondition().toString()); + errorCondition.put("expression", conditions.getErrorConditionExp().toString()); + + final JSONObject errorAction = new JSONObject(); + json.put("errorAction", errorAction); + errorAction.put("action", conditions.getErrorAction().toString()); + errorAction.put("expression", conditions.getErrorActionExp().toString()); + } + + return json.toString(); + } + public static String cancelActionFeedback(final String id, final String execution) throws JSONException { return cancelActionFeedback(id, execution, RandomStringUtils.randomAscii(1000)); @@ -404,7 +441,9 @@ public abstract class JsonBuilder { throws JSONException { final List messages = new ArrayList(); messages.add(message); - return new JSONObject().put("id", id).put("time", "20140511T121314") + return new JSONObject() + .put("id", id) + .put("time", "20140511T121314") .put("status", new JSONObject().put("execution", execution) .put("result", new JSONObject().put("finished", "success")).put("details", messages)) @@ -414,12 +453,13 @@ public abstract class JsonBuilder { public static String configData(final String id, final Map attributes, final String execution) throws JSONException { - return new JSONObject().put("id", id).put("time", "20140511T121314") + return new JSONObject() + .put("id", id) + .put("time", "20140511T121314") .put("status", new JSONObject().put("execution", execution) .put("result", new JSONObject().put("finished", "success")) - .put("details", new ArrayList())) - .put("data", attributes).toString(); + .put("details", new ArrayList())).put("data", attributes).toString(); } diff --git a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/RolloutResourceTest.java b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/RolloutResourceTest.java new file mode 100644 index 000000000..d0ed796fc --- /dev/null +++ b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/RolloutResourceTest.java @@ -0,0 +1,598 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource; + +import static org.fest.assertions.api.Assertions.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.notNullValue; +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; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.List; +import java.util.concurrent.Callable; + +import org.eclipse.hawkbit.AbstractIntegrationTest; +import org.eclipse.hawkbit.MockMvcResultPrinter; +import org.eclipse.hawkbit.TestDataUtil; +import org.eclipse.hawkbit.WithUser; +import org.eclipse.hawkbit.repository.RolloutGroupManagement; +import org.eclipse.hawkbit.repository.RolloutManagement; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupConditionBuilder; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition; +import org.eclipse.hawkbit.repository.model.Target; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Description; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.http.MediaType; + +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +/** + * Tests for covering the {@link RolloutResource}. + */ +@Features("Component Tests - Management RESTful API") +@Stories("Rollout Resource") +public class RolloutResourceTest extends AbstractIntegrationTest { + + @Autowired + private RolloutManagement rolloutManagement; + + @Autowired + private RolloutGroupManagement rolloutGroupManagement; + + @Test + @Description("Testing that creating rollout with wrong body returns bad request") + public void createRolloutWithInvalidBodyReturnsBadRequest() throws Exception { + mvc.perform(post("/rest/v1/rollouts").content("invalid body").contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("errorCode", equalTo("hawkbit.server.error.rest.body.notReadable"))); + } + + @Test + @Description("Testing that creating rollout with insufficient permission returns forbidden") + @WithUser(allSpPermissions = true, removeFromAllPermission = "ROLLOUT_MANAGEMENT") + public void createRolloutWithInsufficientPermissionReturnsForbidden() throws Exception { + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + mvc.perform(post("/rest/v1/rollouts") + .content(JsonBuilder.rollout("name", "desc", 10, dsA.getId(), "name==test", null)) + .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated()).andReturn(); + } + + @Test + @Description("Testing that creating rollout with not exisiting distribution set returns not found") + public void createRolloutWithNotExistingDistributionSetReturnsNotFound() throws Exception { + mvc.perform(post("/rest/v1/rollouts").content(JsonBuilder.rollout("name", "desc", 10, 1234, "name==test", null)) + .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound()).andReturn(); + } + + @Test + @Description("Testing that creating rollout with not valid formed target filter query returns bad request") + public void createRolloutWithNotWellFormedFilterReturnsBadRequest() throws Exception { + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + mvc.perform(post("/rest/v1/rollouts") + .content(JsonBuilder.rollout("name", "desc", 10, dsA.getId(), "name=test", null)) + .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest()) + .andExpect(jsonPath("errorCode", equalTo("hawkbit.server.error.rest.param.rsqlParamSyntax"))) + .andReturn(); + } + + @Description("TODO") + public void missingTargetFilterQueryInRollout() throws Exception { + + final String targetFilterQuery = null; + + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + mvc.perform(post("/rest/v1/rollouts") + .content(JsonBuilder.rollout("rollout1", "desc", 10, dsA.getId(), targetFilterQuery, null)) + .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest()) + .andExpect(jsonPath("errorCode", equalTo("hawkbit.server.error.rest.param.rsqlParamSyntax"))) + .andReturn(); + + } + + @Test + @Description("Testing that rollout can be created") + public void createRollout() throws Exception { + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + postRollout("rollout1", 10, dsA.getId(), "name==target1"); + } + + @Test + @Description("Testing the empty list is returned if no rollout exists") + public void noRolloutReturnsEmptyList() throws Exception { + mvc.perform(get("/rest/v1/rollouts")).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$content", hasSize(0))).andExpect(jsonPath("$total", equalTo(0))); + } + + @Test + @Description("Testing that rollout paged list contains rollouts") + public void rolloutPagedListContainsAllRollouts() throws Exception { + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + // setup - create 2 rollouts + postRollout("rollout1", 10, dsA.getId(), "name==target1"); + postRollout("rollout2", 5, dsA.getId(), "name==target2"); + + mvc.perform(get("/rest/v1/rollouts")).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$content", hasSize(2))).andExpect(jsonPath("$total", equalTo(2))) + .andExpect(jsonPath("content[0].name", equalTo("rollout1"))) + .andExpect(jsonPath("content[0].status", equalTo("ready"))) + .andExpect(jsonPath("content[0].targetFilterQuery", equalTo("name==target1"))) + .andExpect(jsonPath("content[0].distributionSetId", equalTo(dsA.getId().intValue()))) + .andExpect(jsonPath("content[1].name", equalTo("rollout2"))) + .andExpect(jsonPath("content[1].status", equalTo("ready"))) + .andExpect(jsonPath("content[1].targetFilterQuery", equalTo("name==target2"))) + .andExpect(jsonPath("content[1].distributionSetId", equalTo(dsA.getId().intValue()))); + } + + @Test + @Description("Testing that rollout paged list is limited by the query param limit") + public void rolloutPagedListIsLimitedToQueryParam() throws Exception { + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + // setup - create 2 rollouts + postRollout("rollout1", 10, dsA.getId(), "name==target1"); + postRollout("rollout2", 5, dsA.getId(), "name==target2"); + + mvc.perform(get("/rest/v1/rollouts?limit=1")).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$content", hasSize(1))).andExpect(jsonPath("$total", equalTo(2))); + } + + @Test + @Description("Testing that rollout paged list is limited by the query param limit") + public void retrieveRolloutGroupsForSpecificRollout() throws Exception { + // setup + final int amountTargets = 20; + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout")); + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + + // create rollout including the created targets with prefix 'rollout' + final Rollout rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*"); + + // retrieve rollout groups from created rollout + mvc.perform(get("/rest/v1/rollouts/{rolloutId}/deploygroups", rollout.getId())) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$content", hasSize(4))).andExpect(jsonPath("$total", equalTo(4))) + .andExpect(jsonPath("$content[0].status", equalTo("ready"))) + .andExpect(jsonPath("$content[1].status", equalTo("ready"))) + .andExpect(jsonPath("$content[2].status", equalTo("ready"))) + .andExpect(jsonPath("$content[3].status", equalTo("ready"))); + } + + @Test + @Description("Testing that starting the rollout switches the state to running") + public void startingRolloutSwitchesIntoRunningState() throws Exception { + // setup + final int amountTargets = 20; + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout")); + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + + // create rollout including the created targets with prefix 'rollout' + final Rollout rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*"); + + // starting rollout + mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()); + + // check rollout is in running state + mvc.perform(get("/rest/v1/rollouts/{rolloutId}", rollout.getId())).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("id", equalTo(rollout.getId().intValue()))) + .andExpect(jsonPath("status", equalTo("running"))); + } + + @Test + @Description("Testing that pausing the rollout switches the state to paused") + public void pausingRolloutSwitchesIntoPausedState() throws Exception { + // setup + final int amountTargets = 20; + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout")); + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + + // create rollout including the created targets with prefix 'rollout' + final Rollout rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*"); + + // starting rollout + mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()); + + // pausing rollout + mvc.perform(post("/rest/v1/rollouts/{rolloutId}/pause", rollout.getId())).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()); + + // check rollout is in running state + mvc.perform(get("/rest/v1/rollouts/{rolloutId}", rollout.getId())).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("id", equalTo(rollout.getId().intValue()))) + .andExpect(jsonPath("status", equalTo("paused"))); + } + + @Test + @Description("Testing that resuming the rollout switches the state to running") + public void resumingRolloutSwitchesIntoRunningState() throws Exception { + // setup + final int amountTargets = 20; + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout")); + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + + // create rollout including the created targets with prefix 'rollout' + final Rollout rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*"); + + // starting rollout + mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()); + + // pausing rollout + mvc.perform(post("/rest/v1/rollouts/{rolloutId}/pause", rollout.getId())).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()); + + // resume rollout + mvc.perform(post("/rest/v1/rollouts/{rolloutId}/resume", rollout.getId())).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()); + + // check rollout is in running state + mvc.perform(get("/rest/v1/rollouts/{rolloutId}", rollout.getId())).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("id", equalTo(rollout.getId().intValue()))) + .andExpect(jsonPath("status", equalTo("running"))); + } + + @Test + @Description("Testing that an already started rollout cannot be started again and returns bad request") + public void startingAlreadyStartedRolloutReturnsBadRequest() throws Exception { + // setup + final int amountTargets = 20; + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout")); + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + + // create rollout including the created targets with prefix 'rollout' + final Rollout rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*"); + + // starting rollout + mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()); + + // starting rollout - already started should lead into bad request + mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("errorCode", equalTo("hawkbit.server.error.rollout.illegalstate"))); + } + + @Test + @Description("Testing that resuming a rollout which is not started leads to bad request") + public void resumingNotStartedRolloutReturnsBadRequest() throws Exception { + // setup + final int amountTargets = 20; + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout")); + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + + // create rollout including the created targets with prefix 'rollout' + final Rollout rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*"); + + // resume not yet started rollout + mvc.perform(post("/rest/v1/rollouts/{rolloutId}/resume", rollout.getId())).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("errorCode", equalTo("hawkbit.server.error.rollout.illegalstate"))); + } + + @Test + @Description("Testing that starting rollout the first rollout group is in running state") + public void startingRolloutFirstRolloutGroupIsInRunningState() throws Exception { + // setup + final int amountTargets = 10; + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout")); + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + + // create rollout including the created targets with prefix 'rollout' + final Rollout rollout = createRollout("rollout1", 2, dsA.getId(), "controllerId==rollout*"); + + // starting rollout + mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()); + + // retrieve rollout groups from created rollout - 2 groups exists + // (amountTargets / groupSize = 2) + mvc.perform(get("/rest/v1/rollouts/{rolloutId}/deploygroups?sort=ID:ASC", rollout.getId())) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$content", hasSize(2))).andExpect(jsonPath("$total", equalTo(2))) + .andExpect(jsonPath("$content[0].status", equalTo("running"))) + .andExpect(jsonPath("$content[1].status", equalTo("scheduled"))); + } + + @Test + @Description("Testing that a single rollout group can be retrieved") + public void retrieveSingleRolloutGroup() throws Exception { + // setup + final int amountTargets = 10; + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout")); + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + + // create rollout including the created targets with prefix 'rollout' + final Rollout rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*"); + + final RolloutGroup firstGroup = rolloutGroupManagement + .findRolloutGroupsByRolloutId(rollout.getId(), new PageRequest(0, 1, Direction.ASC, "id")).getContent() + .get(0); + + // retrieve single rollout group with known ID + mvc.perform(get("/rest/v1/rollouts/{rolloutId}/deploygroups/{groupId}", rollout.getId(), firstGroup.getId())) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("id", equalTo(firstGroup.getId().intValue()))) + .andExpect(jsonPath("status", equalTo("ready"))).andExpect(jsonPath("name", notNullValue())); + } + + @Test + @Description("Testing that the targets of rollout group can be retrieved") + public void retrieveTargetsFromRolloutGroup() throws Exception { + // setup + final int amountTargets = 10; + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout")); + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + + // create rollout including the created targets with prefix 'rollout' + final Rollout rollout = createRollout("rollout1", 2, dsA.getId(), "controllerId==rollout*"); + + final RolloutGroup firstGroup = rolloutGroupManagement + .findRolloutGroupsByRolloutId(rollout.getId(), new PageRequest(0, 1, Direction.ASC, "id")).getContent() + .get(0); + + // retrieve targets from the first rollout group with known ID + mvc.perform(get("/rest/v1/rollouts/{rolloutId}/deploygroups/{groupId}/targets", rollout.getId(), + firstGroup.getId())).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$content", hasSize(5))).andExpect(jsonPath("$total", equalTo(5))); + } + + @Test + @Description("Testing that the targets of rollout group can be retrieved with rsql query param") + public void retrieveTargetsFromRolloutGroupWithQuery() throws Exception { + // setup + final int amountTargets = 10; + final List targets = targetManagement + .createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout")); + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + + // create rollout including the created targets with prefix 'rollout' + final Rollout rollout = createRollout("rollout1", 2, dsA.getId(), "controllerId==rollout*"); + + final RolloutGroup firstGroup = rolloutGroupManagement + .findRolloutGroupsByRolloutId(rollout.getId(), new PageRequest(0, 1, Direction.ASC, "id")).getContent() + .get(0); + + // retrieve targets from the first rollout group with known ID + mvc.perform( + get("/rest/v1/rollouts/{rolloutId}/deploygroups/{groupId}/targets", rollout.getId(), firstGroup.getId()) + .param("q", "controllerId==" + targets.get(0).getControllerId())) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$content", hasSize(1))).andExpect(jsonPath("$total", equalTo(1))); + } + + @Test + @Description("Testing that the targets of rollout group can be retrieved after the rollout has been started") + public void retrieveTargetsFromRolloutGroupAfterRolloutIsStarted() throws Exception { + // setup + final int amountTargets = 10; + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout")); + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + + // create rollout including the created targets with prefix 'rollout' + final Rollout rollout = createRollout("rollout1", 2, dsA.getId(), "controllerId==rollout*"); + + rolloutManagement.startRollout(rollout); + + final RolloutGroup firstGroup = rolloutGroupManagement + .findRolloutGroupsByRolloutId(rollout.getId(), new PageRequest(0, 1, Direction.ASC, "id")).getContent() + .get(0); + + // retrieve targets from the first rollout group with known ID + mvc.perform(get("/rest/v1/rollouts/{rolloutId}/deploygroups/{groupId}/targets", rollout.getId(), + firstGroup.getId())).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$content", hasSize(5))).andExpect(jsonPath("$total", equalTo(5))); + } + + // TODO + @Test + @Description("Start the rollout in async mode") + public void startingRolloutSwitchesIntoRunningStateAsync() throws Exception { + + final int amountTargets = 1000; + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout")); + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + + // create rollout including the created targets with prefix 'rollout' + final Rollout rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*"); + + // starting rollout + mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId()) + .param(RestConstants.REQUEST_PARAMETER_ASYNC, "true")).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()); + + // check if running + assertThat(doWithTimeout(() -> getRollout(rollout.getId()), result -> success(result), 5000, 100)).isNotNull(); + + } + + @Test + @Description("Testing that rollout paged list with rsql parameter") + public void getRolloutWithRSQLParam() throws Exception { + + final int amountTargetsRollout1 = 25; + final int amountTargetsRollout2 = 25; + final int amountTargetsRollout3 = 25; + final int amountTargetsOther = 25; + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargetsRollout1, "rollout1", "rollout1")); + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargetsRollout2, "rollout2", "rollout2")); + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargetsRollout3, "rollout3", "rollout3")); + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargetsOther, "other1", "other1")); + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + + createRollout("rollout1", 5, dsA.getId(), "controllerId==rollout1*"); + final Rollout rollout2 = createRollout("rollout2", 5, dsA.getId(), "controllerId==rollout2*"); + createRollout("rollout3", 5, dsA.getId(), "controllerId==rollout3*"); + createRollout("other1", 5, dsA.getId(), "controllerId==other1*"); + + mvc.perform(get("/rest/v1/rollouts").param(RestConstants.REQUEST_PARAMETER_SEARCH, "name==*2")) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$content", hasSize(1))).andExpect(jsonPath("$total", equalTo(1))) + .andExpect(jsonPath("$content[0].name", equalTo(rollout2.getName()))); + + mvc.perform(get("/rest/v1/rollouts").param(RestConstants.REQUEST_PARAMETER_SEARCH, "name==rollout*")) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$content", hasSize(3))).andExpect(jsonPath("$total", equalTo(3))); + + mvc.perform(get("/rest/v1/rollouts").param(RestConstants.REQUEST_PARAMETER_SEARCH, "name==*1")) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$content", hasSize(2))).andExpect(jsonPath("$total", equalTo(2))); + + } + + @Test + @Description("Testing that rolloutgroup paged list with rsql parameter") + public void retrieveRolloutGroupsForSpecificRolloutWithRSQLParam() throws Exception { + // setup + final int amountTargets = 20; + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout")); + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + + // create rollout including the created targets with prefix 'rollout' + final Rollout rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*"); + + // retrieve rollout groups from created rollout + mvc.perform(get("/rest/v1/rollouts/{rolloutId}/deploygroups", rollout.getId()) + .param(RestConstants.REQUEST_PARAMETER_SEARCH, "name==group-1")).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$content", hasSize(1))).andExpect(jsonPath("$total", equalTo(1))) + .andExpect(jsonPath("$content[0].name", equalTo("group-1"))); + + mvc.perform(get("/rest/v1/rollouts/{rolloutId}/deploygroups", rollout.getId()) + .param(RestConstants.REQUEST_PARAMETER_SEARCH, "name==group*")).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$content", hasSize(4))).andExpect(jsonPath("$total", equalTo(4))); + + mvc.perform(get("/rest/v1/rollouts/{rolloutId}/deploygroups", rollout.getId()) + .param(RestConstants.REQUEST_PARAMETER_SEARCH, "name==group-1,name==group-2")) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$content", hasSize(2))).andExpect(jsonPath("$total", equalTo(2))); + + } + + // TODO copied code from sp-bic-test + protected T doWithTimeout(final Callable callable, final SuccessCondition successCondition, + final long timeout, final long pollInterval) throws Exception // NOPMD + { + + if (pollInterval < 0) { + throw new IllegalArgumentException("pollInterval must non negative"); + } + + long duration = 0; + Exception exception = null; + T returnValue = null; + while (untilTimeoutReached(timeout, duration)) { + try { + returnValue = callable.call(); + // clear exception + exception = null; + } catch (final Exception ex) { + exception = ex; + } + Thread.sleep(pollInterval); + duration += pollInterval > 0 ? pollInterval : 1; + if (exception == null && successCondition.success(returnValue)) { + return returnValue; + } else { + returnValue = null; + } + } + if (exception != null) { + throw exception; + } + return returnValue; + } + + protected boolean untilTimeoutReached(final long timeout, final long duration) { + return duration <= timeout || timeout < 0; + } + + private void postRollout(final String name, final int groupSize, final long distributionSetId, + final String targetFilterQuery) throws Exception { + mvc.perform(post("/rest/v1/rollouts") + .content(JsonBuilder.rollout(name, "desc", groupSize, distributionSetId, targetFilterQuery, null)) + .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated()).andReturn(); + } + + private Rollout createRollout(final String name, final int amountGroups, final long distributionSetId, + final String targetFilterQuery) { + final Rollout rollout = new Rollout(); + rollout.setDistributionSet(distributionSetManagement.findDistributionSetById(distributionSetId)); + rollout.setName(name); + rollout.setTargetFilterQuery(targetFilterQuery); + return rolloutManagement.createRollout(rollout, amountGroups, new RolloutGroupConditionBuilder() + .successCondition(RolloutGroupSuccessCondition.THRESHOLD, "100").build()); + } + + protected boolean success(final Rollout result) { + if (null != result && result.getStatus() == RolloutStatus.RUNNING) { + return true; + } + return false; + } + + public Rollout getRollout(final Long rolloutId) throws Exception { + return rolloutManagement.findRolloutById(rolloutId); + } + +} diff --git a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/SuccessCondition.java b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/SuccessCondition.java new file mode 100644 index 000000000..2593da27c --- /dev/null +++ b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/SuccessCondition.java @@ -0,0 +1,21 @@ +/** + * Copyright (c) 2011-2015 Bosch Software Innovations GmbH, Germany. All rights reserved. + */ +package org.eclipse.hawkbit.rest.resource; + +/** + * + * @author Dennis Melzer + * + * @param + */ +public interface SuccessCondition { + + /** + * + * @param result + * @return + */ + boolean success(final T result); + +} diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpPermission.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpPermission.java index 2e46d74c7..d105f8d70 100644 --- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpPermission.java +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpPermission.java @@ -23,8 +23,7 @@ import org.springframework.security.core.GrantedAuthority; * The Permissions cover CRUD for two data areas of SP:
*
* XX_Target_CRUD which covers the following entities: {@link Target} entities - * including metadata, {@link TargetTag}s, {@link Action}s, - * {@link TargetRegistrationRule}s
+ * including metadata, {@link TargetTag}s, {@link TargetRegistrationRule}s
* XX_Repository CRUD which covers: {@link DistributionSet}s, * {@link SoftwareModule}s, DS Tags
*

@@ -131,6 +130,11 @@ public final class SpPermission { */ public static final String TENANT_CONFIGURATION = "TENANT_CONFIGURATION"; + /** + * Permission to administrate a rollout management. + */ + public static final String ROLLOUT_MANAGEMENT = "ROLLOUT_MANAGEMENT"; + private SpPermission() { // Constants only } @@ -178,6 +182,12 @@ public final class SpPermission { */ public static final String CONTROLLER_ROLE_ANONYMOUS = "ROLE_CONTROLLER_ANONYMOUS"; + /** + * The role which contains the spring security context in case the + * system is executing code which is necessary to be privileged. + */ + public static final String SYSTEM_ROLE = "ROLE_SYSTEM_CODE"; + /** * The spring security eval expression operator {@code or}. */ @@ -265,8 +275,15 @@ public final class SpPermission { * context contains the anoynmous role or the controller specific role * {@link SpPermission#CONTROLLER_ROLE}. */ - public static final String IS_CONTROLLER = "hasAnyRole('" + CONTROLLER_ROLE_ANONYMOUS + "', '" + CONTROLLER_ROLE - + "')"; + public static final String IS_CONTROLLER = "hasAnyRole('" + CONTROLLER_ROLE_ANONYMOUS + "', '" + + CONTROLLER_ROLE + "')"; + + /** + * Spring security eval hasAnyRole expression to check if the spring + * context contains system code role + * {@link SpringEvalExpressions#SYSTEM_ROLE}. + */ + public static final String IS_SYSTEM_CODE = HAS_AUTH_PREFIX + SYSTEM_ROLE + HAS_AUTH_SUFFIX; /** * Spring security eval hasAuthority expression to check if spring @@ -276,6 +293,21 @@ public final class SpPermission { public static final String HAS_AUTH_CREATE_REPOSITORY_AND_CREATE_TARGET = HAS_AUTH_PREFIX + CREATE_REPOSITORY + HAS_AUTH_SUFFIX + HAS_AUTH_AND + HAS_AUTH_PREFIX + CREATE_TARGET + HAS_AUTH_SUFFIX; + /** + * Spring security eval hasAuthority expression to check if spring + * context contains {@link SpPermission#ROLLOUT_MANAGEMENT} + */ + public static final String HAS_AUTH_ROLLOUT_MANAGEMENT_READ = HAS_AUTH_PREFIX + ROLLOUT_MANAGEMENT + + HAS_AUTH_SUFFIX; + + /** + * Spring security eval hasAuthority expression to check if spring + * context contains {@link SpPermission#ROLLOUT_MANAGEMENT} and + * {@link SpPermission#UPDATE_TARGET}. + */ + public static final String HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE = HAS_AUTH_PREFIX + ROLLOUT_MANAGEMENT + + HAS_AUTH_SUFFIX + HAS_AUTH_AND + HAS_AUTH_PREFIX + UPDATE_TARGET + HAS_AUTH_SUFFIX; + private SpringEvalExpressions() { // utility class } diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java index a3cb56d91..2d8ac52df 100644 --- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java @@ -8,6 +8,8 @@ */ package org.eclipse.hawkbit.security; +import java.util.concurrent.atomic.AtomicInteger; + import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails; import org.eclipse.hawkbit.tenancy.TenantAware; import org.springframework.security.core.Authentication; @@ -27,6 +29,7 @@ import org.springframework.security.core.context.SecurityContextHolder; public class SecurityContextTenantAware implements TenantAware { private static final ThreadLocal TENANT_THREAD_LOCAL = new ThreadLocal<>(); + private static final ThreadLocal RUN_AS_DEPTH = new ThreadLocal<>(); /* * (non-Javadoc) @@ -56,11 +59,21 @@ public class SecurityContextTenantAware implements TenantAware { */ @Override public T runAsTenant(final String tenant, final TenantRunner callable) { + AtomicInteger runAsDepth = RUN_AS_DEPTH.get(); + if (runAsDepth == null) { + runAsDepth = new AtomicInteger(1); + RUN_AS_DEPTH.set(runAsDepth); + } else { + runAsDepth.incrementAndGet(); + } TENANT_THREAD_LOCAL.set(tenant); try { return callable.run(); } finally { - TENANT_THREAD_LOCAL.remove(); + if (runAsDepth.decrementAndGet() <= 0) { + RUN_AS_DEPTH.remove(); + TENANT_THREAD_LOCAL.remove(); + } } } } diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SystemSecurityContext.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SystemSecurityContext.java new file mode 100644 index 000000000..7e3dc8de7 --- /dev/null +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SystemSecurityContext.java @@ -0,0 +1,112 @@ +/** + * 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.security; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Callable; + +import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; +import org.eclipse.hawkbit.tenancy.TenantAware; +import org.eclipse.hawkbit.tenancy.TenantAware.TenantRunner; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextImpl; +import org.springframework.stereotype.Service; + +import com.google.common.base.Throwables; + +/** + * @author Michael Hirsch + * + */ +@Service +public class SystemSecurityContext { + + private static final Logger logger = LoggerFactory.getLogger(SystemSecurityContext.class); + + @Autowired + private TenantAware tenantAware; + + public T runAsSystem(final Callable callable) { + final SecurityContext oldContext = SecurityContextHolder.getContext(); + try { + logger.debug("entering system code execution"); + return tenantAware.runAsTenant(tenantAware.getCurrentTenant(), new TenantRunner() { + @Override + public T run() { + try { + setSystemContext(); + return callable.call(); + } catch (final Exception e) { + throw Throwables.propagate(e); + } + } + }); + + } finally { + SecurityContextHolder.setContext(oldContext); + logger.debug("leaving system code execution"); + } + } + + private static void setSystemContext() { + final SecurityContextImpl securityContextImpl = new SecurityContextImpl(); + securityContextImpl.setAuthentication(new SystemCodeAuthentication()); + SecurityContextHolder.setContext(securityContextImpl); + } + + public static class SystemCodeAuthentication implements Authentication { + + private static final long serialVersionUID = 1L; + private static final List AUTHORITIES = Collections + .singletonList(new SimpleGrantedAuthority(SpringEvalExpressions.SYSTEM_ROLE)); + + @Override + public String getName() { + return null; + } + + @Override + public Collection getAuthorities() { + return AUTHORITIES; + } + + @Override + public Object getCredentials() { + return null; + } + + @Override + public Object getDetails() { + return null; + } + + @Override + public Object getPrincipal() { + return null; + } + + @Override + public boolean isAuthenticated() { + return true; + } + + @Override + public void setAuthenticated(final boolean isAuthenticated) throws IllegalArgumentException { + } + } +} diff --git a/hawkbit-ui/pom.xml b/hawkbit-ui/pom.xml index f0fa058cf..0bd083e36 100644 --- a/hawkbit-ui/pom.xml +++ b/hawkbit-ui/pom.xml @@ -30,6 +30,7 @@ ${project.build.directory}/classes/VAADIN/widgetsets ${project.build.directory}/classes/VAADIN/widgetsets + src/main/resources true false @@ -212,6 +213,16 @@ org.vaadin.addons tokenfield
+ + + org.vaadin.alump.distributionbar + dbar-addon + + + org.vaadin.addons + contextmenu + + diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/repository/SpPermissionChecker.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/repository/SpPermissionChecker.java index 0df7af872..1bb103e51 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/repository/SpPermissionChecker.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/repository/SpPermissionChecker.java @@ -139,4 +139,42 @@ public class SpPermissionChecker implements Serializable { return hasReadDistributionPermission() && permissionService.hasPermission(SpPermission.DELETE_REPOSITORY); } + /** + * Gets the SP rollout create permission. + * + * @return permission for rollout update + */ + public boolean hasRolloutUpdatePermission() { + return hasUpdateTargetPermission() && hasReadDistributionPermission() + && permissionService.hasPermission(SpPermission.ROLLOUT_MANAGEMENT); + } + + /** + * Gets the SP rollout create permission. + * + * @return permission for rollout create + */ + public boolean hasRolloutCreatePermission() { + return hasUpdateTargetPermission() && hasReadDistributionPermission() + && permissionService.hasPermission(SpPermission.ROLLOUT_MANAGEMENT); + } + + /** + * + * Gets the SP rollout read permission. + * + * @return Gets the SP rollout read permission. + */ + public boolean hasRolloutReadPermission() { + return permissionService.hasPermission(SpPermission.ROLLOUT_MANAGEMENT); + } + + /** + * Gets the SP rollout targets read permission. + * + * @return permission to read rollout targets + */ + public boolean hasRolloutTargetsReadPermission() { + return hasTargetReadPermission() && permissionService.hasPermission(SpPermission.ROLLOUT_MANAGEMENT); + } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/AppWidgetSet.gwt.xml b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/AppWidgetSet.gwt.xml index 8811defaf..020ce7a77 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/AppWidgetSet.gwt.xml +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/AppWidgetSet.gwt.xml @@ -21,4 +21,10 @@ + + + + + + diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/UploadArtifactViewMenuItem.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/UploadArtifactViewMenuItem.java index 7f31f75b1..05fbbd1d3 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/UploadArtifactViewMenuItem.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/UploadArtifactViewMenuItem.java @@ -25,7 +25,7 @@ import com.vaadin.server.Resource; * */ @Component -@Order(400) +@Order(500) public class UploadArtifactViewMenuItem implements DashboardMenuItem { private static final long serialVersionUID = 4096851897640769726L; diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/components/ProxyTarget.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/components/ProxyTarget.java index 9b3b22eed..9a374f980 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/components/ProxyTarget.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/components/ProxyTarget.java @@ -11,6 +11,7 @@ package org.eclipse.hawkbit.ui.components; import java.net.URI; import java.security.SecureRandom; +import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetIdName; @@ -53,6 +54,8 @@ public class ProxyTarget extends Target { private String modifiedByUser; + private Status status; + /** * @param controllerId */ @@ -297,4 +300,19 @@ public class ProxyTarget extends Target { public void setPollStatusToolTip(final String pollStatusToolTip) { this.pollStatusToolTip = pollStatusToolTip; } + + /** + * @return the status + */ + public Status getStatus() { + return status; + } + + /** + * @param status + * the status to set + */ + public void setStatus(final Status status) { + this.status = status; + } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/DistributionsViewMenuItem.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/DistributionsViewMenuItem.java index f47e19e76..f8650e509 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/DistributionsViewMenuItem.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/DistributionsViewMenuItem.java @@ -26,7 +26,7 @@ import com.vaadin.server.Resource; * */ @Component -@Order(300) +@Order(400) public class DistributionsViewMenuItem implements DashboardMenuItem { private static final long serialVersionUID = -4048522766974227222L; diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/documentation/DocumentationPageLink.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/documentation/DocumentationPageLink.java index f103b86bb..b4064ef68 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/documentation/DocumentationPageLink.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/documentation/DocumentationPageLink.java @@ -45,7 +45,10 @@ public enum DocumentationPageLink { TARGET_SECURITY_TOKEN("security.html", DocumentationUtil.DEVELOPERGUIDE, "concepts"), // userguide/targetfilter - TARGET_FILTER_VIEW("targetfilter.html", DocumentationUtil.USERGUIDE); + TARGET_FILTER_VIEW("targetfilter.html", DocumentationUtil.USERGUIDE), + + // userguide/ROLLOUT + ROLLOUT_VIEW("rollout.html", DocumentationUtil.USERGUIDE); private static final String ROOT_PATH = "../documentation"; 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 c122b19ac..78e9ad3e3 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 @@ -186,7 +186,7 @@ public class CreateOrUpdateFilterTable extends Table { columnList.add(new TableColumn(SPUILabelDefinitions.INSTALLED_DISTRIBUTION_NAME_VER, i18n.get("header.installed.ds"), 0.125F)); columnList.add(new TableColumn(SPUILabelDefinitions.VAR_DESC, i18n.get("header.description"), 0.1F)); - columnList.add(new TableColumn(SPUILabelDefinitions.STATUS_ICON, i18n.get("header.target.status"), 0.1F)); + columnList.add(new TableColumn(SPUILabelDefinitions.STATUS_ICON, i18n.get("header.status"), 0.1F)); return columnList; } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/FilterManagementViewMenuItem.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/FilterManagementViewMenuItem.java index e0edf541e..3926f53e3 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/FilterManagementViewMenuItem.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/FilterManagementViewMenuItem.java @@ -26,7 +26,7 @@ import com.vaadin.server.Resource; * */ @Component -@Order(200) +@Order(300) public class FilterManagementViewMenuItem implements DashboardMenuItem { private static final long serialVersionUID = -1272853053031512243L; diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/FilterQueryValidation.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/FilterQueryValidation.java index 385cb9be1..d3ec6245d 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/FilterQueryValidation.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/FilterQueryValidation.java @@ -63,20 +63,20 @@ public final class FilterQueryValidation { setExceptionDetails(new Exception(ex.getCause().getCause()), expectedTokens, result, tokenDesc); result.setMessage(getCustomMessage(ex.getCause().getMessage(), result.getExpectedTokens())); result.setIsValidationFailed(Boolean.TRUE); - LOGGER.info("Syntax exception on parsing :", ex); + LOGGER.trace("Syntax exception on parsing :", ex); } catch (final RSQLParserException ex) { setExceptionDetails(ex, expectedTokens, result, tokenDesc); result.setMessage(getCustomMessage(ex.getMessage(), result.getExpectedTokens())); result.setIsValidationFailed(Boolean.TRUE); - LOGGER.info("Exception on parsing :", ex); + LOGGER.trace("Exception on parsing :", ex); } catch (final IllegalArgumentException ex) { result.setMessage(getCustomMessage(ex.getMessage(), null)); result.setIsValidationFailed(Boolean.TRUE); - LOGGER.info("Illegal argument on parsing :", ex); + LOGGER.trace("Illegal argument on parsing :", ex); } catch (final RSQLParameterUnsupportedFieldException ex) { result.setMessage(getCustomMessage(ex.getMessage(), null)); result.setIsValidationFailed(Boolean.TRUE); - LOGGER.info("Unsupported field on parsing :", ex); + LOGGER.trace("Unsupported field on parsing :", ex); } return result; diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterBeanQuery.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterBeanQuery.java index 023400139..7dbe3467e 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterBeanQuery.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterBeanQuery.java @@ -97,6 +97,7 @@ public class TargetFilterBeanQuery extends AbstractBeanQuery proxyTarFilter.setCreatedBy(HawkbitCommonUtil.getIMUser(tarFilterQuery.getCreatedBy())); proxyTarFilter.setModifiedDate(SPDateTimeUtil.getFormattedDate(tarFilterQuery.getLastModifiedAt())); proxyTarFilter.setLastModifiedBy(HawkbitCommonUtil.getIMUser(tarFilterQuery.getLastModifiedBy())); + proxyTarFilter.setQuery(tarFilterQuery.getQuery()); proxyTargetFilter.add(proxyTarFilter); } return proxyTargetFilter; 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 b857cc026..2455eab64 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 @@ -142,6 +142,15 @@ public class ActionHistoryTable extends TreeTable implements Handler { createContainer(); setContainerDataSource(hierarchicalContainer); addGeneratedColumns(); + setColumnExpandRatioForMinimisedTable(); + } + + private void setColumnExpandRatioForMinimisedTable() { + setColumnExpandRatio(SPUIDefinitions.ACTION_HIS_TBL_ACTION_ID, 0.1f); + setColumnExpandRatio(SPUIDefinitions.ACTION_HIS_TBL_DIST, 0.3f); + setColumnExpandRatio(SPUIDefinitions.ACTION_HIS_TBL_STATUS, 0.15f); + setColumnExpandRatio(SPUIDefinitions.ACTION_HIS_TBL_DATETIME, 0.3f); + setColumnExpandRatio(SPUIDefinitions.ACTION_HIS_TBL_FORCED, 0.15f); } private void initializeTableSettings() { @@ -190,7 +199,7 @@ public class ActionHistoryTable extends TreeTable implements Handler { hierarchicalContainer.addContainerProperty(SPUIDefinitions.ACTION_HIS_TBL_STATUS_HIDDEN, Action.Status.class, null); hierarchicalContainer.addContainerProperty(SPUIDefinitions.ACTION_HIS_TBL_MSGS_HIDDEN, List.class, null); - + hierarchicalContainer.addContainerProperty(SPUIDefinitions.ACTION_HIS_TBL_ROLLOUT_NAME, String.class, null); } /** @@ -209,6 +218,7 @@ public class ActionHistoryTable extends TreeTable implements Handler { visibleColumnIds.add(SPUIDefinitions.ACTION_HIS_TBL_STATUS); visibleColumnIds.add(SPUIDefinitions.ACTION_HIS_TBL_FORCED); if (managementUIState.isActionHistoryMaximized()) { + visibleColumnIds.add(SPUIDefinitions.ACTION_HIS_TBL_ROLLOUT_NAME); visibleColumnIds.add(SPUIDefinitions.ACTION_HIS_TBL_MSGS); visibleColumnIds.add(1, SPUIDefinitions.ACTION_HIS_TBL_ACTION_ID); } @@ -265,14 +275,14 @@ public class ActionHistoryTable extends TreeTable implements Handler { final Item item = hierarchicalContainer.addItem(actionWithStatusCount.getActionId()); - item.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_STATUS_HIDDEN) - .setValue(actionWithStatusCount.getActionStatus()); + item.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_STATUS_HIDDEN).setValue( + actionWithStatusCount.getActionStatus()); /* * add action id. */ - item.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_ACTION_ID) - .setValue(actionWithStatusCount.getActionId().toString()); + item.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_ACTION_ID).setValue( + actionWithStatusCount.getActionId().toString()); /* * add active/inactive status to the item which will be used in * Column generator to generate respective icon @@ -284,24 +294,28 @@ public class ActionHistoryTable extends TreeTable implements Handler { * add action Id to the item which will be used for fetching child * items ( previous action status ) during expand */ - item.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_ACTION_ID_HIDDEN) - .setValue(actionWithStatusCount.getActionId()); + item.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_ACTION_ID_HIDDEN).setValue( + actionWithStatusCount.getActionId()); /* * add distribution name to the item which will be displayed in the * table. The name should not exceed certain limit. */ - item.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_DIST).setValue(HawkbitCommonUtil - .getFormattedText(actionWithStatusCount.getDsName() + ":" + actionWithStatusCount.getDsVersion())); + item.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_DIST).setValue( + HawkbitCommonUtil.getFormattedText(actionWithStatusCount.getDsName() + ":" + + actionWithStatusCount.getDsVersion())); item.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_FORCED).setValue(action); /* Default no child */ ((Hierarchical) hierarchicalContainer).setChildrenAllowed(actionWithStatusCount.getActionId(), false); item.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_DATETIME) - .setValue(SPDateTimeUtil.getFormattedDate((actionWithStatusCount.getActionLastModifiedAt() != null) - ? actionWithStatusCount.getActionLastModifiedAt() - : actionWithStatusCount.getActionCreatedAt())); + .setValue( + SPDateTimeUtil.getFormattedDate((actionWithStatusCount.getActionLastModifiedAt() != null) ? actionWithStatusCount + .getActionLastModifiedAt() : actionWithStatusCount.getActionCreatedAt())); + + item.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_ROLLOUT_NAME).setValue( + actionWithStatusCount.getRolloutName()); if (actionWithStatusCount.getActionStatusCount() > 0) { ((Hierarchical) hierarchicalContainer).setChildrenAllowed(actionWithStatusCount.getActionId(), true); @@ -338,9 +352,6 @@ public class ActionHistoryTable extends TreeTable implements Handler { return getForcedColumn(itemId); } }); - setColumnExpandRatio(SPUIDefinitions.ACTION_HIS_TBL_DIST, 0.4f); - setColumnExpandRatio(SPUIDefinitions.ACTION_HIS_TBL_STATUS, 0.2f); - setColumnExpandRatio(SPUIDefinitions.ACTION_HIS_TBL_DATETIME, 0.4f); } /** @@ -368,14 +379,21 @@ public class ActionHistoryTable extends TreeTable implements Handler { * @return */ private Component getActiveColumn(final Object itemId) { - final String activeValue = (String) hierarchicalContainer.getItem(itemId) - .getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_ACTIVE_HIDDEN).getValue(); + final Action.Status status = (Action.Status) hierarchicalContainer.getItem(itemId) + .getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_STATUS_HIDDEN).getValue(); + String activeValue; + if (status == Action.Status.SCHEDULED) { + activeValue = Action.Status.SCHEDULED.toString().toLowerCase(); + } else { + activeValue = (String) hierarchicalContainer.getItem(itemId) + .getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_ACTIVE_HIDDEN).getValue(); + } final String distName = (String) hierarchicalContainer.getItem(itemId) .getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_DIST).getValue(); - final Label activeStatusIcon = createActiveStatusLabel(activeValue, + final Label activeStatusIcon = createActiveStatusLabel( + activeValue, (Action.Status) hierarchicalContainer.getItem(itemId) - .getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_STATUS_HIDDEN) - .getValue() == Action.Status.ERROR); + .getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_STATUS_HIDDEN).getValue() == Action.Status.ERROR); activeStatusIcon.setId(new StringJoiner(".").add(distName).add(itemId.toString()) .add(SPUIDefinitions.ACTION_HIS_TBL_ACTIVE).add(activeValue).toString()); return activeStatusIcon; @@ -429,14 +447,14 @@ public class ActionHistoryTable extends TreeTable implements Handler { */ childItem.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_ACTIVE_HIDDEN).setValue(""); - childItem.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_DIST) - .setValue(HawkbitCommonUtil.getFormattedText(action.getDistributionSet().getName() + ":" + childItem.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_DIST).setValue( + HawkbitCommonUtil.getFormattedText(action.getDistributionSet().getName() + ":" + action.getDistributionSet().getVersion())); - childItem.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_DATETIME) - .setValue(SPDateTimeUtil.getFormattedDate(actionStatus.getCreatedAt())); - childItem.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_STATUS_HIDDEN) - .setValue(actionStatus.getStatus()); + childItem.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_DATETIME).setValue( + SPDateTimeUtil.getFormattedDate(actionStatus.getCreatedAt())); + childItem.getItemProperty(SPUIDefinitions.ACTION_HIS_TBL_STATUS_HIDDEN).setValue( + actionStatus.getStatus()); showOrHideMessage(childItem, actionStatus); /* No further child items allowed for the child items */ ((Hierarchical) hierarchicalContainer).setChildrenAllowed(childId, false); @@ -512,6 +530,10 @@ public class ActionHistoryTable extends TreeTable implements Handler { label.setStyleName(statusIconPending); label.setDescription(i18n.get("label.download")); label.setValue(FontAwesome.CLOUD_DOWNLOAD.getHtml()); + } else if (Action.Status.SCHEDULED == status) { + label.setStyleName(statusIconPending); + label.setDescription(i18n.get("label.scheduled")); + label.setValue(FontAwesome.BULLSEYE.getHtml()); } else { label.setDescription(""); label.setValue(""); @@ -539,11 +561,13 @@ public class ActionHistoryTable extends TreeTable implements Handler { if (actionWithActiveStatus.isHitAutoForceTime(currentTimeMillis)) { autoForceLabel.setDescription("autoforced"); autoForceLabel.setStyleName(STATUS_ICON_GREEN); - autoForceLabel.setDescription("auto forced since " + SPDateTimeUtil - .getDurationFormattedString(actionWithActiveStatus.getForcedTime(), currentTimeMillis, i18n)); + autoForceLabel.setDescription("auto forced since " + + SPDateTimeUtil.getDurationFormattedString(actionWithActiveStatus.getForcedTime(), + currentTimeMillis, i18n)); } else { - autoForceLabel.setDescription("auto forcing in " + SPDateTimeUtil - .getDurationFormattedString(currentTimeMillis, actionWithActiveStatus.getForcedTime(), i18n)); + autoForceLabel.setDescription("auto forcing in " + + SPDateTimeUtil.getDurationFormattedString(currentTimeMillis, + actionWithActiveStatus.getForcedTime(), i18n)); autoForceLabel.setStyleName("statusIconPending"); autoForceLabel.setValue(FontAwesome.HISTORY.getHtml()); } @@ -560,7 +584,10 @@ public class ActionHistoryTable extends TreeTable implements Handler { private Label createActiveStatusLabel(final String activeValue, final boolean endedWithError) { final Label label = SPUIComponentProvider.getLabel("", SPUILabelDefinitions.SP_LABEL_SIMPLE); label.setContentMode(ContentMode.HTML); - if (SPUIDefinitions.ACTIVE.equals(activeValue)) { + if (SPUIDefinitions.SCHEDULED.equals(activeValue)) { + label.setDescription("Scheduled"); + label.setValue(FontAwesome.BULLSEYE.getHtml()); + } else if (SPUIDefinitions.ACTIVE.equals(activeValue)) { label.setDescription("Active"); label.setStyleName("statusIconActive"); } else if (SPUIDefinitions.IN_ACTIVE.equals(activeValue)) { @@ -605,11 +632,19 @@ public class ActionHistoryTable extends TreeTable implements Handler { } }); setVisibleColumns(getVisbleColumns().toArray()); - /* set messages column can expand the rest of the available space */ - setColumnExpandRatio(SPUIDefinitions.ACTION_HIS_TBL_DIST, 0.3f); - setColumnExpandRatio(SPUIDefinitions.ACTION_HIS_TBL_MSGS, 0.7f); - setColumnExpandRatio(SPUIDefinitions.ACTION_HIS_TBL_DATETIME, 0.3f); + setColumnExpantRatioOnTableMaximize(); + } + private void setColumnExpantRatioOnTableMaximize() { + /* set messages column can expand the rest of the available space */ + setColumnExpandRatio(SPUIDefinitions.ACTION_HIS_TBL_ACTIVE, 0.1f); + setColumnExpandRatio(SPUIDefinitions.ACTION_HIS_TBL_ACTION_ID, 0.1f); + setColumnExpandRatio(SPUIDefinitions.ACTION_HIS_TBL_STATUS, 0.1f); + setColumnExpandRatio(SPUIDefinitions.ACTION_HIS_TBL_DIST, 0.2f); + setColumnExpandRatio(SPUIDefinitions.ACTION_HIS_TBL_FORCED, 0.1f); + setColumnExpandRatio(SPUIDefinitions.ACTION_HIS_TBL_ROLLOUT_NAME, 0.1f); + setColumnExpandRatio(SPUIDefinitions.ACTION_HIS_TBL_MSGS, 0.35f); + setColumnExpandRatio(SPUIDefinitions.ACTION_HIS_TBL_DATETIME, 0.15f); } /** @@ -668,8 +703,7 @@ public class ActionHistoryTable extends TreeTable implements Handler { managementUIState.setActionHistoryMaximized(false); removeGeneratedColumn(SPUIDefinitions.ACTION_HIS_TBL_MSGS); setVisibleColumns(getVisbleColumns().toArray()); - setColumnExpandRatio(SPUIDefinitions.ACTION_HIS_TBL_DIST, 0.6f); - setColumnExpandRatio(SPUIDefinitions.ACTION_HIS_TBL_DATETIME, 0.4f); + setColumnExpandRatioForMinimisedTable(); } @Override @@ -742,8 +776,9 @@ public class ActionHistoryTable extends TreeTable implements Handler { private void confirmAndForceQuitAction(final Long actionId) { /* Display the confirmation */ - final ConfirmationDialog confirmDialog = new ConfirmationDialog(i18n.get("caption.forcequit.action.confirmbox"), - i18n.get("message.forcequit.action.confirm"), i18n.get("button.ok"), i18n.get("button.cancel"), ok -> { + final ConfirmationDialog confirmDialog = new ConfirmationDialog( + i18n.get("caption.forcequit.action.confirmbox"), i18n.get("message.forcequit.action.confirm"), + i18n.get("button.ok"), i18n.get("button.cancel"), ok -> { if (ok) { final boolean cancelResult = forceQuitActiveAction(actionId); if (cancelResult) { @@ -758,7 +793,7 @@ public class ActionHistoryTable extends TreeTable implements Handler { notification.displayValidationError(i18n.get("message.forcequit.action.failed")); } } - } , FontAwesome.WARNING); + }, FontAwesome.WARNING); UI.getCurrent().addWindow(confirmDialog.getWindow()); confirmDialog.getWindow().bringToFront(); } @@ -845,8 +880,8 @@ public class ActionHistoryTable extends TreeTable implements Handler { if (managementUIState.getDistributionTableFilters().getPinnedTargetId().isPresent() && null != managementUIState.getDistributionTableFilters().getPinnedTargetId().get()) { - final String alreadyPinnedControllerId = managementUIState.getDistributionTableFilters().getPinnedTargetId() - .get(); + final String alreadyPinnedControllerId = managementUIState.getDistributionTableFilters() + .getPinnedTargetId().get(); // if the current target is pinned publish a pin event again if (null != alreadyPinnedControllerId && alreadyPinnedControllerId.equals(target.getControllerId())) { eventBus.publish(this, PinUnpinEvent.PIN_TARGET); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/event/DistributionTagDropEvent.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/event/DistributionTagDropEvent.java index 6370a9883..c87a5eec9 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/event/DistributionTagDropEvent.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/event/DistributionTagDropEvent.java @@ -18,7 +18,6 @@ import org.eclipse.hawkbit.repository.SpPermissionChecker; import org.eclipse.hawkbit.repository.model.DistributionSetIdName; import org.eclipse.hawkbit.repository.model.DistributionSetTagAssigmentResult; import org.eclipse.hawkbit.ui.management.state.DistributionTableFilters; -import org.eclipse.hawkbit.ui.management.state.ManagementUIState; import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil; import org.eclipse.hawkbit.ui.utils.I18N; import org.eclipse.hawkbit.ui.utils.SPUIComponetIdProvider; @@ -69,9 +68,6 @@ public class DistributionTagDropEvent implements DropHandler { @Autowired private ManagementViewAcceptCriteria managementViewAcceptCriteria; - @Autowired - private ManagementUIState managementUIState; - private static final String ITEMID = "itemId"; @Override @@ -90,8 +86,8 @@ public class DistributionTagDropEvent implements DropHandler { private Boolean isNoTagAssigned(final DragAndDropEvent event) { final String tagName = ((DragAndDropWrapper) (event.getTargetDetails().getTarget())).getData().toString(); if (tagName.equals(SPUIDefinitions.DISTRIBUTION_TAG_BUTTON)) { - notification.displayValidationError( - i18n.get("message.tag.cannot.be.assigned", new Object[] { i18n.get("label.no.tag.assigned") })); + notification.displayValidationError(i18n.get("message.tag.cannot.be.assigned", + new Object[] { i18n.get("label.no.tag.assigned") })); return false; } return true; @@ -176,8 +172,8 @@ public class DistributionTagDropEvent implements DropHandler { SPUIDefinitions.DISTRIBUTION_TAG_ID_PREFIXS); final List tagsClickedList = distFilterParameters.getDistSetTags(); - final DistributionSetTagAssigmentResult result = distributionSetManagement.toggleTagAssignment(distributionList, - distTagName); + final DistributionSetTagAssigmentResult result = distributionSetManagement.toggleTagAssignment( + distributionList, distTagName); notification.displaySuccess(HawkbitCommonUtil.getDistributionTagAssignmentMsg(distTagName, result, i18n)); if (result.getUnassigned() >= 1 && !tagsClickedList.isEmpty()) { diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/footer/ActionTypeOptionGroupLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/footer/ActionTypeOptionGroupLayout.java index 9269e435f..65d4cdf4a 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/footer/ActionTypeOptionGroupLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/footer/ActionTypeOptionGroupLayout.java @@ -127,8 +127,8 @@ public class ActionTypeOptionGroupLayout extends HorizontalLayout { forcedTimeDateField.setStyleName("dist-window-forcedtime"); final TimeZone tz = SPDateTimeUtil.getBrowserTimeZone(); - forcedTimeDateField.setValue( - Date.from(LocalDateTime.now().plusWeeks(2).atZone(SPDateTimeUtil.getTimeZoneId(tz)).toInstant())); + forcedTimeDateField.setValue(Date.from(LocalDateTime.now().plusWeeks(2) + .atZone(SPDateTimeUtil.getTimeZoneId(tz)).toInstant())); forcedTimeDateField.setImmediate(true); forcedTimeDateField.setTimeZone(tz); forcedTimeDateField.setLocale(i18n.getLocale()); @@ -145,7 +145,7 @@ public class ActionTypeOptionGroupLayout extends HorizontalLayout { actionTypeOptionGroup.select(ActionTypeOption.FORCED); } - enum ActionTypeOption { + public enum ActionTypeOption { FORCED(ActionType.FORCED), SOFT(ActionType.SOFT), AUTO_FORCED(ActionType.TIMEFORCED); private final ActionType actionType; 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 41d8ecab6..62276346e 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 @@ -73,8 +73,8 @@ import org.vaadin.spring.events.EventBus; import org.vaadin.spring.events.EventScope; import org.vaadin.spring.events.annotation.EventBusListenerMethod; -import com.google.common.base.Strings; import com.google.common.collect.Iterables; +import com.google.gwt.thirdparty.guava.common.base.Strings; import com.vaadin.data.Container; import com.vaadin.data.Item; import com.vaadin.event.Action; @@ -249,7 +249,7 @@ public class TargetTable extends AbstractTable implements Handler { /* * (non-Javadoc) - * + * * @see * org.eclipse.hawkbit.server.ui.common.table.AbstractTable#getTableId() */ @@ -260,7 +260,7 @@ public class TargetTable extends AbstractTable implements Handler { /* * (non-Javadoc) - * + * * @see * org.eclipse.hawkbit.server.ui.common.table.AbstractTable#createContainer( * ) @@ -285,7 +285,7 @@ public class TargetTable extends AbstractTable implements Handler { /* * (non-Javadoc) - * + * * @see hawkbit.server.ui.common.table.AbstractTable#addContainerProperties * (com.vaadin.data.Container ) */ @@ -312,7 +312,7 @@ public class TargetTable extends AbstractTable implements Handler { /* * (non-Javadoc) - * + * * @see org.eclipse.hawkbit.server.ui.common.table.AbstractTable# * addCustomGeneratedColumns () */ @@ -326,7 +326,7 @@ public class TargetTable extends AbstractTable implements Handler { /* * (non-Javadoc) - * + * * @see org.eclipse.hawkbit.server.ui.common.table.AbstractTable# * isFirstRowSelectedOnLoad () */ @@ -338,7 +338,7 @@ public class TargetTable extends AbstractTable implements Handler { /* * (non-Javadoc) - * + * * @see hawkbit.server.ui.common.table.AbstractTable#getItemIdToSelect() */ @Override @@ -352,7 +352,7 @@ public class TargetTable extends AbstractTable implements Handler { /* * (non-Javadoc) - * + * * @see * org.eclipse.hawkbit.server.ui.common.table.AbstractTable#onValueChange() */ @@ -377,7 +377,7 @@ public class TargetTable extends AbstractTable implements Handler { /* * (non-Javadoc) - * + * * @see * org.eclipse.hawkbit.server.ui.common.table.AbstractTable#isMaximized() */ @@ -388,7 +388,7 @@ public class TargetTable extends AbstractTable implements Handler { /* * (non-Javadoc) - * + * * @see hawkbit.server.ui.common.table.AbstractTable#getTableVisibleColumns * () */ @@ -415,7 +415,7 @@ public class TargetTable extends AbstractTable implements Handler { /* * (non-Javadoc) - * + * * @see hawkbit.server.ui.common.table.AbstractTable#getTableDropHandler() */ @Override @@ -1154,5 +1154,4 @@ public class TargetTable extends AbstractTable implements Handler { private boolean isFilteredByTags() { return !managementUIState.getTargetTableFilters().getClickedTargetTags().isEmpty(); } - -} +} \ No newline at end of file diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/AbstractSimpleTable.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/AbstractSimpleTable.java new file mode 100644 index 000000000..db0b468cc --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/AbstractSimpleTable.java @@ -0,0 +1,116 @@ +/** + * 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.rollout; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; +import org.eclipse.hawkbit.ui.utils.TableColumn; + +import com.vaadin.data.Container; +import com.vaadin.ui.Table; +import com.vaadin.ui.themes.ValoTheme; + +/** + * Abstract table class. + * + */ +public abstract class AbstractSimpleTable extends Table { + + private static final long serialVersionUID = 4856562746502217630L; + + /** + * Initialize the components. + */ + protected void init() { + addStyleName("sp-table rollout-table"); + setSizeFull(); + setImmediate(true); + setHeight(100.0f, Unit.PERCENTAGE); + addStyleName(ValoTheme.TABLE_NO_VERTICAL_LINES); + addStyleName(ValoTheme.TABLE_SMALL); + setSortEnabled(false); + setId(getTableId()); + addCustomGeneratedColumns(); + addNewContainerDS(); + setColumnProperties(); + addValueChangeListener(event -> onValueChange()); + setPageLength(SPUIDefinitions.PAGE_SIZE); + setSelectable(false); + setColumnCollapsingAllowed(true); + setCollapsiblecolumns(); + } + + private void setColumnProperties() { + final List columnList = getTableVisibleColumns(); + final List columnIds = new ArrayList<>(); + for (final TableColumn column : columnList) { + setColumnHeader(column.getColumnPropertyId(), column.getColumnHeader()); + setColumnExpandRatio(column.getColumnPropertyId(), column.getExpandRatio()); + columnIds.add(column.getColumnPropertyId()); + } + setVisibleColumns(columnIds.toArray()); + } + + private void addNewContainerDS() { + final Container container = createContainer(); + addContainerProperties(container); + setContainerDataSource(container); + int size = 0; + if (container != null) { + size = container.size(); + } + if (size == 0) { + setData(SPUIDefinitions.NO_DATA); + } + } + + /** + * Based on table state (max/min) columns to be shown are returned. + * + * @return List list of visible columns + */ + protected abstract List getTableVisibleColumns(); + + /** + * Create container of the data to be displayed by the table. + */ + protected abstract Container createContainer(); + + /** + * Add container properties to the container passed in the reference. + * + * @param container + * reference of {@link Container} + */ + protected abstract void addContainerProperties(Container container); + + /** + * Get Id of the table. + * + * @return Id. + */ + protected abstract String getTableId(); + + /** + * On select of row. + */ + protected abstract void onValueChange(); + + /** + * Add any generated columns if required. + */ + protected abstract void addCustomGeneratedColumns(); + + /** + * Set list of columns collapsible. + */ + protected abstract void setCollapsiblecolumns(); +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/AbstractSimpleTableHeader.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/AbstractSimpleTableHeader.java new file mode 100644 index 000000000..c51c02fe4 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/AbstractSimpleTableHeader.java @@ -0,0 +1,273 @@ +/** + * 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.rollout; + +import org.eclipse.hawkbit.ui.components.SPUIButton; +import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; +import org.eclipse.hawkbit.ui.decorators.SPUIButtonStyleSmallNoBorder; +import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; +import org.eclipse.hawkbit.ui.utils.SPUILabelDefinitions; +import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions; + +import com.google.gwt.thirdparty.guava.common.base.Strings; +import com.vaadin.server.FontAwesome; +import com.vaadin.ui.AbstractTextField.TextChangeEventMode; +import com.vaadin.ui.Alignment; +import com.vaadin.ui.Button; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.TextField; +import com.vaadin.ui.VerticalLayout; + +/** + * + * Abstract table header. + * + */ +public abstract class AbstractSimpleTableHeader extends VerticalLayout { + + private static final long serialVersionUID = -24429876573255519L; + + private HorizontalLayout headerCaptionLayout; + + private TextField searchField; + + private SPUIButton searchResetIcon; + + private Button addButton; + + private Button closeButton; + + protected void init() { + createComponents(); + buildLayout(); + restoreState(); + } + + private void restoreState() { + final String onLoadSearchBoxValue = onLoadSearchBoxValue(); + if (!Strings.isNullOrEmpty(onLoadSearchBoxValue)) { + openSearchTextField(); + searchField.setValue(onLoadSearchBoxValue); + } + restoreCaption(); + } + + private void createComponents() { + headerCaptionLayout = getHeaderCaptionLayout(); + if (isRollout()) { + searchField = createSearchField(); + searchResetIcon = createSearchResetIcon(); + addButton = createAddButton(); + } + closeButton = createCloseButton(); + } + + private void buildLayout() { + final HorizontalLayout titleFilterIconsLayout = createHeaderFilterIconLayout(); + titleFilterIconsLayout.addComponents(headerCaptionLayout); + + if (isAllowSearch() && isRollout()) { + titleFilterIconsLayout.addComponents(searchField, searchResetIcon); + titleFilterIconsLayout.setExpandRatio(headerCaptionLayout, 0.3F); + titleFilterIconsLayout.setExpandRatio(searchField, 0.7F); + } + if (hasCreatePermission() && isRollout()) { + titleFilterIconsLayout.addComponent(addButton); + titleFilterIconsLayout.setComponentAlignment(addButton, Alignment.TOP_LEFT); + } + if (showCloseButton()) { + titleFilterIconsLayout.addComponent(closeButton); + titleFilterIconsLayout.setComponentAlignment(closeButton, Alignment.TOP_RIGHT); + } + + titleFilterIconsLayout.setHeight("40px"); + addComponent(titleFilterIconsLayout); + addStyleName("bordered-layout"); + addStyleName("no-border-bottom"); + } + + private HorizontalLayout createHeaderFilterIconLayout() { + final HorizontalLayout titleFilterIconsLayout = new HorizontalLayout(); + titleFilterIconsLayout.addStyleName(SPUIStyleDefinitions.WIDGET_TITLE); + titleFilterIconsLayout.setSpacing(false); + titleFilterIconsLayout.setMargin(false); + titleFilterIconsLayout.setSizeFull(); + return titleFilterIconsLayout; + } + + private TextField createSearchField() { + final TextField textField = SPUIComponentProvider.getTextField("filter-box", "text-style filter-box-hide", + false, "", "", false, SPUILabelDefinitions.TEXT_FIELD_MAX_LENGTH); + textField.setId(getSearchBoxId()); + textField.setWidth(100.0f, Unit.PERCENTAGE); + textField.addTextChangeListener(event -> searchBy(event.getText())); + textField.setTextChangeEventMode(TextChangeEventMode.LAZY); + textField.setTextChangeTimeout(1000); + return textField; + } + + private SPUIButton createSearchResetIcon() { + final SPUIButton button = (SPUIButton) SPUIComponentProvider.getButton(getSearchRestIconId(), "", "", null, + false, FontAwesome.SEARCH, SPUIButtonStyleSmallNoBorder.class); + button.addClickListener(event -> onSearchResetClick()); + button.setData(Boolean.FALSE); + return button; + } + + private Button createAddButton() { + final Button button = SPUIComponentProvider.getButton(getAddIconId(), "", "", null, false, FontAwesome.PLUS, + SPUIButtonStyleSmallNoBorder.class); + button.addClickListener(event -> addNewItem(event)); + return button; + } + + private Button createCloseButton() { + final Button button = SPUIComponentProvider.getButton(getCloseButtonId(), "", "", null, false, + FontAwesome.TIMES, SPUIButtonStyleSmallNoBorder.class); + button.addClickListener(event -> onClose(event)); + return button; + } + + private void onSearchResetClick() { + final Boolean flag = isSearchFieldOpen(); + if (flag == null || Boolean.FALSE.equals(flag)) { + // Clicked on search Icon + openSearchTextField(); + } else { + // Clicked on rest icon + closeSearchTextField(); + } + } + + protected Boolean isSearchFieldOpen() { + return (Boolean) searchResetIcon.getData(); + } + + private void openSearchTextField() { + searchResetIcon.addStyleName(SPUIDefinitions.FILTER_RESET_ICON); + searchResetIcon.togleIcon(FontAwesome.TIMES); + searchResetIcon.setData(Boolean.TRUE); + searchField.removeStyleName(SPUIDefinitions.FILTER_BOX_HIDE); + searchField.focus(); + } + + private void closeSearchTextField() { + searchField.setValue(""); + searchField.addStyleName(SPUIDefinitions.FILTER_BOX_HIDE); + searchResetIcon.removeStyleName(SPUIDefinitions.FILTER_RESET_ICON); + searchResetIcon.togleIcon(FontAwesome.SEARCH); + searchResetIcon.setData(Boolean.FALSE); + resetSearchText(); + } + + /** + * This method will be called when user resets the search text means on + * click of (X) icon. + */ + protected abstract void resetSearchText(); + + /** + * get Id of search text field. + * + * @return Id of the text field. + */ + protected abstract String getSearchBoxId(); + + /** + * Get search reset Icon Id. + * + * @return Id of search reset icon. + */ + protected abstract String getSearchRestIconId(); + + /** + * On search by text. + * + * @param newSearchText + * search text + */ + protected abstract void searchBy(String newSearchText); + + /** + * Get Id of add Icon. + * + * @return String of add Icon. + */ + protected abstract String getAddIconId(); + + /** + * On click add button. + * + * @param event + * add button click event + */ + protected abstract void addNewItem(final Button.ClickEvent event); + + /** + * On click of close button. + * + * @param event + * close button click event + */ + protected abstract void onClose(final Button.ClickEvent event); + + /** + * Checks create permission. + * + * @return boolean Returns true if user has create permission + */ + protected abstract boolean hasCreatePermission(); + + /** + * Get close button id. + * + * @return String button id + */ + protected abstract String getCloseButtonId(); + + /** + * Checks if close button to be displayed. + * + * @return true if close button has to be displayed + */ + protected abstract boolean showCloseButton(); + + /** + * Checks if search is allowed. + * + * @return boolean if true search field is displayed. + */ + protected abstract boolean isAllowSearch(); + + /** + * Get search box on load text value. + * + * @return value of search box. + */ + protected abstract String onLoadSearchBoxValue(); + + /** + * Checks for the rollout group. + * + * @return boolean value for Rollout Group. + */ + protected abstract boolean isRollout(); + + /** + * Get header caption layout. + * + * @return layout with caption + */ + protected abstract HorizontalLayout getHeaderCaptionLayout(); + + /** + * Restore caption details on refresh. + */ + protected abstract void restoreCaption(); +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/AbstractSimpleTableLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/AbstractSimpleTableLayout.java new file mode 100644 index 000000000..560e4040e --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/AbstractSimpleTableLayout.java @@ -0,0 +1,92 @@ +/** + * 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.rollout; + +import org.eclipse.hawkbit.ui.utils.SPUIComponetIdProvider; + +import com.vaadin.ui.Alignment; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.Label; +import com.vaadin.ui.VerticalLayout; + +/** + * + * Abstract table layout class which builds layout with table + * {@link AbstractSimpleTable} and table header + * {@link AbstractSimpleTableHeader}. + * + */ +public abstract class AbstractSimpleTableLayout extends VerticalLayout { + + private static final long serialVersionUID = 8611248179949245460L; + + private AbstractSimpleTableHeader tableHeader; + + private AbstractSimpleTable table; + + protected void init(final AbstractSimpleTableHeader tableHeader, final AbstractSimpleTable table) { + this.tableHeader = tableHeader; + this.table = table; + buildLayout(); + } + + private void buildLayout() { + setSizeFull(); + setSpacing(true); + setMargin(false); + setStyleName("group"); + final VerticalLayout tableHeaderLayout = new VerticalLayout(); + tableHeaderLayout.setSizeFull(); + tableHeaderLayout.setSpacing(false); + tableHeaderLayout.setMargin(false); + + tableHeaderLayout.setStyleName("table-layout"); + tableHeaderLayout.addComponent(tableHeader); + + tableHeaderLayout.setComponentAlignment(tableHeader, Alignment.TOP_CENTER); + tableHeaderLayout.addComponent(table); + tableHeaderLayout.setComponentAlignment(table, Alignment.TOP_CENTER); + tableHeaderLayout.setExpandRatio(table, 1.0f); + + + addComponent(tableHeaderLayout); + setComponentAlignment(tableHeaderLayout, Alignment.TOP_CENTER); + setExpandRatio(tableHeaderLayout, 1.0f); + if (hasCountMessage()) { + final HorizontalLayout rolloutGroupTargetsCountLayout = createCountMessageComponent(); + addComponent(rolloutGroupTargetsCountLayout); + setComponentAlignment(rolloutGroupTargetsCountLayout, Alignment.BOTTOM_CENTER); + } + + } + + /** + * @return + * + */ + private HorizontalLayout createCountMessageComponent() { + final HorizontalLayout rolloutGroupTargetsCountLayout = new HorizontalLayout(); + final Label countMessageLabel = getCountMessageLabel(); + countMessageLabel.setId(SPUIComponetIdProvider.ROLLOUT_GROUP_TARGET_LABEL); + rolloutGroupTargetsCountLayout.addComponent(getCountMessageLabel()); + rolloutGroupTargetsCountLayout.setStyleName("footer-layout"); + rolloutGroupTargetsCountLayout.setWidth("100%"); + return rolloutGroupTargetsCountLayout; + + } + + /** + * Only in rollout group targets view count message is displayed. + * + * @return + */ + protected abstract boolean hasCountMessage(); + + protected abstract Label getCountMessageLabel(); +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/AddUpdateRolloutWindowLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/AddUpdateRolloutWindowLayout.java new file mode 100644 index 000000000..d0cddfb19 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/AddUpdateRolloutWindowLayout.java @@ -0,0 +1,848 @@ +/** + * 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.rollout; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; + +import org.eclipse.hawkbit.repository.DistributionSetManagement; +import org.eclipse.hawkbit.repository.RolloutManagement; +import org.eclipse.hawkbit.repository.TargetManagement; +import org.eclipse.hawkbit.repository.model.Action; +import org.eclipse.hawkbit.repository.model.Action.ActionType; +import org.eclipse.hawkbit.repository.model.DistributionSetIdName; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupConditions; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorAction; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorCondition; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessAction; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition; +import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; +import org.eclipse.hawkbit.ui.decorators.SPUIButtonStyleSmallNoBorder; +import org.eclipse.hawkbit.ui.documentation.DocumentationPageLink; +import org.eclipse.hawkbit.ui.filtermanagement.TargetFilterBeanQuery; +import org.eclipse.hawkbit.ui.management.footer.ActionTypeOptionGroupLayout; +import org.eclipse.hawkbit.ui.management.footer.ActionTypeOptionGroupLayout.ActionTypeOption; +import org.eclipse.hawkbit.ui.rollout.event.RolloutEvent; +import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil; +import org.eclipse.hawkbit.ui.utils.I18N; +import org.eclipse.hawkbit.ui.utils.SPDateTimeUtil; +import org.eclipse.hawkbit.ui.utils.SPUIComponetIdProvider; +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.UINotification; +import org.springframework.beans.factory.annotation.Autowired; +import org.vaadin.addons.lazyquerycontainer.BeanQueryFactory; +import org.vaadin.addons.lazyquerycontainer.LazyQueryContainer; +import org.vaadin.addons.lazyquerycontainer.LazyQueryDefinition; +import org.vaadin.spring.events.EventBus; + +import com.google.common.base.Strings; +import com.vaadin.data.Container; +import com.vaadin.data.Item; +import com.vaadin.data.Property.ValueChangeEvent; +import com.vaadin.data.Validator; +import com.vaadin.data.validator.IntegerRangeValidator; +import com.vaadin.data.validator.RegexpValidator; +import com.vaadin.server.FontAwesome; +import com.vaadin.spring.annotation.SpringComponent; +import com.vaadin.spring.annotation.ViewScope; +import com.vaadin.ui.Alignment; +import com.vaadin.ui.Button; +import com.vaadin.ui.ComboBox; +import com.vaadin.ui.CustomComponent; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.Label; +import com.vaadin.ui.Link; +import com.vaadin.ui.OptionGroup; +import com.vaadin.ui.TextArea; +import com.vaadin.ui.TextField; +import com.vaadin.ui.UI; +import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.Window; +import com.vaadin.ui.themes.ValoTheme; + +/** + * + * Rollout add or update popup layout. + * + */ +@SpringComponent +@ViewScope +public class AddUpdateRolloutWindowLayout extends CustomComponent { + + private static final long serialVersionUID = 2999293468801479916L; + + private static final String MESSAGE_ROLLOUT_FIELD_VALUE_RANGE = "message.rollout.field.value.range"; + + private static final String MESSAGE_ENTER_NUMBER = "message.enter.number"; + + private static final String NUMBER_REGEXP = "[-]?[0-9]*\\.?,?[0-9]+"; + + @Autowired + private ActionTypeOptionGroupLayout actionTypeOptionGroupLayout; + + @Autowired + private transient RolloutManagement rolloutManagement; + + @Autowired + private transient DistributionSetManagement distributionSetManagement; + + @Autowired + private transient TargetManagement targetManagement; + + @Autowired + private UINotification uiNotification; + + @Autowired + private I18N i18n; + + @Autowired + private transient EventBus.SessionEventBus eventBus; + + private Label madatoryLabel; + + private TextField rolloutName; + + private ComboBox distributionSet; + + private ComboBox targetFilterQueryCombo; + + private TextField noOfGroups; + + private Label groupSizeLabel; + + private TextField triggerThreshold; + + private TextField errorThreshold; + + private TextArea description; + + private Button saveRollout; + + private Button discardRolllout; + + private OptionGroup errorThresholdOptionGroup; + + private Link linkToHelp; + + private Window addUpdateRolloutWindow; + + private Boolean editRollout; + + private Rollout rolloutForEdit; + + private Long totalTargetsCount; + + private Label totalTargetsLabel; + + private TextArea targetFilterQuery; + + /** + * Create components and layout. + */ + public void init() { + createRequiredComponents(); + buildLayout(); + } + + public Window getWindow() { + addUpdateRolloutWindow = SPUIComponentProvider.getWindow(i18n.get("caption.configure.rollout"), null, + SPUIDefinitions.CREATE_UPDATE_WINDOW); + addUpdateRolloutWindow.setContent(this); + return addUpdateRolloutWindow; + } + + /** + * Reset the field values. + */ + public void resetComponents() { + editRollout = Boolean.FALSE; + rolloutName.clear(); + targetFilterQuery.clear(); + resetFields(); + enableFields(); + populateDistributionSet(); + populateTargetFilterQuery(); + setDefaultSaveStartGroupOption(); + totalTargetsLabel.setVisible(false); + groupSizeLabel.setVisible(false); + targetFilterQuery.setVisible(false); + targetFilterQueryCombo.setVisible(true); + actionTypeOptionGroupLayout.selectDefaultOption(); + totalTargetsCount = 0L; + rolloutForEdit = null; + } + + private void resetFields() { + rolloutName.removeStyleName(SPUIStyleDefinitions.SP_TEXTFIELD_ERROR); + noOfGroups.clear(); + noOfGroups.removeStyleName(SPUIStyleDefinitions.SP_TEXTFIELD_ERROR); + triggerThreshold.clear(); + triggerThreshold.removeStyleName(SPUIStyleDefinitions.SP_TEXTFIELD_ERROR); + errorThreshold.clear(); + errorThreshold.removeStyleName(SPUIStyleDefinitions.SP_TEXTFIELD_ERROR); + description.clear(); + description.removeStyleName(SPUIStyleDefinitions.SP_TEXTFIELD_ERROR); + } + + private void buildLayout() { + final VerticalLayout mainLayout = new VerticalLayout(); + mainLayout.setSpacing(Boolean.TRUE); + mainLayout.setSizeUndefined(); + + mainLayout.addComponents(madatoryLabel, rolloutName, distributionSet, getTargetFilterLayout(), + getGroupDetailsLayout(), getTriggerThresoldLayout(), getErrorThresoldLayout(), description, + actionTypeOptionGroupLayout, linkToHelp, getSaveDiscardButtonLayout()); + mainLayout.setComponentAlignment(linkToHelp, Alignment.BOTTOM_RIGHT); + setCompositionRoot(mainLayout); + } + + private HorizontalLayout getGroupDetailsLayout() { + final HorizontalLayout groupLayout = new HorizontalLayout(); + groupLayout.setSizeFull(); + groupLayout.addComponents(noOfGroups, groupSizeLabel); + groupLayout.setExpandRatio(noOfGroups, 1.0f); + groupLayout.setComponentAlignment(groupSizeLabel, Alignment.MIDDLE_LEFT); + return groupLayout; + } + + private HorizontalLayout getErrorThresoldLayout() { + final HorizontalLayout errorThresoldLayout = new HorizontalLayout(); + errorThresoldLayout.setSizeFull(); + errorThresoldLayout.addComponents(errorThreshold, errorThresholdOptionGroup); + errorThresoldLayout.setExpandRatio(errorThreshold, 1.0f); + return errorThresoldLayout; + } + + private HorizontalLayout getTargetFilterLayout() { + final HorizontalLayout targetFilterLayout = new HorizontalLayout(); + targetFilterLayout.setSizeFull(); + targetFilterLayout.addComponents(targetFilterQueryCombo, targetFilterQuery, totalTargetsLabel); + targetFilterLayout.setExpandRatio(targetFilterQueryCombo, 0.71f); + targetFilterLayout.setExpandRatio(targetFilterQuery, 0.70f); + targetFilterLayout.setExpandRatio(totalTargetsLabel, 0.29f); + targetFilterLayout.setComponentAlignment(totalTargetsLabel, Alignment.MIDDLE_LEFT); + return targetFilterLayout; + } + + private HorizontalLayout getTriggerThresoldLayout() { + final HorizontalLayout triggerThresholdLayout = new HorizontalLayout(); + triggerThresholdLayout.setSizeFull(); + triggerThresholdLayout.addComponents(triggerThreshold, getPercentHintLabel()); + triggerThresholdLayout.setExpandRatio(triggerThreshold, 1.0f); + return triggerThresholdLayout; + } + + private Label getPercentHintLabel() { + final Label percentSymbol = new Label("%"); + percentSymbol.addStyleName(ValoTheme.LABEL_TINY + " " + ValoTheme.LABEL_BOLD); + percentSymbol.setSizeUndefined(); + return percentSymbol; + } + + private HorizontalLayout getSaveDiscardButtonLayout() { + final HorizontalLayout buttonsLayout = new HorizontalLayout(); + buttonsLayout.setSizeFull(); + buttonsLayout.addComponents(saveRollout, discardRolllout); + buttonsLayout.setComponentAlignment(saveRollout, Alignment.BOTTOM_LEFT); + buttonsLayout.setComponentAlignment(discardRolllout, Alignment.BOTTOM_RIGHT); + buttonsLayout.addStyleName("window-style"); + return buttonsLayout; + } + + private void createRequiredComponents() { + madatoryLabel = createMandatoryLabel(); + rolloutName = createRolloutNameField(); + distributionSet = createDistributionSetCombo(); + populateDistributionSet(); + + targetFilterQueryCombo = createTargetFilterQueryCombo(); + populateTargetFilterQuery(); + + noOfGroups = createNoOfGroupsField(); + groupSizeLabel = createGroupSizeLabel(); + triggerThreshold = createTriggerThresold(); + errorThreshold = createErrorThresold(); + description = createDescription(); + errorThresholdOptionGroup = createErrorThresholdOptionGroup(); + setDefaultSaveStartGroupOption(); + saveRollout = createSaveButton(); + discardRolllout = createDiscardButton(); + actionTypeOptionGroupLayout.selectDefaultOption(); + + totalTargetsLabel = createTotalTargetsLabel(); + targetFilterQuery = createTargetFilterQuery(); + + linkToHelp = DocumentationPageLink.ROLLOUT_VIEW.getLink(); + actionTypeOptionGroupLayout.addStyleName(SPUIStyleDefinitions.ROLLOUT_ACTION_TYPE_LAYOUT); + + } + + private Label createGroupSizeLabel() { + final Label groupSize = SPUIComponentProvider.getLabel("", SPUILabelDefinitions.SP_LABEL_SIMPLE); + groupSize.addStyleName(ValoTheme.LABEL_TINY + " " + "rollout-target-count-message"); + groupSize.setImmediate(true); + groupSize.setVisible(false); + groupSize.setSizeUndefined(); + return groupSize; + } + + private TextArea createTargetFilterQuery() { + final TextArea filterField = SPUIComponentProvider.getTextArea("text-area-style", ValoTheme.TEXTFIELD_TINY, + false, null, null, SPUILabelDefinitions.TARGET_FILTER_QUERY_TEXT_FIELD_LENGTH); + filterField.setId(SPUIComponetIdProvider.ROLLOUT_TARGET_FILTER_QUERY_FIELD); + filterField.setNullRepresentation(HawkbitCommonUtil.SP_STRING_EMPTY); + filterField.setVisible(false); + filterField.setEnabled(false); + filterField.setSizeFull(); + return filterField; + } + + private Label createTotalTargetsLabel() { + final Label targetCountLabel = SPUIComponentProvider.getLabel("", SPUILabelDefinitions.SP_LABEL_SIMPLE); + targetCountLabel.addStyleName(ValoTheme.LABEL_TINY + " " + "rollout-target-count-message"); + targetCountLabel.setImmediate(true); + targetCountLabel.setVisible(false); + targetCountLabel.setSizeUndefined(); + return targetCountLabel; + } + + private OptionGroup createErrorThresholdOptionGroup() { + final OptionGroup errorThresoldOptions = new OptionGroup(); + for (final ERRORTHRESOLDOPTIONS option : ERRORTHRESOLDOPTIONS.values()) { + errorThresoldOptions.addItem(option.getValue()); + } + errorThresoldOptions.setId(SPUIComponetIdProvider.ROLLOUT_ERROR_THRESOLD_OPTION_ID); + errorThresoldOptions.addStyleName(ValoTheme.OPTIONGROUP_HORIZONTAL); + errorThresoldOptions.addStyleName(SPUIStyleDefinitions.ROLLOUT_OPTION_GROUP); + errorThresoldOptions.setSizeUndefined(); + errorThresoldOptions.addValueChangeListener(event -> onErrorThresoldOptionChange(event)); + return errorThresoldOptions; + } + + private void onErrorThresoldOptionChange(final ValueChangeEvent event) { + errorThreshold.clear(); + errorThreshold.removeAllValidators(); + if (event.getProperty().getValue().equals(ERRORTHRESOLDOPTIONS.COUNT.getValue())) { + errorThreshold.addValidator(new ErrorThresoldOptionValidator()); + } else { + errorThreshold.addValidator(new ThresoldFieldValidator()); + } + errorThreshold.getValidators(); + } + + private ComboBox createTargetFilterQueryCombo() { + final ComboBox targetFilter = SPUIComponentProvider.getComboBox("", "", null, null, true, "", + i18n.get("prompt.target.filter")); + targetFilter.setImmediate(true); + targetFilter.setPageLength(7); + targetFilter.setItemCaptionPropertyId(SPUILabelDefinitions.VAR_NAME); + targetFilter.setId(SPUIComponetIdProvider.ROLLOUT_TARGET_FILTER_COMBO_ID); + targetFilter.setSizeFull(); + targetFilter.addValueChangeListener(event -> onTargetFilterChange()); + return targetFilter; + } + + private void onTargetFilterChange() { + final String filterQueryString = getTargetFilterQuery(); + if (!Strings.isNullOrEmpty(filterQueryString)) { + totalTargetsCount = targetManagement.countTargetByTargetFilterQuery(filterQueryString); + totalTargetsLabel.setValue(getTotalTargetMessage()); + totalTargetsLabel.setVisible(true); + } else { + totalTargetsCount = 0L; + totalTargetsLabel.setVisible(false); + } + onGroupNumberChange(); + } + + private String getTotalTargetMessage() { + return new StringBuilder(i18n.get("label.target.filter.count")).append(totalTargetsCount).toString(); + } + + private String getTargetPerGroupMessage(final String value) { + return new StringBuilder(i18n.get("label.target.per.group")).append(value).toString(); + } + + private void populateTargetFilterQuery() { + final Container container = createTargetFilterComboContainer(); + targetFilterQueryCombo.setContainerDataSource(container); + } + + private Container createTargetFilterComboContainer() { + final BeanQueryFactory targetFilterQF = new BeanQueryFactory<>( + TargetFilterBeanQuery.class); + return new LazyQueryContainer(new LazyQueryDefinition(true, SPUIDefinitions.PAGE_SIZE, + SPUILabelDefinitions.VAR_NAME), targetFilterQF); + + } + + private Button createDiscardButton() { + final Button discardRollloutBtn = SPUIComponentProvider.getButton( + SPUIComponetIdProvider.ROLLOUT_CREATE_UPDATE_DISCARD_ID, "", "", "", true, FontAwesome.TIMES, + SPUIButtonStyleSmallNoBorder.class); + discardRollloutBtn.addClickListener(event -> onDiscard()); + return discardRollloutBtn; + } + + private Button createSaveButton() { + final Button saveRolloutBtn = SPUIComponentProvider.getButton( + SPUIComponetIdProvider.ROLLOUT_CREATE_UPDATE_SAVE_ID, "", "", "", true, FontAwesome.SAVE, + SPUIButtonStyleSmallNoBorder.class); + saveRolloutBtn.addClickListener(event -> onRolloutSave()); + saveRolloutBtn.setImmediate(true); + return saveRolloutBtn; + } + + private void onDiscard() { + closeThisWindow(); + } + + private void onRolloutSave() { + if (editRollout) { + editRollout(); + } else { + createRollout(); + } + } + + private void editRollout() { + if (mandatoryCheckForEdit() && validateFields() && duplicateCheckForEdit() && null != rolloutForEdit) { + rolloutForEdit.setName(rolloutName.getValue()); + rolloutForEdit.setDescription(description.getValue()); + final DistributionSetIdName distributionSetIdName = (DistributionSetIdName) distributionSet.getValue(); + rolloutForEdit.setDistributionSet(distributionSetManagement.findDistributionSetById(distributionSetIdName + .getId())); + rolloutForEdit.setActionType(getActionType()); + rolloutForEdit.setForcedTime(getForcedTimeStamp()); + final int amountGroup = Integer.parseInt(noOfGroups.getValue()); + final int errorThresoldPercent = getErrorThresoldPercentage(amountGroup); + + for (final RolloutGroup rolloutGroup : rolloutForEdit.getRolloutGroups()) { + rolloutGroup.setErrorConditionExp(triggerThreshold.getValue()); + rolloutGroup.setSuccessConditionExp(String.valueOf(errorThresoldPercent)); + } + final Rollout updatedRollout = rolloutManagement.updateRollout(rolloutForEdit); + uiNotification + .displaySuccess(i18n.get("message.update.success", new Object[] { updatedRollout.getName() })); + closeThisWindow(); + eventBus.publish(this, RolloutEvent.UPDATE_ROLLOUT); + } + } + + private boolean duplicateCheckForEdit() { + final String rolloutNameVal = getRolloutName(); + if (!rolloutForEdit.getName().equals(rolloutNameVal) + && rolloutManagement.findRolloutByName(rolloutNameVal) != null) { + uiNotification.displayValidationError(i18n.get("message.rollout.duplicate.check", rolloutNameVal)); + return false; + } + return true; + } + + private long getForcedTimeStamp() { + return (((ActionTypeOptionGroupLayout.ActionTypeOption) actionTypeOptionGroupLayout.getActionTypeOptionGroup() + .getValue()) == ActionTypeOption.AUTO_FORCED) ? actionTypeOptionGroupLayout.getForcedTimeDateField() + .getValue().getTime() : Action.NO_FORCE_TIME; + } + + private ActionType getActionType() { + return ((ActionTypeOptionGroupLayout.ActionTypeOption) actionTypeOptionGroupLayout.getActionTypeOptionGroup() + .getValue()).getActionType(); + } + + private void createRollout() { + if (mandatoryCheck() && validateFields() && duplicateCheck()) { + final Rollout rolloutToCreate = saveRollout(); + uiNotification.displaySuccess(i18n.get("message.save.success", new Object[] { rolloutToCreate.getName() })); + eventBus.publish(this, RolloutEvent.CREATE_ROLLOUT); + closeThisWindow(); + } + } + + private Rollout saveRollout() { + Rollout rolloutToCreate = new Rollout(); + final int amountGroup = Integer.parseInt(noOfGroups.getValue()); + final String targetFilter = getTargetFilterQuery(); + final int errorThresoldPercent = getErrorThresoldPercentage(amountGroup); + + final RolloutGroupConditions conditions = new RolloutGroup.RolloutGroupConditionBuilder() + .successAction(RolloutGroupSuccessAction.NEXTGROUP, null) + .successCondition(RolloutGroupSuccessCondition.THRESHOLD, triggerThreshold.getValue()) + .errorCondition(RolloutGroupErrorCondition.THRESHOLD, String.valueOf(errorThresoldPercent)) + .errorAction(RolloutGroupErrorAction.PAUSE, null).build(); + + final DistributionSetIdName distributionSetIdName = (DistributionSetIdName) distributionSet.getValue(); + rolloutToCreate.setName(rolloutName.getValue()); + rolloutToCreate.setDescription(description.getValue()); + rolloutToCreate.setTargetFilterQuery(targetFilter); + rolloutToCreate.setDistributionSet(distributionSetManagement.findDistributionSetById(distributionSetIdName + .getId())); + rolloutToCreate.setActionType(getActionType()); + rolloutToCreate.setForcedTime(getForcedTimeStamp()); + + rolloutToCreate = rolloutManagement.createRolloutAsync(rolloutToCreate, amountGroup, conditions); + return rolloutToCreate; + } + + private String getTargetFilterQuery() { + if (null != targetFilterQueryCombo.getValue() + && HawkbitCommonUtil.trimAndNullIfEmpty((String) targetFilterQueryCombo.getValue()) != null) { + final Item filterItem = targetFilterQueryCombo.getContainerDataSource().getItem( + targetFilterQueryCombo.getValue()); + return (String) filterItem.getItemProperty("query").getValue(); + } + return null; + } + + private int getErrorThresoldPercentage(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 validateFields() { + if (!noOfGroups.isValid() || !errorThreshold.isValid() || !triggerThreshold.isValid()) { + uiNotification.displayValidationError(i18n.get("message.correct.invalid.value")); + return false; + } + return true; + } + + private void closeThisWindow() { + addUpdateRolloutWindow.close(); + UI.getCurrent().removeWindow(addUpdateRolloutWindow); + } + + private boolean mandatoryCheck() { + final DistributionSetIdName ds = getDistributionSetSelected(); + final String targetFilter = (String) targetFilterQueryCombo.getValue(); + final String triggerThresoldValue = triggerThreshold.getValue(); + final String errorThresoldValue = errorThreshold.getValue(); + if (hasNoNameOrTargetFilter(targetFilter) || ds == null + || HawkbitCommonUtil.trimAndNullIfEmpty(noOfGroups.getValue()) == null + || isThresholdValueMissing(triggerThresoldValue, errorThresoldValue)) { + uiNotification.displayValidationError(i18n.get("message.mandatory.check")); + return false; + } + return true; + } + + private boolean mandatoryCheckForEdit() { + final DistributionSetIdName ds = getDistributionSetSelected(); + final String targetFilter = targetFilterQuery.getValue(); + final String triggerThresoldValue = triggerThreshold.getValue(); + final String errorThresoldValue = errorThreshold.getValue(); + if (hasNoNameOrTargetFilter(targetFilter) || ds == null + || HawkbitCommonUtil.trimAndNullIfEmpty(noOfGroups.getValue()) == null + || isThresholdValueMissing(triggerThresoldValue, errorThresoldValue)) { + uiNotification.displayValidationError(i18n.get("message.mandatory.check")); + return false; + } + return true; + } + + private boolean hasNoNameOrTargetFilter(final String targetFilter) { + return getRolloutName() == null || targetFilter == null; + } + + private boolean isThresholdValueMissing(final String triggerThresoldValue, final String errorThresoldValue) { + return HawkbitCommonUtil.trimAndNullIfEmpty(triggerThresoldValue) == null + || HawkbitCommonUtil.trimAndNullIfEmpty(errorThresoldValue) == null; + } + + private boolean duplicateCheck() { + if (rolloutManagement.findRolloutByName(getRolloutName()) != null) { + uiNotification.displayValidationError(i18n.get("message.rollout.duplicate.check", + new Object[] { getRolloutName() })); + return false; + } + return true; + } + + private void setDefaultSaveStartGroupOption() { + errorThresholdOptionGroup.setValue(ERRORTHRESOLDOPTIONS.PERCENT.getValue()); + } + + private TextArea createDescription() { + final TextArea descriptionField = SPUIComponentProvider.getTextArea("text-area-style", + ValoTheme.TEXTFIELD_TINY, false, null, i18n.get("textfield.description"), + SPUILabelDefinitions.TEXT_AREA_MAX_LENGTH); + descriptionField.setId(SPUIComponetIdProvider.ROLLOUT_DESCRIPTION_ID); + descriptionField.setNullRepresentation(HawkbitCommonUtil.SP_STRING_EMPTY); + descriptionField.setSizeFull(); + return descriptionField; + } + + private TextField createErrorThresold() { + final TextField errorField = SPUIComponentProvider.getTextField("", ValoTheme.TEXTFIELD_TINY, true, null, + i18n.get("prompt.error.threshold"), true, SPUILabelDefinitions.TEXT_FIELD_MAX_LENGTH); + errorField.addValidator(new ThresoldFieldValidator()); + errorField.setId(SPUIComponetIdProvider.ROLLOUT_ERROR_THRESOLD_ID); + errorField.setMaxLength(7); + errorField.setSizeFull(); + return errorField; + } + + private TextField createTriggerThresold() { + final TextField thresholdField = SPUIComponentProvider.getTextField("", ValoTheme.TEXTFIELD_TINY, true, null, + i18n.get("prompt.tigger.thresold"), true, SPUILabelDefinitions.TEXT_FIELD_MAX_LENGTH); + thresholdField.setId(SPUIComponetIdProvider.ROLLOUT_TRIGGER_THRESOLD_ID); + thresholdField.addValidator(new ThresoldFieldValidator()); + thresholdField.setSizeFull(); + thresholdField.setMaxLength(3); + return thresholdField; + } + + private TextField createNoOfGroupsField() { + final TextField noOfGroupsField = SPUIComponentProvider.getTextField("", ValoTheme.TEXTFIELD_TINY, true, null, + i18n.get("prompt.number.of.groups"), true, SPUILabelDefinitions.TEXT_FIELD_MAX_LENGTH); + noOfGroupsField.setId(SPUIComponetIdProvider.ROLLOUT_NO_OF_GROUPS_ID); + noOfGroupsField.addValidator(new GroupNumberValidator()); + noOfGroupsField.setSizeFull(); + noOfGroupsField.setMaxLength(3); + noOfGroupsField.addValueChangeListener(evevt -> onGroupNumberChange()); + return noOfGroupsField; + } + + private void onGroupNumberChange() { + if (noOfGroups.isValid() && !Strings.isNullOrEmpty(noOfGroups.getValue())) { + groupSizeLabel.setValue(getTargetPerGroupMessage(String.valueOf(getGroupSize()))); + groupSizeLabel.setVisible(true); + } else { + groupSizeLabel.setVisible(false); + } + } + + private ComboBox createDistributionSetCombo() { + final ComboBox dsSet = SPUIComponentProvider.getComboBox("", "", null, null, true, "", + i18n.get("prompt.distribution.set")); + dsSet.setImmediate(true); + dsSet.setPageLength(7); + dsSet.setItemCaptionPropertyId(SPUILabelDefinitions.VAR_NAME); + dsSet.setId(SPUIComponetIdProvider.ROLLOUT_DS_ID); + dsSet.setSizeFull(); + return dsSet; + } + + private void populateDistributionSet() { + final Container container = createDsComboContainer(); + distributionSet.setContainerDataSource(container); + } + + private Container createDsComboContainer() { + final BeanQueryFactory distributionQF = new BeanQueryFactory<>(DistBeanQuery.class); + return new LazyQueryContainer(new LazyQueryDefinition(true, SPUIDefinitions.PAGE_SIZE, + SPUILabelDefinitions.VAR_DIST_ID_NAME), distributionQF); + + } + + private TextField createRolloutNameField() { + final TextField rolloutNameField = SPUIComponentProvider.getTextField("", ValoTheme.TEXTFIELD_TINY, true, null, + i18n.get("textfield.name"), true, SPUILabelDefinitions.TEXT_FIELD_MAX_LENGTH); + rolloutNameField.setId(SPUIComponetIdProvider.ROLLOUT_NAME_FIELD_ID); + rolloutNameField.setSizeFull(); + return rolloutNameField; + } + + private Label createMandatoryLabel() { + final Label madatoryLbl = new Label(i18n.get("label.mandatory.field")); + madatoryLbl.addStyleName(SPUIStyleDefinitions.SP_TEXTFIELD_ERROR + " " + ValoTheme.LABEL_SMALL); + return madatoryLbl; + } + + private String getRolloutName() { + return HawkbitCommonUtil.trimAndNullIfEmpty(rolloutName.getValue()); + } + + private DistributionSetIdName getDistributionSetSelected() { + return (DistributionSetIdName) distributionSet.getValue(); + } + + class ErrorThresoldOptionValidator implements Validator { + private static final long serialVersionUID = 9049939751976326550L; + + @Override + public void validate(final Object value) { + try { + if (HawkbitCommonUtil.trimAndNullIfEmpty(noOfGroups.getValue()) == null + || HawkbitCommonUtil.trimAndNullIfEmpty((String) targetFilterQueryCombo.getValue()) == null) { + uiNotification.displayValidationError(i18n + .get("message.rollout.noofgroups.or.targetfilter.missing")); + } else { + new RegexpValidator(NUMBER_REGEXP, i18n.get(MESSAGE_ENTER_NUMBER)).validate(value); + final int groupSize = getGroupSize(); + new IntegerRangeValidator(i18n.get(MESSAGE_ROLLOUT_FIELD_VALUE_RANGE, 0, groupSize), 0, groupSize) + .validate(Integer.valueOf(value.toString())); + } + } catch (final InvalidValueException ex) { + throw ex; + } + } + + } + + private int getGroupSize() { + return (int) Math.ceil((double) totalTargetsCount / Double.parseDouble(noOfGroups.getValue())); + } + + class ThresoldFieldValidator implements Validator { + private static final long serialVersionUID = 9049939751976326550L; + + @Override + public void validate(final Object value) { + try { + new RegexpValidator(NUMBER_REGEXP, i18n.get(MESSAGE_ENTER_NUMBER)).validate(value); + new IntegerRangeValidator(i18n.get(MESSAGE_ROLLOUT_FIELD_VALUE_RANGE, 0, 100), 0, 100).validate(Integer + .valueOf(value.toString())); + } catch (final InvalidValueException ex) { + throw ex; + } + } + } + + class GroupNumberValidator implements Validator { + private static final long serialVersionUID = 9049939751976326550L; + + @Override + public void validate(final Object value) { + try { + new RegexpValidator(NUMBER_REGEXP, i18n.get(MESSAGE_ENTER_NUMBER)).validate(value); + new IntegerRangeValidator(i18n.get(MESSAGE_ROLLOUT_FIELD_VALUE_RANGE, 0, 500), 0, 500).validate(Integer + .valueOf(value.toString())); + } catch (final InvalidValueException ex) { + throw ex; + } + } + } + + /** + * + * Populate rollout details. + * + * @param rolloutId + * rollout id + */ + public void populateData(final Long rolloutId) { + resetComponents(); + editRollout = Boolean.TRUE; + rolloutForEdit = rolloutManagement.findRolloutById(rolloutId); + rolloutName.setValue(rolloutForEdit.getName()); + description.setValue(rolloutForEdit.getDescription()); + distributionSet.setValue(rolloutForEdit.getDistributionSet().getDistributionSetIdName()); + final List rolloutGroups = rolloutForEdit.getRolloutGroups(); + setThresoldValues(rolloutGroups); + setActionType(rolloutForEdit); + if (rolloutForEdit.getStatus() != RolloutStatus.READY) { + disableRequiredFieldsOnEdit(); + } + + noOfGroups.setEnabled(false); + targetFilterQuery.setValue(rolloutForEdit.getTargetFilterQuery()); + targetFilterQuery.setVisible(true); + targetFilterQueryCombo.setVisible(false); + + totalTargetsCount = targetManagement.countTargetByTargetFilterQuery(rolloutForEdit.getTargetFilterQuery()); + totalTargetsLabel.setValue(getTotalTargetMessage()); + totalTargetsLabel.setVisible(true); + } + + private void disableRequiredFieldsOnEdit() { + distributionSet.setEnabled(false); + errorThreshold.setEnabled(false); + triggerThreshold.setEnabled(false); + actionTypeOptionGroupLayout.getActionTypeOptionGroup().setEnabled(false); + errorThresholdOptionGroup.setEnabled(false); + actionTypeOptionGroupLayout.addStyleName(SPUIStyleDefinitions.DISABLE_ACTION_TYPE_LAYOUT); + } + + private void enableFields() { + distributionSet.setEnabled(true); + errorThreshold.setEnabled(true); + triggerThreshold.setEnabled(true); + actionTypeOptionGroupLayout.getActionTypeOptionGroup().setEnabled(true); + actionTypeOptionGroupLayout.removeStyleName(SPUIStyleDefinitions.DISABLE_ACTION_TYPE_LAYOUT); + noOfGroups.setEnabled(true); + targetFilterQueryCombo.setEnabled(true); + errorThresholdOptionGroup.setEnabled(true); + } + + private void setActionType(final Rollout rollout) { + for (final ActionTypeOptionGroupLayout.ActionTypeOption groupAction : ActionTypeOptionGroupLayout.ActionTypeOption + .values()) { + if (groupAction.getActionType() == rollout.getActionType()) { + actionTypeOptionGroupLayout.getActionTypeOptionGroup().setValue(groupAction); + final SimpleDateFormat format = new SimpleDateFormat(SPUIDefinitions.LAST_QUERY_DATE_FORMAT); + format.setTimeZone(SPDateTimeUtil.getBrowserTimeZone()); + actionTypeOptionGroupLayout.getForcedTimeDateField().setValue(new Date(rollout.getForcedTime())); + break; + } + } + } + + /** + * @param rolloutGroups + */ + private void setThresoldValues(final List rolloutGroups) { + if (null != rolloutGroups && !rolloutGroups.isEmpty()) { + errorThreshold.setValue(rolloutGroups.get(0).getErrorConditionExp()); + triggerThreshold.setValue(rolloutGroups.get(0).getSuccessConditionExp()); + noOfGroups.setValue(String.valueOf(rolloutGroups.size())); + } else { + errorThreshold.setValue("0"); + triggerThreshold.setValue("0"); + noOfGroups.setValue("0"); + } + } + + enum SAVESTARTOPTIONS { + SAVE("Save"), START("Start"); + + String value; + + private SAVESTARTOPTIONS(final String val) { + this.value = val; + } + + /** + * @return the value + */ + public String getValue() { + return value; + } + + } + + enum ERRORTHRESOLDOPTIONS { + PERCENT("%"), COUNT("Count"); + + String value; + + private ERRORTHRESOLDOPTIONS(final String val) { + this.value = val; + } + + /** + * @return the value + */ + public String getValue() { + return value; + } + + } +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/DistBeanQuery.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/DistBeanQuery.java new file mode 100644 index 000000000..6226471ac --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/DistBeanQuery.java @@ -0,0 +1,168 @@ +/** + * 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.rollout; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.eclipse.hawkbit.repository.DistributionSetFilter; +import org.eclipse.hawkbit.repository.DistributionSetFilter.DistributionSetFilterBuilder; +import org.eclipse.hawkbit.repository.DistributionSetManagement; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.ui.components.ProxyDistribution; +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.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; +import org.vaadin.addons.lazyquerycontainer.AbstractBeanQuery; +import org.vaadin.addons.lazyquerycontainer.QueryDefinition; + +/** + * Bean query for distribution set combo. + * + */ +public class DistBeanQuery extends AbstractBeanQuery { + + private static final long serialVersionUID = 5176481314404662215L; + private Sort sort = new Sort(Direction.ASC, "name", "version"); + private transient DistributionSetManagement distributionSetManagement; + private transient Page firstPageDistributionSets = null; + + /** + * Parametric Constructor. + * + * @param definition + * as QueryDefinition + * @param queryConfig + * as Config + * @param sortPropertyIds + * as sort + * @param sortStates + * as Sort status + */ + public DistBeanQuery(final QueryDefinition definition, final Map queryConfig, + final Object[] sortPropertyIds, final boolean[] sortStates) { + super(definition, queryConfig, sortPropertyIds, sortStates); + + if (sortStates.length > 0) { + // Initalize sort + sort = new Sort(sortStates[0] ? Direction.ASC : Direction.DESC, (String) sortPropertyIds[0]); + // Add sort + for (int distId = 1; distId < sortPropertyIds.length; distId++) { + sort.and(new Sort(sortStates[distId] ? Direction.ASC : Direction.DESC, (String) sortPropertyIds[distId])); + } + } + } + + /* + * (non-Javadoc) + * + * @see + * org.vaadin.addons.lazyquerycontainer.AbstractBeanQuery#constructBean() + */ + @Override + protected ProxyDistribution constructBean() { + return new ProxyDistribution(); + } + + /* + * (non-Javadoc) + * + * @see + * org.vaadin.addons.lazyquerycontainer.AbstractBeanQuery#loadBeans(int, + * int) + */ + @Override + protected List loadBeans(final int startIndex, final int count) { + Page distBeans; + final DistributionSetFilter distributionSetFilter = new DistributionSetFilterBuilder().setIsDeleted(false) + .build(); + if (startIndex == 0 && firstPageDistributionSets != null) { + distBeans = firstPageDistributionSets; + } else { + distBeans = getDistributionSetManagement().findDistributionSetsByFilters( + new PageRequest(startIndex / count, count, sort), distributionSetFilter); + } + return createProxyDistributions(distBeans); + } + + private List createProxyDistributions(final Page distBeans) { + final List proxyDistributions = new ArrayList<>(); + for (final DistributionSet distributionSet : distBeans) { + final ProxyDistribution proxyDistribution = new ProxyDistribution(); + proxyDistribution.setName(HawkbitCommonUtil.getFormattedNameVersion(distributionSet.getName(), + distributionSet.getVersion())); + proxyDistribution.setDescription(distributionSet.getDescription()); + proxyDistribution.setDistId(distributionSet.getId()); + proxyDistribution.setId(distributionSet.getId()); + proxyDistribution.setVersion(distributionSet.getVersion()); + proxyDistribution.setCreatedDate(SPDateTimeUtil.getFormattedDate(distributionSet.getCreatedAt())); + proxyDistribution.setLastModifiedDate(SPDateTimeUtil.getFormattedDate(distributionSet.getLastModifiedAt())); + proxyDistribution.setDescription(distributionSet.getDescription()); + proxyDistribution.setCreatedByUser(HawkbitCommonUtil.getIMUser(distributionSet.getCreatedBy())); + proxyDistribution.setModifiedByUser(HawkbitCommonUtil.getIMUser(distributionSet.getLastModifiedBy())); + proxyDistribution.setIsComplete(distributionSet.isComplete()); + proxyDistributions.add(proxyDistribution); + } + return proxyDistributions; + } + + /* + * (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) { + // Add,Delete and Update are performed through repository methods + } + + /* + * (non-Javadoc) + * + * @see org.vaadin.addons.lazyquerycontainer.AbstractBeanQuery#size() + */ + @Override + public int size() { + final DistributionSetFilter distributionSetFilter = new DistributionSetFilterBuilder().setIsDeleted(false) + .setIsComplete(true).build(); + + firstPageDistributionSets = getDistributionSetManagement().findDistributionSetsByFilters( + new PageRequest(0, SPUIDefinitions.PAGE_SIZE, sort), distributionSetFilter); + final long size = firstPageDistributionSets.getTotalElements(); + if (size > Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } + + return (int) size; + } + + private DistributionSetManagement getDistributionSetManagement() { + if (distributionSetManagement == null) { + distributionSetManagement = SpringContextHelper.getBean(DistributionSetManagement.class); + } + return distributionSetManagement; + } + + @SuppressWarnings("unchecked") + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + firstPageDistributionSets = (Page) in.readObject(); + } +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/ProxyRollout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/ProxyRollout.java new file mode 100644 index 000000000..efe065b85 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/ProxyRollout.java @@ -0,0 +1,258 @@ +/** + * 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.rollout; + +import org.eclipse.hawkbit.repository.model.Rollout; + +/** + * Proxy rollout with suctome properties. + * + */ +public class ProxyRollout extends Rollout { + + private static final long serialVersionUID = 4539849939617681918L; + + private String distributionSetNameVersion; + + private String createdDate; + + private String modifiedDate; + + private String createdBy; + + private String lastModifiedBy; + + private Long numberOfGroups; + + private Long runningTargetsCount; + + private Long scheduledTargetsCount; + + private Long cancelledTargetsCount; + + private Long errorTargetsCount; + + private Long finishedTargetsCount; + + private Long notStartedTargetsCount; + + private Boolean isActionRecieved = Boolean.FALSE; + + private String totalTargetsCount; + + /** + * @return the distributionSetNameVersion + */ + public String getDistributionSetNameVersion() { + return distributionSetNameVersion; + } + + /** + * @param distributionSetNameVersion + * the distributionSetNameVersion to set + */ + public void setDistributionSetNameVersion(final String distributionSetNameVersion) { + this.distributionSetNameVersion = distributionSetNameVersion; + } + + /** + * @return the numberOfGroups + */ + public Long getNumberOfGroups() { + return numberOfGroups; + } + + /** + * @param numberOfGroups + * the numberOfGroups to set + */ + public void setNumberOfGroups(final Long numberOfGroups) { + this.numberOfGroups = numberOfGroups; + } + + /** + * @return the createdDate + */ + public String getCreatedDate() { + return createdDate; + } + + /** + * @param createdDate + * the createdDate to set + */ + public void setCreatedDate(final String createdDate) { + this.createdDate = createdDate; + } + + /** + * @return the modifiedDate + */ + public String getModifiedDate() { + return modifiedDate; + } + + /** + * @param modifiedDate + * the modifiedDate to set + */ + public void setModifiedDate(final String modifiedDate) { + this.modifiedDate = modifiedDate; + } + + /** + * @return the createdBy + */ + public String getCreatedBy() { + return createdBy; + } + + /** + * @param createdBy + * the createdBy to set + */ + public void setCreatedBy(final String createdBy) { + this.createdBy = createdBy; + } + + /** + * @return the lastModifiedBy + */ + public String getLastModifiedBy() { + return lastModifiedBy; + } + + /** + * @param lastModifiedBy + * the lastModifiedBy to set + */ + public void setLastModifiedBy(final String lastModifiedBy) { + this.lastModifiedBy = lastModifiedBy; + } + + /** + * @return the runningTargetsCount + */ + public Long getRunningTargetsCount() { + return runningTargetsCount; + } + + /** + * @param runningTargetsCount + * the runningTargetsCount to set + */ + public void setRunningTargetsCount(final Long runningTargetsCount) { + this.runningTargetsCount = runningTargetsCount; + } + + /** + * @return the scheduledTargetsCount + */ + public Long getScheduledTargetsCount() { + return scheduledTargetsCount; + } + + /** + * @param scheduledTargetsCount + * the scheduledTargetsCount to set + */ + public void setScheduledTargetsCount(final Long scheduledTargetsCount) { + this.scheduledTargetsCount = scheduledTargetsCount; + } + + /** + * @return the cancelledTargetsCount + */ + public Long getCancelledTargetsCount() { + return cancelledTargetsCount; + } + + /** + * @param cancelledTargetsCount + * the cancelledTargetsCount to set + */ + public void setCancelledTargetsCount(final Long cancelledTargetsCount) { + this.cancelledTargetsCount = cancelledTargetsCount; + } + + /** + * @return the errorTargetsCount + */ + public Long getErrorTargetsCount() { + return errorTargetsCount; + } + + /** + * @param errorTargetsCount + * the errorTargetsCount to set + */ + public void setErrorTargetsCount(final Long errorTargetsCount) { + this.errorTargetsCount = errorTargetsCount; + } + + /** + * @return the finishedTargetsCount + */ + public Long getFinishedTargetsCount() { + return finishedTargetsCount; + } + + /** + * @param finishedTargetsCount + * the finishedTargetsCount to set + */ + public void setFinishedTargetsCount(final Long finishedTargetsCount) { + this.finishedTargetsCount = finishedTargetsCount; + } + + /** + * @return the isActionRecieved + */ + public Boolean getIsActionRecieved() { + return isActionRecieved; + } + + /** + * @param isActionRecieved + * the isActionRecieved to set + */ + public void setIsActionRecieved(final Boolean isActionRecieved) { + this.isActionRecieved = isActionRecieved; + } + + /** + * @return the notStartedTargetsCount + */ + public Long getNotStartedTargetsCount() { + return notStartedTargetsCount; + } + + /** + * @param notStartedTargetsCount + * the notStartedTargetsCount to set + */ + public void setNotStartedTargetsCount(final Long notStartedTargetsCount) { + this.notStartedTargetsCount = notStartedTargetsCount; + } + + /** + * @return the totalTargetsCount + */ + public String getTotalTargetsCount() { + return totalTargetsCount; + } + + /** + * @param totalTargetsCount + * the totalTargetsCount to set + */ + public void setTotalTargetsCount(final String totalTargetsCount) { + this.totalTargetsCount = totalTargetsCount; + } +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/ProxyRolloutGroup.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/ProxyRolloutGroup.java new file mode 100644 index 000000000..8373fcf08 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/ProxyRolloutGroup.java @@ -0,0 +1,242 @@ +/** + * 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.rollout; + +import org.eclipse.hawkbit.repository.model.RolloutGroup; + +/** + * Proxy rollout group with suctome properties. + * + */ +public class ProxyRolloutGroup extends RolloutGroup { + + private static final long serialVersionUID = -2745056813306692356L; + + private String createdDate; + + private String modifiedDate; + + private String createdBy; + + private String lastModifiedBy; + + private String finishedPercentage; + + private Long runningTargetsCount; + + private Long scheduledTargetsCount; + + private Long cancelledTargetsCount; + + private Long errorTargetsCount; + + private Long finishedTargetsCount; + + private Long notStartedTargetsCount; + + private Boolean isActionRecieved = Boolean.FALSE; + + private String totalTargetsCount; + + /** + * @return the createdDate + */ + public String getCreatedDate() { + return createdDate; + } + + /** + * @param createdDate + * the createdDate to set + */ + public void setCreatedDate(final String createdDate) { + this.createdDate = createdDate; + } + + /** + * @return the modifiedDate + */ + public String getModifiedDate() { + return modifiedDate; + } + + /** + * @param modifiedDate + * the modifiedDate to set + */ + public void setModifiedDate(final String modifiedDate) { + this.modifiedDate = modifiedDate; + } + + /** + * @return the createdBy + */ + public String getCreatedBy() { + return createdBy; + } + + /** + * @param createdBy + * the createdBy to set + */ + public void setCreatedBy(final String createdBy) { + this.createdBy = createdBy; + } + + /** + * @return the lastModifiedBy + */ + public String getLastModifiedBy() { + return lastModifiedBy; + } + + /** + * @param lastModifiedBy + * the lastModifiedBy to set + */ + public void setLastModifiedBy(final String lastModifiedBy) { + this.lastModifiedBy = lastModifiedBy; + } + + /** + * @return the finishedPercentage + */ + public String getFinishedPercentage() { + return finishedPercentage; + } + + /** + * @param finishedPercentage + * the finishedPercentage to set + */ + public void setFinishedPercentage(final String finishedPercentage) { + this.finishedPercentage = finishedPercentage; + } + + /** + * @return the runningTargetsCount + */ + public Long getRunningTargetsCount() { + return runningTargetsCount; + } + + /** + * @param runningTargetsCount + * the runningTargetsCount to set + */ + public void setRunningTargetsCount(final Long runningTargetsCount) { + this.runningTargetsCount = runningTargetsCount; + } + + /** + * @return the scheduledTargetsCount + */ + public Long getScheduledTargetsCount() { + return scheduledTargetsCount; + } + + /** + * @param scheduledTargetsCount + * the scheduledTargetsCount to set + */ + public void setScheduledTargetsCount(final Long scheduledTargetsCount) { + this.scheduledTargetsCount = scheduledTargetsCount; + } + + /** + * @return the cancelledTargetsCount + */ + public Long getCancelledTargetsCount() { + return cancelledTargetsCount; + } + + /** + * @param cancelledTargetsCount + * the cancelledTargetsCount to set + */ + public void setCancelledTargetsCount(final Long cancelledTargetsCount) { + this.cancelledTargetsCount = cancelledTargetsCount; + } + + /** + * @return the errorTargetsCount + */ + public Long getErrorTargetsCount() { + return errorTargetsCount; + } + + /** + * @param errorTargetsCount + * the errorTargetsCount to set + */ + public void setErrorTargetsCount(final Long errorTargetsCount) { + this.errorTargetsCount = errorTargetsCount; + } + + /** + * @return the finishedTargetsCount + */ + public Long getFinishedTargetsCount() { + return finishedTargetsCount; + } + + /** + * @param finishedTargetsCount + * the finishedTargetsCount to set + */ + public void setFinishedTargetsCount(final Long finishedTargetsCount) { + this.finishedTargetsCount = finishedTargetsCount; + } + + /** + * @return the notStartedTargetsCount + */ + public Long getNotStartedTargetsCount() { + return notStartedTargetsCount; + } + + /** + * @param notStartedTargetsCount + * the notStartedTargetsCount to set + */ + public void setNotStartedTargetsCount(final Long notStartedTargetsCount) { + this.notStartedTargetsCount = notStartedTargetsCount; + } + + /** + * @return the isActionRecieved + */ + public Boolean getIsActionRecieved() { + return isActionRecieved; + } + + /** + * @param isActionRecieved + * the isActionRecieved to set + */ + public void setIsActionRecieved(final Boolean isActionRecieved) { + this.isActionRecieved = isActionRecieved; + } + + /** + * @return the totalTargetsCount + */ + public String getTotalTargetsCount() { + return totalTargetsCount; + } + + /** + * @param totalTargetsCount + * the totalTargetsCount to set + */ + public void setTotalTargetsCount(final String totalTargetsCount) { + this.totalTargetsCount = totalTargetsCount; + } + +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutBeanQuery.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutBeanQuery.java new file mode 100644 index 000000000..8dfdca4b5 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutBeanQuery.java @@ -0,0 +1,214 @@ +/** + * 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.rollout; + +import java.util.ArrayList; +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; +import org.eclipse.hawkbit.ui.rollout.state.RolloutUIState; +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.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; +import org.vaadin.addons.lazyquerycontainer.AbstractBeanQuery; +import org.vaadin.addons.lazyquerycontainer.QueryDefinition; + +import com.google.common.base.Strings; + +/** + * + * Simple implementation of generics bean query which dynamically loads a batch + * of {@link ProxyRollout} beans. + * + */ +public class RolloutBeanQuery extends AbstractBeanQuery { + + private static final long serialVersionUID = 4027879794344836185L; + + private final String searchText; + + private Sort sort = new Sort(Direction.ASC, "createdAt"); + + private transient RolloutManagement rolloutManagement; + + private transient TargetFilterQueryManagement filterQueryManagement; + + private transient RolloutUIState rolloutUIState; + + /** + * Parametric Constructor. + * + * @param definition + * as QueryDefinition + * @param queryConfig + * as Config + * @param sortIds + * as sort + * @param sortStates + * as Sort status + */ + public RolloutBeanQuery(final QueryDefinition definition, final Map queryConfig, + final Object[] sortIds, final boolean[] sortStates) { + super(definition, queryConfig, sortIds, sortStates); + + searchText = getSearchText(); + + if (HawkbitCommonUtil.checkBolArray(sortStates)) { + // Initalize Sor + sort = new Sort(sortStates[0] ? Direction.ASC : Direction.DESC, (String) sortIds[0]); + // Add sort. + for (int targetId = 1; targetId < sortIds.length; targetId++) { + sort.and(new Sort(sortStates[targetId] ? Direction.ASC : Direction.DESC, (String) sortIds[targetId])); + } + } + } + + private String getSearchText() { + if (getRolloutUIState().getSearchText().isPresent()) { + return String.format("%%%s%%", getRolloutUIState().getSearchText().get()); + } + return null; + } + + @Override + protected ProxyRollout constructBean() { + return new ProxyRollout(); + } + + /* + * (non-Javadoc) + * + * @see + * org.vaadin.addons.lazyquerycontainer.AbstractBeanQuery#loadBeans(int, + * int) + */ + @Override + protected List loadBeans(final int startIndex, final int count) { + final Slice rolloutBeans; + if (Strings.isNullOrEmpty(searchText)) { + rolloutBeans = getRolloutManagement().findAllRolloutsWithDetailedStatus( + new PageRequest(startIndex / SPUIDefinitions.PAGE_SIZE, SPUIDefinitions.PAGE_SIZE, sort)); + } else { + rolloutBeans = getRolloutManagement().findRolloutByFilters( + new PageRequest(startIndex / SPUIDefinitions.PAGE_SIZE, SPUIDefinitions.PAGE_SIZE, sort), + searchText); + } + return getProxyRolloutList(rolloutBeans); + } + + private List getProxyRolloutList(final Slice rolloutBeans) { + final List proxyRolloutList = new ArrayList<>(); + for (final Rollout rollout : rolloutBeans) { + final ProxyRollout proxyRollout = new ProxyRollout(); + proxyRollout.setName(rollout.getName()); + proxyRollout.setDescription(rollout.getDescription()); + final DistributionSet distributionSet = rollout.getDistributionSet(); + proxyRollout.setDistributionSetNameVersion(HawkbitCommonUtil.getFormattedNameVersion( + distributionSet.getName(), distributionSet.getVersion())); + proxyRollout.setDistributionSet(distributionSet); + proxyRollout.setNumberOfGroups(Long.valueOf(rollout.getRolloutGroups().size())); + proxyRollout.setCreatedDate(SPDateTimeUtil.getFormattedDate(rollout.getCreatedAt())); + proxyRollout.setModifiedDate(SPDateTimeUtil.getFormattedDate(rollout.getLastModifiedAt())); + proxyRollout.setCreatedBy(HawkbitCommonUtil.getIMUser(rollout.getCreatedBy())); + proxyRollout.setLastModifiedBy(HawkbitCommonUtil.getIMUser(rollout.getLastModifiedBy())); + proxyRollout.setForcedTime(rollout.getForcedTime()); + proxyRollout.setId(rollout.getId()); + proxyRollout.setStatus(rollout.getStatus()); + + final TotalTargetCountStatus totalTargetCountActionStatus = rollout.getTotalTargetCountStatus(); + + proxyRollout.setRunningTargetsCount(totalTargetCountActionStatus + .getTotalTargetCountByStatus(TotalTargetCountStatus.Status.RUNNING)); + proxyRollout.setErrorTargetsCount(totalTargetCountActionStatus + .getTotalTargetCountByStatus(TotalTargetCountStatus.Status.ERROR)); + proxyRollout.setCancelledTargetsCount(totalTargetCountActionStatus + .getTotalTargetCountByStatus(TotalTargetCountStatus.Status.CANCELLED)); + proxyRollout.setFinishedTargetsCount(totalTargetCountActionStatus + .getTotalTargetCountByStatus(TotalTargetCountStatus.Status.FINISHED)); + proxyRollout.setScheduledTargetsCount(totalTargetCountActionStatus + .getTotalTargetCountByStatus(TotalTargetCountStatus.Status.SCHEDULED)); + proxyRollout.setNotStartedTargetsCount(totalTargetCountActionStatus + .getTotalTargetCountByStatus(TotalTargetCountStatus.Status.NOTSTARTED)); + proxyRolloutList.add(proxyRollout); + + proxyRollout.setTotalTargetsCount(String.valueOf(rollout.getTotalTargets())); + + } + 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) { + /** + * CRUD operations on Target will be done through repository methods + */ + } + + /* + * (non-Javadoc) + * + * @see org.vaadin.addons.lazyquerycontainer.AbstractBeanQuery#size() + */ + @Override + public int size() { + int size = getRolloutManagement().countRolloutsAll().intValue(); + if (!Strings.isNullOrEmpty(searchText)) { + size = getRolloutManagement().countRolloutsAllByFilters(searchText).intValue(); + } + return size; + } + + /** + * @return the rolloutManagement + */ + public 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() { + if (null == rolloutUIState) { + rolloutUIState = SpringContextHelper.getBean(RolloutUIState.class); + } + return rolloutUIState; + } + +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupBeanQuery.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupBeanQuery.java new file mode 100644 index 000000000..7d6a3a46f --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupBeanQuery.java @@ -0,0 +1,205 @@ +/** + * 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.rollout; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.eclipse.hawkbit.repository.RolloutGroupManagement; +import org.eclipse.hawkbit.repository.RolloutManagement; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus; +import org.eclipse.hawkbit.ui.rollout.state.RolloutUIState; +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.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; +import org.vaadin.addons.lazyquerycontainer.AbstractBeanQuery; +import org.vaadin.addons.lazyquerycontainer.QueryDefinition; + +/** + * @author gah6kor + * + */ +public class RolloutGroupBeanQuery extends AbstractBeanQuery { + + private static final long serialVersionUID = 5342450502894318589L; + + private Sort sort = new Sort(Direction.ASC, "createdAt"); + + private transient Page firstPageRolloutGroupSets = null; + + private transient RolloutManagement rolloutManagement; + + private transient RolloutGroupManagement rolloutGroupManagement; + + private transient RolloutUIState rolloutUIState; + + private final Long rolloutId; + + /** + * Parametric Constructor. + * + * @param definition + * as QueryDefinition + * @param queryConfig + * as Config + * @param sortPropertyIds + * as sort + * @param sortStates + * as Sort status + */ + public RolloutGroupBeanQuery(final QueryDefinition definition, final Map queryConfig, + final Object[] sortPropertyIds, final boolean[] sortStates) { + super(definition, queryConfig, sortPropertyIds, sortStates); + + rolloutId = getRolloutId(); + + if (HawkbitCommonUtil.checkBolArray(sortStates)) { + // Initalize Sor + sort = new Sort(sortStates[0] ? Direction.ASC : Direction.DESC, (String) sortPropertyIds[0]); + // Add sort. + for (int targetId = 1; targetId < sortPropertyIds.length; targetId++) { + sort.and(new Sort(sortStates[targetId] ? Direction.ASC : Direction.DESC, + (String) sortPropertyIds[targetId])); + } + } + } + + /** + * @return + */ + private Long getRolloutId() { + return getRolloutUIState().getRolloutId().isPresent() ? getRolloutUIState().getRolloutId().get() : null; + } + + @Override + protected ProxyRolloutGroup constructBean() { + return new ProxyRolloutGroup(); + } + + @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(); + } + return getProxyRolloutGroupList(proxyRolloutGroupsList); + } + + private List getProxyRolloutGroupList(final List rolloutGroupBeans) { + final List proxyRolloutGroupsList = new ArrayList<>(); + for (final RolloutGroup rolloutGroup : rolloutGroupBeans) { + final ProxyRolloutGroup proxyRolloutGroup = new ProxyRolloutGroup(); + proxyRolloutGroup.setName(rolloutGroup.getName()); + proxyRolloutGroup.setDescription(rolloutGroup.getDescription()); + proxyRolloutGroup.setCreatedDate(SPDateTimeUtil.getFormattedDate(rolloutGroup.getCreatedAt())); + proxyRolloutGroup.setModifiedDate(SPDateTimeUtil.getFormattedDate(rolloutGroup.getLastModifiedAt())); + proxyRolloutGroup.setCreatedBy(HawkbitCommonUtil.getIMUser(rolloutGroup.getCreatedBy())); + proxyRolloutGroup.setLastModifiedBy(HawkbitCommonUtil.getIMUser(rolloutGroup.getLastModifiedBy())); + proxyRolloutGroup.setId(rolloutGroup.getId()); + proxyRolloutGroup.setStatus(rolloutGroup.getStatus()); + proxyRolloutGroup.setErrorAction(rolloutGroup.getErrorAction()); + proxyRolloutGroup.setErrorActionExp(rolloutGroup.getErrorActionExp()); + proxyRolloutGroup.setErrorCondition(rolloutGroup.getErrorCondition()); + proxyRolloutGroup.setErrorConditionExp(rolloutGroup.getErrorConditionExp()); + proxyRolloutGroup.setSuccessCondition(rolloutGroup.getSuccessCondition()); + proxyRolloutGroup.setSuccessConditionExp(rolloutGroup.getSuccessConditionExp()); + proxyRolloutGroup.setFinishedPercentage(calculateFinishedPercentage(rolloutGroup)); + + final TotalTargetCountStatus totalTargetCountActionStatus = rolloutGroup.getTotalTargetCountStatus(); + + proxyRolloutGroup.setRunningTargetsCount( + totalTargetCountActionStatus.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.RUNNING)); + proxyRolloutGroup.setErrorTargetsCount( + totalTargetCountActionStatus.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.ERROR)); + proxyRolloutGroup.setCancelledTargetsCount( + totalTargetCountActionStatus.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.CANCELLED)); + proxyRolloutGroup.setFinishedTargetsCount( + totalTargetCountActionStatus.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.FINISHED)); + proxyRolloutGroup.setScheduledTargetsCount( + totalTargetCountActionStatus.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.SCHEDULED)); + proxyRolloutGroup.setNotStartedTargetsCount( + totalTargetCountActionStatus.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.NOTSTARTED)); + + proxyRolloutGroup.setTotalTargetsCount(String.valueOf(rolloutGroup.getTotalTargets())); + + proxyRolloutGroupsList.add(proxyRolloutGroup); + } + return proxyRolloutGroupsList; + } + + private String calculateFinishedPercentage(final RolloutGroup rolloutGroup) { + return HawkbitCommonUtil.formattingFinishedPercentage(rolloutGroup, getRolloutManagement() + .getFinishedPercentForRunningGroup(rolloutGroup.getRollout().getId(), rolloutGroup)); + } + + @Override + protected void saveBeans(final List arg0, final List arg1, + final List arg2) { + /** + * CRUD operations be done through repository methods. + */ + } + + @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 (size > Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } + + return (int) size; + } + + /** + * @return the rolloutManagement + */ + public RolloutManagement getRolloutManagement() { + if (null == rolloutManagement) { + rolloutManagement = SpringContextHelper.getBean(RolloutManagement.class); + } + return rolloutManagement; + } + + /** + * @return the rolloutManagement + */ + public RolloutGroupManagement getRolloutGroupManagement() { + if (null == rolloutGroupManagement) { + rolloutGroupManagement = SpringContextHelper.getBean(RolloutGroupManagement.class); + } + return rolloutGroupManagement; + } + + /** + * @return the rolloutUIState + */ + public RolloutUIState getRolloutUIState() { + if (null == rolloutUIState) { + rolloutUIState = SpringContextHelper.getBean(RolloutUIState.class); + } + return rolloutUIState; + } + +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupListTable.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupListTable.java new file mode 100644 index 000000000..cf1a004d8 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupListTable.java @@ -0,0 +1,361 @@ +/** + * 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.rollout; + +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +import org.eclipse.hawkbit.eventbus.event.RolloutGroupChangeEvent; +import org.eclipse.hawkbit.repository.RolloutGroupManagement; +import org.eclipse.hawkbit.repository.RolloutManagement; +import org.eclipse.hawkbit.repository.SpPermissionChecker; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus; +import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus; +import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; +import org.eclipse.hawkbit.ui.decorators.SPUIButtonStyleSmallNoBorder; +import org.eclipse.hawkbit.ui.rollout.event.RolloutEvent; +import org.eclipse.hawkbit.ui.rollout.state.RolloutUIState; +import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil; +import org.eclipse.hawkbit.ui.utils.I18N; +import org.eclipse.hawkbit.ui.utils.SPUIComponetIdProvider; +import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; +import org.eclipse.hawkbit.ui.utils.SPUILabelDefinitions; +import org.eclipse.hawkbit.ui.utils.TableColumn; +import org.springframework.beans.factory.annotation.Autowired; +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 org.vaadin.spring.events.EventBus; +import org.vaadin.spring.events.EventScope; +import org.vaadin.spring.events.annotation.EventBusListenerMethod; + +import com.vaadin.data.Container; +import com.vaadin.data.Item; +import com.vaadin.data.Property; +import com.vaadin.server.FontAwesome; +import com.vaadin.shared.ui.label.ContentMode; +import com.vaadin.spring.annotation.SpringComponent; +import com.vaadin.spring.annotation.ViewScope; +import com.vaadin.ui.Button; +import com.vaadin.ui.Component; +import com.vaadin.ui.Label; +import com.vaadin.ui.themes.ValoTheme; + +/** + * Rollout Group Table in List view. + * + */ +@SpringComponent +@ViewScope +public class RolloutGroupListTable extends AbstractSimpleTable { + + private static final String IS_ACTION_RECIEVED = "isActionRecieved"; + + private static final long serialVersionUID = 1182656768844867443L; + + @Autowired + private I18N i18n; + + @Autowired + private transient EventBus.SessionEventBus eventBus; + + @Autowired + private transient RolloutGroupManagement rolloutGroupManagement; + + @Autowired + private transient RolloutManagement rolloutManagement; + + @Autowired + private transient RolloutUIState rolloutUIState; + + @Autowired + private transient SpPermissionChecker permissionChecker; + + @Override + @PostConstruct + protected void init() { + super.init(); + eventBus.subscribe(this); + } + + @PreDestroy + void destroy() { + eventBus.unsubscribe(this); + } + + @EventBusListenerMethod(scope = EventScope.SESSION) + void onEvent(final RolloutEvent event) { + if (event == RolloutEvent.SHOW_ROLLOUT_GROUPS) { + ((LazyQueryContainer) getContainerDataSource()).refresh(); + } + } + + /** + * + * Handles the RolloutGroupChangeEvent to refresh the item in the table. + * + * + * @param rolloutGroupChangeEvent + * the event which contains the rollout group which has been + * change + */ + @EventBusListenerMethod(scope = EventScope.SESSION) + public void onEvent(final RolloutGroupChangeEvent rolloutGroupChangeEvent) { + final List visibleItemIds = (List) getVisibleItemIds(); + if (visibleItemIds.contains(rolloutGroupChangeEvent.getRolloutGroupId())) { + final RolloutGroup rolloutGroup = rolloutGroupManagement + .findRolloutGroupWithDetailedStatus(rolloutGroupChangeEvent.getRolloutGroupId()); + final TotalTargetCountStatus totalTargetCountStatus = rolloutGroup.getTotalTargetCountStatus(); + final LazyQueryContainer rolloutContainer = (LazyQueryContainer) getContainerDataSource(); + final Item item = rolloutContainer.getItem(rolloutGroup.getId()); + item.getItemProperty(SPUILabelDefinitions.VAR_STATUS).setValue(rolloutGroup.getStatus()); + item.getItemProperty(SPUILabelDefinitions.VAR_COUNT_TARGETS_RUNNING).setValue( + totalTargetCountStatus.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.RUNNING)); + item.getItemProperty(SPUILabelDefinitions.VAR_COUNT_TARGETS_ERROR) + .setValue(totalTargetCountStatus.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.ERROR)); + item.getItemProperty(SPUILabelDefinitions.VAR_COUNT_TARGETS_FINISHED).setValue( + totalTargetCountStatus.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.FINISHED)); + item.getItemProperty(SPUILabelDefinitions.VAR_COUNT_TARGETS_NOT_STARTED).setValue( + totalTargetCountStatus.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.NOTSTARTED)); + item.getItemProperty(SPUILabelDefinitions.VAR_COUNT_TARGETS_CANCELLED).setValue( + totalTargetCountStatus.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.CANCELLED)); + item.getItemProperty(SPUILabelDefinitions.VAR_COUNT_TARGETS_SCHEDULED).setValue( + totalTargetCountStatus.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.SCHEDULED)); + item.getItemProperty(IS_ACTION_RECIEVED) + .setValue(!(Boolean) item.getItemProperty(IS_ACTION_RECIEVED).getValue()); + } + } + + @Override + protected List getTableVisibleColumns() { + final List columnList = new ArrayList<>(); + columnList.add(new TableColumn(SPUIDefinitions.ROLLOUT_GROUP_NAME, i18n.get("header.name"), 0.1f)); + columnList.add(new TableColumn(SPUIDefinitions.ROLLOUT_GROUP_STATUS, i18n.get("header.status"), 0.1f)); + columnList.add(new TableColumn(SPUIDefinitions.DETAIL_STATUS, i18n.get("header.detail.status"), 0.42f)); + columnList + .add(new TableColumn(SPUILabelDefinitions.VAR_TOTAL_TARGETS, i18n.get("header.total.targets"), 0.08f)); + columnList.add(new TableColumn(SPUIDefinitions.ROLLOUT_GROUP_INSTALLED_PERCENTAGE, + i18n.get("header.rolloutgroup.installed.percentage"), 0.1f)); + columnList.add(new TableColumn(SPUIDefinitions.ROLLOUT_GROUP_ERROR_THRESHOLD, + i18n.get("header.rolloutgroup.threshold.error"), 0.1f)); + columnList.add(new TableColumn(SPUIDefinitions.ROLLOUT_GROUP_THRESHOLD, + i18n.get("header.rolloutgroup.threshold"), 0.1f)); + columnList.add(new TableColumn(SPUILabelDefinitions.VAR_CREATED_DATE, i18n.get("header.createdDate"), 0.15f)); + columnList.add(new TableColumn(SPUILabelDefinitions.VAR_CREATED_USER, i18n.get("header.createdBy"), 0.15f)); + columnList.add(new TableColumn(SPUILabelDefinitions.VAR_MODIFIED_DATE, i18n.get("header.modifiedDate"), 0.15f)); + columnList.add(new TableColumn(SPUILabelDefinitions.VAR_MODIFIED_BY, i18n.get("header.modifiedBy"), 0.15f)); + return columnList; + } + + @Override + protected Container createContainer() { + final BeanQueryFactory rolloutQf = new BeanQueryFactory<>(RolloutGroupBeanQuery.class); + return new LazyQueryContainer( + new LazyQueryDefinition(true, SPUIDefinitions.PAGE_SIZE, SPUILabelDefinitions.VAR_ID), rolloutQf); + } + + @Override + protected void addContainerProperties(final Container container) { + final LazyQueryContainer rolloutTableContainer = (LazyQueryContainer) container; + rolloutTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_ID, String.class, null, false, false); + rolloutTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_NAME, String.class, "", false, false); + rolloutTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_DESC, String.class, null, false, false); + rolloutTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_STATUS, RolloutGroupStatus.class, null, + false, false); + rolloutTableContainer.addContainerProperty(SPUIDefinitions.ROLLOUT_GROUP_INSTALLED_PERCENTAGE, String.class, + null, false, false); + rolloutTableContainer.addContainerProperty(SPUIDefinitions.ROLLOUT_GROUP_ERROR_THRESHOLD, String.class, null, + false, false); + + rolloutTableContainer.addContainerProperty(SPUIDefinitions.ROLLOUT_GROUP_THRESHOLD, String.class, null, false, + false); + + rolloutTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_CREATED_DATE, String.class, null, false, + false); + + rolloutTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_MODIFIED_DATE, String.class, null, false, + false); + rolloutTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_CREATED_USER, String.class, null, false, + false); + rolloutTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_MODIFIED_BY, String.class, null, false, + false); + rolloutTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_COUNT_TARGETS_NOT_STARTED, Long.class, 0L, + false, false); + rolloutTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_COUNT_TARGETS_RUNNING, Long.class, 0L, + false, false); + rolloutTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_COUNT_TARGETS_SCHEDULED, Long.class, 0L, + false, false); + rolloutTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_COUNT_TARGETS_ERROR, Long.class, 0L, false, + false); + rolloutTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_COUNT_TARGETS_FINISHED, Long.class, 0L, + false, false); + rolloutTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_COUNT_TARGETS_CANCELLED, Long.class, 0L, + false, false); + rolloutTableContainer.addContainerProperty(IS_ACTION_RECIEVED, Boolean.class, false, false, false); + + rolloutTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_TOTAL_TARGETS, String.class, "0", false, + false); + + } + + @Override + protected String getTableId() { + return SPUIComponetIdProvider.ROLLOUT_GROUP_LIST_TABLE_ID; + } + + @Override + protected void onValueChange() { + /** + * No implementation required. + */ + } + + @Override + protected void addCustomGeneratedColumns() { + addGeneratedColumn(SPUIDefinitions.ROLLOUT_GROUP_NAME, + (source, itemId, columnId) -> getRolloutNameLink(itemId)); + addGeneratedColumn(SPUIDefinitions.ROLLOUT_GROUP_STATUS, (source, itemId, columnId) -> getStatusLabel(itemId)); + addGeneratedColumn(SPUIDefinitions.DETAIL_STATUS, (source, itemId, columnId) -> getProgressBar(itemId)); + setColumnAlignment(SPUIDefinitions.ROLLOUT_GROUP_STATUS, Align.CENTER); + + } + + private Label getStatusLabel(final Object itemId) { + final Label statusLabel = new Label(); + statusLabel.setHeightUndefined(); + statusLabel.setContentMode(ContentMode.HTML); + setStatusIcon(itemId, statusLabel); + statusLabel.setDescription(getDescription(itemId)); + statusLabel.setSizeUndefined(); + addPropertyChangeListener(itemId, statusLabel); + return statusLabel; + } + + private void addPropertyChangeListener(final Object itemId, final Label statusLabel) { + final Property status = getContainerProperty(itemId, SPUILabelDefinitions.VAR_STATUS); + final Property.ValueChangeNotifier notifier = (Property.ValueChangeNotifier) status; + notifier.addValueChangeListener(new ValueChangeListener() { + @Override + public void valueChange(final com.vaadin.data.Property.ValueChangeEvent event) { + setStatusIcon(itemId, statusLabel); + } + }); + } + + private String getDescription(final Object itemId) { + final Item item = getItem(itemId); + if (item != null) { + final RolloutGroupStatus rolloutGroupStatus = (RolloutGroupStatus) item + .getItemProperty(SPUILabelDefinitions.VAR_STATUS).getValue(); + return rolloutGroupStatus.toString().toLowerCase(); + } + return null; + } + + private void setStatusIcon(final Object itemId, final Label statusLabel) { + final Item item = getItem(itemId); + if (item != null) { + final RolloutGroupStatus rolloutGroupStatus = (RolloutGroupStatus) item + .getItemProperty(SPUILabelDefinitions.VAR_STATUS).getValue(); + setRolloutStatusIcon(rolloutGroupStatus, statusLabel); + } + } + + private void setRolloutStatusIcon(final RolloutGroupStatus rolloutGroupStatus, final Label statusLabel) { + switch (rolloutGroupStatus) { + case FINISHED: + statusLabel.setValue(FontAwesome.CHECK_CIRCLE.getHtml()); + statusLabel.setStyleName("statusIconGreen"); + break; + case SCHEDULED: + statusLabel.setValue(FontAwesome.BULLSEYE.getHtml()); + statusLabel.setStyleName("statusIconBlue"); + break; + case RUNNING: + statusLabel.setValue(FontAwesome.ADJUST.getHtml()); + statusLabel.setStyleName("statusIconYellow"); + break; + case READY: + statusLabel.setValue(FontAwesome.DOT_CIRCLE_O.getHtml()); + statusLabel.setStyleName("statusIconLightBlue"); + break; + case ERROR: + statusLabel.setValue(FontAwesome.EXCLAMATION_CIRCLE.getHtml()); + statusLabel.setStyleName("statusIconRed"); + break; + default: + break; + } + statusLabel.addStyleName(ValoTheme.LABEL_SMALL); + } + + private Component getRolloutNameLink(final Object itemId) { + final Item row = getItem(itemId); + final String rolloutGroupName = (String) row.getItemProperty(SPUILabelDefinitions.VAR_NAME).getValue(); + if (permissionChecker.hasRolloutTargetsReadPermission()) { + final Button rolloutGroupNameLink = SPUIComponentProvider.getButton(getDetailLinkId(rolloutGroupName), + rolloutGroupName, SPUILabelDefinitions.SHOW_ROLLOUT_GROUP_DETAILS, null, false, null, + SPUIButtonStyleSmallNoBorder.class); + rolloutGroupNameLink.setData(rolloutGroupName); + rolloutGroupNameLink.addStyleName(ValoTheme.LINK_SMALL + " " + "on-focus-no-border link"); + rolloutGroupNameLink.addClickListener(event -> showRolloutGroups(itemId)); + return rolloutGroupNameLink; + } else { + final Label rolloutGroupNameLabel = new Label(); + rolloutGroupNameLabel.setHeightUndefined(); + rolloutGroupNameLabel.addStyleName(ValoTheme.LABEL_SMALL); + rolloutGroupNameLabel.setValue(rolloutGroupName); + return rolloutGroupNameLabel; + } + } + + private void showRolloutGroups(final Object itemId) { + rolloutUIState.setRolloutGroup(rolloutGroupManagement.findRolloutGroupWithDetailedStatus((Long) itemId)); + eventBus.publish(this, RolloutEvent.SHOW_ROLLOUT_GROUP_TARGETS); + } + + private DistributionBar getProgressBar(final Object itemId) { + final DistributionBar bar = new DistributionBar(2); + bar.setSizeFull(); + bar.setZeroSizedVisible(false); + HawkbitCommonUtil.initialiseProgressBar(bar, getItem(itemId)); + addPropertyChangeListenerOnActionRecieved(itemId, bar); + return bar; + } + + private void addPropertyChangeListenerOnActionRecieved(final Object itemId, final DistributionBar bar) { + final Property status = getContainerProperty(itemId, IS_ACTION_RECIEVED); + final Property.ValueChangeNotifier notifier = (Property.ValueChangeNotifier) status; + notifier.addValueChangeListener(new ValueChangeListener() { + @Override + public void valueChange(final com.vaadin.data.Property.ValueChangeEvent event) { + HawkbitCommonUtil.initialiseProgressBar(bar, getItem(itemId)); + } + }); + } + + private static String getDetailLinkId(final String rolloutGroupName) { + return new StringBuilder(SPUIComponetIdProvider.ROLLOUT_GROUP_NAME_LINK_ID).append('.').append(rolloutGroupName) + .toString(); + } + + @Override + protected void setCollapsiblecolumns() { + setColumnCollapsed(SPUILabelDefinitions.VAR_CREATED_DATE, true); + setColumnCollapsed(SPUILabelDefinitions.VAR_MODIFIED_DATE, true); + setColumnCollapsed(SPUILabelDefinitions.VAR_CREATED_USER, true); + setColumnCollapsed(SPUILabelDefinitions.VAR_MODIFIED_BY, true); + } + +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupTargetsBeanQuery.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupTargetsBeanQuery.java new file mode 100644 index 000000000..b4d78a4d5 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupTargetsBeanQuery.java @@ -0,0 +1,179 @@ +/** + * 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.rollout; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.eclipse.hawkbit.repository.RolloutGroupManagement; +import org.eclipse.hawkbit.repository.RolloutManagement; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.model.TargetWithActionStatus; +import org.eclipse.hawkbit.ui.components.ProxyTarget; +import org.eclipse.hawkbit.ui.rollout.state.RolloutUIState; +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.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; +import org.vaadin.addons.lazyquerycontainer.AbstractBeanQuery; +import org.vaadin.addons.lazyquerycontainer.QueryDefinition; + +/** + * @author gah6kor + * + */ +public class RolloutGroupTargetsBeanQuery extends AbstractBeanQuery { + + private static final long serialVersionUID = -8841076207255485907L; + + private final Sort sort = new Sort(Direction.ASC, "createdAt"); + + private transient Page firstPageTargetSets = null; + + private transient RolloutManagement rolloutManagement; + + private transient RolloutGroupManagement rolloutGroupManagement; + + private transient RolloutUIState rolloutUIState; + + private final RolloutGroup rolloutGroup; + + /** + * Parametric Constructor. + * + * @param definition + * as QueryDefinition + * @param queryConfig + * as Config + * @param sortPropertyIds + * as sort + * @param sortStates + * as Sort status + */ + public RolloutGroupTargetsBeanQuery(final QueryDefinition definition, final Map queryConfig, + final Object[] sortPropertyIds, final boolean[] sortStates) { + + super(definition, queryConfig, sortPropertyIds, sortStates); + + rolloutGroup = getRolloutUIState().getRolloutGroup().isPresent() ? getRolloutUIState().getRolloutGroup().get() + : null; + + } + + @Override + protected ProxyTarget constructBean() { + return new ProxyTarget(); + } + + @Override + protected List loadBeans(final int startIndex, final int count) { + List rolloutGroupTargetsList = new ArrayList<>(); + if (startIndex == 0 && firstPageTargetSets != null) { + rolloutGroupTargetsList = firstPageTargetSets.getContent(); + } else if (null != rolloutGroup) { + rolloutGroupTargetsList = getRolloutGroupManagement().findAllTargetsWithActionStatus( + new PageRequest(startIndex / count, count), rolloutGroup).getContent(); + } + return getProxyRolloutGroupTargetsList(rolloutGroupTargetsList); + } + + private List getProxyRolloutGroupTargetsList(final List rolloutGroupTargets) { + final List proxyTargetBeans = new ArrayList<>(); + for (final TargetWithActionStatus targetWithActionStatus : rolloutGroupTargets) { + final Target targ = targetWithActionStatus.getTarget(); + final ProxyTarget prxyTarget = new ProxyTarget(); + prxyTarget.setTargetIdName(targ.getTargetIdName()); + prxyTarget.setName(targ.getName()); + prxyTarget.setDescription(targ.getDescription()); + prxyTarget.setControllerId(targ.getControllerId()); + prxyTarget.setInstallationDate(targ.getTargetInfo().getInstallationDate()); + prxyTarget.setAddress(targ.getTargetInfo().getAddress()); + prxyTarget.setLastTargetQuery(targ.getTargetInfo().getLastTargetQuery()); + prxyTarget.setLastModifiedDate(SPDateTimeUtil.getFormattedDate(targ.getLastModifiedAt())); + prxyTarget.setCreatedDate(SPDateTimeUtil.getFormattedDate(targ.getCreatedAt())); + prxyTarget.setCreatedAt(targ.getCreatedAt()); + prxyTarget.setCreatedByUser(HawkbitCommonUtil.getIMUser(targ.getCreatedBy())); + prxyTarget.setModifiedByUser(HawkbitCommonUtil.getIMUser(targ.getLastModifiedBy())); + if (targetWithActionStatus.getStatus() != null) { + prxyTarget.setStatus(targetWithActionStatus.getStatus()); + } + prxyTarget.setLastTargetQuery(targ.getTargetInfo().getLastTargetQuery()); + prxyTarget.setTargetInfo(targ.getTargetInfo()); + prxyTarget.setId(targ.getId()); + if (targ.getAssignedDistributionSet() != null) { + prxyTarget.setAssignedDistNameVersion(HawkbitCommonUtil.getFormattedNameVersion(targ + .getAssignedDistributionSet().getName(), targ.getAssignedDistributionSet().getVersion())); + } + proxyTargetBeans.add(prxyTarget); + + } + return proxyTargetBeans; + } + + @Override + protected void saveBeans(final List arg0, final List arg1, final List arg2) { + /** + * No implementation required. + */ + } + + @Override + public int size() { + long size = 0; + if (null != rolloutGroup) { + firstPageTargetSets = getRolloutGroupManagement().findAllTargetsWithActionStatus( + new PageRequest(0, SPUIDefinitions.PAGE_SIZE, sort), rolloutGroup); + size = firstPageTargetSets.getTotalElements(); + } + getRolloutUIState().setRolloutGroupTargetsTotalCount(size); + if (size > SPUIDefinitions.MAX_TARGET_TABLE_ENTRIES) { + getRolloutUIState().setRolloutGroupTargetsTruncated(size - SPUIDefinitions.MAX_TARGET_TABLE_ENTRIES); + return SPUIDefinitions.MAX_TARGET_TABLE_ENTRIES; + } + + return (int) size; + } + + /** + * @return the rolloutManagement + */ + public RolloutManagement getRolloutManagement() { + if (null == rolloutManagement) { + rolloutManagement = SpringContextHelper.getBean(RolloutManagement.class); + } + return rolloutManagement; + } + + /** + * @return the rolloutManagement + */ + public RolloutGroupManagement getRolloutGroupManagement() { + if (null == rolloutGroupManagement) { + rolloutGroupManagement = SpringContextHelper.getBean(RolloutGroupManagement.class); + } + return rolloutGroupManagement; + } + + /** + * @return the rolloutUIState + */ + public RolloutUIState getRolloutUIState() { + if (null == rolloutUIState) { + rolloutUIState = SpringContextHelper.getBean(RolloutUIState.class); + } + return rolloutUIState; + } + +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupTargetsCountLabelMessage.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupTargetsCountLabelMessage.java new file mode 100644 index 000000000..9bc24c251 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupTargetsCountLabelMessage.java @@ -0,0 +1,121 @@ +/** + * 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.rollout; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +import org.eclipse.hawkbit.ui.rollout.event.RolloutEvent; +import org.eclipse.hawkbit.ui.rollout.state.RolloutUIState; +import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil; +import org.eclipse.hawkbit.ui.utils.I18N; +import org.eclipse.hawkbit.ui.utils.SPUIComponetIdProvider; +import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; +import org.eclipse.hawkbit.ui.utils.SPUILabelDefinitions; +import org.springframework.beans.factory.annotation.Autowired; +import org.vaadin.spring.events.EventBus; +import org.vaadin.spring.events.EventScope; +import org.vaadin.spring.events.annotation.EventBusListenerMethod; + +import com.vaadin.server.FontAwesome; +import com.vaadin.shared.ui.label.ContentMode; +import com.vaadin.spring.annotation.SpringComponent; +import com.vaadin.spring.annotation.ViewScope; +import com.vaadin.ui.Label; + +/** + * count message label for the targets of the rollout group. + * + */ +@SpringComponent +@ViewScope +public class RolloutGroupTargetsCountLabelMessage extends Label { + + /** + * + */ + private static final long serialVersionUID = -3876685878918411453L; + + @Autowired + private transient RolloutUIState rolloutUIState; + + @Autowired + private transient RolloutGroupTargetsListTable rolloutGroupTargetsListTable; + + @Autowired + private I18N i18n; + + @Autowired + private transient EventBus.SessionEventBus eventBus; + + /** + * PostConstruct method called by spring after bean has been initialized. + */ + @PostConstruct + public void postConstruct() { + applyStyle(); + displayRolloutGroupTargetMessage(); + eventBus.subscribe(this); + } + + @PreDestroy + void destroy() { + eventBus.unsubscribe(this); + } + + /** + * Event Listener to show the message count. + * + * @param event + */ + @EventBusListenerMethod(scope = EventScope.SESSION) + public void onEvent(final RolloutEvent event) { + if (event == RolloutEvent.SHOW_ROLLOUT_GROUP_TARGETS_COUNT) { + displayRolloutGroupTargetMessage(); + + } + } + + /** + * + */ + private void applyStyle() { + /* Create label for Targets count message displaying below the table */ + addStyleName(SPUILabelDefinitions.SP_LABEL_MESSAGE_STYLE); + setContentMode(ContentMode.HTML); + setId(SPUIComponetIdProvider.COUNT_LABEL); + } + + private void displayRolloutGroupTargetMessage() { + long totalTargetTableEnteries = rolloutGroupTargetsListTable.size(); + if (rolloutUIState.getRolloutGroupTargetsTruncated() != null) { + // set the icon + setIcon(FontAwesome.INFO_CIRCLE); + setDescription(i18n.get("rollout.group.label.target.truncated", + rolloutUIState.getRolloutGroupTargetsTruncated(), SPUIDefinitions.MAX_TARGET_TABLE_ENTRIES)); + totalTargetTableEnteries += rolloutUIState.getRolloutGroupTargetsTruncated(); + } else { + setIcon(null); + setDescription(null); + } + + final StringBuilder message = new StringBuilder(i18n.get("label.target.filter.count")); + message.append(rolloutUIState.getRolloutGroupTargetsTotalCount()); + message.append(HawkbitCommonUtil.SP_STRING_SPACE); + if (totalTargetTableEnteries > SPUIDefinitions.MAX_TARGET_TABLE_ENTRIES) { + message.append(i18n.get("label.filter.shown")); + message.append(SPUIDefinitions.MAX_TARGET_TABLE_ENTRIES); + } else { + message.append(i18n.get("label.filter.shown")); + message.append(rolloutGroupTargetsListTable.size()); + } + message.append(HawkbitCommonUtil.SP_STRING_SPACE); + setCaption(message.toString()); + } +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupTargetsListHeader.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupTargetsListHeader.java new file mode 100644 index 000000000..562569136 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupTargetsListHeader.java @@ -0,0 +1,218 @@ +/** + * 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.rollout; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; +import org.eclipse.hawkbit.ui.decorators.SPUIButtonStyleSmallNoBorder; +import org.eclipse.hawkbit.ui.rollout.event.RolloutEvent; +import org.eclipse.hawkbit.ui.rollout.state.RolloutUIState; +import org.eclipse.hawkbit.ui.utils.I18N; +import org.eclipse.hawkbit.ui.utils.SPUIComponetIdProvider; +import org.eclipse.hawkbit.ui.utils.SPUILabelDefinitions; +import org.springframework.beans.factory.annotation.Autowired; +import org.vaadin.spring.events.EventBus; +import org.vaadin.spring.events.EventScope; +import org.vaadin.spring.events.annotation.EventBusListenerMethod; + +import com.vaadin.spring.annotation.SpringComponent; +import com.vaadin.spring.annotation.ViewScope; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.Label; +import com.vaadin.ui.themes.ValoTheme; + +/** + * Header Layout of Rollout Group Targets list view. + * + */ +@SpringComponent +@ViewScope +public class RolloutGroupTargetsListHeader extends AbstractSimpleTableHeader { + + private static final long serialVersionUID = 5613986489156507581L; + @Autowired + private transient EventBus.SessionEventBus eventBus; + + @Autowired + private I18N i18n; + + @Autowired + private RolloutUIState rolloutUiState; + + private Button rolloutsGroupViewLink; + private Label headerCaption; + + @PostConstruct + protected void init() { + super.init(); + eventBus.subscribe(this); + } + + @PreDestroy + void destroy() { + eventBus.unsubscribe(this); + } + + @EventBusListenerMethod(scope = EventScope.SESSION) + void onEvent(final RolloutEvent event) { + if (event == RolloutEvent.SHOW_ROLLOUT_GROUP_TARGETS) { + setCaptionDetails(); + } + } + + private void setCaptionDetails() { + RolloutGroup rolloutGroup; + if (rolloutUiState.getRolloutGroup().isPresent()) { + rolloutGroup = rolloutUiState.getRolloutGroup().get(); + headerCaption.setCaption(rolloutGroup.getName()); + } + + rolloutsGroupViewLink.setCaption(rolloutUiState.getRolloutName().isPresent() ? rolloutUiState.getRolloutName() + .get() : ""); + } + + @Override + protected void resetSearchText() { + /** + * No implementation required. + */ + } + + @Override + protected String getSearchBoxId() { + + return null; + } + + @Override + protected String getSearchRestIconId() { + + return null; + } + + @Override + protected void searchBy(final String newSearchText) { + /** + * No implementation required. + */ + + } + + @Override + protected String getAddIconId() { + + return null; + } + + @Override + protected void addNewItem(final ClickEvent event) { + /** + * No implementation required. + */ + } + + @Override + protected void onClose(final ClickEvent event) { + eventBus.publish(this, RolloutEvent.SHOW_ROLLOUT_GROUPS); + } + + @Override + protected boolean hasCreatePermission() { + + return false; + } + + @Override + protected String getCloseButtonId() { + return SPUIComponetIdProvider.ROLLOUT_TARGET_VIEW_CLOSE_BUTTON_ID; + } + + @Override + protected boolean showCloseButton() { + + return true; + } + + @Override + protected boolean isAllowSearch() { + + return false; + } + + @Override + protected String onLoadSearchBoxValue() { + + return null; + } + + @Override + protected boolean isRollout() { + + return false; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.hawkbit.ui.rollout.AbstractSimpleTableHeader# + * getHeaderCaptionLayout() + */ + @Override + protected HorizontalLayout getHeaderCaptionLayout() { + headerCaption = SPUIComponentProvider.getLabel("", SPUILabelDefinitions.SP_WIDGET_CAPTION); + headerCaption.setStyleName(ValoTheme.LABEL_BOLD + " " + ValoTheme.LABEL_SMALL); + final Button rolloutsListViewLink = SPUIComponentProvider.getButton(null, "", "", null, false, null, + SPUIButtonStyleSmallNoBorder.class); + rolloutsListViewLink.setStyleName(ValoTheme.LINK_SMALL + " " + "on-focus-no-border link rollout-caption-links"); + rolloutsListViewLink.setDescription(i18n.get("message.rollouts")); + rolloutsListViewLink.setCaption(i18n.get("message.rollouts")); + rolloutsListViewLink.addClickListener(value -> showRolloutListView()); + + rolloutsGroupViewLink = 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()); + + final HorizontalLayout headerCaptionLayout = new HorizontalLayout(); + headerCaptionLayout.addComponent(rolloutsListViewLink); + headerCaptionLayout.addComponent(new Label(">")); + headerCaptionLayout.addComponent(rolloutsGroupViewLink); + headerCaptionLayout.addComponent(new Label(">")); + headerCaptionLayout.addComponent(headerCaption); + + return headerCaptionLayout; + } + + private void showRolloutGroupListView() { + eventBus.publish(this, RolloutEvent.SHOW_ROLLOUT_GROUPS); + } + + private void showRolloutListView() { + eventBus.publish(this, RolloutEvent.SHOW_ROLLOUTS); + } + + /* + * (non-Javadoc) + * + * @see + * org.eclipse.hawkbit.ui.rollout.AbstractSimpleTableHeader#restoreCaption() + */ + @Override + protected void restoreCaption() { + setCaptionDetails(); + } + +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupTargetsListTable.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupTargetsListTable.java new file mode 100644 index 000000000..5cb8543ac --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupTargetsListTable.java @@ -0,0 +1,229 @@ +/** + * 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.rollout; + +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +import org.eclipse.hawkbit.repository.model.Action.Status; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus; +import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; +import org.eclipse.hawkbit.ui.rollout.event.RolloutEvent; +import org.eclipse.hawkbit.ui.rollout.state.RolloutUIState; +import org.eclipse.hawkbit.ui.utils.I18N; +import org.eclipse.hawkbit.ui.utils.SPUIComponetIdProvider; +import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; +import org.eclipse.hawkbit.ui.utils.SPUILabelDefinitions; +import org.eclipse.hawkbit.ui.utils.TableColumn; +import org.springframework.beans.factory.annotation.Autowired; +import org.vaadin.addons.lazyquerycontainer.BeanQueryFactory; +import org.vaadin.addons.lazyquerycontainer.LazyQueryContainer; +import org.vaadin.addons.lazyquerycontainer.LazyQueryDefinition; +import org.vaadin.spring.events.EventBus; +import org.vaadin.spring.events.EventScope; +import org.vaadin.spring.events.annotation.EventBusListenerMethod; + +import com.vaadin.data.Container; +import com.vaadin.data.Item; +import com.vaadin.server.FontAwesome; +import com.vaadin.shared.ui.label.ContentMode; +import com.vaadin.spring.annotation.SpringComponent; +import com.vaadin.spring.annotation.ViewScope; +import com.vaadin.ui.Label; +import com.vaadin.ui.themes.ValoTheme; + +/** + * Rollout Group Targets Table in List view. + * + */ +@SpringComponent +@ViewScope +public class RolloutGroupTargetsListTable extends AbstractSimpleTable { + + private static final long serialVersionUID = 7984314603271801746L; + + @Autowired + private I18N i18n; + + @Autowired + private transient EventBus.SessionEventBus eventBus; + + @Autowired + private transient RolloutUIState rolloutUIState; + + @PostConstruct + protected void init() { + super.init(); + eventBus.subscribe(this); + } + + @PreDestroy + void destroy() { + eventBus.unsubscribe(this); + } + + @EventBusListenerMethod(scope = EventScope.SESSION) + void onEvent(final RolloutEvent event) { + if (event == RolloutEvent.SHOW_ROLLOUT_GROUP_TARGETS) { + ((LazyQueryContainer) getContainerDataSource()).refresh(); + eventBus.publish(this, RolloutEvent.SHOW_ROLLOUT_GROUP_TARGETS_COUNT); + } + } + + @Override + protected List getTableVisibleColumns() { + final List columnList = new ArrayList<>(); + columnList.add(new TableColumn(SPUILabelDefinitions.VAR_NAME, i18n.get("header.name"), 0.15f)); + columnList.add(new TableColumn(SPUILabelDefinitions.VAR_CREATED_BY, i18n.get("header.createdBy"), 0.15f)); + columnList.add(new TableColumn(SPUILabelDefinitions.VAR_CREATED_DATE, i18n.get("header.createdDate"), 0.15f)); + columnList + .add(new TableColumn(SPUILabelDefinitions.VAR_LAST_MODIFIED_BY, i18n.get("header.modifiedBy"), 0.15f)); + columnList.add(new TableColumn(SPUILabelDefinitions.VAR_LAST_MODIFIED_DATE, i18n.get("header.modifiedDate"), + 0.15f)); + columnList.add(new TableColumn(SPUILabelDefinitions.VAR_DESC, i18n.get("header.description"), 0.15f)); + + columnList.add(new TableColumn(SPUILabelDefinitions.VAR_TARGET_STATUS, i18n.get("header.status"), 0.1f)); + + return columnList; + } + + @Override + protected Container createContainer() { + final BeanQueryFactory rolloutgrouBeanQueryFactory = new BeanQueryFactory<>( + RolloutGroupTargetsBeanQuery.class); + return new LazyQueryContainer(new LazyQueryDefinition(true, SPUIDefinitions.PAGE_SIZE, + SPUILabelDefinitions.VAR_ID), rolloutgrouBeanQueryFactory); + } + + @Override + protected void addContainerProperties(final Container container) { + final LazyQueryContainer rolloutGroupTargetTableContainer = (LazyQueryContainer) container; + rolloutGroupTargetTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_CONT_ID, String.class, "", + false, false); + rolloutGroupTargetTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_NAME, String.class, "", false, + true); + rolloutGroupTargetTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_STATUS, Status.class, + Status.RETRIEVED, false, false); + + rolloutGroupTargetTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_TARGET_STATUS, + TargetUpdateStatus.class, TargetUpdateStatus.UNKNOWN, false, false); + + rolloutGroupTargetTableContainer.addContainerProperty(SPUILabelDefinitions.ASSIGNED_DISTRIBUTION_NAME_VER, + String.class, "", false, true); + rolloutGroupTargetTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_CREATED_BY, String.class, null, + false, true); + rolloutGroupTargetTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_LAST_MODIFIED_BY, String.class, + null, false, true); + rolloutGroupTargetTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_CREATED_DATE, String.class, + null, false, true); + rolloutGroupTargetTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_LAST_MODIFIED_DATE, + String.class, null, false, true); + rolloutGroupTargetTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_DESC, String.class, "", false, + true); + } + + @Override + protected String getTableId() { + + return SPUIComponetIdProvider.ROLLOUT_GROUP_TARGETS_LIST_TABLE_ID; + } + + @Override + protected void onValueChange() { + /** + * No implementation required. + */ + + } + + @Override + protected void addCustomGeneratedColumns() { + addGeneratedColumn(SPUILabelDefinitions.VAR_TARGET_STATUS, (source, itemId, columnId) -> getStatusLabel(itemId)); + setColumnAlignment(SPUILabelDefinitions.VAR_TARGET_STATUS, Align.CENTER); + } + + @Override + protected void setCollapsiblecolumns() { + /** + * No implementation required. + */ + } + + private Label getStatusLabel(final Object itemId) { + final Label statusLabel = new Label(); + statusLabel.addStyleName(ValoTheme.LABEL_SMALL); + statusLabel.setHeightUndefined(); + statusLabel.setContentMode(ContentMode.HTML); + setStatusIcon(itemId, statusLabel); + statusLabel.setSizeUndefined(); + return statusLabel; + } + + private void setStatusIcon(final Object itemId, final Label statusLabel) { + final Item item = getItem(itemId); + final RolloutGroup rolloutGroup = rolloutUIState.getRolloutGroup().isPresent() ? rolloutUIState + .getRolloutGroup().get() : null; + if (item != null) { + final Status status = (Status) item.getItemProperty(SPUILabelDefinitions.VAR_STATUS).getValue(); + if (status == null) { + if (rolloutGroup != null && rolloutGroup.getStatus() == RolloutGroupStatus.READY) { + statusLabel.setValue(FontAwesome.DOT_CIRCLE_O.getHtml()); + statusLabel.addStyleName("statusIconLightBlue"); + statusLabel.setDescription(RolloutGroupStatus.READY.toString().toLowerCase()); + } else if (rolloutGroup != null && rolloutGroup.getStatus() == RolloutGroupStatus.FINISHED) { + statusLabel.setValue(FontAwesome.MINUS_CIRCLE.getHtml()); + statusLabel.addStyleName("statusIconBlue"); + + final String dsNameVersion = (String) item.getItemProperty( + SPUILabelDefinitions.ASSIGNED_DISTRIBUTION_NAME_VER).getValue(); + statusLabel.setDescription(i18n + .get("message.dist.already.assigned", new Object[] { dsNameVersion })); + } + } else { + setRolloutStatusIcon(status, statusLabel); + statusLabel.setDescription(status.toString().toLowerCase()); + } + } + } + + private void setRolloutStatusIcon(final Status targetUpdateStatus, final Label statusLabel) { + switch (targetUpdateStatus) { + case ERROR: + statusLabel.setValue(FontAwesome.EXCLAMATION_CIRCLE.getHtml()); + statusLabel.addStyleName("statusIconRed"); + break; + case SCHEDULED: + statusLabel.setValue(FontAwesome.BULLSEYE.getHtml()); + statusLabel.addStyleName("statusIconBlue"); + break; + case FINISHED: + statusLabel.setValue(FontAwesome.CHECK_CIRCLE.getHtml()); + statusLabel.addStyleName("statusIconGreen"); + break; + case RUNNING: + case RETRIEVED: + case WARNING: + case DOWNLOAD: + statusLabel.setValue(FontAwesome.ADJUST.getHtml()); + statusLabel.addStyleName("statusIconYellow"); + break; + case CANCELED: + case CANCELING: + statusLabel.setValue(FontAwesome.TIMES_CIRCLE.getHtml()); + statusLabel.addStyleName("statusIconPending"); + break; + default: + break; + } + } +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupTargetsListView.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupTargetsListView.java new file mode 100644 index 000000000..40b8f6eb7 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupTargetsListView.java @@ -0,0 +1,57 @@ +/** + * 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.rollout; + +import javax.annotation.PostConstruct; + +import org.springframework.beans.factory.annotation.Autowired; + +import com.vaadin.spring.annotation.SpringComponent; +import com.vaadin.spring.annotation.ViewScope; +import com.vaadin.ui.Label; + +/** + * Rollout Group Targets List View. + */ +@SpringComponent +@ViewScope +public class RolloutGroupTargetsListView extends AbstractSimpleTableLayout { + + private static final long serialVersionUID = 26089134783467012L; + + @Autowired + private RolloutGroupTargetsListHeader rolloutGroupTargetsListHeader; + + @Autowired + private RolloutGroupTargetsListTable rolloutGroupTargetsListTable; + + @Autowired + private RolloutGroupTargetsCountLabelMessage rolloutGroupTargetsCountLabelMessage; + + /** + * Initialization of Rollout group component. + */ + @PostConstruct + protected void init() { + super.init(rolloutGroupTargetsListHeader, rolloutGroupTargetsListTable); + } + + @Override + protected boolean hasCountMessage() { + + return true; + } + + @Override + protected Label getCountMessageLabel() { + + return rolloutGroupTargetsCountLabelMessage; + } + +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupsListHeader.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupsListHeader.java new file mode 100644 index 000000000..58cc2a753 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupsListHeader.java @@ -0,0 +1,202 @@ +/** + * 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.rollout; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; +import org.eclipse.hawkbit.ui.decorators.SPUIButtonStyleSmallNoBorder; +import org.eclipse.hawkbit.ui.rollout.event.RolloutEvent; +import org.eclipse.hawkbit.ui.rollout.state.RolloutUIState; +import org.eclipse.hawkbit.ui.utils.I18N; +import org.eclipse.hawkbit.ui.utils.SPUIComponetIdProvider; +import org.eclipse.hawkbit.ui.utils.SPUILabelDefinitions; +import org.springframework.beans.factory.annotation.Autowired; +import org.vaadin.spring.events.EventBus; +import org.vaadin.spring.events.EventScope; +import org.vaadin.spring.events.annotation.EventBusListenerMethod; + +import com.vaadin.spring.annotation.SpringComponent; +import com.vaadin.spring.annotation.ViewScope; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.Label; +import com.vaadin.ui.themes.ValoTheme; + +/** + * Header Layout of Rollout Group list view. + * + */ +@SpringComponent +@ViewScope +public class RolloutGroupsListHeader extends AbstractSimpleTableHeader { + + private static final long serialVersionUID = 5077741997839715209L; + + @Autowired + private transient EventBus.SessionEventBus eventBus; + + @Autowired + private RolloutUIState rolloutUiState; + + @Autowired + private I18N i18n; + + private Label headerCaption; + + @PostConstruct + protected void init() { + super.init(); + eventBus.subscribe(this); + } + + @PreDestroy + void destroy() { + eventBus.unsubscribe(this); + } + + @EventBusListenerMethod(scope = EventScope.SESSION) + void onEvent(final RolloutEvent event) { + if (event == RolloutEvent.SHOW_ROLLOUT_GROUPS) { + setCaptionDetails(); + } + } + + private void setCaptionDetails() { + headerCaption.setCaption(rolloutUiState.getRolloutName().isPresent() ? rolloutUiState.getRolloutName().get() + : ""); + } + + @Override + protected void resetSearchText() { + /** + * No implementation required. + */ + } + + @Override + protected String getSearchBoxId() { + /** + * No implementation required. + */ + return null; + } + + @Override + protected String getSearchRestIconId() { + /** + * No implementation required. + */ + return null; + } + + @Override + protected void searchBy(final String newSearchText) { + /** + * No implementation required. + */ + + } + + @Override + protected String getAddIconId() { + /** + * No implementation required. + */ + return null; + } + + @Override + protected void addNewItem(final ClickEvent event) { + /** + * No implementation required. + */ + } + + @Override + protected void onClose(final ClickEvent event) { + eventBus.publish(this, RolloutEvent.SHOW_ROLLOUTS); + + } + + @Override + protected boolean hasCreatePermission() { + + return true; + } + + @Override + protected String getCloseButtonId() { + return SPUIComponetIdProvider.ROLLOUT_GROUP_CLOSE; + } + + @Override + protected boolean showCloseButton() { + + return true; + } + + @Override + protected boolean isAllowSearch() { + return false; + } + + @Override + protected String onLoadSearchBoxValue() { + return null; + } + + @Override + protected boolean isRollout() { + return false; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.hawkbit.ui.rollout.AbstractSimpleTableHeader# + * getHeaderCaptionLayout() + */ + @Override + protected HorizontalLayout getHeaderCaptionLayout() { + headerCaption = SPUIComponentProvider.getLabel("", SPUILabelDefinitions.SP_WIDGET_CAPTION); + headerCaption.setId(SPUIComponetIdProvider.ROLLOUT_GROUP_HEADER_CAPTION); + final Button rolloutsListViewLink = SPUIComponentProvider.getButton(null, "", "", null, false, null, + SPUIButtonStyleSmallNoBorder.class); + rolloutsListViewLink.setStyleName(ValoTheme.LINK_SMALL + " " + "on-focus-no-border link rollout-caption-links"); + rolloutsListViewLink.setDescription(i18n.get("message.rollouts")); + rolloutsListViewLink.setCaption(i18n.get("message.rollouts")); + rolloutsListViewLink.addClickListener(value -> showRolloutListView()); + + final HorizontalLayout headerCaptionLayout = new HorizontalLayout(); + headerCaptionLayout.addComponent(rolloutsListViewLink); + headerCaptionLayout.addComponent(new Label(">")); + headerCaptionLayout.addComponent(headerCaption); + + return headerCaptionLayout; + } + + /* + * (non-Javadoc) + * + * @see + * org.eclipse.hawkbit.ui.rollout.AbstractSimpleTableHeader#restoreCaption() + */ + @Override + protected void restoreCaption() { + setCaptionDetails(); + } + + private void showRolloutListView() { + eventBus.publish(this, RolloutEvent.SHOW_ROLLOUTS); + } + +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupsListView.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupsListView.java new file mode 100644 index 000000000..0725da6ee --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutGroupsListView.java @@ -0,0 +1,52 @@ +/** + * 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.rollout; + +import javax.annotation.PostConstruct; + +import org.springframework.beans.factory.annotation.Autowired; + +import com.vaadin.spring.annotation.SpringComponent; +import com.vaadin.spring.annotation.ViewScope; +import com.vaadin.ui.Label; + +/** + * Groups List View. + * + */ +@SpringComponent +@ViewScope +public class RolloutGroupsListView extends AbstractSimpleTableLayout { + + private static final long serialVersionUID = 7252345838154270259L; + + @Autowired + private RolloutGroupsListHeader rolloutGroupListHeader; + + @Autowired + private RolloutGroupListTable rolloutGroupListTable; + + @PostConstruct + protected void init() { + super.init(rolloutGroupListHeader, rolloutGroupListTable); + } + + @Override + protected boolean hasCountMessage() { + + return false; + } + + @Override + protected Label getCountMessageLabel() { + + return null; + } + +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutListHeader.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutListHeader.java new file mode 100644 index 000000000..6abfdb584 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutListHeader.java @@ -0,0 +1,169 @@ +/** + * 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.rollout; + +import javax.annotation.PostConstruct; + +import org.eclipse.hawkbit.repository.SpPermissionChecker; +import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; +import org.eclipse.hawkbit.ui.rollout.event.RolloutEvent; +import org.eclipse.hawkbit.ui.rollout.state.RolloutUIState; +import org.eclipse.hawkbit.ui.utils.SPUIComponetIdProvider; +import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; +import org.eclipse.hawkbit.ui.utils.SPUILabelDefinitions; +import org.springframework.beans.factory.annotation.Autowired; +import org.vaadin.spring.events.EventBus; + +import com.vaadin.spring.annotation.SpringComponent; +import com.vaadin.spring.annotation.ViewScope; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.Label; +import com.vaadin.ui.UI; +import com.vaadin.ui.Window; + +/** + * + * Header layout of rollout list view. + * + */ +@SpringComponent +@ViewScope +public class RolloutListHeader extends AbstractSimpleTableHeader { + private static final long serialVersionUID = 2365400733081333174L; + + @Autowired + private SpPermissionChecker permissionChecker; + + @Autowired + private transient RolloutUIState rolloutUIState; + + @Autowired + private transient EventBus.SessionEventBus eventBus; + + @Autowired + private AddUpdateRolloutWindowLayout addUpdateRolloutWindow; + + /** + * Initialization of Target Header Component. + */ + @PostConstruct + protected void init() { + super.init(); + addUpdateRolloutWindow.init(); + } + + @Override + protected void resetSearchText() { + rolloutUIState.setSearchText(null); + eventBus.publish(this, RolloutEvent.FILTER_BY_TEXT); + } + + protected String getHeaderCaption() { + return SPUIDefinitions.ROLLOUT_LIST_HEADER_CAPTION; + } + + @Override + protected String getSearchBoxId() { + return SPUIComponetIdProvider.ROLLOUT_LIST_SEARCH_BOX_ID; + } + + @Override + protected String getSearchRestIconId() { + return SPUIComponetIdProvider.ROLLOUT_LIST_SEARCH_RESET_ICON_ID; + } + + @Override + protected void searchBy(final String newSearchText) { + rolloutUIState.setSearchText(newSearchText); + eventBus.publish(this, RolloutEvent.FILTER_BY_TEXT); + } + + @Override + protected String getAddIconId() { + return SPUIComponetIdProvider.ROLLOUT_ADD_ICON_ID; + } + + @Override + protected void addNewItem(final ClickEvent event) { + addUpdateRolloutWindow.resetComponents(); + final Window addTargetWindow = addUpdateRolloutWindow.getWindow(); + UI.getCurrent().addWindow(addTargetWindow); + addTargetWindow.setVisible(Boolean.TRUE); + + } + + @Override + protected void onClose(final ClickEvent event) { + /** + * No implementation required. + */ + } + + @Override + protected boolean hasCreatePermission() { + return permissionChecker.hasRolloutCreatePermission(); + } + + @Override + protected String getCloseButtonId() { + return null; + } + + @Override + protected boolean showCloseButton() { + return false; + } + + @Override + protected boolean isAllowSearch() { + return true; + } + + @Override + protected String onLoadSearchBoxValue() { + return rolloutUIState.getSearchText().isPresent() ? rolloutUIState.getSearchText().get() : null; + } + + @Override + protected boolean isRollout() { + + return true; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.hawkbit.ui.rollout.AbstractSimpleTableHeader# + * getHeaderCaptionLayout() + */ + @Override + protected HorizontalLayout getHeaderCaptionLayout() { + final Label headerCaption = SPUIComponentProvider.getLabel(getHeaderCaption(), + SPUILabelDefinitions.SP_WIDGET_CAPTION); + final HorizontalLayout headerCaptionLayout = new HorizontalLayout(); + headerCaptionLayout.addComponent(headerCaption); + + return headerCaptionLayout; + } + + /* + * (non-Javadoc) + * + * @see + * org.eclipse.hawkbit.ui.rollout.AbstractSimpleTableHeader#restoreCaption() + */ + @Override + protected void restoreCaption() { + /** + * No implementation required. + */ + } + +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutListTable.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutListTable.java new file mode 100644 index 000000000..5b5602748 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutListTable.java @@ -0,0 +1,552 @@ +/** + * 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.rollout; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +import org.eclipse.hawkbit.eventbus.event.RolloutChangeEvent; +import org.eclipse.hawkbit.repository.RolloutManagement; +import org.eclipse.hawkbit.repository.SpPermissionChecker; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus; +import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus; +import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; +import org.eclipse.hawkbit.ui.decorators.SPUIButtonStyleSmallNoBorder; +import org.eclipse.hawkbit.ui.rollout.event.RolloutEvent; +import org.eclipse.hawkbit.ui.rollout.state.RolloutUIState; +import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil; +import org.eclipse.hawkbit.ui.utils.I18N; +import org.eclipse.hawkbit.ui.utils.SPUIComponetIdProvider; +import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; +import org.eclipse.hawkbit.ui.utils.SPUILabelDefinitions; +import org.eclipse.hawkbit.ui.utils.TableColumn; +import org.eclipse.hawkbit.ui.utils.UINotification; +import org.springframework.beans.factory.annotation.Autowired; +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 org.vaadin.peter.contextmenu.ContextMenu; +import org.vaadin.peter.contextmenu.ContextMenu.ContextMenuItem; +import org.vaadin.peter.contextmenu.ContextMenu.ContextMenuItemClickEvent; +import org.vaadin.spring.events.EventBus; +import org.vaadin.spring.events.EventScope; +import org.vaadin.spring.events.annotation.EventBusListenerMethod; + +import com.vaadin.data.Container; +import com.vaadin.data.Item; +import com.vaadin.data.Property; +import com.vaadin.server.FontAwesome; +import com.vaadin.shared.ui.label.ContentMode; +import com.vaadin.spring.annotation.SpringComponent; +import com.vaadin.spring.annotation.ViewScope; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Label; +import com.vaadin.ui.UI; +import com.vaadin.ui.Window; +import com.vaadin.ui.themes.ValoTheme; + +/** + * + * Rollout table in list view. + * + */ +@SpringComponent +@ViewScope +public class RolloutListTable extends AbstractSimpleTable { + + private static final String IS_ACTION_RECIEVED = "isActionRecieved"; + + private static final long serialVersionUID = 8141874975649180139L; + + @Autowired + private I18N i18n; + + @Autowired + private transient EventBus.SessionEventBus eventBus; + + @Autowired + private transient RolloutManagement rolloutManagement; + + @Autowired + private AddUpdateRolloutWindowLayout addUpdateRolloutWindow; + + @Autowired + private UINotification uiNotification; + + @Autowired + private transient RolloutUIState rolloutUIState; + + @Autowired + private transient SpPermissionChecker permissionChecker; + + @Override + @PostConstruct + protected void init() { + super.init(); + eventBus.subscribe(this); + } + + @PreDestroy + void destroy() { + eventBus.unsubscribe(this); + } + + @EventBusListenerMethod(scope = EventScope.SESSION) + void onEvent(final RolloutEvent event) { + if (event == RolloutEvent.FILTER_BY_TEXT || event == RolloutEvent.CREATE_ROLLOUT + || event == RolloutEvent.UPDATE_ROLLOUT || event == RolloutEvent.SHOW_ROLLOUTS) { + refreshTable(); + } + } + + /** + * Handles the RolloutChangeEvent to refresh the item in the table. + * + * @param rolloutChangeEvent + * the event which contains the rollout which has been changed + */ + @EventBusListenerMethod(scope = EventScope.SESSION) + public void onEvent(final RolloutChangeEvent rolloutChangeEvent) { + final List visibleItemIds = (List) getVisibleItemIds(); + if (visibleItemIds.contains(rolloutChangeEvent.getRolloutId())) { + final Rollout rollout = rolloutManagement.findRolloutWithDetailedStatus(rolloutChangeEvent.getRolloutId()); + final TotalTargetCountStatus totalTargetCountStatus = rollout.getTotalTargetCountStatus(); + final LazyQueryContainer rolloutContainer = (LazyQueryContainer) getContainerDataSource(); + final Item item = rolloutContainer.getItem(rolloutChangeEvent.getRolloutId()); + item.getItemProperty(SPUILabelDefinitions.VAR_STATUS).setValue(rollout.getStatus()); + + item.getItemProperty(SPUILabelDefinitions.VAR_COUNT_TARGETS_RUNNING).setValue( + totalTargetCountStatus.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.RUNNING)); + item.getItemProperty(SPUILabelDefinitions.VAR_COUNT_TARGETS_ERROR).setValue( + totalTargetCountStatus.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.ERROR)); + item.getItemProperty(SPUILabelDefinitions.VAR_COUNT_TARGETS_FINISHED).setValue( + totalTargetCountStatus.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.FINISHED)); + item.getItemProperty(SPUILabelDefinitions.VAR_COUNT_TARGETS_NOT_STARTED).setValue( + totalTargetCountStatus.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.NOTSTARTED)); + item.getItemProperty(SPUILabelDefinitions.VAR_COUNT_TARGETS_CANCELLED).setValue( + totalTargetCountStatus.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.CANCELLED)); + item.getItemProperty(SPUILabelDefinitions.VAR_COUNT_TARGETS_SCHEDULED).setValue( + totalTargetCountStatus.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.SCHEDULED)); + item.getItemProperty(IS_ACTION_RECIEVED).setValue( + !(Boolean) item.getItemProperty(IS_ACTION_RECIEVED).getValue()); + + final Long groupCount = (Long) item.getItemProperty(SPUILabelDefinitions.VAR_NUMBER_OF_GROUPS).getValue(); + if (null != rollout.getRolloutGroups() && groupCount != rollout.getRolloutGroups().size()) { + item.getItemProperty(SPUILabelDefinitions.VAR_NUMBER_OF_GROUPS).setValue( + Long.valueOf(rollout.getRolloutGroups().size())); + } + } + } + + @Override + protected List getTableVisibleColumns() { + final List columnList = new ArrayList<>(); + columnList.add(new TableColumn(SPUIDefinitions.ROLLOUT_NAME, i18n.get("header.name"), 0.225f)); + + columnList.add(new TableColumn(SPUILabelDefinitions.VAR_DIST_NAME_VERSION, i18n.get("header.distributionset"), + 0.225f)); + columnList.add(new TableColumn(SPUIDefinitions.ROLLOUT_STATUS, i18n.get("header.status"), 0.07f)); + columnList.add(new TableColumn(SPUIDefinitions.DETAIL_STATUS, i18n.get("header.detail.status"), 0.58f)); + columnList.add(new TableColumn(SPUILabelDefinitions.VAR_NUMBER_OF_GROUPS, i18n.get("header.numberofgroups"), + 0.1f)); + columnList.add(new TableColumn(SPUILabelDefinitions.VAR_TOTAL_TARGETS, i18n.get("header.total.targets"), 0.1f)); + columnList.add(new TableColumn(SPUIDefinitions.ROLLOUT_ACTION, i18n.get("upload.action"), 0.1f)); + columnList.add(new TableColumn(SPUILabelDefinitions.VAR_CREATED_DATE, i18n.get("header.createdDate"), 0.1f)); + columnList.add(new TableColumn(SPUILabelDefinitions.VAR_CREATED_USER, i18n.get("header.createdBy"), 0.1f)); + columnList.add(new TableColumn(SPUILabelDefinitions.VAR_MODIFIED_DATE, i18n.get("header.modifiedDate"), 0.1f)); + columnList.add(new TableColumn(SPUILabelDefinitions.VAR_MODIFIED_BY, i18n.get("header.modifiedBy"), 0.1f)); + columnList.add(new TableColumn(SPUILabelDefinitions.VAR_DESC, i18n.get("header.description"), 0.1f)); + return columnList; + } + + @Override + protected Container createContainer() { + final BeanQueryFactory rolloutQf = new BeanQueryFactory<>(RolloutBeanQuery.class); + return new LazyQueryContainer(new LazyQueryDefinition(true, SPUIDefinitions.PAGE_SIZE, + SPUILabelDefinitions.VAR_ID), rolloutQf); + + } + + @Override + protected void addContainerProperties(final Container container) { + final LazyQueryContainer rolloutTableContainer = (LazyQueryContainer) container; + rolloutTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_ID, String.class, null, false, false); + rolloutTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_NAME, String.class, "", false, false); + rolloutTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_DESC, String.class, null, false, false); + rolloutTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_STATUS, RolloutStatus.class, null, false, + false); + rolloutTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_DIST_NAME_VERSION, String.class, null, + false, false); + + rolloutTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_TARGETFILTERQUERY, String.class, null, + false, false); + rolloutTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_CREATED_DATE, String.class, null, false, + false); + + rolloutTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_MODIFIED_DATE, String.class, null, false, + false); + rolloutTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_CREATED_USER, String.class, null, false, + false); + rolloutTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_MODIFIED_BY, String.class, null, false, + false); + rolloutTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_NUMBER_OF_GROUPS, Integer.class, 0, false, + false); + + rolloutTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_COUNT_TARGETS_NOT_STARTED, Long.class, 0L, + false, false); + rolloutTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_COUNT_TARGETS_RUNNING, Long.class, 0L, + false, false); + rolloutTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_COUNT_TARGETS_SCHEDULED, Long.class, 0L, + false, false); + rolloutTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_COUNT_TARGETS_ERROR, Long.class, 0L, false, + false); + rolloutTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_COUNT_TARGETS_FINISHED, Long.class, 0L, + false, false); + rolloutTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_COUNT_TARGETS_CANCELLED, Long.class, 0L, + false, false); + rolloutTableContainer.addContainerProperty(SPUILabelDefinitions.VAR_TOTAL_TARGETS, String.class, "0", false, + false); + + } + + @Override + protected String getTableId() { + return SPUIComponetIdProvider.ROLLOUT_LIST_TABLE_ID; + } + + @Override + protected void onValueChange() { + /** + * No implementation required. + */ + } + + @Override + protected void addCustomGeneratedColumns() { + addGeneratedColumn(SPUIDefinitions.ROLLOUT_NAME, (source, itemId, columnId) -> getRolloutNameLink(itemId)); + addGeneratedColumn(SPUIDefinitions.ROLLOUT_STATUS, (source, itemId, columnId) -> getStatusLabel(itemId)); + addGeneratedColumn(SPUIDefinitions.DETAIL_STATUS, (source, itemId, columnId) -> getProgressBar(itemId)); + addGeneratedColumn(SPUIDefinitions.ROLLOUT_ACTION, (source, itemId, columnId) -> getActionButton(itemId)); + + setColumnAlignment(SPUIDefinitions.ROLLOUT_STATUS, Align.CENTER); + setColumnAlignment(SPUIDefinitions.DETAIL_STATUS, Align.CENTER); + setColumnAlignment(SPUIDefinitions.ROLLOUT_ACTION, Align.CENTER); + + } + + @Override + protected void setCollapsiblecolumns() { + setColumnCollapsed(SPUILabelDefinitions.VAR_CREATED_DATE, true); + setColumnCollapsed(SPUILabelDefinitions.VAR_MODIFIED_DATE, true); + setColumnCollapsed(SPUILabelDefinitions.VAR_CREATED_USER, true); + setColumnCollapsed(SPUILabelDefinitions.VAR_MODIFIED_BY, true); + setColumnCollapsed(SPUILabelDefinitions.VAR_DESC, true); + } + + private Button getActionButton(final Object itemId) { + final Item row = getItem(itemId); + final String rolloutName = (String) row.getItemProperty(SPUILabelDefinitions.VAR_NAME).getValue(); + final Button actionButton = SPUIComponentProvider.getButton(getActionButtonId(rolloutName), "", + SPUILabelDefinitions.ACTION, ValoTheme.BUTTON_TINY + " ", true, FontAwesome.CIRCLE_O, + SPUIButtonStyleSmallNoBorder.class); + actionButton.setData(itemId); + actionButton.setHtmlContentAllowed(true); + actionButton.addClickListener(event -> onAction(event)); + addStatusPropertyChangeListener(itemId, actionButton); + + final RolloutStatus rolloutStatus = (RolloutStatus) row.getItemProperty(SPUILabelDefinitions.VAR_STATUS) + .getValue(); + enableDisableActions(rolloutStatus, actionButton); + return actionButton; + } + + private void enableDisableActions(final RolloutStatus rolloutStatus, final Button actionButton) { + final RolloutStatus[] statusList = new RolloutStatus[] { RolloutStatus.FINISHED, RolloutStatus.STARTING, + RolloutStatus.CREATING, RolloutStatus.ERROR_CREATING, RolloutStatus.ERROR_STARTING }; + if (Arrays.asList(statusList).contains(rolloutStatus)) { + actionButton.setEnabled(false); + } else { + actionButton.setEnabled(true); + } + } + + private void addStatusPropertyChangeListener(final Object itemId, final Button actionButton) { + final Property status = getContainerProperty(itemId, SPUILabelDefinitions.VAR_STATUS); + final Property.ValueChangeNotifier notifier = (Property.ValueChangeNotifier) status; + notifier.addValueChangeListener(new ValueChangeListener() { + @Override + public void valueChange(final com.vaadin.data.Property.ValueChangeEvent event) { + final Item row = getItem(itemId); + final RolloutStatus rolloutStatus = (RolloutStatus) row + .getItemProperty(SPUILabelDefinitions.VAR_STATUS).getValue(); + enableDisableActions(rolloutStatus, actionButton); + } + }); + } + + private ContextMenu createContextMenu(final Long rolloutId) { + final Item row = getItem(rolloutId); + final RolloutStatus rolloutStatus = (RolloutStatus) row.getItemProperty(SPUILabelDefinitions.VAR_STATUS) + .getValue(); + final ContextMenu context = new ContextMenu(); + context.addItemClickListener(event -> menuItemClicked(event)); + if (rolloutStatus == RolloutStatus.READY) { + final ContextMenuItem startItem = context.addItem("Start"); + startItem.setData(new ContextMenuData(rolloutId, ACTION.START)); + } else if (rolloutStatus == RolloutStatus.RUNNING) { + final ContextMenuItem pauseItem = context.addItem("Pause"); + pauseItem.setData(new ContextMenuData(rolloutId, ACTION.PAUSE)); + } else if (rolloutStatus == RolloutStatus.PAUSED) { + final ContextMenuItem resumeItem = context.addItem("Resume"); + resumeItem.setData(new ContextMenuData(rolloutId, ACTION.RESUME)); + } else if (rolloutStatus == RolloutStatus.STARTING || rolloutStatus == RolloutStatus.CREATING) { + return context; + } + if (permissionChecker.hasRolloutUpdatePermission()) { + final ContextMenuItem cancelItem = context.addItem("Update"); + cancelItem.setData(new ContextMenuData(rolloutId, ACTION.UPDATE)); + } + return context; + } + + private void menuItemClicked(final ContextMenuItemClickEvent event) { + final ContextMenuItem item = (ContextMenuItem) event.getSource(); + final ContextMenuData contextMenuData = (ContextMenuData) item.getData(); + final Item row = getItem(contextMenuData.getRolloutId()); + final String rolloutName = (String) row.getItemProperty(SPUILabelDefinitions.VAR_NAME).getValue(); + + if (contextMenuData.getAction() == ACTION.PAUSE) { + rolloutManagement.pauseRollout(rolloutManagement.findRolloutById(contextMenuData.getRolloutId())); + uiNotification.displaySuccess(i18n.get("message.rollout.paused", rolloutName)); + } else if (contextMenuData.getAction() == ACTION.RESUME) { + rolloutManagement.resumeRollout(rolloutManagement.findRolloutById(contextMenuData.getRolloutId())); + uiNotification.displaySuccess(i18n.get("message.rollout.resumed", rolloutName)); + } else if (contextMenuData.getAction() == ACTION.START) { + rolloutManagement.startRolloutAsync(rolloutManagement.findRolloutByName(rolloutName)); + uiNotification.displaySuccess(i18n.get("message.rollout.started", rolloutName)); + } else if (contextMenuData.getAction() == ACTION.UPDATE) { + addUpdateRolloutWindow.populateData(contextMenuData.getRolloutId()); + final Window addTargetWindow = addUpdateRolloutWindow.getWindow(); + addTargetWindow.setCaption(i18n.get("caption.update.rollout")); + UI.getCurrent().addWindow(addTargetWindow); + addTargetWindow.setVisible(Boolean.TRUE); + } + } + + private void onAction(final ClickEvent event) { + final ContextMenu contextMenu = createContextMenu((Long) event.getButton().getData()); + contextMenu.setAsContextMenuOf(event.getButton()); + contextMenu.open(event.getClientX(), event.getClientY()); + } + + private Button getRolloutNameLink(final Object itemId) { + final Item row = getItem(itemId); + final String rolloutName = (String) row.getItemProperty(SPUILabelDefinitions.VAR_NAME).getValue(); + final Button updateIcon = SPUIComponentProvider.getButton(getDetailLinkId(rolloutName), rolloutName, + SPUILabelDefinitions.SHOW_ROLLOUT_GROUP_DETAILS, null, false, null, SPUIButtonStyleSmallNoBorder.class); + updateIcon.setData(rolloutName); + updateIcon.addStyleName(ValoTheme.LINK_SMALL + " " + "on-focus-no-border link"); + updateIcon.addClickListener(event -> showRolloutGroups(itemId)); + return updateIcon; + } + + private void showRolloutGroups(final Object itemId) { + rolloutUIState.setRolloutId((long) itemId); + final String rolloutName = (String) getItem(itemId).getItemProperty(SPUILabelDefinitions.VAR_NAME).getValue(); + rolloutUIState.setRolloutName(rolloutName); + eventBus.publish(this, RolloutEvent.SHOW_ROLLOUT_GROUPS); + } + + private static String getActionButtonId(final String rollOutName) { + return new StringBuilder(SPUIComponetIdProvider.ROLLOUT_ACTION_BUTTON_ID).append('.').append(rollOutName) + .toString(); + } + + private static String getDetailLinkId(final String rollOutName) { + return new StringBuilder(SPUIComponetIdProvider.ROLLOUT_NAME_LINK_ID).append('.').append(rollOutName) + .toString(); + } + + private DistributionBar getProgressBar(final Object itemId) { + final DistributionBar bar = new DistributionBar(2); + bar.setId(SPUIComponetIdProvider.ROLLOUT_PROGRESS_BAR); + bar.setSizeFull(); + bar.setZeroSizedVisible(false); + HawkbitCommonUtil.initialiseProgressBar(bar, getItem(itemId)); + addPropertyChangeListenerOnActionRecieved(itemId, bar); + return bar; + } + + private void addPropertyChangeListenerOnActionRecieved(final Object itemId, final DistributionBar bar) { + final Property status = getContainerProperty(itemId, IS_ACTION_RECIEVED); + final Property.ValueChangeNotifier notifier = (Property.ValueChangeNotifier) status; + notifier.addValueChangeListener(new ValueChangeListener() { + @Override + public void valueChange(final com.vaadin.data.Property.ValueChangeEvent event) { + HawkbitCommonUtil.initialiseProgressBar(bar, getItem(itemId)); + } + }); + } + + private Label getStatusLabel(final Object itemId) { + final Label statusLabel = new Label(); + statusLabel.setHeightUndefined(); + statusLabel.setContentMode(ContentMode.HTML); + statusLabel.setId(getRolloutStatusId(itemId)); + setStatusIcon(itemId, statusLabel); + statusLabel.setSizeUndefined(); + addPropertyChangeListener(itemId, statusLabel); + return statusLabel; + } + + private void addPropertyChangeListener(final Object itemId, final Label statusLabel) { + final Property status = getContainerProperty(itemId, SPUILabelDefinitions.VAR_STATUS); + final Property.ValueChangeNotifier notifier = (Property.ValueChangeNotifier) status; + notifier.addValueChangeListener(new ValueChangeListener() { + @Override + public void valueChange(final com.vaadin.data.Property.ValueChangeEvent event) { + setStatusIcon(itemId, statusLabel); + } + }); + } + + private String getRolloutStatusId(final Object itemId) { + final String rolloutName = (String) getItem(itemId).getItemProperty(SPUILabelDefinitions.VAR_NAME).getValue(); + return new StringBuilder(SPUIComponetIdProvider.ROLLOUT_STATUS_LABEL_ID).append(".").append(rolloutName) + .toString(); + } + + private void setStatusIcon(final Object itemId, final Label statusLabel) { + final Item item = getItem(itemId); + if (item != null) { + final RolloutStatus rolloutStatus = (RolloutStatus) item.getItemProperty(SPUILabelDefinitions.VAR_STATUS) + .getValue(); + setRolloutStatusIcon(rolloutStatus, statusLabel); + } + } + + private void setRolloutStatusIcon(final RolloutStatus rolloutStatus, final Label statusLabel) { + statusLabel.setDescription(rolloutStatus.toString().toLowerCase()); + switch (rolloutStatus) { + case FINISHED: + statusLabel.setValue(FontAwesome.CHECK_CIRCLE.getHtml()); + statusLabel.setStyleName("statusIconGreen"); + break; + case PAUSED: + statusLabel.setValue(FontAwesome.PAUSE.getHtml()); + statusLabel.setStyleName("statusIconBlue"); + break; + case RUNNING: + statusLabel.setValue(null); + statusLabel.setStyleName("yellowSpinner"); + break; + case READY: + statusLabel.setValue(FontAwesome.DOT_CIRCLE_O.getHtml()); + statusLabel.setStyleName("statusIconLightBlue"); + break; + case STOPPED: + statusLabel.setValue(FontAwesome.STOP.getHtml()); + statusLabel.setStyleName("statusIconRed"); + break; + case CREATING: + statusLabel.setValue(null); + statusLabel.setStyleName("greySpinner"); + break; + case STARTING: + statusLabel.setValue(null); + statusLabel.setStyleName("blueSpinner"); + break; + case ERROR_CREATING: + statusLabel.setValue(FontAwesome.EXCLAMATION_CIRCLE.getHtml()); + statusLabel.setStyleName("statusIconRed"); + statusLabel.setDescription(i18n.get("message.error.creating.rollout")); + break; + case ERROR_STARTING: + statusLabel.setValue(FontAwesome.EXCLAMATION_CIRCLE.getHtml()); + statusLabel.setStyleName("statusIconRed"); + statusLabel.setDescription(i18n.get("message.error.starting.rollout")); + break; + default: + break; + } + statusLabel.addStyleName(ValoTheme.LABEL_SMALL); + } + + private void refreshTable() { + final LazyQueryContainer container = (LazyQueryContainer) getContainerDataSource(); + container.refresh(); + } + + enum ACTION { + PAUSE, RESUME, START, UPDATE + } + + /** + * Represents data of context menu item. + * + */ + public static class ContextMenuData { + + private Long rolloutId; + + private ACTION action; + + /** + * Set rollout if and action. + * + * @param rolloutId + * id of rollout + * @param action + * user action {@link ACTION} + */ + public ContextMenuData(final Long rolloutId, final ACTION action) { + this.action = action; + this.rolloutId = rolloutId; + } + + /** + * @return the rolloutId + */ + public Long getRolloutId() { + return rolloutId; + } + + /** + * @param rolloutId + * the rolloutId to set + */ + public void setRolloutId(final Long rolloutId) { + this.rolloutId = rolloutId; + } + + /** + * @return the action + */ + public ACTION getAction() { + return action; + } + + /** + * @param action + * the action to set + */ + public void setAction(final ACTION action) { + this.action = action; + } + + } + +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutListView.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutListView.java new file mode 100644 index 000000000..462bfd081 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutListView.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.ui.rollout; + +import javax.annotation.PostConstruct; + +import org.springframework.beans.factory.annotation.Autowired; + +import com.vaadin.spring.annotation.SpringComponent; +import com.vaadin.spring.annotation.ViewScope; +import com.vaadin.ui.Label; + +/** + * + * Rollout list view. + * + */ +@SpringComponent +@ViewScope +public class RolloutListView extends AbstractSimpleTableLayout { + + private static final long serialVersionUID = -2703552177439393208L; + + @Autowired + private RolloutListHeader rolloutListHeader; + + @Autowired + private RolloutListTable rolloutListTable; + + @PostConstruct + void init() { + super.init(rolloutListHeader, rolloutListTable); + } + + + @Override + protected boolean hasCountMessage() { + + return false; + } + + + @Override + protected Label getCountMessageLabel() { + + return null; + } + +} 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 new file mode 100644 index 000000000..98e009dad --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutView.java @@ -0,0 +1,148 @@ +/** + * 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.rollout; + +import javax.annotation.PreDestroy; + +import org.eclipse.hawkbit.repository.SpPermissionChecker; +import org.eclipse.hawkbit.ui.HawkbitUI; +import org.eclipse.hawkbit.ui.rollout.event.RolloutEvent; +import org.eclipse.hawkbit.ui.rollout.state.RolloutUIState; +import org.springframework.beans.factory.annotation.Autowired; +import org.vaadin.spring.events.EventBus; +import org.vaadin.spring.events.EventScope; +import org.vaadin.spring.events.annotation.EventBusListenerMethod; + +import com.vaadin.navigator.View; +import com.vaadin.navigator.ViewChangeListener.ViewChangeEvent; +import com.vaadin.spring.annotation.SpringView; +import com.vaadin.spring.annotation.ViewScope; +import com.vaadin.ui.VerticalLayout; + +/** + * Rollout management view. + */ +@SpringView(name = RolloutView.VIEW_NAME, ui = HawkbitUI.class) +@ViewScope +public class RolloutView extends VerticalLayout implements View { + + private static final long serialVersionUID = -6199789714170913988L; + + public static final String VIEW_NAME = "rollout"; + + @Autowired + private SpPermissionChecker permChecker; + + @Autowired + private RolloutListView rolloutListView; + + @Autowired + private RolloutGroupsListView rolloutGroupsListView; + + @Autowired + private RolloutGroupTargetsListView rolloutGroupTargetsListView; + + @Autowired + private transient RolloutUIState rolloutUIState; + + @Autowired + private transient EventBus.SessionEventBus eventBus; + + @Override + public void enter(final ViewChangeEvent event) { + setSizeFull(); + if (!(rolloutUIState.isShowRollOuts() || rolloutUIState.isShowRolloutGroups() || rolloutUIState + .isShowRolloutGroupTargets())) { + rolloutUIState.setShowRollOuts(true); + } + buildLayout(); + eventBus.subscribe(this); + } + + @PreDestroy + void destroy() { + eventBus.unsubscribe(this); + } + + @EventBusListenerMethod(scope = EventScope.SESSION) + void onEvent(final RolloutEvent event) { + if (event == RolloutEvent.SHOW_ROLLOUTS) { + rolloutUIState.setShowRollOuts(true); + rolloutUIState.setShowRolloutGroups(false); + rolloutUIState.setShowRolloutGroupTargets(false); + buildLayout(); + } else if (event == RolloutEvent.SHOW_ROLLOUT_GROUPS) { + rolloutUIState.setShowRollOuts(false); + rolloutUIState.setShowRolloutGroups(true); + rolloutUIState.setShowRolloutGroupTargets(false); + buildLayout(); + } else if (event == RolloutEvent.SHOW_ROLLOUT_GROUP_TARGETS) { + rolloutUIState.setShowRollOuts(false); + rolloutUIState.setShowRolloutGroups(false); + rolloutUIState.setShowRolloutGroupTargets(true); + buildLayout(); + } + } + + private void buildLayout() { + if (permChecker.hasRolloutReadPermission() && rolloutUIState.isShowRollOuts()) { + showRolloutListView(); + } else if (permChecker.hasRolloutReadPermission() && rolloutUIState.isShowRolloutGroups()) { + showRolloutGroupListView(); + } else if (permChecker.hasRolloutTargetsReadPermission() && rolloutUIState.isShowRolloutGroupTargets()) { + showRolloutGroupTargetsListView(); + } + } + + /** + * + */ + private void showRolloutGroupTargetsListView() { + rolloutGroupTargetsListView.setVisible(true); + if (rolloutListView.isVisible()) { + rolloutListView.setVisible(false); + } + if (rolloutGroupsListView.isVisible()) { + rolloutGroupsListView.setVisible(false); + } + addComponent(rolloutGroupTargetsListView); + setExpandRatio(rolloutGroupTargetsListView, 1.0f); + } + + /** + * + */ + private void showRolloutGroupListView() { + rolloutGroupsListView.setVisible(true); + if (rolloutListView.isVisible()) { + rolloutListView.setVisible(false); + } + if (rolloutGroupTargetsListView.isVisible()) { + rolloutGroupTargetsListView.setVisible(false); + } + addComponent(rolloutGroupsListView); + setExpandRatio(rolloutGroupsListView, 1.0f); + } + + /** + * + */ + private void showRolloutListView() { + rolloutListView.setVisible(true); + if (rolloutGroupsListView.isVisible()) { + rolloutGroupsListView.setVisible(false); + } + if (rolloutGroupTargetsListView.isVisible()) { + rolloutGroupTargetsListView.setVisible(false); + } + addComponent(rolloutListView); + setExpandRatio(rolloutListView, 1.0f); + } + +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutViewMenuItem.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutViewMenuItem.java new file mode 100644 index 000000000..12ae1f2da --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/RolloutViewMenuItem.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.ui.rollout; + +import java.util.Arrays; +import java.util.List; + +import org.eclipse.hawkbit.im.authentication.SpPermission; +import org.eclipse.hawkbit.ui.menu.DashboardMenuItem; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import com.vaadin.server.FontAwesome; +import com.vaadin.server.Resource; + +/** + * Menu item for rollout . + * + * + * + */ +@Component +@Order(200) +public class RolloutViewMenuItem implements DashboardMenuItem { + + private static final long serialVersionUID = 6112540239655168995L; + + @Override + public String getViewName() { + return RolloutView.VIEW_NAME; + } + + @Override + public Resource getDashboardIcon() { + return FontAwesome.TASKS; + } + + @Override + public String getDashboardCaption() { + return "Rollout"; + } + + @Override + public String getDashboardCaptionLong() { + return "Rollout Management"; + } + + @Override + public List getPermissions() { + return Arrays.asList(SpPermission.ROLLOUT_MANAGEMENT); + } +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/event/RolloutEvent.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/event/RolloutEvent.java new file mode 100644 index 000000000..724187397 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/event/RolloutEvent.java @@ -0,0 +1,17 @@ +/** + * 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.rollout.event; + +/** + * Enum for events in rollout management. + * + */ +public enum RolloutEvent { + FILTER_BY_TEXT, CREATE_ROLLOUT, UPDATE_ROLLOUT, SHOW_ROLLOUTS, SHOW_ROLLOUT_GROUPS, SHOW_ROLLOUT_GROUP_TARGETS, SHOW_ROLLOUT_GROUP_TARGETS_COUNT +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/state/RolloutUIState.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/state/RolloutUIState.java new file mode 100644 index 000000000..8dc5b1e17 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/state/RolloutUIState.java @@ -0,0 +1,181 @@ +/** + * 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.rollout.state; + +import java.io.Serializable; +import java.util.Optional; + +import org.eclipse.hawkbit.repository.model.RolloutGroup; + +import com.vaadin.spring.annotation.SpringComponent; +import com.vaadin.spring.annotation.VaadinSessionScope; + +/** + * Stores user actions in rollout management view. + * + */ +@VaadinSessionScope +@SpringComponent +public class RolloutUIState implements Serializable { + + private static final long serialVersionUID = -5751690471177053140L; + + private String searchText; + + private Long rolloutId; + + private String rolloutName; + + private RolloutGroup rolloutGroup; + + private boolean showRolloutGroups; + + private boolean showRolloutGroupTargets; + + private boolean showRollOuts = true; + + private Long rolloutGroupTargetsTruncated; + + private long rolloutGroupTargetsTotalCount; + + /** + * @return the searchText + */ + public Optional getSearchText() { + return null != searchText ? Optional.of(searchText) : Optional.empty(); + } + + /** + * @param searchText + * the searchText to set + */ + public void setSearchText(final String searchText) { + this.searchText = searchText; + } + + /** + * @return the rolloutId + */ + public Optional getRolloutId() { + return rolloutId != null ? Optional.of(rolloutId) : Optional.empty(); + } + + /** + * @param rolloutId + * the rolloutId to set + */ + public void setRolloutId(final long rolloutId) { + this.rolloutId = rolloutId; + } + + /** + * @return the rolloutGroup + */ + public Optional getRolloutGroup() { + return rolloutGroup == null ? Optional.empty() : Optional.of(rolloutGroup); + } + + /** + * @param rolloutGroup + * the rolloutGroup to set + */ + public void setRolloutGroup(final RolloutGroup rolloutGroup) { + this.rolloutGroup = rolloutGroup; + } + + /** + * @return the showRolloutGroups + */ + public boolean isShowRolloutGroups() { + return showRolloutGroups; + } + + /** + * @param showRolloutGroups + * the showRolloutGroups to set + */ + public void setShowRolloutGroups(final boolean showRolloutGroups) { + this.showRolloutGroups = showRolloutGroups; + } + + /** + * @return the showRolloutGroupTargets + */ + public boolean isShowRolloutGroupTargets() { + return showRolloutGroupTargets; + } + + /** + * @param showRolloutGroupTargets + * the showRolloutGroupTargets to set + */ + public void setShowRolloutGroupTargets(final boolean showRolloutGroupTargets) { + this.showRolloutGroupTargets = showRolloutGroupTargets; + } + + /** + * @return the showRollOuts + */ + public boolean isShowRollOuts() { + return showRollOuts; + } + + /** + * @param showRollOuts + * the showRollOuts to set + */ + public void setShowRollOuts(final boolean showRollOuts) { + this.showRollOuts = showRollOuts; + } + + /** + * @return the rolloutName + */ + public Optional getRolloutName() { + return rolloutName == null ? Optional.empty() : Optional.of(rolloutName); + } + + /** + * @param rolloutName + * the rolloutName to set + */ + public void setRolloutName(final String rolloutName) { + this.rolloutName = rolloutName; + } + + /** + * @return the rolloutGroupTargetsTruncated + */ + public Long getRolloutGroupTargetsTruncated() { + return rolloutGroupTargetsTruncated; + } + + /** + * @param rolloutGroupTargetsTruncated + * the rolloutGroupTargetsTruncated to set + */ + public void setRolloutGroupTargetsTruncated(final long rolloutGroupTargetsTruncated) { + this.rolloutGroupTargetsTruncated = rolloutGroupTargetsTruncated; + } + + /** + * @return the rolloutGroupTargetsTotalCount + */ + public long getRolloutGroupTargetsTotalCount() { + return rolloutGroupTargetsTotalCount; + } + + /** + * @param rolloutGroupTargetsTotalCount + * the rolloutGroupTargetsTotalCount to set + */ + public void setRolloutGroupTargetsTotalCount(final long rolloutGroupTargetsTotalCount) { + this.rolloutGroupTargetsTotalCount = rolloutGroupTargetsTotalCount; + } +} 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 4e073c829..18839ae42 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 @@ -11,6 +11,7 @@ package org.eclipse.hawkbit.ui.utils; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashSet; @@ -23,12 +24,14 @@ import org.eclipse.hawkbit.im.authentication.UserPrincipal; import org.eclipse.hawkbit.repository.SoftwareManagement; import org.eclipse.hawkbit.repository.model.DistributionSetIdName; import org.eclipse.hawkbit.repository.model.DistributionSetTagAssigmentResult; +import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModuleType; import org.eclipse.hawkbit.repository.model.TargetIdName; import org.eclipse.hawkbit.repository.model.TargetInfo.PollStatus; import org.eclipse.hawkbit.repository.model.TargetTagAssigmentResult; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; +import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus; import org.eclipse.hawkbit.ui.management.dstable.DistributionTable; import org.eclipse.hawkbit.ui.management.targettable.TargetTable; import org.slf4j.Logger; @@ -40,6 +43,7 @@ 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.vaadin.data.Container; import com.vaadin.data.Item; @@ -1258,4 +1262,116 @@ public final class HawkbitCommonUtil { } } + /** + * Set status progress bar value. + * + * @param bar + * DistributionBar + * @param statusName + * status name + * @param count + * target counts in a status + * @param index + * bar part index + */ + public 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); + } + + /** + * Initialize status progress bar with values and number of parts on load. + * + * @param bar + * DistributionBar + * @param item + * row of a table + */ + public static void initialiseProgressBar(final DistributionBar bar, final Item item) { + final Long notStartedTargetsCount = getStatusCount(SPUILabelDefinitions.VAR_COUNT_TARGETS_NOT_STARTED, item); + final Long runningTargetsCount = getStatusCount(SPUILabelDefinitions.VAR_COUNT_TARGETS_RUNNING, item); + final Long scheduledTargetsCount = getStatusCount(SPUILabelDefinitions.VAR_COUNT_TARGETS_SCHEDULED, item); + final Long errorTargetsCount = getStatusCount(SPUILabelDefinitions.VAR_COUNT_TARGETS_ERROR, item); + final Long finishedTargetsCount = getStatusCount(SPUILabelDefinitions.VAR_COUNT_TARGETS_FINISHED, item); + final Long cancelledTargetsCount = getStatusCount(SPUILabelDefinitions.VAR_COUNT_TARGETS_CANCELLED, item); + if (isNoTargets(errorTargetsCount, notStartedTargetsCount, runningTargetsCount, scheduledTargetsCount, + finishedTargetsCount, cancelledTargetsCount)) { + HawkbitCommonUtil.setBarPartSize(bar, TotalTargetCountStatus.Status.SCHEDULED.toString().toLowerCase(), 0, + 0); + HawkbitCommonUtil.setBarPartSize(bar, TotalTargetCountStatus.Status.FINISHED.toString().toLowerCase(), 0, + 1); + + } else { + bar.setNumberOfParts(6); + setProgressBarDetails(bar, item); + } + } + + /** + * Formats the finished percentage of a rollout group into a string with one + * digit after comma. + * + * @param rolloutGroup + * the rollout group + * @param finishedPercentage + * the calculated finished percentage of the rolloutgroup + * @return formatted String value + */ + public static String formattingFinishedPercentage(final RolloutGroup rolloutGroup, final float finishedPercentage) { + float tmpFinishedPercentage = 0; + switch (rolloutGroup.getStatus()) { + case READY: + case SCHEDULED: + case ERROR: + tmpFinishedPercentage = 0.0F; + break; + case FINISHED: + tmpFinishedPercentage = 100.0F; + break; + case RUNNING: + tmpFinishedPercentage = finishedPercentage; + break; + default: + break; + } + return String.format("%.1f", tmpFinishedPercentage); + } + + /** + * Reset the values of status progress bar on change of values. + * + * @param bar + * DistributionBar + * @param item + * row of the table + */ + private static void setProgressBarDetails(final DistributionBar bar, final Item item) { + bar.setNumberOfParts(6); + final Long notStartedTargetsCount = getStatusCount(SPUILabelDefinitions.VAR_COUNT_TARGETS_NOT_STARTED, item); + HawkbitCommonUtil.setBarPartSize(bar, TotalTargetCountStatus.Status.NOTSTARTED.toString().toLowerCase(), + notStartedTargetsCount.intValue(), 0); + HawkbitCommonUtil.setBarPartSize(bar, TotalTargetCountStatus.Status.SCHEDULED.toString().toLowerCase(), + getStatusCount(SPUILabelDefinitions.VAR_COUNT_TARGETS_SCHEDULED, item).intValue(), 1); + HawkbitCommonUtil.setBarPartSize(bar, TotalTargetCountStatus.Status.RUNNING.toString().toLowerCase(), + getStatusCount(SPUILabelDefinitions.VAR_COUNT_TARGETS_RUNNING, item).intValue(), 2); + HawkbitCommonUtil.setBarPartSize(bar, TotalTargetCountStatus.Status.ERROR.toString().toLowerCase(), + getStatusCount(SPUILabelDefinitions.VAR_COUNT_TARGETS_ERROR, item).intValue(), 3); + HawkbitCommonUtil.setBarPartSize(bar, TotalTargetCountStatus.Status.FINISHED.toString().toLowerCase(), + getStatusCount(SPUILabelDefinitions.VAR_COUNT_TARGETS_FINISHED, item).intValue(), 4); + HawkbitCommonUtil.setBarPartSize(bar, TotalTargetCountStatus.Status.CANCELLED.toString().toLowerCase(), + getStatusCount(SPUILabelDefinitions.VAR_COUNT_TARGETS_CANCELLED, item).intValue(), 5); + } + + private static boolean isNoTargets(final Long... statusCount) { + if (Arrays.asList(statusCount).stream().filter(value -> value > 0).toArray().length > 0) { + return false; + } + return true; + } + + private static Long getStatusCount(final String propertName, final Item item) { + return (Long) item.getItemProperty(propertName).getValue(); + } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIComponetIdProvider.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIComponetIdProvider.java index 170c7ab32..8beb5b68c 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIComponetIdProvider.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIComponetIdProvider.java @@ -717,6 +717,147 @@ public final class SPUIComponetIdProvider { public static final String BULK_UPLOAD_CLOSE_BUTTON_ID = "bulk.upload.close.button.id"; + /** + * Rollout list view - search box id. + */ + public static final String ROLLOUT_LIST_SEARCH_BOX_ID = "rollout.list.search.id"; + + /** + * Rollout list view - search reset icon id. + */ + public static final String ROLLOUT_LIST_SEARCH_RESET_ICON_ID = "rollout.list.search.reset.icon.id"; + + /** + * Rollout list view - add icon id. + */ + public static final String ROLLOUT_ADD_ICON_ID = "rollout.add.button.id"; + + /** + * Rollout list table id. + */ + public static final String ROLLOUT_LIST_TABLE_ID = "rollout.table.id"; + + /** + * Rollout group list table id. + */ + public static final String ROLLOUT_GROUP_LIST_TABLE_ID = "rollout.group.table.id"; + + /** + * Rollout group list table id. + */ + public static final String ROLLOUT_GROUP_TARGETS_LIST_TABLE_ID = "rollout.group.targets.table.id"; + + /** + * Rollout delete button id. + */ + public static final String ROLLOUT_DELETE_ICON = "rollout.delete.button.id"; + + /** + * Rollout text field name id. + */ + public static final String ROLLOUT_NAME_FIELD_ID = "rollout.name.field.id"; + /** + * Rollout number of groups id. + */ + public static final String ROLLOUT_NO_OF_GROUPS_ID = "rollout.no.ofgroups.id"; + + /** + * Rollout trigger threshold field if. + */ + public static final String ROLLOUT_TRIGGER_THRESOLD_ID = "rollout.trigger.thresold.id"; + + /** + * Rollout error thresold field id. + */ + public static final String ROLLOUT_ERROR_THRESOLD_ID = "rollout.error.thresold.id"; + + /** + * Rollout distribution set combo id. + */ + public static final String ROLLOUT_DS_ID = "rollout.ds.id"; + /** + * Rollout description field id. + */ + public static final String ROLLOUT_DESCRIPTION_ID = "rollout.description.id"; + /** + * Rollout create update window - save buttopn id. + */ + public static final String ROLLOUT_CREATE_UPDATE_SAVE_ID = "rollout.create.update.save.id"; + + /** + * Rollout create update - discard button id. + */ + public static final String ROLLOUT_CREATE_UPDATE_DISCARD_ID = "rollout.create.update.discard.id"; + /** + * Rollout name link id. + */ + public static final String ROLLOUT_NAME_LINK_ID = "rollout.name.link"; + /** + * Rollout Group name link id. + */ + public static final String ROLLOUT_GROUP_NAME_LINK_ID = "rollout.group.name.link"; + /** + * Rollout target filter query combo id. + */ + public static final String ROLLOUT_TARGET_FILTER_COMBO_ID = "rollout.target.filter.combo.id"; + /** + * Rollout action button id. + */ + public static final String ROLLOUT_ACTION_BUTTON_ID = "rollout.action.button.id"; + /** + * Rollout pause button id. + */ + public static final String ROLLOUT_PAUSE_BUTTON_ID = "rollout.pause.button.id"; + + /** + * Rollout resume button id. + */ + public static final String ROLLOUT_RESUME_BUTTON_ID = "rollout.resume.button.id"; + + /** + * Rollout save or start option group id. + */ + public static final String ROLLOUT_SAVESTARTOPTION_ID = "rollout.savestartoption.id"; + /** + * Rollout status label id. + */ + public static final String ROLLOUT_STATUS_LABEL_ID = "rollout.status.id"; + + /** + * Rollout % or count option group id. + */ + public static final String ROLLOUT_ERROR_THRESOLD_OPTION_ID = "rollout.error.thresold.option.id"; + + /** + * Rollout target filter query value text area id. + */ + public static final String ROLLOUT_TARGET_FILTER_QUERY_FIELD = "rollout.target.filter.query.field.id"; + + /** + * Rollout target view- close button id. + */ + public static final String ROLLOUT_TARGET_VIEW_CLOSE_BUTTON_ID = "rollout.group.target.close.id"; + /** + * Rollout status progress bar id. + */ + public static final String ROLLOUT_PROGRESS_BAR = "rollout.status.progress.bar.id"; + /** + * Rollout group header caption. + */ + public static final String ROLLOUT_GROUP_HEADER_CAPTION = "rollout.group.header.caption"; + /** + * Rollout group target header caption. + */ + public static final String ROLLOUT_GROUP_TARGET_HEADER_CAPTION = "rollout.group.header.target.caption"; + /** + * Rollout group close id. + */ + public static final String ROLLOUT_GROUP_CLOSE = "rollout.group.close.id"; + /** + * Rollout group targets count message label. + */ + public static final String ROLLOUT_GROUP_TARGET_LABEL = "rollout.group.target.label"; + /** * /* Private Constructor. */ diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIDefinitions.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIDefinitions.java index f3df05424..1d3e97168 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIDefinitions.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIDefinitions.java @@ -98,7 +98,12 @@ public final class SPUIDefinitions { public static final String ACTION_HIS_TBL_MSGS_HIDDEN = "Messages_Hidden"; /** - * Action history status hidden column. This is using to generate status + * Action history layout - rollout name column. + */ + public static final String ACTION_HIS_TBL_ROLLOUT_NAME = "Rollout name"; + + /** + * /** Action history status hidden column. This is using to generate status * icons under status coloumn. */ public static final String ACTION_HIS_TBL_STATUS_HIDDEN = "Status_Hidden"; @@ -118,6 +123,11 @@ public final class SPUIDefinitions { */ public static final String IN_ACTIVE = "inactive"; + /** + * Action history helping constant. + */ + public static final String SCHEDULED = "scheduled"; + /** * Action history lazy fetch page length. */ @@ -932,6 +942,11 @@ public final class SPUIDefinitions { */ public static final String BULK_UPLOD_DS_COMBO_STYLE = "bulk-upload-ds-combo"; + /** + * Bulk Targets upload window. + */ + public static final String BULK_UPLOAD_WINDOW = "bulk-update-window"; + /** * Filter by target filter query. */ @@ -947,6 +962,70 @@ public final class SPUIDefinitions { */ public static final Direction TARGET_TABLE_CREATE_AT_SORT_ORDER = Direction.ASC; + /** + * Rollout list view - header caption . + */ + public static final String ROLLOUT_LIST_HEADER_CAPTION = "Rollouts"; + + /** + * Rollout status. + */ + public static final String ROLLOUT_STATUS = "rollout-status"; + + /** + * Rollout group list view - header caption . + */ + public static final String ROLLOUT_GROUP_LIST_HEADER_CAPTION = "Rollout Groups"; + + /** + * Rollout delete - column property name. + */ + public static final String DELETE = "delete"; + + /** + * Rollout detail status - column property status. + */ + public static final String DETAIL_STATUS = "detail-status"; + + /** + * Rollout name column property. + */ + public static final String ROLLOUT_NAME = "rollout-name"; + /** + * Rollout group name column property. + */ + public static final String ROLLOUT_GROUP_NAME = "Name"; + + /** + * Rollout group started date column property. + */ + public static final String ROLLOUT_GROUP_STARTED_DATE = "Started date"; + + /** + * Rollout group started date column property. + */ + public static final String ROLLOUT_GROUP_ERROR_THRESHOLD = "errorConditionExp"; + + /** + * Rollout group started date column property. + */ + public static final String ROLLOUT_GROUP_THRESHOLD = "successConditionExp"; + + /** + * Rollout group installed percentage column property. + */ + public static final String ROLLOUT_GROUP_INSTALLED_PERCENTAGE = "finishedPercentage"; + + /** + * Rollout group status column property. + */ + public static final String ROLLOUT_GROUP_STATUS = "Status"; + + /** + * Rollout action column property. + */ + public static final String ROLLOUT_ACTION = "rollout-action"; + /** * /** Constructor. */ diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUILabelDefinitions.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUILabelDefinitions.java index 7a99ca031..f5848e650 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUILabelDefinitions.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUILabelDefinitions.java @@ -464,6 +464,82 @@ public final class SPUILabelDefinitions { */ public static final int TARGET_FILTER_QUERY_TEXT_FIELD_LENGTH = 1024; + /** + * Status - column property. + */ + public static final String VAR_STATUS = "status"; + + /** + * Target filter query - column property. + */ + public static final String VAR_TARGETFILTERQUERY = "targetFilterQuery"; + + /** + * Distribution name and version - column property. + */ + public static final String VAR_DIST_NAME_VERSION = "distributionSetNameVersion"; + + /** + * Number of groups in rollout- column property. + */ + public static final String VAR_NUMBER_OF_GROUPS = "numberOfGroups"; + + /** + * Delete label. + */ + public static final String DELETE = "Delete"; + /** + * Rollout name link's description. + */ + public static final String SHOW_ROLLOUT_GROUP_DETAILS = "show group details"; + /** + * Rollout action button description. + */ + public static final String ACTION = "Action"; + /** + * Rollout pause button name. + */ + public static final String PAUSE = "Pause"; + + /** + * Rollout resume button name. + */ + public static final String RESUME = "Resume"; + /** + * Rollout and rollout group property - count of not started targets. + */ + public static final String VAR_COUNT_TARGETS_NOT_STARTED = "notStartedTargetsCount"; + + /** + * Rollout and rollout group property - count of running targets. + */ + public static final String VAR_COUNT_TARGETS_RUNNING = "runningTargetsCount"; + + /** + * Rollout and rollout group property - count of scheduled targets. + */ + public static final String VAR_COUNT_TARGETS_SCHEDULED = "scheduledTargetsCount"; + + /** + * Rollout and rollout group property - count of targets in error. + */ + public static final String VAR_COUNT_TARGETS_ERROR = "errorTargetsCount"; + + /** + * Rollout and rollout group property - count of finished targets. + */ + public static final String VAR_COUNT_TARGETS_FINISHED = "finishedTargetsCount"; + + /** + * Rollout and rollout group property - count of targets cancelled targets. + */ + public static final String VAR_COUNT_TARGETS_CANCELLED = "cancelledTargetsCount"; + + /** + * Total target coulmn property name. + */ + public static final String VAR_TOTAL_TARGETS = "totalTargetsCount"; + /** * Constructor. */ 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 ab6b93c39..e4e61d991 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 @@ -225,6 +225,19 @@ public final class SPUIStyleDefinitions { * Query validator icon -error style. */ public static final String ERROR_ICON = "error-icon"; + /** + * Rollout action type layout style. + */ + public static final String ROLLOUT_ACTION_TYPE_LAYOUT = "rollout-action-type-layout"; + /** + * Rollout save option group style. + */ + public static final String ROLLOUT_OPTION_GROUP = "rollout-option-group"; + + /** + * Style to disable the action type layout. + */ + public static final String DISABLE_ACTION_TYPE_LAYOUT = "disable-action-type-layout"; /** * Bulk upload progress indicator style. diff --git a/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/hawkbitvariables.scss b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/hawkbitvariables.scss index c63af2a63..81e0617b3 100644 --- a/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/hawkbitvariables.scss +++ b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/hawkbitvariables.scss @@ -127,3 +127,11 @@ $white-hex-color: #fff; $tag-border-green-color: #2c9720; $tag-button-disabled-grey:#c0c0c0; $twin-table-border-grey:#888; + + +$progress-bar-scheduled-part: $blue-color; +$progress-bar-running-part: $signal-yellow-color; +$progress-bar-error-part: $signal-red-color; +$progress-bar-finished-part: $signal-green-color; +$progress-bar-cancelled-part: $grey-light; +$progress-bar-notstarted-part: $grey-color; \ No newline at end of file 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 2c20f4cc1..879fb8dd6 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 @@ -198,19 +198,34 @@ .statusIconNeutral { color: $hawkbit-primary-color; } + + + .greySpinner{ + @include valo-spinner($size: $v-font-size--small,$color: $grey-color); + pointer-events: auto !important; + } + + .yellowSpinner{ + @include valo-spinner($size: $v-font-size--small,$color: $signal-yellow-color); + pointer-events: auto !important; + } + .blueSpinner{ + @include valo-spinner($size: $v-font-size--small,$color: $bosch-color-light-blue); + pointer-events: auto !important; + } // Disabled row style when distribution is incomplete - .v-table-row-incomplete-distribution { - color: $disabled-row-color-grey !important; - } + .v-table-row-incomplete-distribution { + color: $disabled-row-color-grey !important; + } - .v-link { - text-decoration: none; - padding-right: 10px; - font-weight: 300; - } + .v-link { + text-decoration: none; + padding-right: 10px; + font-weight: 300; + } - .v-tooltip{ - max-width:43em; - } + .v-tooltip{ + max-width:43em; + } } diff --git a/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/rollout.scss b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/rollout.scss new file mode 100644 index 000000000..1c7714061 --- /dev/null +++ b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/rollout.scss @@ -0,0 +1,60 @@ +/** + * 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 + */ +@mixin rollout { + .rollout-option-group{ + font-size:12px; + font-weight:400; + margin-left:8px; + } + + .rollout-action-type-layout { + .v-caption-padding-right-style{ + padding-right:0px !important; + } + } + + .v-context-menu .v-context-menu-item-basic-icon-container{ + height:0px !important; + width:0px !important; + } + + .v-context-menu, .v-context-menu .v-context-menu-item-basic{ + background-color: #feffff !important; + border-radius: 4px; + } + + .v-context-menu .v-context-menu-item-basic:focus, .v-context-menu .v-context-menu-item-basic-submenu:focus, .v-context-menu .v-context-menu-item-basic-open { + @include valo-gradient($color: $hawkbit-primary-color); + background-color: $hawkbit-primary-color !important; + color: #e8eef3; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.05); + } + + .disable-action-type-layout{ + opacity: 0.5; + } + + .rollout-caption-links{ + font-weight: 400; + height: 25px ; + padding: 0px 4px ; + } + + .rollout-target-count-message{ + color: $info-message-color-grey; + } + + .rollout-table{ + .v-table-cell-wrapper { + cursor: default; + } + } + +} + \ No newline at end of file diff --git a/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/statusprogressbar.scss b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/statusprogressbar.scss new file mode 100644 index 000000000..867fa0151 --- /dev/null +++ b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/statusprogressbar.scss @@ -0,0 +1,64 @@ +/** + * 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 + */ +@mixin statusprogressbar { + .alump-dbar { + .status-bar-part-scheduled { + background-color: $progress-bar-scheduled-part !important; + } + + .status-bar-part-running { + background-color: $progress-bar-running-part !important; + } + + .status-bar-part-error { + background-color: $progress-bar-error-part !important; + } + + .status-bar-part-finished { + background-color: $progress-bar-finished-part !important; + } + + .status-bar-part-cancelled { + background-color: $progress-bar-cancelled-part !important; + } + + .status-bar-part-notstarted { + background-color: $progress-bar-notstarted-part !important; + } + + .alump-dbar-left { + padding: 3px 0px 3px 0px !important; + } + + .alump-dbar-middle { + padding: 3px 0px 3px 0px !important; + } + + .alump-dbar-right { + padding: 3px 0px 3px 0px !important; + } + + .alump-dbar-only { + padding: 3px 0px 3px 0px !important; + } + + } + + &.alump-dbar-tooltip { + background-color: rgba(27, 54, 73, 0.9); + border-radius: 3px; + color: white; + z-index: 100; + padding: 3px; + font-size: 10px; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.2); + -moz-box-shadow: 0 2px 12px rgba(0, 0, 0, 0.2); + -webkit-box-shadow: 0 2px 12px rgba(0, 0, 0, 0.2); + } +} \ No newline at end of file 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 f60e37dbb..8e543aa31 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 @@ -11,7 +11,7 @@ //Table header - filter text field style .filter-box { - height: 21px !important; + height: 25px !important; transition: width .5s ease-in-out; float: right; border-radius: $v-border-radius; diff --git a/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/hawkbittheme.scss b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/hawkbittheme.scss index 87d84f588..1c2235e5b 100644 --- a/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/hawkbittheme.scss +++ b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/hawkbittheme.scss @@ -54,6 +54,9 @@ $v-table-border-color: $widget-border-color; @import 'customstyles/footer-common'; @import 'customstyles/popup-common'; @import 'customstyles/target-filter-query'; +@import 'customstyles/statusprogressbar'; +@import 'customstyles/rollout'; + // Optimize the CSS output $v-included-components: remove($v-included-components, calendar); @@ -88,7 +91,10 @@ $v-included-components: remove($v-included-components, form); @include popup-common; @include valo-menu-responsive; @include target-filter-query; - + @include statusprogressbar; + @include rollout; + + .v-app-loading { background-color: rgba(0, 0, 0, 0); background-image: $app-background-image, linear-gradient(to bottom, $app-background-image-gradient); @@ -120,5 +126,4 @@ $v-included-components: remove($v-included-components, form); left: 50%; margin-left: -20px; } - } diff --git a/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/styles.scss b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/styles.scss index 654cb1a61..f740c76fd 100644 --- a/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/styles.scss +++ b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/styles.scss @@ -8,9 +8,11 @@ */ @import "customstyles/hawkbitvariables"; @import "../hawkbit/hawkbittheme"; +@import "../hawkbit/addons"; /* This file prefixes all rules with the theme name to avoid causing conflicts with other themes. */ /* The actual styles should be defined in testvalothemedemo.scss */ .hawkbit { + @include addons; @include hawkbittheme; } diff --git a/hawkbit-ui/src/main/resources/messages.properties b/hawkbit-ui/src/main/resources/messages.properties index 57b807b01..8e785c3db 100644 --- a/hawkbit-ui/src/main/resources/messages.properties +++ b/hawkbit-ui/src/main/resources/messages.properties @@ -123,10 +123,10 @@ label.combobox.type = Select Type label.combobox.tag = Select Tag label.choose.tag = Choose Tag to update label.choose.tag.color = Choose Tag Color -label.filter = Filter: -label.target.filter.count = Total Targets: -label.filter.selected = Selected: -label.filter.shown = Shown: +label.filter = Filter : +label.target.filter.count = Total Targets : +label.filter.selected = Selected : +label.filter.shown = Shown : label.filter.targets = Filtered targets : label.filter.status = Status, label.filter.tags = Tags, @@ -419,6 +419,10 @@ header.assigned.ds = Assigned DS header.installed.ds = Installed DS header.target.status = Status header.target.tags = Tags +header.distributionset = Distribution set +header.numberofgroups = No. of groups +header.detail.status = Detail status +header.total.targets = Total targets distribution.details.header = Distribution set target.details.header = Target @@ -433,5 +437,42 @@ combo.type.tag.name = Type tag name label.yes = Yes label.no = No +#rollout - start +header.distributionset = Distribution set +header.numberofgroups = No. of groups +header.detail.status = Detail status + +header.rolloutgroup.installed.percentage = % Finished +header.rolloutgroup.threshold.error = Error threshold +header.rolloutgroup.threshold = Trigger threshold +header.rolloutgroup.target.date = Date and time +header.rolloutgroup.target.message = Messages +rollout.group.label.target.truncated = {0} targets has been truncated in the list due the target size limit of {1} + + +prompt.number.of.groups = Number of groups +prompt.tigger.thresold = Trigger threshold +prompt.error.threshold = Error threshold +prompt.distribution.set = Distribution set +caption.configure.rollout = Configure rollout +caption.update.rollout = Update rollout +prompt.target.filter = Custom target filter +message.rollout.nonzero.group.number = Number of groups must be greater than zero +message.rollout.max.group.number = Number of groups must not be greater than 500 +message.rollout.duplicate.check = Rollout [ {0} ] must be unique, entered value already exists. +message.correct.invalid.value = Please correct invalid values +message.enter.number = Please enter number +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.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 +#rollout - end + #Menu menu.title = Software Provisioning diff --git a/hawkbit-ui/src/main/resources/messages_de.properties b/hawkbit-ui/src/main/resources/messages_de.properties index ffb753c58..94a0df4cf 100644 --- a/hawkbit-ui/src/main/resources/messages_de.properties +++ b/hawkbit-ui/src/main/resources/messages_de.properties @@ -122,10 +122,10 @@ label.combobox.type = Select Type label.combobox.tag = Select Tag label.choose.tag = Choose Tag to update label.choose.tag.color = Choose Tag Color -label.filter = Filter: -label.target.filter.count = Total Targets: -label.filter.selected = Selected: -label.filter.shown = Shown: +label.filter = Filter : +label.target.filter.count = Total Targets : +label.filter.selected = Selected : +label.filter.shown = Shown : label.filter.status = Status, label.filter.tags = Tags, label.filter.text = Search Text @@ -142,6 +142,7 @@ label.cancelled = Cancelled label.cancelling = Canceling label.retrieved = Retrieved label.download = Downloading +label.scheduled = Scheduled label.target.id = Controller Id : label.target.ip = Controller IP : label.target.security.token = Security token : @@ -405,6 +406,24 @@ header.createdBy = Created By header.createdDate = Created Date header.modifiedBy = Modified By header.modifiedDate = Modified Date +header.delete = Delete +header.assigned.ds = Assigned DS +header.installed.ds = Installed DS +header.status = Status +header.target.tags = Tags +header.distributionset = Distribution set +header.numberofgroups = No. of groups +header.detail.status = Detail status +header.total.targets = Total targets + +header.rolloutgroup.installed.percentage = % Finished +header.rolloutgroup.threshold.error = Error threshold +header.rolloutgroup.threshold = Trigger threshold + +header.rolloutgroup.target.date = Date and time +header.rolloutgroup.target.message = Messages + +rollout.group.label.target.truncated = {0} targets has been truncated in the list due the target size limit of {1} distribution.details.header = Distribution set target.details.header = Target @@ -422,3 +441,28 @@ label.no =No #Menu menu.title = Software Provisioning + + +#Rollout management +prompt.number.of.groups = Number of groups +prompt.tigger.thresold = Trigger threshold +prompt.error.threshold = Error threshold +prompt.distribution.set = Distribution set +caption.configure.rollout = Configure rollout +caption.update.rollout = Update rollout +prompt.target.filter = Custom target filter +message.rollout.nonzero.group.number = Number of groups must be greater than zero +message.rollout.max.group.number = Number of groups must not be greater than 500 +message.rollout.duplicate.check = Rollout [ {0} ] must be unique, entered value already exists. +message.rollout.field.value.range = Value should be in range {0} to {1} +message.correct.invalid.value = Please correct invalid values +message.enter.number = Please enter number +message.rollout.started = Rollout {0} started successfully +message.rollout.paused = Rollout {0} paused successfully +message.rollout.resumed = Rollout {0} resumed 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 diff --git a/hawkbit-ui/src/main/resources/messages_en.properties b/hawkbit-ui/src/main/resources/messages_en.properties index 32e7cf0e2..d31ca2af3 100644 --- a/hawkbit-ui/src/main/resources/messages_en.properties +++ b/hawkbit-ui/src/main/resources/messages_en.properties @@ -82,8 +82,7 @@ caption.attributes = Attributes caption.panel.dist.installed = Installed distribution set caption.panel.dist.assigned = Assigned distribution set caption.soft.delete.confirmbox = Confirm Software Module Delete Action -caption.cancel.action.confirmbox = Confirm action cancel -caption.forcequit.action.confirmbox = Confirm force quit action +caption.cancel.action.confirmbox = Confirm action cancellation caption.forced.datefield = Force update at time caption.force.action.confirmbox = Confirm Force Active Action @@ -123,17 +122,16 @@ label.combobox.type = Select Type label.combobox.tag = Select Tag label.choose.tag = Choose Tag to update label.choose.tag.color = Choose Tag Color -label.filter = Filter: -label.target.filter.count = Total Targets: -label.filter.selected = Selected: -label.filter.shown = Shown: -label.filter.targets = Filtered targets : +label.filter = Filter : +label.target.filter.count = Total Targets : +label.filter.selected = Selected : +label.filter.shown = Shown : label.filter.status = Status, label.filter.tags = Tags, label.filter.text = Search Text label.filter.dist = Distribution, label.filter.custom = Custom -label.target.filter.truncated={0} targets has been truncated in the list due the target size limit of {1}, use filters to reduce the targets to be shown +label.target.filter.truncated={0} targets has been truncated in the list due the target size limit of {1}, use filters to reduce the targets to be shown label.active =Active label.inactive = In-active label.finished = Finished @@ -144,7 +142,7 @@ label.cancelled = Cancelled label.cancelling = Canceling label.retrieved = Retrieved label.download = Downloading -label.unknown = Unknown +label.scheduled = Scheduled label.target.id = Controller Id : label.target.ip = Controller IP : label.target.security.token = Security token : @@ -210,10 +208,8 @@ message.target.assigned.pending = Some target(s) are already assigned.Pending f message.cannot.delete = Cannot be deleted message.check.softwaremodule = Please provide both name and verion! message.duplicate.softwaremodule = {0} : {1} already exists! -message.cannot.delete.default.dstype = Default distribution set type cannot be deleted message.tag.delete = Please unclick the tag {0}, then try to delete message.dist.type.check.delete = Please unclick the distribution type {0}, then try to delete -message.cannot.delete.default.dstype = Default distribution set type cannot be deleted message.swmodule.type.check.delete = Please unclick the software module type {0}, then try to delete message.targets.already.deleted = Few Target(s) are already deleted.Pending for action message.dists.already.deleted = Few distribution(s) are already deleted.Pending for action @@ -260,7 +256,7 @@ message.sm.delete.confirm = Are you sure that you want to delete the selected {0 message.error.os.softmodule = Please select the OS to delete message.error.ah.softmodule = Please select the Application to delete message.error.softmodule.deleted = The selected software module is already deleted -message.cancel.action = Cancel.. +message.cancel.action = Cancel message.cancel.action.success = Action cancelled successfully ! message.cancel.action.failed = Unable to cancel the action ! message.cancel.action.confirm = Are you sure that you want to cancel this action? @@ -269,10 +265,6 @@ message.dist.alreadyAssigned = {0} Distribution Set(s) were already assigned message.force.action = Force message.force.action.confirm = Are you sure that you want to force this action? message.force.action.success = Action forced successfully ! -message.forcequit.action = Force Quit.. -message.forcequit.action.success = Action has been force quit successfully ! -message.forcequit.action.failed = Force Quitting the action is not possible ! -message.forcequit.action.confirm = Attention!\nForce quit should only be used when the assignment action is not working properly.\nForce quitting an action has no effect on the connected target. It is just resetting \nthe data stored on the SP update server. \nAre you absolutely sure that you want to force quit this action? message.distribution.no.update = distribution {0} set is already assigned to targets and cannot be changed message.action.not.allowed = Action not allowed message.onlyone.distribution.assigned = Only one distribution set can be assigned @@ -286,15 +278,13 @@ message.type.delete = Please unclick the distribution type {0}, then try to dele message.error.dist.set.type.update= Distribution Set Type is already assigned to targets and cannot be changed message.target.ds.assign.success = Assignments saved successfully ! message.no.directory.upload = Directory upload is not supported + message.delete.filter.confirm = Are you sure that you want to delete custom filter? message.delete.filter.success = Custom filter {0} deleted Successfully! message.create.filter.success = Custom filter {0} created Successfully! message.update.filter.success = Custom filter updated Successfully! message.target.filter.validation = Please enter name and query message.target.filter.duplicate = {0} already exists, please enter another value -message.tag.use.bulk.upload = {0} cannot be deleted .It is in use in targets bulk upload -message.bulk.upload.tag.assignment.failed = Tag {0} assignment failed as tag no longer exists -message.bulk.upload.tag.assignments.failed= Few tag assignments failed as tags no longer exists # action info action.target.table.selectall = Select all (Ctrl+A) @@ -317,8 +307,6 @@ message.swModule.deleted = {0} Software module(s) deleted message.upload.failed = Streaming Failed message.uploadedfile.size.exceeded = File size exceeded .Allowed size {0} bytes message.file.not.found = File not found -message.artifact.deleted =Artifact with file {0} deleted successfully - upload.swModuleTable.header = Software module upload.selectedfile.name = file selected for upload @@ -344,13 +332,9 @@ caption.tab.description = Description caption.delete.artifact.confirmbox = Confirm Artifact Delete Action -custom.filter.name = Filter Name -custom.filter.created.by = Created By -custom.created.date = Created Date #Manage distributions view label.drop.dist.delete.area = Drop here
to delete -label.no.tag.assigned = NO TAG caption.assign.software.dist.accordion.tab = Assign Software Modules message.software.assignment = {0} Software Module(s) Assignment(s) done message.dist.inuse = {0} Distribution is already assigned to target @@ -362,8 +346,6 @@ message.sw.module.type.delete = {0} Software Module Type(s) deleted successfully message.dist.type.discard.success = All Distribution Types are discarded successfully ! message.dist.discard.success = All Distributions are discarded successfully ! message.assign.discard.success = All assignments are discarded successfully ! -message.bulk.upload.assignment.failed = Distribution set assignment failed as distribution set no longer exists! -message.target.ds.assign.success = Assignments saved successfully ! # Login view notification.login.title=Welcome to Bosch IoT Software Provisioning. @@ -418,21 +400,57 @@ header.modifiedDate = Modified Date header.delete = Delete header.assigned.ds = Assigned DS header.installed.ds = Installed DS -header.target.status = Status +header.status = Status header.target.tags = Tags +header.distributionset = Distribution set +header.numberofgroups = No. of groups +header.detail.status = Detail status +header.total.targets = Total targets + +header.rolloutgroup.installed.percentage = % Finished +header.rolloutgroup.threshold.error = Error threshold +header.rolloutgroup.threshold = Trigger threshold + +header.rolloutgroup.target.date = Date and time +header.rolloutgroup.target.message = Messages + +rollout.group.label.target.truncated = {0} targets has been truncated in the list due the target size limit of {1} distribution.details.header = Distribution set target.details.header = Target header.caption.mandatory = Mandatory header.caption.typename = SoftwareModuleType header.caption.softwaremodule = SoftwareModule -header.caption.unassign = Unassign -message.sw.unassigned = Software module {0} successfully unassigned header.caption.upload.details = Upload details combo.type.tag.name = Type tag name label.yes = Yes -label.no = No +label.no =No #Menu menu.title = Software Provisioning + + +#Rollout management +prompt.number.of.groups = Number of groups +prompt.tigger.thresold = Trigger threshold +prompt.error.threshold = Error threshold +prompt.distribution.set = Distribution set +caption.configure.rollout = Configure rollout +caption.update.rollout = Update rollout +prompt.target.filter = Custom target filter +message.rollout.nonzero.group.number = Number of groups must be greater than zero +message.rollout.max.group.number = Number of groups must not be greater than 500 +message.rollout.duplicate.check = Rollout [ {0} ] must be unique, entered value already exists. +message.correct.invalid.value = Please correct invalid values +message.enter.number = Please enter number +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.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 \ No newline at end of file diff --git a/pom.xml b/pom.xml index a9d303782..d9eb427e2 100644 --- a/pom.xml +++ b/pom.xml @@ -406,6 +406,16 @@ tokenfield 7.0.1 + + org.vaadin.alump.distributionbar + dbar-addon + 1.2.0 + + + org.vaadin.addons + contextmenu + 4.5 +