Refactor action repository (#2118)

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2024-12-05 11:41:41 +02:00
committed by GitHub
parent 1d52d3b102
commit 39861e7790
21 changed files with 318 additions and 393 deletions

View File

@@ -144,9 +144,7 @@ public class DdiRootController implements DdiRootControllerRestApi {
} }
@Override @Override
public ResponseEntity<DdiControllerBase> getControllerBase( public ResponseEntity<DdiControllerBase> getControllerBase(final String tenant, final String controllerId) {
final String tenant,
final String controllerId) {
log.debug("getControllerBase({})", controllerId); log.debug("getControllerBase({})", controllerId);
final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotExist(controllerId, IpUtil final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotExist(controllerId, IpUtil

View File

@@ -81,9 +81,8 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
/** /**
* {@link AmqpMessageDispatcherService} create all outgoing AMQP messages and * {@link AmqpMessageDispatcherService} create all outgoing AMQP messages and delegate the messages to a {@link AmqpMessageSenderService}.
* delegate the messages to a {@link AmqpMessageSenderService}. * <p/>
*
* Additionally, the dispatcher listener/subscribe for some target events e.g. assignment. * Additionally, the dispatcher listener/subscribe for some target events e.g. assignment.
*/ */
@Slf4j @Slf4j
@@ -173,7 +172,7 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
} }
log.debug("MultiActionEvent received for {}", multiActionEvent.getControllerIds()); log.debug("MultiActionEvent received for {}", multiActionEvent.getControllerIds());
sendMultiActionRequestMessages(multiActionEvent.getTenant(), multiActionEvent.getControllerIds()); sendMultiActionRequestMessages(multiActionEvent.getControllerIds());
} }
protected void sendUpdateMessageToTarget( protected void sendUpdateMessageToTarget(
@@ -184,27 +183,6 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
sendUpdateMessageToTargets(actionProp, Collections.singletonList(target), softwareModules); sendUpdateMessageToTargets(actionProp, Collections.singletonList(target), softwareModules);
} }
protected void sendMultiActionRequestToTarget(
final String tenant, final Target target, final List<Action> actions,
final Function<Action, Map<SoftwareModule, List<SoftwareModuleMetadata>>> getSoftwareModuleMetaData) {
final URI targetAddress = target.getAddress();
if (!IpUtil.isAmqpUri(targetAddress) || CollectionUtils.isEmpty(actions)) {
return;
}
final DmfMultiActionRequest multiActionRequest = new DmfMultiActionRequest();
actions.forEach(action -> {
final DmfActionRequest actionRequest = createDmfActionRequest(target, action, getSoftwareModuleMetaData.apply(action));
final int weight = deploymentManagement.getWeightConsideringDefault(action);
multiActionRequest.addElement(getEventTypeForAction(action), actionRequest, weight);
});
final Message message = getMessageConverter().toMessage(
multiActionRequest,
createConnectorMessagePropertiesEvent(tenant, target.getControllerId(), EventTopic.MULTI_ACTION));
amqpSenderService.sendMessage(message, targetAddress);
}
protected DmfDownloadAndUpdateRequest createDownloadAndUpdateRequest( protected DmfDownloadAndUpdateRequest createDownloadAndUpdateRequest(
final Target target, final Long actionId, final Target target, final Long actionId,
final Map<SoftwareModule, List<SoftwareModuleMetadata>> softwareModules) { final Map<SoftwareModule, List<SoftwareModuleMetadata>> softwareModules) {
@@ -303,14 +281,6 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
return dmfTarget; return dmfTarget;
} }
/**
* Creates a Confirmation request.
*
* @param target the target
* @param actionId the actionId
* @param softwareModules the software modules
* @return confirm request
*/
protected DmfConfirmRequest createConfirmRequest( protected DmfConfirmRequest createConfirmRequest(
final Target target, final Long actionId, final Map<SoftwareModule, List<SoftwareModuleMetadata>> softwareModules) { final Target target, final Long actionId, final Map<SoftwareModule, List<SoftwareModuleMetadata>> softwareModules) {
final DmfConfirmRequest request = new DmfConfirmRequest(); final DmfConfirmRequest request = new DmfConfirmRequest();
@@ -325,6 +295,33 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
return request; return request;
} }
void sendMultiActionRequestToTarget(
final Target target, final List<Action> actions,
final Function<SoftwareModule, List<SoftwareModuleMetadata>> getSoftwareModuleMetaData) {
final URI targetAddress = target.getAddress();
if (!IpUtil.isAmqpUri(targetAddress) || CollectionUtils.isEmpty(actions)) {
return;
}
final DmfMultiActionRequest multiActionRequest = new DmfMultiActionRequest();
actions.forEach(action -> {
final DmfActionRequest actionRequest = createDmfActionRequest(
target, action,
action.getDistributionSet().getModules().stream()
.collect(Collectors.toMap(Function.identity(), module -> {
final List<SoftwareModuleMetadata> softwareModuleMetadata = getSoftwareModuleMetaData.apply(module);
return softwareModuleMetadata == null ? Collections.emptyList() : softwareModuleMetadata;
})));
final int weight = deploymentManagement.getWeightConsideringDefault(action);
multiActionRequest.addElement(getEventTypeForAction(action), actionRequest, weight);
});
final Message message = getMessageConverter().toMessage(
multiActionRequest,
createConnectorMessagePropertiesEvent(target.getTenant(), target.getControllerId(), EventTopic.MULTI_ACTION));
amqpSenderService.sendMessage(message, targetAddress);
}
private static DmfActionRequest createPlainActionRequest(final Action action) { private static DmfActionRequest createPlainActionRequest(final Action action) {
final DmfActionRequest actionRequest = new DmfActionRequest(); final DmfActionRequest actionRequest = new DmfActionRequest();
actionRequest.setActionId(action.getId()); actionRequest.setActionId(action.getId());
@@ -463,23 +460,27 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
} }
} }
private void sendMultiActionRequestMessages(final String tenant, final List<String> controllerIds) { private void sendMultiActionRequestMessages(final List<String> controllerIds) {
final Map<SoftwareModule, List<SoftwareModuleMetadata>> softwareModuleMetadata = new HashMap<>(); final Map<String, List<Action>> controllerIdToActions = controllerIds.stream()
targetManagement.getByControllerID(controllerIds).stream() .collect(Collectors.toMap(
.filter(target -> IpUtil.isAmqpUri(target.getAddress())).forEach(target -> { Function.identity(),
final List<Action> activeActions = deploymentManagement controllerId -> deploymentManagement.findActiveActionsWithHighestWeight(controllerId, MAX_ACTION_COUNT)));
.findActiveActionsWithHighestWeight(target.getControllerId(), MAX_ACTION_COUNT);
activeActions.forEach(action -> // gets all software modules for all action at once
action.getDistributionSet().getModules().forEach(module -> final Set<Long> allSmIds = controllerIdToActions.values().stream()
softwareModuleMetadata.computeIfAbsent(module, this::getSoftwareModuleMetadata))); .flatMap(actions -> actions.stream()
.map(Action::getDistributionSet)
.flatMap(ds -> ds.getModules().stream())
.map(SoftwareModule::getId))
.collect(Collectors.toSet());
final Map<Long, List<SoftwareModuleMetadata>> getSoftwareModuleMetadata =
allSmIds.isEmpty()
? Collections.emptyMap()
: softwareModuleManagement.findMetaDataBySoftwareModuleIdsAndTargetVisible(allSmIds);
if (!activeActions.isEmpty()) { targetManagement.getByControllerID(controllerIds).forEach(target ->
sendMultiActionRequestToTarget(tenant, target, activeActions, sendMultiActionRequestToTarget(
action -> action.getDistributionSet().getModules().stream() target, controllerIdToActions.get(target.getControllerId()), module -> getSoftwareModuleMetadata.get(module.getId())));
.collect(Collectors.toMap(m -> m, softwareModuleMetadata::get)));
}
});
} }
private DmfActionRequest createDmfActionRequest( private DmfActionRequest createDmfActionRequest(
@@ -602,7 +603,7 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
} }
private Map<SoftwareModule, List<SoftwareModuleMetadata>> getSoftwareModulesWithMetadata(final DistributionSet distributionSet) { private Map<SoftwareModule, List<SoftwareModuleMetadata>> getSoftwareModulesWithMetadata(final DistributionSet distributionSet) {
return distributionSet.getModules().stream().collect(Collectors.toMap(m -> m, this::getSoftwareModuleMetadata)); return distributionSet.getModules().stream().collect(Collectors.toMap(Function.identity(), this::getSoftwareModuleMetadata));
} }
private List<SoftwareModuleMetadata> getSoftwareModuleMetadata(final SoftwareModule module) { private List<SoftwareModuleMetadata> getSoftwareModuleMetadata(final SoftwareModule module) {

View File

@@ -20,6 +20,7 @@ import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -67,9 +68,8 @@ import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
* {@link AmqpMessageHandlerService} handles all incoming target interaction * {@link AmqpMessageHandlerService} handles all incoming target interaction AMQP messages (e.g. create target, check for updates etc.) for the
* AMQP messages (e.g. create target, check for updates etc.) for the queue * queue which is configured for the property hawkbit.dmf.rabbitmq.receiverQueue.
* which is configured for the property hawkbit.dmf.rabbitmq.receiverQueue.
*/ */
@Slf4j @Slf4j
public class AmqpMessageHandlerService extends BaseAmqpService { public class AmqpMessageHandlerService extends BaseAmqpService {
@@ -344,41 +344,39 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
private void sendCurrentActionsAsMultiActionToTarget(final Target target) { private void sendCurrentActionsAsMultiActionToTarget(final Target target) {
final List<Action> actions = controllerManagement.findActiveActionsWithHighestWeight(target.getControllerId(), MAX_ACTION_COUNT); final List<Action> actions = controllerManagement.findActiveActionsWithHighestWeight(target.getControllerId(), MAX_ACTION_COUNT);
final Set<DistributionSet> distributionSets = actions.stream().map(Action::getDistributionSet).collect(Collectors.toSet()); // gets all software modules for all action at once
final Map<Long, Map<SoftwareModule, List<SoftwareModuleMetadata>>> softwareModulesPerDistributionSet = distributionSets final Set<Long> allSmIds = actions.stream()
.stream().collect(Collectors.toMap(DistributionSet::getId, this::getSoftwareModulesWithMetadata)); .map(Action::getDistributionSet)
.flatMap(ds -> ds.getModules().stream())
.map(SoftwareModule::getId)
.collect(Collectors.toSet());
final Map<Long, List<SoftwareModuleMetadata>> getSoftwareModuleMetadata =
allSmIds.isEmpty() ? Collections.emptyMap() : controllerManagement.findTargetVisibleMetaDataBySoftwareModuleId(allSmIds);
amqpMessageDispatcherService.sendMultiActionRequestToTarget( amqpMessageDispatcherService.sendMultiActionRequestToTarget(target, actions, module -> getSoftwareModuleMetadata.get(module.getId()));
target.getTenant(), target, actions,
action -> softwareModulesPerDistributionSet.get(action.getDistributionSet().getId()));
} }
private void sendOldestActionToTarget(final Target target) { private void sendOldestActionToTarget(final Target target) {
final Optional<Action> actionOptional = controllerManagement.findActiveActionWithHighestWeight(target.getControllerId()); final Optional<Action> actionOptional = controllerManagement.findActiveActionWithHighestWeight(target.getControllerId());
if (actionOptional.isEmpty()) { if (actionOptional.isEmpty()) {
return; return;
} }
final Action action = actionOptional.get(); final Action action = actionOptional.get();
if (action.isCancelingOrCanceled()) { if (action.isCancelingOrCanceled()) {
amqpMessageDispatcherService.sendCancelMessageToTarget(target.getTenant(), target.getControllerId(), amqpMessageDispatcherService.sendCancelMessageToTarget(
action.getId(), target.getAddress()); target.getTenant(), target.getControllerId(), action.getId(), target.getAddress());
} else { } else {
amqpMessageDispatcherService.sendUpdateMessageToTarget(new ActionProperties(action), action.getTarget(), amqpMessageDispatcherService.sendUpdateMessageToTarget(
getSoftwareModulesWithMetadata(action.getDistributionSet())); new ActionProperties(action), action.getTarget(), getSoftwareModulesWithMetadata(action.getDistributionSet()));
} }
} }
private Map<SoftwareModule, List<SoftwareModuleMetadata>> getSoftwareModulesWithMetadata(final DistributionSet distributionSet) { private Map<SoftwareModule, List<SoftwareModuleMetadata>> getSoftwareModulesWithMetadata(final DistributionSet distributionSet) {
final List<Long> smIds = distributionSet.getModules().stream().map(SoftwareModule::getId) final List<Long> smIds = distributionSet.getModules().stream().map(SoftwareModule::getId).collect(Collectors.toList());
.collect(Collectors.toList()); final Map<Long, List<SoftwareModuleMetadata>> metadata = controllerManagement.findTargetVisibleMetaDataBySoftwareModuleId(smIds);
return distributionSet.getModules().stream().collect(Collectors.toMap(
final Map<Long, List<SoftwareModuleMetadata>> metadata = controllerManagement Function.identity(), sm -> metadata.getOrDefault(sm.getId(), Collections.emptyList())));
.findTargetVisibleMetaDataBySoftwareModuleId(smIds);
return distributionSet.getModules().stream()
.collect(Collectors.toMap(sm -> sm, sm -> metadata.getOrDefault(sm.getId(), Collections.emptyList())));
} }

View File

@@ -465,8 +465,9 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
assertLatestMultiActionMessageContainsInstallMessages(controllerId, Arrays.asList(smIds1, smIds2, smIds1)); assertLatestMultiActionMessageContainsInstallMessages(controllerId, Arrays.asList(smIds1, smIds2, smIds1));
final List<Long> installActions = getLatestMultiActionMessageActions(controllerId).stream() final List<Long> installActions = getLatestMultiActionMessageActions(controllerId).stream()
.filter(entry -> entry.getValue().equals(EventTopic.DOWNLOAD_AND_INSTALL)).map(Entry::getKey) .filter(entry -> entry.getValue().equals(EventTopic.DOWNLOAD_AND_INSTALL))
.collect(Collectors.toList()); .map(Entry::getKey)
.toList();
updateActionViaDmfClient(controllerId, installActions.get(0), DmfActionStatus.FINISHED); updateActionViaDmfClient(controllerId, installActions.get(0), DmfActionStatus.FINISHED);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.REQUEST_ATTRIBUTES_UPDATE, EventTopic.MULTI_ACTION); waitUntilEventMessagesAreDispatchedToTarget(EventTopic.REQUEST_ATTRIBUTES_UPDATE, EventTopic.MULTI_ACTION);

View File

@@ -41,23 +41,19 @@ import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
/** /**
* Service layer for all operations of the DDI API (with access permissions only * Service layer for all operations of the DDI API (with access permissions only for the controller).
* for the controller).
*/ */
public interface ControllerManagement { public interface ControllerManagement {
/** /**
* Adds an {@link ActionStatus} for a cancel {@link Action} including * Adds an {@link ActionStatus} for a cancel {@link Action} including potential state changes for the target and the {@link Action} itself.
* potential state changes for the target and the {@link Action} itself.
* *
* @param create to be added * @param create to be added
* @return the updated {@link Action} * @return the updated {@link Action}
* @throws EntityAlreadyExistsException if a given entity already exists * @throws EntityAlreadyExistsException if a given entity already exists
* @throws AssignmentQuotaExceededException if more than the allowed number of status entries or messages * @throws AssignmentQuotaExceededException if more than the allowed number of status entries or messages per entry are inserted
* per entry are inserted
* @throws EntityNotFoundException if given action does not exist * @throws EntityNotFoundException if given action does not exist
* @throws ConstraintViolationException if fields are not filled as specified. Check * @throws ConstraintViolationException if fields are not filled as specified. Check {@link ActionStatusCreate} for field constraints.
* {@link ActionStatusCreate} for field constraints.
*/ */
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
Action addCancelActionStatus(@NotNull @Valid ActionStatusCreate create); Action addCancelActionStatus(@NotNull @Valid ActionStatusCreate create);
@@ -72,54 +68,45 @@ public interface ControllerManagement {
Optional<SoftwareModule> getSoftwareModule(long moduleId); Optional<SoftwareModule> getSoftwareModule(long moduleId);
/** /**
* Retrieves {@link SoftwareModuleMetadata} where * Retrieves {@link SoftwareModuleMetadata} where {@link SoftwareModuleMetadata#isTargetVisible()}.
* {@link SoftwareModuleMetadata#isTargetVisible()}.
* *
* @param moduleId of the {@link SoftwareModule} * @param moduleId of the {@link SoftwareModule}
* @return list of {@link SoftwareModuleMetadata} with maximum size of * @return the map of software module id to {@link SoftwareModuleMetadata} with maximum size of
* {@link RepositoryConstants#MAX_META_DATA_COUNT} * {@link RepositoryConstants#MAX_META_DATA_COUNT}
*/ */
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
Map<Long, List<SoftwareModuleMetadata>> findTargetVisibleMetaDataBySoftwareModuleId( Map<Long, List<SoftwareModuleMetadata>> findTargetVisibleMetaDataBySoftwareModuleId(@NotNull Collection<Long> moduleId);
@NotNull Collection<Long> moduleId);
/** /**
* Simple addition of a new {@link ActionStatus} entry to the * Simple addition of a new {@link ActionStatus} entry to the {@link Action}. No state changes.
* {@link Action}. No state changes.
* *
* @param create to add to the action * @param create to add to the action
* @return created {@link ActionStatus} entity * @return created {@link ActionStatus} entity
* @throws AssignmentQuotaExceededException if more than the allowed number of status entries or messages * @throws AssignmentQuotaExceededException if more than the allowed number of status entries or messages per entry are inserted
* per entry are inserted
* @throws EntityNotFoundException if given action does not exist * @throws EntityNotFoundException if given action does not exist
* @throws ConstraintViolationException if fields are not filled as specified. Check * @throws ConstraintViolationException if fields are not filled as specified. Check {@link ActionStatusCreate} for field constraints.
* {@link ActionStatusCreate} for field constraints.
*/ */
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
ActionStatus addInformationalActionStatus(@NotNull @Valid ActionStatusCreate create); ActionStatus addInformationalActionStatus(@NotNull @Valid ActionStatusCreate create);
/** /**
* Adds an {@link ActionStatus} entry for an update {@link Action} including * Adds an {@link ActionStatus} entry for an update {@link Action} including potential state changes for the target and the {@link Action}
* potential state changes for the target and the {@link Action} itself. * itself.
* *
* @param create to be added * @param create to be added
* @return the updated {@link Action} * @return the updated {@link Action}
* @throws EntityAlreadyExistsException if a given entity already exists * @throws EntityAlreadyExistsException if a given entity already exists
* @throws AssignmentQuotaExceededException if more than the allowed number of status entries or messages * @throws AssignmentQuotaExceededException if more than the allowed number of status entries or messages per entry are inserted
* per entry are inserted
* @throws EntityNotFoundException if action status not exist * @throws EntityNotFoundException if action status not exist
* @throws ConstraintViolationException if fields are not filled as specified. Check * @throws ConstraintViolationException if fields are not filled as specified. Check {@link ActionStatusCreate} for field constraints.
* {@link ActionStatusCreate} for field constraints.
*/ */
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
Action addUpdateActionStatus(@NotNull @Valid ActionStatusCreate create); Action addUpdateActionStatus(@NotNull @Valid ActionStatusCreate create);
/** /**
* Retrieves active {@link Action} with highest priority that is assigned to * Retrieves active {@link Action} with the highest priority that is assigned to a {@link Target}.
* a {@link Target}.
* *
* For performance reasons this method does not throw * For performance reasons this method does not throw {@link EntityNotFoundException} in case target with given controllerId
* {@link EntityNotFoundException} in case target with given controllerId
* does not exist but will return an {@link Optional#empty()} instead. * does not exist but will return an {@link Optional#empty()} instead.
* *
* @param controllerId identifies the target to retrieve the action from * @param controllerId identifies the target to retrieve the action from
@@ -140,17 +127,7 @@ public interface ControllerManagement {
List<Action> findActiveActionsWithHighestWeight(@NotEmpty String controllerId, int maxActionCount); List<Action> findActiveActionsWithHighestWeight(@NotEmpty String controllerId, int maxActionCount);
/** /**
* Get weight of an Action. Returns the default value if the weight is null * Get the {@link Action} entity for given actionId with all lazy attributes.
* according to the properties.
*
* @param action to extract the weight from
* @return weight of the action
*/
int getWeightConsideringDefault(final Action action);
/**
* Get the {@link Action} entity for given actionId with all lazy
* attributes.
* *
* @param actionId to be id of the action * @param actionId to be id of the action
* @return the corresponding {@link Action} * @return the corresponding {@link Action}
@@ -159,8 +136,7 @@ public interface ControllerManagement {
Optional<Action> findActionWithDetails(long actionId); Optional<Action> findActionWithDetails(long actionId);
/** /**
* Retrieves all the {@link ActionStatus} entries of the given * Retrieves all the {@link ActionStatus} entries of the given {@link Action}.
* {@link Action}.
* *
* @param pageReq pagination parameter * @param pageReq pagination parameter
* @param actionId to be filtered on * @param actionId to be filtered on
@@ -171,11 +147,8 @@ public interface ControllerManagement {
Page<ActionStatus> findActionStatusByAction(@NotNull Pageable pageReq, long actionId); Page<ActionStatus> findActionStatusByAction(@NotNull Pageable pageReq, long actionId);
/** /**
* Register new target in the repository (plug-and-play) and in case it * Register new target in the repository (plug-and-play) and in case it already exists updates {@link Target#getAddress()} and
* already exists updates {@link Target#getAddress()} and * {@link Target#getLastTargetQuery()} and switches if {@link TargetUpdateStatus#UNKNOWN} to {@link TargetUpdateStatus#REGISTERED}.
* {@link Target#getLastTargetQuery()} and switches if
* {@link TargetUpdateStatus#UNKNOWN} to
* {@link TargetUpdateStatus#REGISTERED}.
* *
* @param controllerId reference * @param controllerId reference
* @param address the client IP address of the target, might be {@code null} * @param address the client IP address of the target, might be {@code null}
@@ -199,16 +172,13 @@ public interface ControllerManagement {
* @return target reference * @return target reference
*/ */
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
Target findOrRegisterTargetIfItDoesNotExist(@NotEmpty String controllerId, @NotNull URI address, String name, Target findOrRegisterTargetIfItDoesNotExist(@NotEmpty String controllerId, @NotNull URI address, String name, String type);
String type);
/** /**
* Retrieves last {@link Action} for a download of an artifact of given * Retrieves last {@link Action} for a download of an artifact of given module and target if exists and is not canceled.
* module and target if exists and is not canceled.
* *
* @param controllerId to look for * @param controllerId to look for
* @param moduleId of the the {@link SoftwareModule} that should be assigned to * @param moduleId of the {@link SoftwareModule} that should be assigned to the target
* the target
* @return last {@link Action} for given combination * @return last {@link Action} for given combination
* @throws EntityNotFoundException if target with given ID does not exist * @throws EntityNotFoundException if target with given ID does not exist
*/ */
@@ -216,8 +186,7 @@ public interface ControllerManagement {
Optional<Action> getActionForDownloadByTargetAndSoftwareModule(@NotEmpty String controllerId, long moduleId); Optional<Action> getActionForDownloadByTargetAndSoftwareModule(@NotEmpty String controllerId, long moduleId);
/** /**
* Returns configured polling interval at which the controller polls hawkBit * Returns configured polling interval at which the controller polls hawkBit server.
* server.
* *
* @return current {@link TenantConfigurationKey#POLLING_TIME_INTERVAL}. * @return current {@link TenantConfigurationKey#POLLING_TIME_INTERVAL}.
*/ */
@@ -233,44 +202,33 @@ public interface ControllerManagement {
String getMinPollingTime(); String getMinPollingTime();
/** /**
* Returns the count to be used for reducing polling interval while calling * Returns the count to be used for reducing polling interval while calling {@link ControllerManagement#getPollingTimeForAction(long)}.
* {@link ControllerManagement#getPollingTimeForAction(long)}.
* *
* @return configured value of * @return configured value of {@link TenantConfigurationKey#MAINTENANCE_WINDOW_POLL_COUNT}.
* {@link TenantConfigurationKey#MAINTENANCE_WINDOW_POLL_COUNT}.
*/ */
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
int getMaintenanceWindowPollCount(); int getMaintenanceWindowPollCount();
/** /**
* Returns polling time based on the maintenance window for an action. * Returns polling time based on the maintenance window for an action. Server will reduce the polling interval as the start time for
* Server will reduce the polling interval as the start time for maintenance * maintenance window approaches, so that at least these many attempts are made between current polling until start of maintenance window.
* window approaches, so that at least these many attempts are made between * Poll time keeps reducing with MinPollingTime as lower limit {@link TenantConfigurationKey#MIN_POLLING_TIME_INTERVAL}. After the start
* current polling until start of maintenance window. Poll time keeps * of maintenance window, it resets to default {@link TenantConfigurationKey#POLLING_TIME_INTERVAL}.
* reducing with MinPollingTime as lower limit
* {@link TenantConfigurationKey#MIN_POLLING_TIME_INTERVAL}. After the start
* of maintenance window, it resets to default
* {@link TenantConfigurationKey#POLLING_TIME_INTERVAL}.
* *
* @param actionId id the {@link Action} for which polling time is calculated * @param actionId id the {@link Action} for which polling time is calculated based on it having maintenance window or not
* based on it having maintenance window or not
* @return current {@link TenantConfigurationKey#POLLING_TIME_INTERVAL}. * @return current {@link TenantConfigurationKey#POLLING_TIME_INTERVAL}.
*/ */
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
String getPollingTimeForAction(long actionId); String getPollingTimeForAction(long actionId);
/** /**
* Checks if a given target has currently or has even been assigned to the * Checks if a given target has currently or has even been assigned to the given artifact through the action history list. This can e.g.
* given artifact through the action history list. This can e.g. indicate if * indicate if a target is allowed to download a given artifact because it has currently assigned or had ever been assigned to the target
* a target is allowed to download a given artifact because it has currently * and so it's visible to a specific target e.g. for downloading.
* assigned or had ever been assigned to the target and so it's visible to a
* specific target e.g. for downloading.
* *
* @param controllerId the ID of the target to check * @param controllerId the ID of the target to check
* @param sha1Hash of the artifact to verify if the given target had even been * @param sha1Hash of the artifact to verify if the given target had even been assigned to
* assigned to * @return {@code true} if the given target has currently or had ever a relation to the given artifact through the action history,
* @return {@code true} if the given target has currently or had ever a
* relation to the given artifact through the action history,
* otherwise {@code false} * otherwise {@code false}
* @throws EntityNotFoundException if target with given ID does not exist * @throws EntityNotFoundException if target with given ID does not exist
*/ */
@@ -278,17 +236,13 @@ public interface ControllerManagement {
boolean hasTargetArtifactAssigned(@NotEmpty String controllerId, @NotEmpty String sha1Hash); boolean hasTargetArtifactAssigned(@NotEmpty String controllerId, @NotEmpty String sha1Hash);
/** /**
* Checks if a given target has currently or has even been assigned to the * Checks if a given target has currently or has even been assigned to the given artifact through the action history list. This can e.g.
* given artifact through the action history list. This can e.g. indicate if * indicate if a target is allowed to download a given artifact because it has currently assigned or had ever been assigned to the target
* a target is allowed to download a given artifact because it has currently * and so it's visible to a specific target e.g. for downloading.
* assigned or had ever been assigned to the target and so it's visible to a
* specific target e.g. for downloading.
* *
* @param targetId the ID of the target to check * @param targetId the ID of the target to check
* @param sha1Hash of the artifact to verify if the given target had even been * @param sha1Hash of the artifact to verify if the given target had even been assigned to
* assigned to * @return {@code true} if the given target has currently or had ever a relation to the given artifact through the action history,
* @return {@code true} if the given target has currently or had ever a
* relation to the given artifact through the action history,
* otherwise {@code false} * otherwise {@code false}
* @throws EntityNotFoundException if target with given ID does not exist * @throws EntityNotFoundException if target with given ID does not exist
*/ */
@@ -296,21 +250,18 @@ public interface ControllerManagement {
boolean hasTargetArtifactAssigned(long targetId, @NotEmpty String sha1Hash); boolean hasTargetArtifactAssigned(long targetId, @NotEmpty String sha1Hash);
/** /**
* Registers retrieved status for given {@link Target} and {@link Action} if * Registers retrieved status for given {@link Target} and {@link Action} if it does not exist yet.
* it does not exist yet.
* *
* @param actionId to the handle status for * @param actionId to the handle status for
* @param message for the status * @param message for the status
* @return the update action in case the status has been changed to * @return the update action in case the status has been changed to {@link Status#RETRIEVED}
* {@link Status#RETRIEVED}
* @throws EntityNotFoundException if action with given ID does not exist * @throws EntityNotFoundException if action with given ID does not exist
*/ */
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
Action registerRetrieved(long actionId, String message); Action registerRetrieved(long actionId, String message);
/** /**
* Updates attributes of the controller according to the given * Updates attributes of the controller according to the given {@link UpdateMode}.
* {@link UpdateMode}.
* *
* @param controllerId to update * @param controllerId to update
* @param attributes to insert * @param attributes to insert
@@ -324,47 +275,37 @@ public interface ControllerManagement {
Target updateControllerAttributes(@NotEmpty String controllerId, @NotNull Map<String, String> attributes, UpdateMode mode); Target updateControllerAttributes(@NotEmpty String controllerId, @NotNull Map<String, String> attributes, UpdateMode mode);
/** /**
* Finds {@link Target} based on given controller ID returns found Target * Finds {@link Target} based on given controller ID returns found Target without details, i.e.
* without details, i.e. NO {@link Target#getTags()} and * NO {@link Target#getTags()} and {@link Target#getActions()} possible.
* {@link Target#getActions()} possible.
* *
* @param controllerId to look for. * @param controllerId to look for.
* @return {@link Target} or {@code null} if it does not exist * @return {@link Target} or {@code null} if it does not exist
* @see Target#getControllerId() * @see Target#getControllerId()
*/ */
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER + SpringEvalExpressions.HAS_AUTH_OR @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER + SpringEvalExpressions.HAS_AUTH_OR + SpringEvalExpressions.IS_SYSTEM_CODE)
+ SpringEvalExpressions.IS_SYSTEM_CODE)
Optional<Target> getByControllerId(@NotEmpty String controllerId); Optional<Target> getByControllerId(@NotEmpty String controllerId);
/** /**
* Finds {@link Target} based on given ID returns found Target without * Finds {@link Target} based on given ID returns found Target without details, i.e.
* details, i.e. NO {@link Target#getTags()} and {@link Target#getActions()} * NO {@link Target#getTags()} and {@link Target#getActions()}
* possible. * possible.
* *
* @param targetId to look for. * @param targetId to look for.
* @return {@link Target} or {@code null} if it does not exist * @return {@link Target} or {@code null} if it does not exist
* @see Target#getId() * @see Target#getId()
*/ */
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER + SpringEvalExpressions.HAS_AUTH_OR @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER + SpringEvalExpressions.HAS_AUTH_OR + SpringEvalExpressions.IS_SYSTEM_CODE)
+ SpringEvalExpressions.IS_SYSTEM_CODE)
Optional<Target> get(long targetId); Optional<Target> get(long targetId);
/** /**
* Retrieves the specified number of messages from action history of the * Retrieves the specified number of messages from action history of the given {@link Action} based on messageCount. Regardless of the
* given {@link Action} based on messageCount. Regardless of the value of * value of messageCount, in order to restrict resource utilisation by controllers, maximum number of messages that are retrieved from
* messageCount, in order to restrict resource utilisation by controllers, * database is limited by {@link RepositoryConstants#MAX_ACTION_HISTORY_MSG_COUNT}. messageCount less than zero, retrieves the maximum
* maximum number of messages that are retrieved from database is limited by * allowed number of action status messages from history; messageCount equal zero, does not retrieve any message; and messageCount larger
* {@link RepositoryConstants#MAX_ACTION_HISTORY_MSG_COUNT}. messageCount * than zero, retrieves the specified number of messages, limited by maximum allowed number. A controller sends the feedback for an
* less then zero, retrieves the maximum allowed number of action status * {@link ActionStatus} as a list of messages; while returning the messages, even though the messages from multiple {@link ActionStatus}
* messages from history; messageCount equal zero, does not retrieve any * are retrieved in descending order by the reported time ({@link ActionStatus#getOccurredAt()}), i.e. latest ActionStatus first, the
* message; and messageCount larger then zero, retrieves the specified * sub-ordering of messages from within single {@link ActionStatus} is unspecified.
* number of messages, limited by maximum allowed number. A controller sends
* the feedback for an {@link ActionStatus} as a list of messages; while
* returning the messages, even though the messages from multiple
* {@link ActionStatus} are retrieved in descending order by the reported
* time ({@link ActionStatus#getOccurredAt()}), i.e. latest ActionStatus
* first, the sub-ordering of messages from within single
* {@link ActionStatus} is unspecified.
* *
* @param actionId to be filtered on * @param actionId to be filtered on
* @param messageCount is the number of messages to return from history * @param messageCount is the number of messages to return from history
@@ -374,10 +315,8 @@ public interface ControllerManagement {
List<String> getActionHistoryMessages(long actionId, int messageCount); List<String> getActionHistoryMessages(long actionId, int messageCount);
/** /**
* Cancels given {@link Action} for this {@link Target}. However, it might * Cancels given {@link Action} for this {@link Target}. However, it might be possible that the controller will continue to work on the
* be possible that the controller will continue to work on the * cancellation. The controller needs to acknowledge or reject the cancellation using {@link DdiRootController#postCancelActionFeedback}.
* cancellation. The controller needs to acknowledge or reject the
* cancellation using {@link DdiRootController#postCancelActionFeedback}.
* *
* @param actionId to be canceled * @param actionId to be canceled
* @return canceled {@link Action} * @return canceled {@link Action}
@@ -425,8 +364,7 @@ public interface ControllerManagement {
* Activate auto confirmation for a given controllerId * Activate auto confirmation for a given controllerId
* *
* @param controllerId to activate auto-confirmation on * @param controllerId to activate auto-confirmation on
* @param initiator can be set optionally (fallback is the current acting security * @param initiator can be set optionally (fallback is the current acting security user)
* user)
* @param remark (optional) remark * @param remark (optional) remark
* @return the persisted {@link AutoConfirmationStatus} * @return the persisted {@link AutoConfirmationStatus}
*/ */
@@ -452,4 +390,4 @@ public interface ControllerManagement {
*/ */
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
boolean updateOfflineAssignedVersion(@NotEmpty String controllerId, String distributionName, String version); boolean updateOfflineAssignedVersion(@NotEmpty String controllerId, String distributionName, String version);
} }

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import jakarta.validation.Valid; import jakarta.validation.Valid;
@@ -173,8 +174,7 @@ public interface SoftwareModuleManagement
* @throws EntityNotFoundException if software module with given ID does not exist * @throws EntityNotFoundException if software module with given ID does not exist
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
Page<SoftwareModuleMetadata> findMetaDataBySoftwareModuleIdAndTargetVisible(@NotNull Pageable pageable, Page<SoftwareModuleMetadata> findMetaDataBySoftwareModuleIdAndTargetVisible(@NotNull Pageable pageable, long id);
long id);
/** /**
* Finds all meta data by the given software module id. * Finds all meta data by the given software module id.
@@ -190,8 +190,7 @@ public interface SoftwareModuleManagement
* @throws EntityNotFoundException if software module with given ID does not exist * @throws EntityNotFoundException if software module with given ID does not exist
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
Page<SoftwareModuleMetadata> findMetaDataByRsql(@NotNull Pageable pageable, long id, Page<SoftwareModuleMetadata> findMetaDataByRsql(@NotNull Pageable pageable, long id, @NotNull String rsqlParam);
@NotNull String rsqlParam);
/** /**
* Retrieves the {@link SoftwareModule}s by their {@link SoftwareModuleType} * Retrieves the {@link SoftwareModule}s by their {@link SoftwareModuleType}
@@ -235,4 +234,7 @@ public interface SoftwareModuleManagement
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
SoftwareModuleMetadata updateMetaData(@NotNull @Valid SoftwareModuleMetadataUpdate update); SoftwareModuleMetadata updateMetaData(@NotNull @Valid SoftwareModuleMetadataUpdate update);
}
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
Map<Long, List<SoftwareModuleMetadata>> findMetaDataBySoftwareModuleIdsAndTargetVisible(Collection<Long> moduleIds);
}

View File

@@ -29,8 +29,7 @@ public interface BaseEntity extends Serializable, Identifiable<Long> {
String getCreatedBy(); String getCreatedBy();
/** /**
* @return time in {@link TimeUnit#MILLISECONDS} when the {@link BaseEntity} * @return time in {@link TimeUnit#MILLISECONDS} when the {@link BaseEntity} was created.
* was created.
*/ */
long getCreatedAt(); long getCreatedAt();

View File

@@ -13,10 +13,9 @@ import static org.eclipse.hawkbit.repository.model.Action.ActionType.DOWNLOAD_ON
import static org.eclipse.hawkbit.repository.model.Action.Status.ERROR; import static org.eclipse.hawkbit.repository.model.Action.Status.ERROR;
import static org.eclipse.hawkbit.repository.model.Action.Status.FINISHED; import static org.eclipse.hawkbit.repository.model.Action.Status.FINISHED;
import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Stream;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.repository.QuotaManagement; import org.eclipse.hawkbit.repository.QuotaManagement;
@@ -25,7 +24,6 @@ import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.jpa.builder.JpaActionStatusCreate; import org.eclipse.hawkbit.repository.jpa.builder.JpaActionStatusCreate;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction; import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus; import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction_;
import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository; import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository;
import org.eclipse.hawkbit.repository.jpa.repository.ActionStatusRepository; import org.eclipse.hawkbit.repository.jpa.repository.ActionStatusRepository;
import org.eclipse.hawkbit.repository.jpa.specifications.ActionSpecifications; import org.eclipse.hawkbit.repository.jpa.specifications.ActionSpecifications;
@@ -34,7 +32,7 @@ import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.ActionStatus; import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Pageable;
/** /**
* Implements utility methods for managing {@link Action}s * Implements utility methods for managing {@link Action}s
@@ -104,32 +102,19 @@ public class JpaActionManagement {
quotaManagement.getMaxMessagesPerActionStatus(), "Message", ActionStatus.class.getSimpleName(), null); quotaManagement.getMaxMessagesPerActionStatus(), "Message", ActionStatus.class.getSimpleName(), null);
} }
List<Action> findActiveActionsWithHighestWeightConsideringDefault(final String controllerId, protected List<Action> findActiveActionsWithHighestWeightConsideringDefault(final String controllerId, final int maxActionCount) {
final int maxActionCount) { final Pageable pageable = PageRequest.of(0, maxActionCount);
final List<Action> actions = new ArrayList<>(); return Stream.concat(
actions.addAll( // get the highest actions with weight
actionRepository actionRepository
.findAll( .findFetchDsByTarget_ControllerIdAndActiveIsTrueAndWeightNotNullOrderByWeightDescIdAsc(controllerId, pageable)
ActionSpecifications.byTargetControllerIdAndActiveAndWeightIsNullFetchDS(controllerId, false), .stream(),
PageRequest.of( // get the oldest actions without weight
0, maxActionCount, actionRepository.findFetchDsByTarget_ControllerIdAndActiveIsTrueAndWeightIsNullOrderByIdAsc(controllerId, pageable)
Sort.by( .stream())
Sort.Order.desc(JpaAction_.WEIGHT), .sorted(Comparator.comparingInt(this::getWeightConsideringDefault).reversed().thenComparing(Action::getId))
Sort.Order.asc(JpaAction_.ID)))) .limit(maxActionCount)
.getContent()); .toList();
actions.addAll(
actionRepository
.findAll(
ActionSpecifications.byTargetControllerIdAndActiveAndWeightIsNullFetchDS(controllerId, true),
PageRequest.of(
0, maxActionCount,
Sort.by(
Sort.Order.asc(JpaAction_.ID))))
.getContent());
final Comparator<Action> actionImportance = Comparator.comparingInt(this::getWeightConsideringDefault)
.reversed().thenComparing(Action::getId);
return actions.stream().sorted(actionImportance).limit(maxActionCount).collect(Collectors.toList());
} }
private static boolean isIntermediateStatus(final JpaActionStatus actionStatus) { private static boolean isIntermediateStatus(final JpaActionStatus actionStatus) {

View File

@@ -21,6 +21,7 @@ import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalUnit; import java.time.temporal.TemporalUnit;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@@ -32,6 +33,7 @@ import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManager;
import jakarta.persistence.Query; import jakarta.persistence.Query;
@@ -250,8 +252,7 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
@Override @Override
public Map<Long, List<SoftwareModuleMetadata>> findTargetVisibleMetaDataBySoftwareModuleId(final Collection<Long> moduleId) { public Map<Long, List<SoftwareModuleMetadata>> findTargetVisibleMetaDataBySoftwareModuleId(final Collection<Long> moduleId) {
return softwareModuleMetadataRepository return softwareModuleMetadataRepository
.findBySoftwareModuleIdInAndTargetVisible( .findBySoftwareModuleIdInAndTargetVisible(moduleId, true, PageRequest.of(0, RepositoryConstants.MAX_META_DATA_COUNT))
PageRequest.of(0, RepositoryConstants.MAX_META_DATA_COUNT), moduleId, true)
.getContent().stream() .getContent().stream()
.collect(Collectors.groupingBy(o -> (Long) o[0], Collectors.mapping(o -> (SoftwareModuleMetadata) o[1], Collectors.toList()))); .collect(Collectors.groupingBy(o -> (Long) o[0], Collectors.mapping(o -> (SoftwareModuleMetadata) o[1], Collectors.toList())));
} }
@@ -280,14 +281,23 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
return addActionStatus((JpaActionStatusCreate) statusCreate); return addActionStatus((JpaActionStatusCreate) statusCreate);
} }
private static final Pageable PAGEABLE_1 = PageRequest.of(0, 1);
@Override @Override
public Optional<Action> findActiveActionWithHighestWeight(final String controllerId) { public Optional<Action> findActiveActionWithHighestWeight(final String controllerId) {
return findActiveActionsWithHighestWeight(controllerId, 1).stream().findFirst(); return Stream.concat(
// get the highest action with weight
actionRepository
.findByTarget_ControllerIdAndActiveIsTrueAndWeightNotNullOrderByWeightDescIdAsc(controllerId, PAGEABLE_1)
.stream(),
// get the oldest action without weight
actionRepository.findByTarget_ControllerIdAndActiveIsTrueAndWeightIsNullOrderByIdAsc(controllerId, PAGEABLE_1).stream())
.min(Comparator.comparingInt(this::getWeightConsideringDefault).reversed().thenComparing(Action::getId));
} }
@Override @Override
public List<Action> findActiveActionsWithHighestWeight(final String controllerId, final int maxActionCount) { public List<Action> findActiveActionsWithHighestWeight(final String controllerId, final int maxActionCount) {
return findActiveActionsWithHighestWeightConsideringDefault(controllerId, maxActionCount); return super.findActiveActionsWithHighestWeightConsideringDefault(controllerId, maxActionCount);
} }
@Override @Override
@@ -453,7 +463,7 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
@Override @Override
public Optional<Target> get(final long targetId) { public Optional<Target> get(final long targetId) {
return targetRepository.findById(targetId).map(t -> (Target) t); return targetRepository.findById(targetId).map(Target.class::cast);
} }
@Override @Override
@@ -463,18 +473,16 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
return Collections.emptyList(); return Collections.emptyList();
} }
// For negative and large value of messageCount, limit the number of // For negative and large value of messageCount, limit the number of messages.
// messages.
final int limit = messageCount < 0 || messageCount >= RepositoryConstants.MAX_ACTION_HISTORY_MSG_COUNT final int limit = messageCount < 0 || messageCount >= RepositoryConstants.MAX_ACTION_HISTORY_MSG_COUNT
? RepositoryConstants.MAX_ACTION_HISTORY_MSG_COUNT ? RepositoryConstants.MAX_ACTION_HISTORY_MSG_COUNT
: messageCount; : messageCount;
final PageRequest pageable = PageRequest.of(0, limit, Sort.by(Direction.DESC, "occurredAt")); final PageRequest pageable = PageRequest.of(0, limit, Sort.by(Direction.DESC, "occurredAt"));
final Page<String> messages = actionStatusRepository.findMessagesByActionIdAndMessageNotLike(pageable, actionId, final Page<String> messages = actionStatusRepository.findMessagesByActionIdAndMessageNotLike(
RepositoryConstants.SERVER_MESSAGE_PREFIX + "%"); actionId, RepositoryConstants.SERVER_MESSAGE_PREFIX + "%", pageable);
log.debug("Retrieved {} message(s) from action history for action {}.", messages.getNumberOfElements(), log.debug("Retrieved {} message(s) from action history for action {}.", messages.getNumberOfElements(), actionId);
actionId);
return messages.getContent(); return messages.getContent();
} }

View File

@@ -304,7 +304,7 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement {
if (!rolloutGroupIds.isEmpty()) { if (!rolloutGroupIds.isEmpty()) {
final List<TotalTargetCountActionStatus> resultList = actionRepository final List<TotalTargetCountActionStatus> resultList = actionRepository
.getStatusCountByRolloutGroupId(rolloutGroupIds); .getStatusCountByRolloutGroupIds(rolloutGroupIds);
final Map<Long, List<TotalTargetCountActionStatus>> fromDb = resultList.stream() final Map<Long, List<TotalTargetCountActionStatus>> fromDb = resultList.stream()
.collect(Collectors.groupingBy(TotalTargetCountActionStatus::getId)); .collect(Collectors.groupingBy(TotalTargetCountActionStatus::getId));

View File

@@ -711,8 +711,7 @@ public class JpaRolloutManagement implements RolloutManagement {
.collect(Collectors.toList()); .collect(Collectors.toList());
if (!rolloutIds.isEmpty()) { if (!rolloutIds.isEmpty()) {
final List<TotalTargetCountActionStatus> resultList = actionRepository final List<TotalTargetCountActionStatus> resultList = actionRepository.getStatusCountByRolloutIds(rolloutIds);
.getStatusCountByRolloutId(rolloutIds);
final Map<Long, List<TotalTargetCountActionStatus>> fromDb = resultList.stream() final Map<Long, List<TotalTargetCountActionStatus>> fromDb = resultList.stream()
.collect(Collectors.groupingBy(TotalTargetCountActionStatus::getId)); .collect(Collectors.groupingBy(TotalTargetCountActionStatus::getId));

View File

@@ -411,7 +411,7 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
assertSoftwareModuleExists(id); assertSoftwareModuleExists(id);
return JpaManagementHelper.convertPage(softwareModuleMetadataRepository.findBySoftwareModuleIdAndTargetVisible( return JpaManagementHelper.convertPage(softwareModuleMetadataRepository.findBySoftwareModuleIdAndTargetVisible(
PageRequest.of(0, RepositoryConstants.MAX_META_DATA_COUNT), id, true), pageable); id, true, PageRequest.of(0, RepositoryConstants.MAX_META_DATA_COUNT)), pageable);
} }
@Override @Override
@@ -486,6 +486,14 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
return softwareModuleMetadataRepository.save(metadata); return softwareModuleMetadataRepository.save(metadata);
} }
@Override
public Map<Long, List<SoftwareModuleMetadata>> findMetaDataBySoftwareModuleIdsAndTargetVisible(final Collection<Long> moduleIds) {
return softwareModuleMetadataRepository
.findBySoftwareModuleIdInAndTargetVisible(moduleIds, true, PageRequest.of(0, RepositoryConstants.MAX_META_DATA_COUNT))
.getContent().stream()
.collect(Collectors.groupingBy(o -> (Long) o[0], Collectors.mapping(o -> (SoftwareModuleMetadata) o[1], Collectors.toList())));
}
private static Stream<JpaSoftwareModuleMetadataCreate> createJpaMetadataCreateStream( private static Stream<JpaSoftwareModuleMetadataCreate> createJpaMetadataCreateStream(
final Collection<SoftwareModuleMetadataCreate> create) { final Collection<SoftwareModuleMetadataCreate> create) {
return create.stream().map(JpaSoftwareModuleMetadataCreate.class::cast); return create.stream().map(JpaSoftwareModuleMetadataCreate.class::cast);

View File

@@ -83,15 +83,14 @@ public class JpaActionStatus extends AbstractJpaTenantAwareBaseEntity implements
@CollectionTable( @CollectionTable(
name = "sp_action_status_messages", name = "sp_action_status_messages",
joinColumns = @JoinColumn( joinColumns = @JoinColumn(
name = "action_status_id", insertable = false, updatable = false, nullable = false, name = "action_status_id", nullable = false,
foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_stat_msg_act_stat")), foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_stat_msg_act_stat")),
indexes = { indexes = { @Index(name = "sp_idx_action_status_msgs_01", columnList = "action_status_id") })
@Index(name = "sp_idx_action_status_msgs_01", columnList = "action_status_id") }) @Column(name = "detail_message", length = MESSAGE_ENTRY_LENGTH, nullable = false)
@Column(name = "detail_message", length = MESSAGE_ENTRY_LENGTH, nullable = false, insertable = false, updatable = false)
private List<String> messages; private List<String> messages;
@Setter @Setter
@Column(name = "code", nullable = true, updatable = false) @Column(name = "code", updatable = false)
private Integer code; private Integer code;
/** /**

View File

@@ -16,6 +16,7 @@ import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import jakarta.persistence.CascadeType; import jakarta.persistence.CascadeType;
@@ -69,6 +70,8 @@ import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.persistence.descriptors.DescriptorEvent; import org.eclipse.persistence.descriptors.DescriptorEvent;
import org.eclipse.persistence.descriptors.DescriptorEventAdapter; import org.eclipse.persistence.descriptors.DescriptorEventAdapter;
import org.eclipse.persistence.queries.UpdateObjectQuery; import org.eclipse.persistence.queries.UpdateObjectQuery;
import org.hibernate.event.spi.PreUpdateEvent;
import org.hibernate.event.spi.PreUpdateEventListener;
/** /**
* JPA implementation of {@link Target}. * JPA implementation of {@link Target}.
@@ -174,12 +177,12 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
// no cascade option on an ElementCollection, the target objects are always persisted, merged, removed with their parent. // no cascade option on an ElementCollection, the target objects are always persisted, merged, removed with their parent.
@Getter @Getter
@ElementCollection @ElementCollection
@Column(name = "attribute_value", length = Target.CONTROLLER_ATTRIBUTE_VALUE_SIZE)
@MapKeyColumn(name = "attribute_key", nullable = false, length = Target.CONTROLLER_ATTRIBUTE_KEY_SIZE)
@CollectionTable( @CollectionTable(
name = "sp_target_attributes", name = "sp_target_attributes",
joinColumns = { @JoinColumn(name = "target_id", nullable = false, insertable = false, updatable = false) }, joinColumns = { @JoinColumn(name = "target_id", nullable = false) },
foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_attrib_target")) foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_attrib_target"))
@Column(name = "attribute_value", length = Target.CONTROLLER_ATTRIBUTE_VALUE_SIZE)
@MapKeyColumn(name = "attribute_key", length = Target.CONTROLLER_ATTRIBUTE_KEY_SIZE)
private Map<String, String> controllerAttributes; private Map<String, String> controllerAttributes;
@OneToMany(mappedBy = "target", fetch = FetchType.LAZY, cascade = { CascadeType.REMOVE }, targetEntity = JpaTargetMetadata.class) @OneToMany(mappedBy = "target", fetch = FetchType.LAZY, cascade = { CascadeType.REMOVE }, targetEntity = JpaTargetMetadata.class)
@@ -331,7 +334,7 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
/** /**
* Listens to updates on {@link JpaTarget} entities, Filtering out updates that only change the "lastTargetQuery" or "address" fields. * Listens to updates on {@link JpaTarget} entities, Filtering out updates that only change the "lastTargetQuery" or "address" fields.
*/ */
public static class EntityPropertyChangeListener extends DescriptorEventAdapter { public static class EntityPropertyChangeListener extends DescriptorEventAdapter implements PreUpdateEventListener {
private static final List<String> TARGET_UPDATE_EVENT_IGNORE_FIELDS = List.of( private static final List<String> TARGET_UPDATE_EVENT_IGNORE_FIELDS = List.of(
"lastTargetQuery", "address", // actual to be skipped "lastTargetQuery", "address", // actual to be skipped
@@ -346,5 +349,21 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
doNotify(() -> ((EventAwareEntity) object).fireUpdateEvent()); doNotify(() -> ((EventAwareEntity) object).fireUpdateEvent());
} }
} }
@Override
public boolean onPreUpdate(final PreUpdateEvent event) {
final Object[] oldState = event.getOldState();
final Object[] newState = event.getState();
for (int i = 0; i < newState.length; i++) {
if (!Objects.equals(oldState[i], newState[i])) {
final String attribute = event.getPersister().getAttributeMapping(i).getAttributeName();
if (!TARGET_UPDATE_EVENT_IGNORE_FIELDS.contains(attribute)) {
doNotify(() -> ((EventAwareEntity) event.getEntity()).fireUpdateEvent());
break;
}
}
}
return false;
}
} }
} }

View File

@@ -52,15 +52,22 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction> {
* @param targetId the action belongs to * @param targetId the action belongs to
* @param dsId of the ds that is assigned to the target * @param dsId of the ds that is assigned to the target
* @param status of the action * @param status of the action
* @return action if there is one with assigned target and assigned * @return action if there is one with assigned target and assigned {@link DistributionSet}.
* {@link DistributionSet}.
*/ */
Optional<Action> findFirstByTargetIdAndDistributionSetIdAndStatusOrderByIdDesc(@Param("target") long targetId, Optional<Action> findFirstByTargetIdAndDistributionSetIdAndStatusOrderByIdDesc(
@Param("ds") Long dsId, @Param("status") Action.Status status); @Param("target") long targetId, @Param("ds") Long dsId, @Param("status") Action.Status status);
List<Action> findByTarget_ControllerIdAndActiveIsTrueAndWeightIsNullOrderByIdAsc(String controllerId, Pageable pageable);
List<Action> findByTarget_ControllerIdAndActiveIsTrueAndWeightNotNullOrderByWeightDescIdAsc(String controllerId, Pageable pageable);
@EntityGraph(value = "Action.ds", type = EntityGraphType.LOAD)
List<Action> findFetchDsByTarget_ControllerIdAndActiveIsTrueAndWeightIsNullOrderByIdAsc(String controllerId, Pageable pageable);
@EntityGraph(value = "Action.ds", type = EntityGraphType.LOAD)
List<Action> findFetchDsByTarget_ControllerIdAndActiveIsTrueAndWeightNotNullOrderByWeightDescIdAsc(String controllerId, Pageable pageable);
/** /**
* Switches the status of actions from one specific status into another, only if * Switches the status of actions from one specific status into another, only if the actions are in a specific status. This should be
* the actions are in a specific status. This should be a atomic operation. * an atomic operation.
* <p/> * <p/>
* No access control applied * No access control applied
* *
@@ -72,7 +79,8 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction> {
@Modifying @Modifying
@Transactional @Transactional
@Query("UPDATE JpaAction a SET a.status = :statusToSet WHERE a.target.id IN :targetsIds AND a.active = :active AND a.status = :currentStatus AND a.distributionSet.requiredMigrationStep = false") @Query("UPDATE JpaAction a SET a.status = :statusToSet WHERE a.target.id IN :targetsIds AND a.active = :active AND a.status = :currentStatus AND a.distributionSet.requiredMigrationStep = false")
void switchStatus(@Param("statusToSet") Action.Status statusToSet, @Param("targetsIds") List<Long> targetIds, void switchStatus(
@Param("statusToSet") Action.Status statusToSet, @Param("targetsIds") List<Long> targetIds,
@Param("active") boolean active, @Param("currentStatus") Action.Status currentStatus); @Param("active") boolean active, @Param("currentStatus") Action.Status currentStatus);
/** /**
@@ -124,8 +132,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction> {
Long countByDistributionSetIdAndActiveIsTrue(Long distributionSet); Long countByDistributionSetIdAndActiveIsTrue(Long distributionSet);
/** /**
* Counts all active {@link Action}s referring to the given DistributionSet * Counts all active {@link Action}s referring to the given DistributionSet that are not in a given state.
* that are not in a given state.
* <p/> * <p/>
* No access control applied * No access control applied
* *
@@ -136,43 +143,39 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction> {
Long countByDistributionSetIdAndActiveIsTrueAndStatusIsNot(Long distributionSet, Status status); Long countByDistributionSetIdAndActiveIsTrueAndStatusIsNot(Long distributionSet, Status status);
/** /**
* Counts all actions referring to a given rollout and rolloutgroup which * Counts all actions referring to a given rollout and rolloutgroup which are currently not in the given status. An in-clause statement
* are currently not in the given status. An in-clause statement does not * does not work with the spring-data, so this is specific usecase regarding the rollout-management to find out actions which are not in
* work with the spring-data, so this is specific usecase regarding to the * specific states.
* rollout-management to find out actions which are not in specific states.
* <p/> * <p/>
* No access control applied * No access control applied
* *
* @param rollout the rollout the actions are belong to * @param rollout the rollout the actions are belong to
* @param rolloutGroup the rolloutgroup the actions are belong to * @param rolloutGroup the rollout group the actions are belong to
* @param statuses the list of statuses the action should not have * @param statuses the list of statuses the action should not have
* @return the count of actions referring the rollout and rolloutgroup and * @return the count of actions referring the rollout and rollout group and are not in given states
* are not in given states
*/ */
Long countByRolloutAndRolloutGroupAndStatusNotIn(JpaRollout rollout, JpaRolloutGroup rolloutGroup, Long countByRolloutAndRolloutGroupAndStatusNotIn(JpaRollout rollout, JpaRolloutGroup rolloutGroup, List<Status> statuses);
List<Status> statuses);
/** /**
* Counts all actions referring to a given rollout and rolloutgroup. * Counts all actions referring to a given rollout and rollout group.
* <p/> * <p/>
* No access control applied * No access control applied
* *
* @param rollout the rollout the actions belong to * @param rollout the rollout the actions belong to
* @param rolloutGroup the rolloutgroup the actions belong to * @param rolloutGroup the rollout group the actions belong to
* @return the count of actions referring to a rollout and rolloutgroup * @return the count of actions referring to a rollout and rollout group
*/ */
Long countByRolloutAndRolloutGroup(JpaRollout rollout, JpaRolloutGroup rolloutGroup); Long countByRolloutAndRolloutGroup(JpaRollout rollout, JpaRolloutGroup rolloutGroup);
/** /**
* Counts all actions referring to a given rollout, rolloutgroup and status. * Counts all actions referring to a given rollout, rollout group and status.
* <p/> * <p/>
* No access control applied * No access control applied
* *
* @param rolloutId the ID of rollout the actions belong to * @param rolloutId the ID of rollout the actions belong to
* @param rolloutGroupId the ID rolloutgroup the actions belong to * @param rolloutGroupId the ID rollout group the actions belong to
* @param status the status the actions should have * @param status the status the actions should have
* @return the count of actions referring to a rollout, rolloutgroup and are * @return the count of actions referring to a rollout, rollout group and are in a given status
* in a given status
*/ */
Long countByRolloutIdAndRolloutGroupIdAndStatus(Long rolloutId, Long rolloutGroupId, Action.Status status); Long countByRolloutIdAndRolloutGroupIdAndStatus(Long rolloutId, Long rolloutGroupId, Action.Status status);
@@ -189,50 +192,43 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction> {
Long countByRolloutIdAndStatus(Long rolloutId, Action.Status status); Long countByRolloutIdAndStatus(Long rolloutId, Action.Status status);
/** /**
* Returns {@code true} if actions for the given rollout exists, otherwise * Returns {@code true} if actions for the given rollout exists, otherwise {@code false}
* {@code false}
* <p/> * <p/>
* No access control applied * No access control applied
* *
* @param rolloutId the ID of the rollout the actions belong to * @param rolloutId the ID of the rollout the actions belong to
* @return {@code true} if actions for the given rollout exists, otherwise * @return {@code true} if actions for the given rollout exists, otherwise {@code false}
* {@code false}
*/ */
@Query("SELECT CASE WHEN COUNT(a)>0 THEN 'true' ELSE 'false' END FROM JpaAction a WHERE a.rollout.id=:rolloutId") @Query("SELECT CASE WHEN COUNT(a)>0 THEN 'true' ELSE 'false' END FROM JpaAction a WHERE a.rollout.id=:rolloutId")
boolean existsByRolloutId(@Param("rolloutId") Long rolloutId); boolean existsByRolloutId(@Param("rolloutId") Long rolloutId);
/** /**
* Returns {@code true} if actions for the given rollout exists, otherwise * Returns {@code true} if actions for the given rollout exists, otherwise {@code false}
* {@code false}
* <p/> * <p/>
* No access control applied * No access control applied
* *
* @param rolloutId the ID of the rollout the actions belong to * @param rolloutId the ID of the rollout the actions belong to
* @param status the action is not to be in * @param status the action is not to be in
* @return {@code true} if actions for the given rollout exists, otherwise * @return {@code true} if actions for the given rollout exists, otherwise {@code false}
* {@code false}
*/ */
@Query("SELECT CASE WHEN COUNT(a)>0 THEN 'true' ELSE 'false' END FROM JpaAction a WHERE a.rollout.id=:rolloutId AND a.status != :status") @Query("SELECT CASE WHEN COUNT(a)>0 THEN 'true' ELSE 'false' END FROM JpaAction a WHERE a.rollout.id=:rolloutId AND a.status != :status")
boolean existsByRolloutIdAndStatusNotIn(@Param("rolloutId") Long rolloutId, @Param("status") Status status); boolean existsByRolloutIdAndStatusNotIn(@Param("rolloutId") Long rolloutId, @Param("status") Status status);
/** /**
* Retrieving all actions referring to a given rollout with a specific action as * Retrieving all actions referring to a given rollout with a specific action as parent reference and a specific status.
* parent reference and a specific status.
* <p/> * <p/>
* Finding all actions of a specific rolloutgroup parent relation. * Finding all actions of a specific rollout group parent relation.
* <p/> * <p/>
* No access control applied * No access control applied
* *
* @param pageable page parameters * @param pageable page parameters
* @param rollout the rollout the actions belong to * @param rollout the rollout the actions belong to
* @param rolloutGroupParent the parent rolloutgroup the actions should reference * @param rolloutGroupParent the parent rollout group the actions should reference
* @param actionStatus the status the actions have * @param actionStatus the status the actions have
* @return the actions referring a specific rollout and a specific parent * @return the actions referring a specific rollout and a specific parent rolloutgroup in a specific status
* rolloutgroup in a specific status
*/ */
@EntityGraph(attributePaths = { "target", "target.autoConfirmationStatus", "rolloutGroup" }, type = EntityGraphType.LOAD) @EntityGraph(attributePaths = { "target", "target.autoConfirmationStatus", "rolloutGroup" }, type = EntityGraphType.LOAD)
Page<Action> findByRolloutIdAndRolloutGroupParentIdAndStatus(Pageable pageable, Long rollout, Page<Action> findByRolloutIdAndRolloutGroupParentIdAndStatus(Pageable pageable, Long rollout, Long rolloutGroupParent, Status actionStatus);
Long rolloutGroupParent, Status actionStatus);
/** /**
* Retrieving all actions referring to the first group of a rollout. * Retrieving all actions referring to the first group of a rollout.
@@ -242,13 +238,10 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction> {
* @param pageable page parameters * @param pageable page parameters
* @param rollout the rollout the actions belong to * @param rollout the rollout the actions belong to
* @param actionStatus the status the actions have * @param actionStatus the status the actions have
* @return the actions referring a specific rollout and a specific parent * @return the actions referring a specific rollout and a specific parent rolloutgroup in a specific status
* rolloutgroup in a specific status
*/ */
@EntityGraph(attributePaths = { "target", "target.autoConfirmationStatus", @EntityGraph(attributePaths = { "target", "target.autoConfirmationStatus", "rolloutGroup" }, type = EntityGraphType.LOAD)
"rolloutGroup" }, type = EntityGraphType.LOAD) Page<Action> findByRolloutIdAndRolloutGroupParentIsNullAndStatus(Pageable pageable, Long rollout, Status actionStatus);
Page<Action> findByRolloutIdAndRolloutGroupParentIsNullAndStatus(Pageable pageable, Long rollout,
Status actionStatus);
/** /**
* Retrieves all actions for a specific rollout and in a specific status. * Retrieves all actions for a specific rollout and in a specific status.
@@ -263,44 +256,40 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction> {
Page<JpaAction> findByRolloutIdAndStatus(Pageable pageable, Long rolloutId, Status actionStatus); Page<JpaAction> findByRolloutIdAndStatus(Pageable pageable, Long rolloutId, Status actionStatus);
/** /**
* Get list of objects which has details of status and count of targets in * Get list of objects which has details of status and count of targets in each status in specified rollout.
* each status in specified rollout.
* <p/> * <p/>
* No access control applied * No access control applied
* *
* @param rolloutId id of {@link Rollout} * @param rolloutId id of {@link Rollout}
* @return list of objects with status and target count * @return list of objects with status and target count
*/ */
@Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus( a.rollout.id, a.status , COUNT(a.id)) FROM JpaAction a WHERE a.rollout.id IN ?1 GROUP BY a.rollout.id,a.status") @Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus(a.rollout.id, a.status, COUNT(a.id)) FROM JpaAction a WHERE a.rollout.id = ?1 GROUP BY a.rollout.id,a.status")
List<TotalTargetCountActionStatus> getStatusCountByRolloutId(List<Long> rolloutId);
/**
* Get list of objects which has details of status and count of targets in
* each status in specified rollout.
* <p/>
* No access control applied
*
* @param rolloutId id of {@link Rollout}
* @return list of objects with status and target count
*/
@Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus( a.rollout.id, a.status , COUNT(a.id)) FROM JpaAction a WHERE a.rollout.id = ?1 GROUP BY a.rollout.id,a.status")
List<TotalTargetCountActionStatus> getStatusCountByRolloutId(Long rolloutId); List<TotalTargetCountActionStatus> getStatusCountByRolloutId(Long rolloutId);
/** /**
* Get list of objects which has details of status and count of targets in * Get list of objects which has details of status and count of targets in each status in specified rollout.
* each status in specified rollout group. * <p/>
* No access control applied
*
* @param rolloutId id of {@link Rollout}
* @return list of objects with status and target count
*/
@Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus(a.rollout.id, a.status, COUNT(a.id)) FROM JpaAction a WHERE a.rollout.id IN ?1 GROUP BY a.rollout.id,a.status")
List<TotalTargetCountActionStatus> getStatusCountByRolloutIds(List<Long> rolloutId);
/**
* Get list of objects which has details of status and count of targets in each status in specified rollout group.
* <p/> * <p/>
* No access control applied * No access control applied
* *
* @param rolloutGroupId id of {@link RolloutGroup} * @param rolloutGroupId id of {@link RolloutGroup}
* @return list of objects with status and target count * @return list of objects with status and target count
*/ */
@Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus(a.rolloutGroup.id, a.status , COUNT(a.id)) FROM JpaAction a WHERE a.rolloutGroup.id = ?1 GROUP BY a.rolloutGroup.id, a.status") @Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus(a.rolloutGroup.id, a.status, COUNT(a.id)) FROM JpaAction a WHERE a.rolloutGroup.id = ?1 GROUP BY a.rolloutGroup.id, a.status")
List<TotalTargetCountActionStatus> getStatusCountByRolloutGroupId(Long rolloutGroupId); List<TotalTargetCountActionStatus> getStatusCountByRolloutGroupId(Long rolloutGroupId);
/** /**
* Get list of objects which has details of status and count of targets in * Get list of objects which has details of status and count of targets in each status in specified rollout group.
* each status in specified rollout group.
* <p/> * <p/>
* No access control applied * No access control applied
* *
@@ -308,7 +297,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction> {
* @return list of objects with status and target count * @return list of objects with status and target count
*/ */
@Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus(a.rolloutGroup.id, a.status , COUNT(a.id)) FROM JpaAction a WHERE a.rolloutGroup.id IN ?1 GROUP BY a.rolloutGroup.id, a.status") @Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus(a.rolloutGroup.id, a.status , COUNT(a.id)) FROM JpaAction a WHERE a.rolloutGroup.id IN ?1 GROUP BY a.rolloutGroup.id, a.status")
List<TotalTargetCountActionStatus> getStatusCountByRolloutGroupId(List<Long> rolloutGroupId); List<TotalTargetCountActionStatus> getStatusCountByRolloutGroupIds(List<Long> rolloutGroupId);
/** /**
* Updates the externalRef of an action by its actionId. * Updates the externalRef of an action by its actionId.
@@ -331,4 +320,4 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction> {
// Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477 // Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477
@Query("DELETE FROM JpaAction a WHERE a.id IN ?1") @Query("DELETE FROM JpaAction a WHERE a.id IN ?1")
void deleteByIdIn(Collection<Long> actionIDs); void deleteByIdIn(Collection<Long> actionIDs);
} }

View File

@@ -52,13 +52,11 @@ public interface ActionStatusRepository extends BaseEntityRepository<JpaActionSt
* <p/> * <p/>
* No access control applied * No access control applied
* *
* @param pageable for page configuration
* @param actionId for which to get the status messages * @param actionId for which to get the status messages
* @param filter is the SQL like pattern to use for filtering out or excluding * @param filter is the SQL like pattern to use for filtering out or excluding the messages
* the messages * @param pageable for page configuration
* @return Page with found status messages. * @return Page with found status messages.
*/ */
@Query("SELECT message FROM JpaActionStatus actionstatus JOIN actionstatus.messages message WHERE actionstatus.action.id = :actionId AND message NOT LIKE :filter") @Query("SELECT message FROM JpaActionStatus actionstatus JOIN actionstatus.messages message WHERE actionstatus.action.id = :actionId AND message NOT LIKE :filter")
Page<String> findMessagesByActionIdAndMessageNotLike(Pageable pageable, @Param("actionId") Long actionId, Page<String> findMessagesByActionIdAndMessageNotLike(@Param("actionId") Long actionId, @Param("filter") String filter, Pageable pageable);
@Param("filter") String filter); }
}

View File

@@ -133,8 +133,7 @@ public class BaseEntityRepositoryACM<T extends AbstractJpaTenantAwareBaseEntity>
@Override @Override
public void deleteByTenant(final String tenant) { public void deleteByTenant(final String tenant) {
if (accessController.getAccessRules(AccessController.Operation.DELETE).isPresent()) { if (accessController.getAccessRules(AccessController.Operation.DELETE).isPresent()) {
throw new InsufficientPermissionException( throw new InsufficientPermissionException("DELETE operation has restriction for given context! deleteAll can't be executed!");
"DELETE operation has restriction for given context! deleteAll can't be executed!");
} }
repository.deleteByTenant(tenant); repository.deleteByTenant(tenant);
} }
@@ -185,8 +184,7 @@ public class BaseEntityRepositoryACM<T extends AbstractJpaTenantAwareBaseEntity>
} }
@Override @Override
public <S extends T, R> R findBy(final Specification<T> spec, public <S extends T, R> R findBy(final Specification<T> spec, final Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction) {
final Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction) {
return repository.findBy(accessController.appendAccessRules(AccessController.Operation.READ, spec), queryFunction); return repository.findBy(accessController.appendAccessRules(AccessController.Operation.READ, spec), queryFunction);
} }

View File

@@ -33,40 +33,37 @@ public interface SoftwareModuleMetadataRepository
JpaSpecificationExecutor<JpaSoftwareModuleMetadata> { JpaSpecificationExecutor<JpaSoftwareModuleMetadata> {
/** /**
* Locates the meta data entries that match the given software module ID and * Locates the meta-data entries that match the given software module ID and target visibility flag.
* target visibility flag.
* *
* @param page The pagination parameters.
* @param moduleId The ID of the software module. * @param moduleId The ID of the software module.
* @param targetVisible The target visibility flag. * @param targetVisible The target visibility flag.
* @return A {@link Page} with the matching meta data entries. * @param page The pagination parameters.
* @return A {@link Page} with the matching meta-data entries.
*/ */
Page<JpaSoftwareModuleMetadata> findBySoftwareModuleIdAndTargetVisible(Pageable page, Long moduleId, Page<JpaSoftwareModuleMetadata> findBySoftwareModuleIdAndTargetVisible(
boolean targetVisible); @Param("moduleId") Long moduleId, @Param("targetVisible") boolean targetVisible, Pageable page);
/** /**
* Locates the meta data entries that match the given software module IDs * Locates the meta-data entries that match the given software module IDs and target visibility flag.
* and target visibility flag.
* <p/> * <p/>
* No access control applied * No access control applied
* *
* @param page The pagination parameters.
* @param moduleId List of software module IDs. * @param moduleId List of software module IDs.
* @param targetVisible The target visibility flag. * @param targetVisible The target visibility flag.
* @return A {@link Page} with the matching meta data entries. * @param page The pagination parameters.
* @return A {@link Page} with the matching meta-data entries.
*/ */
@Query("SELECT smd.softwareModule.id, smd FROM JpaSoftwareModuleMetadata smd WHERE smd.softwareModule.id IN :moduleId AND smd.targetVisible = :targetVisible") @Query("SELECT smd.softwareModule.id, smd FROM JpaSoftwareModuleMetadata smd WHERE smd.softwareModule.id IN :moduleId AND smd.targetVisible = :targetVisible")
Page<Object[]> findBySoftwareModuleIdInAndTargetVisible(Pageable page, @Param("moduleId") Collection<Long> moduleId, Page<Object[]> findBySoftwareModuleIdInAndTargetVisible(
@Param("targetVisible") boolean targetVisible); @Param("moduleId") Collection<Long> moduleId, @Param("targetVisible") boolean targetVisible, Pageable page);
/** /**
* Counts the meta data entries that are associated with the addressed * Counts the meta-data entries that are associated with the addressed software module.
* software module.
* <p/> * <p/>
* No access control applied * No access control applied
* *
* @param moduleId The ID of the software module. * @param moduleId The ID of the software module.
* @return The number of meta data entries associated with the software module. * @return The number of meta-data entries associated with the software module.
*/ */
long countBySoftwareModuleId(@Param("moduleId") Long moduleId); long countBySoftwareModuleId(@Param("moduleId") Long moduleId);
} }

View File

@@ -12,7 +12,6 @@ package org.eclipse.hawkbit.repository.jpa.specifications;
import java.util.List; import java.util.List;
import jakarta.persistence.criteria.Join; import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.JoinType;
import jakarta.persistence.criteria.ListJoin; import jakarta.persistence.criteria.ListJoin;
import jakarta.persistence.criteria.SetJoin; import jakarta.persistence.criteria.SetJoin;
@@ -82,18 +81,13 @@ public final class ActionSpecifications {
* Fetches action's distribution set. * Fetches action's distribution set.
* *
* @param controllerId controller id * @param controllerId controller id
* @param isNull if <code>true</code> return with <code>null</code> weight, otherwise with non-<code>null</code>
* @return the matching action s. * @return the matching action s.
*/ */
public static Specification<JpaAction> byTargetControllerIdAndActiveAndWeightIsNullFetchDS(final String controllerId, public static Specification<JpaAction> byTargetControllerIdAndActive(final String controllerId) {
final boolean isNull) { return (root, query, cb) ->
return (root, query, cb) -> { cb.and(
root.fetch(JpaAction_.distributionSet, JoinType.LEFT);
return cb.and(
cb.equal(root.get(JpaAction_.target).get(JpaTarget_.controllerId), controllerId), cb.equal(root.get(JpaAction_.target).get(JpaTarget_.controllerId), controllerId),
cb.equal(root.get(JpaAction_.active), true), cb.equal(root.get(JpaAction_.active), true));
isNull ? cb.isNull(root.get(JpaAction_.weight)) : cb.isNotNull(root.get(JpaAction_.weight)));
};
} }
public static Specification<JpaAction> byDistributionSetId(final Long distributionSetId) { public static Specification<JpaAction> byDistributionSetId(final Long distributionSetId) {
@@ -158,4 +152,4 @@ public final class ActionSpecifications {
criteriaBuilder.equal(actionRoot.get(JpaAction_.target).get(JpaTarget_.id), targetId)); criteriaBuilder.equal(actionRoot.get(JpaAction_.target).get(JpaTarget_.id), targetId));
}; };
} }
} }

View File

@@ -967,38 +967,29 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
final Long actionId = createTargetAndAssignDs(); final Long actionId = createTargetAndAssignDs();
// test and verify // test and verify
controllerManagement controllerManagement.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.RUNNING));
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.RUNNING)); assertActionStatus(actionId, DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING, Action.Status.RUNNING, Action.Status.RUNNING, true);
assertActionStatus(actionId, DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING, Action.Status.RUNNING,
Action.Status.RUNNING, true);
controllerManagement controllerManagement.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.ERROR));
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.ERROR)); assertActionStatus(actionId, DEFAULT_CONTROLLER_ID, TargetUpdateStatus.ERROR, Action.Status.ERROR, Action.Status.ERROR, false);
assertActionStatus(actionId, DEFAULT_CONTROLLER_ID, TargetUpdateStatus.ERROR, Action.Status.ERROR,
Action.Status.ERROR, false);
// try with disabled late feedback // try with disabled late feedback
repositoryProperties.setRejectActionStatusForClosedAction(true); repositoryProperties.setRejectActionStatusForClosedAction(true);
controllerManagement controllerManagement.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED));
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED));
// test // test
assertActionStatus(actionId, DEFAULT_CONTROLLER_ID, TargetUpdateStatus.ERROR, Action.Status.ERROR, assertActionStatus(actionId, DEFAULT_CONTROLLER_ID, TargetUpdateStatus.ERROR, Action.Status.ERROR, Action.Status.ERROR, false);
Action.Status.ERROR, false);
// try with enabled late feedback - should not make a difference as it // try with enabled late feedback - should not make a difference as it
// only allows intermediate feedback and not multiple close // only allows intermediate feedback and not multiple close
repositoryProperties.setRejectActionStatusForClosedAction(false); repositoryProperties.setRejectActionStatusForClosedAction(false);
controllerManagement controllerManagement.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED));
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED));
// test // test
assertActionStatus(actionId, DEFAULT_CONTROLLER_ID, TargetUpdateStatus.ERROR, Action.Status.ERROR, assertActionStatus(actionId, DEFAULT_CONTROLLER_ID, TargetUpdateStatus.ERROR, Action.Status.ERROR, Action.Status.ERROR, false);
Action.Status.ERROR, false);
assertThat(actionStatusRepository.count()).isEqualTo(3); assertThat(actionStatusRepository.count()).isEqualTo(3);
assertThat(controllerManagement.findActionStatusByAction(PAGE, actionId).getNumberOfElements()).isEqualTo(3); assertThat(controllerManagement.findActionStatusByAction(PAGE, actionId).getNumberOfElements()).isEqualTo(3);
} }
@Test @Test
@@ -1716,27 +1707,24 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
actionStatus, true); actionStatus, true);
} }
private void assertActionStatus(final Long actionId, final String controllerId, private void assertActionStatus(
final Long actionId, final String controllerId,
final TargetUpdateStatus expectedTargetUpdateStatus, final Action.Status expectedActionActionStatus, final TargetUpdateStatus expectedTargetUpdateStatus, final Action.Status expectedActionActionStatus,
final Action.Status expectedActionStatus, final boolean actionActive) { final Action.Status expectedActionStatus, final boolean actionActive) {
final TargetUpdateStatus targetStatus = targetManagement.getByControllerID(controllerId).get() final TargetUpdateStatus targetStatus = targetManagement.getByControllerID(controllerId).get().getUpdateStatus();
.getUpdateStatus();
assertThat(targetStatus).isEqualTo(expectedTargetUpdateStatus); assertThat(targetStatus).isEqualTo(expectedTargetUpdateStatus);
final Action action = deploymentManagement.findAction(actionId).get(); final Action action = deploymentManagement.findAction(actionId).get();
assertThat(action.getStatus()).isEqualTo(expectedActionActionStatus); assertThat(action.getStatus()).isEqualTo(expectedActionActionStatus);
assertThat(action.isActive()).isEqualTo(actionActive); assertThat(action.isActive()).isEqualTo(actionActive);
final List<ActionStatus> actionStatusList = controllerManagement.findActionStatusByAction(PAGE, actionId) final List<ActionStatus> actionStatusList = controllerManagement.findActionStatusByAction(PAGE, actionId).getContent();
.getContent();
assertThat(actionStatusList.get(actionStatusList.size() - 1).getStatus()).isEqualTo(expectedActionStatus); assertThat(actionStatusList.get(actionStatusList.size() - 1).getStatus()).isEqualTo(expectedActionStatus);
if (actionActive) { if (actionActive) {
assertThat(controllerManagement.findActiveActionWithHighestWeight(controllerId).get().getId()) assertThat(controllerManagement.findActiveActionWithHighestWeight(controllerId).get().getId()).isEqualTo(actionId);
.isEqualTo(actionId);
} }
} }
private void createTargetType(String targetTypeName) { private void createTargetType(String targetTypeName) {
systemSecurityContext.runAsSystem(() -> systemSecurityContext.runAsSystem(() -> targetTypeManagement.create(entityFactory.targetType().create().name(targetTypeName)));
targetTypeManagement.create(entityFactory.targetType().create().name(targetTypeName)));
} }
@Step @Step

View File

@@ -72,6 +72,12 @@ public class EventVerifier extends AbstractTestExecutionListener {
@Override @Override
public void afterTestMethod(final TestContext testContext) { public void afterTestMethod(final TestContext testContext) {
if (testContext.getTestException() != null) {
// test has failed anyway
// not expected event set could be result of failed test / incomplete steps - no need to check and mess up with real exception
return;
}
final Optional<Expect[]> expectedEvents = getExpectationsFrom(testContext.getTestMethod()); final Optional<Expect[]> expectedEvents = getExpectationsFrom(testContext.getTestMethod());
try { try {
expectedEvents.ifPresent(this::afterTest); expectedEvents.ifPresent(this::afterTest);