/* (c) 2017 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.cluster.integration; import org.geoserver.catalog.AttributionInfo; import org.geoserver.catalog.Catalog; import org.geoserver.catalog.CoverageInfo; import org.geoserver.catalog.CoverageStoreInfo; import org.geoserver.catalog.DataStoreInfo; import org.geoserver.catalog.FeatureTypeInfo; import org.geoserver.catalog.LayerGroupInfo; import org.geoserver.catalog.LayerGroupInfo.Mode; import org.geoserver.catalog.LayerInfo; import org.geoserver.catalog.NamespaceInfo; import org.geoserver.catalog.ProjectionPolicy; import org.geoserver.catalog.PublishedType; import org.geoserver.catalog.StyleInfo; import org.geoserver.catalog.WMSLayerInfo; import org.geoserver.catalog.WMSStoreInfo; import org.geoserver.catalog.WorkspaceInfo; import org.geoserver.catalog.impl.AttributionInfoImpl; import org.geoserver.catalog.impl.CoverageInfoImpl; import org.geoserver.catalog.impl.CoverageStoreInfoImpl; import org.geoserver.catalog.impl.DataStoreInfoImpl; import org.geoserver.catalog.impl.FeatureTypeInfoImpl; import org.geoserver.catalog.impl.LayerGroupInfoImpl; import org.geoserver.catalog.impl.LayerInfoImpl; import org.geoserver.catalog.impl.NamespaceInfoImpl; import org.geoserver.catalog.impl.StyleInfoImpl; import org.geoserver.catalog.impl.WMSLayerInfoImpl; import org.geoserver.catalog.impl.WMSStoreInfoImpl; import org.geoserver.catalog.impl.WorkspaceInfoImpl; import org.geoserver.config.ContactInfo; import org.geoserver.config.GeoServer; import org.geoserver.config.GeoServerInfo; import org.geoserver.config.ServiceInfo; import org.geoserver.config.SettingsInfo; import org.geoserver.config.impl.SettingsInfoImpl; import org.geoserver.data.test.MockData; import org.geoserver.platform.resource.Resource; import org.geoserver.util.IOUtils; import org.geoserver.wms.WMSInfo; import org.geoserver.wms.WMSInfoImpl; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.referencing.crs.DefaultGeographicCRS; import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; import java.io.File; import java.io.InputStream; import java.io.OutputStream; import java.util.Arrays; import java.util.List; import java.util.UUID; import static org.geoserver.cluster.integration.IntegrationTestsUtils.checkNoDifferences; import static org.geoserver.cluster.integration.IntegrationTestsUtils.differences; import static org.geoserver.cluster.integration.IntegrationTestsUtils.equalizeInstances; import static org.geoserver.cluster.integration.IntegrationTestsUtils.resetEventsCount; import static org.geoserver.cluster.integration.IntegrationTestsUtils.resetJmsConfiguration; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.junit.Assert.assertThat; /** * Integration tests for JMS that tests that GeoServer configurations events and GeoServer * catalog events are correctly propagated and handled. */ public final class IntegrationTest { // instantiate some GeoServer instances private static final GeoServerInstance INSTANCE_A = new GeoServerInstance("INSTANCE-A"); private static final GeoServerInstance INSTANCE_B = new GeoServerInstance("INSTANCE-B"); private static final GeoServerInstance INSTANCE_C = new GeoServerInstance("INSTANCE-C"); private static final GeoServerInstance INSTANCE_D = new GeoServerInstance("INSTANCE-D"); private static final GeoServerInstance[] INSTANCES = new GeoServerInstance[]{ INSTANCE_A, INSTANCE_B, INSTANCE_C, INSTANCE_D }; @Before public void resetInstances() { // disable JMS before equalizing the instances configuration and catalog Arrays.stream(INSTANCES).forEach(instance -> { instance.disableJmsMaster(); instance.disableJmsSlave(); }); // equalize the configuration and the catalog equalizeInstances(INSTANCES); // reset JMS configuration and events count resetJmsConfiguration(INSTANCES); resetEventsCount(INSTANCES); } @AfterClass public static void tearDown() { // destroy all instances Arrays.stream(INSTANCES).forEach(GeoServerInstance::destroy); } @Test public void testConfigurationMastersSlavesApplyToMaster() throws Exception { // assert instances are equal checkNoDifferences(INSTANCES); // use instance A for control INSTANCE_A.disableJmsMaster(); INSTANCE_A.disableJmsSlave(); // instance B will be a pure master and instances C and D pure slaves INSTANCE_B.disableJmsSlave(); INSTANCE_C.disableJmsMaster(); INSTANCE_D.disableJmsMaster(); // apply add and modify configuration changes to master applyAddModifyConfigurationChanges(INSTANCE_B); // check instance C waitAndCheckEvents(INSTANCE_C, 9); List<InfoDiff> differences = differences(INSTANCE_B, INSTANCE_C); assertThat(differences.size(), is(0)); // check instance D waitAndCheckEvents(INSTANCE_D, 9); differences = differences(INSTANCE_B, INSTANCE_D); assertThat(differences.size(), is(0)); // check instance A waitAndCheckEvents(INSTANCE_A, 0); differences = differences(INSTANCE_B, INSTANCE_A); assertThat(differences.size(), is(4)); // apply remove configuration changes to master applyDeleteConfigurationChanges(INSTANCE_B); // check instance C waitAndCheckEvents(INSTANCE_C, 4); differences = differences(INSTANCE_B, INSTANCE_C); assertThat(differences.size(), is(0)); // check instance D waitAndCheckEvents(INSTANCE_D, 4); differences = differences(INSTANCE_B, INSTANCE_D); assertThat(differences.size(), is(0)); // check instance A waitAndCheckEvents(INSTANCE_A, 0); differences = differences(INSTANCE_B, INSTANCE_A); assertThat(differences.size(), is(2)); } @Test public void testConfigurationMastersSlavesApplyToSlave() throws Exception { // assert instances are equal checkNoDifferences(INSTANCES); // use instance A for control INSTANCE_A.disableJmsMaster(); INSTANCE_A.disableJmsSlave(); // instance B will be a pure master and instances C and D pure slaves INSTANCE_B.disableJmsSlave(); INSTANCE_C.disableJmsMaster(); INSTANCE_D.disableJmsMaster(); // apply add and modify configuration changes to slave applyAddModifyConfigurationChanges(INSTANCE_C); // check instance A waitNoEvents(INSTANCE_A, 100); List<InfoDiff> differences = differences(INSTANCE_C, INSTANCE_A); assertThat(differences.size(), is(4)); // check instance B waitNoEvents(INSTANCE_B, 100); differences = differences(INSTANCE_C, INSTANCE_B); assertThat(differences.size(), is(4)); // check instance D waitNoEvents(INSTANCE_D, 100); differences = differences(INSTANCE_C, INSTANCE_D); assertThat(differences.size(), is(4)); // apply remove configuration changes to slave applyDeleteConfigurationChanges(INSTANCE_C); // check instance A waitNoEvents(INSTANCE_A, 100); differences = differences(INSTANCE_C, INSTANCE_A); assertThat(differences.size(), is(2)); // check instance B waitNoEvents(INSTANCE_C, 100); differences = differences(INSTANCE_C, INSTANCE_B); assertThat(differences.size(), is(2)); // check instance D waitNoEvents(INSTANCE_D, 100); differences = differences(INSTANCE_C, INSTANCE_D); assertThat(differences.size(), is(2)); } @Test public void testCatalogMastersSlavesApplyToMaster() throws Exception { // assert instances are equal checkNoDifferences(INSTANCES); // use instance A for control INSTANCE_A.disableJmsMaster(); INSTANCE_A.disableJmsSlave(); // instance B will be a pure master and instances C and D pure slaves INSTANCE_B.disableJmsSlave(); INSTANCE_C.disableJmsMaster(); INSTANCE_D.disableJmsMaster(); // apply catalog add changes to master applyAddCatalogChanges(INSTANCE_B); // check instance C waitAndCheckEvents(INSTANCE_C, 25); List<InfoDiff> differences = differences(INSTANCE_B, INSTANCE_C); assertThat(differences.size(), is(0)); // check instance D waitAndCheckEvents(INSTANCE_D, 25); differences = differences(INSTANCE_B, INSTANCE_D); assertThat(differences.size(), is(0)); // check instance A waitAndCheckEvents(INSTANCE_A, 0); differences = differences(INSTANCE_B, INSTANCE_A); assertThat(differences.size(), is(11)); // apply modify changes to the catalog applyModifyCatalogChanges(INSTANCE_B); // check instance C waitAndCheckEvents(INSTANCE_C, 20); differences = differences(INSTANCE_B, INSTANCE_C); assertThat(differences.size(), is(0)); // check instance D waitAndCheckEvents(INSTANCE_D, 20); differences = differences(INSTANCE_B, INSTANCE_D); assertThat(differences.size(), is(0)); // check instance A waitAndCheckEvents(INSTANCE_A, 0); differences = differences(INSTANCE_B, INSTANCE_A); assertThat(differences.size(), is(11)); // apply catalog delete events applyDeleteCatalogChanges(INSTANCE_B); // check instance C waitAndCheckEvents(INSTANCE_C, 28); differences = differences(INSTANCE_B, INSTANCE_C); assertThat(differences.size(), is(0)); // check instance D waitAndCheckEvents(INSTANCE_D, 28); differences = differences(INSTANCE_B, INSTANCE_D); assertThat(differences.size(), is(0)); // check instance A waitAndCheckEvents(INSTANCE_A, 0); differences = differences(INSTANCE_B, INSTANCE_A); assertThat(differences.size(), is(0)); } /** * Helper methods that waits a specified amount of time and checks * that no events were consumed. */ private void waitNoEvents(GeoServerInstance instance, int waitTimeMs) { try { // wait the specified amount of time Thread.sleep(waitTimeMs); } catch (InterruptedException exception) { // well we got interrupted Thread.currentThread().interrupt(); } // check that no events were consumed assertThat(instance.getConsumedEventsCount(), is(0)); } /** * Waits for the expected number of events to be consumed or for the timeout * of two seconds to be reached and then checks if the expected number of * events were consumed. */ private void waitAndCheckEvents(GeoServerInstance instance, int expectedEvents) { instance.waitEvents(expectedEvents, 2000); assertThat(instance.getConsumedEventsCount(), is(expectedEvents)); instance.resetConsumedEventsCount(); } /** * Helper method that adds some new services and settings to the provided * GeoServer instance and also modifies some existing ones. */ private void applyAddModifyConfigurationChanges(GeoServerInstance instance) { GeoServer geoServer = instance.getGeoServer(); Catalog catalog = instance.getCatalog(); WorkspaceInfo workspace = catalog.getWorkspaceByName(MockData.DEFAULT_PREFIX); // change GeoServer global settings GeoServerInfo geoServerInfo = geoServer.getGlobal(); SettingsInfo geoServerSettings = geoServerInfo.getSettings(); ContactInfo geoServerContact = geoServerSettings.getContact(); geoServerContact.setContactPerson(randomString()); geoServerSettings.setContact(geoServerContact); geoServerInfo.setSettings(geoServerSettings); geoServer.save(geoServerInfo); // create workspace specific settings assertThat(workspace, notNullValue()); SettingsInfo workspaceSettings = new SettingsInfoImpl(); workspaceSettings.setTitle(randomString()); workspaceSettings.setWorkspace(workspace); geoServer.add(workspaceSettings); // change WMS service settings ServiceInfo wmsService = geoServer.getService(WMSInfo.class); wmsService.setAbstract(randomString()); geoServer.save(wmsService); // create workspace specific settings for WMS service WMSInfoImpl workspaceWmsService = new WMSInfoImpl(); workspaceWmsService.setName(randomString()); workspaceWmsService.setTitle(randomString()); workspaceWmsService.setWorkspace(workspace); geoServer.add(workspaceWmsService); } /** * Helper method that removes some services and settings from the provided * GeoServer instance. */ private void applyDeleteConfigurationChanges(GeoServerInstance instance) { GeoServer geoServer = instance.getGeoServer(); Catalog catalog = instance.getCatalog(); WorkspaceInfo workspace = catalog.getWorkspaceByName(MockData.DEFAULT_PREFIX); // remove workspace specific settings geoServer.remove(geoServer.getSettings(workspace)); // remove WMS workspace specific settings geoServer.remove(geoServer.getService(workspace, WMSInfo.class)); } /** * Helper method that add some new catalog elements to the provided GeoServer instance. */ private void applyAddCatalogChanges(GeoServerInstance instance) { // instantiate some common objects Catalog catalog = instance.getCatalog(); ReferencedEnvelope envelope = new ReferencedEnvelope(-1.0, 1.0, -2.0, 2.0, DefaultGeographicCRS.WGS84); AttributionInfo attribution = new AttributionInfoImpl(); attribution.setTitle("attribution-Title"); attribution.setHref("attribution-Href"); attribution.setLogoURL("attribution-LogoURL"); attribution.setLogoType("attribution-LogoType"); attribution.setLogoWidth(500); attribution.setLogoHeight(600); // add workspace WorkspaceInfo workspace = new WorkspaceInfoImpl(); workspace.setName("workspace-Name"); catalog.add(workspace); // add namespace NamespaceInfo namespace = new NamespaceInfoImpl(); namespace.setPrefix(workspace.getName()); namespace.setURI("namespace-URI"); catalog.add(namespace); // add data store DataStoreInfo dataStore = new DataStoreInfoImpl(catalog); dataStore.setEnabled(false); dataStore.setName("dataStore-Name"); dataStore.setWorkspace(workspace); dataStore.setType("dataStore-Type"); dataStore.setDescription("dataStore-Description"); catalog.add(dataStore); // add coverage store CoverageStoreInfo coverageStore = new CoverageStoreInfoImpl(catalog); coverageStore.setEnabled(false); coverageStore.setName("coverageStore-Name"); coverageStore.setWorkspace(workspace); coverageStore.setType("coverageStore-Type"); coverageStore.setDescription("coverageStore-Description"); catalog.add(coverageStore); // add WMS store WMSStoreInfo wmsStore = new WMSStoreInfoImpl(catalog); wmsStore.setEnabled(false); wmsStore.setName("wmsStore-Name"); wmsStore.setWorkspace(workspace); wmsStore.setType("wmsStore-Type"); wmsStore.setDescription("wmsStore-Description"); wmsStore.setCapabilitiesURL("wmsStore-CapabilitiesURL"); wmsStore.setUseConnectionPooling(false); wmsStore.setUsername("wmsStore-Username"); wmsStore.setPassword("wmsStore-Password"); wmsStore.setMaxConnections(0); wmsStore.setReadTimeout(0); wmsStore.setConnectTimeout(0); catalog.add(wmsStore); // add feature type FeatureTypeInfo featureType = new FeatureTypeInfoImpl(catalog); featureType.setName("featureType-Name"); featureType.setNativeName("featureType-NativeName"); featureType.setNamespace(namespace); featureType.setTitle("featureType-Title"); featureType.setDescription("featureType-Description"); featureType.setAbstract("featureType-Abstract"); featureType.setSRS("EPSG:4326"); featureType.setLatLonBoundingBox(envelope); featureType.setEnabled(false); featureType.setStore(dataStore); featureType.setNativeBoundingBox(envelope); featureType.setNativeCRS(DefaultGeographicCRS.WGS84); featureType.setAdvertised(false); featureType.setMaxFeatures(100); featureType.setNumDecimals(4); featureType.setOverridingServiceSRS(false); featureType.setSkipNumberMatched(false); featureType.setProjectionPolicy(ProjectionPolicy.FORCE_DECLARED); catalog.add(featureType); // add coverage CoverageInfo coverage = new CoverageInfoImpl(catalog); coverage.setName("coverage-Name"); coverage.setNativeName("coverage-NativeName"); coverage.setNamespace(namespace); coverage.setAbstract("coverage-Abstract"); coverage.setDescription("coverage-Description"); coverage.setLatLonBoundingBox(envelope); coverage.setNativeBoundingBox(envelope); coverage.setSRS("EPSG:4326"); coverage.setNativeCRS(DefaultGeographicCRS.WGS84); coverage.setEnabled(false); coverage.setStore(coverageStore); coverage.setAdvertised(false); coverage.setNativeCoverageName("coverage-NativeCoverageName"); coverage.setProjectionPolicy(ProjectionPolicy.FORCE_DECLARED); catalog.add(coverage); // add style info and style file copyStyle(instance, "/test_style.sld", "test_style.sld"); StyleInfo style = new StyleInfoImpl(catalog); style.setName("style-Name"); style.setFormat("sld"); style.setFilename("test_style.sld"); catalog.add(style); // add layer info LayerInfo layer = new LayerInfoImpl(); layer.setResource(featureType); layer.setAbstract("layer-Abstract"); layer.setAttribution(attribution); layer.setType(PublishedType.VECTOR); layer.setDefaultStyle(style); layer.setEnabled(false); layer.setQueryable(false); layer.setOpaque(false); layer.setAdvertised(false); catalog.add(layer); // add WMS layer info WMSLayerInfo wmsLayer = new WMSLayerInfoImpl(catalog); wmsLayer.setName("wmsLayer-Name"); wmsLayer.setNativeName("wmsLayer-NativeName"); wmsLayer.setNamespace(namespace); wmsLayer.setTitle("wmsLayer-Title"); wmsLayer.setAbstract("wmsLayer-Abstract"); wmsLayer.setDescription("wmsLayer-Description"); wmsLayer.setLatLonBoundingBox(envelope); wmsLayer.setNativeBoundingBox(envelope); wmsLayer.setSRS("EPSG:4326"); wmsLayer.setNativeCRS(DefaultGeographicCRS.WGS84); wmsLayer.setEnabled(false); wmsLayer.setStore(wmsStore); wmsLayer.setAdvertised(false); catalog.add(wmsLayer); // layer group LayerGroupInfo layerGroup = new LayerGroupInfoImpl(); layerGroup.setTitle("layerGroup-Title"); layerGroup.setName("layerGroup-Name"); layerGroup.setMode(Mode.SINGLE); layerGroup.setQueryDisabled(false); layerGroup.setBounds(envelope); layerGroup.getLayers().add(layer); layerGroup.getStyles().add(style); catalog.add(layerGroup); } /** * Helper method that apply some catalog changes to the provided GeoServer instance. */ private void applyModifyCatalogChanges(GeoServerInstance instance) { Catalog catalog = instance.getCatalog(); // change namespace NamespaceInfo namespace = catalog.getNamespaceByPrefix("workspace-Name"); namespace.setURI("namespace-URI-modified"); catalog.save(namespace); // change data store DataStoreInfo dataStore = catalog.getDataStoreByName("dataStore-Name"); dataStore.setDescription("dataStore-Description-modified"); catalog.save(dataStore); // change coverage store CoverageStoreInfo coverageStore = catalog.getCoverageStoreByName("coverageStore-Name"); coverageStore.setDescription("coverageStore-Description-modified"); catalog.save(coverageStore); // change WMS store WMSStoreInfo wmsStore = catalog.getStoreByName("wmsStore-Name", WMSStoreInfo.class); wmsStore.setDescription("wmsStore-Description-modified"); catalog.save(wmsStore); // change feature type FeatureTypeInfo featureType = catalog.getFeatureTypeByName("featureType-Name"); featureType.setDescription("featureType-Description-modified"); catalog.save(featureType); // change coverage CoverageInfo coverage = catalog.getCoverageByName("coverage-Name"); coverage.setAbstract("coverage-Abstract-modified"); catalog.save(coverage); // change style info StyleInfo style = catalog.getStyleByName("style-Name"); style.setName("style-Name-modified"); catalog.save(style); // change layer info LayerInfo layer = catalog.getLayerByName("featureType-Name"); layer.setAbstract("layer-Abstract-modified"); catalog.save(layer); // change WMS layer info WMSLayerInfo wmsLayer = catalog.getResourceByName("wmsLayer-Name", WMSLayerInfo.class); wmsLayer.setAbstract("wmsLayer-Abstract-modified"); catalog.save(wmsLayer); // change group LayerGroupInfo layerGroup = catalog.getLayerGroupByName("layerGroup-Name"); layerGroup.setTitle("layerGroup-Title-modified"); catalog.save(layerGroup); } /** * Helper method that removes some elements from the catalog of the provided instance. */ private void applyDeleteCatalogChanges(GeoServerInstance instance) { Catalog catalog = instance.getCatalog(); // delete group LayerGroupInfo layerGroup = catalog.getLayerGroupByName("layerGroup-Name"); catalog.remove(layerGroup); // delete layer info LayerInfo layer = catalog.getLayerByName("featureType-Name"); catalog.remove(layer); // delete WMS layer info WMSLayerInfo wmsLayer = catalog.getResourceByName("wmsLayer-Name", WMSLayerInfo.class); catalog.remove(wmsLayer); // delete feature type FeatureTypeInfo featureType = catalog.getFeatureTypeByName("featureType-Name"); catalog.remove(featureType); // delete style info StyleInfo style = catalog.getStyleByName("style-Name-modified"); catalog.remove(style); // delete coverage CoverageInfo coverage = catalog.getCoverageByName("coverage-Name"); catalog.remove(coverage); // delete data store DataStoreInfo dataStore = catalog.getDataStoreByName("dataStore-Name"); catalog.remove(dataStore); // delete coverage store CoverageStoreInfo coverageStore = catalog.getCoverageStoreByName("coverageStore-Name"); catalog.remove(coverageStore); // delete WMS store WMSStoreInfo wmsStore = catalog.getStoreByName("wmsStore-Name", WMSStoreInfo.class); catalog.remove(wmsStore); // delete namespace NamespaceInfo namespace = catalog.getNamespaceByPrefix("workspace-Name"); catalog.remove(namespace); } /** * Helper method that copies a style file to the provided GeoServer instance. */ private void copyStyle(GeoServerInstance instance, String resource, String fileName) { Resource styleResource = instance.getDataDirectory() .get("styles" + File.separator + fileName); try (OutputStream output = styleResource.out(); InputStream input = this.getClass().getResourceAsStream(resource)) { IOUtils.copy(input, output); } catch (Exception exception) { throw new RuntimeException("Error copying test style.", exception); } } /** * Helper method that simply returns a random string. */ public static String randomString() { return UUID.randomUUID().toString(); } }