Remote Events migrated from Spring Bus to Spring Cloud Stream (#2563)
* Remote Events migrated from Spring Bus to Spring Cloud Stream --------- Co-authored-by: vasilchev <vasil.ilchev@bosch.com>
This commit is contained in:
@@ -30,7 +30,6 @@ import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.hawkbit.im.authentication.SpPermission;
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.repository.event.remote.RemoteTenantAwareEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.TenantConfigurationDeletedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.RemoteEntityEvent;
|
||||
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
|
||||
@@ -58,7 +57,6 @@ import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.cloud.bus.ServiceMatcher;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.core.convert.support.ConfigurableConversionService;
|
||||
@@ -89,9 +87,7 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
|
||||
private final CacheManager cacheManager;
|
||||
private final AfterTransactionCommitExecutor afterCommitExecutor;
|
||||
|
||||
private ServiceMatcher serviceMatcher;
|
||||
|
||||
protected JpaTenantConfigurationManagement(
|
||||
public JpaTenantConfigurationManagement(
|
||||
final TenantConfigurationRepository tenantConfigurationRepository,
|
||||
final TenantConfigurationProperties tenantConfigurationProperties,
|
||||
final CacheManager cacheManager, final AfterTransactionCommitExecutor afterCommitExecutor,
|
||||
@@ -103,11 +99,6 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setServiceMatcher(final ServiceMatcher serviceMatcher) {
|
||||
this.serviceMatcher = serviceMatcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
@CacheEvict(value = "tenantConfiguration", key = "#configurationKeyName")
|
||||
@Transactional
|
||||
@@ -186,10 +177,6 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
|
||||
*/
|
||||
@EventListener
|
||||
public void onTenantConfigurationDeletedEvent(final TenantConfigurationDeletedEvent event) {
|
||||
if (!shouldProcessRemoteTenantAwareEvent(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
evictCacheEntryByKeyIfPresent(event.getConfigKey());
|
||||
}
|
||||
|
||||
@@ -200,10 +187,6 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
|
||||
*/
|
||||
@EventListener
|
||||
public void onTenantConfigurationRemoteEntityEvent(final RemoteEntityEvent<TenantConfiguration> event) {
|
||||
if (!shouldProcessRemoteTenantAwareEvent(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.getEntity().ifPresent(tenantConfiguration -> evictCacheEntryByKeyIfPresent(tenantConfiguration.getKey()));
|
||||
}
|
||||
|
||||
@@ -391,8 +374,4 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
|
||||
cache.evictIfPresent(key);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldProcessRemoteTenantAwareEvent(final RemoteTenantAwareEvent event) {
|
||||
return serviceMatcher == null || !serviceMatcher.isFromSelf(event) && serviceMatcher.isForSelf(event);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,6 @@ import org.eclipse.hawkbit.repository.event.remote.entity.ActionUpdatedEvent;
|
||||
import org.eclipse.hawkbit.repository.jpa.utils.MapAttributeConverter;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.ActionStatus;
|
||||
import org.eclipse.hawkbit.repository.model.BaseEntity;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup;
|
||||
|
||||
@@ -11,49 +11,42 @@ package org.eclipse.hawkbit.repository.event.remote;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.eclipse.hawkbit.event.BusProtoStuffMessageConverter;
|
||||
import org.eclipse.hawkbit.event.EventProtoStuffMessageConverter;
|
||||
import org.eclipse.hawkbit.repository.event.TenantAwareEvent;
|
||||
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cloud.bus.event.RemoteApplicationEvent;
|
||||
import org.springframework.cloud.bus.jackson.BusJacksonAutoConfiguration;
|
||||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.integration.support.MutableMessageHeaders;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.converter.AbstractMessageConverter;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.MimeType;
|
||||
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
|
||||
import org.springframework.messaging.converter.MessageConverter;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
|
||||
/**
|
||||
* Test the remote entity events.
|
||||
*/
|
||||
@TestPropertySource(properties = { "spring.cloud.bus.enabled=true" })
|
||||
@SuppressWarnings("java:S6813") // constructor injects are not possible for test classes
|
||||
public abstract class AbstractRemoteEventTest extends AbstractJpaIntegrationTest {
|
||||
@Import(AbstractRemoteEventTest.EventProtoStuffTestConfig.class)
|
||||
public abstract class AbstractRemoteEventTest extends AbstractJpaIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private BusProtoStuffMessageConverter busProtoStuffMessageConverter;
|
||||
private EventProtoStuffMessageConverter eventProtoStuffMessageConverter;
|
||||
|
||||
private AbstractMessageConverter jacksonMessageConverter;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws Exception {
|
||||
final BusJacksonAutoConfiguration autoConfiguration = new BusJacksonAutoConfiguration();
|
||||
this.jacksonMessageConverter = autoConfiguration.busJsonConverter(null);
|
||||
ReflectionTestUtils.setField(
|
||||
jacksonMessageConverter, "packagesToScan",
|
||||
new String[] { "org.eclipse.hawkbit.repository.event.remote", ClassUtils.getPackageName(RemoteApplicationEvent.class) });
|
||||
((InitializingBean) jacksonMessageConverter).afterPropertiesSet();
|
||||
public void setup() {
|
||||
this.jacksonMessageConverter = new MappingJackson2MessageConverter();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -65,24 +58,33 @@ import org.springframework.util.MimeTypeUtils;
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T extends TenantAwareEvent> T createProtoStuffEvent(final T event) {
|
||||
final Message<?> message = createProtoStuffMessage(event);
|
||||
return (T) busProtoStuffMessageConverter.fromMessage(message, event.getClass());
|
||||
return (T) eventProtoStuffMessageConverter.fromMessage(message, event.getClass());
|
||||
}
|
||||
|
||||
private Message<?> createProtoStuffMessage(final TenantAwareEvent event) {
|
||||
final Map<String, Object> headers = new LinkedHashMap<>();
|
||||
headers.put(MessageHeaders.CONTENT_TYPE, BusProtoStuffMessageConverter.APPLICATION_BINARY_PROTOSTUFF);
|
||||
return busProtoStuffMessageConverter.toMessage(event, new MutableMessageHeaders(headers));
|
||||
return eventProtoStuffMessageConverter.toMessage(
|
||||
event, new MutableMessageHeaders(Map.of(MessageHeaders.CONTENT_TYPE,
|
||||
EventProtoStuffMessageConverter.APPLICATION_BINARY_PROTOSTUFF))
|
||||
);
|
||||
}
|
||||
|
||||
private Message<String> createJsonMessage(final Object event) {
|
||||
final Map<String, MimeType> headers = new LinkedHashMap<>();
|
||||
headers.put(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON);
|
||||
try {
|
||||
final String json = new ObjectMapper().writeValueAsString(event);
|
||||
return MessageBuilder.withPayload(json).copyHeaders(headers).build();
|
||||
} catch (final JsonProcessingException e) {
|
||||
String json = new ObjectMapper().writeValueAsString(event);
|
||||
return MessageBuilder.withPayload(json)
|
||||
.setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON)
|
||||
.build();
|
||||
} catch (JsonProcessingException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@TestConfiguration
|
||||
static class EventProtoStuffTestConfig {
|
||||
@Bean
|
||||
public MessageConverter eventProtoBufConverter() {
|
||||
return new EventProtoStuffMessageConverter();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.event.remote;
|
||||
|
||||
import org.eclipse.hawkbit.repository.event.EventPublisherHolder;
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.service.CancelTargetAssignmentServiceEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.service.MultiActionAssignServiceEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.service.TargetAssignDistributionSetServiceEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.service.TargetAttributesRequestedServiceEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.service.TargetCreatedServiceEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.service.TargetDeletedServiceEvent;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.cloud.stream.function.StreamBridge;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
class ServiceEventsTest {
|
||||
|
||||
private StreamBridge streamBridge;
|
||||
private ApplicationEventPublisher delegate;
|
||||
private ApplicationEventPublisher publisher;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws IllegalAccessException, NoSuchFieldException {
|
||||
streamBridge = mock(StreamBridge.class);
|
||||
delegate = mock(ApplicationEventPublisher.class);
|
||||
EventPublisherHolder.getInstance().setApplicationEventPublisher(delegate);
|
||||
EventPublisherHolder.getInstance().setStreamBridge(streamBridge);
|
||||
publisher = EventPublisherHolder.getInstance().getEventPublisher();
|
||||
|
||||
// Set up fields via reflection
|
||||
var remoteEventsEnabledField = EventPublisherHolder.class.getDeclaredField("remoteEventsEnabled");
|
||||
remoteEventsEnabledField.setAccessible(true);
|
||||
remoteEventsEnabledField.set(EventPublisherHolder.getInstance(), true);
|
||||
|
||||
var remoteServiceEventsEnabledField = EventPublisherHolder.class.getDeclaredField("remoteServiceEventsEnabled");
|
||||
remoteServiceEventsEnabledField.setAccessible(true);
|
||||
remoteServiceEventsEnabledField.set(EventPublisherHolder.getInstance(), true);
|
||||
|
||||
var fanoutChannelField = EventPublisherHolder.class.getDeclaredField("fanoutEventChannel");
|
||||
fanoutChannelField.setAccessible(true);
|
||||
fanoutChannelField.set(EventPublisherHolder.getInstance(), "fanout");
|
||||
|
||||
var groupChannelField = EventPublisherHolder.class.getDeclaredField("serviceEventChannel");
|
||||
groupChannelField.setAccessible(true);
|
||||
groupChannelField.set(EventPublisherHolder.getInstance(), "group");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExpectedServiceEvents(){
|
||||
var expected = Set.of(
|
||||
TargetAssignDistributionSetEvent.class,
|
||||
MultiActionAssignEvent.class,
|
||||
MultiActionCancelEvent.class,
|
||||
CancelTargetAssignmentEvent.class,
|
||||
TargetDeletedEvent.class,
|
||||
TargetCreatedEvent.class,
|
||||
TargetAttributesRequestedEvent.class
|
||||
);
|
||||
assertEquals(EventPublisherHolder.SERVICE_EVENTS, expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessingTargetAssignDistributionSetEventIsSent() {
|
||||
TargetAssignDistributionSetEvent event = new TargetAssignDistributionSetEvent(mockAction());
|
||||
|
||||
publisher.publishEvent(event);
|
||||
|
||||
verify(streamBridge).send("fanout", event);
|
||||
verify(streamBridge).send(eq("group"), any(TargetAssignDistributionSetServiceEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessingTargetCreatedEventIsSent() {
|
||||
TargetCreatedEvent event = new TargetCreatedEvent(mock(Target.class));
|
||||
publisher.publishEvent(event);
|
||||
|
||||
verify(streamBridge).send("fanout", event);
|
||||
verify(streamBridge).send(eq("group"), any(TargetCreatedServiceEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessingTargetDeletedEventIsSent() {
|
||||
TargetDeletedEvent event = new TargetDeletedEvent("testtenant", 1l, Target.class, "testControllerId", "address");
|
||||
publisher.publishEvent(event);
|
||||
|
||||
verify(streamBridge).send("fanout", event);
|
||||
verify(streamBridge).send(eq("group"), any(TargetDeletedServiceEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessingTargetAttributesRequestedEventIsSent() {
|
||||
TargetAttributesRequestedEvent event = new TargetAttributesRequestedEvent("testtenant", 1l, Target.class, "testControllerId","address");
|
||||
publisher.publishEvent(event);
|
||||
|
||||
verify(streamBridge).send("fanout", event);
|
||||
verify(streamBridge).send(eq("group"), any(TargetAttributesRequestedServiceEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessingMultiActionAssignmentEventIsSent() {
|
||||
MultiActionAssignEvent event = new MultiActionAssignEvent("testtenant", List.of(mockAction()));
|
||||
|
||||
publisher.publishEvent(event);
|
||||
|
||||
verify(streamBridge).send("fanout", event);
|
||||
verify(streamBridge).send(eq("group"), any(MultiActionAssignServiceEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCancelTargetAssignmentEventIsSent() {
|
||||
CancelTargetAssignmentEvent event = new CancelTargetAssignmentEvent(mockAction());
|
||||
|
||||
publisher.publishEvent(event);
|
||||
|
||||
verify(streamBridge).send("fanout", event);
|
||||
verify(streamBridge).send(eq("group"), any(CancelTargetAssignmentServiceEvent.class));
|
||||
}
|
||||
|
||||
private Action mockAction() {
|
||||
final Action actionMock = mock(Action.class);
|
||||
final Target targetMock = mock(Target.class);
|
||||
final DistributionSet distributionSetMock = mock(DistributionSet.class);
|
||||
when(distributionSetMock.getId()).thenReturn(1L);
|
||||
when(actionMock.getDistributionSet()).thenReturn(distributionSetMock);
|
||||
when(actionMock.getId()).thenReturn(1l);
|
||||
when(actionMock.getTenant()).thenReturn("DEFAULT");
|
||||
when(actionMock.getTarget()).thenReturn(targetMock);
|
||||
when(actionMock.getActionType()).thenReturn(Action.ActionType.SOFT);
|
||||
when(targetMock.getControllerId()).thenReturn("target1");
|
||||
return actionMock;
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@
|
||||
#
|
||||
|
||||
### Debug & Monitor Eclipselink - START
|
||||
|
||||
logging.level.org.eclipse.persistence=ERROR
|
||||
## Uncomment to see the debug of persistence, e.g. to see the generated SQLs
|
||||
#logging.level.org.eclipse.persistence=DEBUG
|
||||
@@ -56,5 +55,5 @@ hawkbit.repository.cluster.lock.refreshOnRemainMS=200
|
||||
hawkbit.repository.cluster.lock.refreshOnRemainPercent=10
|
||||
# reduce scheduler tic period to speed up tests
|
||||
hawkbit.repository.cluster.lock.ticPeriodMS=10
|
||||
# disable spring cloud bus for tests
|
||||
spring.cloud.bus.enabled=false
|
||||
|
||||
org.eclipse.hawkbit.events.remote-enabled=false
|
||||
Reference in New Issue
Block a user