Skip to content

Commit

Permalink
Merge pull request #65 from xenit-eu/ALFREDOPS-864-fix-broken-integra…
Browse files Browse the repository at this point in the history
…tion-tests

ALFREDOPS-864: fix broken integration tests
  • Loading branch information
pvriel authored Jan 9, 2025
2 parents 65d7d26 + eced407 commit 3dd8d5d
Show file tree
Hide file tree
Showing 14 changed files with 216 additions and 188 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ Version template:

# Alfresco Health Processor Changelog

## [0.6.1] - 2025-01-08

### Fixed
* Fixed a bug that caused the OOTB admin console to crash the health processor web scripts from the integration tests.

## [0.6.0] - 2025-01-07

### Added
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
package eu.xenit.alfresco.healthprocessor.fixer.api;

import lombok.Getter;
import lombok.Setter;

/**
* {@link HealthFixerPlugin} that already has an <code>enabled</code> property
*
* @since 0.5.0
*/
public abstract class ToggleableHealthFixerPlugin implements HealthFixerPlugin {
public interface ToggleableHealthFixerPlugin extends HealthFixerPlugin {

@Getter
@Setter
private boolean enabled;
void setEnabled(boolean status);

}
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,13 @@
<bean class="eu.xenit.alfresco.healthprocessor.plugins.solr.filter.DeletedNodeFilter" />

<bean id="eu.xenit.alfresco.healthprocessor.fixer.solr.SolrMissingNodeFixerPlugin"
class="eu.xenit.alfresco.healthprocessor.fixer.solr.SolrMissingNodeFixerPlugin">
class="eu.xenit.alfresco.healthprocessor.fixer.solr.SolrMissingNodeFixerPluginImpl">
<property name="enabled" value="${eu.xenit.alfresco.healthprocessor.fixer.solr-missing-node.enabled}" />
<constructor-arg name="solrRequestExecutor"
ref="eu.xenit.alfresco.healthprocessor.plugins.solr.SolrRequestExecutor" />
</bean>
<bean id="eu.xenit.alfresco.healthprocessor.fixer.solr.SolrDuplicateNodeFixerPlugin"
class="eu.xenit.alfresco.healthprocessor.fixer.solr.SolrDuplicateNodeFixerPlugin">
class="eu.xenit.alfresco.healthprocessor.fixer.solr.SolrDuplicateNodeFixerPluginImpl">
<property name="enabled" value="${eu.xenit.alfresco.healthprocessor.fixer.solr-duplicate-node.enabled}" />
<constructor-arg name="solrRequestExecutor"
ref="eu.xenit.alfresco.healthprocessor.plugins.solr.SolrRequestExecutor" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,17 @@
import java.util.Set;
import javax.annotation.Nonnull;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

@AllArgsConstructor
@RequiredArgsConstructor
@Slf4j
abstract class AbstractSolrNodeFixerPlugin extends ToggleableHealthFixerPlugin {
abstract class AbstractSolrNodeFixerPlugin implements ToggleableHealthFixerPlugin {

private final SolrRequestExecutor solrRequestExecutor;
private @Getter @Setter boolean enabled;

@Nonnull
@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,42 +1,18 @@
package eu.xenit.alfresco.healthprocessor.fixer.solr;

import eu.xenit.alfresco.healthprocessor.fixer.api.NodeFixReport;
import eu.xenit.alfresco.healthprocessor.fixer.api.NodeFixStatus;
import eu.xenit.alfresco.healthprocessor.plugins.solr.NodeIndexHealthReport;
import eu.xenit.alfresco.healthprocessor.plugins.solr.NodeIndexHealthReport.IndexHealthStatus;
import eu.xenit.alfresco.healthprocessor.plugins.solr.SolrRequestExecutor;
import eu.xenit.alfresco.healthprocessor.plugins.solr.SolrRequestExecutor.SolrNodeCommand;
import eu.xenit.alfresco.healthprocessor.reporter.api.NodeHealthReport;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import eu.xenit.alfresco.healthprocessor.fixer.api.ToggleableHealthFixerPlugin;

public class SolrDuplicateNodeFixerPlugin extends AbstractSolrNodeFixerPlugin {
/**
* <p>
* Interface representation of the {@link SolrDuplicateNodeFixerPluginImpl} class.
* </p>
* <p>
* The reason for tbe existence this interface is to allow the {@link org.alfresco.repo.management.subsystems.SubsystemProxyFactory}
* to create a proxy of the {@link SolrDuplicateNodeFixerPluginImpl} class from the health processor application context
* in the main application context.
* This proxy is only used as part of the integration tests from the health processor repo.
* </p>
*/
public interface SolrDuplicateNodeFixerPlugin extends ToggleableHealthFixerPlugin {

public SolrDuplicateNodeFixerPlugin(SolrRequestExecutor solrRequestExecutor) {
super(solrRequestExecutor);
}

@Override
protected Set<NodeFixReport> handleHealthReport(NodeHealthReport unhealthyReport,
NodeIndexHealthReport endpointHealthReport) {
if (endpointHealthReport.getHealthStatus() != IndexHealthStatus.DUPLICATE) {
return Collections.emptySet();
}
// When a duplicate node is detected, purge it from the index and reindex it
// According to MetadataTracker#maintenance(), purge is processed before reindex
// Even if it is executed in the same maintenance cycle.
// Ref: https://github.com/Alfresco/SearchServices/blob/e7f05e2f13a709cd28afa3ae6acfd3d0000b22ff/search-services/alfresco-search/src/main/java/org/alfresco/solr/tracker/MetadataTracker.java#L257-L266
// Purge has to be done before reindex, else we end up with a broken index which will only be fixed
// by a subsequent health processor cycle, which would be unacceptable.
Set<NodeFixReport> fixReports = new HashSet<>();
NodeFixReport purgeStatus = trySendSolrCommand(unhealthyReport, endpointHealthReport, SolrNodeCommand.PURGE);
fixReports.add(purgeStatus);
if(purgeStatus.getFixStatus() == NodeFixStatus.SUCCEEDED) {
fixReports.add(trySendSolrCommand(unhealthyReport, endpointHealthReport, SolrNodeCommand.REINDEX));
}

return fixReports;
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package eu.xenit.alfresco.healthprocessor.fixer.solr;

import eu.xenit.alfresco.healthprocessor.fixer.api.NodeFixReport;
import eu.xenit.alfresco.healthprocessor.fixer.api.NodeFixStatus;
import eu.xenit.alfresco.healthprocessor.plugins.solr.NodeIndexHealthReport;
import eu.xenit.alfresco.healthprocessor.plugins.solr.NodeIndexHealthReport.IndexHealthStatus;
import eu.xenit.alfresco.healthprocessor.plugins.solr.SolrRequestExecutor;
import eu.xenit.alfresco.healthprocessor.plugins.solr.SolrRequestExecutor.SolrNodeCommand;
import eu.xenit.alfresco.healthprocessor.reporter.api.NodeHealthReport;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

public class SolrDuplicateNodeFixerPluginImpl extends AbstractSolrNodeFixerPlugin implements SolrDuplicateNodeFixerPlugin {

public SolrDuplicateNodeFixerPluginImpl(SolrRequestExecutor solrRequestExecutor) {
super(solrRequestExecutor);
}

@Override
protected Set<NodeFixReport> handleHealthReport(NodeHealthReport unhealthyReport,
NodeIndexHealthReport endpointHealthReport) {
if (endpointHealthReport.getHealthStatus() != IndexHealthStatus.DUPLICATE) {
return Collections.emptySet();
}
// When a duplicate node is detected, purge it from the index and reindex it
// According to MetadataTracker#maintenance(), purge is processed before reindex
// Even if it is executed in the same maintenance cycle.
// Ref: https://github.com/Alfresco/SearchServices/blob/e7f05e2f13a709cd28afa3ae6acfd3d0000b22ff/search-services/alfresco-search/src/main/java/org/alfresco/solr/tracker/MetadataTracker.java#L257-L266
// Purge has to be done before reindex, else we end up with a broken index which will only be fixed
// by a subsequent health processor cycle, which would be unacceptable.
Set<NodeFixReport> fixReports = new HashSet<>();
NodeFixReport purgeStatus = trySendSolrCommand(unhealthyReport, endpointHealthReport, SolrNodeCommand.PURGE);
fixReports.add(purgeStatus);
if(purgeStatus.getFixStatus() == NodeFixStatus.SUCCEEDED) {
fixReports.add(trySendSolrCommand(unhealthyReport, endpointHealthReport, SolrNodeCommand.REINDEX));
}

return fixReports;
}

}
Original file line number Diff line number Diff line change
@@ -1,78 +1,18 @@
package eu.xenit.alfresco.healthprocessor.fixer.solr;

import eu.xenit.alfresco.healthprocessor.fixer.api.NodeFixReport;
import eu.xenit.alfresco.healthprocessor.fixer.api.NodeFixStatus;
import eu.xenit.alfresco.healthprocessor.plugins.api.HealthProcessorPlugin;
import eu.xenit.alfresco.healthprocessor.plugins.solr.NodeIndexHealthReport;
import eu.xenit.alfresco.healthprocessor.plugins.solr.NodeIndexHealthReport.IndexHealthStatus;
import eu.xenit.alfresco.healthprocessor.plugins.solr.SolrRequestExecutor;
import eu.xenit.alfresco.healthprocessor.plugins.solr.SolrRequestExecutor.SolrNodeCommand;
import eu.xenit.alfresco.healthprocessor.plugins.solr.endpoint.SearchEndpoint;
import eu.xenit.alfresco.healthprocessor.reporter.api.NodeHealthReport;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
import org.alfresco.service.cmr.repository.NodeRef;

import javax.annotation.Nonnull;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

@Slf4j
public class SolrMissingNodeFixerPlugin extends AbstractSolrNodeFixerPlugin {
private Map<SearchEndpointTxId, NodeFixReport> searchEndpointTxCache = new HashMap<>();

public SolrMissingNodeFixerPlugin(SolrRequestExecutor solrRequestExecutor) {
super(solrRequestExecutor);
}

@Nonnull
@Override
public Set<NodeFixReport> fix(Class<? extends HealthProcessorPlugin> pluginClass,
Set<NodeHealthReport> unhealthyReports) {
clearCache();
return super.fix(pluginClass, unhealthyReports);
}

@Override
protected Set<NodeFixReport> handleHealthReport(NodeHealthReport unhealthyReport,
NodeIndexHealthReport endpointHealthReport) {
if (endpointHealthReport.getHealthStatus() != IndexHealthStatus.NOT_FOUND) {
return Collections.emptySet();
}

NodeRef.Status nodeStatus = endpointHealthReport.getNodeRefStatus();
SearchEndpointTxId searchEndpointTxId = new SearchEndpointTxId(endpointHealthReport.getEndpoint(), nodeStatus.getDbTxnId());
if (searchEndpointTxCache.containsKey(searchEndpointTxId)) {
NodeFixReport cachedNodeFixReport = searchEndpointTxCache.get(searchEndpointTxId);
log.trace("We already have a fix report for {}: {}", searchEndpointTxId, cachedNodeFixReport);
//If a successful reindex action was already sent for this tx to this endpoint, do not schedule another one
if (cachedNodeFixReport.getFixStatus() == NodeFixStatus.SUCCEEDED) {
log.trace("Fix for TX of {} has already succeeded, sending existing fix report messages.", unhealthyReport);
return Collections.singleton(new NodeFixReport(cachedNodeFixReport.getFixStatus(), unhealthyReport,
cachedNodeFixReport.getMessages()));
}
}

log.trace("Performing reindex for {}", searchEndpointTxId);

// Action not yet (successfully) sent
NodeFixReport nodeFixReport = trySendSolrCommand(unhealthyReport, endpointHealthReport,
SolrNodeCommand.REINDEX_TRANSACTION);

searchEndpointTxCache.put(searchEndpointTxId, nodeFixReport);
return Collections.singleton(nodeFixReport);
}

@Value
public static class SearchEndpointTxId {
private final SearchEndpoint searchEndpoint;
private final Long txId;
}

private void clearCache() {
searchEndpointTxCache.clear();
}
import eu.xenit.alfresco.healthprocessor.fixer.api.ToggleableHealthFixerPlugin;

/**
* <p>
* Interface representation of the {@link SolrMissingNodeFixerPluginImpl} class.
* </p>
* <p>
* The reason for tbe existence this interface is to allow the {@link org.alfresco.repo.management.subsystems.SubsystemProxyFactory}
* to create a proxy of the {@link SolrMissingNodeFixerPluginImpl} class from the health processor application context
* in the main application context.
* This proxy is only used as part of the integration tests from the health processor repo.
* </p>
*/
public interface SolrMissingNodeFixerPlugin extends ToggleableHealthFixerPlugin {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package eu.xenit.alfresco.healthprocessor.fixer.solr;

import eu.xenit.alfresco.healthprocessor.fixer.api.NodeFixReport;
import eu.xenit.alfresco.healthprocessor.fixer.api.NodeFixStatus;
import eu.xenit.alfresco.healthprocessor.plugins.api.HealthProcessorPlugin;
import eu.xenit.alfresco.healthprocessor.plugins.solr.NodeIndexHealthReport;
import eu.xenit.alfresco.healthprocessor.plugins.solr.NodeIndexHealthReport.IndexHealthStatus;
import eu.xenit.alfresco.healthprocessor.plugins.solr.SolrRequestExecutor;
import eu.xenit.alfresco.healthprocessor.plugins.solr.SolrRequestExecutor.SolrNodeCommand;
import eu.xenit.alfresco.healthprocessor.plugins.solr.endpoint.SearchEndpoint;
import eu.xenit.alfresco.healthprocessor.reporter.api.NodeHealthReport;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
import org.alfresco.service.cmr.repository.NodeRef;

import javax.annotation.Nonnull;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

@Slf4j
public class SolrMissingNodeFixerPluginImpl extends AbstractSolrNodeFixerPlugin implements SolrMissingNodeFixerPlugin {

private Map<SearchEndpointTxId, NodeFixReport> searchEndpointTxCache = new HashMap<>();

public SolrMissingNodeFixerPluginImpl(SolrRequestExecutor solrRequestExecutor) {
super(solrRequestExecutor);
}

@Nonnull
@Override
public Set<NodeFixReport> fix(Class<? extends HealthProcessorPlugin> pluginClass,
Set<NodeHealthReport> unhealthyReports) {
clearCache();
return super.fix(pluginClass, unhealthyReports);
}

@Override
protected Set<NodeFixReport> handleHealthReport(NodeHealthReport unhealthyReport,
NodeIndexHealthReport endpointHealthReport) {
if (endpointHealthReport.getHealthStatus() != IndexHealthStatus.NOT_FOUND) {
return Collections.emptySet();
}

NodeRef.Status nodeStatus = endpointHealthReport.getNodeRefStatus();
SearchEndpointTxId searchEndpointTxId = new SearchEndpointTxId(endpointHealthReport.getEndpoint(), nodeStatus.getDbTxnId());
if (searchEndpointTxCache.containsKey(searchEndpointTxId)) {
NodeFixReport cachedNodeFixReport = searchEndpointTxCache.get(searchEndpointTxId);
log.trace("We already have a fix report for {}: {}", searchEndpointTxId, cachedNodeFixReport);
//If a successful reindex action was already sent for this tx to this endpoint, do not schedule another one
if (cachedNodeFixReport.getFixStatus() == NodeFixStatus.SUCCEEDED) {
log.trace("Fix for TX of {} has already succeeded, sending existing fix report messages.", unhealthyReport);
return Collections.singleton(new NodeFixReport(cachedNodeFixReport.getFixStatus(), unhealthyReport,
cachedNodeFixReport.getMessages()));
}
}

log.trace("Performing reindex for {}", searchEndpointTxId);

// Action not yet (successfully) sent
NodeFixReport nodeFixReport = trySendSolrCommand(unhealthyReport, endpointHealthReport,
SolrNodeCommand.REINDEX_TRANSACTION);

searchEndpointTxCache.put(searchEndpointTxId, nodeFixReport);
return Collections.singleton(nodeFixReport);
}

@Value
public static class SearchEndpointTxId {
private final SearchEndpoint searchEndpoint;
private final Long txId;
}

private void clearCache() {
searchEndpointTxCache.clear();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@
import org.mockito.InOrder;
import org.mockito.Mockito;

class SolrDuplicateNodeFixerPluginTest {
class SolrDuplicateNodeFixerPluginImplTest {

private SolrRequestExecutor executor;
private SolrDuplicateNodeFixerPlugin duplicateNodeFixerPlugin;
private SolrDuplicateNodeFixerPluginImpl duplicateNodeFixerPlugin;

@BeforeEach
void setup() {
executor = mock(SolrRequestExecutor.class);
duplicateNodeFixerPlugin = new SolrDuplicateNodeFixerPlugin(executor);
duplicateNodeFixerPlugin = new SolrDuplicateNodeFixerPluginImpl(executor);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

class SolrMissingNodeFixerPluginTest {
class SolrMissingNodeFixerPluginImplTest {

private SolrRequestExecutor executor;
private SolrMissingNodeFixerPlugin missingNodeFixerPlugin;
private SolrMissingNodeFixerPluginImpl missingNodeFixerPlugin;

@BeforeEach
void setup() {
executor = mock(SolrRequestExecutor.class);
missingNodeFixerPlugin = new SolrMissingNodeFixerPlugin(executor);
missingNodeFixerPlugin = new SolrMissingNodeFixerPluginImpl(executor);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public Optional<Duration> getEstimatedCompletion() {

CycleProgressView indexingProgressView = new CycleProgressView(mockIndexingProgress);

assertThat(indexingProgressView.getProgress(), is(equalTo("10.00%")));
assertThat(indexingProgressView.getProgress().replaceAll(",", "."), is(equalTo("10.00%")));
}

@Test
Expand Down
Loading

0 comments on commit 3dd8d5d

Please sign in to comment.