From 5eff0574688cc721dbb8e0022f0522dcc2ff96a5 Mon Sep 17 00:00:00 2001 From: dev-mlb <19797865+dev-mlb@users.noreply.github.com> Date: Sun, 29 Dec 2024 12:31:44 -0500 Subject: [PATCH] update ServiceProviderPlace to support config refresh --- .../emissary/directory/DirectoryPlace.java | 4 - .../emissary/place/ServiceProviderPlace.java | 73 ++++++++++++++++++- .../place/ServiceProviderPlaceTest.java | 49 +++++++++++++ 3 files changed, 120 insertions(+), 6 deletions(-) diff --git a/src/main/java/emissary/directory/DirectoryPlace.java b/src/main/java/emissary/directory/DirectoryPlace.java index 225b19c161..58172be310 100755 --- a/src/main/java/emissary/directory/DirectoryPlace.java +++ b/src/main/java/emissary/directory/DirectoryPlace.java @@ -1186,10 +1186,6 @@ public int removePlaces(final List keys) { */ @Override public int irdRemovePlaces(@Nullable final List keys, final boolean propagating) { - if (this.emissaryNode.isStandalone()) { - logger.debug("Cannot remove remote places in standalone nodes"); - return 0; - } if ((keys == null) || keys.isEmpty()) { logger.warn("Ignoring null or empty key list for irdRemovePlaces"); diff --git a/src/main/java/emissary/place/ServiceProviderPlace.java b/src/main/java/emissary/place/ServiceProviderPlace.java index 175e78fab6..862c274489 100755 --- a/src/main/java/emissary/place/ServiceProviderPlace.java +++ b/src/main/java/emissary/place/ServiceProviderPlace.java @@ -27,6 +27,7 @@ import emissary.util.JMXUtil; import com.codahale.metrics.Timer; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -110,6 +111,9 @@ public abstract class ServiceProviderPlace implements IServiceProviderPlace, @Nullable protected String serviceDescription; + private String placeLocation; + private final List configLocs = new ArrayList<>(); + /** * Static context logger */ @@ -253,12 +257,11 @@ protected Configurator loadConfigurator(@Nullable String placeLocation) throws I if (placeLocation == null) { placeLocation = this.getClass().getSimpleName(); } - + this.placeLocation = placeLocation; // Extract config data stream name from place location // and try finding config info with and without the // package name of this class (in that order) String myPackage = this.getClass().getPackage().getName(); - List configLocs = new ArrayList<>(); // Dont use KeyManipulator for this, only works when hostname/fqdn has dots int pos = placeLocation.lastIndexOf("/"); String serviceClass = (pos > -1 ? placeLocation.substring(pos + 1) : placeLocation); @@ -925,6 +928,72 @@ protected void deregisterFromDirectory(List keys) { } } + /** + * Reset the keys and reload the {@link Configurator} + * + * @throws IOException if there is an issue loading the config + */ + protected void refresh() throws IOException { + clearKeys(); + this.configG = refreshConfigurator(); + setupPlace(this.dirPlace, this.placeLocation); + } + + /** + * Reset the keys and reload the {@link Configurator} + * + * @param configStream the configuration input stream to load + * @throws IOException if there is an issue loading the config + */ + protected void refresh(@Nullable final InputStream configStream) throws IOException { + clearKeys(); + this.configG = refreshConfigurator(configStream); + setupPlace(this.dirPlace, this.placeLocation); + } + + /** + * Reload the {@link Configurator} + * + * @throws IOException if there is an issue loading the config + */ + protected Configurator refreshConfigurator() throws IOException { + return refreshConfigurator(this.configLocs); + } + + /** + * Reload the {@link Configurator} + * + * @param configLocations the list of configuration files to load + * @throws IOException if there is an issue loading the config + */ + protected Configurator refreshConfigurator(@Nullable final List configLocations) throws IOException { + if (CollectionUtils.isNotEmpty(configLocations)) { + return ConfigUtil.getConfigInfo(configLocations); + } + throw new IOException("No config locations specified"); + } + + /** + * Reload the {@link Configurator} + * + * @param configStream the stream of configuration data + * @throws IOException if there is an issue loading the config + */ + protected Configurator refreshConfigurator(@Nullable final InputStream configStream) throws IOException { + if (configStream != null) { + return ConfigUtil.getConfigInfo(configStream); + } + throw new IOException("Null config stream supplied"); + } + + /** + * Unbind from the namespace and clear all loaded keys + */ + protected void clearKeys() { + unbindFromNamespace(); + new ArrayList<>(this.keys).forEach(this::removeKey); + this.keys.clear(); + } /** * Remove a service proxy from the running place. Proxy strings not found registered will be ignored Will remove all diff --git a/src/test/java/emissary/place/ServiceProviderPlaceTest.java b/src/test/java/emissary/place/ServiceProviderPlaceTest.java index 526d4dbafd..183c9d0270 100644 --- a/src/test/java/emissary/place/ServiceProviderPlaceTest.java +++ b/src/test/java/emissary/place/ServiceProviderPlaceTest.java @@ -5,6 +5,7 @@ import emissary.core.EmissaryException; import emissary.core.IBaseDataObject; import emissary.core.Namespace; +import emissary.core.NamespaceException; import emissary.directory.DirectoryEntry; import emissary.directory.KeyManipulator; import emissary.test.core.junit5.UnitTest; @@ -24,6 +25,7 @@ import java.util.Set; import javax.annotation.Nullable; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertInstanceOf; @@ -552,6 +554,53 @@ void testDeniedServiceProxy() { } } + private static final byte[] cfgData = ("SERVICE_KEY = \"UNKNOWN.TEST_PLACE.ID.http://localhost:8001/PlaceTest$6050\"\n" + + "KEY_1 = 200").getBytes(); + private static final byte[] cfgDataReload = ("SERVICE_KEY = \"*.TEST_PLACE.ID.http://localhost:8001/PlaceTest$5060\"\n" + + "KEY_1 = 300").getBytes(); + + @Test + void refreshConfigurator() throws IOException { + PlaceTest placeTest = new PlaceTest(new ByteArrayInputStream(cfgData)); + assertNotNull(placeTest, "Place created and configured"); + assertEquals("PlaceTest", placeTest.getPlaceName(), "Configured place name"); + assertEquals("UNKNOWN", placeTest.getPrimaryProxy(), "Primary proxy"); + assertEquals("UNKNOWN.TEST_PLACE.ID.http://localhost:8001/PlaceTest", placeTest.getKey(), "Key generation"); + DirectoryEntry de = placeTest.getDirectoryEntry(); + assertNotNull(de, "Directory entry"); + assertEquals(60, de.getCost(), "Cost in directory entry"); + assertEquals(50, de.getQuality(), "Quality in directory entry"); + assertEquals("Description not available", de.getDescription(), "Description in directory entry"); + assertNotNull(placeTest.configG); + assertEquals(200, placeTest.configG.findIntEntry("KEY_1", 0)); + assertDoesNotThrow(() -> Namespace.lookup("http://localhost:8001/PlaceTest")); + + placeTest.refresh(new ByteArrayInputStream(cfgDataReload)); + assertNotNull(placeTest, "Place created and configured"); + assertEquals("PlaceTest", placeTest.getPlaceName(), "Configured place name"); + assertEquals("*", placeTest.getPrimaryProxy(), "Primary proxy"); + assertEquals("*.TEST_PLACE.ID.http://localhost:8001/PlaceTest", placeTest.getKey(), "Key generation"); + de = placeTest.getDirectoryEntry(); + assertNotNull(de, "Directory entry"); + assertEquals(50, de.getCost(), "Cost in directory entry"); + assertEquals(40, de.getQuality(), "Quality in directory entry"); + assertEquals("Description not available", de.getDescription(), "Description in directory entry"); + assertNotNull(placeTest.configG); + assertEquals(300, placeTest.configG.findIntEntry("KEY_1", 0)); + assertDoesNotThrow(() -> Namespace.lookup("http://localhost:8001/PlaceTest")); + } + + @Test + void testClearKeys() throws IOException { + PlaceTest placeTest = new PlaceTest(new ByteArrayInputStream(cfgData)); + assertFalse(placeTest.keys.isEmpty()); + assertDoesNotThrow(() -> Namespace.lookup("http://localhost:8001/PlaceTest")); + + placeTest.clearKeys(); + assertTrue(placeTest.keys.isEmpty()); + assertThrows(NamespaceException.class, () -> Namespace.lookup("http://localhost:8001/PlaceTest")); + } + private static final class PlaceTest extends ServiceProviderPlace { public PlaceTest() throws IOException {