Feature offline deployments (#563)
* Repository support offline deployments. Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com> * Add offline assignment to Management API. Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com> * DsAssignmentStrategy introduced. Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com> * Fixed JavaDoc. Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com> * Readibility improved. Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>
This commit is contained in:
@@ -219,6 +219,9 @@ public interface MgmtDistributionSetRestApi {
|
|||||||
* @param targetIds
|
* @param targetIds
|
||||||
* the IDs of the target which should get assigned to the
|
* the IDs of the target which should get assigned to the
|
||||||
* distribution set given in the response body
|
* distribution set given in the response body
|
||||||
|
* @param offline
|
||||||
|
* to <code>true</code> if update was executed offline, i.e. not
|
||||||
|
* managed by hawkBit.
|
||||||
* @return status OK if the assignment of the targets was successful and a
|
* @return status OK if the assignment of the targets was successful and a
|
||||||
* complex return body which contains information about the assigned
|
* complex return body which contains information about the assigned
|
||||||
* targets and the already assigned targets counters
|
* 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,
|
MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }, produces = { MediaTypes.HAL_JSON_VALUE,
|
||||||
MediaType.APPLICATION_JSON_VALUE })
|
MediaType.APPLICATION_JSON_VALUE })
|
||||||
ResponseEntity<MgmtTargetAssignmentResponseBody> createAssignedTarget(
|
ResponseEntity<MgmtTargetAssignmentResponseBody> createAssignedTarget(
|
||||||
@PathVariable("distributionSetId") Long distributionSetId, List<MgmtTargetAssignmentRequestBody> targetIds);
|
@PathVariable("distributionSetId") Long distributionSetId,
|
||||||
|
final List<MgmtTargetAssignmentRequestBody> targetIds,
|
||||||
|
@RequestParam(value = "offline", required = false) boolean offline);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a paged list of meta data for a distribution set.
|
* Gets a paged list of meta data for a distribution set.
|
||||||
|
|||||||
@@ -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.MgmtActionRequestBodyPut;
|
||||||
import org.eclipse.hawkbit.mgmt.json.model.action.MgmtActionStatus;
|
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.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.MgmtDistributionSetAssigment;
|
||||||
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget;
|
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget;
|
||||||
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetAttributes;
|
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetAttributes;
|
||||||
@@ -245,6 +246,7 @@ public interface MgmtTargetRestApi {
|
|||||||
*
|
*
|
||||||
* @param controllerId
|
* @param controllerId
|
||||||
* the ID of the target to retrieve the assigned distribution
|
* the ID of the target to retrieve the assigned distribution
|
||||||
|
*
|
||||||
* @return the assigned distribution set with status OK, if none is assigned
|
* @return the assigned distribution set with status OK, if none is assigned
|
||||||
* than {@code null} content (e.g. "{}")
|
* than {@code null} content (e.g. "{}")
|
||||||
*/
|
*/
|
||||||
@@ -259,13 +261,20 @@ public interface MgmtTargetRestApi {
|
|||||||
* of the target to change
|
* of the target to change
|
||||||
* @param dsId
|
* @param dsId
|
||||||
* of the distributionset that is to be assigned
|
* of the distributionset that is to be assigned
|
||||||
* @return http status
|
* @param offline
|
||||||
|
* to <code>true</code> 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 = {
|
@RequestMapping(method = RequestMethod.POST, value = "/{controllerId}/assignedDS", consumes = {
|
||||||
MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }, produces = { MediaTypes.HAL_JSON_VALUE,
|
MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }, produces = { MediaTypes.HAL_JSON_VALUE,
|
||||||
MediaType.APPLICATION_JSON_VALUE })
|
MediaType.APPLICATION_JSON_VALUE })
|
||||||
ResponseEntity<Void> postAssignedDistributionSet(@PathVariable("controllerId") String controllerId,
|
ResponseEntity<MgmtTargetAssignmentResponseBody> postAssignedDistributionSet(
|
||||||
MgmtDistributionSetAssigment dsId);
|
@PathVariable("controllerId") String controllerId, MgmtDistributionSetAssigment dsId,
|
||||||
|
@RequestParam(value = "offline", required = false) boolean offline);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the GET request of retrieving the installed distribution set of
|
* Handles the GET request of retrieving the installed distribution set of
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
|
|||||||
import org.eclipse.hawkbit.repository.TargetManagement;
|
import org.eclipse.hawkbit.repository.TargetManagement;
|
||||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
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.DistributionSetMetadata;
|
||||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||||
import org.eclipse.hawkbit.repository.model.Target;
|
import org.eclipse.hawkbit.repository.model.Target;
|
||||||
@@ -229,16 +228,22 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
|
|||||||
@Override
|
@Override
|
||||||
public ResponseEntity<MgmtTargetAssignmentResponseBody> createAssignedTarget(
|
public ResponseEntity<MgmtTargetAssignmentResponseBody> createAssignedTarget(
|
||||||
@PathVariable("distributionSetId") final Long distributionSetId,
|
@PathVariable("distributionSetId") final Long distributionSetId,
|
||||||
@RequestBody final List<MgmtTargetAssignmentRequestBody> targetIds) {
|
@RequestBody final List<MgmtTargetAssignmentRequestBody> 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,
|
distributionSetId,
|
||||||
targetIds.stream()
|
assignments.stream()
|
||||||
.map(t -> new TargetWithActionType(t.getId(),
|
.map(assignment -> new TargetWithActionType(assignment.getId(),
|
||||||
MgmtRestModelMapper.convertActionType(t.getType()), t.getForcetime()))
|
MgmtRestModelMapper.convertActionType(assignment.getType()), assignment.getForcetime()))
|
||||||
.collect(Collectors.toList()));
|
.collect(Collectors.toList()))));
|
||||||
|
|
||||||
return ResponseEntity.ok(MgmtDistributionSetMapper.toResponse(assignDistributionSet));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -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.action.MgmtActionStatus;
|
||||||
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType;
|
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.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.MgmtDistributionSetAssigment;
|
||||||
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget;
|
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget;
|
||||||
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetAttributes;
|
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetAttributes;
|
||||||
@@ -253,16 +254,21 @@ public class MgmtTargetResource implements MgmtTargetRestApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ResponseEntity<Void> postAssignedDistributionSet(@PathVariable("controllerId") final String controllerId,
|
public ResponseEntity<MgmtTargetAssignmentResponseBody> postAssignedDistributionSet(
|
||||||
@RequestBody final MgmtDistributionSetAssigment dsId) {
|
@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);
|
findTargetWithExceptionIfNotFound(controllerId);
|
||||||
final ActionType type = (dsId.getType() != null) ? MgmtRestModelMapper.convertActionType(dsId.getType())
|
final ActionType type = (dsId.getType() != null) ? MgmtRestModelMapper.convertActionType(dsId.getType())
|
||||||
: ActionType.FORCED;
|
: ActionType.FORCED;
|
||||||
this.deploymentManagement.assignDistributionSet(dsId.getId(), type, dsId.getForcetime(),
|
return ResponseEntity.ok(MgmtDistributionSetMapper.toResponse(deploymentManagement
|
||||||
Arrays.asList(controllerId));
|
.assignDistributionSet(dsId.getId(), type, dsId.getForcetime(), Arrays.asList(controllerId))));
|
||||||
|
|
||||||
return ResponseEntity.ok().build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -205,30 +205,49 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
|
|||||||
@Test
|
@Test
|
||||||
@Description("Ensures that multi target assignment through API is reflected by the repository.")
|
@Description("Ensures that multi target assignment through API is reflected by the repository.")
|
||||||
public void assignMultipleTargetsToDistributionSet() throws Exception {
|
public void assignMultipleTargetsToDistributionSet() throws Exception {
|
||||||
// prepare distribution set
|
final DistributionSet createdDs = testdataFactory.createDistributionSet();
|
||||||
final Set<DistributionSet> createDistributionSetsAlphabetical = createDistributionSetsAlphabetical(1);
|
final List<Target> targets = testdataFactory.createTargets(5);
|
||||||
final DistributionSet createdDs = createDistributionSetsAlphabetical.iterator().next();
|
|
||||||
// prepare targets
|
|
||||||
final String[] knownTargetIds = new String[] { "1", "2", "3", "4", "5" };
|
|
||||||
final JSONArray list = new JSONArray();
|
final JSONArray list = new JSONArray();
|
||||||
for (final String targetId : knownTargetIds) {
|
targets.forEach(target -> list.put(new JSONObject().put("id", target.getControllerId())));
|
||||||
testdataFactory.createTarget(targetId);
|
|
||||||
list.put(new JSONObject().put("id", Long.valueOf(targetId)));
|
|
||||||
}
|
|
||||||
// assign already one target to DS
|
// assign already one target to DS
|
||||||
assignDistributionSet(createdDs.getId(), knownTargetIds[0]);
|
assignDistributionSet(createdDs.getId(), targets.get(0).getControllerId());
|
||||||
|
|
||||||
mvc.perform(post(
|
mvc.perform(post(
|
||||||
MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + createdDs.getId() + "/assignedTargets")
|
MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + createdDs.getId() + "/assignedTargets")
|
||||||
.contentType(MediaType.APPLICATION_JSON).content(list.toString()))
|
.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("$.alreadyAssigned", equalTo(1)))
|
||||||
.andExpect(jsonPath("$.total", equalTo(knownTargetIds.length)));
|
.andExpect(jsonPath("$.total", equalTo(targets.size())));
|
||||||
|
|
||||||
assertThat(targetManagement.findTargetByAssignedDistributionSet(createdDs.getId(), PAGE).getContent())
|
assertThat(targetManagement.findTargetByAssignedDistributionSet(createdDs.getId(), PAGE).getContent())
|
||||||
.as("Five targets in repository have DS assigned").hasSize(5);
|
.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<Target> 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
|
@Test
|
||||||
@Description("Ensures that assigned targets of DS are returned as reflected by the repository.")
|
@Description("Ensures that assigned targets of DS are returned as reflected by the repository.")
|
||||||
public void getAssignedTargetsOfDistributionSet() throws Exception {
|
public void getAssignedTargetsOfDistributionSet() throws Exception {
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ import org.eclipse.hawkbit.repository.model.ActionStatus;
|
|||||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||||
import org.eclipse.hawkbit.repository.model.Target;
|
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.repository.test.util.WithUser;
|
||||||
import org.eclipse.hawkbit.rest.exception.MessageNotReadableException;
|
import org.eclipse.hawkbit.rest.exception.MessageNotReadableException;
|
||||||
import org.eclipse.hawkbit.rest.json.model.ExceptionInfo;
|
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")
|
mvc.perform(post(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + target.getControllerId() + "/assignedDS")
|
||||||
.content("{\"id\":" + set.getId() + "}").contentType(MediaType.APPLICATION_JSON))
|
.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);
|
assertThat(deploymentManagement.getAssignedDistributionSet(target.getControllerId()).get()).isEqualTo(set);
|
||||||
target = targetManagement.findTargetByControllerID(target.getControllerId()).get();
|
target = targetManagement.findTargetByControllerID(target.getControllerId()).get();
|
||||||
@@ -1153,7 +1156,41 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest
|
|||||||
// repeating DS assignment leads again to OK
|
// repeating DS assignment leads again to OK
|
||||||
mvc.perform(post(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + target.getControllerId() + "/assignedDS")
|
mvc.perform(post(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + target.getControllerId() + "/assignedDS")
|
||||||
.content("{\"id\":" + set.getId() + "}").contentType(MediaType.APPLICATION_JSON))
|
.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
|
// ...but does not change the target
|
||||||
assertThat(targetManagement.findTargetByControllerID(target.getControllerId()).get()).isEqualTo(target);
|
assertThat(targetManagement.findTargetByControllerID(target.getControllerId()).get()).isEqualTo(target);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import java.util.Optional;
|
|||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
|
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.CancelActionNotAllowedException;
|
||||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||||
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
|
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.DistributionSetType;
|
||||||
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
|
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
|
||||||
import org.eclipse.hawkbit.repository.model.Target;
|
import org.eclipse.hawkbit.repository.model.Target;
|
||||||
|
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
|
||||||
import org.eclipse.hawkbit.repository.model.TargetWithActionType;
|
import org.eclipse.hawkbit.repository.model.TargetWithActionType;
|
||||||
import org.hibernate.validator.constraints.NotEmpty;
|
import org.hibernate.validator.constraints.NotEmpty;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
@@ -69,7 +71,6 @@ public interface DeploymentManagement {
|
|||||||
DistributionSetAssignmentResult assignDistributionSet(@NotNull Long dsID, @NotNull ActionType actionType,
|
DistributionSetAssignmentResult assignDistributionSet(@NotNull Long dsID, @NotNull ActionType actionType,
|
||||||
long forcedTimestamp, @NotEmpty Collection<String> controllerIDs);
|
long forcedTimestamp, @NotEmpty Collection<String> controllerIDs);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* method assigns the {@link DistributionSet} to all {@link Target}s by
|
* method assigns the {@link DistributionSet} to all {@link Target}s by
|
||||||
* their IDs with a specific {@link ActionType} and {@code forcetime}.
|
* their IDs with a specific {@link ActionType} and {@code forcetime}.
|
||||||
@@ -116,6 +117,38 @@ public interface DeploymentManagement {
|
|||||||
DistributionSetAssignmentResult assignDistributionSet(@NotNull Long dsID,
|
DistributionSetAssignmentResult assignDistributionSet(@NotNull Long dsID,
|
||||||
@NotEmpty Collection<TargetWithActionType> targets, String actionMessage);
|
@NotEmpty Collection<TargetWithActionType> 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:<br/>
|
||||||
|
*
|
||||||
|
* <ol type="A">
|
||||||
|
* <li>it ignores targets completely that are in
|
||||||
|
* {@link TargetUpdateStatus#PENDING}.</li>
|
||||||
|
* <li>it creates completed actions.</li>
|
||||||
|
* <li>sets both installed and assigned DS on the target and switches the
|
||||||
|
* status to {@link TargetUpdateStatus#IN_SYNC}.</li>
|
||||||
|
* <li>does not send a {@link TargetAssignDistributionSetEvent}.</li>
|
||||||
|
* </ol>
|
||||||
|
*
|
||||||
|
* @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<String> controllerIDs);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cancels given {@link Action} for given {@link Target}. The method will
|
* Cancels given {@link Action} for given {@link Target}. The method will
|
||||||
* immediately add a {@link Status#CANCELED} status to the action. However,
|
* immediately add a {@link Status#CANCELED} status to the action. However,
|
||||||
|
|||||||
@@ -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<JpaTarget> findTargetsForAssignment(final List<String> 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<List<Long>> targetIds,
|
||||||
|
final String currentUser);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels actions that can be canceled (i.e.
|
||||||
|
* {@link DistributionSet#isRequiredMigrationStep() is <code>false</code>})
|
||||||
|
* 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<Long> findTargetIdsToCancel(List<List<Long>> 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<JpaTarget> targets, final Set<Long> targetIdsCancelList,
|
||||||
|
final Map<String, JpaAction> 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<Long> overrideObsoleteUpdateActions(final Collection<Long> targetsIds) {
|
||||||
|
|
||||||
|
// Figure out if there are potential target/action combinations that
|
||||||
|
// need to be considered for cancellation
|
||||||
|
final List<JpaAction> 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<String, TargetWithActionType> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,8 +31,6 @@ import org.eclipse.hawkbit.repository.DeploymentManagement;
|
|||||||
import org.eclipse.hawkbit.repository.RepositoryConstants;
|
import org.eclipse.hawkbit.repository.RepositoryConstants;
|
||||||
import org.eclipse.hawkbit.repository.TargetManagement;
|
import org.eclipse.hawkbit.repository.TargetManagement;
|
||||||
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
|
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.CancelActionNotAllowedException;
|
||||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||||
import org.eclipse.hawkbit.repository.exception.ForceQuitActionNotAllowedException;
|
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.model.JpaTarget_;
|
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
|
||||||
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
|
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;
|
||||||
import org.eclipse.hawkbit.repository.model.Action.ActionType;
|
import org.eclipse.hawkbit.repository.model.Action.ActionType;
|
||||||
import org.eclipse.hawkbit.repository.model.Action.Status;
|
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.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.ApplicationEventPublisher;
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
import org.springframework.dao.ConcurrencyFailureException;
|
import org.springframework.dao.ConcurrencyFailureException;
|
||||||
@@ -99,51 +95,70 @@ public class JpaDeploymentManagement implements DeploymentManagement {
|
|||||||
*/
|
*/
|
||||||
private static final int ACTION_PAGE_LIMIT = 1000;
|
private static final int ACTION_PAGE_LIMIT = 1000;
|
||||||
|
|
||||||
@Autowired
|
private final EntityManager entityManager;
|
||||||
private 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<String> 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
|
JpaDeploymentManagement(final EntityManager entityManager, final ActionRepository actionRepository,
|
||||||
private ActionRepository actionRepository;
|
final DistributionSetRepository distributionSetRepository, final TargetRepository targetRepository,
|
||||||
|
final ActionStatusRepository actionStatusRepository, final TargetManagement targetManagement,
|
||||||
|
final AuditorAware<String> 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
|
@Override
|
||||||
private DistributionSetRepository distributionSetRepository;
|
@Transactional(isolation = Isolation.READ_COMMITTED)
|
||||||
|
@Retryable(include = {
|
||||||
@Autowired
|
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
|
||||||
private TargetRepository targetRepository;
|
public DistributionSetAssignmentResult offlineAssignedDistributionSet(final Long dsID,
|
||||||
|
final Collection<String> controllerIDs) {
|
||||||
@Autowired
|
return assignDistributionSetToTargets(dsID,
|
||||||
private ActionStatusRepository actionStatusRepository;
|
controllerIDs.stream()
|
||||||
|
.map(controllerId -> new TargetWithActionType(controllerId, ActionType.FORCED, -1))
|
||||||
@Autowired
|
.collect(Collectors.toList()),
|
||||||
private TargetManagement targetManagement;
|
null, offlineDsAssignmentStrategy);
|
||||||
|
}
|
||||||
@Autowired
|
|
||||||
private AuditorAware<String> auditorProvider;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private ApplicationEventPublisher eventPublisher;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private ApplicationContext applicationContext;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private AfterTransactionCommitExecutor afterCommit;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private VirtualPropertyReplacer virtualPropertyReplacer;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private PlatformTransactionManager txManager;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(isolation = Isolation.READ_COMMITTED)
|
@Transactional(isolation = Isolation.READ_COMMITTED)
|
||||||
@Retryable(include = {
|
@Retryable(include = {
|
||||||
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
|
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
|
||||||
public DistributionSetAssignmentResult assignDistributionSet(final Long dsID, final ActionType actionType,
|
public DistributionSetAssignmentResult assignDistributionSet(final Long dsID, final ActionType actionType,
|
||||||
final long forcedTimestamp, final Collection<String> targetIDs) {
|
final long forcedTimestamp, final Collection<String> controllerIDs) {
|
||||||
|
|
||||||
return assignDistributionSetToTargets(dsID, targetIDs.stream()
|
return assignDistributionSetToTargets(dsID,
|
||||||
.map(t -> new TargetWithActionType(t, actionType, forcedTimestamp)).collect(Collectors.toList()), null);
|
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,
|
public DistributionSetAssignmentResult assignDistributionSet(final Long dsID,
|
||||||
final Collection<TargetWithActionType> targets) {
|
final Collection<TargetWithActionType> targets) {
|
||||||
|
|
||||||
return assignDistributionSetToTargets(dsID, targets, null);
|
return assignDistributionSetToTargets(dsID, targets, null, onlineDsAssignmentStrategy);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -164,12 +179,22 @@ public class JpaDeploymentManagement implements DeploymentManagement {
|
|||||||
public DistributionSetAssignmentResult assignDistributionSet(final Long dsID,
|
public DistributionSetAssignmentResult assignDistributionSet(final Long dsID,
|
||||||
final Collection<TargetWithActionType> targets, final String actionMessage) {
|
final Collection<TargetWithActionType> 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
|
* method assigns the {@link DistributionSet} to all {@link Target}s by
|
||||||
* their IDs with a specific {@link ActionType} and {@code forcetime}.
|
* 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:<br/>
|
||||||
|
* A. it ignores targets completely that are in
|
||||||
|
* {@link TargetUpdateStatus#PENDING}.<br/>
|
||||||
|
* B. it created completed actions.<br/>
|
||||||
|
* C. sets both installed and assigned DS on the target and switches the
|
||||||
|
* status to {@link TargetUpdateStatus#IN_SYNC} <br/>
|
||||||
|
* D. does not send a {@link TargetAssignDistributionSetEvent}.<br/>
|
||||||
*
|
*
|
||||||
* @param dsID
|
* @param dsID
|
||||||
* the ID of the distribution set to assign
|
* 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
|
* a list of all targets and their action type
|
||||||
* @param actionMessage
|
* @param actionMessage
|
||||||
* an optional message to be written into the action status
|
* an optional message to be written into the action status
|
||||||
|
* @param offline
|
||||||
|
* to <code>true</code> in offline case
|
||||||
* @return the assignment result
|
* @return the assignment result
|
||||||
*
|
*
|
||||||
* @throw IncompleteDistributionSetException if mandatory
|
* @throw IncompleteDistributionSetException if mandatory
|
||||||
@@ -184,7 +211,8 @@ public class JpaDeploymentManagement implements DeploymentManagement {
|
|||||||
* {@link DistributionSetType}.
|
* {@link DistributionSetType}.
|
||||||
*/
|
*/
|
||||||
private DistributionSetAssignmentResult assignDistributionSetToTargets(final Long dsID,
|
private DistributionSetAssignmentResult assignDistributionSetToTargets(final Long dsID,
|
||||||
final Collection<TargetWithActionType> targetsWithActionType, final String actionMessage) {
|
final Collection<TargetWithActionType> targetsWithActionType, final String actionMessage,
|
||||||
|
final AbstractDsAssignmentStrategy assignmentStrategy) {
|
||||||
|
|
||||||
final JpaDistributionSet set = distributionSetRepository.findOne(dsID);
|
final JpaDistributionSet set = distributionSetRepository.findOne(dsID);
|
||||||
if (set == null) {
|
if (set == null) {
|
||||||
@@ -209,10 +237,7 @@ public class JpaDeploymentManagement implements DeploymentManagement {
|
|||||||
// maximum 1000 elements, so we need to split the entries here and
|
// maximum 1000 elements, so we need to split the entries here and
|
||||||
// execute multiple statements we take the target only into account if
|
// execute multiple statements we take the target only into account if
|
||||||
// the requested operation is no duplicate of a previous one
|
// the requested operation is no duplicate of a previous one
|
||||||
final List<JpaTarget> targets = Lists.partition(controllerIDs, Constants.MAX_ENTRIES_IN_STATEMENT).stream()
|
final List<JpaTarget> targets = assignmentStrategy.findTargetsForAssignment(controllerIDs, set.getId());
|
||||||
.map(ids -> targetRepository
|
|
||||||
.findAll(TargetSpecifications.hasControllerIdAndAssignedDistributionSetIdNot(ids, set.getId())))
|
|
||||||
.flatMap(List::stream).collect(Collectors.toList());
|
|
||||||
|
|
||||||
if (targets.isEmpty()) {
|
if (targets.isEmpty()) {
|
||||||
// detaching as it is not necessary to persist the set itself
|
// 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
|
// 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
|
// because for targets which we have changed to canceling we don't want
|
||||||
// to publish the new action update event.
|
// to publish the new action update event.
|
||||||
final Set<Long> targetIdsCancellList = targetIds.stream().map(this::overrideObsoleteUpdateActions)
|
final Set<Long> targetIdsCancellList = assignmentStrategy.findTargetIdsToCancel(targetIds);
|
||||||
.flatMap(Collection::stream).collect(Collectors.toSet());
|
|
||||||
|
|
||||||
// cancel all scheduled actions which are in-active, these actions were
|
// cancel all scheduled actions which are in-active, these actions were
|
||||||
// not active before and the manual assignment which has been done
|
// not active before and the manual assignment which has been done
|
||||||
@@ -245,24 +269,26 @@ public class JpaDeploymentManagement implements DeploymentManagement {
|
|||||||
currentUser = null;
|
currentUser = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
targetIds.forEach(tIds -> targetRepository.setAssignedDistributionSetAndUpdateStatus(TargetUpdateStatus.PENDING,
|
assignmentStrategy.updateTargetStatus(set, targetIds, currentUser);
|
||||||
set, System.currentTimeMillis(), currentUser, tIds));
|
|
||||||
final Map<String, JpaAction> targetIdsToActions = targets.stream()
|
final Map<String, JpaAction> 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()));
|
.collect(Collectors.toMap(a -> a.getTarget().getControllerId(), Function.identity()));
|
||||||
|
|
||||||
// create initial action status when action is created so we remember
|
// create initial action status when action is created so we remember
|
||||||
// the initial running status because we will change the status
|
// the initial running status because we will change the status
|
||||||
// of the action itself and with this action status we have a nicer
|
// of the action itself and with this action status we have a nicer
|
||||||
// action history.
|
// 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
|
// detaching as it is not necessary to persist the set itself
|
||||||
entityManager.detach(set);
|
entityManager.detach(set);
|
||||||
// detaching as the entity has been updated by the JPQL query above
|
// detaching as the entity has been updated by the JPQL query above
|
||||||
targets.forEach(entityManager::detach);
|
targets.forEach(entityManager::detach);
|
||||||
|
|
||||||
sendAssignmentEvents(targets, targetIdsCancellList, targetIdsToActions);
|
assignmentStrategy.sendAssignmentEvents(targets, targetIdsCancellList, targetIdsToActions);
|
||||||
|
|
||||||
return new DistributionSetAssignmentResult(
|
return new DistributionSetAssignmentResult(
|
||||||
targets.stream().map(Target::getControllerId).collect(Collectors.toList()), targets.size(),
|
targets.stream().map(Target::getControllerId).collect(Collectors.toList()), targets.size(),
|
||||||
@@ -270,77 +296,6 @@ public class JpaDeploymentManagement implements DeploymentManagement {
|
|||||||
targetManagement);
|
targetManagement);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendAssignmentEvents(final List<JpaTarget> targets, final Set<Long> targetIdsCancellList,
|
|
||||||
final Map<String, JpaAction> targetIdsToActions) {
|
|
||||||
|
|
||||||
targets.forEach(target -> {
|
|
||||||
sendTargetUpdatedEvent(target);
|
|
||||||
if (targetIdsCancellList.contains(target.getId())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sendTargetAssignDistributionSetEvent(targetIdsToActions.get(target.getControllerId()));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static JpaAction createTargetAction(final Map<String, TargetWithActionType> 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<Long> overrideObsoleteUpdateActions(final Collection<Long> targetsIds) {
|
|
||||||
|
|
||||||
// Figure out if there are potential target/action combinations that
|
|
||||||
// need to be considered for cancellation
|
|
||||||
final List<JpaAction> 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
|
@Override
|
||||||
@Transactional(isolation = Isolation.READ_COMMITTED)
|
@Transactional(isolation = Isolation.READ_COMMITTED)
|
||||||
@Retryable(include = {
|
@Retryable(include = {
|
||||||
@@ -363,7 +318,7 @@ public class JpaDeploymentManagement implements DeploymentManagement {
|
|||||||
actionStatusRepository.save(new JpaActionStatus(action, Status.CANCELING, System.currentTimeMillis(),
|
actionStatusRepository.save(new JpaActionStatus(action, Status.CANCELING, System.currentTimeMillis(),
|
||||||
RepositoryConstants.SERVER_MESSAGE_PREFIX + "manual cancelation requested"));
|
RepositoryConstants.SERVER_MESSAGE_PREFIX + "manual cancelation requested"));
|
||||||
final Action saveAction = actionRepository.save(action);
|
final Action saveAction = actionRepository.save(action);
|
||||||
cancelAssignDistributionSetEvent(action.getTarget(), action.getId());
|
onlineDsAssignmentStrategy.cancelAssignDistributionSetEvent(action.getTarget(), action.getId());
|
||||||
|
|
||||||
return saveAction;
|
return saveAction;
|
||||||
} else {
|
} 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
|
@Override
|
||||||
@Transactional(isolation = Isolation.READ_COMMITTED)
|
@Transactional(isolation = Isolation.READ_COMMITTED)
|
||||||
@Retryable(include = {
|
@Retryable(include = {
|
||||||
@@ -471,14 +412,14 @@ public class JpaDeploymentManagement implements DeploymentManagement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check if we need to override running update actions
|
// check if we need to override running update actions
|
||||||
final List<Long> overrideObsoleteUpdateActions = overrideObsoleteUpdateActions(
|
final List<Long> overrideObsoleteUpdateActions = onlineDsAssignmentStrategy
|
||||||
Collections.singletonList(action.getTarget().getId()));
|
.overrideObsoleteUpdateActions(Collections.singletonList(action.getTarget().getId()));
|
||||||
|
|
||||||
action.setActive(true);
|
action.setActive(true);
|
||||||
action.setStatus(Status.RUNNING);
|
action.setStatus(Status.RUNNING);
|
||||||
final JpaAction savedAction = actionRepository.save(action);
|
final JpaAction savedAction = actionRepository.save(action);
|
||||||
|
|
||||||
setRunningActionStatus(savedAction, null);
|
actionStatusRepository.save(onlineDsAssignmentStrategy.createActionStatus(savedAction, null));
|
||||||
|
|
||||||
target = (JpaTarget) entityManager.merge(savedAction.getTarget());
|
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) {
|
private void setSkipActionStatus(final JpaAction action) {
|
||||||
final JpaActionStatus actionStatus = new JpaActionStatus();
|
final JpaActionStatus actionStatus = new JpaActionStatus();
|
||||||
actionStatus.setAction(action);
|
actionStatus.setAction(action);
|
||||||
@@ -683,4 +612,5 @@ public class JpaDeploymentManagement implements DeploymentManagement {
|
|||||||
|
|
||||||
return distributionSetRepository.findInstalledAtTarget(controllerId);
|
return distributionSetRepository.findInstalledAtTarget(controllerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<JpaTarget> targets, final Set<Long> targetIdsCancellList,
|
||||||
|
final Map<String, JpaAction> targetIdsToActions) {
|
||||||
|
|
||||||
|
targets.forEach(target -> {
|
||||||
|
target.setUpdateStatus(TargetUpdateStatus.IN_SYNC);
|
||||||
|
sendTargetUpdatedEvent(target);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<JpaTarget> findTargetsForAssignment(final List<String> 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<Long> findTargetIdsToCancel(final List<List<Long>> targetIds) {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void updateTargetStatus(final JpaDistributionSet set, final List<List<Long>> targetIds, final String currentUser) {
|
||||||
|
targetIds.forEach(tIds -> targetRepository.setAssignedAndInstalledDistributionSetAndUpdateStatus(
|
||||||
|
TargetUpdateStatus.IN_SYNC, set, System.currentTimeMillis(), currentUser, tIds));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected JpaAction createTargetAction(final Map<String, TargetWithActionType> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<JpaTarget> targets, final Set<Long> targetIdsCancellList,
|
||||||
|
final Map<String, JpaAction> targetIdsToActions) {
|
||||||
|
|
||||||
|
targets.forEach(target -> {
|
||||||
|
target.setUpdateStatus(TargetUpdateStatus.PENDING);
|
||||||
|
sendTargetUpdatedEvent(target);
|
||||||
|
if (targetIdsCancellList.contains(target.getId())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendTargetAssignDistributionSetEvent(targetIdsToActions.get(target.getControllerId()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
List<JpaTarget> findTargetsForAssignment(final List<String> 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<Long> findTargetIdsToCancel(final List<List<Long>> targetIds) {
|
||||||
|
return targetIds.stream().map(this::overrideObsoleteUpdateActions).flatMap(Collection::stream)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void updateTargetStatus(final JpaDistributionSet set, final List<List<Long>> targetIds, final String currentUser) {
|
||||||
|
targetIds.forEach(tIds -> targetRepository.setAssignedDistributionSetAndUpdateStatus(TargetUpdateStatus.PENDING,
|
||||||
|
set, System.currentTimeMillis(), currentUser, tIds));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
JpaAction createTargetAction(final Map<String, TargetWithActionType> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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.builder.JpaTargetFilterQueryBuilder;
|
||||||
import org.eclipse.hawkbit.repository.jpa.configuration.MultiTenantJpaTransactionManager;
|
import org.eclipse.hawkbit.repository.jpa.configuration.MultiTenantJpaTransactionManager;
|
||||||
import org.eclipse.hawkbit.repository.jpa.event.JpaEventEntityManager;
|
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.AfterTransactionCommitExecutorHolder;
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.helper.EntityInterceptorHolder;
|
import org.eclipse.hawkbit.repository.jpa.model.helper.EntityInterceptorHolder;
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.helper.SecurityTokenGeneratorHolder;
|
import org.eclipse.hawkbit.repository.jpa.model.helper.SecurityTokenGeneratorHolder;
|
||||||
@@ -87,6 +88,7 @@ import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
|||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
import org.springframework.context.annotation.Profile;
|
import org.springframework.context.annotation.Profile;
|
||||||
import org.springframework.context.annotation.PropertySource;
|
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.EnableJpaAuditing;
|
||||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||||
import org.springframework.integration.support.locks.LockRegistry;
|
import org.springframework.integration.support.locks.LockRegistry;
|
||||||
@@ -496,8 +498,16 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
|
|||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
DeploymentManagement deploymentManagement() {
|
DeploymentManagement deploymentManagement(final EntityManager entityManager,
|
||||||
return new JpaDeploymentManagement();
|
final ActionRepository actionRepository, final DistributionSetRepository distributionSetRepository,
|
||||||
|
final TargetRepository targetRepository, final ActionStatusRepository actionStatusRepository,
|
||||||
|
final TargetManagement targetManagement, final AuditorAware<String> 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -58,6 +58,29 @@ public interface TargetRepository extends BaseEntityRepository<JpaTarget, Long>,
|
|||||||
@Param("set") JpaDistributionSet set, @Param("lastModifiedAt") Long modifiedAt,
|
@Param("set") JpaDistributionSet set, @Param("lastModifiedAt") Long modifiedAt,
|
||||||
@Param("lastModifiedBy") String modifiedBy, @Param("targets") Collection<Long> targets);
|
@Param("lastModifiedBy") String modifiedBy, @Param("targets") Collection<Long> 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<Long> targets);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads {@link Target} by given ID.
|
* Loads {@link Target} by given ID.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -97,6 +97,19 @@ public final class TargetSpecifications {
|
|||||||
return (targetRoot, query, cb) -> targetRoot.get(JpaTarget_.updateStatus).in(updateStatus);
|
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<JpaTarget> 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
|
* {@link Specification} for retrieving {@link Target}s that are overdue. A
|
||||||
* target is overdue if it did not respond during the configured
|
* target is overdue if it did not respond during the configured
|
||||||
|
|||||||
@@ -428,6 +428,38 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
return action;
|
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<String> controllerIds = testdataFactory.createTargets(10).stream().map(Target::getControllerId)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
final List<Target> 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<Target> 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
|
* test a simple deployment by calling the
|
||||||
* {@link TargetRepository#assignDistributionSet(DistributionSet, Iterable)}
|
* {@link TargetRepository#assignDistributionSet(DistributionSet, Iterable)}
|
||||||
|
|||||||
Reference in New Issue
Block a user