This is an automated email from the ASF dual-hosted git repository.
stefanegli pushed a commit to branch master
in repository
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git
The following commit(s) were added to refs/heads/master by this push:
new d62c7da SLING-10489 : ignore partially started instances : (#4)
d62c7da is described below
commit d62c7da07909373098c6ad4c2bc54124b0b4017f
Author: stefan-egli <[email protected]>
AuthorDate: Tue Sep 14 16:40:38 2021 +0200
SLING-10489 : ignore partially started instances : (#4)
* SLING-10489 : ignore partially started instances : a. skipping activeIds
in OakBacklogClusterSyncService if they are partially started, b. ignore
syncToken for view change checks if there are partially started instances plus
bonus c. LogSilencer introduced, which reduces log.info spam caused by discovery
* SLING-10489 : removed a noisy log.info
* SLING-10489 : lowered a noisy log.info
* SLING-10489 : lowered a noisy log.info
* SLING-10489 : lowered a noisy log.info
* SLING-10489: Ignore partially started, newly joining instances to avoid
disturbing discovery (for a while)
Unit test for LocalClusterView
* Update
src/main/java/org/apache/sling/discovery/commons/providers/util/LogSilencer.java
Co-authored-by: Marcel Reutegger <[email protected]>
* Update
src/main/java/org/apache/sling/discovery/commons/providers/util/LogSilencer.java
Co-authored-by: Marcel Reutegger <[email protected]>
* SLING-10489 : javadoc updated
* SLING-10489 : copy partiallyStartedClusterNodeIds on clone as well
* SLING-10489 : simplified equalsIgnoreSyncToken - plus a unit test added
for it
Co-authored-by: Marcel Reutegger <[email protected]>
Co-authored-by: Marcel Reutegger <[email protected]>
---
.../providers/base/MinEventDelayHandler.java | 37 +++--
.../providers/base/ViewStateManagerImpl.java | 45 +++++-
.../commons/providers/base/package-info.java | 4 +-
.../commons/providers/spi/LocalClusterView.java | 27 ++++
.../base/AbstractServiceWithBackgroundCheck.java | 16 +-
.../spi/base/OakBacklogClusterSyncService.java | 40 +++--
.../providers/spi/base/SyncTokenService.java | 29 ++--
.../commons/providers/spi/package-info.java | 4 +-
.../commons/providers/util/LogSilencer.java | 97 ++++++++++++
.../commons/providers/util/package-info.java | 4 +-
.../commons/providers/DummyTopologyView.java | 11 +-
.../providers/base/TestViewStateManager.java | 173 +++++++++++++++++++++
.../providers/spi/LocalClusterViewTest.java | 46 ++++++
.../spi/base/TestOakSyncTokenService.java | 84 +++++++++-
14 files changed, 566 insertions(+), 51 deletions(-)
diff --git
a/src/main/java/org/apache/sling/discovery/commons/providers/base/MinEventDelayHandler.java
b/src/main/java/org/apache/sling/discovery/commons/providers/base/MinEventDelayHandler.java
index 43093ad..12a4dfe 100644
---
a/src/main/java/org/apache/sling/discovery/commons/providers/base/MinEventDelayHandler.java
+++
b/src/main/java/org/apache/sling/discovery/commons/providers/base/MinEventDelayHandler.java
@@ -25,6 +25,7 @@ import org.apache.sling.commons.scheduler.Scheduler;
import org.apache.sling.discovery.DiscoveryService;
import org.apache.sling.discovery.TopologyView;
import org.apache.sling.discovery.commons.providers.BaseTopologyView;
+import org.apache.sling.discovery.commons.providers.util.LogSilencer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -51,6 +52,8 @@ class MinEventDelayHandler {
private volatile int cancelCnt = 0;
+ private final LogSilencer logSilencer = new LogSilencer(logger);
+
MinEventDelayHandler(ViewStateManagerImpl viewStateManager, Lock lock,
DiscoveryService discoveryService, Scheduler scheduler,
long minEventDelaySecs) {
@@ -74,23 +77,33 @@ class MinEventDelayHandler {
* Asks the MinEventDelayHandler to handle the new view
* and return true if the caller shouldn't worry about any follow-up
action -
* only if the method returns false should the caller do the usual
- * handleNewView action
+ * handleNewView action.
+ * This caller of this method must ensure to be in a lock.lock() block
*/
boolean handlesNewView(BaseTopologyView newView) {
if (isDelaying) {
// already delaying, so we'll soon ask the DiscoveryServiceImpl
for the
// latest view and go ahead then
- logger.info("handleNewView: already delaying, ignoring new view
meanwhile");
+ logSilencer.infoOrDebug("handlesNewView-" +
newView.getLocalClusterSyncTokenId(),
+ "handleNewView: already delaying, ignoring new view
meanwhile");
return true;
}
if (!viewStateManager.hadPreviousView()) {
- logger.info("handlesNewView: never had a previous view, hence no
delaying applicable");
+ logSilencer.infoOrDebug("handlesNewView-" +
newView.getLocalClusterSyncTokenId(),
+ "handlesNewView: never had a previous view, hence no
delaying applicable");
+ return false;
+ }
+
+ if (viewStateManager.equalsIgnoreSyncToken(newView)) {
+ // this is a frequent case, hence only log.debug
+ logger.debug("handlesNewView: equalsIgnoreSyncToken, hence no
delaying applicable");
return false;
}
if (viewStateManager.onlyDiffersInProperties(newView)) {
- logger.info("handlesNewView: only properties differ, hence no
delaying applicable");
+ logSilencer.infoOrDebug("handlesNewView-" +
newView.getLocalClusterSyncTokenId(),
+ "handlesNewView: only properties differ, hence no delaying
applicable");
return false;
}
@@ -103,7 +116,8 @@ class MinEventDelayHandler {
// thanks to force==true this will always return true
if (!triggerAsyncDelaying(newView)) {
- logger.info("handleNewView: could not trigger async delaying,
sending new view now.");
+ logSilencer.infoOrDebug("handlesNewView-" +
newView.getLocalClusterSyncTokenId(),
+ "handleNewView: could not trigger async delaying, sending
new view now.");
viewStateManager.handleNewViewNonDelayed(newView);
} else {
// if triggering the async event was successful, then we should
also
@@ -118,7 +132,7 @@ class MinEventDelayHandler {
return true;
}
- private boolean triggerAsyncDelaying(BaseTopologyView newView) {
+ private boolean triggerAsyncDelaying(final BaseTopologyView newView) {
final int validCancelCnt = cancelCnt;
final boolean triggered = runAfter(minEventDelaySecs /*seconds*/ , new
Runnable() {
@@ -127,7 +141,8 @@ class MinEventDelayHandler {
lock.lock();
try{
if (cancelCnt!=validCancelCnt) {
- logger.info("asyncDelay.run: got cancelled
(validCancelCnt="+validCancelCnt+", cancelCnt="+cancelCnt+"), quitting.");
+ logSilencer.infoOrDebug("asyncDelay.run-cancel-" +
newView.getLocalClusterSyncTokenId(),
+ "asyncDelay.run: got cancelled
(validCancelCnt="+validCancelCnt+", cancelCnt="+cancelCnt+"), quitting.");
return;
}
@@ -144,10 +159,12 @@ class MinEventDelayHandler {
BaseTopologyView topology = (BaseTopologyView) t;
if (topology.isCurrent()) {
- logger.info("asyncDelay.run: done delaying. got new
view: "+ topology.toShortString());
+ logSilencer.infoOrDebug("asyncDelay.run-done-" +
newView.getLocalClusterSyncTokenId(),
+ "asyncDelay.run: done delaying. got new view:
"+ topology.toShortString());
viewStateManager.handleNewViewNonDelayed(topology);
} else {
- logger.info("asyncDelay.run: done delaying. new view
(still/again) not current, delaying again");
+ logSilencer.infoOrDebug("asyncDelay.run-done-" +
newView.getLocalClusterSyncTokenId(),
+ "asyncDelay.run: done delaying. new view
(still/again) not current, delaying again");
triggerAsyncDelaying(topology);
// we're actually not interested in the result here
// if the async part failed, then we have to rely
@@ -168,7 +185,7 @@ class MinEventDelayHandler {
}
});
- logger.info("triggerAsyncDelaying: asynch delaying of
"+minEventDelaySecs+" triggered: "+triggered);
+ logSilencer.infoOrDebug("triggerAsyncDelaying", "triggerAsyncDelaying:
asynch delaying of "+minEventDelaySecs+" triggered: "+triggered);
if (triggered) {
isDelaying = true;
}
diff --git
a/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerImpl.java
b/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerImpl.java
index b9f9fc7..da47195 100644
---
a/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerImpl.java
+++
b/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerImpl.java
@@ -28,6 +28,7 @@ import java.util.Set;
import java.util.concurrent.locks.Lock;
import org.apache.sling.commons.scheduler.Scheduler;
+import org.apache.sling.discovery.ClusterView;
import org.apache.sling.discovery.DiscoveryService;
import org.apache.sling.discovery.InstanceDescription;
import org.apache.sling.discovery.TopologyEvent;
@@ -37,6 +38,8 @@ import
org.apache.sling.discovery.commons.providers.BaseTopologyView;
import org.apache.sling.discovery.commons.providers.EventHelper;
import org.apache.sling.discovery.commons.providers.ViewStateManager;
import org.apache.sling.discovery.commons.providers.spi.ClusterSyncService;
+import org.apache.sling.discovery.commons.providers.spi.LocalClusterView;
+import org.apache.sling.discovery.commons.providers.util.LogSilencer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -137,6 +140,8 @@ public class ViewStateManagerImpl implements
ViewStateManager {
private MinEventDelayHandler minEventDelayHandler;
+ private final LogSilencer logSilencer = new LogSilencer(logger);
+
/**
* Creates a new ViewStateManager which synchronizes each method with the
given
* lock and which optionally uses the given ClusterSyncService to sync the
repository
@@ -456,7 +461,7 @@ public class ViewStateManagerImpl implements
ViewStateManager {
// verify if there is actually a change between previousView
and newView
// if there isn't, then there is not much point in sending a
CHANGING/CHANGED tuple
// at all
- if (previousView!=null && previousView.equals(newView)) {
+ if (previousView!=null && (previousView.equals(newView) ||
equalsIgnoreSyncToken(newView))) {
// then nothing to send - the view has not changed, and we
haven't
// sent the CHANGING event - so we should not do anything
here
logger.debug("handleNewViewNonDelayed: we were not in
changing state and new view matches old, so - ignoring");
@@ -486,7 +491,7 @@ public class ViewStateManagerImpl implements
ViewStateManager {
if (!isChanging && onlyDiffersInProperties(newView)) {
// well then send a properties changed event only
// and that one does not go via consistencyservice
- logger.info("handleNewViewNonDelayed: properties changed to:
"+newView);
+
logSilencer.infoOrDebug("handleNewViewNonDelayed-propsChanged",
"handleNewViewNonDelayed: properties changed to: "+newView);
previousView.setNotCurrent();
enqueueForAll(eventListeners,
EventHelper.newPropertiesChangedEvent(previousView, newView));
logger.trace("handleNewViewNonDelayed: setting previousView to
{}", newView);
@@ -496,7 +501,7 @@ public class ViewStateManagerImpl implements
ViewStateManager {
final boolean invokeClusterSyncService;
if (consistencyService==null) {
- logger.info("handleNewViewNonDelayed: no ClusterSyncService
set - continuing directly.");
+
logSilencer.infoOrDebug("handleNewViewNonDelayed-noSyncService",
"handleNewViewNonDelayed: no ClusterSyncService set - continuing directly.");
invokeClusterSyncService = false;
} else {
// there used to be a distinction between:
@@ -511,7 +516,7 @@ public class ViewStateManagerImpl implements
ViewStateManager {
//
// which is a long way of saying: if the consistencyService is
configured,
// then we always use it, hence:
- logger.info("handleNewViewNonDelayed: ClusterSyncService set -
invoking...");
+
logSilencer.infoOrDebug("handleNewViewNonDelayed-invokeSyncService",
"handleNewViewNonDelayed: ClusterSyncService set - invoking...");
invokeClusterSyncService = true;
}
@@ -520,7 +525,7 @@ public class ViewStateManagerImpl implements
ViewStateManager {
// then:
// run the set consistencyService
final int lastModCnt = modCnt;
- logger.info("handleNewViewNonDelayed: invoking
waitForAsyncEvents, then clusterSyncService (modCnt={})", modCnt);
+
logSilencer.infoOrDebug("handleNewViewNonDelayed-invokeWaitAsync",
"handleNewViewNonDelayed: invoking waitForAsyncEvents, then
clusterSyncService");
asyncEventSender.enqueue(new AsyncEvent() {
@Override
@@ -544,7 +549,7 @@ public class ViewStateManagerImpl implements
ViewStateManager {
lastModCnt, modCnt);
return;
}
-
logger.info("handleNewViewNonDelayed/waitForAsyncEvents.run: done, now invoking
consistencyService (modCnt={})", modCnt);
+
logSilencer.infoOrDebug("waitForAsyncEvents-asyncRun",
"handleNewViewNonDelayed/waitForAsyncEvents.run: done, now invoking
consistencyService");
consistencyService.sync(newView,
new Runnable() {
@@ -558,7 +563,7 @@ public class ViewStateManagerImpl implements
ViewStateManager {
lastModCnt, modCnt);
return;
}
-
logger.info("consistencyService.callback.run: invoking doHandleConsistent.");
+
logSilencer.infoOrDebug("consistencyService-callBackRun",
"consistencyService.callback.run: invoking doHandleConsistent.");
// else:
doHandleConsistent(newView);
} finally {
@@ -579,7 +584,7 @@ public class ViewStateManagerImpl implements
ViewStateManager {
// or using it is not applicable at this stage - so continue
// with sending the TOPOLOGY_CHANGED (or TOPOLOGY_INIT if there
// are any newly bound topology listeners) directly
- logger.info("handleNewViewNonDelayed: not invoking
consistencyService, considering consistent now");
+
logSilencer.infoOrDebug("handleNewViewNonDelayed-noSyncService-ignore",
"handleNewViewNonDelayed: not invoking consistencyService, considering
consistent now");
doHandleConsistent(newView);
}
logger.debug("handleNewViewNonDelayed: end");
@@ -590,6 +595,30 @@ public class ViewStateManagerImpl implements
ViewStateManager {
}
}
+ /**
+ * Checks if the previouesView is equal to the newView, ignoring the
+ * syncToken (but only if the newView has partially started instances).
+ * <p/>
+ * This caller of this method must ensure to be in a lock.lock() block
+ */
+ protected boolean equalsIgnoreSyncToken(BaseTopologyView newView) {
+ if (previousView==null) {
+ return false;
+ }
+ if (newView==null) {
+ throw new IllegalArgumentException("newView must not be null");
+ }
+ final ClusterView cluster =
newView.getLocalInstance().getClusterView();
+ if (cluster instanceof LocalClusterView) {
+ final LocalClusterView local = (LocalClusterView)cluster;
+ if (!local.hasPartiallyStartedInstances()) {
+ // then we should not ignore the syncToken I'm afraid
+ return previousView.equals(newView);
+ }
+ }
+ return previousView.getInstances().equals(newView.getInstances());
+ }
+
protected boolean onlyDiffersInProperties(BaseTopologyView newView) {
if (previousView==null) {
return false;
diff --git
a/src/main/java/org/apache/sling/discovery/commons/providers/base/package-info.java
b/src/main/java/org/apache/sling/discovery/commons/providers/base/package-info.java
index 9d9bd43..fd0915d 100644
---
a/src/main/java/org/apache/sling/discovery/commons/providers/base/package-info.java
+++
b/src/main/java/org/apache/sling/discovery/commons/providers/base/package-info.java
@@ -20,9 +20,9 @@
/**
* Provides commons implementations for providers of the Discovery API.
*
- * @version 1.0.0
+ * @version 1.1.0
*/
-@Version("1.0.0")
+@Version("1.1.0")
package org.apache.sling.discovery.commons.providers.base;
import org.osgi.annotation.versioning.Version;
diff --git
a/src/main/java/org/apache/sling/discovery/commons/providers/spi/LocalClusterView.java
b/src/main/java/org/apache/sling/discovery/commons/providers/spi/LocalClusterView.java
index 12363af..29663c0 100644
---
a/src/main/java/org/apache/sling/discovery/commons/providers/spi/LocalClusterView.java
+++
b/src/main/java/org/apache/sling/discovery/commons/providers/spi/LocalClusterView.java
@@ -18,11 +18,17 @@
*/
package org.apache.sling.discovery.commons.providers.spi;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
import org.apache.sling.discovery.commons.providers.DefaultClusterView;
public class LocalClusterView extends DefaultClusterView {
private final String localClusterSyncTokenId;
+ private Set<Integer> partiallyStartedClusterNodeIds;
public LocalClusterView(String id, String localClusterSyncTokenId) {
super(id);
@@ -33,4 +39,25 @@ public class LocalClusterView extends DefaultClusterView {
return localClusterSyncTokenId;
}
+ public Set<Integer> getPartiallyStartedClusterNodeIds() {
+ return Collections.unmodifiableSet(partiallyStartedClusterNodeIds);
+ }
+
+ public void setPartiallyStartedClusterNodeIds(Collection<Integer>
clusterNodeIds) {
+ this.partiallyStartedClusterNodeIds = new
HashSet<Integer>(clusterNodeIds);
+ }
+
+ public boolean isPartiallyStarted(Integer clusterNodeId) {
+ if (partiallyStartedClusterNodeIds == null || clusterNodeId == null) {
+ return false;
+ }
+ return partiallyStartedClusterNodeIds.contains(clusterNodeId);
+ }
+
+ public boolean hasPartiallyStartedInstances() {
+ if (partiallyStartedClusterNodeIds == null) {
+ return false;
+ }
+ return !partiallyStartedClusterNodeIds.isEmpty();
+ }
}
diff --git
a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/AbstractServiceWithBackgroundCheck.java
b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/AbstractServiceWithBackgroundCheck.java
index 84f2f76..e7a2e5c 100644
---
a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/AbstractServiceWithBackgroundCheck.java
+++
b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/AbstractServiceWithBackgroundCheck.java
@@ -18,6 +18,7 @@
*/
package org.apache.sling.discovery.commons.providers.spi.base;
+import org.apache.sling.discovery.commons.providers.util.LogSilencer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -32,6 +33,8 @@ public abstract class AbstractServiceWithBackgroundCheck {
protected String slingId;
+ private final LogSilencer logSilencer = new LogSilencer(logger);
+
/**
* The BackgroundCheckRunnable implements the details of
* calling BackgroundCheck.check and looping until it
@@ -83,9 +86,9 @@ public abstract class AbstractServiceWithBackgroundCheck {
if (timeoutMillis != -1 &&
(System.currentTimeMillis() > start +
timeoutMillis)) {
if (callback == null) {
- logger.info("backgroundCheck.run: timeout hit (no
callback to invoke)");
+ logSilencer.infoOrDebug("backgroundCheck.run",
"backgroundCheck.run: timeout hit (no callback to invoke)");
} else {
- logger.info("backgroundCheck.run: timeout hit,
invoking callback.");
+ logSilencer.infoOrDebug("backgroundCheck.run",
"backgroundCheck.run: timeout hit, invoking callback.");
callback.run();
}
return;
@@ -131,7 +134,7 @@ public abstract class AbstractServiceWithBackgroundCheck {
void cancel() {
if (!done) {
- logger.info("cancel: "+threadName);
+ logSilencer.infoOrDebug("cancel-" + threadName, "cancel:
"+threadName);
}
cancelled = true;
}
@@ -200,14 +203,15 @@ public abstract class AbstractServiceWithBackgroundCheck {
// then we're not even going to start the background-thread
// we're already done
if (callback!=null) {
- logger.info("backgroundCheck: already done, backgroundCheck
successful, invoking callback");
+ logSilencer.infoOrDebug("backgroundCheck", "backgroundCheck:
already done, backgroundCheck successful, invoking callback");
callback.run();
} else {
- logger.info("backgroundCheck: already done, backgroundCheck
successful. no callback to invoke.");
+ logSilencer.infoOrDebug("backgroundCheck", "backgroundCheck:
already done, backgroundCheck successful. no callback to invoke.");
}
return;
}
- logger.info("backgroundCheck: spawning background-thread for
'"+threadName+"'");
+ logSilencer.infoOrDebug("backgroundCheck-" + threadName,
+ "backgroundCheck: spawning background-thread for
'"+threadName+"'");
backgroundCheckRunnable = new BackgroundCheckRunnable(callback, check,
timeoutMillis, waitMillis, threadName);
Thread th = new Thread(backgroundCheckRunnable);
th.setName(threadName);
diff --git
a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/OakBacklogClusterSyncService.java
b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/OakBacklogClusterSyncService.java
index f171728..abd94fb 100644
---
a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/OakBacklogClusterSyncService.java
+++
b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/OakBacklogClusterSyncService.java
@@ -30,6 +30,8 @@ import org.apache.sling.discovery.ClusterView;
import org.apache.sling.discovery.InstanceDescription;
import org.apache.sling.discovery.commons.providers.BaseTopologyView;
import org.apache.sling.discovery.commons.providers.spi.ClusterSyncService;
+import org.apache.sling.discovery.commons.providers.spi.LocalClusterView;
+import org.apache.sling.discovery.commons.providers.util.LogSilencer;
import org.apache.sling.settings.SlingSettingsService;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Activate;
@@ -67,6 +69,8 @@ public class OakBacklogClusterSyncService extends
AbstractServiceWithBackgroundC
private ClusterSyncHistory consistencyHistory = new ClusterSyncHistory();
+ private final LogSilencer logSilencer = new LogSilencer(logger);
+
public static OakBacklogClusterSyncService testConstructorAndActivate(
final DiscoveryLiteConfig commonsConfig,
final IdMapService idMapService,
@@ -132,20 +136,21 @@ public class OakBacklogClusterSyncService extends
AbstractServiceWithBackgroundC
cancelPreviousBackgroundCheck();
// first do the wait-for-backlog part
- logger.info("sync: doing wait-for-backlog part for
view="+view.toShortString());
+ logSilencer.infoOrDebug("sync", "sync: doing wait-for-backlog part for
view="+view.toShortString());
waitWhileBacklog(view, callback);
}
private void waitWhileBacklog(final BaseTopologyView view, final Runnable
runnable) {
// start backgroundChecking until the backlogStatus
// is NO_BACKLOG
- startBackgroundCheck("OakBacklogClusterSyncService-backlog-waiting",
new BackgroundCheck() {
+ startBackgroundCheck("OakBacklogClusterSyncService-backlog-waiting-" +
view.getLocalClusterSyncTokenId(), new BackgroundCheck() {
@Override
public boolean check() {
try {
if (!idMapService.isInitialized()) {
- logger.info("waitWhileBacklog: could not
initialize...");
+ logSilencer.infoOrDebug("waitWhileBacklog-" +
view.toShortString(),
+ "waitWhileBacklog: could not initialize...");
consistencyHistory.addHistoryEntry(view, "could not
initialize idMapService");
return false;
}
@@ -156,11 +161,13 @@ public class OakBacklogClusterSyncService extends
AbstractServiceWithBackgroundC
}
BacklogStatus backlogStatus = getBacklogStatus(view);
if (backlogStatus == BacklogStatus.NO_BACKLOG) {
- logger.info("waitWhileBacklog: no backlog (anymore),
done.");
+ logSilencer.infoOrDebug("waitWhileBacklog-" +
view.toShortString(),
+ "waitWhileBacklog: no backlog (anymore), done.");
consistencyHistory.addHistoryEntry(view, "no backlog
(anymore)");
return true;
} else {
- logger.info("waitWhileBacklog: backlogStatus still
"+backlogStatus);
+ logSilencer.infoOrDebug("waitWhileBacklog-" +
view.toShortString(),
+ "waitWhileBacklog: backlogStatus still
"+backlogStatus);
// clear the cache to make sure to get the latest version
in case something changed
idMapService.clearCache();
consistencyHistory.addHistoryEntry(view, "backlog status
"+backlogStatus);
@@ -201,11 +208,16 @@ public class OakBacklogClusterSyncService extends
AbstractServiceWithBackgroundC
// 1) 'deactivating' must be empty
if (deactivatingIds.length!=0) {
- logger.info("getBacklogStatus: there are deactivating
instances: "+Arrays.toString(deactivatingIds));
+ logSilencer.infoOrDebug("getBacklogStatus-hasBacklog-" +
view.toShortString(),
+ "getBacklogStatus: there are deactivating instances:
"+Arrays.toString(deactivatingIds));
return BacklogStatus.HAS_BACKLOG;
}
ClusterView cluster = view.getLocalInstance().getClusterView();
+ LocalClusterView localCluster = null;
+ if (cluster instanceof LocalClusterView) {
+ localCluster = (LocalClusterView)cluster;
+ }
Set<String> slingIds = new HashSet<>();
for (InstanceDescription instance : cluster.getInstances()) {
slingIds.add(instance.getSlingId());
@@ -213,23 +225,31 @@ public class OakBacklogClusterSyncService extends
AbstractServiceWithBackgroundC
for(int i=0; i<activeIds.length; i++) {
int activeId = activeIds[i];
+ if (localCluster != null &&
localCluster.isPartiallyStarted(activeId)) {
+ // ignore this one then
+ continue;
+ }
String slingId = idMapService.toSlingId(activeId,
resourceResolver);
// 2) all ids of the descriptor must have a mapping to slingIds
if (slingId == null) {
- logger.info("getBacklogStatus: no slingId found for active
id: "+activeId);
+ logSilencer.infoOrDebug("getBacklogStatus-undefined-" +
view.toShortString(),
+ "getBacklogStatus: no slingId found for active id:
"+activeId);
return BacklogStatus.UNDEFINED;
}
// 3) all 'active' instances must be in the view
if (!slingIds.contains(slingId)) {
- logger.info("getBacklogStatus: active instance's
("+activeId+") slingId ("+slingId+") not found in cluster ("+cluster+")");
+ logSilencer.infoOrDebug("getBacklogStatus-hasBacklog-" +
view.toShortString(),
+ "getBacklogStatus: active instance's
("+activeId+") slingId ("+slingId+") not found in cluster ("+cluster+")");
return BacklogStatus.HAS_BACKLOG;
}
}
- logger.info("getBacklogStatus: no backlog (anymore)");
+ logSilencer.infoOrDebug("getBacklogStatus-" + view.toShortString(),
+ "getBacklogStatus: no backlog (anymore)");
return BacklogStatus.NO_BACKLOG;
} catch(Exception e) {
- logger.info("getBacklogStatus: failed to determine backlog status:
"+e);
+ logSilencer.infoOrDebug("getBacklogStatus-undefined-" +
view.toShortString(),
+ "getBacklogStatus: failed to determine backlog status:
"+e);
return BacklogStatus.UNDEFINED;
} finally {
logger.trace("getBacklogStatus: end");
diff --git
a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/SyncTokenService.java
b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/SyncTokenService.java
index c5bc4d9..0fe5209 100644
---
a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/SyncTokenService.java
+++
b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/SyncTokenService.java
@@ -28,6 +28,7 @@ import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.discovery.InstanceDescription;
import org.apache.sling.discovery.commons.providers.BaseTopologyView;
import org.apache.sling.discovery.commons.providers.spi.ClusterSyncService;
+import org.apache.sling.discovery.commons.providers.util.LogSilencer;
import org.apache.sling.discovery.commons.providers.util.ResourceHelper;
import org.apache.sling.settings.SlingSettingsService;
import org.osgi.framework.Constants;
@@ -63,6 +64,8 @@ public class SyncTokenService extends
AbstractServiceWithBackgroundCheck impleme
protected ClusterSyncHistory clusterSyncHistory = new ClusterSyncHistory();
+ private final LogSilencer logSilencer = new LogSilencer(logger);
+
public static SyncTokenService testConstructorAndActivate(
DiscoveryLiteConfig commonsConfig,
ResourceResolverFactory resourceResolverFactory,
@@ -128,7 +131,7 @@ public class SyncTokenService extends
AbstractServiceWithBackgroundCheck impleme
protected void syncToken(final BaseTopologyView view, final Runnable
callback) {
- startBackgroundCheck("SyncTokenService", new BackgroundCheck() {
+ startBackgroundCheck("SyncTokenService-" +
view.getLocalClusterSyncTokenId(), new BackgroundCheck() {
@Override
public boolean check() {
@@ -172,9 +175,11 @@ public class SyncTokenService extends
AbstractServiceWithBackgroundCheck impleme
if (updateToken) {
syncTokens.put(slingId, syncTokenId);
resourceResolver.commit();
- logger.info("storeMySyncToken: stored syncToken of
slingId="+slingId+" as="+syncTokenId);
+ logSilencer.infoOrDebug("storeMySyncToken-" + syncTokenId,
+ "storeMySyncToken: stored syncToken of
slingId="+slingId+" as="+syncTokenId);
} else {
- logger.info("storeMySyncToken: syncToken was left unchanged
for slingId="+slingId+" at="+syncTokenId);
+ logSilencer.infoOrDebug("storeMySyncToken-" + syncTokenId,
+ "storeMySyncToken: syncToken was left unchanged for
slingId="+slingId+" at="+syncTokenId);
}
return true;
} catch (LoginException e) {
@@ -208,10 +213,12 @@ public class SyncTokenService extends
AbstractServiceWithBackgroundCheck impleme
boolean success = true;
StringBuffer historyEntry = new StringBuffer();
for (InstanceDescription instance :
view.getLocalInstance().getClusterView().getInstances()) {
- Object currentValue = syncTokens.get(instance.getSlingId());
+ String instanceSlingId = instance.getSlingId();
+ Object currentValue = syncTokens.get(instanceSlingId);
if (currentValue == null) {
String msg = "no syncToken yet of "+instance.getSlingId();
- logger.info("seenAllSyncTokens: " + msg);
+ logSilencer.infoOrDebug("seenAllSyncToken-" + syncToken +
"-no-" + instanceSlingId,
+ "seenAllSyncTokens: " + msg);
if (historyEntry.length() != 0) {
historyEntry.append(",");
}
@@ -221,7 +228,8 @@ public class SyncTokenService extends
AbstractServiceWithBackgroundCheck impleme
String msg = "syncToken of " + instance.getSlingId()
+ " is " + currentValue
+ " waiting for " + syncToken;
- logger.info("seenAllSyncTokens: " + msg);
+ logSilencer.infoOrDebug("seenAllSyncToken-" + syncToken +
"-wait-"+instanceSlingId,
+ "seenAllSyncTokens: " + msg);
if (historyEntry.length() != 0) {
historyEntry.append(",");
}
@@ -230,15 +238,18 @@ public class SyncTokenService extends
AbstractServiceWithBackgroundCheck impleme
}
}
if (!success) {
- logger.info("seenAllSyncTokens: not yet seen all expected
syncTokens (see above for details)");
+ logSilencer.infoOrDebug("seenAllSyncToken-result-" + syncToken,
+ "seenAllSyncTokens: not yet seen all expected
syncTokens (see above for details)");
clusterSyncHistory.addHistoryEntry(view,
historyEntry.toString());
return false;
} else {
- clusterSyncHistory.addHistoryEntry(view, "seen all
syncTokens");
+ clusterSyncHistory.addHistoryEntry(view,
+ "seen all syncTokens");
}
resourceResolver.commit();
- logger.info("seenAllSyncTokens: seen all syncTokens!");
+ logSilencer.infoOrDebug("seenAllSyncToken-result-" + syncToken,
+ "seenAllSyncTokens: seen all syncTokens!");
return true;
} catch (LoginException e) {
logger.error("seenAllSyncTokens: could not login: "+e, e);
diff --git
a/src/main/java/org/apache/sling/discovery/commons/providers/spi/package-info.java
b/src/main/java/org/apache/sling/discovery/commons/providers/spi/package-info.java
index df9d9f2..6d1dbe0 100644
---
a/src/main/java/org/apache/sling/discovery/commons/providers/spi/package-info.java
+++
b/src/main/java/org/apache/sling/discovery/commons/providers/spi/package-info.java
@@ -20,9 +20,9 @@
/**
* Provides an SPI for providers, used by discovery.commons.providers.impl
*
- * @version 1.0.0
+ * @version 1.1.0
*/
-@Version("1.0.0")
+@Version("1.1.0")
package org.apache.sling.discovery.commons.providers.spi;
import org.osgi.annotation.versioning.Version;
diff --git
a/src/main/java/org/apache/sling/discovery/commons/providers/util/LogSilencer.java
b/src/main/java/org/apache/sling/discovery/commons/providers/util/LogSilencer.java
new file mode 100644
index 0000000..9b9ee0f
--- /dev/null
+++
b/src/main/java/org/apache/sling/discovery/commons/providers/util/LogSilencer.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.util;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import org.slf4j.Logger;
+
+/**
+ * Helper class to help reduce log.info output. It will avoid repetitive
+ * log.info calls and instead log future occurrences to log.debug
+ */
+public class LogSilencer {
+
+ private static final long DEFAULT_AUTO_RESET_DELAY_MINUTES = 10;
+
+ private final Logger logger;
+
+ private final Object syncObj = new Object();
+
+ private final long autoResetDelayMillis;
+
+ private Map<String, String> lastMsgPerCategory;
+
+ private long autoResetTime = 0;
+
+ public LogSilencer(Logger logger, long autoResetDelayMinutes) {
+ this.logger = logger;
+ if (autoResetDelayMinutes > 0) {
+ autoResetDelayMillis =
TimeUnit.MINUTES.toMillis(autoResetDelayMinutes);
+ } else {
+ autoResetDelayMillis = 0;
+ }
+ }
+
+ public LogSilencer(Logger logger) {
+ this(logger, DEFAULT_AUTO_RESET_DELAY_MINUTES);
+ }
+
+ public void infoOrDebug(String category, String msg) {
+ final boolean doLogInfo;
+ synchronized (syncObj) {
+ if (autoResetTime == 0 || System.currentTimeMillis() >
autoResetTime) {
+ reset();
+ }
+ if (lastMsgPerCategory == null) {
+ lastMsgPerCategory = new HashMap<>();
+ }
+ final String localLastMsg = lastMsgPerCategory.get(category);
+ if (localLastMsg == null || !localLastMsg.equals(msg)) {
+ doLogInfo = true;
+ lastMsgPerCategory.put(category, msg);
+ } else {
+ doLogInfo = false;
+ }
+ }
+ if (doLogInfo) {
+ logger.info("{} {}", msg, "(future identical logs go to debug)");
+ } else {
+ logger.debug(msg);
+ }
+ }
+
+ public void infoOrDebug(String msg) {
+ infoOrDebug(null, msg);
+ }
+
+ public void reset() {
+ synchronized (syncObj) {
+ lastMsgPerCategory = null;
+ if (autoResetDelayMillis == 0) {
+ autoResetTime = Long.MAX_VALUE;
+ } else {
+ autoResetTime = System.currentTimeMillis() +
autoResetDelayMillis;
+ }
+ }
+ }
+
+}
diff --git
a/src/main/java/org/apache/sling/discovery/commons/providers/util/package-info.java
b/src/main/java/org/apache/sling/discovery/commons/providers/util/package-info.java
index b4778b2..cbe2750 100644
---
a/src/main/java/org/apache/sling/discovery/commons/providers/util/package-info.java
+++
b/src/main/java/org/apache/sling/discovery/commons/providers/util/package-info.java
@@ -20,9 +20,9 @@
/**
* Provides some static helpers for providers of the Discovery API.
*
- * @version 1.0.0
+ * @version 1.1.0
*/
-@Version("1.0.0")
+@Version("1.1.0")
package org.apache.sling.discovery.commons.providers.util;
import org.osgi.annotation.versioning.Version;
diff --git
a/src/test/java/org/apache/sling/discovery/commons/providers/DummyTopologyView.java
b/src/test/java/org/apache/sling/discovery/commons/providers/DummyTopologyView.java
index e12b24d..140b747 100644
---
a/src/test/java/org/apache/sling/discovery/commons/providers/DummyTopologyView.java
+++
b/src/test/java/org/apache/sling/discovery/commons/providers/DummyTopologyView.java
@@ -30,6 +30,7 @@ import java.util.UUID;
import org.apache.sling.discovery.ClusterView;
import org.apache.sling.discovery.InstanceDescription;
import org.apache.sling.discovery.InstanceFilter;
+import org.apache.sling.discovery.commons.providers.spi.LocalClusterView;
public class DummyTopologyView extends BaseTopologyView {
@@ -199,7 +200,15 @@ public class DummyTopologyView extends BaseTopologyView {
String clusterId = id.getClusterView().getId();
DefaultClusterView cluster = clusters.get(clusterId);
if (cluster==null) {
- cluster = new DefaultClusterView(clusterId);
+ final ClusterView origCluster = id.getClusterView();
+ if (origCluster instanceof LocalClusterView) {
+ final LocalClusterView localOrigCluster =
(LocalClusterView) origCluster;
+ final LocalClusterView clonedCluster = new
LocalClusterView(origCluster.getId(),
localOrigCluster.getLocalClusterSyncTokenId());
+
clonedCluster.setPartiallyStartedClusterNodeIds(localOrigCluster.getPartiallyStartedClusterNodeIds());
+ cluster = clonedCluster;
+ } else {
+ cluster = new DefaultClusterView(clusterId);
+ }
clusters.put(clusterId, cluster);
}
DefaultInstanceDescription clone = clone(cluster, id);
diff --git
a/src/test/java/org/apache/sling/discovery/commons/providers/base/TestViewStateManager.java
b/src/test/java/org/apache/sling/discovery/commons/providers/base/TestViewStateManager.java
index 3b29202..c48a115 100644
---
a/src/test/java/org/apache/sling/discovery/commons/providers/base/TestViewStateManager.java
+++
b/src/test/java/org/apache/sling/discovery/commons/providers/base/TestViewStateManager.java
@@ -21,12 +21,15 @@ package org.apache.sling.discovery.commons.providers.base;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.Semaphore;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@@ -43,6 +46,7 @@ import
org.apache.sling.discovery.commons.providers.DefaultInstanceDescription;
import org.apache.sling.discovery.commons.providers.DummyTopologyView;
import org.apache.sling.discovery.commons.providers.EventHelper;
import org.apache.sling.discovery.commons.providers.spi.ClusterSyncService;
+import org.apache.sling.discovery.commons.providers.spi.LocalClusterView;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -716,4 +720,173 @@ public class TestViewStateManager {
assertFalse(mgr.onlyDiffersInProperties(view6));
}
+ @Test
+ public void testSuppression_withDelay() throws Exception {
+ doTestSuppression(true);
+ }
+
+ @Test
+ public void testSuppression_withoutDelay() throws Exception {
+ doTestSuppression(false);
+ }
+
+ private void doTestSuppression(boolean minEventDelayHelepr) throws
Exception {
+ logger.info("testSuppression: start");
+
+ final AtomicReference<TopologyView> topologyRef = new
AtomicReference<>();
+ if (minEventDelayHelepr) {
+ mgr.installMinEventDelayHandler(new DiscoveryService() {
+
+ @Override
+ public TopologyView getTopology() {
+ return topologyRef.get();
+ }
+ }, new DummyScheduler(), 1);
+ }
+
+ final String slingId1 = UUID.randomUUID().toString();
+ final String slingId2 = UUID.randomUUID().toString();
+ final String clusterId = UUID.randomUUID().toString();
+ final String syncToken1 = "s1";
+ final LocalClusterView cluster1 = new LocalClusterView(clusterId,
syncToken1);
+ final DummyTopologyView view1 = new DummyTopologyView(syncToken1)
+ .addInstance(slingId1, cluster1, true, true);
+ final String syncToken4 = "s4";
+ final LocalClusterView cluster4 = new LocalClusterView(clusterId,
syncToken4);
+ final DummyTopologyView view4 = new DummyTopologyView(syncToken4)
+ .addInstance(slingId1, cluster4, true, true)
+ .addInstance(slingId2, cluster4, false, false);
+
+ final DummyListener listener = new DummyListener();
+ mgr.bind(listener);
+ TestHelper.assertNoEvents(listener);
+ mgr.handleActivated();
+ TestHelper.assertNoEvents(listener);
+
+ logger.info("testSuppression: handleNewView(view1)");
+ mgr.handleNewView(view1);
+ topologyRef.set(view1);
+ assertEquals(0, mgr.waitForAsyncEvents(5000));
+ TopologyEvent initEvent = EventHelper.newInitEvent(view1);
+ assertEvents(listener, initEvent);
+
+ // for a view change to "go undetected" ie not trigger any topology
change,
+ // the list of instances must remain the same
+ // (but the syncToken can differ and it can have suppressed
clusterNodeIds)
+ for(int i = 0; i < 100; i++) {
+ final String syncToken2SameAsS1 = "s1";
+ final LocalClusterView cluster1Suppressed = new
LocalClusterView(clusterId, syncToken2SameAsS1);
+ final DummyTopologyView view1Suppressed = new
DummyTopologyView(syncToken2SameAsS1)
+ .addInstance(slingId1, cluster1Suppressed, true, true);
+
cluster1Suppressed.setPartiallyStartedClusterNodeIds(Arrays.asList(1));
+ logger.info("testSuppression: handleNewView(view2[a])");
+ mgr.handleNewView(view1Suppressed);
+ topologyRef.set(view1Suppressed);
+ assertEquals(0, mgr.waitForAsyncEvents(5000));
+ TestHelper.assertNoEvents(listener);
+ }
+ for(int i = 0; i < 100; i++) {
+ final String syncToken2Different = "s1Suppressed";
+ final LocalClusterView cluster1Suppressed = new
LocalClusterView(clusterId, syncToken2Different);
+ final DummyTopologyView view1Suppressed = new
DummyTopologyView(syncToken2Different)
+ .addInstance(slingId1, cluster1Suppressed, true, true);
+
cluster1Suppressed.setPartiallyStartedClusterNodeIds(Arrays.asList(1));
+ logger.info("testSuppression: handleNewView(view2[b])");
+ mgr.handleNewView(view1Suppressed);
+ topologyRef.set(view1Suppressed);
+ assertEquals(0, mgr.waitForAsyncEvents(5000));
+ TestHelper.assertNoEvents(listener);
+ }
+
+ logger.info("testSuppression: handleNewView(view4)");
+ mgr.handleNewView(view4);
+ topologyRef.set(view4);
+ assertEquals(0, mgr.waitForAsyncEvents(5000));
+ assertEvents(listener, EventHelper.newChangingEvent(view1),
EventHelper.newChangedEvent(view1, view4));
+ }
+
+ @Test
+ public void testEqualsIgnoreSyncToken() throws Exception {
+ // no previous view, so returns false
+ assertFalse(mgr.equalsIgnoreSyncToken(null));
+
+ final String clusterId = UUID.randomUUID().toString();
+ final String syncToken1 = "s1";
+ final DummyTopologyView view1 = createTopology(clusterId, syncToken1,
1);
+ assertTrue(mgr.handleNewViewNonDelayed(view1));
+ try {
+ mgr.equalsIgnoreSyncToken(null);
+ fail("should have thrown a NPE");
+ } catch(RuntimeException e) {
+ // ok
+ }
+ assertTrue(mgr.equalsIgnoreSyncToken(view1));
+
+ DummyTopologyView view;
+ for(int i = 1; i < 10; i++) {
+ // same instances, same syncToken, no partiallyStartedInstances =>
true
+ view = createTopology(view1, syncToken1, 0);
+ assertTrue(mgr.equalsIgnoreSyncToken(view));
+ // same instances, same syncToken, with partiallyStartedInstances
=> true
+ addPartiallyStartedInstance(view, 1);
+ assertTrue(mgr.equalsIgnoreSyncToken(view));
+
+ // different instances, same syncToken, no
partiallyStartedInstances => false
+ view = createTopology(view1, syncToken1, i);
+ assertFalse(mgr.equalsIgnoreSyncToken(view));
+ // different instances, same syncToken, with
partiallyStartedInstances => false
+ addPartiallyStartedInstance(view, 1);
+ assertFalse(mgr.equalsIgnoreSyncToken(view));
+
+ // same instances, different syncToken, no
partiallyStartedInstances => false
+ final String differentSyncToken = "s2";
+ view = createTopology(view1, differentSyncToken, 0);
+ assertFalse(mgr.equalsIgnoreSyncToken(view));
+ // same instances, different syncToken, with
partiallyStartedInstances => true
+ view = createTopology(view1, differentSyncToken, 0);
+ addPartiallyStartedInstance(view, 1);
+ assertTrue(mgr.equalsIgnoreSyncToken(view));
+
+ // different instances, different syncToken, no
partiallyStartedInstances => false
+ view = createTopology(view1, differentSyncToken, i);
+ assertFalse(mgr.equalsIgnoreSyncToken(view));
+ // different instances, different syncToken, with
partiallyStartedInstances => false
+ view = createTopology(view1, differentSyncToken, i);
+ addPartiallyStartedInstance(view, 1);
+ assertFalse(mgr.equalsIgnoreSyncToken(view));
+ }
+ }
+
+ private void addPartiallyStartedInstance(TopologyView view, Integer...
clusterNodeIds) {
+ LocalClusterView local = (LocalClusterView)
view.getLocalInstance().getClusterView();
+ local.setPartiallyStartedClusterNodeIds(Arrays.asList(clusterNodeIds));
+ }
+
+ private DummyTopologyView createTopology(String clusterId, String
syncToken, int numInstances) {
+ final LocalClusterView cluster = new LocalClusterView(clusterId,
syncToken);
+ final DummyTopologyView view = new DummyTopologyView(syncToken);
+ for(int i = 0; i < numInstances; i++) {
+ view.addInstance(UUID.randomUUID().toString(), cluster, i == 0, i
== 0);
+ }
+ return view;
+ }
+
+ private DummyTopologyView createTopology(DummyTopologyView base, String
syncToken,
+ int numAdditionalInstances) {
+ return createTopology(base.getLocalInstance().getClusterView(),
syncToken,
+ numAdditionalInstances);
+ }
+
+ private DummyTopologyView createTopology(ClusterView baseCluster, String
syncToken,
+ int numAdditionalInstances) {
+ final LocalClusterView cluster = new
LocalClusterView(baseCluster.getId(), syncToken);
+ final DummyTopologyView view2 = new DummyTopologyView(syncToken);
+ for (InstanceDescription inst : baseCluster.getInstances()) {
+ view2.addInstance(inst.getSlingId(), cluster, inst.isLeader(),
inst.isLocal());
+ }
+ for (int i = 0; i < numAdditionalInstances; i++) {
+ view2.addInstance(UUID.randomUUID().toString(), cluster, false,
false);
+ }
+ return view2;
+ }
}
\ No newline at end of file
diff --git
a/src/test/java/org/apache/sling/discovery/commons/providers/spi/LocalClusterViewTest.java
b/src/test/java/org/apache/sling/discovery/commons/providers/spi/LocalClusterViewTest.java
new file mode 100644
index 0000000..243f0db
--- /dev/null
+++
b/src/test/java/org/apache/sling/discovery/commons/providers/spi/LocalClusterViewTest.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.discovery.commons.providers.spi;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class LocalClusterViewTest {
+
+ private final LocalClusterView view = new LocalClusterView("id", "token");
+
+ @Test
+ public void partiallyStarted() {
+ assertFalse(view.hasPartiallyStartedInstances());
+ assertFalse(view.isPartiallyStarted(42));
+
+
view.setPartiallyStartedClusterNodeIds(Collections.<Integer>emptyList());
+ assertFalse(view.hasPartiallyStartedInstances());
+ assertFalse(view.isPartiallyStarted(42));
+
+ view.setPartiallyStartedClusterNodeIds(Arrays.asList(1, 2, 3));
+ assertTrue(view.hasPartiallyStartedInstances());
+ assertFalse(view.isPartiallyStarted(42));
+ assertTrue(view.isPartiallyStarted(1));
+ assertFalse(view.isPartiallyStarted(null));
+ }
+}
diff --git
a/src/test/java/org/apache/sling/discovery/commons/providers/spi/base/TestOakSyncTokenService.java
b/src/test/java/org/apache/sling/discovery/commons/providers/spi/base/TestOakSyncTokenService.java
index ce8b31a..6f99021 100644
---
a/src/test/java/org/apache/sling/discovery/commons/providers/spi/base/TestOakSyncTokenService.java
+++
b/src/test/java/org/apache/sling/discovery/commons/providers/spi/base/TestOakSyncTokenService.java
@@ -24,6 +24,7 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.lang.reflect.Field;
+import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@@ -36,11 +37,11 @@ import
org.apache.sling.discovery.commons.providers.ViewStateManager;
import org.apache.sling.discovery.commons.providers.base.DummyListener;
import org.apache.sling.discovery.commons.providers.base.TestHelper;
import
org.apache.sling.discovery.commons.providers.base.ViewStateManagerFactory;
+import org.apache.sling.discovery.commons.providers.spi.LocalClusterView;
import
org.apache.sling.discovery.commons.providers.spi.base.AbstractServiceWithBackgroundCheck.BackgroundCheckRunnable;
import org.apache.sling.jcr.api.SlingRepository;
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -223,4 +224,85 @@ public class TestOakSyncTokenService {
Object backgroundCheckRunnable = field.get(idMapService);
return (BackgroundCheckRunnable) backgroundCheckRunnable;
}
+
+ @Test
+ public void testPartiallyStartedInstance() throws Exception {
+ logger.info("testPartiallyStartedInstance: start");
+ OakBacklogClusterSyncService cs =
OakBacklogClusterSyncService.testConstructorAndActivate(new
SimpleCommonsConfig(), idMapService1, new DummySlingSettingsService(slingId1),
factory1);
+ Lock lock = new ReentrantLock();
+ ViewStateManager vsm =
ViewStateManagerFactory.newViewStateManager(lock, cs);
+ DummyListener l = new DummyListener();
+ vsm.bind(l);
+ vsm.handleActivated();
+
+ final DummyTopologyView view1 = TestHelper.newView(true, slingId1,
slingId1, slingId1);
+ {
+ // simulate a view with just itself (slingId1 / 1)
+ vsm.handleNewView(view1);
+ cs.triggerBackgroundCheck();
+ assertEquals(0, vsm.waitForAsyncEvents(1000));
+ assertEquals(0, l.countEvents());
+ DescriptorHelper.setDiscoveryLiteDescriptor(factory1, new
DiscoveryLiteDescriptorBuilder().me(1).seq(1).activeIds(1).setFinal(true));
+ assertTrue(idMapService1.waitForInit(5000));
+ cs.triggerBackgroundCheck();
+ assertEquals(0, vsm.waitForAsyncEvents(1000));
+ assertEquals(1, l.countEvents());
+ }
+
+ assertTrue(idMapService1.waitForInit(5000));
+
+ {
+ // simulate a new instance coming up - first it will show up in
oak leases/lite-view
+ DescriptorHelper.setDiscoveryLiteDescriptor(factory1, new
DiscoveryLiteDescriptorBuilder().me(1).seq(2).activeIds(1, 2).setFinal(true));
+
+ // the view is still the same (only contains slingId1) - but it
has the flag 'partial' set
+ final String syncToken2 = "s2";
+ final String clusterId =
view1.getLocalInstance().getClusterView().getId();
+ final LocalClusterView cluster1Suppressed = new
LocalClusterView(clusterId, syncToken2);
+ final DummyTopologyView view1Suppressed = new
DummyTopologyView(syncToken2)
+ .addInstance(slingId1, cluster1Suppressed, true, true);
+
cluster1Suppressed.setPartiallyStartedClusterNodeIds(Arrays.asList(2));
+
+ vsm.handleNewView(view1Suppressed);
+ cs.triggerBackgroundCheck();
+ assertEquals(0, vsm.waitForAsyncEvents(1000));
+ assertEquals(1, l.countEvents());
+ }
+ final String slingId2 = UUID.randomUUID().toString();
+ {
+ // now define slingId for activeId == 2
+ IdMapService idMapService2 = IdMapService.testConstructor(
+ new SimpleCommonsConfig(), new
DummySlingSettingsService(slingId2), factory2);
+ DescriptorHelper.setDiscoveryLiteDescriptor(factory2, new
DiscoveryLiteDescriptorBuilder().setFinal(true).me(2).seq(2).activeIds(1, 2));
+ assertTrue(idMapService2.waitForInit(5000));
+ }
+ {
+ // now that shouldn't have triggered anything yet towards the
listeners
+ final String syncToken2 = "s2";
+ final String clusterId =
view1.getLocalInstance().getClusterView().getId();
+ final LocalClusterView cluster1Suppressed = new
LocalClusterView(clusterId, syncToken2);
+ final DummyTopologyView view1Suppressed = new
DummyTopologyView(syncToken2)
+ .addInstance(slingId1, cluster1Suppressed, true, true);
+
cluster1Suppressed.setPartiallyStartedClusterNodeIds(Arrays.asList(2));
+
+ vsm.handleNewView(view1Suppressed);
+ cs.triggerBackgroundCheck();
+ assertEquals(0, vsm.waitForAsyncEvents(1000));
+ assertEquals(1, l.countEvents());
+ }
+ {
+ // now let's finish slingId2 startup - only this should trigger
CHANGING/CHANGED
+ final String syncToken2 = "s2";
+ final String clusterId =
view1.getLocalInstance().getClusterView().getId();
+ final LocalClusterView cluster1Suppressed = new
LocalClusterView(clusterId, syncToken2);
+ final DummyTopologyView view2 = new DummyTopologyView(syncToken2)
+ .addInstance(slingId1, cluster1Suppressed, true, true)
+ .addInstance(slingId2, cluster1Suppressed, false, false);
+ vsm.handleNewView(view2);
+ cs.triggerBackgroundCheck();
+ assertEquals(0, vsm.waitForAsyncEvents(1000));
+ assertEquals(3, l.countEvents());
+ }
+ logger.info("testPartiallyStartedInstance: end");
+ }
}