Skip to content

Commit 342adc7

Browse files
authored
refresh :: add ServiceProviderRefreshablePlace with "light" and "hard" refresh logic (#1073)
1 parent 1c8797c commit 342adc7

15 files changed

+591
-17
lines changed

src/main/config/emissary.admin.ClassNameInventory.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ JavascriptEscapePlace = "emissary.transform.JavascriptEscapePlace"
1414
JsonEscapePlace = "emissary.transform.JsonEscapePlace"
1515
KffHashPlace = "emissary.place.KffHashPlace"
1616
PythonPlace = "emissary.place.PythonPlace"
17+
RefreshablePlace = "emissary.place.sample.RefreshablePlace"
1718
ToLowerPlace = "emissary.place.sample.ToLowerPlace"
1819
ToUpperPlace = "emissary.place.sample.ToUpperPlace"
1920
UnixCommandPlace = "emissary.place.UnixCommandPlace"
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
PLACE_NAME = "RefreshablePlace"
2+
SERVICE_NAME = "REFRESH_EXAMPLE"
3+
SERVICE_TYPE = "ANALYZE"
4+
SERVICE_DESCRIPTION = "refresh config stuff"
5+
SERVICE_COST = 50
6+
SERVICE_QUALITY = 60
7+
8+
# Add Wildcard entry
9+
SERVICE_PROXY = "*"
10+
SERVICE_PROXY_DENY = "MYFORM"
11+
12+
SAMPLE_CONFIG = 200

src/main/config/places.cfg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ PLACE = "@{URL}/DropOffPlace"
44
PLACE = "@{URL}/UnixFilePlace"
55
PLACE = "@{URL}/JsonEscapePlace"
66
PLACE = "@{URL}/VersionPlace"
7+
8+
PLACE = "@{URL}/RefreshablePlace"

src/main/java/emissary/command/ServerCommand.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import java.util.LinkedHashSet;
1717
import java.util.Locale;
1818
import java.util.Set;
19+
import java.util.concurrent.TimeUnit;
1920

2021
import static emissary.directory.EmissaryNode.STRICT_STARTUP_MODE;
2122

@@ -37,6 +38,9 @@ public class ServerCommand extends ServiceCommand {
3738
@Option(names = {"-a", "--agents"}, description = "number of mobile agents (default is based on memory)\nDefault: ${DEFAULT-VALUE}")
3839
private int agents;
3940

41+
@Option(names = {"-t", "--timeout"}, description = "max amount of time to attempt a server refresh (in minutes) \nDefault: ${DEFAULT-VALUE}")
42+
private int timeout;
43+
4044
@Option(names = {"--dumpJettyBeans"}, description = "dump all the jetty beans that loaded\nDefault: ${DEFAULT-VALUE}")
4145
private boolean dumpJettyBeans = false;
4246

@@ -65,6 +69,10 @@ public int getAgents() {
6569
return agents;
6670
}
6771

72+
public long getTimeout() {
73+
return timeout;
74+
}
75+
6876
public boolean shouldDumpJettyBeans() {
6977
return dumpJettyBeans;
7078
}
@@ -81,6 +89,10 @@ public boolean shouldStrictMode() {
8189
@Override
8290
public void setupCommand() {
8391
setupHttp();
92+
if (getTimeout() > 0) {
93+
System.setProperty(EmissaryNode.NODE_REFRESH_TIMEOUT_PROPERTY, String.valueOf(TimeUnit.MINUTES.toMillis(getTimeout())));
94+
}
95+
8496
reinitLogback();
8597
setupServer();
8698
}
@@ -116,6 +128,16 @@ protected void startService() {
116128
EmissaryServer.init(this).startServer();
117129
}
118130

131+
@Override
132+
protected void refreshService() {
133+
EmissaryResponse response = performPost(getServiceRefreshEndpoint());
134+
if (response.getStatus() != 200) {
135+
LOG.error("Failed to {} Emissary services: {}", isInvalidate() ? "invalidate" : "refresh", response.getContentString());
136+
} else {
137+
LOG.info("{} Emissary services", isInvalidate() ? "Invalidating" : "Refreshing");
138+
}
139+
}
140+
119141
@Override
120142
protected void pauseService() {
121143
setServerState(Pause.PAUSE);

src/main/java/emissary/command/ServiceCommand.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import picocli.CommandLine.Option;
1212

1313
import static emissary.server.api.HealthCheckAction.HEALTH;
14+
import static emissary.server.api.Refresh.INVALIDATE;
15+
import static emissary.server.api.Refresh.REFRESH;
1416
import static emissary.server.api.Shutdown.SHUTDOWN;
1517

1618
/**
@@ -24,13 +26,21 @@ public abstract class ServiceCommand extends HttpCommand {
2426
public static final String SERVICE_HEALTH_ENDPOINT = "/api/" + HEALTH;
2527
public static final String SERVICE_SHUTDOWN_ENDPOINT = "/api/" + SHUTDOWN;
2628
public static final String SERVICE_KILL_ENDPOINT = SERVICE_SHUTDOWN_ENDPOINT + "/force";
29+
public static final String SERVICE_INVALIDATE_ENDPOINT = "/api/" + INVALIDATE;
30+
public static final String SERVICE_REFRESH_ENDPOINT = "/api/" + REFRESH;
2731

2832
@Option(names = {"--csrf"}, description = "disable csrf protection\nDefault: ${DEFAULT-VALUE}", arity = "1")
2933
private boolean csrf = true;
3034

3135
@Option(names = {"--stop"}, description = "Shutdown the service\nDefault: ${DEFAULT-VALUE}")
3236
private boolean stop = false;
3337

38+
@Option(names = {"--invalidate"}, description = "Invalidate services that are refreshable\nDefault: ${DEFAULT-VALUE}")
39+
private boolean invalidate = false;
40+
41+
@Option(names = {"--refresh"}, description = "Force refresh of services\nDefault: ${DEFAULT-VALUE}")
42+
private boolean refresh = false;
43+
3444
@Option(names = {"--kill"}, description = "Force the shutdown of the service\nDefault: ${DEFAULT-VALUE}")
3545
private boolean kill = false;
3646

@@ -48,6 +58,14 @@ public boolean isStop() {
4858
return stop;
4959
}
5060

61+
public boolean isInvalidate() {
62+
return invalidate;
63+
}
64+
65+
public boolean isRefresh() {
66+
return invalidate || refresh;
67+
}
68+
5169
public boolean isKill() {
5270
return kill;
5371
}
@@ -68,6 +86,10 @@ public String getServiceShutdownEndpoint() {
6886
return isKill() ? SERVICE_KILL_ENDPOINT : SERVICE_SHUTDOWN_ENDPOINT;
6987
}
7088

89+
public String getServiceRefreshEndpoint() {
90+
return refresh ? SERVICE_REFRESH_ENDPOINT : SERVICE_INVALIDATE_ENDPOINT;
91+
}
92+
7193
public String getServiceName() {
7294
return getCommandName();
7395
}
@@ -109,6 +131,8 @@ public void run(CommandLine c) {
109131
pauseService();
110132
} else if (isUnpause()) {
111133
unpauseService();
134+
} else if (isRefresh()) {
135+
refreshService();
112136
} else {
113137
throw new EmissaryRuntimeException("Emissary " + getServiceName() + " is already running");
114138
}
@@ -151,4 +175,11 @@ protected void unpauseService() {
151175
throw new UnsupportedOperationException("Unpause not implemented for " + getServiceName());
152176
}
153177

178+
/**
179+
* A method that refreshes services
180+
*/
181+
protected void refreshService() {
182+
throw new UnsupportedOperationException("Refresh not implemented for " + getServiceName());
183+
}
184+
154185
}

src/main/java/emissary/directory/DirectoryEntry.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,13 @@ public boolean isLocal() {
528528
return this.localPlace != null;
529529
}
530530

531+
/**
532+
* Force a lookup from the namespace
533+
*/
534+
public void clearLocalPlace() {
535+
this.lookupAttempted = false;
536+
}
537+
531538
/**
532539
* Change the key such that the place specified by proxyKey acts as a proxy for the current key. We keep the same data
533540
* type, service type, service name and expense but change the place to the proxy

src/main/java/emissary/directory/DirectoryPlace.java

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import emissary.core.Namespace;
77
import emissary.log.MDCConstants;
88
import emissary.place.ServiceProviderPlace;
9+
import emissary.place.ServiceProviderRefreshablePlace;
910
import emissary.server.mvc.adapters.DirectoryAdapter;
1011

1112
import jakarta.annotation.Nullable;
@@ -979,6 +980,11 @@ protected List<DirectoryEntry> nextKeys(final String dataId, final IBaseDataObje
979980
// remove denied entries
980981
currentList.removeIf(de -> de.getLocalPlace() != null && de.getLocalPlace().isDenied(payload.currentForm()));
981982

983+
// test for invalidated places
984+
currentList.stream().filter(e -> e.getLocalPlace() instanceof ServiceProviderRefreshablePlace
985+
&& ((ServiceProviderRefreshablePlace) e.getLocalPlace()).isInvalidated())
986+
.forEach(this::handleEntryRefresh);
987+
982988
if (currentList.isEmpty()) {
983989
logger.debug("nextKeys - no non-DENIED entries found here for {}", dataId);
984990
return List.of();
@@ -1036,6 +1042,19 @@ protected List<DirectoryEntry> nextKeys(final String dataId, final IBaseDataObje
10361042
return keyList;
10371043
}
10381044

1045+
protected synchronized void handleEntryRefresh(final DirectoryEntry entry) {
1046+
// attempt to see if the place has been refreshed already
1047+
entry.clearLocalPlace();
1048+
1049+
final ServiceProviderRefreshablePlace place = (ServiceProviderRefreshablePlace) entry.getLocalPlace();
1050+
if (place.isInvalidated()) {
1051+
logger.debug("{} has been invalidated, attempting refresh ....", entry);
1052+
place.refresh();
1053+
entry.clearLocalPlace();
1054+
logger.debug("{} has been refreshed", entry);
1055+
}
1056+
}
1057+
10391058
/**
10401059
* Get the possibly wildcarded DirectoryEntryList for the dataId
10411060
*
@@ -1186,10 +1205,6 @@ public int removePlaces(final List<String> keys) {
11861205
*/
11871206
@Override
11881207
public int irdRemovePlaces(@Nullable final List<String> keys, final boolean propagating) {
1189-
if (this.emissaryNode.isStandalone()) {
1190-
logger.debug("Cannot remove remote places in standalone nodes");
1191-
return 0;
1192-
}
11931208

11941209
if ((keys == null) || keys.isEmpty()) {
11951210
logger.warn("Ignoring null or empty key list for irdRemovePlaces");

src/main/java/emissary/directory/EmissaryNode.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.ArrayList;
2626
import java.util.List;
2727
import java.util.Locale;
28+
import java.util.concurrent.TimeUnit;
2829

2930
/**
3031
* Hold some details about being a P2P node in the emissary network The order of preference to find the node
@@ -52,6 +53,9 @@ public class EmissaryNode {
5253
/** Node service type property */
5354
public static final String NODE_SERVICE_TYPE_PROPERTY = "emissary.node.service.type";
5455

56+
/** Node refresh timeout property */
57+
public static final String NODE_REFRESH_TIMEOUT_PROPERTY = "emissary.node.refresh.timeout";
58+
5559
/** Node service type is {@value} */
5660
public static final String DEFAULT_NODE_SERVICE_TYPE = "server";
5761

@@ -64,6 +68,9 @@ public class EmissaryNode {
6468
/** Property that determines if server will shut down in the event a place fails to start */
6569
public static final String STRICT_STARTUP_MODE = "strict.mode";
6670

71+
/** Property that sets the max amount of time to wait for a refresh before a failure condition */
72+
public static final long DEFAULT_REFRESH_TIMEOUT = TimeUnit.MINUTES.toMillis(30);
73+
6774
public enum Mode {
6875
STANDALONE, CLUSTER;
6976
}
@@ -80,7 +87,7 @@ public enum Mode {
8087
protected boolean nodeNameIsDefault = false;
8188
@Nullable
8289
protected String nodeServiceType = null;
83-
90+
protected long nodeRefreshTimeout;
8491
protected boolean strictStartupMode = false;
8592

8693
public EmissaryNode() {
@@ -109,6 +116,7 @@ public EmissaryNode(Mode nodeMode) {
109116
this.nodePort = Integer.getInteger(NODE_PORT_PROPERTY, -1).intValue();
110117
this.nodeType = System.getProperty("os.name", DEFAULT_NODE_TYPE).toLowerCase(Locale.getDefault()).replace(' ', '_');
111118
this.nodeServiceType = System.getProperty(NODE_SERVICE_TYPE_PROPERTY, DEFAULT_NODE_SERVICE_TYPE);
119+
this.nodeRefreshTimeout = Long.getLong(NODE_REFRESH_TIMEOUT_PROPERTY, DEFAULT_REFRESH_TIMEOUT);
112120
this.strictStartupMode = Boolean.parseBoolean(System.getProperty(STRICT_STARTUP_MODE, String.valueOf(false)));
113121
}
114122

@@ -144,6 +152,13 @@ public String getNodeScheme() {
144152
return this.nodeScheme;
145153
}
146154

155+
/**
156+
* The node max amount of time to wait for a refresh before throwing a failure condition
157+
*/
158+
public long getNodeRefreshTimeout() {
159+
return this.nodeRefreshTimeout;
160+
}
161+
147162
/**
148163
* Get the value as a url
149164
*/

0 commit comments

Comments
 (0)