Performance optimizations for Multi-Assignments (#858)

* Add remote event test for the new MultiActionEvent
* Improve test descriptions
* Improve sendMultiActionRequestMessages
* Moved action filtering to the database query level (#12)
* Use @ExpectedEvents instead of EventHandlerStubs
* Removed @Param from 'existsByTargetControllerIdAndStatusAndActiveIsTrue'
* Reverted metadata initialization
* Fix hawkBit bot findings

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>
Signed-off-by: Ahmed Sayed <ahmed.sayed@bosch-si.com>
This commit is contained in:
Stefan Behl
2019-07-19 14:47:29 +02:00
committed by GitHub
parent 749218098f
commit 4e9308a949
15 changed files with 133 additions and 243 deletions

View File

@@ -16,6 +16,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.api.ApiType;
@@ -65,8 +66,6 @@ import org.springframework.context.event.EventListener;
import org.springframework.data.domain.PageRequest;
import org.springframework.util.CollectionUtils;
import com.google.common.collect.Maps;
/**
* {@link AmqpMessageDispatcherService} create all outgoing AMQP messages and
* delegate the messages to a {@link AmqpMessageSenderService}.
@@ -172,7 +171,7 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
protected void sendMultiActionRequestMessages(final String tenant, final List<String> controllerIds) {
final Map<Long, Map<SoftwareModule, List<SoftwareModuleMetadata>>> softwareModuleMetadata = new HashMap<>();
final Map<SoftwareModule, List<SoftwareModuleMetadata>> softwareModuleMetadata = new HashMap<>();
targetManagement.getByControllerID(controllerIds).stream()
.filter(target -> IpUtil.isAmqpUri(target.getAddress())).forEach(target -> {
@@ -180,14 +179,13 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
.findActiveActionsByTarget(PageRequest.of(0, MAX_ACTION_COUNT), target.getControllerId())
.getContent();
activeActions.forEach(action -> {
final DistributionSet distributionSet = action.getDistributionSet();
softwareModuleMetadata.computeIfAbsent(distributionSet.getId(),
id -> getSoftwareModulesWithMetadata(distributionSet));
});
activeActions.forEach(action -> action.getDistributionSet().getModules().forEach(
module -> softwareModuleMetadata.computeIfAbsent(module, this::getSoftwareModuleMetadata)));
if (!activeActions.isEmpty()) {
sendMultiActionRequestToTarget(tenant, target, activeActions, softwareModuleMetadata);
sendMultiActionRequestToTarget(tenant, target, activeActions,
action -> action.getDistributionSet().getModules().stream()
.collect(Collectors.toMap(m -> m, softwareModuleMetadata::get)));
}
});
@@ -195,7 +193,7 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
}
protected void sendMultiActionRequestToTarget(final String tenant, final Target target, final List<Action> actions,
final Map<Long, Map<SoftwareModule, List<SoftwareModuleMetadata>>> softwareModulesPerDistributionSet) {
final Function<Action, Map<SoftwareModule, List<SoftwareModuleMetadata>>> getSoftwareModuleMetaData) {
final URI targetAdress = target.getAddress();
if (!IpUtil.isAmqpUri(targetAdress) || CollectionUtils.isEmpty(actions)) {
@@ -205,7 +203,7 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
final DmfMultiActionRequest multiActionRequest = new DmfMultiActionRequest();
actions.forEach(action -> {
final DmfActionRequest actionRequest = createDmfActionRequest(target, action,
softwareModulesPerDistributionSet.get(action.getDistributionSet().getId()));
getSoftwareModuleMetaData.apply(action));
multiActionRequest.addElement(getEventTypeForAction(action), actionRequest);
});
@@ -471,15 +469,12 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
private Map<SoftwareModule, List<SoftwareModuleMetadata>> getSoftwareModulesWithMetadata(
final DistributionSet distributionSet) {
final Map<SoftwareModule, List<SoftwareModuleMetadata>> moduleMetadata = Maps
.newHashMapWithExpectedSize(distributionSet.getModules().size());
distributionSet.getModules()
.forEach(
module -> moduleMetadata.put(module,
softwareModuleManagement.findMetaDataBySoftwareModuleIdAndTargetVisible(
PageRequest.of(0, RepositoryConstants.MAX_META_DATA_COUNT), module.getId())
.getContent()));
return moduleMetadata;
return distributionSet.getModules().stream().collect(Collectors.toMap(m -> m, this::getSoftwareModuleMetadata));
}
private List<SoftwareModuleMetadata> getSoftwareModuleMetadata(final SoftwareModule module) {
return softwareModuleManagement.findMetaDataBySoftwareModuleIdAndTargetVisible(
PageRequest.of(0, RepositoryConstants.MAX_META_DATA_COUNT), module.getId()).getContent();
}
}

View File

@@ -227,7 +227,7 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
.stream().collect(Collectors.toMap(DistributionSet::getId, this::getSoftwareModulesWithMetadata));
amqpMessageDispatcherService.sendMultiActionRequestToTarget(target.getTenant(), target, actions,
softwareModulesPerDistributionSet);
action -> softwareModulesPerDistributionSet.get(action.getDistributionSet().getId()));
}
private void sendOldestActionToTarget(final Target target) {

View File

@@ -135,21 +135,21 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
@Test
@Description("Verify that a distribution assignment multiple times send cancel and assign events with right softwaremodules")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 2),
@Expect(type = CancelTargetAssignmentEvent.class, count = 1),
@Expect(type = ActionCreatedEvent.class, count = 2), @Expect(type = ActionUpdatedEvent.class, count = 1),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 6),
@Expect(type = SoftwareModuleUpdatedEvent.class, count = 6),
@Expect(type = SoftwareModuleUpdatedEvent.class, count = 12),
@Expect(type = DistributionSetCreatedEvent.class, count = 2),
@Expect(type = TargetUpdatedEvent.class, count = 2), @Expect(type = TargetPollEvent.class, count = 3) })
@Expect(type = TargetUpdatedEvent.class, count = 2), @Expect(type = TargetPollEvent.class, count = 2) })
public void assignDistributionSetMultipleTimes() {
final String controllerId = TARGET_PREFIX + "assignDistributionSetMultipleTimes";
final DistributionSetAssignmentResult assignmentResult = registerTargetAndAssignDistributionSet(controllerId);
final DistributionSet distributionSet2 = testdataFactory.createDistributionSet(UUID.randomUUID().toString());
registerTargetAndAssignDistributionSet(distributionSet2.getId(), TargetUpdateStatus.PENDING,
getDistributionSet().getModules(), controllerId);
testdataFactory.addSoftwareModuleMetadata(distributionSet2);
assignDistributionSet(distributionSet2.getId(), controllerId);
assertDownloadAndInstallMessage(distributionSet2.getModules(), controllerId);
assertCancelActionMessage(assignmentResult.getActionIds().get(0), controllerId);
createAndSendThingCreated(controllerId, TENANT_EXIST);

View File

@@ -57,50 +57,27 @@ public final class SoftwareModuleJsonMatcher {
this.expectedModules = expectedModules;
}
static boolean containsExactly(final Object actual, final List<DmfSoftwareModule> expected) {
boolean containsExactly(final Object actual) {
if (actual == null) {
return expected == null;
return expectedModules == null;
}
@SuppressWarnings("unchecked")
final Collection<SoftwareModule> modules = (Collection<SoftwareModule>) actual;
if (modules.size() != expected.size()) {
return false;
return expectedModules.stream().allMatch(e -> existsIn(e, modules));
}
for (final SoftwareModule repoSoftwareModule : modules) {
boolean containsElement = false;
for (final DmfSoftwareModule jsonSoftwareModule : expected) {
if (!jsonSoftwareModule.getModuleId().equals(repoSoftwareModule.getId())) {
continue;
}
containsElement = true;
if (!jsonSoftwareModule.getModuleType().equals(repoSoftwareModule.getType().getKey())) {
return false;
}
if (!jsonSoftwareModule.getModuleVersion().equals(repoSoftwareModule.getVersion())) {
return false;
}
if (jsonSoftwareModule.getArtifacts().size() != repoSoftwareModule.getArtifacts().size()) {
return false;
}
}
if (!containsElement) {
return false;
}
}
return true;
private static boolean existsIn(final DmfSoftwareModule module, final Collection<SoftwareModule> actual) {
return actual.stream()
.anyMatch(e -> module.getModuleType().equals(e.getType().getKey())
&& module.getModuleVersion().equals(e.getVersion())
&& module.getArtifacts().size() == e.getArtifacts().size());
}
@Override
public boolean matches(final Object actualValue) {
return containsExactly(actualValue, expectedModules);
return containsExactly(actualValue);
}
@Override

View File

@@ -79,4 +79,8 @@ public class DmfMetadata {
return true;
}
@Override
public String toString() {
return String.format("DmfMetadata [key='%s', value='%s']", key, value);
}
}

View File

@@ -83,4 +83,11 @@ public class DmfSoftwareModule {
}
}
@Override
public String toString() {
return String.format(
"DmfSoftwareModule [moduleId=%d, moduleType='%s', moduleVersion='%s', artifacts=%s, metadata=%s]",
moduleId, moduleType, moduleVersion, artifacts, metadata);
}
}

View File

@@ -127,6 +127,20 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
@Query("SELECT CASE WHEN COUNT(a)>0 THEN 'true' ELSE 'false' END FROM JpaAction a JOIN a.target t WHERE t.controllerId=:controllerId AND a.active=1")
boolean activeActionExistsForControllerId(@Param("controllerId") String controllerId);
/**
* Check if any active actions with given action status and given controller
* ID exist.
*
* @param controllerId
* of the target to check for actions
* @param currentStatus
* of the active action to look for
*
* @return <code>true</code> if one or more active actions for the given
* controllerId and action status are found
*/
boolean existsByTargetControllerIdAndStatusAndActiveIsTrue(String controllerId, Action.Status currentStatus);
/**
* Retrieves latest {@link Action} for given target and
* {@link SoftwareModule}.

View File

@@ -8,8 +8,6 @@
*/
package org.eclipse.hawkbit.repository.jpa;
import static org.eclipse.hawkbit.repository.RepositoryConstants.MAX_ACTION_COUNT;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -39,8 +37,6 @@ import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.model.TargetWithActionType;
import org.springframework.cloud.bus.BusProperties;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.util.CollectionUtils;
import com.google.common.collect.Lists;
@@ -51,8 +47,6 @@ import com.google.common.collect.Lists;
*/
public class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
private static final Pageable ACTION_PAGE_REQUEST = PageRequest.of(0, MAX_ACTION_COUNT);
private final Supplier<Boolean> multiAssignmentsConfig;
OnlineDsAssignmentStrategy(final TargetRepository targetRepository,
@@ -196,8 +190,8 @@ public class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
}
private boolean hasPendingCancellations(final Target target) {
return actionRepository.findByActiveAndTarget(ACTION_PAGE_REQUEST, target.getControllerId(), true).getContent()
.stream().anyMatch(action -> action.getStatus() == Status.CANCELING);
return actionRepository.existsByTargetControllerIdAndStatusAndActiveIsTrue(target.getControllerId(),
Status.CANCELING);
}
/**

View File

@@ -79,13 +79,15 @@ public abstract class AbstractRemoteEventTest extends AbstractJpaIntegrationTest
return busProtoStuffMessageConverter.toMessage(event, new MessageHeaders(headers));
}
protected TenantAwareEvent createJacksonEvent(final TenantAwareEvent event) {
final Message<?> message = createJsonMessage(event);
return (TenantAwareEvent) jacksonMessageConverter.fromMessage(message, event.getClass());
@SuppressWarnings("unchecked")
protected <T extends TenantAwareEvent> T createJacksonEvent(final T event) {
final Message<String> message = createJsonMessage(event);
return (T) jacksonMessageConverter.fromMessage(message, event.getClass());
}
protected TenantAwareEvent createProtoStuffEvent(final TenantAwareEvent event) {
@SuppressWarnings("unchecked")
protected <T extends TenantAwareEvent> T createProtoStuffEvent(final T event) {
final Message<?> message = createProtoStuffMessage(event);
return (TenantAwareEvent) busProtoStuffMessageConverter.fromMessage(message, event.getClass());
return (T) busProtoStuffMessageConverter.fromMessage(message, event.getClass());
}
}

View File

@@ -94,10 +94,10 @@ public class RemoteIdEventTest extends AbstractRemoteEventTest {
protected void assertEntity(final RemoteIdEvent event) {
assertThat(event.getEntityId()).isSameAs(ENTITY_ID);
final RemoteIdEvent protoStuffEvent = (RemoteIdEvent) createProtoStuffEvent(event);
final RemoteIdEvent protoStuffEvent = createProtoStuffEvent(event);
assertDeserializeEvent(protoStuffEvent, event);
final RemoteIdEvent jacksonEvent = (RemoteIdEvent) createJacksonEvent(event);
final RemoteIdEvent jacksonEvent = createJacksonEvent(event);
assertDeserializeEvent(jacksonEvent, event);
}

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.event.remote;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Arrays;
import java.util.List;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.model.Action;
@@ -18,7 +19,6 @@ import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionProperties;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Target;
import org.junit.Test;
import io.qameta.allure.Description;
@@ -29,47 +29,71 @@ import io.qameta.allure.Story;
@Story("RemoteTenantAwareEvent Tests")
public class RemoteTenantAwareEventTest extends AbstractRemoteEventTest {
private static final String TENANT_DEFAULT = "DEFAULT";
private static final String APPLICATION_ID_DEFAULT = "Node";
@Test
@Description("Verifies that the download progress reloading by remote events works")
public void reloadDownloadProgessByRemoteEvent() {
final DownloadProgressEvent downloadProgressEvent = new DownloadProgressEvent("DEFAULT", 1L, 3L, "Node");
@Description("Verifies that a MultiActionEvent can be properly serialized and deserialized")
public void testMultiActionEvent() {
DownloadProgressEvent remoteEvent = (DownloadProgressEvent) createProtoStuffEvent(downloadProgressEvent);
assertThat(downloadProgressEvent).isEqualTo(remoteEvent);
final List<String> controllerIds = Arrays.asList("id0", "id1", "id2", "id3",
"id4loooooooooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnng");
final MultiActionEvent event = new MultiActionEvent(TENANT_DEFAULT, APPLICATION_ID_DEFAULT, controllerIds);
final MultiActionEvent remoteEventProtoStuff = createProtoStuffEvent(event);
assertThat(event).isEqualTo(remoteEventProtoStuff);
assertThat(remoteEventProtoStuff.getControllerIds()).containsExactlyElementsOf(controllerIds);
final MultiActionEvent remoteEventJackson = createJacksonEvent(event);
assertThat(event).isEqualTo(remoteEventJackson);
assertThat(remoteEventJackson.getControllerIds()).containsExactlyElementsOf(controllerIds);
remoteEvent = (DownloadProgressEvent) createJacksonEvent(downloadProgressEvent);
assertThat(downloadProgressEvent).isEqualTo(remoteEvent);
}
@Test
@Description("Verifies that target assignment event works")
@Description("Verifies that a DownloadProgressEvent can be properly serialized and deserialized")
public void reloadDownloadProgessByRemoteEvent() {
final DownloadProgressEvent downloadProgressEvent = new DownloadProgressEvent(TENANT_DEFAULT, 1L, 3L,
APPLICATION_ID_DEFAULT);
final DownloadProgressEvent remoteEventProtoStuff = createProtoStuffEvent(downloadProgressEvent);
assertThat(downloadProgressEvent).isEqualTo(remoteEventProtoStuff);
final DownloadProgressEvent remoteEventJackson = createJacksonEvent(downloadProgressEvent);
assertThat(downloadProgressEvent).isEqualTo(remoteEventJackson);
}
@Test
@Description("Verifies that a TargetAssignDistributionSetEvent can be properly serialized and deserialized")
public void testTargetAssignDistributionSetEvent() {
final DistributionSet dsA = testdataFactory.createDistributionSet("");
final JpaAction generateAction = new JpaAction();
generateAction.setActionType(ActionType.FORCED);
final Target target = testdataFactory.createTarget("Test");
generateAction.setTarget(target);
generateAction.setTarget(testdataFactory.createTarget("Test"));
generateAction.setDistributionSet(dsA);
generateAction.setStatus(Status.RUNNING);
final Action action = actionRepository.save(generateAction);
final TargetAssignDistributionSetEvent assignmentEvent = new TargetAssignDistributionSetEvent(
action.getTenant(), dsA.getId(), Arrays.asList(action), serviceMatcher.getServiceId(),
action.isMaintenanceWindowAvailable());
TargetAssignDistributionSetEvent underTest = (TargetAssignDistributionSetEvent) createProtoStuffEvent(
assignmentEvent);
assertTargetAssignDistributionSetEvent(action, underTest);
final TargetAssignDistributionSetEvent remoteEventProtoStuff = createProtoStuffEvent(assignmentEvent);
assertTargetAssignDistributionSetEvent(action, remoteEventProtoStuff);
underTest = (TargetAssignDistributionSetEvent) createJacksonEvent(assignmentEvent);
assertTargetAssignDistributionSetEvent(action, underTest);
final TargetAssignDistributionSetEvent remoteEventJackson = createJacksonEvent(assignmentEvent);
assertTargetAssignDistributionSetEvent(action, remoteEventJackson);
}
private void assertTargetAssignDistributionSetEvent(final Action action,
final TargetAssignDistributionSetEvent underTest) {
assertThat(underTest.getActions().size()).isEqualTo(1);
ActionProperties actionProperties = underTest.getActions().get(action.getTarget().getControllerId());
final ActionProperties actionProperties = underTest.getActions().get(action.getTarget().getControllerId());
assertThat(actionProperties).isNotNull();
assertThat(actionProperties).isEqualToComparingFieldByField(new ActionProperties(action));
assertThat(underTest.getDistributionSetId()).isEqualTo(action.getDistributionSet().getId());

View File

@@ -57,10 +57,10 @@ public abstract class AbstractRemoteEntityEventTest<E> extends AbstractRemoteEve
protected RemoteEntityEvent<?> assertEntity(final E baseEntity, final RemoteEntityEvent<?> event) {
assertThat(event.getEntity()).isSameAs(baseEntity);
RemoteEntityEvent<?> underTestCreatedEvent = (RemoteEntityEvent<?>) createProtoStuffEvent(event);
RemoteEntityEvent<?> underTestCreatedEvent = createProtoStuffEvent(event);
assertThat(underTestCreatedEvent.getEntity()).isEqualTo(baseEntity);
underTestCreatedEvent = (RemoteEntityEvent<?>) createJacksonEvent(event);
underTestCreatedEvent = createJacksonEvent(event);
assertThat(underTestCreatedEvent.getEntity()).isEqualTo(baseEntity);
return underTestCreatedEvent;
}

View File

@@ -74,12 +74,12 @@ public class ActionEventTest extends AbstractRemoteEntityEventTest<Action> {
assertThat(event.getEntity()).isSameAs(baseEntity);
assertThat(event.getRolloutId()).isEqualTo(1L);
AbstractActionEvent underTestCreatedEvent = (AbstractActionEvent) createProtoStuffEvent(event);
AbstractActionEvent underTestCreatedEvent = createProtoStuffEvent(event);
assertThat(underTestCreatedEvent.getEntity()).isEqualTo(baseEntity);
assertThat(underTestCreatedEvent.getRolloutId()).isEqualTo(1L);
assertThat(underTestCreatedEvent.getRolloutGroupId()).isEqualTo(2L);
underTestCreatedEvent = (AbstractActionEvent) createJacksonEvent(event);
underTestCreatedEvent = createJacksonEvent(event);
assertThat(underTestCreatedEvent.getEntity()).isEqualTo(baseEntity);
assertThat(underTestCreatedEvent.getRolloutId()).isEqualTo(1L);
assertThat(underTestCreatedEvent.getRolloutGroupId()).isEqualTo(2L);

View File

@@ -76,11 +76,11 @@ public class RolloutGroupEventTest extends AbstractRemoteEntityEventTest<Rollout
assertThat(event.getEntity()).isSameAs(baseEntity);
assertThat(event.getRolloutId()).isEqualTo(1L);
AbstractRolloutGroupEvent underTestCreatedEvent = (AbstractRolloutGroupEvent) createProtoStuffEvent(event);
AbstractRolloutGroupEvent underTestCreatedEvent = createProtoStuffEvent(event);
assertThat(underTestCreatedEvent.getEntity()).isEqualTo(baseEntity);
assertThat(underTestCreatedEvent.getRolloutId()).isEqualTo(1L);
underTestCreatedEvent = (AbstractRolloutGroupEvent) createJacksonEvent(event);
underTestCreatedEvent = createJacksonEvent(event);
assertThat(underTestCreatedEvent.getEntity()).isEqualTo(baseEntity);
assertThat(underTestCreatedEvent.getRolloutId()).isEqualTo(1L);

View File

@@ -17,10 +17,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@@ -31,6 +28,7 @@ import org.eclipse.hawkbit.repository.event.remote.entity.ActionCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.ActionUpdatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.CancelTargetAssignmentEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetUpdatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
@@ -48,7 +46,6 @@ import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionProperties;
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
@@ -60,11 +57,7 @@ import org.eclipse.hawkbit.repository.model.TargetWithActionType;
import org.eclipse.hawkbit.repository.test.matcher.Expect;
import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Slice;
@@ -90,22 +83,6 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
private static final boolean STATE_ACTIVE = true;
private static final boolean STATE_INACTIVE = false;
private EventHandlerStub eventHandlerStub;
private CancelEventHandlerStub cancelEventHandlerStub;
@Autowired
private ConfigurableApplicationContext applicationContext;
@Before
public void addHandler() {
eventHandlerStub = new EventHandlerStub();
applicationContext.addApplicationListener(eventHandlerStub);
cancelEventHandlerStub = new CancelEventHandlerStub();
applicationContext.addApplicationListener(cancelEventHandlerStub);
}
@Test
@Description("Verifies that management get access react as specfied on calls for non existing entities by means "
+ "of Optional not present.")
@@ -276,7 +253,7 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
@Description("Test verifies that an assignment with automatic cancelation works correctly even if the update is split into multiple partitions on the database.")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = Constants.MAX_ENTRIES_IN_STATEMENT + 10),
@Expect(type = TargetUpdatedEvent.class, count = 2 * (Constants.MAX_ENTRIES_IN_STATEMENT + 10)),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 2),
@Expect(type = ActionCreatedEvent.class, count = 2 * (Constants.MAX_ENTRIES_IN_STATEMENT + 10)),
@Expect(type = CancelTargetAssignmentEvent.class, count = Constants.MAX_ENTRIES_IN_STATEMENT + 10),
@Expect(type = ActionUpdatedEvent.class, count = Constants.MAX_ENTRIES_IN_STATEMENT + 10),
@@ -581,12 +558,15 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
* {@link TargetRepository#assignDistributionSet(DistributionSet, Iterable)}
* and checking the active action and the action history of the targets.
*
* @throws InterruptedException
*/
@Test
@Description("Simple deployment or distribution set to target assignment test.")
public void assignDistributionSet2Targets() throws InterruptedException {
eventHandlerStub.setExpectedNumberOfEvents(1);
@ExpectEvents({@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
@Expect(type = TargetCreatedEvent.class, count = 30), @Expect(type = ActionCreatedEvent.class, count = 20),
@Expect(type = TargetUpdatedEvent.class, count = 20),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3)})
public void assignDistributionSet2Targets() {
final String myCtrlIDPref = "myCtrlID";
final Iterable<Target> savedNakedTargets = testdataFactory.createTargets(10, myCtrlIDPref, "first description");
@@ -631,17 +611,19 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
assertThat(ua.getDistributionSet()).as("action has wrong ds").isEqualTo(ds);
}
}
final List<TargetAssignDistributionSetEvent> events = eventHandlerStub.getEvents(10, TimeUnit.SECONDS);
assertTargetAssignDistributionSetEvents(savedDeployedTargets, ds, events);
}
@Test
@Description("Test that it is not possible to assign a distribution set that is not complete.")
public void failDistributionSetAssigmentThatIsNotComplete() throws InterruptedException {
eventHandlerStub.setExpectedNumberOfEvents(0);
@ExpectEvents({@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
@Expect(type = TargetCreatedEvent.class, count = 10), @Expect(type = ActionCreatedEvent.class, count = 10),
@Expect(type = TargetUpdatedEvent.class, count = 10),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 2),
@Expect(type = DistributionSetUpdatedEvent.class, count = 1)
})
public void failDistributionSetAssigmentThatIsNotComplete() throws InterruptedException {
final List<Target> targets = testdataFactory.createTargets(10);
final SoftwareModule ah = testdataFactory.createSoftwareModuleApp();
@@ -656,20 +638,11 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
} catch (final IncompleteDistributionSetException ex) {
}
// give some chance to receive events asynchronously
Thread.sleep(1L);
final List<TargetAssignDistributionSetEvent> events = eventHandlerStub.getEvents(5, TimeUnit.SECONDS);
assertThat(events).as("events should be empty").isEmpty();
final DistributionSet nowComplete = distributionSetManagement.assignSoftwareModules(incomplete.getId(),
Sets.newHashSet(os.getId()));
eventHandlerStub.setExpectedNumberOfEvents(1);
assertThat(assignDistributionSet(nowComplete, targets).getAssigned()).as("assign ds doesn't work")
.isEqualTo(10);
assertTargetAssignDistributionSetEvents(targets, nowComplete, eventHandlerStub.getEvents(15, TimeUnit.SECONDS));
}
@Test
@@ -682,25 +655,15 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
@Expect(type = CancelTargetAssignmentEvent.class, count = 4 * 2),
@Expect(type = DistributionSetCreatedEvent.class, count = 3),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 9),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1) })
@Expect(type = TargetAssignDistributionSetEvent.class, count = 2)})
public void mutipleDeployments() throws InterruptedException {
final String undeployedTargetPrefix = "undep-T";
final int noOfUndeployedTargets = 5;
final String deployedTargetPrefix = "dep-T";
final int noOfDeployedTargets = 4;
final int noOfDistributionSets = 3;
// One assigment per DS
final int expectedNumberOfEventsForAssignment = 1;
eventHandlerStub.setExpectedNumberOfEvents(expectedNumberOfEventsForAssignment);
// Each of the four targets get two more assignment the which are
// cancelled (4 * 2 = 8)
final int expectedNumberOfEventsForCancel = 8;
cancelEventHandlerStub.setExpectedNumberOfEvents(expectedNumberOfEventsForCancel);
final DeploymentResult deploymentResult = prepareComplexRepo(undeployedTargetPrefix, noOfUndeployedTargets,
deployedTargetPrefix, noOfDeployedTargets, noOfDistributionSets, "myTestDS");
@@ -739,13 +702,6 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
.doesNotContain(Iterables.toArray(undeployedTargetsFromDB, JpaTarget.class));
assertThat(undeployedTargetsFromDB).as("content of undeployed target is wrong").containsAll(savedNakedTargets)
.doesNotContain(Iterables.toArray(deployedTargetsFromDB, JpaTarget.class));
// For each of the 4 targets 1 distribution sets gets assigned
eventHandlerStub.getEvents(10, TimeUnit.SECONDS);
// For each of the 4 targets 2 distribution sets gets cancelled
cancelEventHandlerStub.getEvents(10, TimeUnit.SECONDS);
}
@Test
@@ -1111,22 +1067,6 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
}
private void assertTargetAssignDistributionSetEvents(final List<Target> targets, final DistributionSet ds,
final List<TargetAssignDistributionSetEvent> events) {
assertThat(events).hasSize(1);
final TargetAssignDistributionSetEvent event = events.get(0);
assertThat(event).isNotNull();
assertThat(event.getDistributionSetId()).isEqualTo(ds.getId());
final List<Long> eventActionIds = event.getActions().values().stream().map(ActionProperties::getId)
.collect(Collectors.toList());
final List<Long> targetActiveActionIds = targets.stream()
.map(t -> deploymentManagement.findActiveActionsByTarget(PAGE, t.getControllerId()).getContent())
.flatMap(List::stream).map(Action::getId).collect(Collectors.toList());
assertThat(eventActionIds).containsOnlyElementsOf(targetActiveActionIds);
}
private class DeploymentResult {
final List<Long> deployedTargetIDs = new ArrayList<>();
final List<Long> undeployedTargetIDs = new ArrayList<>();
@@ -1178,73 +1118,6 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
}
protected static class EventHandlerStub implements ApplicationListener<TargetAssignDistributionSetEvent> {
private final List<TargetAssignDistributionSetEvent> events = Collections.synchronizedList(new LinkedList<>());
private CountDownLatch latch;
private int expectedNumberOfEvents;
/**
* @param expectedNumberOfEvents
* the expectedNumberOfEvents to set
*/
public void setExpectedNumberOfEvents(final int expectedNumberOfEvents) {
events.clear();
this.expectedNumberOfEvents = expectedNumberOfEvents;
this.latch = new CountDownLatch(expectedNumberOfEvents);
}
public List<TargetAssignDistributionSetEvent> getEvents(final long timeout, final TimeUnit unit)
throws InterruptedException {
latch.await(timeout, unit);
final List<TargetAssignDistributionSetEvent> handledEvents = Collections
.unmodifiableList(new LinkedList<>(events));
assertThat(handledEvents).as("Did not receive the expected amount of events (" + expectedNumberOfEvents
+ ") within timeout. Received events are " + handledEvents).hasSize(expectedNumberOfEvents);
return handledEvents;
}
@Override
public void onApplicationEvent(final TargetAssignDistributionSetEvent event) {
if (latch == null) {
return;
}
events.add(event);
latch.countDown();
}
}
private static class CancelEventHandlerStub implements ApplicationListener<CancelTargetAssignmentEvent> {
private final List<CancelTargetAssignmentEvent> events = Collections.synchronizedList(new LinkedList<>());
private CountDownLatch latch;
private int expectedNumberOfEvents;
public void setExpectedNumberOfEvents(final int expectedNumberOfEvents) {
events.clear();
this.expectedNumberOfEvents = expectedNumberOfEvents;
this.latch = new CountDownLatch(expectedNumberOfEvents);
}
public List<CancelTargetAssignmentEvent> getEvents(final long timeout, final TimeUnit unit)
throws InterruptedException {
latch.await(timeout, unit);
final List<CancelTargetAssignmentEvent> handledEvents = new LinkedList<>(events);
assertThat(handledEvents).as("Did not receive the expected amount of events (" + expectedNumberOfEvents
+ ") within timeout. Received events are " + handledEvents).hasSize(expectedNumberOfEvents);
return handledEvents;
}
@Override
public void onApplicationEvent(final CancelTargetAssignmentEvent event) {
if (latch == null) {
return;
}
events.add(event);
latch.countDown();
}
}
private void enableMultiAssignments() {
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED, true);
}