Skip to content

Commit

Permalink
Merge pull request #554 from geoserver/backport-549-to-release/1.8.x
Browse files Browse the repository at this point in the history
[Backport release/1.8.x] Distributed event notification of GWC config changes
  • Loading branch information
groldan authored Oct 17, 2024
2 parents 0c985bb + d59b17e commit 8e8735b
Show file tree
Hide file tree
Showing 12 changed files with 365 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;

import org.geoserver.GeoServerConfigurationLock;
import org.geoserver.cloud.gwc.backend.pgconfig.PgconfigTileLayerCatalog;
import org.geoserver.cloud.gwc.config.core.AbstractGwcInitializer;
import org.geoserver.cloud.gwc.event.TileLayerEvent;
import org.geoserver.cloud.gwc.repository.GeoServerTileLayerConfiguration;
import org.geoserver.config.GeoServerReinitializer;
import org.geoserver.gwc.ConfigurableBlobStore;
import org.geoserver.gwc.config.AbstractGwcInitializer;
import org.geoserver.gwc.config.GWCConfigPersister;
import org.geoserver.gwc.config.GWCInitializer;
import org.geoserver.gwc.layer.TileLayerCatalog;
Expand Down Expand Up @@ -46,8 +47,9 @@ class PgconfigGwcInitializer extends AbstractGwcInitializer {
public PgconfigGwcInitializer(
@NonNull GWCConfigPersister configPersister,
@NonNull ConfigurableBlobStore blobStore,
@NonNull GeoServerTileLayerConfiguration geoseverTileLayers) {
super(configPersister, blobStore, geoseverTileLayers);
@NonNull GeoServerTileLayerConfiguration geoseverTileLayers,
@NonNull GeoServerConfigurationLock configLock) {
super(configPersister, blobStore, geoseverTileLayers, configLock);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;

import org.geoserver.GeoServerConfigurationLock;
import org.geoserver.catalog.Catalog;
import org.geoserver.cloud.autoconfigure.catalog.backend.pgconfig.ConditionalOnPgconfigBackendEnabled;
import org.geoserver.cloud.autoconfigure.catalog.backend.pgconfig.PgconfigDataSourceAutoConfiguration;
Expand Down Expand Up @@ -58,13 +59,18 @@ void log() {
log.info("GeoWebCache TileLayerCatalog using PostgreSQL config backend");
}

/** Replacement for {@link GWCInitializer} when using {@link GeoServerTileLayerConfiguration} */
/**
* Replacement for {@link GWCInitializer} when using {@link GeoServerTileLayerConfiguration}
*
* @param configLock
*/
@Bean
PgconfigGwcInitializer gwcInitializer(
GWCConfigPersister configPersister,
ConfigurableBlobStore blobStore,
GeoServerTileLayerConfiguration tileLayerCatalog) {
return new PgconfigGwcInitializer(configPersister, blobStore, tileLayerCatalog);
GeoServerTileLayerConfiguration tileLayerCatalog,
GeoServerConfigurationLock configLock) {
return new PgconfigGwcInitializer(configPersister, blobStore, tileLayerCatalog, configLock);
}

@Bean(name = "gwcCatalogConfiguration")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,15 @@
import org.geoserver.cloud.gwc.repository.CloudDefaultStorageFinder;
import org.geoserver.cloud.gwc.repository.CloudGwcXmlConfiguration;
import org.geoserver.cloud.gwc.repository.CloudXMLResourceProvider;
import org.geoserver.gwc.ConfigurableLockProvider;
import org.geoserver.gwc.GWC;
import org.geoserver.gwc.GeoServerLockProvider;
import org.geoserver.gwc.config.AbstractGwcInitializer;
import org.geoserver.gwc.config.DefaultGwcInitializer;
import org.geoserver.gwc.config.GWCConfig;
import org.geoserver.gwc.config.GWCConfigPersister;
import org.geoserver.platform.GeoServerExtensionsHelper;
import org.geowebcache.locks.LockProvider;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
Expand Down Expand Up @@ -65,6 +72,35 @@ void defaultCacheDirectoryIsAFile(@TempDir File tmpDir) throws IOException {
assertContextLoadFails(BeanInitializationException.class, "is not a directory");
}

@Test
void lockProviderDelegatesStoGeoSeverLockProvider() {
runner.run(
context -> {
GeoServerExtensionsHelper.init(context);
assertThat(context)
.hasNotFailed()
.hasBean(AbstractGwcInitializer.GWC_LOCK_PROVIDER_BEAN_NAME)
.getBean(AbstractGwcInitializer.GWC_LOCK_PROVIDER_BEAN_NAME)
.isInstanceOf(GeoServerLockProvider.class);

GWCConfigPersister persister = context.getBean(GWCConfigPersister.class);
GWCConfig config = persister.getConfig();
assertThat(config.getLockProviderName())
.isEqualTo(AbstractGwcInitializer.GWC_LOCK_PROVIDER_BEAN_NAME);

GWC gwc = GWC.get();

LockProvider lockProvider = gwc.getLockProvider();
assertThat(lockProvider).isInstanceOf(ConfigurableLockProvider.class);
GeoServerLockProvider expected =
context.getBean(
AbstractGwcInitializer.GWC_LOCK_PROVIDER_BEAN_NAME,
GeoServerLockProvider.class);
assertThat(((ConfigurableLockProvider) lockProvider).getDelegate())
.isSameAs(expected);
});
}

@Test
void contextLoads() {
runner.run(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@
import lombok.extern.slf4j.Slf4j;

import org.geoserver.cloud.config.factory.FilteringXmlBeanDefinitionReader;
import org.geoserver.cloud.gwc.event.ConfigChangeEvent;
import org.geoserver.config.util.XStreamPersisterFactory;
import org.geoserver.gwc.config.CloudGwcConfigPersister;
import org.geoserver.gwc.config.GWCConfigPersister;
import org.geoserver.platform.GeoServerResourceLoader;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;

Expand All @@ -32,6 +39,7 @@ public class GeoServerIntegrationConfiguration {
|gwcTransactionListener\
|gwcWMSExtendedCapabilitiesProvider\
|gwcInitializer\
|gwcGeoServervConfigPersister\
).*$\
""";

Expand All @@ -42,4 +50,20 @@ public class GeoServerIntegrationConfiguration {
public void log() {
log.info("GeoWebCache core GeoServer integration enabled");
}

/**
* Overrides {@code gwcGeoServervConfigPersister} with a cluster-aware {@link
* GWCConfigPersister} that sends {@link ConfigChangeEvent}s upon {@link
* GWCConfigPersister#save(org.geoserver.gwc.config.GWCConfig)}
*
* @param xsfp
* @param resourceLoader
*/
@Bean
GWCConfigPersister gwcGeoServervConfigPersister(
XStreamPersisterFactory xsfp,
GeoServerResourceLoader resourceLoader,
ApplicationEventPublisher publisher) {
return new CloudGwcConfigPersister(xsfp, resourceLoader, publisher::publishEvent);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@
import org.geoserver.cloud.gwc.repository.CloudDefaultStorageFinder;
import org.geoserver.cloud.gwc.repository.CloudGwcXmlConfiguration;
import org.geoserver.cloud.gwc.repository.CloudXMLResourceProvider;
import org.geoserver.gwc.GeoServerLockProvider;
import org.geoserver.gwc.config.AbstractGwcInitializer;
import org.geoserver.platform.resource.Resource;
import org.geoserver.platform.resource.ResourceStore;
import org.geoserver.platform.resource.Resources;
import org.geowebcache.config.ConfigurationResourceProvider;
import org.geowebcache.config.XMLConfiguration;
import org.geowebcache.config.XMLFileResourceProvider;
import org.geowebcache.locks.LockProvider;
import org.geowebcache.storage.DefaultStorageFinder;
import org.geowebcache.util.ApplicationContextProvider;
import org.springframework.beans.FatalBeanException;
Expand Down Expand Up @@ -56,11 +59,22 @@
@ImportResource(
reader = FilteringXmlBeanDefinitionReader.class, //
locations = {
"jar:gs-gwc-[0-9]+.*!/geowebcache-core-context.xml#name=^(?!gwcXmlConfig|gwcDefaultStorageFinder|gwcGeoServervConfigPersister|metastoreRemover).*$"
"jar:gs-gwc-[0-9]+.*!/geowebcache-core-context.xml#name=^(?!gwcXmlConfig|gwcDefaultStorageFinder|metastoreRemover).*$"
})
@Slf4j(topic = "org.geoserver.cloud.gwc.config.core")
public class GeoWebCacheCoreConfiguration {

/**
* @return a {@link GeoServerLockProvider} delegating the the {@link ResourceStore}, whose
* {@link ResourceStore#getLockProvider()} is known to be cluster-capable
*/
@Bean(name = AbstractGwcInitializer.GWC_LOCK_PROVIDER_BEAN_NAME)
LockProvider gwcLockProvider(@Qualifier("resourceStoreImpl") ResourceStore resourceStore) {
var provider = new GeoServerLockProvider();
provider.setDelegate(resourceStore);
return provider;
}

@Bean
SetRequestPathInfoFilter setRequestPathInfoFilter() {
return new SetRequestPathInfoFilter();
Expand Down Expand Up @@ -283,9 +297,12 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
* </ul>
*/
protected ServletRequest adaptRequest(HttpServletRequest request) {
// full request URI (e.g. '/geoserver/cloud/{workspace}/gwc/service/tms/1.0.0', where
// '/geoserver/cloud' is the context path as given by the gateway's base uri, and
// '/{workspace}/gwc' the suffix after which comes the pathInfo '/service/tms/1.0.0')
// full request URI (e.g. '/geoserver/cloud/{workspace}/gwc/service/tms/1.0.0',
// where
// '/geoserver/cloud' is the context path as given by the gateway's base uri,
// and
// '/{workspace}/gwc' the suffix after which comes the pathInfo
// '/service/tms/1.0.0')
final String requestURI = request.getRequestURI();

final String gwc = "/gwc";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.geoserver.cloud.gwc.event;

@SuppressWarnings("serial")
public class ConfigChangeEvent extends GeoWebCacheEvent {

public static final String OBJECT_ID = "gwcConfig";

public ConfigChangeEvent(Object source) {
super(source, Type.MODIFIED);
}

@Override
protected String getObjectId() {
return OBJECT_ID;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* (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.gwc.config.core;
package org.geoserver.gwc.config;

import static com.google.common.base.Preconditions.checkNotNull;

Expand All @@ -11,21 +11,25 @@
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

import org.geoserver.GeoServerConfigurationLock;
import org.geoserver.GeoServerConfigurationLock.LockType;
import org.geoserver.cloud.gwc.event.TileLayerEvent;
import org.geoserver.cloud.gwc.repository.GeoServerTileLayerConfiguration;
import org.geoserver.config.GeoServer;
import org.geoserver.config.GeoServerReinitializer;
import org.geoserver.gwc.ConfigurableBlobStore;
import org.geoserver.gwc.config.GWCConfig;
import org.geoserver.gwc.config.GWCConfigPersister;
import org.geoserver.gwc.config.GWCInitializer;
import org.geoserver.gwc.GWC;
import org.geoserver.gwc.layer.GeoServerTileLayer;
import org.geoserver.gwc.layer.TileLayerCatalog;
import org.geoserver.platform.resource.Resource;
import org.geoserver.platform.resource.Resources;
import org.geowebcache.layer.TileLayer;
import org.geowebcache.locks.LockProvider;
import org.geowebcache.storage.blobstore.memory.CacheConfiguration;
import org.geowebcache.storage.blobstore.memory.CacheProvider;
import org.geowebcache.storage.blobstore.memory.guava.GuavaCacheProvider;
import org.slf4j.Logger;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.event.EventListener;
import org.springframework.util.StringUtils;

Expand Down Expand Up @@ -54,14 +58,28 @@
* @since 1.8
*/
@RequiredArgsConstructor
public abstract class AbstractGwcInitializer implements GeoServerReinitializer {
public abstract class AbstractGwcInitializer implements GeoServerReinitializer, InitializingBean {

/**
* {@link GWC#saveConfig(GWCConfig)} will lookup for the {@link LockProvider} named after {@link
* GWCConfig#getLockProviderName()}. We need it to be a cluster-aware lock provider. This is the
* bean name to be registered by the configuration, and we'll set it to {@link
* GWCConfig#setLockProviderName(String)} during initialization.
*/
public static final String GWC_LOCK_PROVIDER_BEAN_NAME = "gwcClusteringLockProvider";

protected final @NonNull GWCConfigPersister configPersister;
protected final @NonNull ConfigurableBlobStore blobStore;
protected final @NonNull GeoServerTileLayerConfiguration geoseverTileLayers;
protected final @NonNull GeoServerConfigurationLock globalConfigLock;

protected abstract Logger logger();

@Override
public void afterPropertiesSet() throws IOException {
initializeGeoServerIntegrationConfigFile();
}

/**
* @see org.geoserver.config.GeoServerInitializer#initialize(org.geoserver.config.GeoServer)
*/
Expand All @@ -80,6 +98,58 @@ public void initialize(final GeoServer geoServer) throws Exception {
setUpNonMemoryCacheableLayers();
}

/**
* Initialize the datadir/gs-gwc.xml file before {@link
* #initialize(org.geoserver.config.GeoServer) super.initialize(GeoServer)}
*/
private void initializeGeoServerIntegrationConfigFile() throws IOException {
globalConfigLock.lock(LockType.WRITE);
try {
if (configFileExists()) {
updateLockProviderName();
} else {
logger().info(
"Initializing GeoServer specific GWC configuration {}",
configPersister.findConfigFile());
GWCConfig defaults = new GWCConfig();
defaults.setVersion("1.1.0");
defaults.setLockProviderName(GWC_LOCK_PROVIDER_BEAN_NAME);
configPersister.save(defaults);
}
} finally {
globalConfigLock.unlock();
}
}

/**
* In case the {@link GWCConfig} exists and its lock provider name is not {@link
* #GWC_LOCK_PROVIDER_BEAN_NAME}, updates and saves the configuration.
*
* <p>At this point, {@link #configFileExists()} is known to be true.
*/
private void updateLockProviderName() throws IOException {
final GWCConfig gwcConfig = configPersister.getConfig();
if (!GWC_LOCK_PROVIDER_BEAN_NAME.equals(gwcConfig.getLockProviderName())) {
if (null == gwcConfig.getLockProviderName()) {
logger().info(
"Setting GeoWebCache lock provider to {}",
GWC_LOCK_PROVIDER_BEAN_NAME);
} else {
logger().warn(
"Updating GeoWebCache lock provider from {} to {}",
gwcConfig.getLockProviderName(),
GWC_LOCK_PROVIDER_BEAN_NAME);
}
gwcConfig.setLockProviderName(GWC_LOCK_PROVIDER_BEAN_NAME);
configPersister.save(gwcConfig);
}
}

private boolean configFileExists() throws IOException {
Resource configFile = configPersister.findConfigFile();
return Resources.exists(configFile);
}

@EventListener(TileLayerEvent.class)
void onTileLayerEvent(TileLayerEvent event) {
cacheProvider().ifPresent(cache -> onTileLayerEvent(event, cache));
Expand Down
Loading

0 comments on commit 8e8735b

Please sign in to comment.