Skip to content

Commit

Permalink
addressing unexpected place startups (#598)
Browse files Browse the repository at this point in the history
  • Loading branch information
sambish5 authored Nov 7, 2023
1 parent a35b765 commit 5794d03
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 3 deletions.
60 changes: 58 additions & 2 deletions src/main/java/emissary/admin/Startup.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
import emissary.config.ServiceConfigGuide;
import emissary.core.EmissaryException;
import emissary.core.Namespace;
import emissary.directory.DirectoryEntry;
import emissary.directory.DirectoryPlace;
import emissary.directory.EmissaryNode;
import emissary.directory.IDirectoryPlace;
import emissary.directory.KeyManipulator;
import emissary.pickup.PickUpPlace;
import emissary.place.IServiceProviderPlace;
Expand All @@ -18,6 +21,7 @@
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
Expand Down Expand Up @@ -65,6 +69,10 @@ public class Startup {
protected final Map<String, List<String>> placeLists = new ConcurrentHashMap<>();
protected final Map<String, List<String>> pickupLists = new ConcurrentHashMap<>();

// sets to keep track of possible invisible place startup
protected Set<String> activeDirPlaces = new LinkedHashSet<>();
protected Set<String> placeAlreadyStarted = new LinkedHashSet<>();

/**
* n return the full DNS name and port without the protocol part
*/
Expand Down Expand Up @@ -160,6 +168,10 @@ public void start() throws EmissaryException {
// they've completed their own set-up). So we have to startup
// the pickup places here.
startPickUpPlaces();

if (!verifyNoInvisiblePlacesStarted()) {
// TODO: If invisible places are started, shutdown the EmissaryServer
}
}


Expand Down Expand Up @@ -367,6 +379,8 @@ protected boolean placeSetup(final int directoryActionArg, final Map<String, Str
if (directoryActionArg == DIRECTORYADD && Namespace.exists(thePlaceLocation)) {
logger.info("Local place already exists: {}", thePlaceLocation);
Startup.this.placesToStart.remove(thePlaceLocation);
// add place to placeAlreadyStarted list, so can be verified in verifyNoInvisibleStartPlaces
placeAlreadyStarted.add(thePlaceLocation.substring(thePlaceLocation.lastIndexOf("/") + 1));
return;
}

Expand All @@ -381,11 +395,9 @@ protected boolean placeSetup(final int directoryActionArg, final Map<String, Str
Startup.this.failedPlaces.add(thePlaceLocation);
Startup.this.placesToStart.remove(thePlaceLocation);
}

}
});
});

t.start();
return true;
}
Expand Down Expand Up @@ -492,4 +504,48 @@ private void sortPickupOrPlace(String theLocation, Map<String, List<String>> pla
List<String> l = placeList.computeIfAbsent(host, k -> new ArrayList<>());
l.add(theLocation);
}

/**
* Verifies the active directory places vs places started up. Log if any places are started without being announced in
* start-up.
*
* @return true if no invisible places started, false if yes
*/
public boolean verifyNoInvisiblePlacesStarted() {
try {
IDirectoryPlace dirPlace = DirectoryPlace.lookup();
List<DirectoryEntry> dirEntries = dirPlace.getEntries();
for (DirectoryEntry currentDir : dirEntries) {
// add place names of active directories
activeDirPlaces.add(currentDir.getLocalPlace().getPlaceName());
}

// remove DirectoryPlace from activeDirPlaces. DirectoryPlace is started up automatically in order to
// start all other places, so it isn't per se "announced", but it is known and logged
activeDirPlaces.removeIf(dir -> dir.equalsIgnoreCase("DirectoryPlace"));
} catch (EmissaryException e) {
throw new RuntimeException(e);
}

// compares place names in active dirs and active places, removes them from set if found
for (String thePlaceLocation : places.values()) {
activeDirPlaces.removeIf(dir -> dir.equalsIgnoreCase(thePlaceLocation.substring(thePlaceLocation.lastIndexOf("/") + 1)));
}

// places that are attempted to startup but are already up are added to separate list
// this will only check if places are added to that list
if (!placeAlreadyStarted.isEmpty()) {
for (String thePlaceLocation : placeAlreadyStarted) {
activeDirPlaces.removeIf(dir -> dir.equalsIgnoreCase(thePlaceLocation));
}
}

// if any places are left in active dir keys, they are places not announced on startup
if (!activeDirPlaces.isEmpty()) {
logger.warn("{} place(s) started up without being announced! {}", activeDirPlaces.size(), activeDirPlaces);
return false;
}

return true;
}
}
68 changes: 67 additions & 1 deletion src/test/java/emissary/admin/StartupTest.java
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
package emissary.admin;

import emissary.core.Namespace;
import emissary.directory.DirectoryPlace;
import emissary.directory.EmissaryNode;
import emissary.pickup.file.FilePickUpClient;
import emissary.pickup.file.FilePickUpPlace;
import emissary.place.CoordinationPlace;
import emissary.place.sample.DelayPlace;
import emissary.place.sample.DevNullPlace;
import emissary.test.core.junit5.UnitTest;
import emissary.util.io.ResourceReader;

import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertIterableEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.spy;

class StartupTest extends UnitTest {

@Test
void testSortPlaces() throws IOException {
List<String> somePlaces = new ArrayList<>();
Expand All @@ -43,4 +49,64 @@ void testSortPlaces() throws IOException {
places);
}

@Test
void testInvisPlaceStart() throws IOException {
// setup node, startup, and DirectoryPlace
EmissaryNode node = new EmissaryNode();
Startup startup = new Startup(node.getNodeConfigurator(), node);
String location = "http://" + node.getNodeName() + ":" + node.getNodePort();
dirStartUp();

// test if place is already started before startup
startup.activeDirPlaces.add(location + "/PlaceAlreadyStartedTest");
startup.placeAlreadyStarted.add(location + "/PlaceAlreadyStartedTest");
assertTrue(startup.verifyNoInvisiblePlacesStarted());
startup.activeDirPlaces.clear();
startup.placeAlreadyStarted.clear();

// test if place is started up normally and is active dir
startup.places.put(location, DevNullPlace.class.getSimpleName());
startup.activeDirPlaces.add(DevNullPlace.class.getSimpleName());
assertTrue(startup.verifyNoInvisiblePlacesStarted());
startup.activeDirPlaces.clear();
startup.places.remove(location, DevNullPlace.class.getSimpleName());

// test unannounced place with active dir
startup.activeDirPlaces.add(location + "/PlaceStartUnannouncedTest");
assertFalse(startup.verifyNoInvisiblePlacesStarted());
startup.activeDirPlaces.clear();

// teardown
dirTeardown();
}

private DirectoryPlace master = null;
private DirectoryPlace client = null;

public void dirStartUp() throws IOException {
// master directory
InputStream configStream = new ResourceReader().getConfigDataAsStream(this);

String masterloc = "http://localhost:8001/TestMasterDirectoryPlace";
this.master = spy(new DirectoryPlace(configStream, masterloc, new EmissaryNode()));
configStream.close();
Namespace.bind(masterloc, this.master);

// non-master directory
configStream = new ResourceReader().getConfigDataAsStream(this);
String clientloc = "http://localhost:8001/DirectoryPlace";
this.client = spy(new DirectoryPlace(configStream, this.master.getDirectoryEntry().getKey(), clientloc, new EmissaryNode()));
configStream.close();
Namespace.bind(clientloc, this.client);
}

public void dirTeardown() {
this.master.shutDown();
this.master = null;
this.client.shutDown();
this.client = null;
for (String s : Namespace.keySet()) {
Namespace.unbind(s);
}
}
}
7 changes: 7 additions & 0 deletions src/test/resources/emissary/admin/StartupTest.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
PLACE_NAME = DirectoryPlace
SERVICE_NAME = DIRECTORY
SERVICE_TYPE = STUDY
SERVICE_DESCRIPTION = "This place will provide location information to agents"
SERVICE_COST = 50
SERVICE_QUALITY = 50
SERVICE_PROXY = "EMISSARY_DIRECTORY_SERVICES"
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
PLACE_NAME = DirectoryPlace
SERVICE_NAME = DIRECTORY
SERVICE_TYPE = STUDY
SERVICE_DESCRIPTION = "This place will provide location information to agents"
SERVICE_COST = 50
SERVICE_QUALITY = 50
SERVICE_PROXY = "EMISSARY_DIRECTORY_SERVICES"
# This is too fast for normal operations, just for the test!
HEARTBEAT_DELAY_SECONDS = 2
HEARTBEAT_INTERVAL_SECONDS = 5

0 comments on commit 5794d03

Please sign in to comment.