Skip to content

Commit

Permalink
feat: spawn reload and reset actions to all pods
Browse files Browse the repository at this point in the history
Co-authored-by: Nils Bühner <[email protected]>
  • Loading branch information
dnlkoch and buehner committed Oct 30, 2024
1 parent ffdc5a6 commit e755881
Show file tree
Hide file tree
Showing 21 changed files with 619 additions and 74 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* (c) 2024 Open Source Geospatial Foundation - all rights reserved This code is licensed under the
* GPL 2.0 license, available at the root application directory.
*/
package org.geoserver.cloud.autoconfigure.catalog.backend.core;

import org.geoserver.cloud.autoconfigure.catalog.event.ConditionalOnCatalogEvents;
import org.geoserver.cloud.event.lifecycle.LifecycleEvent;
import org.geoserver.cloud.event.remote.lifecycle.LifecycleEventProcessor;
import org.geoserver.config.plugin.GeoServerImpl;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;

/**
* @since 1.0
*/
@AutoConfiguration
@ConditionalOnClass(LifecycleEvent.class)
@ConditionalOnCatalogEvents
public class LifecycleEventAutoConfiguration {

@Bean
LifecycleEventProcessor lifecycleEventProcessor(
@Qualifier("geoServer") GeoServerImpl rawGeoServer) {

return new LifecycleEventProcessor(rawGeoServer);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
@AutoConfiguration
@ConditionalOnClass(InfoEvent.class)
@ConditionalOnCatalogEvents
public class RemoteEventResourcePoolCleaupUpAutoConfiguration {
public class RemoteEventResourcePoolCleanupUpAutoConfiguration {

@Bean
RemoteEventResourcePoolProcessor remoteEventResourcePoolProcessor(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* (c) 2020 Open Source Geospatial Foundation - all rights reserved This code is licensed under the
* GPL 2.0 license, available at the root application directory.
*/
package org.geoserver.cloud.event.remote.lifecycle;

import lombok.extern.slf4j.Slf4j;

import org.geoserver.cloud.event.lifecycle.ReloadEvent;
import org.geoserver.cloud.event.lifecycle.ResetEvent;
import org.geoserver.config.plugin.GeoServerImpl;
import org.springframework.context.event.EventListener;

/**
* Listens for and processes {@link ResetEvent} and {@link ReloadEvent} events.
*
* @since 1.0
*/
@Slf4j(topic = "org.geoserver.cloud.event.remote.lifecycle")
public class LifecycleEventProcessor {

private final GeoServerImpl rawGeoServer;

/**
* @param rawGeoServer used to reset or reload
*/
public LifecycleEventProcessor(GeoServerImpl rawGeoServer) {
this.rawGeoServer = rawGeoServer;
}

@EventListener(ResetEvent.class)
public void onReset(ResetEvent event) {

if (event.isRemote()) {
log.debug("Received a remote ResetEvent, triggering a GeoServer reset ({})", event);
rawGeoServer.reset(true);
}
}

@EventListener(ReloadEvent.class)
public void onReload(ReloadEvent event) {

if (event.isRemote()) {
log.debug("Received a remote ReloadEvent, triggering a GeoServer reload ({})", event);
try {
rawGeoServer.reload(null, true);
} catch (Exception e) {
log.error("Error reloading catalog: ", e);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ org.geoserver.cloud.autoconfigure.geotools.GeoToolsHttpClientAutoConfiguration,\
org.geoserver.cloud.autoconfigure.catalog.backend.core.GeoServerBackendAutoConfiguration,\
org.geoserver.cloud.autoconfigure.catalog.backend.core.DefaultUpdateSequenceAutoConfiguration,\
org.geoserver.cloud.autoconfigure.catalog.backend.core.XstreamServiceLoadersAutoConfiguration,\
org.geoserver.cloud.autoconfigure.catalog.backend.core.RemoteEventResourcePoolCleaupUpAutoConfiguration,\
org.geoserver.cloud.autoconfigure.catalog.backend.core.RemoteEventResourcePoolCleanupUpAutoConfiguration,\
org.geoserver.cloud.autoconfigure.catalog.backend.core.LifecycleEventAutoConfiguration,\
org.geoserver.cloud.autoconfigure.security.GeoServerSecurityAutoConfiguration,\
org.geoserver.cloud.autoconfigure.metrics.catalog.CatalogMetricsAutoConfiguration
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* (c) 2024 Open Source Geospatial Foundation - all rights reserved This code is licensed under the
* GPL 2.0 license, available at the root application directory.
*/
package org.geoserver.cloud.autoconfigure.catalog.backend.core;

import static org.assertj.core.api.Assertions.assertThat;

import org.geoserver.cloud.event.lifecycle.LifecycleEvent;
import org.geoserver.config.plugin.GeoServerImpl;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;

class LifecycleEventAutoConfigurationTest {

private final ApplicationContextRunner runner =
new ApplicationContextRunner()
.withBean("geoServer", GeoServerImpl.class)
.withConfiguration(
AutoConfigurations.of(LifecycleEventAutoConfiguration.class));

@Test
void testDefaultAppContextContributions() {
runner.run(
context -> assertThat(context).hasNotFailed().hasBean("lifecycleEventProcessor"));
}

@Test
void whenDependentClassesAreNotPresent_thenBeanMissing() {
runner.withClassLoader(new FilteredClassLoader(LifecycleEvent.class))
.run(
context ->
assertThat(context)
.hasNotFailed()
.doesNotHaveBean("lifecycleEventProcessor"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* (c) 2024 Open Source Geospatial Foundation - all rights reserved This code is licensed under the
* GPL 2.0 license, available at the root application directory.
*/
package org.geoserver.cloud.autoconfigure.catalog.backend.core;

import static org.assertj.core.api.Assertions.assertThat;

import org.geoserver.catalog.plugin.CatalogPlugin;
import org.geoserver.cloud.event.info.InfoEvent;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;

class RemoteEventResourcePoolCleanupUpAutoConfigurationTest {

private final ApplicationContextRunner runner =
new ApplicationContextRunner()
.withBean("rawCatalog", CatalogPlugin.class)
.withConfiguration(
AutoConfigurations.of(
RemoteEventResourcePoolCleanupUpAutoConfiguration.class));

@Test
void testDefaultAppContextContributions() {
runner.run(
context ->
assertThat(context)
.hasNotFailed()
.hasBean("remoteEventResourcePoolProcessor"));
}

@Test
void whenDependentClassesAreNotPresent_thenBeanMissing() {
runner.withClassLoader(new FilteredClassLoader(InfoEvent.class))
.run(
context ->
assertThat(context)
.hasNotFailed()
.doesNotHaveBean("remoteEventResourcePoolProcessor"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ class DataDirectoryAutoConfigurationTest {
org.geoserver.cloud.autoconfigure.catalog.backend.core
.XstreamServiceLoadersAutoConfiguration.class,
org.geoserver.cloud.autoconfigure.catalog.backend.core
.RemoteEventResourcePoolCleaupUpAutoConfiguration.class,
.RemoteEventResourcePoolCleanupUpAutoConfiguration
.class,
org.geoserver.cloud.autoconfigure.security
.GeoServerSecurityAutoConfiguration.class,
org.geoserver.cloud.autoconfigure.metrics.catalog
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import org.geoserver.cloud.event.info.InfoEvent;
import org.geoserver.cloud.event.info.InfoModified;
import org.geoserver.cloud.event.info.InfoRemoved;
import org.geoserver.cloud.event.lifecycle.LifecycleEvent;
import org.geoserver.config.GeoServer;
import org.geoserver.config.GeoServerInfo;
import org.geoserver.config.LoggingInfo;
Expand Down Expand Up @@ -93,7 +94,7 @@ public abstract class BusAmqpIntegrationTests {

@Container
private static final RabbitMQContainer rabbitMQContainer =
new RabbitMQContainer("rabbitmq:3.11-management");
new RabbitMQContainer("rabbitmq:3.13-management");

protected static ConfigurableApplicationContext remoteAppContext;
private @Autowired ConfigurableApplicationContext localAppContext;
Expand All @@ -113,6 +114,7 @@ static void properties(DynamicPropertyRegistry registry) {

@BeforeAll
static void setUpRemoteApplicationContext() {

remoteAppContext =
new SpringApplicationBuilder(
TestConfigurationAutoConfiguration.class, BusEventCollector.class)
Expand Down Expand Up @@ -430,6 +432,12 @@ public <E extends InfoEvent> EventsCaptor captureEventsOf(Class<E> type) {
return this;
}

public <E extends LifecycleEvent> EventsCaptor captureLifecycleEventsOf(Class<E> type) {
local.captureLifecycle(type);
remote.captureLifecycle(type);
return this;
}

public EventsCaptor stop() {
remote.stop();
local.stop();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.geoserver.cloud.event.GeoServerEvent;
import org.geoserver.cloud.event.info.ConfigInfoType;
import org.geoserver.cloud.event.info.InfoEvent;
import org.geoserver.cloud.event.lifecycle.LifecycleEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
Expand All @@ -38,7 +39,7 @@ public class BusEventCollector {
private @Value("${spring.cloud.bus.id}") String busId;
private @Autowired RemoteGeoServerEventBridge bridge;

private @NonNull Class<? extends InfoEvent> eventType = InfoEvent.class;
private @NonNull Class<? extends GeoServerEvent> eventType = GeoServerEvent.class;

private volatile boolean capturing = false;

Expand Down Expand Up @@ -66,6 +67,10 @@ public void capture(@NonNull Class<? extends InfoEvent> type) {
this.eventType = type;
}

public void captureLifecycle(@NonNull Class<? extends LifecycleEvent> type) {
this.eventType = type;
}

public <T extends InfoEvent> RemoteGeoServerEvent expectOne(Class<T> payloadType) {

return expectOne(payloadType, x -> true);
Expand All @@ -80,7 +85,7 @@ public <T extends InfoEvent> RemoteGeoServerEvent expectOne(
Class<T> payloadType, Predicate<T> filter) {

List<RemoteGeoServerEvent> matches =
await().atMost(Duration.ofSeconds(500)) //
await().atMost(Duration.ofSeconds(10)) //
.until(() -> allOf(payloadType, filter), not(List::isEmpty));

Supplier<String> message =
Expand All @@ -92,6 +97,21 @@ public <T extends InfoEvent> RemoteGeoServerEvent expectOne(
return matches.get(0);
}

public <T extends LifecycleEvent> RemoteGeoServerEvent expectOneLifecycleEvent(
Class<T> payloadType) {

List<RemoteGeoServerEvent> matches =
await().atMost(Duration.ofSeconds(10)) //
.until(
() -> allOfLifecycle(payloadType, filter -> true),
not(List::isEmpty));

assertThat(matches.size()).isOne();

//noinspection OptionalGetWithoutIsPresent
return matches.stream().findFirst().get();
}

public <T extends InfoEvent> List<RemoteGeoServerEvent> allOf(
Class<T> payloadEventType, Predicate<T> eventFilter) {

Expand All @@ -102,6 +122,16 @@ public <T extends InfoEvent> List<RemoteGeoServerEvent> allOf(
.toList();
}

public <T extends LifecycleEvent> List<RemoteGeoServerEvent> allOfLifecycle(
Class<T> payloadEventType, Predicate<T> eventFilter) {

return capturedLifecycleEvents(payloadEventType)
.filter(
remoteEvent ->
eventFilter.test(payloadEventType.cast(remoteEvent.getEvent())))
.toList();
}

public <T extends InfoEvent> List<RemoteGeoServerEvent> allOf(Class<T> payloadType) {
return capturedEvents(payloadType).toList();
}
Expand All @@ -115,6 +145,11 @@ private <T extends InfoEvent> Stream<RemoteGeoServerEvent> capturedEvents(
return capturedEvents().filter(remote -> payloadType.isInstance(remote.getEvent()));
}

private <T extends LifecycleEvent> Stream<RemoteGeoServerEvent> capturedLifecycleEvents(
Class<T> payloadType) {
return capturedEvents().filter(remote -> payloadType.isInstance(remote.getEvent()));
}

private Stream<RemoteGeoServerEvent> capturedEvents() {
return events.stream();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* (c) 2020 Open Source Geospatial Foundation - all rights reserved This code is licensed under the
* GPL 2.0 license, available at the root application directory.
*/
package org.geoserver.cloud.event.bus;

import org.geoserver.cloud.event.lifecycle.LifecycleEvent;
import org.geoserver.cloud.event.lifecycle.ReloadEvent;
import org.geoserver.cloud.event.lifecycle.ResetEvent;
import org.geoserver.config.GeoServer;
import org.geoserver.platform.GeoServerExtensions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import java.util.function.Consumer;

class LifecycleRemoteApplicationEventsIT extends BusAmqpIntegrationTests {

@BeforeAll
static void handleGsExtensions() {
GeoServerExtensions gse = new GeoServerExtensions();
gse.setApplicationContext(remoteAppContext);
}

@Test
void testGeoServerHasExecutedReset() {

this.eventsCaptor.stop().clear().captureLifecycleEventsOf(LifecycleEvent.class).start();

Consumer<GeoServer> modifier = GeoServer::reset;
modifier.accept(geoserver);

eventsCaptor.local().expectOneLifecycleEvent(ResetEvent.class);
eventsCaptor.remote().expectOneLifecycleEvent(ResetEvent.class);
}

@Test
void testGeoServerHasExecutedReload() {

this.eventsCaptor.stop().clear().captureLifecycleEventsOf(LifecycleEvent.class).start();

Consumer<GeoServer> modifier =
geoServer -> {
try {
geoServer.reload();
} catch (Exception e) {
throw new RuntimeException(e);
}
};
modifier.accept(geoserver);

// reload also triggers reset!
eventsCaptor.local().expectOneLifecycleEvent(ReloadEvent.class);
eventsCaptor.local().expectOneLifecycleEvent(ResetEvent.class);
eventsCaptor.remote().expectOneLifecycleEvent(ReloadEvent.class);
eventsCaptor.remote().expectOneLifecycleEvent(ResetEvent.class);
}
}
Loading

0 comments on commit e755881

Please sign in to comment.