diff --git a/src/apps/geoserver/gwc/src/main/resources/bootstrap.yml b/src/apps/geoserver/gwc/src/main/resources/bootstrap.yml index 869612237..47afd724a 100644 --- a/src/apps/geoserver/gwc/src/main/resources/bootstrap.yml +++ b/src/apps/geoserver/gwc/src/main/resources/bootstrap.yml @@ -62,6 +62,7 @@ spring: - org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration - org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration - org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration + - org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration # override default of true, this service does not use the registry (when eureka client is enabled) eureka.client.fetch-registry: false diff --git a/src/apps/geoserver/gwc/src/test/resources/bootstrap-test.yml b/src/apps/geoserver/gwc/src/test/resources/bootstrap-test.yml index 81ba7bc66..6c7d3c7f5 100644 --- a/src/apps/geoserver/gwc/src/test/resources/bootstrap-test.yml +++ b/src/apps/geoserver/gwc/src/test/resources/bootstrap-test.yml @@ -2,6 +2,7 @@ spring: cloud.config.enabled: false cloud.config.discovery.enabled: false cloud.discovery.enabled: false + cloud.bus.enabled: false eureka.client.enabled: false geoserver: acl.enabled: false @@ -16,7 +17,9 @@ logging: root: warn org.geotools: warn org.geoserver: warn + org.geoserver.security: error org.geoserver.cloud: info + org.geoserver.cloud.config.catalog: warn org.geoserver.cloud.config.factory: info org.geowebcache.config.XMLConfiguration: off org.springframework.test: info diff --git a/src/apps/infrastructure/gateway/pom.xml b/src/apps/infrastructure/gateway/pom.xml index aa4ec4472..62c45551d 100644 --- a/src/apps/infrastructure/gateway/pom.xml +++ b/src/apps/infrastructure/gateway/pom.xml @@ -12,6 +12,7 @@ false geoserver-cloud-gateway + 4.1.94.Final diff --git a/src/gwc/backends/pom.xml b/src/gwc/backends/pom.xml index 744b56b38..bf790bf91 100644 --- a/src/gwc/backends/pom.xml +++ b/src/gwc/backends/pom.xml @@ -27,5 +27,10 @@ provided true + + org.testcontainers + junit-jupiter + test + diff --git a/src/gwc/backends/src/test/java/org/geoserver/cloud/gwc/config/blobstore/AzureBlobStoreTest.java b/src/gwc/backends/src/test/java/org/geoserver/cloud/gwc/config/blobstore/AzureBlobStoreTest.java new file mode 100644 index 000000000..426ed612c --- /dev/null +++ b/src/gwc/backends/src/test/java/org/geoserver/cloud/gwc/config/blobstore/AzureBlobStoreTest.java @@ -0,0 +1,119 @@ +/* + * (c) 2023 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.blobstore; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.apache.commons.io.IOUtils; +import org.geowebcache.GeoWebCacheEnvironment; +import org.geowebcache.GeoWebCacheExtensions; +import org.geowebcache.azure.AzureBlobStore; +import org.geowebcache.azure.AzureBlobStoreInfo; +import org.geowebcache.io.ByteArrayResource; +import org.geowebcache.io.Resource; +import org.geowebcache.layer.TileLayer; +import org.geowebcache.layer.TileLayerDispatcher; +import org.geowebcache.locks.MemoryLockProvider; +import org.geowebcache.storage.BlobStore; +import org.geowebcache.storage.StorageException; +import org.geowebcache.storage.TileObject; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.context.support.StaticApplicationContext; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.Map; + +/** + * Verify {@link AzureBlobStore} works by connecting to an Azurite container + * + * @see AzuriteContainer + */ +@Testcontainers +public class AzureBlobStoreTest { + + @Container static AzuriteContainer azurite = new AzuriteContainer(); + + protected AzureBlobStoreInfo newAzureBlobStoreInfo() { + AzureBlobStoreInfo bsi = new AzureBlobStoreInfo(); + + String accountName = azurite.getAccountName(); + String accountKey = azurite.getAccountKey(); + String blobsUrl = azurite.getBlobsUrl(); + + bsi.setAccountName(accountName); + bsi.setAccountKey(accountKey); + bsi.setServiceURL(blobsUrl); + bsi.setUseHTTPS(false); + bsi.setContainer("azureblobstoretest"); + bsi.setName("AzureBlobStoreTest"); + bsi.setEnabled(true); + // bsi.setPrefix("/gwc/"); + return bsi; + } + + static StaticApplicationContext stubAppContext; + + static @BeforeAll void setUpContext() { + GeoWebCacheExtensions extensions = new GeoWebCacheExtensions(); + GeoWebCacheEnvironment environment = new GeoWebCacheEnvironment(); + + stubAppContext = new StaticApplicationContext(); + stubAppContext.registerBean(GeoWebCacheExtensions.class, () -> extensions); + stubAppContext.registerBean(GeoWebCacheEnvironment.class, () -> environment); + stubAppContext.refresh(); + } + + static @AfterAll void closeContext() { + stubAppContext.close(); + } + + public @Test void createBlobStore() throws StorageException { + AzureBlobStoreInfo info = newAzureBlobStoreInfo(); + BlobStore store = + info.createInstance(mock(TileLayerDispatcher.class), new MemoryLockProvider()); + assertThat(store).isInstanceOf(AzureBlobStore.class); + } + + public @Test void testPutGet() throws Exception { + TileLayerDispatcher layers = mock(TileLayerDispatcher.class); + + AzureBlobStoreInfo info = newAzureBlobStoreInfo(); + AzureBlobStore store = + (AzureBlobStore) info.createInstance(layers, new MemoryLockProvider()); + + final String layerName = "FakeLayer"; + final long[] xyz = new long[] {0, 0, 0}; + final String gridSetId = "EPSG:3857"; + final String format = "image/png"; + final Map parameters = null; + byte[] contents = new byte[] {1, 2, 3, 4, 5, 6, 7}; + final Resource blob = new ByteArrayResource(contents); + + TileLayer tileLayer = mock(TileLayer.class); + when(tileLayer.getId()).thenReturn(layerName); + when(layers.getTileLayer(eq(layerName))).thenReturn(tileLayer); + + TileObject tile = + TileObject.createCompleteTileObject( + layerName, xyz, gridSetId, format, parameters, blob); + store.put(tile); + + TileObject query = + TileObject.createQueryTileObject(layerName, xyz, gridSetId, format, parameters); + + // can't really test get, see https://github.com/Azure/Azurite/issues/217 + if (true) return; + assertThat(store.get(query)).isTrue(); + assertThat(query.getBlob()).isNotNull(); + byte[] readContents = IOUtils.toByteArray(query.getBlob().getInputStream()); + assertThat(readContents).isEqualTo(contents); + } +} diff --git a/src/gwc/backends/src/test/java/org/geoserver/cloud/gwc/config/blobstore/AzuriteContainer.java b/src/gwc/backends/src/test/java/org/geoserver/cloud/gwc/config/blobstore/AzuriteContainer.java new file mode 100644 index 000000000..80ed13b26 --- /dev/null +++ b/src/gwc/backends/src/test/java/org/geoserver/cloud/gwc/config/blobstore/AzuriteContainer.java @@ -0,0 +1,50 @@ +package org.geoserver.cloud.gwc.config.blobstore; + +import lombok.Getter; +import lombok.NonNull; + +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +/** + * {@link Testcontainers} container for AWS Azurite blobstore test environment. + * + *

Runs the Azurite + * emulator for local Azure Storage development with testcontainers. + * + *

Azurite accepts the same well-known account and key used by the legacy Azure Storage Emulator. + * + *

+ */ +public class AzuriteContainer extends GenericContainer { + + private static final @NonNull DockerImageName IMAGE_NAME = + DockerImageName.parse("mcr.microsoft.com/azure-storage/azurite:latest"); + + public final @Getter String accountName = "devstoreaccount1"; + public final @Getter String accountKey = + "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="; + + private final int blobsPort = 10_000; + + public AzuriteContainer() { + super(IMAGE_NAME); + super.setWaitStrategy(Wait.forListeningPort()); + super.addExposedPort(blobsPort); + } + + public int getBlobsPort() { + return super.getMappedPort(blobsPort); + } + + public String getBlobsUrl() { + return "http://localhost:%d/%s".formatted(getBlobsPort(), getAccountName()); + } +} diff --git a/src/gwc/backends/src/test/resources/logback-test.xml b/src/gwc/backends/src/test/resources/logback-test.xml new file mode 100644 index 000000000..84b8339d1 --- /dev/null +++ b/src/gwc/backends/src/test/resources/logback-test.xml @@ -0,0 +1,16 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger [%X{instance-id}] %msg%n + + + + + + + + + + + + \ No newline at end of file diff --git a/src/pom.xml b/src/pom.xml index 9cd43b3a4..8bf26f9da 100644 --- a/src/pom.xml +++ b/src/pom.xml @@ -28,10 +28,8 @@ 2.23-CLOUD 29-SNAPSHOT 1.0.2 - - + 4.1.41.Final 1.18.30 1.4.2.Final @@ -904,6 +902,7 @@ true false + --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.text=ALL-UNNAMED --add-opens=java.desktop/java.awt.font=ALL-UNNAMED --add-opens=java.desktop/sun.awt.image=ALL-UNNAMED --add-opens=java.naming/com.sun.jndi.ldap=ALL-UNNAMED @@ -1113,57 +1112,57 @@ io.netty netty-buffer - 4.1.94.Final + ${netty.version} io.netty netty-codec - 4.1.94.Final + ${netty.version} io.netty netty-codec-http - 4.1.94.Final + ${netty.version} io.netty netty-codec-http2 - 4.1.94.Final + ${netty.version} io.netty netty-codec-socks - 4.1.94.Final + ${netty.version} io.netty netty-common - 4.1.94.Final + ${netty.version} io.netty netty-handler - 4.1.94.Final + ${netty.version} io.netty netty-handler-proxy - 4.1.94.Final + ${netty.version} io.netty netty-resolver - 4.1.94.Final + ${netty.version} io.netty netty-transport - 4.1.94.Final + ${netty.version} io.netty netty-transport-native-unix-common - 4.1.94.Final + ${netty.version}