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

addressing unexpected place startups #598

Merged
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
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<>();
sambish5 marked this conversation as resolved.
Show resolved Hide resolved
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