diff --git a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtDistributionSetRestApi.java b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtDistributionSetRestApi.java
index fddfaf545..b4200494c 100644
--- a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtDistributionSetRestApi.java
+++ b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtDistributionSetRestApi.java
@@ -219,6 +219,9 @@ public interface MgmtDistributionSetRestApi {
* @param targetIds
* the IDs of the target which should get assigned to the
* distribution set given in the response body
+ * @param offline
+ * to true if update was executed offline, i.e. not
+ * managed by hawkBit.
* @return status OK if the assignment of the targets was successful and a
* complex return body which contains information about the assigned
* targets and the already assigned targets counters
@@ -227,7 +230,9 @@ public interface MgmtDistributionSetRestApi {
MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }, produces = { MediaTypes.HAL_JSON_VALUE,
MediaType.APPLICATION_JSON_VALUE })
ResponseEntity createAssignedTarget(
- @PathVariable("distributionSetId") Long distributionSetId, List targetIds);
+ @PathVariable("distributionSetId") Long distributionSetId,
+ final List targetIds,
+ @RequestParam(value = "offline", required = false) boolean offline);
/**
* Gets a paged list of meta data for a distribution set.
diff --git a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetRestApi.java b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetRestApi.java
index 10dde5c50..9d57afc36 100644
--- a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetRestApi.java
+++ b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetRestApi.java
@@ -15,6 +15,7 @@ import org.eclipse.hawkbit.mgmt.json.model.action.MgmtAction;
import org.eclipse.hawkbit.mgmt.json.model.action.MgmtActionRequestBodyPut;
import org.eclipse.hawkbit.mgmt.json.model.action.MgmtActionStatus;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet;
+import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtTargetAssignmentResponseBody;
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtDistributionSetAssigment;
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget;
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetAttributes;
@@ -245,6 +246,7 @@ public interface MgmtTargetRestApi {
*
* @param controllerId
* the ID of the target to retrieve the assigned distribution
+ *
* @return the assigned distribution set with status OK, if none is assigned
* than {@code null} content (e.g. "{}")
*/
@@ -259,13 +261,20 @@ public interface MgmtTargetRestApi {
* of the target to change
* @param dsId
* of the distributionset that is to be assigned
- * @return http status
+ * @param offline
+ * to true if update was executed offline, i.e. not
+ * managed by hawkBit.
+ *
+ * @return status OK if the assignment of the targets was successful and a
+ * complex return body which contains information about the assigned
+ * targets and the already assigned targets counters
*/
@RequestMapping(method = RequestMethod.POST, value = "/{controllerId}/assignedDS", consumes = {
MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }, produces = { MediaTypes.HAL_JSON_VALUE,
MediaType.APPLICATION_JSON_VALUE })
- ResponseEntity postAssignedDistributionSet(@PathVariable("controllerId") String controllerId,
- MgmtDistributionSetAssigment dsId);
+ ResponseEntity postAssignedDistributionSet(
+ @PathVariable("controllerId") String controllerId, MgmtDistributionSetAssigment dsId,
+ @RequestParam(value = "offline", required = false) boolean offline);
/**
* Handles the GET request of retrieving the installed distribution set of
diff --git a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResource.java b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResource.java
index 0cad26bef..ddf81abca 100644
--- a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResource.java
+++ b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResource.java
@@ -35,7 +35,6 @@ import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.DistributionSet;
-import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target;
@@ -229,16 +228,22 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
@Override
public ResponseEntity createAssignedTarget(
@PathVariable("distributionSetId") final Long distributionSetId,
- @RequestBody final List targetIds) {
+ @RequestBody final List assignments,
+ @RequestParam(value = "offline", required = false) final boolean offline) {
- final DistributionSetAssignmentResult assignDistributionSet = this.deployManagament.assignDistributionSet(
+ if (offline) {
+ return ResponseEntity.ok(MgmtDistributionSetMapper
+ .toResponse(this.deployManagament.offlineAssignedDistributionSet(distributionSetId, assignments
+ .stream().map(MgmtTargetAssignmentRequestBody::getId).collect(Collectors.toList()))));
+ }
+
+ return ResponseEntity.ok(MgmtDistributionSetMapper.toResponse(this.deployManagament.assignDistributionSet(
distributionSetId,
- targetIds.stream()
- .map(t -> new TargetWithActionType(t.getId(),
- MgmtRestModelMapper.convertActionType(t.getType()), t.getForcetime()))
- .collect(Collectors.toList()));
+ assignments.stream()
+ .map(assignment -> new TargetWithActionType(assignment.getId(),
+ MgmtRestModelMapper.convertActionType(assignment.getType()), assignment.getForcetime()))
+ .collect(Collectors.toList()))));
- return ResponseEntity.ok(MgmtDistributionSetMapper.toResponse(assignDistributionSet));
}
@Override
diff --git a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java
index 2710d7afe..385810baf 100644
--- a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java
+++ b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java
@@ -21,6 +21,7 @@ import org.eclipse.hawkbit.mgmt.json.model.action.MgmtActionRequestBodyPut;
import org.eclipse.hawkbit.mgmt.json.model.action.MgmtActionStatus;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet;
+import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtTargetAssignmentResponseBody;
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtDistributionSetAssigment;
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget;
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetAttributes;
@@ -253,16 +254,21 @@ public class MgmtTargetResource implements MgmtTargetRestApi {
}
@Override
- public ResponseEntity postAssignedDistributionSet(@PathVariable("controllerId") final String controllerId,
- @RequestBody final MgmtDistributionSetAssigment dsId) {
+ public ResponseEntity postAssignedDistributionSet(
+ @PathVariable("controllerId") final String controllerId,
+ @RequestBody final MgmtDistributionSetAssigment dsId,
+ @RequestParam(value = "offline", required = false) final boolean offline) {
+
+ if (offline) {
+ return ResponseEntity.ok(MgmtDistributionSetMapper.toResponse(
+ deploymentManagement.offlineAssignedDistributionSet(dsId.getId(), Arrays.asList(controllerId))));
+ }
findTargetWithExceptionIfNotFound(controllerId);
final ActionType type = (dsId.getType() != null) ? MgmtRestModelMapper.convertActionType(dsId.getType())
: ActionType.FORCED;
- this.deploymentManagement.assignDistributionSet(dsId.getId(), type, dsId.getForcetime(),
- Arrays.asList(controllerId));
-
- return ResponseEntity.ok().build();
+ return ResponseEntity.ok(MgmtDistributionSetMapper.toResponse(deploymentManagement
+ .assignDistributionSet(dsId.getId(), type, dsId.getForcetime(), Arrays.asList(controllerId))));
}
@Override
diff --git a/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResourceTest.java b/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResourceTest.java
index ef2984bc7..b9e12c958 100644
--- a/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResourceTest.java
+++ b/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResourceTest.java
@@ -205,30 +205,49 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
@Test
@Description("Ensures that multi target assignment through API is reflected by the repository.")
public void assignMultipleTargetsToDistributionSet() throws Exception {
- // prepare distribution set
- final Set createDistributionSetsAlphabetical = createDistributionSetsAlphabetical(1);
- final DistributionSet createdDs = createDistributionSetsAlphabetical.iterator().next();
- // prepare targets
- final String[] knownTargetIds = new String[] { "1", "2", "3", "4", "5" };
+ final DistributionSet createdDs = testdataFactory.createDistributionSet();
+ final List targets = testdataFactory.createTargets(5);
final JSONArray list = new JSONArray();
- for (final String targetId : knownTargetIds) {
- testdataFactory.createTarget(targetId);
- list.put(new JSONObject().put("id", Long.valueOf(targetId)));
- }
+ targets.forEach(target -> list.put(new JSONObject().put("id", target.getControllerId())));
+
// assign already one target to DS
- assignDistributionSet(createdDs.getId(), knownTargetIds[0]);
+ assignDistributionSet(createdDs.getId(), targets.get(0).getControllerId());
mvc.perform(post(
MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + createdDs.getId() + "/assignedTargets")
.contentType(MediaType.APPLICATION_JSON).content(list.toString()))
- .andExpect(status().isOk()).andExpect(jsonPath("$.assigned", equalTo(knownTargetIds.length - 1)))
+ .andExpect(status().isOk()).andExpect(jsonPath("$.assigned", equalTo(targets.size() - 1)))
.andExpect(jsonPath("$.alreadyAssigned", equalTo(1)))
- .andExpect(jsonPath("$.total", equalTo(knownTargetIds.length)));
+ .andExpect(jsonPath("$.total", equalTo(targets.size())));
assertThat(targetManagement.findTargetByAssignedDistributionSet(createdDs.getId(), PAGE).getContent())
.as("Five targets in repository have DS assigned").hasSize(5);
}
+ @Test
+ @Description("Ensures that offline reported multi target assignment through API is reflected by the repository.")
+ public void offlineAssignmentOfMultipleTargetsToDistributionSet() throws Exception {
+ final DistributionSet createdDs = testdataFactory.createDistributionSet();
+ final List targets = testdataFactory.createTargets(5);
+ final JSONArray list = new JSONArray();
+ targets.forEach(target -> list.put(new JSONObject().put("id", target.getControllerId())));
+
+ // assign already one target to DS
+ assignDistributionSet(createdDs.getId(), targets.get(0).getControllerId());
+
+ mvc.perform(post(MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + createdDs.getId()
+ + "/assignedTargets?offline=true").contentType(MediaType.APPLICATION_JSON).content(list.toString()))
+ .andExpect(status().isOk()).andExpect(jsonPath("$.assigned", equalTo(targets.size() - 1)))
+ .andExpect(jsonPath("$.alreadyAssigned", equalTo(1)))
+ .andExpect(jsonPath("$.total", equalTo(targets.size())));
+
+ assertThat(targetManagement.findTargetByAssignedDistributionSet(createdDs.getId(), PAGE).getContent())
+ .as("Five targets in repository have DS assigned").hasSize(5);
+
+ assertThat(targetManagement.findTargetByInstalledDistributionSet(createdDs.getId(), PAGE).getContent())
+ .hasSize(4);
+ }
+
@Test
@Description("Ensures that assigned targets of DS are returned as reflected by the repository.")
public void getAssignedTargetsOfDistributionSet() throws Exception {
diff --git a/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java b/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java
index 1e360d08c..191712807 100644
--- a/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java
+++ b/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java
@@ -47,6 +47,7 @@ import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target;
+import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.eclipse.hawkbit.rest.exception.MessageNotReadableException;
import org.eclipse.hawkbit.rest.json.model.ExceptionInfo;
@@ -1145,7 +1146,9 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest
mvc.perform(post(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + target.getControllerId() + "/assignedDS")
.content("{\"id\":" + set.getId() + "}").contentType(MediaType.APPLICATION_JSON))
- .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
+ .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
+ .andExpect(jsonPath("assigned", equalTo(1))).andExpect(jsonPath("alreadyAssigned", equalTo(0)))
+ .andExpect(jsonPath("total", equalTo(1)));
assertThat(deploymentManagement.getAssignedDistributionSet(target.getControllerId()).get()).isEqualTo(set);
target = targetManagement.findTargetByControllerID(target.getControllerId()).get();
@@ -1153,7 +1156,41 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest
// repeating DS assignment leads again to OK
mvc.perform(post(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + target.getControllerId() + "/assignedDS")
.content("{\"id\":" + set.getId() + "}").contentType(MediaType.APPLICATION_JSON))
- .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
+ .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
+ .andExpect(jsonPath("assigned", equalTo(0))).andExpect(jsonPath("alreadyAssigned", equalTo(1)))
+ .andExpect(jsonPath("total", equalTo(1)));
+
+ // ...but does not change the target
+ assertThat(targetManagement.findTargetByControllerID(target.getControllerId()).get()).isEqualTo(target);
+ }
+
+ @Test
+ @Description("Verfies that an offline DS to target assignment is reflected by the repository and that repeating "
+ + "the assignment does not change the target.")
+ public void offlineAssignDistributionSetToTarget() throws Exception {
+
+ Target target = testdataFactory.createTarget();
+ final DistributionSet set = testdataFactory.createDistributionSet("one");
+
+ mvc.perform(post(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + target.getControllerId()
+ + "/assignedDS?offline=true").content("{\"id\":" + set.getId() + "}")
+ .contentType(MediaType.APPLICATION_JSON))
+ .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
+ .andExpect(jsonPath("assigned", equalTo(1))).andExpect(jsonPath("alreadyAssigned", equalTo(0)))
+ .andExpect(jsonPath("total", equalTo(1)));
+
+ assertThat(deploymentManagement.getAssignedDistributionSet(target.getControllerId()).get()).isEqualTo(set);
+ assertThat(deploymentManagement.getInstalledDistributionSet(target.getControllerId()).get()).isEqualTo(set);
+ target = targetManagement.findTargetByControllerID(target.getControllerId()).get();
+ assertThat(target.getUpdateStatus()).isEqualTo(TargetUpdateStatus.IN_SYNC);
+
+ // repeating DS assignment leads again to OK
+ mvc.perform(post(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + target.getControllerId()
+ + "/assignedDS?offline=true").content("{\"id\":" + set.getId() + "}")
+ .contentType(MediaType.APPLICATION_JSON))
+ .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
+ .andExpect(jsonPath("assigned", equalTo(0))).andExpect(jsonPath("alreadyAssigned", equalTo(1)))
+ .andExpect(jsonPath("total", equalTo(1)));
// ...but does not change the target
assertThat(targetManagement.findTargetByControllerID(target.getControllerId()).get()).isEqualTo(target);
diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java
index c943d77a1..046d9e954 100644
--- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java
+++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java
@@ -14,6 +14,7 @@ import java.util.Optional;
import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
+import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
@@ -28,6 +29,7 @@ import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.model.Target;
+import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.model.TargetWithActionType;
import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.data.domain.Page;
@@ -69,7 +71,6 @@ public interface DeploymentManagement {
DistributionSetAssignmentResult assignDistributionSet(@NotNull Long dsID, @NotNull ActionType actionType,
long forcedTimestamp, @NotEmpty Collection controllerIDs);
-
/**
* method assigns the {@link DistributionSet} to all {@link Target}s by
* their IDs with a specific {@link ActionType} and {@code forcetime}.
@@ -116,6 +117,38 @@ public interface DeploymentManagement {
DistributionSetAssignmentResult assignDistributionSet(@NotNull Long dsID,
@NotEmpty Collection targets, String actionMessage);
+ /**
+ * Method registers an "offline" assignment, i.e. adds a completed action
+ * for the given {@link DistributionSet} to the given {@link Target}s.
+ *
+ * The handling differs to hawkBit managed updates my means that:
+ *
+ *
+ * - it ignores targets completely that are in
+ * {@link TargetUpdateStatus#PENDING}.
+ * - it creates completed actions.
+ * - sets both installed and assigned DS on the target and switches the
+ * status to {@link TargetUpdateStatus#IN_SYNC}.
+ * - does not send a {@link TargetAssignDistributionSetEvent}.
+ *
+ *
+ * @param dsID
+ * the ID of the distribution set that was assigned
+ * @param controllerIDs
+ * a list of IDs of the targets that where assigned
+ * @return the assignment result
+ *
+ * @throws IncompleteDistributionSetException
+ * if mandatory {@link SoftwareModuleType} are not assigned as
+ * defined by the {@link DistributionSetType}.
+ *
+ * @throws EntityNotFoundException
+ * if either provided {@link DistributionSet} or {@link Target}s
+ * do not exist
+ */
+ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET)
+ DistributionSetAssignmentResult offlineAssignedDistributionSet(Long dsID, Collection controllerIDs);
+
/**
* Cancels given {@link Action} for given {@link Target}. The method will
* immediately add a {@link Status#CANCELED} status to the action. However,
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/AbstractDsAssignmentStrategy.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/AbstractDsAssignmentStrategy.java
new file mode 100644
index 000000000..66b50500a
--- /dev/null
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/AbstractDsAssignmentStrategy.java
@@ -0,0 +1,188 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.repository.jpa;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.eclipse.hawkbit.repository.RepositoryConstants;
+import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
+import org.eclipse.hawkbit.repository.event.remote.entity.CancelTargetAssignmentEvent;
+import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
+import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
+import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
+import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
+import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus;
+import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
+import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
+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.Target;
+import org.eclipse.hawkbit.repository.model.TargetWithActionType;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationEventPublisher;
+
+/**
+ * {@link DistributionSet} to {@link Target} assignment strategy as utility for
+ * {@link JpaDeploymentManagement}.
+ *
+ */
+public abstract class AbstractDsAssignmentStrategy {
+
+ protected final TargetRepository targetRepository;
+ protected final AfterTransactionCommitExecutor afterCommit;
+ protected final ApplicationEventPublisher eventPublisher;
+ protected final ApplicationContext applicationContext;
+ private final ActionRepository actionRepository;
+ private final ActionStatusRepository actionStatusRepository;
+
+ AbstractDsAssignmentStrategy(final TargetRepository targetRepository,
+ final AfterTransactionCommitExecutor afterCommit, final ApplicationEventPublisher eventPublisher,
+ final ApplicationContext applicationContext, final ActionRepository actionRepository,
+ final ActionStatusRepository actionStatusRepository) {
+ this.targetRepository = targetRepository;
+ this.afterCommit = afterCommit;
+ this.eventPublisher = eventPublisher;
+ this.applicationContext = applicationContext;
+ this.actionRepository = actionRepository;
+ this.actionStatusRepository = actionStatusRepository;
+ }
+
+ /**
+ * Find targets to be considered for assignment.
+ *
+ * @param controllerIDs
+ * as provided by repository caller
+ * @param distributionSetId
+ * to assign
+ * @return list of targets up to {@link Constants#MAX_ENTRIES_IN_STATEMENT}
+ */
+ abstract List findTargetsForAssignment(final List controllerIDs, final long distributionSetId);
+
+ /**
+ * Update status and DS fields of given target.
+ *
+ * @param distributionSet
+ * to set
+ * @param targetIds
+ * to change
+ * @param currentUser
+ * for auditing
+ */
+ abstract void updateTargetStatus(final JpaDistributionSet distributionSet, final List> targetIds,
+ final String currentUser);
+
+ /**
+ * Cancels actions that can be canceled (i.e.
+ * {@link DistributionSet#isRequiredMigrationStep() is false})
+ * as a result of the new assignment and returns all {@link Target}s where
+ * such actions existed.
+ *
+ * @param targetIds
+ * to cancel actions for
+ * @return {@link Set} of {@link Target#getId()}s
+ */
+ abstract Set findTargetIdsToCancel(List> targetIds);
+
+ /**
+ * Handles event sending related to the assignment.
+ *
+ * @param targets
+ * to send events for
+ * @param targetIdsCancelList
+ * targets where an action was canceled
+ * @param controllerIdsToActions
+ * mapping of {@link Target#getControllerId()} to new
+ * {@link Action} that was created as part of the assignment.
+ */
+ abstract void sendAssignmentEvents(final List targets, final Set targetIdsCancelList,
+ final Map controllerIdsToActions);
+
+ protected void sendTargetAssignDistributionSetEvent(final Action action) {
+ afterCommit.afterCommit(() -> eventPublisher
+ .publishEvent(new TargetAssignDistributionSetEvent(action, applicationContext.getId())));
+ }
+
+ protected void sendTargetUpdatedEvent(final JpaTarget target) {
+ afterCommit.afterCommit(
+ () -> eventPublisher.publishEvent(new TargetUpdatedEvent(target, applicationContext.getId())));
+ }
+
+ /**
+ * Removes {@link Action}s that are no longer necessary and sends
+ * cancellations to the controller.
+ *
+ * @param targetsIds
+ * to override {@link Action}s
+ */
+ protected List overrideObsoleteUpdateActions(final Collection targetsIds) {
+
+ // Figure out if there are potential target/action combinations that
+ // need to be considered for cancellation
+ final List activeActions = actionRepository
+ .findByActiveAndTargetIdInAndActionStatusNotEqualToAndDistributionSetRequiredMigrationStep(targetsIds,
+ Action.Status.CANCELING);
+
+ return activeActions.stream().map(action -> {
+ action.setStatus(Status.CANCELING);
+
+ // document that the status has been retrieved
+ actionStatusRepository.save(new JpaActionStatus(action, Status.CANCELING, System.currentTimeMillis(),
+ RepositoryConstants.SERVER_MESSAGE_PREFIX + "cancel obsolete action due to new update"));
+ actionRepository.save(action);
+
+ cancelAssignDistributionSetEvent(action.getTarget(), action.getId());
+
+ return action.getTarget().getId();
+ }).collect(Collectors.toList());
+
+ }
+
+ /**
+ * Sends the {@link CancelTargetAssignmentEvent} for a specific target to
+ * the eventPublisher.
+ *
+ * @param target
+ * the Target which has been assigned to a distribution set
+ * @param actionId
+ * the action id of the assignment
+ */
+ void cancelAssignDistributionSetEvent(final Target target, final Long actionId) {
+ afterCommit.afterCommit(() -> eventPublisher
+ .publishEvent(new CancelTargetAssignmentEvent(target, actionId, applicationContext.getId())));
+ }
+
+ JpaAction createTargetAction(final Map targetsWithActionMap, final JpaTarget target,
+ final JpaDistributionSet set) {
+ final JpaAction actionForTarget = new JpaAction();
+ final TargetWithActionType targetWithActionType = targetsWithActionMap.get(target.getControllerId());
+ actionForTarget.setActionType(targetWithActionType.getActionType());
+ actionForTarget.setForcedTime(targetWithActionType.getForceTime());
+ actionForTarget.setActive(true);
+ actionForTarget.setTarget(target);
+ actionForTarget.setDistributionSet(set);
+ return actionForTarget;
+ }
+
+ JpaActionStatus createActionStatus(final JpaAction action, final String actionMessage) {
+ final JpaActionStatus actionStatus = new JpaActionStatus();
+ actionStatus.setAction(action);
+ actionStatus.setOccurredAt(action.getCreatedAt());
+
+ if (actionMessage != null) {
+ actionStatus.addMessage(actionMessage);
+ }
+
+ return actionStatus;
+ }
+}
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java
index 9d2e57d98..d01473989 100644
--- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java
@@ -31,8 +31,6 @@ import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
-import org.eclipse.hawkbit.repository.event.remote.entity.CancelTargetAssignmentEvent;
-import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.ForceQuitActionNotAllowedException;
@@ -47,7 +45,6 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
-import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.Action.Status;
@@ -62,7 +59,6 @@ import org.eclipse.hawkbit.repository.model.TargetWithActionType;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.dao.ConcurrencyFailureException;
@@ -99,51 +95,70 @@ public class JpaDeploymentManagement implements DeploymentManagement {
*/
private static final int ACTION_PAGE_LIMIT = 1000;
- @Autowired
- private EntityManager entityManager;
+ private final EntityManager entityManager;
+ private final ActionRepository actionRepository;
+ private final DistributionSetRepository distributionSetRepository;
+ private final TargetRepository targetRepository;
+ private final ActionStatusRepository actionStatusRepository;
+ private final TargetManagement targetManagement;
+ private final AuditorAware auditorProvider;
+ private final ApplicationEventPublisher eventPublisher;
+ private final ApplicationContext applicationContext;
+ private final AfterTransactionCommitExecutor afterCommit;
+ private final VirtualPropertyReplacer virtualPropertyReplacer;
+ private final PlatformTransactionManager txManager;
+ private final OnlineDsAssignmentStrategy onlineDsAssignmentStrategy;
+ private final OfflineDsAssignmentStrategy offlineDsAssignmentStrategy;
- @Autowired
- private ActionRepository actionRepository;
+ JpaDeploymentManagement(final EntityManager entityManager, final ActionRepository actionRepository,
+ final DistributionSetRepository distributionSetRepository, final TargetRepository targetRepository,
+ final ActionStatusRepository actionStatusRepository, final TargetManagement targetManagement,
+ final AuditorAware auditorProvider, final ApplicationEventPublisher eventPublisher,
+ final ApplicationContext applicationContext, final AfterTransactionCommitExecutor afterCommit,
+ final VirtualPropertyReplacer virtualPropertyReplacer, final PlatformTransactionManager txManager) {
+ this.entityManager = entityManager;
+ this.actionRepository = actionRepository;
+ this.distributionSetRepository = distributionSetRepository;
+ this.targetRepository = targetRepository;
+ this.actionStatusRepository = actionStatusRepository;
+ this.targetManagement = targetManagement;
+ this.auditorProvider = auditorProvider;
+ this.eventPublisher = eventPublisher;
+ this.applicationContext = applicationContext;
+ this.afterCommit = afterCommit;
+ this.virtualPropertyReplacer = virtualPropertyReplacer;
+ this.txManager = txManager;
+ onlineDsAssignmentStrategy = new OnlineDsAssignmentStrategy(targetRepository, afterCommit, eventPublisher,
+ applicationContext, actionRepository, actionStatusRepository);
+ offlineDsAssignmentStrategy = new OfflineDsAssignmentStrategy(targetRepository, afterCommit, eventPublisher,
+ applicationContext, actionRepository, actionStatusRepository);
+ }
- @Autowired
- private DistributionSetRepository distributionSetRepository;
-
- @Autowired
- private TargetRepository targetRepository;
-
- @Autowired
- private ActionStatusRepository actionStatusRepository;
-
- @Autowired
- private TargetManagement targetManagement;
-
- @Autowired
- private AuditorAware auditorProvider;
-
- @Autowired
- private ApplicationEventPublisher eventPublisher;
-
- @Autowired
- private ApplicationContext applicationContext;
-
- @Autowired
- private AfterTransactionCommitExecutor afterCommit;
-
- @Autowired
- private VirtualPropertyReplacer virtualPropertyReplacer;
-
- @Autowired
- private PlatformTransactionManager txManager;
+ @Override
+ @Transactional(isolation = Isolation.READ_COMMITTED)
+ @Retryable(include = {
+ ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
+ public DistributionSetAssignmentResult offlineAssignedDistributionSet(final Long dsID,
+ final Collection controllerIDs) {
+ return assignDistributionSetToTargets(dsID,
+ controllerIDs.stream()
+ .map(controllerId -> new TargetWithActionType(controllerId, ActionType.FORCED, -1))
+ .collect(Collectors.toList()),
+ null, offlineDsAssignmentStrategy);
+ }
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public DistributionSetAssignmentResult assignDistributionSet(final Long dsID, final ActionType actionType,
- final long forcedTimestamp, final Collection targetIDs) {
+ final long forcedTimestamp, final Collection controllerIDs) {
- return assignDistributionSetToTargets(dsID, targetIDs.stream()
- .map(t -> new TargetWithActionType(t, actionType, forcedTimestamp)).collect(Collectors.toList()), null);
+ return assignDistributionSetToTargets(dsID,
+ controllerIDs.stream()
+ .map(controllerId -> new TargetWithActionType(controllerId, actionType, forcedTimestamp))
+ .collect(Collectors.toList()),
+ null, onlineDsAssignmentStrategy);
}
@@ -154,7 +169,7 @@ public class JpaDeploymentManagement implements DeploymentManagement {
public DistributionSetAssignmentResult assignDistributionSet(final Long dsID,
final Collection targets) {
- return assignDistributionSetToTargets(dsID, targets, null);
+ return assignDistributionSetToTargets(dsID, targets, null, onlineDsAssignmentStrategy);
}
@Override
@@ -164,12 +179,22 @@ public class JpaDeploymentManagement implements DeploymentManagement {
public DistributionSetAssignmentResult assignDistributionSet(final Long dsID,
final Collection targets, final String actionMessage) {
- return assignDistributionSetToTargets(dsID, targets, actionMessage);
+ return assignDistributionSetToTargets(dsID, targets, actionMessage, onlineDsAssignmentStrategy);
}
/**
* method assigns the {@link DistributionSet} to all {@link Target}s by
* their IDs with a specific {@link ActionType} and {@code forcetime}.
+ *
+ *
+ * In case the update was executed offline (i.e. not managed by hawkBit) the
+ * handling differs my means that:
+ * A. it ignores targets completely that are in
+ * {@link TargetUpdateStatus#PENDING}.
+ * B. it created completed actions.
+ * C. sets both installed and assigned DS on the target and switches the
+ * status to {@link TargetUpdateStatus#IN_SYNC}
+ * D. does not send a {@link TargetAssignDistributionSetEvent}.
*
* @param dsID
* the ID of the distribution set to assign
@@ -177,6 +202,8 @@ public class JpaDeploymentManagement implements DeploymentManagement {
* a list of all targets and their action type
* @param actionMessage
* an optional message to be written into the action status
+ * @param offline
+ * to true in offline case
* @return the assignment result
*
* @throw IncompleteDistributionSetException if mandatory
@@ -184,7 +211,8 @@ public class JpaDeploymentManagement implements DeploymentManagement {
* {@link DistributionSetType}.
*/
private DistributionSetAssignmentResult assignDistributionSetToTargets(final Long dsID,
- final Collection targetsWithActionType, final String actionMessage) {
+ final Collection targetsWithActionType, final String actionMessage,
+ final AbstractDsAssignmentStrategy assignmentStrategy) {
final JpaDistributionSet set = distributionSetRepository.findOne(dsID);
if (set == null) {
@@ -209,10 +237,7 @@ public class JpaDeploymentManagement implements DeploymentManagement {
// maximum 1000 elements, so we need to split the entries here and
// execute multiple statements we take the target only into account if
// the requested operation is no duplicate of a previous one
- final List targets = Lists.partition(controllerIDs, Constants.MAX_ENTRIES_IN_STATEMENT).stream()
- .map(ids -> targetRepository
- .findAll(TargetSpecifications.hasControllerIdAndAssignedDistributionSetIdNot(ids, set.getId())))
- .flatMap(List::stream).collect(Collectors.toList());
+ final List targets = assignmentStrategy.findTargetsForAssignment(controllerIDs, set.getId());
if (targets.isEmpty()) {
// detaching as it is not necessary to persist the set itself
@@ -229,8 +254,7 @@ public class JpaDeploymentManagement implements DeploymentManagement {
// need to remember which one we have been switched to canceling state
// because for targets which we have changed to canceling we don't want
// to publish the new action update event.
- final Set targetIdsCancellList = targetIds.stream().map(this::overrideObsoleteUpdateActions)
- .flatMap(Collection::stream).collect(Collectors.toSet());
+ final Set targetIdsCancellList = assignmentStrategy.findTargetIdsToCancel(targetIds);
// cancel all scheduled actions which are in-active, these actions were
// not active before and the manual assignment which has been done
@@ -245,24 +269,26 @@ public class JpaDeploymentManagement implements DeploymentManagement {
currentUser = null;
}
- targetIds.forEach(tIds -> targetRepository.setAssignedDistributionSetAndUpdateStatus(TargetUpdateStatus.PENDING,
- set, System.currentTimeMillis(), currentUser, tIds));
+ assignmentStrategy.updateTargetStatus(set, targetIds, currentUser);
+
final Map targetIdsToActions = targets.stream()
- .map(t -> actionRepository.save(createTargetAction(targetsWithActionMap, t, set)))
+ .map(t -> actionRepository.save(assignmentStrategy.createTargetAction(targetsWithActionMap, t, set)))
.collect(Collectors.toMap(a -> a.getTarget().getControllerId(), Function.identity()));
// create initial action status when action is created so we remember
// the initial running status because we will change the status
// of the action itself and with this action status we have a nicer
// action history.
- targetIdsToActions.values().forEach(action -> setRunningActionStatus(action, actionMessage));
+ actionStatusRepository.save(targetIdsToActions.values().stream()
+ .map(action -> assignmentStrategy.createActionStatus(action, actionMessage))
+ .collect(Collectors.toList()));
// detaching as it is not necessary to persist the set itself
entityManager.detach(set);
// detaching as the entity has been updated by the JPQL query above
targets.forEach(entityManager::detach);
- sendAssignmentEvents(targets, targetIdsCancellList, targetIdsToActions);
+ assignmentStrategy.sendAssignmentEvents(targets, targetIdsCancellList, targetIdsToActions);
return new DistributionSetAssignmentResult(
targets.stream().map(Target::getControllerId).collect(Collectors.toList()), targets.size(),
@@ -270,77 +296,6 @@ public class JpaDeploymentManagement implements DeploymentManagement {
targetManagement);
}
- private void sendAssignmentEvents(final List targets, final Set targetIdsCancellList,
- final Map targetIdsToActions) {
-
- targets.forEach(target -> {
- sendTargetUpdatedEvent(target);
- if (targetIdsCancellList.contains(target.getId())) {
- return;
- }
-
- sendTargetAssignDistributionSetEvent(targetIdsToActions.get(target.getControllerId()));
- });
- }
-
- private static JpaAction createTargetAction(final Map targetsWithActionMap,
- final JpaTarget target, final JpaDistributionSet set) {
- final JpaAction actionForTarget = new JpaAction();
- final TargetWithActionType targetWithActionType = targetsWithActionMap.get(target.getControllerId());
- actionForTarget.setActionType(targetWithActionType.getActionType());
- actionForTarget.setForcedTime(targetWithActionType.getForceTime());
- actionForTarget.setActive(true);
- actionForTarget.setStatus(Status.RUNNING);
- actionForTarget.setTarget(target);
- actionForTarget.setDistributionSet(set);
- return actionForTarget;
- }
-
- private void sendTargetAssignDistributionSetEvent(final Action action) {
- afterCommit.afterCommit(() -> eventPublisher
- .publishEvent(new TargetAssignDistributionSetEvent(action, applicationContext.getId())));
- }
-
- private void sendTargetUpdatedEvent(final JpaTarget target) {
-
- // Update is not available in the object as the update was executed
- // through JQL
- target.setUpdateStatus(TargetUpdateStatus.PENDING);
-
- afterCommit.afterCommit(
- () -> eventPublisher.publishEvent(new TargetUpdatedEvent(target, applicationContext.getId())));
- }
-
- /**
- * Removes {@link Action}s that are no longer necessary and sends
- * cancellations to the controller.
- *
- * @param targetsIds
- * to override {@link Action}s
- */
- private List overrideObsoleteUpdateActions(final Collection targetsIds) {
-
- // Figure out if there are potential target/action combinations that
- // need to be considered for cancellation
- final List activeActions = actionRepository
- .findByActiveAndTargetIdInAndActionStatusNotEqualToAndDistributionSetRequiredMigrationStep(targetsIds,
- Action.Status.CANCELING);
-
- return activeActions.stream().map(action -> {
- action.setStatus(Status.CANCELING);
- // document that the status has been retrieved
-
- actionStatusRepository.save(new JpaActionStatus(action, Status.CANCELING, System.currentTimeMillis(),
- RepositoryConstants.SERVER_MESSAGE_PREFIX + "cancel obsolete action due to new update"));
- actionRepository.save(action);
-
- cancelAssignDistributionSetEvent(action.getTarget(), action.getId());
-
- return action.getTarget().getId();
- }).collect(Collectors.toList());
-
- }
-
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
@Retryable(include = {
@@ -363,7 +318,7 @@ public class JpaDeploymentManagement implements DeploymentManagement {
actionStatusRepository.save(new JpaActionStatus(action, Status.CANCELING, System.currentTimeMillis(),
RepositoryConstants.SERVER_MESSAGE_PREFIX + "manual cancelation requested"));
final Action saveAction = actionRepository.save(action);
- cancelAssignDistributionSetEvent(action.getTarget(), action.getId());
+ onlineDsAssignmentStrategy.cancelAssignDistributionSetEvent(action.getTarget(), action.getId());
return saveAction;
} else {
@@ -371,20 +326,6 @@ public class JpaDeploymentManagement implements DeploymentManagement {
}
}
- /**
- * Sends the {@link CancelTargetAssignmentEvent} for a specific target to
- * the eventPublisher.
- *
- * @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(() -> eventPublisher
- .publishEvent(new CancelTargetAssignmentEvent(target, actionId, applicationContext.getId())));
- }
-
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
@Retryable(include = {
@@ -471,14 +412,14 @@ public class JpaDeploymentManagement implements DeploymentManagement {
}
// check if we need to override running update actions
- final List overrideObsoleteUpdateActions = overrideObsoleteUpdateActions(
- Collections.singletonList(action.getTarget().getId()));
+ final List overrideObsoleteUpdateActions = onlineDsAssignmentStrategy
+ .overrideObsoleteUpdateActions(Collections.singletonList(action.getTarget().getId()));
action.setActive(true);
action.setStatus(Status.RUNNING);
final JpaAction savedAction = actionRepository.save(action);
- setRunningActionStatus(savedAction, null);
+ actionStatusRepository.save(onlineDsAssignmentStrategy.createActionStatus(savedAction, null));
target = (JpaTarget) entityManager.merge(savedAction.getTarget());
@@ -494,18 +435,6 @@ public class JpaDeploymentManagement implements DeploymentManagement {
}
}
- private void setRunningActionStatus(final JpaAction action, final String actionMessage) {
- final JpaActionStatus actionStatus = new JpaActionStatus();
- actionStatus.setAction(action);
- actionStatus.setOccurredAt(action.getCreatedAt());
- actionStatus.setStatus(Status.RUNNING);
- if (actionMessage != null) {
- actionStatus.addMessage(actionMessage);
- }
-
- actionStatusRepository.save(actionStatus);
- }
-
private void setSkipActionStatus(final JpaAction action) {
final JpaActionStatus actionStatus = new JpaActionStatus();
actionStatus.setAction(action);
@@ -683,4 +612,5 @@ public class JpaDeploymentManagement implements DeploymentManagement {
return distributionSetRepository.findInstalledAtTarget(controllerId);
}
+
}
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/OfflineDsAssignmentStrategy.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/OfflineDsAssignmentStrategy.java
new file mode 100644
index 000000000..671eecf44
--- /dev/null
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/OfflineDsAssignmentStrategy.java
@@ -0,0 +1,96 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.repository.jpa;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.eclipse.hawkbit.repository.RepositoryConstants;
+import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
+import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
+import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
+import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus;
+import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
+import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
+import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
+import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications;
+import org.eclipse.hawkbit.repository.model.Action.Status;
+import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
+import org.eclipse.hawkbit.repository.model.TargetWithActionType;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationEventPublisher;
+
+import com.google.common.collect.Lists;
+
+/**
+ * AbstractDsAssignmentStrategy for offline assignments, i.e. not managed by
+ * hawkBit.
+ *
+ */
+public class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
+
+ OfflineDsAssignmentStrategy(final TargetRepository targetRepository,
+ final AfterTransactionCommitExecutor afterCommit, final ApplicationEventPublisher eventPublisher,
+ final ApplicationContext applicationContext, final ActionRepository actionRepository,
+ final ActionStatusRepository actionStatusRepository) {
+ super(targetRepository, afterCommit, eventPublisher, applicationContext, actionRepository,
+ actionStatusRepository);
+ }
+
+ @Override
+ void sendAssignmentEvents(final List targets, final Set targetIdsCancellList,
+ final Map targetIdsToActions) {
+
+ targets.forEach(target -> {
+ target.setUpdateStatus(TargetUpdateStatus.IN_SYNC);
+ sendTargetUpdatedEvent(target);
+ });
+ }
+
+ @Override
+ public List findTargetsForAssignment(final List controllerIDs, final long setId) {
+ return Lists.partition(controllerIDs, Constants.MAX_ENTRIES_IN_STATEMENT).stream()
+ .map(ids -> targetRepository.findAll(SpecificationsBuilder.combineWithAnd(
+ Arrays.asList(TargetSpecifications.hasControllerIdAndAssignedDistributionSetIdNot(ids, setId),
+ TargetSpecifications.notEqualToTargetUpdateStatus(TargetUpdateStatus.PENDING)))))
+ .flatMap(List::stream).collect(Collectors.toList());
+ }
+
+ @Override
+ public Set findTargetIdsToCancel(final List> targetIds) {
+ return Collections.emptySet();
+ }
+
+ @Override
+ void updateTargetStatus(final JpaDistributionSet set, final List> targetIds, final String currentUser) {
+ targetIds.forEach(tIds -> targetRepository.setAssignedAndInstalledDistributionSetAndUpdateStatus(
+ TargetUpdateStatus.IN_SYNC, set, System.currentTimeMillis(), currentUser, tIds));
+ }
+
+ @Override
+ protected JpaAction createTargetAction(final Map targetsWithActionMap,
+ final JpaTarget target, final JpaDistributionSet set) {
+ final JpaAction result = super.createTargetAction(targetsWithActionMap, target, set);
+ result.setStatus(Status.FINISHED);
+ return result;
+ }
+
+ @Override
+ protected JpaActionStatus createActionStatus(final JpaAction action, final String actionMessage) {
+ final JpaActionStatus result = super.createActionStatus(action, actionMessage);
+ result.setStatus(Status.FINISHED);
+ result.addMessage(RepositoryConstants.SERVER_MESSAGE_PREFIX + "Action reported as offline deployment");
+ return result;
+ }
+
+}
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/OnlineDsAssignmentStrategy.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/OnlineDsAssignmentStrategy.java
new file mode 100644
index 000000000..3f6b0d3d0
--- /dev/null
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/OnlineDsAssignmentStrategy.java
@@ -0,0 +1,97 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.repository.jpa;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
+import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
+import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
+import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus;
+import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
+import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
+import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications;
+import org.eclipse.hawkbit.repository.model.Action.Status;
+import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
+import org.eclipse.hawkbit.repository.model.TargetWithActionType;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationEventPublisher;
+
+import com.google.common.collect.Lists;
+
+/**
+ * AbstractDsAssignmentStrategy for online assignments, i.e. managed by hawkBit.
+ *
+ */
+public class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
+
+ OnlineDsAssignmentStrategy(final TargetRepository targetRepository,
+ final AfterTransactionCommitExecutor afterCommit, final ApplicationEventPublisher eventPublisher,
+ final ApplicationContext applicationContext, final ActionRepository actionRepository,
+ final ActionStatusRepository actionStatusRepository) {
+ super(targetRepository, afterCommit, eventPublisher, applicationContext, actionRepository,
+ actionStatusRepository);
+ }
+
+ @Override
+ void sendAssignmentEvents(final List targets, final Set targetIdsCancellList,
+ final Map targetIdsToActions) {
+
+ targets.forEach(target -> {
+ target.setUpdateStatus(TargetUpdateStatus.PENDING);
+ sendTargetUpdatedEvent(target);
+ if (targetIdsCancellList.contains(target.getId())) {
+ return;
+ }
+
+ sendTargetAssignDistributionSetEvent(targetIdsToActions.get(target.getControllerId()));
+ });
+ }
+
+ @Override
+ List findTargetsForAssignment(final List controllerIDs, final long setId) {
+ return Lists.partition(controllerIDs, Constants.MAX_ENTRIES_IN_STATEMENT).stream()
+ .map(ids -> targetRepository
+ .findAll(TargetSpecifications.hasControllerIdAndAssignedDistributionSetIdNot(ids, setId)))
+ .flatMap(List::stream).collect(Collectors.toList());
+ }
+
+ @Override
+ Set findTargetIdsToCancel(final List> targetIds) {
+ return targetIds.stream().map(this::overrideObsoleteUpdateActions).flatMap(Collection::stream)
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ void updateTargetStatus(final JpaDistributionSet set, final List> targetIds, final String currentUser) {
+ targetIds.forEach(tIds -> targetRepository.setAssignedDistributionSetAndUpdateStatus(TargetUpdateStatus.PENDING,
+ set, System.currentTimeMillis(), currentUser, tIds));
+
+ }
+
+ @Override
+ JpaAction createTargetAction(final Map targetsWithActionMap, final JpaTarget target,
+ final JpaDistributionSet set) {
+ final JpaAction result = super.createTargetAction(targetsWithActionMap, target, set);
+ result.setStatus(Status.RUNNING);
+ return result;
+ }
+
+ @Override
+ JpaActionStatus createActionStatus(final JpaAction action, final String actionMessage) {
+ final JpaActionStatus result = super.createActionStatus(action, actionMessage);
+ result.setStatus(Status.RUNNING);
+ return result;
+ }
+
+}
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java
index cae7ac600..ecdb65b3d 100644
--- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java
@@ -50,6 +50,7 @@ import org.eclipse.hawkbit.repository.jpa.builder.JpaSoftwareModuleBuilder;
import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetFilterQueryBuilder;
import org.eclipse.hawkbit.repository.jpa.configuration.MultiTenantJpaTransactionManager;
import org.eclipse.hawkbit.repository.jpa.event.JpaEventEntityManager;
+import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
import org.eclipse.hawkbit.repository.jpa.model.helper.AfterTransactionCommitExecutorHolder;
import org.eclipse.hawkbit.repository.jpa.model.helper.EntityInterceptorHolder;
import org.eclipse.hawkbit.repository.jpa.model.helper.SecurityTokenGeneratorHolder;
@@ -87,6 +88,7 @@ import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.PropertySource;
+import org.springframework.data.domain.AuditorAware;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.integration.support.locks.LockRegistry;
@@ -496,8 +498,16 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
*/
@Bean
@ConditionalOnMissingBean
- DeploymentManagement deploymentManagement() {
- return new JpaDeploymentManagement();
+ DeploymentManagement deploymentManagement(final EntityManager entityManager,
+ final ActionRepository actionRepository, final DistributionSetRepository distributionSetRepository,
+ final TargetRepository targetRepository, final ActionStatusRepository actionStatusRepository,
+ final TargetManagement targetManagement, final AuditorAware auditorProvider,
+ final ApplicationEventPublisher eventPublisher, final ApplicationContext applicationContext,
+ final AfterTransactionCommitExecutor afterCommit, final VirtualPropertyReplacer virtualPropertyReplacer,
+ final PlatformTransactionManager txManager) {
+ return new JpaDeploymentManagement(entityManager, actionRepository, distributionSetRepository, targetRepository,
+ actionStatusRepository, targetManagement, auditorProvider, eventPublisher, applicationContext,
+ afterCommit, virtualPropertyReplacer, txManager);
}
/**
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetRepository.java
index 722daa4f8..c994e67bf 100644
--- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetRepository.java
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetRepository.java
@@ -58,6 +58,29 @@ public interface TargetRepository extends BaseEntityRepository,
@Param("set") JpaDistributionSet set, @Param("lastModifiedAt") Long modifiedAt,
@Param("lastModifiedBy") String modifiedBy, @Param("targets") Collection targets);
+ /**
+ * Sets {@link JpaTarget#getAssignedDistributionSet()},
+ * {@link JpaTarget#getInstalledDistributionSet()} and
+ * {@link JpaTarget#getInstallationDate()}
+ *
+ * @param set
+ * to use
+ * @param status
+ * to set
+ * @param modifiedAt
+ * current time
+ * @param modifiedBy
+ * current auditor
+ * @param targets
+ * to update
+ */
+ @Modifying
+ @Transactional
+ @Query("UPDATE JpaTarget t SET t.assignedDistributionSet = :set, t.installedDistributionSet = :set, t.installationDate = :lastModifiedAt, t.lastModifiedAt = :lastModifiedAt, t.lastModifiedBy = :lastModifiedBy, t.updateStatus = :status WHERE t.id IN :targets")
+ void setAssignedAndInstalledDistributionSetAndUpdateStatus(@Param("status") TargetUpdateStatus status,
+ @Param("set") JpaDistributionSet set, @Param("lastModifiedAt") Long modifiedAt,
+ @Param("lastModifiedBy") String modifiedBy, @Param("targets") Collection targets);
+
/**
* Loads {@link Target} by given ID.
*
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetSpecifications.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetSpecifications.java
index c67b89921..518e324db 100644
--- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetSpecifications.java
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetSpecifications.java
@@ -97,6 +97,19 @@ public final class TargetSpecifications {
return (targetRoot, query, cb) -> targetRoot.get(JpaTarget_.updateStatus).in(updateStatus);
}
+ /**
+ * {@link Specification} for retrieving {@link Target}s by "not equal to
+ * given {@link TargetUpdateStatus}".
+ *
+ * @param updateStatus
+ * to be filtered on
+ *
+ * @return the {@link Target} {@link Specification}
+ */
+ public static Specification notEqualToTargetUpdateStatus(final TargetUpdateStatus updateStatus) {
+ return (targetRoot, query, cb) -> cb.not(cb.equal(targetRoot.get(JpaTarget_.updateStatus), updateStatus));
+ }
+
/**
* {@link Specification} for retrieving {@link Target}s that are overdue. A
* target is overdue if it did not respond during the configured
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DeploymentManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DeploymentManagementTest.java
index 6fd8ff33b..ca798dec0 100644
--- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DeploymentManagementTest.java
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DeploymentManagementTest.java
@@ -428,6 +428,38 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
return action;
}
+ @Test
+ @Description("Simple offline deployment of a distribution set to a list of targets. Verifies that offline assigment "
+ + "is correctly executed for targets that do not have a running update already. Those are ignored.")
+ @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 20),
+ @Expect(type = TargetUpdatedEvent.class, count = 20), @Expect(type = ActionCreatedEvent.class, count = 20),
+ @Expect(type = DistributionSetCreatedEvent.class, count = 2),
+ @Expect(type = SoftwareModuleCreatedEvent.class, count = 6),
+ @Expect(type = TargetAssignDistributionSetEvent.class, count = 10) })
+ public void assignedDistributionSet() {
+ final List controllerIds = testdataFactory.createTargets(10).stream().map(Target::getControllerId)
+ .collect(Collectors.toList());
+ final List onlineAssignedTargets = testdataFactory.createTargets(10, "2");
+ controllerIds.addAll(onlineAssignedTargets.stream().map(Target::getControllerId).collect(Collectors.toList()));
+
+ final DistributionSet ds = testdataFactory.createDistributionSet();
+ assignDistributionSet(testdataFactory.createDistributionSet("2"), onlineAssignedTargets);
+
+ final long current = System.currentTimeMillis();
+ final List targets = deploymentManagement.offlineAssignedDistributionSet(ds.getId(), controllerIds)
+ .getAssignedEntity();
+ assertThat(actionRepository.count()).isEqualTo(20);
+
+ assertThat(targetManagement.findTargetByInstalledDistributionSet(ds.getId(), PAGE).getContent())
+ .containsAll(targets).hasSize(10)
+ .containsAll(targetManagement.findTargetByAssignedDistributionSet(ds.getId(), PAGE))
+ .as("InstallationDate set").allMatch(target -> target.getInstallationDate() >= current)
+ .as("TargetUpdateStatus IN_SYNC")
+ .allMatch(target -> TargetUpdateStatus.IN_SYNC.equals(target.getUpdateStatus()))
+ .as("InstallationDate equal to LastModifiedAt")
+ .allMatch(target -> target.getLastModifiedAt().equals(target.getInstallationDate()));
+ }
+
/**
* test a simple deployment by calling the
* {@link TargetRepository#assignDistributionSet(DistributionSet, Iterable)}