Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Backport release/1.8.x] Distributed event notification of GWC config changes #553

Merged
merged 3 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading