Feature download only (#810)

* Added initial version of DOWNLOAD_ONLY
* Added DOWNLOAD_ONLY option to ActionTypeOptionGroupLayout
* Removed DOWNLOAD_ONLY checkbox, added Download Only UI option
* Mark actions that finished with DOWNLOADED as finished
* initial changes to realize downoadOnly in UI
* Changed method of disabling maintenanceWindow into smarter solution
* Added new icon for download only option
* Set DistributionSet as unassigned when DOWNLOAD_ONLY
* Enabled update action status for DOWNLOAD_ONLY after download
* Current state of abstraction task
* Assign DistributionSet to target if target installs it after downloading
* Abstracted class redundant methods
* Added tests
* Fixed Rollout finish status for DWONLOAD_ONLY Rollouts
* Added Rollout type json property in test documentation
* Added DOWNLOAD_ONLY test for target assignment
* Added event listener also to DistributionTable
* Fixed event listener problem
* Change column name to "Type" and added also DownloadOnly icon to that column.
* Cleanup
* Center aligned the icons in type column
* Fixed DistributionSet already assigned but not installed
* Rename download_only to downloadonly
* Further changes regarding center aligned the icons
* Fixed target assign status in Rollout view when download_only
* Fixed SonarQube issues
* Fixed SonarQube issues + code formatting
* Fixed Tests
* Marked squid:S128 as suppressed - irrelevant
* Adapting rollouts view by additional column (not finished by now)
* Putted type column on proper position
* Trying to display icons in new type column in rollouts view
* Added icon also for soft, icon might change -> just change
* createOptionGroup method in ActionTypeOptionGroupLayout class
* added first draft of type column in rollouts view
* increase visibility of sendUpdateMessageToTarget method
* Ground functionality of new type column in deployment view is now implemented
* Type column implementation in rollouts view is finished for now
* Rebased on master
* Fixed DurationControl change on ScheduleControl change.
* (Re)Added Soft deployment Icon
* Fixed SonarQube issues
* Fixed SonarQube issues
* Fixed failing test
* Fixes + added missing header
* Added message to the fail() instruction
* Fixed copyright header
* Apply suggestions from code review
* Fixed TotalTargetCountStatus.java
* Removed unused method from TotalTargetCountStatus.java
* add id to rollout create and update UI popup
* Added download_only tests for MgmtTargetResourceTest.java
* added missing header in TotalTargetCountStatusTest.java
* Rename because of newest changes
* added Download_Only dmf integration tests
* Renamed MgmtAction.forcedType to actionType
* renamed actionType to forceType for Mgmt API
* added missing javadocs for public methods
* Added Download Only support for AutoAssignment

Signed-off-by: Ahmed Sayed <ahmed.sayed@bosch-si.com>
Signed-off-by: Ammar Bikic <ammar.bikic@bosch-si.com>
This commit is contained in:
Ahmed Sayed
2019-04-17 12:27:23 +02:00
committed by Stefan Behl
parent 04b9abda3b
commit ed95ae6398
76 changed files with 1754 additions and 639 deletions

View File

@@ -23,9 +23,12 @@ public class RepositoryProperties {
/**
* Set to <code>true</code> if the repository has to reject
* {@link ActionStatus} entries for actions that are closed. Note: if this
* is enforced you have to make sure that the feedback channel from the
* devices i in order.
* {@link ActionStatus} entries for actions that are closed. This is
* especially useful if the action status feedback channel order from the
* device cannot be guaranteed.
*
* Note: if this is enforced you have to make sure that the feedback
* channel from the devices is in order.
*/
private boolean rejectActionStatusForClosedAction;

View File

@@ -8,13 +8,14 @@
*/
package org.eclipse.hawkbit.repository.event.remote;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.ActionProperties;
/**
* TenantAwareEvent that gets sent when a distribution set gets assigned to a
@@ -28,7 +29,7 @@ public class TargetAssignDistributionSetEvent extends RemoteTenantAwareEvent {
private boolean maintenanceWindowAvailable;
private final Map<String, Long> actions = new HashMap<>();
private final Map<String, ActionProperties> actions = new HashMap<>();
/**
* Default constructor.
@@ -57,12 +58,20 @@ public class TargetAssignDistributionSetEvent extends RemoteTenantAwareEvent {
this.distributionSetId = distributionSetId;
this.maintenanceWindowAvailable = maintenanceWindowAvailable;
actions.putAll(a.stream().filter(action -> action.getDistributionSet().getId().longValue() == distributionSetId)
.collect(Collectors.toMap(action -> action.getTarget().getControllerId(), Action::getId)));
.collect(Collectors.toMap(action -> action.getTarget().getControllerId(), ActionProperties::new)));
}
/**
* Constructor.
*
* @param action
* the action created for this assignment
* @param applicationId
* the application id
*/
public TargetAssignDistributionSetEvent(final Action action, final String applicationId) {
this(action.getTenant(), action.getDistributionSet().getId(), Arrays.asList(action), applicationId,
this(action.getTenant(), action.getDistributionSet().getId(), Collections.singletonList(action), applicationId,
action.isMaintenanceWindowAvailable());
}
@@ -74,7 +83,7 @@ public class TargetAssignDistributionSetEvent extends RemoteTenantAwareEvent {
return maintenanceWindowAvailable;
}
public Map<String, Long> getActions() {
public Map<String, ActionProperties> getActions() {
return actions;
}

View File

@@ -229,7 +229,12 @@ public interface Action extends TenantAwareBaseEntity {
* {@link Action#isHitAutoForceTime(long)} is reached, {@link #FORCED}
* after that.
*/
TIMEFORCED;
TIMEFORCED,
/**
* Target is only advised to download, but not install
*/
DOWNLOAD_ONLY;
}
/**

View File

@@ -0,0 +1,71 @@
/**
* Copyright (c) 2019 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;
/**
* Holds properties for {@link Action}
*/
public class ActionProperties implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private Action.ActionType actionType;
private String tenant;
private boolean maintenanceWindowAvailable;
public ActionProperties() {
}
/**
* Constructor
* @param action
* the action to populate the properties from
*/
public ActionProperties(final Action action) {
this.id = action.getId();
this.actionType = action.getActionType();
this.tenant = action.getTenant();
this.maintenanceWindowAvailable = action.isMaintenanceWindowAvailable();
}
public void setId(final Long id) {
this.id = id;
}
public Long getId() {
return id;
}
public void setTenant(final String tenant) {
this.tenant = tenant;
}
public String getTenant() {
return tenant;
}
public void setMaintenanceWindowAvailable(final boolean maintenanceWindowAvailable) {
this.maintenanceWindowAvailable = maintenanceWindowAvailable;
}
public boolean isMaintenanceWindowAvailable() {
return maintenanceWindowAvailable;
}
public Action.ActionType getActionType() {
return actionType;
}
public void setActionType(final Action.ActionType actionType) {
this.actionType = actionType;
}
}

View File

@@ -48,7 +48,7 @@ public interface TargetFilterQuery extends TenantAwareBaseEntity {
* Allowed values for auto-assign action type
*/
Set<ActionType> ALLOWED_AUTO_ASSIGN_ACTION_TYPES = Collections
.unmodifiableSet(EnumSet.of(ActionType.FORCED, ActionType.SOFT));
.unmodifiableSet(EnumSet.of(ActionType.FORCED, ActionType.SOFT, ActionType.DOWNLOAD_ONLY));
/**
* @return name of the {@link TargetFilterQuery}.

View File

@@ -58,6 +58,7 @@ public class TotalTargetCountStatus {
private final Map<Status, Long> statusTotalCountMap = new EnumMap<>(Status.class);
private final Long totalTargetCount;
private final Action.ActionType rolloutType;
/**
* Create a new states map with the target count for each state.
@@ -66,11 +67,14 @@ public class TotalTargetCountStatus {
* the action state map
* @param totalTargetCount
* the total target count
* @param rolloutType
* the type of the rollout
*/
public TotalTargetCountStatus(final List<TotalTargetCountActionStatus> targetCountActionStatus,
final Long totalTargetCount) {
final Long totalTargetCount, final Action.ActionType rolloutType) {
this.totalTargetCount = totalTargetCount;
mapActionStatusToTotalTargetCountStatus(targetCountActionStatus);
this.rolloutType = rolloutType;
addToTotalCount(targetCountActionStatus);
}
/**
@@ -78,9 +82,11 @@ public class TotalTargetCountStatus {
*
* @param totalTargetCount
* the total target count
* @param rolloutType
* the type of the rollout
*/
public TotalTargetCountStatus(final Long totalTargetCount) {
this(Collections.emptyList(), totalTargetCount);
public TotalTargetCountStatus(final Long totalTargetCount, final Action.ActionType rolloutType) {
this(Collections.emptyList(), totalTargetCount, rolloutType);
}
/**
@@ -119,8 +125,7 @@ public class TotalTargetCountStatus {
* @param rolloutStatusCountItems
* all target {@link Status} with total count
*/
private final void mapActionStatusToTotalTargetCountStatus(
final List<TotalTargetCountActionStatus> targetCountActionStatus) {
private void addToTotalCount(final List<TotalTargetCountActionStatus> targetCountActionStatus) {
if (targetCountActionStatus == null) {
statusTotalCountMap.put(TotalTargetCountStatus.Status.NOTSTARTED, totalTargetCount);
return;
@@ -128,40 +133,40 @@ public class TotalTargetCountStatus {
statusTotalCountMap.put(Status.RUNNING, 0L);
Long notStartedTargetCount = totalTargetCount;
for (final TotalTargetCountActionStatus item : targetCountActionStatus) {
convertStatus(item);
addToTotalCount(item);
notStartedTargetCount -= item.getCount();
}
statusTotalCountMap.put(TotalTargetCountStatus.Status.NOTSTARTED, notStartedTargetCount);
}
private void addToTotalCount(final TotalTargetCountActionStatus item) {
final Status status = convertStatus(item.getStatus());
statusTotalCountMap.merge(status, item.getCount(), Long::sum);
}
// Exception squid:MethodCyclomaticComplexity - simple state conversion, not
// really complex.
@SuppressWarnings("squid:MethodCyclomaticComplexity")
private void convertStatus(final TotalTargetCountActionStatus item) {
switch (item.getStatus()) {
private Status convertStatus(final Action.Status status){
switch (status) {
case SCHEDULED:
statusTotalCountMap.put(Status.SCHEDULED, item.getCount());
break;
return Status.SCHEDULED;
case ERROR:
statusTotalCountMap.put(Status.ERROR, item.getCount());
break;
return Status.ERROR;
case FINISHED:
statusTotalCountMap.put(Status.FINISHED, item.getCount());
break;
return Status.FINISHED;
case CANCELED:
return Status.CANCELLED;
case RETRIEVED:
case RUNNING:
case WARNING:
case DOWNLOAD:
case DOWNLOADED:
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;
return Status.RUNNING;
case DOWNLOADED:
return Action.ActionType.DOWNLOAD_ONLY.equals(rolloutType) ? Status.FINISHED : Status.RUNNING;
default:
throw new IllegalArgumentException("State " + item.getStatus() + "is not valid");
throw new IllegalArgumentException("State " + status + "is not valid");
}
}

View File

@@ -0,0 +1,81 @@
/**
* Copyright (c) 2019 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 io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.qameta.allure.Story;
import org.junit.Test;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
@Feature("Component Tests - TotalTargetCountStatus")
@Story("TotalTargetCountStatus should correctly present finished DOWNLOAD_ONLY actions")
public class TotalTargetCountStatusTest {
private final List<TotalTargetCountActionStatus> targetCountActionStatuses = Arrays.asList(
new TotalTargetCountActionStatus(Action.Status.SCHEDULED, 1L),
new TotalTargetCountActionStatus(Action.Status.ERROR, 2L),
new TotalTargetCountActionStatus(Action.Status.FINISHED, 3L),
new TotalTargetCountActionStatus(Action.Status.CANCELED, 4L),
new TotalTargetCountActionStatus(Action.Status.RETRIEVED, 5L),
new TotalTargetCountActionStatus(Action.Status.RUNNING, 6L),
new TotalTargetCountActionStatus(Action.Status.WARNING, 7L),
new TotalTargetCountActionStatus(Action.Status.DOWNLOAD, 8L),
new TotalTargetCountActionStatus(Action.Status.CANCELING, 9L),
new TotalTargetCountActionStatus(Action.Status.DOWNLOADED, 10L));
@Test
@Description("Different Action Statuses should be correctly mapped to the corresponding " +
"TotalTargetCountStatus.Status")
public void shouldCorrectlyMapActionStatuses() {
TotalTargetCountStatus status = new TotalTargetCountStatus(targetCountActionStatuses, 55L,
Action.ActionType.FORCED);
assertThat(status.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.SCHEDULED)).isEqualTo(1L);
assertThat(status.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.ERROR)).isEqualTo(2L);
assertThat(status.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.FINISHED)).isEqualTo(3L);
assertThat(status.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.CANCELLED)).isEqualTo(4L);
assertThat(status.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.RUNNING)).isEqualTo(45L);
assertThat(status.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.NOTSTARTED)).isEqualTo(0L);
assertThat(status.getFinishedPercent()).isEqualTo((float) 100 * 3 / 55);
}
@Test
@Description("When an empty list is passed to the TotalTargetCountStatus, all actions should be displayed as " +
"NOTSTARTED")
public void shouldCorrectlyMapActionStatusesToNotStarted() {
TotalTargetCountStatus status = new TotalTargetCountStatus(Collections.emptyList(), 55L,
Action.ActionType.FORCED);
assertThat(status.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.SCHEDULED)).isEqualTo(0L);
assertThat(status.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.ERROR)).isEqualTo(0L);
assertThat(status.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.FINISHED)).isEqualTo(0L);
assertThat(status.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.CANCELLED)).isEqualTo(0L);
assertThat(status.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.RUNNING)).isEqualTo(0L);
assertThat(status.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.NOTSTARTED)).isEqualTo(55L);
assertThat(status.getFinishedPercent()).isEqualTo(0);
}
@Test
@Description("DownloadOnly actions should be displayed as FINISHED when they have ActionStatus.DOWNLOADED")
public void shouldCorrectlyMapActionStatusesInDownloadOnlyCase() {
TotalTargetCountStatus status = new TotalTargetCountStatus(targetCountActionStatuses, 55L,
Action.ActionType.DOWNLOAD_ONLY);
assertThat(status.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.SCHEDULED)).isEqualTo(1L);
assertThat(status.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.ERROR)).isEqualTo(2L);
assertThat(status.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.FINISHED)).isEqualTo(13L);
assertThat(status.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.CANCELLED)).isEqualTo(4L);
assertThat(status.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.RUNNING)).isEqualTo(35L);
assertThat(status.getTotalTargetCountByStatus(TotalTargetCountStatus.Status.NOTSTARTED)).isEqualTo(0L);
assertThat(status.getFinishedPercent()).isEqualTo((float) 100 * 13 / 55);
}
}